aury-boot 0.0.4__py3-none-any.whl → 0.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. aury/boot/__init__.py +2 -2
  2. aury/boot/_version.py +2 -2
  3. aury/boot/application/__init__.py +45 -36
  4. aury/boot/application/app/__init__.py +12 -8
  5. aury/boot/application/app/base.py +12 -0
  6. aury/boot/application/app/components.py +137 -44
  7. aury/boot/application/app/middlewares.py +2 -0
  8. aury/boot/application/app/startup.py +249 -0
  9. aury/boot/application/config/__init__.py +36 -1
  10. aury/boot/application/config/multi_instance.py +200 -0
  11. aury/boot/application/config/settings.py +341 -12
  12. aury/boot/application/constants/components.py +6 -0
  13. aury/boot/application/errors/handlers.py +17 -3
  14. aury/boot/application/middleware/logging.py +8 -120
  15. aury/boot/application/rpc/__init__.py +2 -2
  16. aury/boot/commands/__init__.py +30 -10
  17. aury/boot/commands/app.py +131 -1
  18. aury/boot/commands/docs.py +104 -17
  19. aury/boot/commands/init.py +27 -8
  20. aury/boot/commands/server/app.py +2 -3
  21. aury/boot/commands/templates/project/AGENTS.md.tpl +217 -0
  22. aury/boot/commands/templates/project/README.md.tpl +2 -2
  23. aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +59 -0
  24. aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +183 -0
  25. aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl +206 -0
  26. aury/boot/commands/templates/project/aury_docs/03-service.md.tpl +398 -0
  27. aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl +95 -0
  28. aury/boot/commands/templates/project/aury_docs/05-api.md.tpl +116 -0
  29. aury/boot/commands/templates/project/aury_docs/06-exception.md.tpl +118 -0
  30. aury/boot/commands/templates/project/aury_docs/07-cache.md.tpl +122 -0
  31. aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl +32 -0
  32. aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl +38 -0
  33. aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl +115 -0
  34. aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +92 -0
  35. aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +56 -0
  36. aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +92 -0
  37. aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +102 -0
  38. aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +147 -0
  39. aury/boot/commands/templates/project/config.py.tpl +1 -1
  40. aury/boot/commands/templates/project/env.example.tpl +73 -5
  41. aury/boot/commands/templates/project/modules/tasks.py.tpl +1 -1
  42. aury/boot/contrib/admin_console/auth.py +2 -3
  43. aury/boot/contrib/admin_console/install.py +1 -1
  44. aury/boot/domain/models/mixins.py +48 -1
  45. aury/boot/domain/pagination/__init__.py +94 -0
  46. aury/boot/domain/repository/impl.py +1 -1
  47. aury/boot/domain/repository/interface.py +1 -1
  48. aury/boot/domain/transaction/__init__.py +8 -9
  49. aury/boot/infrastructure/__init__.py +86 -29
  50. aury/boot/infrastructure/cache/backends.py +102 -18
  51. aury/boot/infrastructure/cache/base.py +12 -0
  52. aury/boot/infrastructure/cache/manager.py +153 -91
  53. aury/boot/infrastructure/channel/__init__.py +24 -0
  54. aury/boot/infrastructure/channel/backends/__init__.py +9 -0
  55. aury/boot/infrastructure/channel/backends/memory.py +83 -0
  56. aury/boot/infrastructure/channel/backends/redis.py +88 -0
  57. aury/boot/infrastructure/channel/base.py +92 -0
  58. aury/boot/infrastructure/channel/manager.py +203 -0
  59. aury/boot/infrastructure/clients/__init__.py +22 -0
  60. aury/boot/infrastructure/clients/rabbitmq/__init__.py +9 -0
  61. aury/boot/infrastructure/clients/rabbitmq/config.py +46 -0
  62. aury/boot/infrastructure/clients/rabbitmq/manager.py +288 -0
  63. aury/boot/infrastructure/clients/redis/__init__.py +28 -0
  64. aury/boot/infrastructure/clients/redis/config.py +51 -0
  65. aury/boot/infrastructure/clients/redis/manager.py +264 -0
  66. aury/boot/infrastructure/database/config.py +1 -2
  67. aury/boot/infrastructure/database/manager.py +16 -38
  68. aury/boot/infrastructure/events/__init__.py +18 -21
  69. aury/boot/infrastructure/events/backends/__init__.py +11 -0
  70. aury/boot/infrastructure/events/backends/memory.py +86 -0
  71. aury/boot/infrastructure/events/backends/rabbitmq.py +193 -0
  72. aury/boot/infrastructure/events/backends/redis.py +162 -0
  73. aury/boot/infrastructure/events/base.py +127 -0
  74. aury/boot/infrastructure/events/manager.py +224 -0
  75. aury/boot/infrastructure/mq/__init__.py +24 -0
  76. aury/boot/infrastructure/mq/backends/__init__.py +9 -0
  77. aury/boot/infrastructure/mq/backends/rabbitmq.py +179 -0
  78. aury/boot/infrastructure/mq/backends/redis.py +167 -0
  79. aury/boot/infrastructure/mq/base.py +143 -0
  80. aury/boot/infrastructure/mq/manager.py +239 -0
  81. aury/boot/infrastructure/scheduler/manager.py +7 -3
  82. aury/boot/infrastructure/storage/__init__.py +9 -9
  83. aury/boot/infrastructure/storage/base.py +17 -5
  84. aury/boot/infrastructure/storage/factory.py +0 -1
  85. aury/boot/infrastructure/tasks/__init__.py +2 -2
  86. aury/boot/infrastructure/tasks/manager.py +47 -29
  87. aury/boot/testing/base.py +2 -2
  88. {aury_boot-0.0.4.dist-info → aury_boot-0.0.5.dist-info}/METADATA +19 -2
  89. aury_boot-0.0.5.dist-info/RECORD +176 -0
  90. aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +0 -1397
  91. aury/boot/infrastructure/events/bus.py +0 -362
  92. aury/boot/infrastructure/events/config.py +0 -52
  93. aury/boot/infrastructure/events/consumer.py +0 -134
  94. aury/boot/infrastructure/events/models.py +0 -63
  95. aury_boot-0.0.4.dist-info/RECORD +0 -137
  96. /aury/boot/commands/templates/project/{CLI.md.tpl → aury_docs/99-cli.md.tpl} +0 -0
  97. {aury_boot-0.0.4.dist-info → aury_boot-0.0.5.dist-info}/WHEEL +0 -0
  98. {aury_boot-0.0.4.dist-info → aury_boot-0.0.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,249 @@
1
+ """启动日志工具。
2
+
3
+ 提供应用启动时的组件状态打印功能,包括 URL 脱敏。
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass, field
9
+ import re
10
+ from typing import Any
11
+
12
+ from aury.boot.common.logging import logger
13
+
14
+
15
+ @dataclass
16
+ class ComponentStatus:
17
+ """组件状态。"""
18
+
19
+ name: str
20
+ status: str # "ok", "error", "disabled"
21
+ backend: str | None = None
22
+ url: str | None = None
23
+ details: dict[str, Any] = field(default_factory=dict)
24
+
25
+
26
+ def mask_url(url: str | None) -> str:
27
+ """URL 脱敏(隐藏密码和敏感信息)。
28
+
29
+ Args:
30
+ url: 原始 URL
31
+
32
+ Returns:
33
+ 脱敏后的 URL
34
+ """
35
+ if not url:
36
+ return "N/A"
37
+
38
+ # 匹配常见 URL 格式中的密码部分
39
+ # redis://:password@host:port/db
40
+ # amqp://user:password@host:port/vhost
41
+ # postgresql://user:password@host:port/db
42
+ patterns = [
43
+ # user:password@ 格式
44
+ (r"(://[^:]+:)([^@]+)(@)", r"\1***\3"),
45
+ # :password@ 格式 (无用户名)
46
+ (r"(://:)([^@]+)(@)", r"\1***\3"),
47
+ ]
48
+
49
+ masked = url
50
+ for pattern, replacement in patterns:
51
+ masked = re.sub(pattern, replacement, masked)
52
+
53
+ return masked
54
+
55
+
56
+ def print_startup_banner(
57
+ app_name: str = "Aury Boot",
58
+ version: str = "",
59
+ components: list[ComponentStatus] | None = None,
60
+ ) -> None:
61
+ """打印启动横幅和组件状态。
62
+
63
+ Args:
64
+ app_name: 应用名称
65
+ version: 版本号
66
+ components: 组件状态列表
67
+ """
68
+ # 打印横幅
69
+ banner = f"""
70
+ ╔══════════════════════════════════════════════════════════════╗
71
+ ║ {app_name:^58} ║
72
+ ║ {f'v{version}' if version else '':^58} ║
73
+ ╚══════════════════════════════════════════════════════════════╝
74
+ """
75
+ logger.info(banner)
76
+
77
+ # 打印组件状态
78
+ if components:
79
+ logger.info("组件状态:")
80
+ logger.info("-" * 60)
81
+
82
+ for comp in components:
83
+ status_icon = "✓" if comp.status == "ok" else "✗" if comp.status == "error" else "○"
84
+ status_text = f"[{status_icon}] {comp.name}"
85
+
86
+ if comp.backend:
87
+ status_text += f" ({comp.backend})"
88
+
89
+ if comp.url:
90
+ status_text += f" -> {mask_url(comp.url)}"
91
+
92
+ if comp.status == "error" and comp.details.get("error"):
93
+ status_text += f" | Error: {comp.details['error']}"
94
+
95
+ logger.info(f" {status_text}")
96
+
97
+ logger.info("-" * 60)
98
+
99
+
100
+ def collect_component_status() -> list[ComponentStatus]:
101
+ """收集所有组件状态。
102
+
103
+ Returns:
104
+ 组件状态列表
105
+ """
106
+ from aury.boot.infrastructure.cache import CacheManager
107
+ from aury.boot.infrastructure.database import DatabaseManager
108
+ from aury.boot.infrastructure.scheduler import SchedulerManager
109
+ from aury.boot.infrastructure.storage import StorageManager
110
+
111
+ # 延迟导入新模块(可能不存在)
112
+ try:
113
+ from aury.boot.infrastructure.clients.redis import RedisClient
114
+ except ImportError:
115
+ RedisClient = None
116
+
117
+ try:
118
+ from aury.boot.infrastructure.channel import ChannelManager
119
+ except ImportError:
120
+ ChannelManager = None
121
+
122
+ try:
123
+ from aury.boot.infrastructure.mq import MQManager
124
+ except ImportError:
125
+ MQManager = None
126
+
127
+ try:
128
+ from aury.boot.infrastructure.events import EventBusManager
129
+ except ImportError:
130
+ EventBusManager = None
131
+
132
+ statuses = []
133
+
134
+ # Database - 收集所有实例
135
+ for name, instance in DatabaseManager._instances.items():
136
+ if instance.is_initialized:
137
+ url = str(instance._engine.url) if instance._engine else None
138
+ statuses.append(
139
+ ComponentStatus(
140
+ name="Database" if name == "default" else f"Database [{name}]",
141
+ status="ok",
142
+ backend="SQLAlchemy",
143
+ url=url,
144
+ )
145
+ )
146
+
147
+ # Cache - 收集所有实例
148
+ for name, instance in CacheManager._instances.items():
149
+ if instance._backend:
150
+ statuses.append(
151
+ ComponentStatus(
152
+ name="Cache" if name == "default" else f"Cache [{name}]",
153
+ status="ok",
154
+ backend=instance.backend_type,
155
+ url=instance._config.get("CACHE_URL") if instance._config else None,
156
+ )
157
+ )
158
+
159
+ # Storage - 收集所有实例
160
+ for name, instance in StorageManager._instances.items():
161
+ if instance._backend:
162
+ backend_type = instance._config.backend.value if instance._config else "unknown"
163
+ url = None
164
+ if instance._config:
165
+ if backend_type == "local":
166
+ url = instance._config.base_path
167
+ elif instance._config.endpoint:
168
+ url = instance._config.endpoint
169
+ statuses.append(
170
+ ComponentStatus(
171
+ name="Storage" if name == "default" else f"Storage [{name}]",
172
+ status="ok",
173
+ backend=backend_type,
174
+ url=url,
175
+ )
176
+ )
177
+
178
+ # Scheduler
179
+ scheduler = SchedulerManager.get_instance()
180
+ if scheduler._initialized:
181
+ statuses.append(
182
+ ComponentStatus(
183
+ name="Scheduler",
184
+ status="ok",
185
+ backend="APScheduler",
186
+ )
187
+ )
188
+
189
+ # Redis Clients
190
+ if RedisClient:
191
+ for name, instance in RedisClient._instances.items():
192
+ if instance.is_initialized:
193
+ statuses.append(
194
+ ComponentStatus(
195
+ name="Redis" if name == "default" else f"Redis [{name}]",
196
+ status="ok",
197
+ backend="redis",
198
+ url=instance._config.url if instance._config else None,
199
+ )
200
+ )
201
+
202
+ # Channel
203
+ if ChannelManager:
204
+ for name, instance in ChannelManager._instances.items():
205
+ if instance.is_initialized:
206
+ statuses.append(
207
+ ComponentStatus(
208
+ name="Channel" if name == "default" else f"Channel [{name}]",
209
+ status="ok",
210
+ backend=instance.backend_type,
211
+ url=getattr(instance._backend, "_url", None) if instance._backend else None,
212
+ )
213
+ )
214
+
215
+ # MQ
216
+ if MQManager:
217
+ for name, instance in MQManager._instances.items():
218
+ if instance.is_initialized:
219
+ statuses.append(
220
+ ComponentStatus(
221
+ name="MQ" if name == "default" else f"MQ [{name}]",
222
+ status="ok",
223
+ backend=instance.backend_type,
224
+ url=getattr(instance._backend, "_url", None) if instance._backend else None,
225
+ )
226
+ )
227
+
228
+ # Events
229
+ if EventBusManager:
230
+ for name, instance in EventBusManager._instances.items():
231
+ if instance.is_initialized:
232
+ statuses.append(
233
+ ComponentStatus(
234
+ name="Events" if name == "default" else f"Events [{name}]",
235
+ status="ok",
236
+ backend=instance.backend_type,
237
+ url=getattr(instance._backend, "_url", None) if instance._backend else None,
238
+ )
239
+ )
240
+
241
+ return statuses
242
+
243
+
244
+ __all__ = [
245
+ "ComponentStatus",
246
+ "collect_component_status",
247
+ "mask_url",
248
+ "print_startup_banner",
249
+ ]
@@ -6,39 +6,74 @@
6
6
  设计原则:
7
7
  - Application 层配置完全独立,不依赖 Infrastructure 层
8
8
  - 配置是纯粹的数据模型定义
9
+
10
+ 多实例配置:
11
+ 框架支持多种组件的多实例配置,使用统一的环境变量格式:
12
+ {PREFIX}_{INSTANCE}_{FIELD}=value
13
+
14
+ 示例:
15
+ DATABASE_DEFAULT_URL=postgresql://main...
16
+ DATABASE_ANALYTICS_URL=postgresql://analytics...
17
+ CACHE_DEFAULT_URL=redis://localhost:6379/1
18
+ MQ_DEFAULT_URL=redis://localhost:6379/2
9
19
  """
10
20
 
21
+ from .multi_instance import (
22
+ MultiInstanceConfigLoader,
23
+ MultiInstanceSettings,
24
+ parse_multi_instance_env,
25
+ )
11
26
  from .settings import (
12
27
  BaseConfig,
28
+ CacheInstanceConfig,
13
29
  CacheSettings,
30
+ ChannelInstanceConfig,
14
31
  CORSSettings,
32
+ DatabaseInstanceConfig,
15
33
  DatabaseSettings,
34
+ EventInstanceConfig,
16
35
  EventSettings,
17
36
  HealthCheckSettings,
18
37
  LogSettings,
38
+ MessageQueueSettings,
19
39
  MigrationSettings,
40
+ MQInstanceConfig,
20
41
  RPCClientSettings,
21
42
  RPCServiceSettings,
22
43
  SchedulerSettings,
23
44
  ServerSettings,
24
45
  ServiceSettings,
46
+ StorageInstanceConfig,
25
47
  TaskSettings,
26
48
  )
27
49
 
28
50
  __all__ = [
51
+ # 配置类
29
52
  "BaseConfig",
30
- "CacheSettings",
31
53
  "CORSSettings",
54
+ # 多实例配置类
55
+ "CacheInstanceConfig",
56
+ "CacheSettings",
57
+ "ChannelInstanceConfig",
58
+ "DatabaseInstanceConfig",
32
59
  "DatabaseSettings",
60
+ "EventInstanceConfig",
33
61
  "EventSettings",
34
62
  "HealthCheckSettings",
35
63
  "LogSettings",
64
+ "MQInstanceConfig",
65
+ "MessageQueueSettings",
36
66
  "MigrationSettings",
67
+ # 多实例配置工具
68
+ "MultiInstanceConfigLoader",
69
+ "MultiInstanceSettings",
37
70
  "RPCClientSettings",
38
71
  "RPCServiceSettings",
39
72
  "SchedulerSettings",
40
73
  "ServerSettings",
41
74
  "ServiceSettings",
75
+ "StorageInstanceConfig",
42
76
  "TaskSettings",
77
+ "parse_multi_instance_env",
43
78
  ]
44
79
 
@@ -0,0 +1,200 @@
1
+ """多实例配置解析工具。
2
+
3
+ 支持从环境变量解析 {PREFIX}_{INSTANCE}_{FIELD} 格式的多实例配置。
4
+
5
+ 示例:
6
+ DATABASE_DEFAULT_URL=postgresql://main...
7
+ DATABASE_DEFAULT_POOL_SIZE=10
8
+ DATABASE_ANALYTICS_URL=postgresql://analytics...
9
+
10
+ 解析后:
11
+ {
12
+ "default": {"url": "postgresql://main...", "pool_size": 10},
13
+ "analytics": {"url": "postgresql://analytics..."}
14
+ }
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import os
20
+ import re
21
+ from typing import Any
22
+
23
+ from pydantic import BaseModel
24
+
25
+
26
+ def parse_multi_instance_env(
27
+ prefix: str,
28
+ fields: list[str],
29
+ *,
30
+ type_hints: dict[str, type] | None = None,
31
+ ) -> dict[str, dict[str, Any]]:
32
+ """从环境变量解析多实例配置。
33
+
34
+ Args:
35
+ prefix: 环境变量前缀,如 "DATABASE"
36
+ fields: 支持的字段列表,如 ["url", "pool_size", "echo"]
37
+ type_hints: 字段类型提示,用于类型转换
38
+
39
+ Returns:
40
+ dict[str, dict[str, Any]]: 实例名 -> 配置字典
41
+
42
+ 示例:
43
+ >>> parse_multi_instance_env("DATABASE", ["url", "pool_size"])
44
+ {
45
+ "default": {"url": "postgresql://...", "pool_size": "10"},
46
+ "analytics": {"url": "postgresql://..."}
47
+ }
48
+ """
49
+ instances: dict[str, dict[str, Any]] = {}
50
+ type_hints = type_hints or {}
51
+
52
+ # 构建正则:{PREFIX}_{INSTANCE}_{FIELD}
53
+ # INSTANCE 和 FIELD 都是大写字母和下划线
54
+ fields_pattern = "|".join(re.escape(f.upper()) for f in fields)
55
+ pattern = re.compile(
56
+ rf"^{prefix}_([A-Z][A-Z0-9_]*)_({fields_pattern})$",
57
+ re.IGNORECASE
58
+ )
59
+
60
+ for key, value in os.environ.items():
61
+ match = pattern.match(key)
62
+ if match:
63
+ instance_name = match.group(1).lower()
64
+ field_name = match.group(2).lower()
65
+
66
+ # 类型转换
67
+ converted_value = _convert_value(value, type_hints.get(field_name))
68
+
69
+ if instance_name not in instances:
70
+ instances[instance_name] = {}
71
+ instances[instance_name][field_name] = converted_value
72
+
73
+ return instances
74
+
75
+
76
+ def _convert_value(value: str, target_type: type | None) -> Any:
77
+ """转换环境变量值到目标类型。"""
78
+ if target_type is None:
79
+ return value
80
+
81
+ if target_type is bool:
82
+ return value.lower() in ("true", "1", "yes", "on")
83
+ elif target_type is int:
84
+ return int(value)
85
+ elif target_type is float:
86
+ return float(value)
87
+ elif target_type is list:
88
+ # 简单的逗号分隔
89
+ return [v.strip() for v in value.split(",") if v.strip()]
90
+ else:
91
+ return value
92
+
93
+
94
+ class MultiInstanceSettings(BaseModel):
95
+ """多实例配置基类。
96
+
97
+ 子类需要定义各实例共享的配置字段。
98
+ """
99
+
100
+ @classmethod
101
+ def get_field_names(cls) -> list[str]:
102
+ """获取所有字段名。"""
103
+ return list(cls.model_fields.keys())
104
+
105
+ @classmethod
106
+ def get_type_hints(cls) -> dict[str, type]:
107
+ """获取字段类型提示。"""
108
+ hints = {}
109
+ for name, field_info in cls.model_fields.items():
110
+ annotation = field_info.annotation
111
+ # 处理 Optional 类型
112
+ if hasattr(annotation, "__origin__"):
113
+ # 如 str | None -> str
114
+ args = getattr(annotation, "__args__", ())
115
+ for arg in args:
116
+ if arg is not type(None):
117
+ hints[name] = arg
118
+ break
119
+ else:
120
+ hints[name] = annotation
121
+ return hints
122
+
123
+
124
+ class MultiInstanceConfigLoader:
125
+ """多实例配置加载器。
126
+
127
+ 使用示例:
128
+ loader = MultiInstanceConfigLoader("DATABASE", DatabaseInstanceConfig)
129
+ instances = loader.load()
130
+ # {"default": DatabaseInstanceConfig(...), "analytics": DatabaseInstanceConfig(...)}
131
+ """
132
+
133
+ def __init__(
134
+ self,
135
+ prefix: str,
136
+ config_class: type[MultiInstanceSettings],
137
+ ):
138
+ """初始化加载器。
139
+
140
+ Args:
141
+ prefix: 环境变量前缀
142
+ config_class: 配置类(继承自 MultiInstanceSettings)
143
+ """
144
+ self.prefix = prefix.upper()
145
+ self.config_class = config_class
146
+
147
+ def load(self) -> dict[str, MultiInstanceSettings]:
148
+ """加载所有实例配置。
149
+
150
+ Returns:
151
+ dict[str, config_class]: 实例名 -> 配置对象
152
+ """
153
+ fields = self.config_class.get_field_names()
154
+ type_hints = self.config_class.get_type_hints()
155
+
156
+ raw_instances = parse_multi_instance_env(
157
+ self.prefix,
158
+ fields,
159
+ type_hints=type_hints,
160
+ )
161
+
162
+ # 转换为配置对象
163
+ instances = {}
164
+ for name, config_dict in raw_instances.items():
165
+ try:
166
+ instances[name] = self.config_class(**config_dict)
167
+ except Exception as e:
168
+ # 配置不完整时跳过,让 Pydantic 验证报错
169
+ raise ValueError(
170
+ f"配置实例 [{self.prefix}_{name.upper()}] 无效: {e}"
171
+ ) from e
172
+
173
+ return instances
174
+
175
+ def load_or_default(
176
+ self,
177
+ default_instance: str = "default",
178
+ ) -> dict[str, MultiInstanceSettings]:
179
+ """加载配置,如果没有任何实例则返回包含默认实例的字典。
180
+
181
+ Args:
182
+ default_instance: 默认实例名
183
+
184
+ Returns:
185
+ dict[str, config_class]: 实例名 -> 配置对象
186
+ """
187
+ instances = self.load()
188
+
189
+ if not instances:
190
+ # 没有配置任何实例,创建一个默认的
191
+ instances[default_instance] = self.config_class()
192
+
193
+ return instances
194
+
195
+
196
+ __all__ = [
197
+ "MultiInstanceConfigLoader",
198
+ "MultiInstanceSettings",
199
+ "parse_multi_instance_env",
200
+ ]