conf.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. # coding=utf-8
  2. """
  3. @project: MaxKB
  4. @Author:虎虎
  5. @file: conf.py
  6. @date:2025/4/11 16:58
  7. @desc:
  8. """
  9. import errno
  10. import logging
  11. import os
  12. import yaml
  13. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  14. PROJECT_DIR = os.path.dirname(BASE_DIR)
  15. logger = logging.getLogger('maxkb.conf')
  16. class Config(dict):
  17. defaults = {
  18. # 数据库相关配置
  19. "DB_HOST": "127.0.0.1",
  20. "DB_PORT": 5432,
  21. "DB_USER": "postgres",
  22. "DB_PASSWORD": "",
  23. "DB_ENGINE": "dj_db_conn_pool.backends.postgresql",
  24. "DB_MAX_OVERFLOW": 80,
  25. 'LOCAL_MODEL_HOST': '127.0.0.1',
  26. 'LOCAL_MODEL_PORT': '11636',
  27. 'LOCAL_MODEL_PROTOCOL': "http",
  28. 'LOCAL_MODEL_HOST_WORKER': 1,
  29. # 语言
  30. 'LANGUAGE_CODE': 'zh-CN',
  31. "DEBUG": False,
  32. # redis host
  33. 'REDIS_HOST': '127.0.0.1',
  34. # 端口
  35. 'REDIS_PORT': 6379,
  36. # 密码
  37. 'REDIS_PASSWORD': '',
  38. # 库
  39. 'REDIS_DB': 0,
  40. # 最大连接数
  41. 'REDIS_MAX_CONNECTIONS': 100
  42. }
  43. def get_debug(self) -> bool:
  44. return self.get('DEBUG') if 'DEBUG' in self else True
  45. def get_time_zone(self) -> str:
  46. return self.get('TIME_ZONE') if 'TIME_ZONE' in self else 'Asia/Shanghai'
  47. def get_db_setting(self) -> dict:
  48. return {
  49. "NAME": self.get('DB_NAME'),
  50. "HOST": self.get('DB_HOST'),
  51. "PORT": self.get('DB_PORT'),
  52. "USER": self.get('DB_USER'),
  53. "PASSWORD": self.get('DB_PASSWORD'),
  54. "ENGINE": self.get('DB_ENGINE'),
  55. "CONN_MAX_AGE": 0,
  56. "POOL_OPTIONS": {
  57. "POOL_SIZE": 20,
  58. "MAX_OVERFLOW": int(self.get('DB_MAX_OVERFLOW')),
  59. "RECYCLE": 1800,
  60. "PRE_PING": True,
  61. "TIMEOUT": 30
  62. }
  63. }
  64. def get_cache_setting(self):
  65. redis_config = {
  66. 'default': {
  67. 'BACKEND': 'django_redis.cache.RedisCache',
  68. 'LOCATION': f'redis://{self.get("REDIS_HOST")}:{self.get("REDIS_PORT")}/{self.get("REDIS_DB")}',
  69. 'OPTIONS': {
  70. 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
  71. "PASSWORD": self.get("REDIS_PASSWORD"),
  72. "CONNECTION_POOL_KWARGS": {"max_connections": int(self.get("REDIS_MAX_CONNECTIONS"))}
  73. },
  74. },
  75. }
  76. if self.get('REDIS_SENTINEL_SENTINELS') is not None:
  77. sentinels_str = self.get('REDIS_SENTINEL_SENTINELS')
  78. sentinels = [
  79. (host.strip(), int(port))
  80. for hostport in sentinels_str.split(',')
  81. for host, port in [hostport.strip().split(':')]
  82. ]
  83. redis_config['default']['LOCATION'] = f'redis://{self.get("REDIS_SENTINEL_MASTER")}/{self.get("REDIS_DB")}'
  84. redis_config['default']['OPTIONS'].update({
  85. 'CLIENT_CLASS': 'django_redis.client.SentinelClient',
  86. 'SENTINELS': sentinels,
  87. 'SENTINEL_MASTER': self.get('REDIS_SENTINEL_MASTER'),
  88. 'PASSWORD': self.get("REDIS_PASSWORD"),
  89. })
  90. return redis_config
  91. def get_language_code(self):
  92. return self.get('LANGUAGE_CODE', 'zh-CN')
  93. def get_log_level(self):
  94. return self.get('LOG_LEVEL', 'DEBUG')
  95. def get_sandbox_python_package_paths(self):
  96. return self.get('SANDBOX_PYTHON_PACKAGE_PATHS',
  97. '/opt/py3/lib/python3.11/site-packages,/opt/maxkb-app/sandbox/python-packages,/opt/maxkb/python-packages')
  98. def get_admin_path(self):
  99. return self.get('ADMIN_PATH', '/admin')
  100. def get_chat_path(self):
  101. return self.get('CHAT_PATH', '/chat')
  102. def get_session_timeout(self):
  103. return int(self.get('SESSION_TIMEOUT', 28800))
  104. def __init__(self, *args):
  105. super().__init__(*args)
  106. def __repr__(self):
  107. return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))
  108. def __getitem__(self, item):
  109. return self.get(item)
  110. def __getattr__(self, item):
  111. return self.get(item)
  112. class ConfigManager:
  113. config_class = Config
  114. def __init__(self, root_path=None):
  115. self.root_path = root_path
  116. self.config = self.config_class()
  117. for key in self.config_class.defaults:
  118. self.config[key] = self.config_class.defaults[key]
  119. def from_mapping(self, *mapping, **kwargs):
  120. """Updates the config like :meth:`update` ignoring items with non-upper
  121. keys.
  122. .. versionadded:: 0.11
  123. """
  124. mappings = []
  125. if len(mapping) == 1:
  126. if hasattr(mapping[0], 'items'):
  127. mappings.append(mapping[0].items())
  128. else:
  129. mappings.append(mapping[0])
  130. elif len(mapping) > 1:
  131. raise TypeError(
  132. 'expected at most 1 positional argument, got %d' % len(mapping)
  133. )
  134. mappings.append(kwargs.items())
  135. for mapping in mappings:
  136. for (key, value) in mapping:
  137. if key.isupper():
  138. self.config[key] = value
  139. return True
  140. def from_yaml(self, filename, silent=False):
  141. if self.root_path:
  142. filename = os.path.join(self.root_path, filename)
  143. try:
  144. with open(filename, 'rt', encoding='utf8') as f:
  145. obj = yaml.safe_load(f)
  146. except IOError as e:
  147. if silent and e.errno in (errno.ENOENT, errno.EISDIR):
  148. return False
  149. e.strerror = 'Unable to load configuration file (%s)' % e.strerror
  150. raise
  151. if obj:
  152. return self.from_mapping(obj)
  153. return True
  154. def load_from_yml(self):
  155. for i in ['config_example.yml', 'config.yaml', 'config.yml']:
  156. if not os.path.isfile(os.path.join(self.root_path, i)):
  157. continue
  158. loaded = self.from_yaml(i)
  159. if loaded:
  160. return True
  161. msg = f"""
  162. Error: No config file found.
  163. You can run `cp config_example.yml {self.root_path}/config.yml`, and edit it.
  164. """
  165. raise ImportError(msg)
  166. def load_from_env(self):
  167. keys = os.environ.keys()
  168. config = {key.replace('MAXKB_', ''): os.environ.get(key) for key in keys if key.startswith('MAXKB_')}
  169. if len(config.keys()) <= 0:
  170. msg = f"""
  171. Error: No config env found.
  172. Please set environment variables
  173. MAXKB_CONFIG_TYPE: 配置文件读取方式 FILE: 使用配置文件配置 ENV: 使用ENV配置
  174. MAXKB_DB_NAME: 数据库名称
  175. MAXKB_DB_HOST: 数据库主机
  176. MAXKB_DB_PORT: 数据库端口
  177. MAXKB_DB_USER: 数据库用户名
  178. MAXKB_DB_PASSWORD: 数据库密码
  179. MAXKB_REDIS_HOST:缓存数据库主机
  180. MAXKB_REDIS_PORT:缓存数据库端口
  181. MAXKB_REDIS_PASSWORD:缓存数据库密码
  182. MAXKB_REDIS_DB:缓存数据库
  183. MAXKB_REDIS_MAX_CONNECTIONS:缓存数据库最大连接数
  184. """
  185. raise ImportError(msg)
  186. self.from_mapping(config)
  187. return True
  188. @classmethod
  189. def load_user_config(cls, root_path=None, config_class=None):
  190. config_class = config_class or Config
  191. cls.config_class = config_class
  192. if not root_path:
  193. root_path = PROJECT_DIR
  194. manager = cls(root_path=root_path)
  195. config_type = os.environ.get('MAXKB_CONFIG_TYPE')
  196. if config_type is None or config_type != 'ENV':
  197. manager.load_from_yml()
  198. else:
  199. manager.load_from_env()
  200. config = manager.config
  201. return config