Blame view

config/tenant_config_loader.py 5.82 KB
0064e946   tangwang   feat: 增量索引服务、租户配置...
1
2
3
  """
  租户配置加载器。
  
038e4e2f   tangwang   refactor(i18n): t...
4
  从统一配置文件(config.yaml)加载租户配置,包括主语言和索引语言(index_languages)。
0064e946   tangwang   feat: 增量索引服务、租户配置...
5
6
7
  """
  
  import logging
038e4e2f   tangwang   refactor(i18n): t...
8
  from typing import Dict, Any, Optional, List
0064e946   tangwang   feat: 增量索引服务、租户配置...
9
  
86d8358b   tangwang   config optimize
10
11
  from config.loader import get_app_config
  
0064e946   tangwang   feat: 增量索引服务、租户配置...
12
13
  logger = logging.getLogger(__name__)
  
038e4e2f   tangwang   refactor(i18n): t...
14
  # 支持的索引语言:code -> display name(供商家勾选主市场语言等场景使用)
6f7840cf   tangwang   refactor: rename ...
15
16
  # 语言代码与展示名的双向映射(供翻译/LLM 提示等统一使用)
  SOURCE_LANG_CODE_MAP: Dict[str, str] = {
038e4e2f   tangwang   refactor(i18n): t...
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
      "en": "English",
      "zh": "Chinese",
      "zh_tw": "Traditional Chinese",
      "ru": "Russian",
      "ja": "Japanese",
      "ko": "Korean",
      "es": "Spanish",
      "fr": "French",
      "pt": "Portuguese",
      "de": "German",
      "it": "Italian",
      "th": "Thai",
      "vi": "Vietnamese",
      "id": "Indonesian",
      "ms": "Malay",
      "ar": "Arabic",
      "hi": "Hindi",
      "he": "Hebrew",
      "my": "Burmese",
      "ta": "Tamil",
      "ur": "Urdu",
      "bn": "Bengali",
      "pl": "Polish",
      "nl": "Dutch",
      "ro": "Romanian",
      "tr": "Turkish",
      "km": "Khmer",
      "lo": "Lao",
      "yue": "Cantonese",
      "cs": "Czech",
      "el": "Greek",
      "sv": "Swedish",
      "hu": "Hungarian",
      "da": "Danish",
      "fi": "Finnish",
      "uk": "Ukrainian",
      "bg": "Bulgarian",
  }
  
6f7840cf   tangwang   refactor: rename ...
56
57
58
  TARGET_LANG_CODE_MAP: Dict[str, str] = {v: k for k, v in SOURCE_LANG_CODE_MAP.items()}
  
  
038e4e2f   tangwang   refactor(i18n): t...
59
60
61
  def normalize_index_languages(value: Any, primary_language: str = "en") -> List[str]:
      """
       index_languages 配置规范化为合法语言代码列表。
26b910bd   tangwang   refactor service ...
62
      仅做规范化,不做默认值兜底。
038e4e2f   tangwang   refactor(i18n): t...
63
      """
26b910bd   tangwang   refactor service ...
64
      del primary_language
038e4e2f   tangwang   refactor(i18n): t...
65
      if value is None:
26b910bd   tangwang   refactor service ...
66
          return []
038e4e2f   tangwang   refactor(i18n): t...
67
      if not isinstance(value, (list, tuple)):
26b910bd   tangwang   refactor service ...
68
          return []
038e4e2f   tangwang   refactor(i18n): t...
69
70
71
72
73
74
      valid: List[str] = []
      seen: set = set()
      for item in value:
          code = (item or "").strip().lower()
          if not code or code in seen:
              continue
6f7840cf   tangwang   refactor: rename ...
75
          if code in SOURCE_LANG_CODE_MAP:
038e4e2f   tangwang   refactor(i18n): t...
76
77
              valid.append(code)
              seen.add(code)
26b910bd   tangwang   refactor service ...
78
      return valid
038e4e2f   tangwang   refactor(i18n): t...
79
80
  
  
26b910bd   tangwang   refactor service ...
81
82
83
84
  def resolve_index_languages(
      tenant_config: Dict[str, Any],
      default_index_languages: List[str],
  ) -> List[str]:
038e4e2f   tangwang   refactor(i18n): t...
85
86
      """
      从租户配置解析 index_languages
86d8358b   tangwang   config optimize
87
      若配置缺失或非法,则回退到默认配置。
038e4e2f   tangwang   refactor(i18n): t...
88
      """
86d8358b   tangwang   config optimize
89
90
91
92
93
      normalized = normalize_index_languages(
          tenant_config.get("index_languages"),
          tenant_config.get("primary_language") or "en",
      )
      return normalized if normalized else list(default_index_languages)
038e4e2f   tangwang   refactor(i18n): t...
94
  
0064e946   tangwang   feat: 增量索引服务、租户配置...
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
  
  class TenantConfigLoader:
      """租户配置加载器。"""
  
      def __init__(self):
          """初始化租户配置加载器。"""
          self._config: Optional[Dict[str, Any]] = None
  
      def load_config(self) -> Dict[str, Any]:
          """
          加载租户配置(从统一配置文件)。
  
          Returns:
              租户配置字典,格式:{"tenants": {...}, "default": {...}}
          """
          if self._config is not None:
              return self._config
  
          try:
86d8358b   tangwang   config optimize
114
115
              tenant_cfg = get_app_config().tenants
              default_cfg = tenant_cfg.default
26b910bd   tangwang   refactor service ...
116
117
118
119
120
121
122
123
124
125
126
127
              if not isinstance(default_cfg, dict):
                  raise RuntimeError("tenant_config.default must be configured in config.yaml")
              default_primary = (default_cfg.get("primary_language") or "en").strip().lower()
              default_index_languages = normalize_index_languages(
                  default_cfg.get("index_languages"),
                  default_primary,
              )
              if not default_index_languages:
                  raise RuntimeError(
                      "tenant_config.default.index_languages must include at least one supported language"
                  )
  
86d8358b   tangwang   config optimize
128
              tenants_cfg = tenant_cfg.tenants
26b910bd   tangwang   refactor service ...
129
130
131
132
133
134
135
136
137
138
139
              if not isinstance(tenants_cfg, dict):
                  raise RuntimeError("tenant_config.tenants must be an object")
  
              normalized_default = dict(default_cfg)
              normalized_default["primary_language"] = default_primary
              normalized_default["index_languages"] = default_index_languages
  
              self._config = {
                  "default": normalized_default,
                  "tenants": tenants_cfg,
              }
0064e946   tangwang   feat: 增量索引服务、租户配置...
140
141
142
143
              logger.info("Loaded tenant config from unified config.yaml")
              return self._config
          except Exception as e:
              logger.error(f"Failed to load tenant config: {e}", exc_info=True)
26b910bd   tangwang   refactor service ...
144
              raise
0064e946   tangwang   feat: 增量索引服务、租户配置...
145
146
147
148
149
150
151
152
153
  
      def get_tenant_config(self, tenant_id: str) -> Dict[str, Any]:
          """
          获取指定租户的配置。
  
          Args:
              tenant_id: 租户ID
  
          Returns:
038e4e2f   tangwang   refactor(i18n): t...
154
              租户配置字典,若租户不存在则用默认配置。始终包含已解析的 index_languages
0064e946   tangwang   feat: 增量索引服务、租户配置...
155
156
157
          """
          config = self.load_config()
          tenant_id_str = str(tenant_id)
26b910bd   tangwang   refactor service ...
158
          default = config["default"]
0064e946   tangwang   feat: 增量索引服务、租户配置...
159
          tenants = config.get("tenants", {})
26b910bd   tangwang   refactor service ...
160
161
162
          raw = tenants[tenant_id_str] if tenant_id_str in tenants else {}
          if raw and not isinstance(raw, dict):
              raise RuntimeError(f"tenant_config.tenants.{tenant_id_str} must be an object")
038e4e2f   tangwang   refactor(i18n): t...
163
          if tenant_id_str not in tenants:
0064e946   tangwang   feat: 增量索引服务、租户配置...
164
              logger.debug(f"Tenant {tenant_id} not found in config, using default")
26b910bd   tangwang   refactor service ...
165
166
167
168
169
170
171
          merged = dict(default)
          merged.update(raw)
          out = dict(merged)
          out["index_languages"] = resolve_index_languages(
              merged,
              default_index_languages=default["index_languages"],
          )
038e4e2f   tangwang   refactor(i18n): t...
172
          return out
0064e946   tangwang   feat: 增量索引服务、租户配置...
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
  
      def reload(self):
          """重新加载配置(用于配置更新)。"""
          self._config = None
          return self.load_config()
  
  
  # 全局实例
  _tenant_config_loader: Optional[TenantConfigLoader] = None
  
  
  def get_tenant_config_loader() -> TenantConfigLoader:
      """获取全局租户配置加载器实例。"""
      global _tenant_config_loader
      if _tenant_config_loader is None:
          _tenant_config_loader = TenantConfigLoader()
      return _tenant_config_loader