Blame view

config/tenant_config_loader.py 6.77 KB
0064e946   tangwang   feat: 增量索引服务、租户配置...
1
2
3
  """
  租户配置加载器。
  
038e4e2f   tangwang   refactor(i18n): t...
4
5
  从统一配置文件(config.yaml)加载租户配置,包括主语言和索引语言(index_languages)。
  支持旧配置 translate_to_en / translate_to_zh 的兼容解析。
0064e946   tangwang   feat: 增量索引服务、租户配置...
6
7
8
  """
  
  import logging
038e4e2f   tangwang   refactor(i18n): t...
9
  from typing import Dict, Any, Optional, List
0064e946   tangwang   feat: 增量索引服务、租户配置...
10
11
12
  
  logger = logging.getLogger(__name__)
  
038e4e2f   tangwang   refactor(i18n): t...
13
  # 支持的索引语言:code -> display name(供商家勾选主市场语言等场景使用)
6f7840cf   tangwang   refactor: rename ...
14
15
  # 语言代码与展示名的双向映射(供翻译/LLM 提示等统一使用)
  SOURCE_LANG_CODE_MAP: Dict[str, str] = {
038e4e2f   tangwang   refactor(i18n): t...
16
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
      "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 ...
55
56
57
  TARGET_LANG_CODE_MAP: Dict[str, str] = {v: k for k, v in SOURCE_LANG_CODE_MAP.items()}
  
  
038e4e2f   tangwang   refactor(i18n): t...
58
59
60
  def normalize_index_languages(value: Any, primary_language: str = "en") -> List[str]:
      """
       index_languages 配置规范化为合法语言代码列表。
26b910bd   tangwang   refactor service ...
61
      仅做规范化,不做默认值兜底。
038e4e2f   tangwang   refactor(i18n): t...
62
      """
26b910bd   tangwang   refactor service ...
63
      del primary_language
038e4e2f   tangwang   refactor(i18n): t...
64
      if value is None:
26b910bd   tangwang   refactor service ...
65
          return []
038e4e2f   tangwang   refactor(i18n): t...
66
      if not isinstance(value, (list, tuple)):
26b910bd   tangwang   refactor service ...
67
          return []
038e4e2f   tangwang   refactor(i18n): t...
68
69
70
71
72
73
      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 ...
74
          if code in SOURCE_LANG_CODE_MAP:
038e4e2f   tangwang   refactor(i18n): t...
75
76
              valid.append(code)
              seen.add(code)
26b910bd   tangwang   refactor service ...
77
      return valid
038e4e2f   tangwang   refactor(i18n): t...
78
79
  
  
26b910bd   tangwang   refactor service ...
80
81
82
83
  def resolve_index_languages(
      tenant_config: Dict[str, Any],
      default_index_languages: List[str],
  ) -> List[str]:
038e4e2f   tangwang   refactor(i18n): t...
84
85
86
87
88
      """
      从租户配置解析 index_languages
      若存在 index_languages 则用之;否则按旧配置 translate_to_en / translate_to_zh 推导。
      """
      if "index_languages" in tenant_config:
26b910bd   tangwang   refactor service ...
89
          normalized = normalize_index_languages(
038e4e2f   tangwang   refactor(i18n): t...
90
91
92
              tenant_config["index_languages"],
              tenant_config.get("primary_language") or "en",
          )
26b910bd   tangwang   refactor service ...
93
          return normalized if normalized else list(default_index_languages)
038e4e2f   tangwang   refactor(i18n): t...
94
95
96
97
      primary = (tenant_config.get("primary_language") or "en").strip().lower()
      to_en = bool(tenant_config.get("translate_to_en"))
      to_zh = bool(tenant_config.get("translate_to_zh"))
      langs: List[str] = []
6f7840cf   tangwang   refactor: rename ...
98
      if primary and primary in SOURCE_LANG_CODE_MAP:
038e4e2f   tangwang   refactor(i18n): t...
99
100
101
          langs.append(primary)
      for code in ("en", "zh"):
          if code not in langs and ((code == "en" and to_en) or (code == "zh" and to_zh)):
6f7840cf   tangwang   refactor: rename ...
102
              if code in SOURCE_LANG_CODE_MAP:
038e4e2f   tangwang   refactor(i18n): t...
103
                  langs.append(code)
26b910bd   tangwang   refactor service ...
104
      return langs if langs else list(default_index_languages)
038e4e2f   tangwang   refactor(i18n): t...
105
  
0064e946   tangwang   feat: 增量索引服务、租户配置...
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
  
  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:
              from config import ConfigLoader
26b910bd   tangwang   refactor service ...
126
  
0064e946   tangwang   feat: 增量索引服务、租户配置...
127
128
              config_loader = ConfigLoader()
              search_config = config_loader.load_config()
26b910bd   tangwang   refactor service ...
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
              tenant_cfg = search_config.tenant_config
              if not isinstance(tenant_cfg, dict):
                  raise RuntimeError("tenant_config must be an object")
  
              default_cfg = tenant_cfg.get("default")
              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"
                  )
  
              tenants_cfg = tenant_cfg.get("tenants", {})
              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: 增量索引服务、租户配置...
158
159
160
161
              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 ...
162
              raise
0064e946   tangwang   feat: 增量索引服务、租户配置...
163
164
165
166
167
168
169
170
171
  
      def get_tenant_config(self, tenant_id: str) -> Dict[str, Any]:
          """
          获取指定租户的配置。
  
          Args:
              tenant_id: 租户ID
  
          Returns:
038e4e2f   tangwang   refactor(i18n): t...
172
              租户配置字典,若租户不存在则用默认配置。始终包含已解析的 index_languages
0064e946   tangwang   feat: 增量索引服务、租户配置...
173
174
175
          """
          config = self.load_config()
          tenant_id_str = str(tenant_id)
26b910bd   tangwang   refactor service ...
176
          default = config["default"]
0064e946   tangwang   feat: 增量索引服务、租户配置...
177
          tenants = config.get("tenants", {})
26b910bd   tangwang   refactor service ...
178
179
180
          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...
181
          if tenant_id_str not in tenants:
0064e946   tangwang   feat: 增量索引服务、租户配置...
182
              logger.debug(f"Tenant {tenant_id} not found in config, using default")
26b910bd   tangwang   refactor service ...
183
184
185
186
187
188
189
          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...
190
          return out
0064e946   tangwang   feat: 增量索引服务、租户配置...
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
  
      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