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.
- aury/boot/_version.py +2 -2
- aury/boot/application/__init__.py +15 -0
- aury/boot/application/adapter/__init__.py +112 -0
- aury/boot/application/adapter/base.py +511 -0
- aury/boot/application/adapter/config.py +242 -0
- aury/boot/application/adapter/decorators.py +259 -0
- aury/boot/application/adapter/exceptions.py +202 -0
- aury/boot/application/adapter/http.py +325 -0
- aury/boot/application/app/middlewares.py +7 -4
- aury/boot/application/config/multi_instance.py +42 -26
- aury/boot/application/config/settings.py +111 -191
- aury/boot/application/middleware/logging.py +14 -1
- aury/boot/commands/generate.py +22 -22
- aury/boot/commands/init.py +41 -9
- aury/boot/commands/templates/project/AGENTS.md.tpl +8 -4
- aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +17 -16
- aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +82 -43
- aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +14 -14
- aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +40 -28
- aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +9 -9
- aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +8 -8
- aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl +403 -0
- aury/boot/commands/templates/project/aury_docs/99-cli.md.tpl +19 -19
- aury/boot/commands/templates/project/config.py.tpl +10 -10
- aury/boot/commands/templates/project/env_templates/_header.tpl +10 -0
- aury/boot/commands/templates/project/env_templates/admin.tpl +49 -0
- aury/boot/commands/templates/project/env_templates/cache.tpl +14 -0
- aury/boot/commands/templates/project/env_templates/database.tpl +22 -0
- aury/boot/commands/templates/project/env_templates/log.tpl +18 -0
- aury/boot/commands/templates/project/env_templates/messaging.tpl +46 -0
- aury/boot/commands/templates/project/env_templates/rpc.tpl +28 -0
- aury/boot/commands/templates/project/env_templates/scheduler.tpl +18 -0
- aury/boot/commands/templates/project/env_templates/service.tpl +18 -0
- aury/boot/commands/templates/project/env_templates/storage.tpl +38 -0
- aury/boot/commands/templates/project/env_templates/third_party.tpl +43 -0
- aury/boot/common/logging/__init__.py +26 -674
- aury/boot/common/logging/context.py +132 -0
- aury/boot/common/logging/decorators.py +118 -0
- aury/boot/common/logging/format.py +315 -0
- aury/boot/common/logging/setup.py +214 -0
- aury/boot/infrastructure/database/config.py +6 -14
- aury/boot/infrastructure/tasks/config.py +5 -13
- aury/boot/infrastructure/tasks/manager.py +8 -4
- aury/boot/testing/base.py +2 -2
- {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/METADATA +2 -1
- {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/RECORD +48 -27
- aury/boot/commands/templates/project/env.example.tpl +0 -281
- {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/WHEEL +0 -0
- {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.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
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
|
+
]
|