aury-boot 0.0.4__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 (122) hide show
  1. aury/boot/__init__.py +2 -2
  2. aury/boot/_version.py +2 -2
  3. aury/boot/application/__init__.py +60 -36
  4. aury/boot/application/adapter/__init__.py +112 -0
  5. aury/boot/application/adapter/base.py +511 -0
  6. aury/boot/application/adapter/config.py +242 -0
  7. aury/boot/application/adapter/decorators.py +259 -0
  8. aury/boot/application/adapter/exceptions.py +202 -0
  9. aury/boot/application/adapter/http.py +325 -0
  10. aury/boot/application/app/__init__.py +12 -8
  11. aury/boot/application/app/base.py +12 -0
  12. aury/boot/application/app/components.py +137 -44
  13. aury/boot/application/app/middlewares.py +9 -4
  14. aury/boot/application/app/startup.py +249 -0
  15. aury/boot/application/config/__init__.py +36 -1
  16. aury/boot/application/config/multi_instance.py +216 -0
  17. aury/boot/application/config/settings.py +398 -149
  18. aury/boot/application/constants/components.py +6 -0
  19. aury/boot/application/errors/handlers.py +17 -3
  20. aury/boot/application/middleware/logging.py +21 -120
  21. aury/boot/application/rpc/__init__.py +2 -2
  22. aury/boot/commands/__init__.py +30 -10
  23. aury/boot/commands/app.py +131 -1
  24. aury/boot/commands/docs.py +104 -17
  25. aury/boot/commands/generate.py +22 -22
  26. aury/boot/commands/init.py +68 -17
  27. aury/boot/commands/server/app.py +2 -3
  28. aury/boot/commands/templates/project/AGENTS.md.tpl +221 -0
  29. aury/boot/commands/templates/project/README.md.tpl +2 -2
  30. aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +59 -0
  31. aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +184 -0
  32. aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl +206 -0
  33. aury/boot/commands/templates/project/aury_docs/03-service.md.tpl +398 -0
  34. aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl +95 -0
  35. aury/boot/commands/templates/project/aury_docs/05-api.md.tpl +116 -0
  36. aury/boot/commands/templates/project/aury_docs/06-exception.md.tpl +118 -0
  37. aury/boot/commands/templates/project/aury_docs/07-cache.md.tpl +122 -0
  38. aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl +32 -0
  39. aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl +38 -0
  40. aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl +115 -0
  41. aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +131 -0
  42. aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +56 -0
  43. aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +104 -0
  44. aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +102 -0
  45. aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +147 -0
  46. aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl +403 -0
  47. aury/boot/commands/templates/project/{CLI.md.tpl → aury_docs/99-cli.md.tpl} +19 -19
  48. aury/boot/commands/templates/project/config.py.tpl +10 -10
  49. aury/boot/commands/templates/project/env_templates/_header.tpl +10 -0
  50. aury/boot/commands/templates/project/env_templates/admin.tpl +49 -0
  51. aury/boot/commands/templates/project/env_templates/cache.tpl +14 -0
  52. aury/boot/commands/templates/project/env_templates/database.tpl +22 -0
  53. aury/boot/commands/templates/project/env_templates/log.tpl +18 -0
  54. aury/boot/commands/templates/project/env_templates/messaging.tpl +46 -0
  55. aury/boot/commands/templates/project/env_templates/rpc.tpl +28 -0
  56. aury/boot/commands/templates/project/env_templates/scheduler.tpl +18 -0
  57. aury/boot/commands/templates/project/env_templates/service.tpl +18 -0
  58. aury/boot/commands/templates/project/env_templates/storage.tpl +38 -0
  59. aury/boot/commands/templates/project/env_templates/third_party.tpl +43 -0
  60. aury/boot/commands/templates/project/modules/tasks.py.tpl +1 -1
  61. aury/boot/common/logging/__init__.py +26 -674
  62. aury/boot/common/logging/context.py +132 -0
  63. aury/boot/common/logging/decorators.py +118 -0
  64. aury/boot/common/logging/format.py +315 -0
  65. aury/boot/common/logging/setup.py +214 -0
  66. aury/boot/contrib/admin_console/auth.py +2 -3
  67. aury/boot/contrib/admin_console/install.py +1 -1
  68. aury/boot/domain/models/mixins.py +48 -1
  69. aury/boot/domain/pagination/__init__.py +94 -0
  70. aury/boot/domain/repository/impl.py +1 -1
  71. aury/boot/domain/repository/interface.py +1 -1
  72. aury/boot/domain/transaction/__init__.py +8 -9
  73. aury/boot/infrastructure/__init__.py +86 -29
  74. aury/boot/infrastructure/cache/backends.py +102 -18
  75. aury/boot/infrastructure/cache/base.py +12 -0
  76. aury/boot/infrastructure/cache/manager.py +153 -91
  77. aury/boot/infrastructure/channel/__init__.py +24 -0
  78. aury/boot/infrastructure/channel/backends/__init__.py +9 -0
  79. aury/boot/infrastructure/channel/backends/memory.py +83 -0
  80. aury/boot/infrastructure/channel/backends/redis.py +88 -0
  81. aury/boot/infrastructure/channel/base.py +92 -0
  82. aury/boot/infrastructure/channel/manager.py +203 -0
  83. aury/boot/infrastructure/clients/__init__.py +22 -0
  84. aury/boot/infrastructure/clients/rabbitmq/__init__.py +9 -0
  85. aury/boot/infrastructure/clients/rabbitmq/config.py +46 -0
  86. aury/boot/infrastructure/clients/rabbitmq/manager.py +288 -0
  87. aury/boot/infrastructure/clients/redis/__init__.py +28 -0
  88. aury/boot/infrastructure/clients/redis/config.py +51 -0
  89. aury/boot/infrastructure/clients/redis/manager.py +264 -0
  90. aury/boot/infrastructure/database/config.py +7 -16
  91. aury/boot/infrastructure/database/manager.py +16 -38
  92. aury/boot/infrastructure/events/__init__.py +18 -21
  93. aury/boot/infrastructure/events/backends/__init__.py +11 -0
  94. aury/boot/infrastructure/events/backends/memory.py +86 -0
  95. aury/boot/infrastructure/events/backends/rabbitmq.py +193 -0
  96. aury/boot/infrastructure/events/backends/redis.py +162 -0
  97. aury/boot/infrastructure/events/base.py +127 -0
  98. aury/boot/infrastructure/events/manager.py +224 -0
  99. aury/boot/infrastructure/mq/__init__.py +24 -0
  100. aury/boot/infrastructure/mq/backends/__init__.py +9 -0
  101. aury/boot/infrastructure/mq/backends/rabbitmq.py +179 -0
  102. aury/boot/infrastructure/mq/backends/redis.py +167 -0
  103. aury/boot/infrastructure/mq/base.py +143 -0
  104. aury/boot/infrastructure/mq/manager.py +239 -0
  105. aury/boot/infrastructure/scheduler/manager.py +7 -3
  106. aury/boot/infrastructure/storage/__init__.py +9 -9
  107. aury/boot/infrastructure/storage/base.py +17 -5
  108. aury/boot/infrastructure/storage/factory.py +0 -1
  109. aury/boot/infrastructure/tasks/__init__.py +2 -2
  110. aury/boot/infrastructure/tasks/config.py +5 -13
  111. aury/boot/infrastructure/tasks/manager.py +55 -33
  112. {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/METADATA +20 -2
  113. aury_boot-0.0.7.dist-info/RECORD +197 -0
  114. aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +0 -1397
  115. aury/boot/commands/templates/project/env.example.tpl +0 -213
  116. aury/boot/infrastructure/events/bus.py +0 -362
  117. aury/boot/infrastructure/events/config.py +0 -52
  118. aury/boot/infrastructure/events/consumer.py +0 -134
  119. aury/boot/infrastructure/events/models.py +0 -63
  120. aury_boot-0.0.4.dist-info/RECORD +0 -137
  121. {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/WHEEL +0 -0
  122. {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/entry_points.txt +0 -0
@@ -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
+ ]