Commit 9307b6eb authored by 崔为之's avatar 崔为之 💪🏽

Update project

parent 1c58b6e6
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
# @Version : Python 3.11.4
# @Software : Sublime Text 4
# @Author : StudentCWZ
# @Email : StudentCWZ@outlook.com
# @Date : 2023/11/3 19:42
# @File : __init__.py
# @Description :
"""
from .config import ConfigHelper
from .file import FileHelper
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
# @Version : Python 3.11.4
# @Software : Sublime Text 4
# @Author : StudentCWZ
# @Email : StudentCWZ@outlook.com
# @Date : 2023/11/3 19:43
# @File : config.py
# @Description :
"""
from typing import Any
from flask import Flask
class ConfigHelper:
"""
The ConfigHelper class is a utility for fetching configuration values
from a Flask application.
:param app: The Flask application instance from which to fetch configuration values.
"""
def __init__(self, app: Flask):
self.app = app
def get_config(self, key: str) -> Any:
"""
Fetch a config value based on the provided key.
:param key: The key for the config value.
:return: The value for the provided key.
"""
return self.app.config.get(key)
def __repr__(self):
return f"<ConfigHelper with app {self.app}>"
def __str__(self):
return f"ConfigHelper for app {self.app}"
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
# @Version : Python 3.11.4
# @Software : Sublime Text 4
# @Author : StudentCWZ
# @Email : StudentCWZ@outlook.com
# @Date : 2023/11/3 19:58
# @File : file.py
# @Description :
"""
class FileHelper:
"""
FileHelper is a utility class that provides file-related operations.
Currently, it only provides a method to get the configuration file name
based on the environment.
"""
@classmethod
def get_filename(cls, env: str) -> str:
"""
Get the configuration file name based on the environment.
:param env: a string representing the environment.
It should be 'PRODUCTION' or None.
:return: a string representing the configuration file name.
If env is 'PRODUCTION', return 'production_config.yaml'.
Otherwise, return 'config.yaml'.
"""
if env == 'PRODUCTION':
return f'{env.lower()}_config.yaml'
else:
return 'config.yaml'
def __repr__(self):
"""
Return a string representing a valid Python expression that could be used
to recreate the FileHelper object.
"""
return "FileHelper()"
def __str__(self):
"""
Return a human-readable string representation of the FileHelper object.
"""
return "This is a FileHelper class that helps with file related operations."
...@@ -23,7 +23,7 @@ class LocalConfig: ...@@ -23,7 +23,7 @@ class LocalConfig:
def load(self, filename: str) -> Any: def load(self, filename: str) -> Any:
filepath = os.path.join(self.config_dir, filename) filepath = os.path.join(self.config_dir, filename)
if not os.path.exists(filepath): if not os.path.exists(filepath):
raise FileNotFoundError(f"No such file or directory: '{filepath}'") raise FileNotFoundError(f'No such file or directory: {filepath}')
with open(filepath, 'r') as file: with open(filepath, 'r') as file:
cfg = yaml.safe_load(file) cfg = yaml.safe_load(file)
return cfg return cfg
...@@ -16,6 +16,8 @@ from elasticsearch import Elasticsearch ...@@ -16,6 +16,8 @@ from elasticsearch import Elasticsearch
from flask import current_app, Flask from flask import current_app, Flask
from loguru import logger from loguru import logger
from application.common import ConfigHelper
class FlaskElasticsearch: class FlaskElasticsearch:
def __init__(self, app=None): def __init__(self, app=None):
...@@ -42,7 +44,7 @@ class FlaskElasticsearch: ...@@ -42,7 +44,7 @@ class FlaskElasticsearch:
"""Lazy initialization of Elasticsearch connection on first use.""" """Lazy initialization of Elasticsearch connection on first use."""
ctx = current_app._get_current_object() ctx = current_app._get_current_object()
if ctx is not None: if ctx is not None:
if not hasattr(ctx, "elasticsearch"): if not hasattr(ctx, 'elasticsearch'):
cfg = self._get_config() cfg = self._get_config()
ctx.elasticsearch = Elasticsearch(**cfg) ctx.elasticsearch = Elasticsearch(**cfg)
if ctx.elasticsearch.ping(): if ctx.elasticsearch.ping():
...@@ -57,29 +59,20 @@ class FlaskElasticsearch: ...@@ -57,29 +59,20 @@ class FlaskElasticsearch:
"""Retrieves Elasticsearch configuration from the current Flask application context.""" """Retrieves Elasticsearch configuration from the current Flask application context."""
with current_app.app_context(): with current_app.app_context():
if current_app: if current_app:
# Retrieve configuration from current_app.config and return it config_helper = ConfigHelper(current_app)
host = current_app.config.Elasticsearch.Host if current_app.config.get( cfg = config_helper.get_config('Elasticsearch')
'Elasticsearch') is not None else 'localhost' if cfg is None:
raise KeyError('Key Elasticsearch is not defined')
port = int(current_app.config.Elasticsearch.Port) if current_app.config.get(
'Elasticsearch') is not None else 9200 host = cfg.Host or 'localhost'
port = int(cfg.Port) or 9200
user = current_app.config.Elasticsearch.User if current_app.config.get( user = cfg.User or None
'Elasticsearch') is not None else 'user' password = cfg.Password or None
use_ssl = cfg.UseSsl == 'True' or False
password = current_app.config.Elasticsearch.Password if current_app.config.get( verify_certs = cfg.VerifyCerts == 'False'
'Elasticsearch') is not None else 'password' ca_certs = cfg.CaCerts or None
use_ssl = current_app.config.Elasticsearch.UseSsl == 'True' if current_app.config.get( options = dict(
'Elasticsearch') is not None else False
verify_certs = current_app.config.Elasticsearch.VerifyCerts == 'False' if current_app.config.get(
'Elasticsearch') is not None else True
ca_certs = current_app.config.Elasticsearch.CaCerts if current_app.config.get(
'Elasticsearch') is not None else None
es_config = dict(
hosts=[{'host': host, 'port': port}], hosts=[{'host': host, 'port': port}],
http_auth=None if not user else (user, password), http_auth=None if not user else (user, password),
use_ssl=use_ssl, use_ssl=use_ssl,
...@@ -87,7 +80,7 @@ class FlaskElasticsearch: ...@@ -87,7 +80,7 @@ class FlaskElasticsearch:
ca_certs=ca_certs ca_certs=ca_certs
) )
return es_config return options
else: else:
logger.error('Attempted to access application configuration outside of application context.') logger.error('Attempted to access application configuration outside of application context.')
raise RuntimeError('Attempted to access application configuration outside of application context.') raise RuntimeError('Attempted to access application configuration outside of application context.')
...@@ -10,5 +10,5 @@ ...@@ -10,5 +10,5 @@
# @Description : # @Description :
""" """
from .dsn import dsn, DatabaseURI from .dsn import DatabaseURI
from .elasticsearch import ElasticsearchUtils from .elasticsearch import ElasticsearchUtils
...@@ -60,7 +60,7 @@ class DatabaseURI: ...@@ -60,7 +60,7 @@ class DatabaseURI:
return f'{self.db_type}://{self.username}:{self.password}@{self.host}:{self.port}/{self.db}' return f'{self.db_type}://{self.username}:{self.password}@{self.host}:{self.port}/{self.db}'
def __repr__(self): def __repr__(self):
return f"<DatabaseURI({self.db_type})>" return f'<DatabaseURI({self.db_type})>'
def __str__(self): def __str__(self):
return self.create() return self.create()
...@@ -22,11 +22,23 @@ from application.lib import ConsulConfig ...@@ -22,11 +22,23 @@ from application.lib import ConsulConfig
IDENTIFIER = "consul_loader" IDENTIFIER = "consul_loader"
class SourceMetadata(NamedTuple): def get_env_vars() -> dict:
loader: str return {
identifier: str 'host': os.environ.get('CONSUL_HOST', 'localhost'),
env: str 'port': os.environ.get('CONSUL_PORT', 8500),
merged: bool = False 'dc': os.environ.get('CONSUL_DC', 'dc1'),
'token': os.getenv('CONSUL_TOKEN'),
}
def parse_config(data: dict, key: str, obj: LazySettings) -> dict:
if key is not None:
data = data[key]
return {
key: parse_conf_data(value, tomlfy=True, box_settings=obj)
for key, value in data.items()
}
def load( def load(
...@@ -36,49 +48,32 @@ def load( ...@@ -36,49 +48,32 @@ def load(
key: str = None, key: str = None,
validate=False, validate=False,
) -> Union[bool, None]: ) -> Union[bool, None]:
consul_host = os.environ.get('CONSUL_HOST', 'localhost') env_vars = get_env_vars()
consul_port = os.environ.get('CONSUL_PORT', 8500)
consul_dc = os.environ.get('CONSUL_DC', 'dc1')
consul_token = os.getenv('CONSUL_TOKEN')
consul_key = os.getenv('CONSUL_KEY') consul_key = os.getenv('CONSUL_KEY')
# 没有对应环境变量,会进入下一个加载器
if consul_key is None: if consul_key is None:
return return
# 实例 ConsulConfig client = ConsulConfig(**env_vars)
client = ConsulConfig(host=consul_host, port=consul_port, token=consul_token, dc=consul_dc)
# 捕获异常
try: try:
data = client.get(key=consul_key) data = client.get(key=consul_key)
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
print(2)
# 连接错误后,则会进入下一个加载器
return return
except Exception as e: except Exception as e:
# 发生未知错误,才会抛出异常
raise RuntimeError(f'Unknown error: {e}') raise RuntimeError(f'Unknown error: {e}')
# 基于 key 获取所需配置
if key is not None:
data = data[key]
if env is None: if env is None:
return return
try: try:
# 获取 consul 注册中心的配置信息 result = parse_config(data, key, obj)
result = {
key: parse_conf_data(value, tomlfy=True, box_settings=obj)
for key, value in data.items()
}
except Exception as e: except Exception as e:
if silent: if silent:
return False return False
raise e raise e
else:
result['Consul'] = True result['Consul'] = True
# 将 result 配置写入 dynaconf 内置配置中
obj.update( obj.update(
result, result,
loader_identifier=IDENTIFIER, loader_identifier=IDENTIFIER,
......
...@@ -17,11 +17,48 @@ from typing import Union ...@@ -17,11 +17,48 @@ from typing import Union
from dynaconf.base import LazySettings from dynaconf.base import LazySettings
from dynaconf.utils.parse_conf import parse_conf_data from dynaconf.utils.parse_conf import parse_conf_data
from application.common import FileHelper
from application.lib import LocalConfig from application.lib import LocalConfig
IDENTIFIER = "yaml_loader" IDENTIFIER = "yaml_loader"
def load_config(local_cfg: LocalConfig, filename: str, key: str) -> dict:
"""
Load the configuration from a file.
:param local_cfg: a LocalConfig object for loading the config.
:param filename: a string representing the name of the config file.
:param key: a string representing the key to extract from the config. If it's None, return the whole config.
:return: a dict representing the loaded config.
"""
if key is None:
return local_cfg.load(filename)
else:
return local_cfg.load(filename)[key]
def parse_config(cfg: dict, env: str, obj: LazySettings) -> dict:
"""
Parse the configuration based on the environment.
:param cfg: a dict representing the loaded config.
:param env: a string representing the environment. It should be 'PRODUCTION' or None.
:param obj: a LazySettings object that the config will be applied to.
:return: a dict representing the parsed config.
"""
if cfg.get(env) is None:
return {
key: parse_conf_data(value, tomlfy=True, box_settings=obj)
for key, value in cfg.items()
}
else:
return {
key: parse_conf_data(value, tomlfy=True, box_settings=obj)
for key, value in cfg[env].items()
}
def load( def load(
obj: LazySettings, obj: LazySettings,
env: str = None, env: str = None,
...@@ -29,43 +66,32 @@ def load( ...@@ -29,43 +66,32 @@ def load(
key: str = None, key: str = None,
validate=False, validate=False,
) -> Union[bool, None]: ) -> Union[bool, None]:
# 判断是否已经加载 consul 配置,如果加载,则不再加载本地配置 """
Load and apply the configuration to a LazySettings object.
:param obj: a LazySettings object that the config will be applied to.
:param env: a string representing the environment. It should be 'PRODUCTION' or None.
:param silent: a bool indicating whether to suppress the KeyError when the key is not in the config.
:param key: a string representing the key to extract from the config. If it's None, apply the whole config.
:param validate: a bool indicating whether to validate the config after loading.
:return: None if the config is successfully applied, False if the key is not in the config and silent is True.
"""
if obj.get('Consul', False): if obj.get('Consul', False):
return return
# 获取本地配置文件目录
config_dir = os.path.join(str(Path(__file__).parent.parent.parent), 'config') config_dir = os.path.join(str(Path(__file__).parent.parent.parent), 'config')
# 实例化 LocalConfig 对象
local_cfg = LocalConfig(config_dir) local_cfg = LocalConfig(config_dir)
# 判断当前是否为生产环境,基于不同环境读取本地不同配置文件 filename = FileHelper.get_filename(env)
if env == 'PRODUCTION': cfg = load_config(local_cfg, filename, key)
filename = f'{env.lower()}_config.yaml'
else:
filename = 'config.yaml'
# 基于 key 获取所需配置
if key is None:
cfg = local_cfg.load(filename)
else:
cfg = local_cfg.load(filename)[key]
try: try:
# 将 cfg 配置写入 dynaconf 内置配置中 result = parse_config(cfg, env, obj)
if cfg.get(env) is None: except KeyError as e:
result = {
key: parse_conf_data(value, tomlfy=True, box_settings=obj)
for key, value in cfg.items()
}
else:
result = {
key: parse_conf_data(value, tomlfy=True, box_settings=obj)
for key, value in cfg[env].items()
}
except Exception as e:
if silent: if silent:
return False return False
raise e raise e
else:
if result: if result:
obj.update( obj.update(
result, result,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment