aury-boot 0.0.5__py3-none-any.whl → 0.0.7__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 (49) hide show
  1. aury/boot/_version.py +2 -2
  2. aury/boot/application/__init__.py +15 -0
  3. aury/boot/application/adapter/__init__.py +112 -0
  4. aury/boot/application/adapter/base.py +511 -0
  5. aury/boot/application/adapter/config.py +242 -0
  6. aury/boot/application/adapter/decorators.py +259 -0
  7. aury/boot/application/adapter/exceptions.py +202 -0
  8. aury/boot/application/adapter/http.py +325 -0
  9. aury/boot/application/app/middlewares.py +7 -4
  10. aury/boot/application/config/multi_instance.py +42 -26
  11. aury/boot/application/config/settings.py +111 -191
  12. aury/boot/application/middleware/logging.py +14 -1
  13. aury/boot/commands/generate.py +22 -22
  14. aury/boot/commands/init.py +41 -9
  15. aury/boot/commands/templates/project/AGENTS.md.tpl +8 -4
  16. aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +17 -16
  17. aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +82 -43
  18. aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +14 -14
  19. aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +40 -28
  20. aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +9 -9
  21. aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +8 -8
  22. aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl +403 -0
  23. aury/boot/commands/templates/project/aury_docs/99-cli.md.tpl +19 -19
  24. aury/boot/commands/templates/project/config.py.tpl +10 -10
  25. aury/boot/commands/templates/project/env_templates/_header.tpl +10 -0
  26. aury/boot/commands/templates/project/env_templates/admin.tpl +49 -0
  27. aury/boot/commands/templates/project/env_templates/cache.tpl +14 -0
  28. aury/boot/commands/templates/project/env_templates/database.tpl +22 -0
  29. aury/boot/commands/templates/project/env_templates/log.tpl +18 -0
  30. aury/boot/commands/templates/project/env_templates/messaging.tpl +46 -0
  31. aury/boot/commands/templates/project/env_templates/rpc.tpl +28 -0
  32. aury/boot/commands/templates/project/env_templates/scheduler.tpl +18 -0
  33. aury/boot/commands/templates/project/env_templates/service.tpl +18 -0
  34. aury/boot/commands/templates/project/env_templates/storage.tpl +38 -0
  35. aury/boot/commands/templates/project/env_templates/third_party.tpl +43 -0
  36. aury/boot/common/logging/__init__.py +26 -674
  37. aury/boot/common/logging/context.py +132 -0
  38. aury/boot/common/logging/decorators.py +118 -0
  39. aury/boot/common/logging/format.py +315 -0
  40. aury/boot/common/logging/setup.py +214 -0
  41. aury/boot/infrastructure/database/config.py +6 -14
  42. aury/boot/infrastructure/tasks/config.py +5 -13
  43. aury/boot/infrastructure/tasks/manager.py +8 -4
  44. aury/boot/testing/base.py +2 -2
  45. {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/METADATA +2 -1
  46. {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/RECORD +48 -27
  47. aury/boot/commands/templates/project/env.example.tpl +0 -281
  48. {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/WHEEL +0 -0
  49. {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/entry_points.txt +0 -0
aury/boot/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.5'
32
- __version_tuple__ = version_tuple = (0, 0, 5)
31
+ __version__ = version = '0.0.7'
32
+ __version_tuple__ = version_tuple = (0, 0, 7)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -5,6 +5,14 @@
5
5
 
6
6
  # 事件系统(从 infrastructure 导入 - Event 定义在最底层)
7
7
  # 事务管理(从 domain 导入)
8
+ # 第三方适配器
9
+ from aury.boot.application.adapter import (
10
+ AdapterError,
11
+ AdapterSettings,
12
+ BaseAdapter,
13
+ HttpAdapter,
14
+ adapter_method,
15
+ )
8
16
  from aury.boot.domain.transaction import (
9
17
  TransactionManager,
10
18
  TransactionRequiredError,
@@ -67,8 +75,12 @@ from .scheduler import run_scheduler, run_scheduler_sync
67
75
  from .server import ApplicationServer, run_app
68
76
 
69
77
  __all__ = [
78
+ # 第三方适配器
79
+ "AdapterError",
80
+ "AdapterSettings",
70
81
  # 服务器集成
71
82
  "ApplicationServer",
83
+ "BaseAdapter",
72
84
  # 配置
73
85
  "BaseConfig",
74
86
  # 中间件
@@ -92,6 +104,7 @@ __all__ = [
92
104
  "EventType",
93
105
  # 应用框架
94
106
  "FoundationApp",
107
+ "HttpAdapter",
95
108
  "IEventBus",
96
109
  "Lifetime",
97
110
  "LogSettings",
@@ -114,6 +127,8 @@ __all__ = [
114
127
  # 事务管理
115
128
  "TransactionManager",
116
129
  "TransactionRequiredError",
130
+ # 第三方适配器装饰器
131
+ "adapter_method",
117
132
  "ensure_transaction",
118
133
  # HTTP 中间件装饰器
119
134
  "log_request",
@@ -0,0 +1,112 @@
1
+ """第三方接口适配器模块。
2
+
3
+ 本模块用于封装第三方接口(如支付、短信、微信、云存储等外部服务)的调用,
4
+ 解决测试环境中第三方接口不可用的问题,通过配置切换真实调用与挡板(Mock)实现。
5
+
6
+ 核心特性:
7
+ - 多模式切换:real(真实)/sandbox(沙箱)/mock(挡板)/disabled(禁用)
8
+ - 按方法级别的模式覆盖:同一 Adapter 不同方法可用不同模式
9
+ - 装饰器声明式定义:@adapter_method + .mock 链式定义挡板逻辑
10
+ - HTTP 类第三方的便捷基类:HttpAdapter 封装请求/认证/错误处理
11
+ - 调用历史记录:便于测试断言,验证调用参数和结果
12
+
13
+ 模式说明:
14
+ - real: 调用真实第三方接口(生产环境)
15
+ - sandbox: 调用第三方提供的沙箱环境(如果有)
16
+ - mock: 使用本地挡板实现,不发出真实请求(测试/开发环境)
17
+ - disabled: 禁用该接口,调用时抛出 AdapterDisabledError
18
+
19
+ 设计原则:
20
+ - 最小约束:只约束"模式怎么切换",不限制具体调用方式
21
+ - 最大自由:HTTP/SDK/gRPC/任意协议都能用
22
+ - 可组合:Adapter 可以组合其他 Adapter
23
+
24
+ 典型使用场景:
25
+ 1. 测试环境:第三方接口不可用,使用 mock 模式运行测试
26
+ 2. 开发环境:避免消耗真实资源(如短信费用),使用 mock
27
+ 3. 部分挡板:查询接口走真实,写入接口走 mock
28
+ 4. 功能开关:禁用某些危险操作(如退款)在测试环境
29
+
30
+ 使用示例:
31
+ from aury.boot.application.adapter import (
32
+ HttpAdapter, adapter_method, AdapterSettings
33
+ )
34
+
35
+ class PaymentAdapter(HttpAdapter):
36
+ '''支付第三方接口适配器。'''
37
+
38
+ @adapter_method("create")
39
+ async def create_order(self, amount: int, order_id: str) -> dict:
40
+ '''创建支付订单(真实实现)。'''
41
+ return await self._request("POST", "/charge", json={"amount": amount, "order_id": order_id})
42
+
43
+ @create_order.mock
44
+ async def create_order_mock(self, amount: int, order_id: str) -> dict:
45
+ '''创建支付订单(挡板实现)。
46
+
47
+ 挡板可以包含业务逻辑,模拟各种场景(成功、失败、边界情况)。
48
+ '''
49
+ if amount > 10000:
50
+ return {"success": False, "error": "金额超限"}
51
+ return {"success": True, "mock": True, "order_id": order_id}
52
+
53
+ # 配置:测试环境使用挡板,但 query 走真实接口
54
+ settings = AdapterSettings(
55
+ mode="mock", # 默认走挡板
56
+ method_modes={"query": "real"}, # query 走真实
57
+ base_url="https://api.payment.com",
58
+ )
59
+
60
+ adapter = PaymentAdapter("payment", settings)
61
+ result = await adapter.create_order(100, "order-1") # 走挡板
62
+
63
+ 环境变量配置:
64
+ # 全局模式
65
+ THIRD_PARTY_PAYMENT_MODE=mock
66
+
67
+ # 连接配置
68
+ THIRD_PARTY_PAYMENT_BASE_URL=https://api.payment.com
69
+ THIRD_PARTY_PAYMENT_SANDBOX_URL=https://sandbox.payment.com
70
+ THIRD_PARTY_PAYMENT_API_KEY=sk_test_xxx
71
+ THIRD_PARTY_PAYMENT_TIMEOUT=30
72
+
73
+ # 方法级模式覆盖(JSON 格式)
74
+ THIRD_PARTY_PAYMENT_METHOD_MODES={"query": "real", "refund": "disabled"}
75
+
76
+ # 挡板配置
77
+ THIRD_PARTY_PAYMENT_MOCK_DELAY=0.1 # 模拟网络延迟
78
+ """
79
+
80
+ from .base import BaseAdapter, CallRecord
81
+ from .config import (
82
+ AdapterMode,
83
+ AdapterSettings,
84
+ MockStrategy,
85
+ ThirdPartySettings,
86
+ )
87
+ from .decorators import adapter_method
88
+ from .exceptions import (
89
+ AdapterDisabledError,
90
+ AdapterError,
91
+ AdapterTimeoutError,
92
+ )
93
+ from .http import HttpAdapter
94
+
95
+ __all__ = [
96
+ # 基类
97
+ "BaseAdapter",
98
+ "HttpAdapter",
99
+ # 配置
100
+ "AdapterSettings",
101
+ "AdapterMode",
102
+ "MockStrategy",
103
+ "ThirdPartySettings",
104
+ # 装饰器
105
+ "adapter_method",
106
+ # 异常
107
+ "AdapterError",
108
+ "AdapterDisabledError",
109
+ "AdapterTimeoutError",
110
+ # 数据类
111
+ "CallRecord",
112
+ ]
@@ -0,0 +1,511 @@
1
+ """第三方接口适配器基类。
2
+
3
+ 本模块用于封装第三方接口(如支付、短信、微信、云存储等外部服务)的调用,
4
+ 提供统一的 Adapter 抽象,只约束"模式怎么切换",不限制具体调用方式。
5
+ 支持 HTTP、SDK、gRPC 等任意协议。
6
+
7
+ 核心职责:
8
+ - 模式路由:real(生产)/sandbox(沙箱)/mock(挡板)/disabled(禁用)
9
+ - 调用历史记录:记录每次调用的参数、结果、耗时,便于测试断言
10
+ - 挡板调用分发:自动根据配置切换真实调用与挡板实现
11
+ - 生命周期钩子:before_call、after_call、on_error 等
12
+
13
+ 挡板(Mock)配置说明:
14
+ 1. 全局挡板:通过 settings.mode="mock" 设置整个 Adapter 使用挡板
15
+ 2. 方法级挡板:通过 settings.method_modes={"create": "mock", "query": "real"}
16
+ 可以让不同方法使用不同模式(如查询走真实,写入走挡板)
17
+ 3. 挡板实现:使用 @adapter_method("name") + @method.mock 装饰器定义挡板逻辑
18
+ 4. 固定响应:简单场景可直接配置 mock_response 或 mock_method_responses
19
+
20
+ 设计原则:
21
+ - 最小约束:只管模式路由,不限制调用方式
22
+ - 可扩展:通过钩子方法支持自定义行为
23
+ - 可观测:记录所有调用历史
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import asyncio
29
+ from dataclasses import dataclass, field
30
+ from datetime import datetime
31
+ from typing import TYPE_CHECKING, Any
32
+
33
+ from aury.boot.common.logging import logger
34
+
35
+ from .config import AdapterMode, AdapterSettings
36
+
37
+ if TYPE_CHECKING:
38
+ from .decorators import AdapterMethodMeta
39
+
40
+
41
+ @dataclass
42
+ class CallRecord:
43
+ """调用记录。
44
+
45
+ 记录每次 Adapter 调用的详细信息,用于测试断言和调试。
46
+
47
+ Attributes:
48
+ operation: 操作名称
49
+ args: 位置参数
50
+ kwargs: 关键字参数
51
+ mode: 调用时的模式
52
+ timestamp: 调用时间戳
53
+ result: 调用结果(如果成功)
54
+ error: 异常信息(如果失败)
55
+ duration_ms: 调用耗时(毫秒)
56
+ """
57
+
58
+ operation: str
59
+ args: tuple[Any, ...]
60
+ kwargs: dict[str, Any]
61
+ mode: AdapterMode
62
+ timestamp: datetime = field(default_factory=datetime.now)
63
+ result: Any = None
64
+ error: str | None = None
65
+ duration_ms: float | None = None
66
+
67
+ def to_dict(self) -> dict[str, Any]:
68
+ """转换为字典。"""
69
+ return {
70
+ "operation": self.operation,
71
+ "args": self.args,
72
+ "kwargs": self.kwargs,
73
+ "mode": self.mode,
74
+ "timestamp": self.timestamp.isoformat(),
75
+ "result": self.result,
76
+ "error": self.error,
77
+ "duration_ms": self.duration_ms,
78
+ }
79
+
80
+
81
+ class BaseAdapter:
82
+ """第三方集成 Adapter 基类。
83
+
84
+ 封装模式路由和调用记录逻辑,子类只需实现具体的调用方法。
85
+
86
+ 核心功能:
87
+ - 根据 settings.mode 和 operation_modes 自动路由
88
+ - 支持 @adapter_method 装饰器声明的 mock 逻辑
89
+ - 记录调用历史,便于测试断言
90
+ - 提供生命周期钩子(before_call, after_call, on_error)
91
+
92
+ 使用示例:
93
+ class PaymentAdapter(BaseAdapter):
94
+ @adapter_method("create")
95
+ async def create_order(self, amount: int) -> dict:
96
+ # 这里是真实调用逻辑
97
+ return await some_sdk.create(amount=amount)
98
+
99
+ @create_order.mock
100
+ async def create_order_mock(self, amount: int) -> dict:
101
+ # mock 逻辑
102
+ return {"success": True, "mock": True}
103
+
104
+ # 使用
105
+ settings = AdapterSettings(mode="mock")
106
+ adapter = PaymentAdapter("payment", settings)
107
+ result = await adapter.create_order(100) # 走 mock
108
+
109
+ # 测试断言
110
+ assert len(adapter.call_history) == 1
111
+ assert adapter.call_history[0].mode == "mock"
112
+
113
+ Attributes:
114
+ name: Adapter 名称(用于日志和异常信息)
115
+ settings: 集成配置
116
+ """
117
+
118
+ def __init__(
119
+ self,
120
+ name: str,
121
+ settings: AdapterSettings,
122
+ ) -> None:
123
+ """初始化 Adapter。
124
+
125
+ Args:
126
+ name: Adapter 名称
127
+ settings: 集成配置
128
+ """
129
+ self.name = name
130
+ self.settings = settings
131
+ self._call_history: list[CallRecord] = []
132
+ self._initialized = False
133
+
134
+ logger.debug(
135
+ f"初始化 Adapter: {name} | "
136
+ f"mode={settings.mode} | "
137
+ f"enabled={settings.enabled}"
138
+ )
139
+
140
+ # ========== 公共 API ==========
141
+
142
+ @property
143
+ def call_history(self) -> list[CallRecord]:
144
+ """获取调用历史(只读)。
145
+
146
+ Returns:
147
+ list[CallRecord]: 调用记录列表
148
+ """
149
+ return list(self._call_history)
150
+
151
+ def clear_history(self) -> None:
152
+ """清空调用历史。
153
+
154
+ 通常在测试的 setUp 或 tearDown 中使用。
155
+ """
156
+ self._call_history.clear()
157
+
158
+ def get_calls_by_operation(self, operation: str) -> list[CallRecord]:
159
+ """获取指定操作的调用记录。
160
+
161
+ Args:
162
+ operation: 操作名称
163
+
164
+ Returns:
165
+ list[CallRecord]: 符合条件的调用记录
166
+ """
167
+ return [c for c in self._call_history if c.operation == operation]
168
+
169
+ def get_last_call(self, operation: str | None = None) -> CallRecord | None:
170
+ """获取最后一次调用记录。
171
+
172
+ Args:
173
+ operation: 操作名称(可选,不指定则返回最后一次任意调用)
174
+
175
+ Returns:
176
+ CallRecord | None: 最后一次调用记录
177
+ """
178
+ if operation:
179
+ calls = self.get_calls_by_operation(operation)
180
+ return calls[-1] if calls else None
181
+ return self._call_history[-1] if self._call_history else None
182
+
183
+ def assert_called(self, operation: str, times: int | None = None) -> None:
184
+ """断言操作被调用(用于测试)。
185
+
186
+ Args:
187
+ operation: 操作名称
188
+ times: 预期调用次数(可选,不指定则只检查是否被调用过)
189
+
190
+ Raises:
191
+ AssertionError: 断言失败
192
+ """
193
+ calls = self.get_calls_by_operation(operation)
194
+ if times is not None:
195
+ assert len(calls) == times, (
196
+ f"Expected {operation} to be called {times} times, "
197
+ f"but was called {len(calls)} times"
198
+ )
199
+ else:
200
+ assert len(calls) > 0, f"Expected {operation} to be called, but was never called"
201
+
202
+ def assert_not_called(self, operation: str) -> None:
203
+ """断言操作未被调用(用于测试)。
204
+
205
+ Args:
206
+ operation: 操作名称
207
+
208
+ Raises:
209
+ AssertionError: 断言失败
210
+ """
211
+ calls = self.get_calls_by_operation(operation)
212
+ assert len(calls) == 0, (
213
+ f"Expected {operation} to not be called, "
214
+ f"but was called {len(calls)} times"
215
+ )
216
+
217
+ # ========== 生命周期方法 ==========
218
+
219
+ async def initialize(self) -> None:
220
+ """初始化 Adapter(可选覆盖)。
221
+
222
+ 在首次使用前调用,用于初始化 SDK 客户端等资源。
223
+ """
224
+ if self._initialized:
225
+ return
226
+ await self._on_initialize()
227
+ self._initialized = True
228
+ logger.debug(f"Adapter 已初始化: {self.name}")
229
+
230
+ async def cleanup(self) -> None:
231
+ """清理 Adapter 资源(可选覆盖)。
232
+
233
+ 在关闭时调用,用于释放连接、关闭客户端等。
234
+ """
235
+ await self._on_cleanup()
236
+ self._initialized = False
237
+ logger.debug(f"Adapter 已清理: {self.name}")
238
+
239
+ async def __aenter__(self) -> BaseAdapter:
240
+ """异步上下文管理器入口。"""
241
+ await self.initialize()
242
+ return self
243
+
244
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
245
+ """异步上下文管理器出口。"""
246
+ await self.cleanup()
247
+
248
+ # ========== 内部方法(供装饰器使用) ==========
249
+
250
+ def _resolve_mode(self, meta: AdapterMethodMeta) -> AdapterMode:
251
+ """解析操作的有效模式。
252
+
253
+ 优先级:装饰器 mode_override > 配置 operation_modes > 配置全局 mode
254
+
255
+ Args:
256
+ meta: 操作元信息
257
+
258
+ Returns:
259
+ AdapterMode: 有效模式
260
+ """
261
+ # 1. 装饰器强制覆盖
262
+ if meta.mode_override:
263
+ return meta.mode_override
264
+
265
+ # 2. 配置 per-method 覆盖
266
+ if meta.name in self.settings.method_modes:
267
+ return self.settings.method_modes[meta.name]
268
+
269
+ # 3. 全局配置
270
+ return self.settings.mode
271
+
272
+ def _record_call(
273
+ self,
274
+ operation: str,
275
+ args: tuple[Any, ...],
276
+ kwargs: dict[str, Any],
277
+ mode: AdapterMode,
278
+ ) -> CallRecord:
279
+ """记录调用。
280
+
281
+ Args:
282
+ operation: 操作名称
283
+ args: 位置参数
284
+ kwargs: 关键字参数
285
+ mode: 调用模式
286
+
287
+ Returns:
288
+ CallRecord: 调用记录
289
+ """
290
+ record = CallRecord(
291
+ operation=operation,
292
+ args=args,
293
+ kwargs=kwargs,
294
+ mode=mode,
295
+ )
296
+ self._call_history.append(record)
297
+
298
+ logger.debug(
299
+ f"Adapter 调用: {self.name}.{operation} | "
300
+ f"mode={mode} | "
301
+ f"args={args} | "
302
+ f"kwargs={kwargs}"
303
+ )
304
+
305
+ return record
306
+
307
+ async def _invoke_mock(
308
+ self,
309
+ meta: AdapterMethodMeta,
310
+ args: tuple[Any, ...],
311
+ kwargs: dict[str, Any],
312
+ ) -> Any:
313
+ """调用 mock 逻辑。
314
+
315
+ 优先级:
316
+ 1. 装饰器注册的 mock_handler(带逻辑)
317
+ 2. 装饰器的 mock_response(固定值)
318
+ 3. 配置的 mock_operation_responses(按操作)
319
+ 4. 配置的 mock_default_response(全局)
320
+ 5. 根据 mock_strategy 生成默认响应
321
+
322
+ Args:
323
+ meta: 操作元信息
324
+ args: 位置参数
325
+ kwargs: 关键字参数
326
+
327
+ Returns:
328
+ Any: mock 响应
329
+ """
330
+ # 模拟延迟
331
+ if self.settings.mock_delay > 0:
332
+ await asyncio.sleep(self.settings.mock_delay)
333
+
334
+ # 1. 装饰器注册的 mock handler(带逻辑)
335
+ if meta.mock_handler:
336
+ logger.debug(f"使用 mock handler: {self.name}.{meta.name}")
337
+ return await meta.mock_handler(self, *args, **kwargs)
338
+
339
+ # 2. 装饰器的 mock_response
340
+ if meta.mock_response is not None:
341
+ logger.debug(f"使用装饰器 mock_response: {self.name}.{meta.name}")
342
+ return meta.mock_response
343
+
344
+ # 3. 配置的按方法 mock 响应
345
+ method_response = self.settings.mock_method_responses.get(meta.name)
346
+ if method_response is not None:
347
+ logger.debug(f"使用配置 mock_method_responses: {self.name}.{meta.name}")
348
+ return method_response
349
+
350
+ # 4. 配置的全局 mock 响应
351
+ if self.settings.mock_default_response is not None:
352
+ logger.debug(f"使用配置 mock_default_response: {self.name}.{meta.name}")
353
+ return self.settings.mock_default_response
354
+
355
+ # 5. 根据策略生成默认响应
356
+ return self._generate_mock_response(meta.name, args, kwargs)
357
+
358
+ def _generate_mock_response(
359
+ self,
360
+ operation: str,
361
+ args: tuple[Any, ...],
362
+ kwargs: dict[str, Any],
363
+ ) -> dict[str, Any]:
364
+ """根据 mock_strategy 生成默认响应。
365
+
366
+ Args:
367
+ operation: 操作名称
368
+ args: 位置参数
369
+ kwargs: 关键字参数
370
+
371
+ Returns:
372
+ dict: 默认 mock 响应
373
+ """
374
+ strategy = self.settings.mock_strategy
375
+
376
+ if strategy == "success":
377
+ return {
378
+ "success": True,
379
+ "mock": True,
380
+ "gateway": self.name,
381
+ "operation": operation,
382
+ }
383
+
384
+ if strategy == "failure":
385
+ return {
386
+ "success": False,
387
+ "mock": True,
388
+ "gateway": self.name,
389
+ "operation": operation,
390
+ "error": "mock failure",
391
+ }
392
+
393
+ if strategy == "echo":
394
+ return {
395
+ "success": True,
396
+ "mock": True,
397
+ "gateway": self.name,
398
+ "operation": operation,
399
+ "echo": {
400
+ "args": args,
401
+ "kwargs": kwargs,
402
+ },
403
+ }
404
+
405
+ # noop / custom / 其他
406
+ return {
407
+ "success": True,
408
+ "mock": True,
409
+ "gateway": self.name,
410
+ "operation": operation,
411
+ }
412
+
413
+ # ========== 钩子方法(子类可覆盖) ==========
414
+
415
+ async def _on_initialize(self) -> None:
416
+ """初始化钩子(子类可覆盖)。
417
+
418
+ 在 initialize() 中调用,用于初始化 SDK 客户端等。
419
+ """
420
+ pass
421
+
422
+ async def _on_cleanup(self) -> None:
423
+ """清理钩子(子类可覆盖)。
424
+
425
+ 在 cleanup() 中调用,用于释放资源。
426
+ """
427
+ pass
428
+
429
+ async def _on_before_call(
430
+ self,
431
+ operation: str,
432
+ args: tuple[Any, ...],
433
+ kwargs: dict[str, Any],
434
+ ) -> None:
435
+ """调用前钩子(子类可覆盖)。
436
+
437
+ 在真实调用前触发,可用于:
438
+ - 参数验证
439
+ - 日志记录
440
+ - 指标采集
441
+
442
+ Args:
443
+ operation: 操作名称
444
+ args: 位置参数
445
+ kwargs: 关键字参数
446
+ """
447
+ pass
448
+
449
+ async def _on_after_call(
450
+ self,
451
+ operation: str,
452
+ args: tuple[Any, ...],
453
+ kwargs: dict[str, Any],
454
+ result: Any,
455
+ ) -> None:
456
+ """调用后钩子(子类可覆盖)。
457
+
458
+ 在真实调用成功后触发,可用于:
459
+ - 响应日志
460
+ - 指标采集
461
+ - 结果缓存
462
+
463
+ Args:
464
+ operation: 操作名称
465
+ args: 位置参数
466
+ kwargs: 关键字参数
467
+ result: 调用结果
468
+ """
469
+ pass
470
+
471
+ async def _on_call_error(
472
+ self,
473
+ operation: str,
474
+ args: tuple[Any, ...],
475
+ kwargs: dict[str, Any],
476
+ error: Exception,
477
+ ) -> None:
478
+ """调用异常钩子(子类可覆盖)。
479
+
480
+ 在真实调用抛出异常时触发,可用于:
481
+ - 异常日志
482
+ - 告警
483
+ - 指标采集
484
+
485
+ 注意:此钩子不会吞掉异常,异常会继续向上抛出。
486
+
487
+ Args:
488
+ operation: 操作名称
489
+ args: 位置参数
490
+ kwargs: 关键字参数
491
+ error: 异常对象
492
+ """
493
+ logger.error(
494
+ f"Adapter 调用异常: {self.name}.{operation} | "
495
+ f"error={type(error).__name__}: {error}"
496
+ )
497
+
498
+ def __repr__(self) -> str:
499
+ """字符串表示。"""
500
+ return (
501
+ f"<{self.__class__.__name__} "
502
+ f"name={self.name!r} "
503
+ f"mode={self.settings.mode!r} "
504
+ f"enabled={self.settings.enabled}>"
505
+ )
506
+
507
+
508
+ __all__ = [
509
+ "BaseAdapter",
510
+ "CallRecord",
511
+ ]