kaq-quant-common 0.2.17__tar.gz → 0.2.18__tar.gz

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 (71) hide show
  1. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/PKG-INFO +1 -1
  2. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/common/__init__.py +1 -1
  3. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/common/api_interface.py +38 -38
  4. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/api_client_base.py +187 -187
  5. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/instruction/helper/commission_helper.py +141 -141
  6. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/instruction/helper/mock_order_helper.py +346 -346
  7. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/instruction/helper/order_helper.py +362 -362
  8. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/instruction/instruction_client.py +40 -2
  9. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/instruction/instruction_server_base.py +62 -53
  10. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/instruction/models/__init__.py +17 -17
  11. kaq_quant_common-0.2.18/kaq_quant_common/api/rest/instruction/models/kline.py +60 -0
  12. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/instruction/models/transfer.py +32 -32
  13. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/ws/exchange/models.py +23 -23
  14. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/ws/exchange/ws_exchange_server.py +440 -440
  15. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/ddb_table_monitor.py +106 -106
  16. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/http_monitor.py +69 -69
  17. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/monitor_base.py +84 -84
  18. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/monitor_group.py +97 -97
  19. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/ws_wrapper.py +21 -21
  20. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/logger_utils.py +4 -4
  21. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/signal_utils.py +23 -23
  22. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/uuid_utils.py +5 -5
  23. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/pyproject.toml +1 -1
  24. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/README.md +0 -0
  25. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/__init__.py +0 -0
  26. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/__init__.py +0 -0
  27. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/common/auth.py +0 -0
  28. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/__init__.py +0 -0
  29. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/api_server_base.py +0 -0
  30. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/instruction/models/account.py +0 -0
  31. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/instruction/models/loan.py +0 -0
  32. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/instruction/models/order.py +0 -0
  33. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/rest/instruction/models/position.py +0 -0
  34. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/ws/__init__.py +0 -0
  35. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/ws/exchange/ws_exchange_client.py +0 -0
  36. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/ws/instruction/__init__.py +0 -0
  37. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/ws/instruction/ws_instruction_client.py +0 -0
  38. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/ws/instruction/ws_instruction_server_base.py +0 -0
  39. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/ws/models.py +0 -0
  40. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/ws/ws_client_base.py +0 -0
  41. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/api/ws/ws_server_base.py +0 -0
  42. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/__init__.py +0 -0
  43. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/modules/funding_rate_helper.py +0 -0
  44. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/modules/limit_order_helper.py +0 -0
  45. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/modules/limit_order_symbol_monitor.py +0 -0
  46. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/modules/limit_order_symbol_monitor_group.py +0 -0
  47. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/redis_table_monitor.py +0 -0
  48. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/statistics/funding_rate_history_statistics.py +0 -0
  49. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/common/statistics/kline_history_statistics.py +0 -0
  50. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/config/config.yaml +0 -0
  51. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/resources/__init__.py +0 -0
  52. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/resources/kaq_ddb_pool_stream_read_resources.py +0 -0
  53. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/resources/kaq_ddb_stream_init_resources.py +0 -0
  54. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/resources/kaq_ddb_stream_read_resources.py +0 -0
  55. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/resources/kaq_ddb_stream_write_resources.py +0 -0
  56. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/resources/kaq_mysql_init_resources.py +0 -0
  57. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/resources/kaq_mysql_resources.py +0 -0
  58. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/resources/kaq_postgresql_resources.py +0 -0
  59. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/resources/kaq_quant_hive_resources.py +0 -0
  60. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/resources/kaq_redis_resources.py +0 -0
  61. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/__init__.py +0 -0
  62. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/dagster_job_check_utils.py +0 -0
  63. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/dagster_utils.py +0 -0
  64. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/date_util.py +0 -0
  65. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/enums_utils.py +0 -0
  66. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/error_utils.py +0 -0
  67. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/hash_utils.py +0 -0
  68. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/log_time_utils.py +0 -0
  69. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/mytt_utils.py +0 -0
  70. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/sqlite_utils.py +0 -0
  71. {kaq_quant_common-0.2.17 → kaq_quant_common-0.2.18}/kaq_quant_common/utils/yml_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kaq_quant_common
3
- Version: 0.2.17
3
+ Version: 0.2.18
4
4
  Summary:
5
5
  Author: kevinfuture
6
6
  Author-email: liuenbofuture@foxmail.com
@@ -1,38 +1,38 @@
1
- from abc import ABC
2
- from functools import wraps
3
- from typing import Callable, Type
4
-
5
- from pydantic import BaseModel
6
-
7
-
8
- def api_method(request_model: Type[BaseModel], response_model: Type[BaseModel]):
9
- """
10
- api 方法注解
11
- :param request_model: 请求模型
12
- :param response_model: 响应模型
13
- :return:
14
- """
15
-
16
- def decorator(func: Callable):
17
- # 将注解信息绑定到原始函数
18
- func._is_api_method = True
19
- func._request_model = request_model
20
- func._response_model = response_model
21
-
22
- @wraps(func)
23
- def wrapper(*args, **kwargs):
24
- return func(*args, **kwargs)
25
-
26
- # 同步注解信息到包装函数,便于通过inspect发现
27
- wrapper._is_api_method = True
28
- wrapper._request_model = request_model
29
- wrapper._response_model = response_model
30
-
31
- return wrapper
32
-
33
- return decorator
34
-
35
-
36
- # 定义 api 接口,暂时没啥用
37
- class ApiInterface(ABC):
38
- pass
1
+ from abc import ABC
2
+ from functools import wraps
3
+ from typing import Callable, Type
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ def api_method(request_model: Type[BaseModel], response_model: Type[BaseModel]):
9
+ """
10
+ api 方法注解
11
+ :param request_model: 请求模型
12
+ :param response_model: 响应模型
13
+ :return:
14
+ """
15
+
16
+ def decorator(func: Callable):
17
+ # 将注解信息绑定到原始函数
18
+ func._is_api_method = True
19
+ func._request_model = request_model
20
+ func._response_model = response_model
21
+
22
+ @wraps(func)
23
+ def wrapper(*args, **kwargs):
24
+ return func(*args, **kwargs)
25
+
26
+ # 同步注解信息到包装函数,便于通过inspect发现
27
+ wrapper._is_api_method = True
28
+ wrapper._request_model = request_model
29
+ wrapper._response_model = response_model
30
+
31
+ return wrapper
32
+
33
+ return decorator
34
+
35
+
36
+ # 定义 api 接口,暂时没啥用
37
+ class ApiInterface(ABC):
38
+ pass
@@ -1,187 +1,187 @@
1
- from typing import Optional, Type, TypeVar, Callable
2
- import asyncio
3
- import threading
4
- import time
5
-
6
- import requests
7
- try:
8
- import httpx
9
- HTTPX_AVAILABLE = True
10
- except ImportError:
11
- HTTPX_AVAILABLE = False
12
-
13
- from kaq_quant_common.api.common.auth import get_auth_token
14
- from kaq_quant_common.utils import logger_utils
15
- from pydantic import BaseModel
16
-
17
- R = TypeVar("R", bound=BaseModel)
18
-
19
-
20
- class ApiClientBase:
21
- """
22
- api 客户端
23
- """
24
-
25
- # 类级别的共享 event loop 线程
26
- _shared_loop: Optional[asyncio.AbstractEventLoop] = None
27
- _shared_loop_thread: Optional[threading.Thread] = None
28
- _loop_lock = threading.Lock()
29
-
30
- def __init__(self, base_url: str, token: Optional[str] = None):
31
- self._base_url = base_url.rstrip("/")
32
- self._token = token if token is not None else get_auth_token()
33
- self._logger = logger_utils.get_logger(self)
34
- # 异步客户端(懒加载)
35
- self._async_client: Optional[httpx.AsyncClient] = None
36
-
37
- @classmethod
38
- def _ensure_shared_loop(cls):
39
- """确保共享的 event loop 已创建并运行"""
40
- if cls._shared_loop is None or not cls._shared_loop.is_running():
41
- with cls._loop_lock:
42
- # 双重检查
43
- if cls._shared_loop is None or not cls._shared_loop.is_running():
44
- def run_loop():
45
- """在后台线程中运行 event loop"""
46
- cls._shared_loop = asyncio.new_event_loop()
47
- asyncio.set_event_loop(cls._shared_loop)
48
- cls._shared_loop.run_forever()
49
-
50
- cls._shared_loop_thread = threading.Thread(
51
- target=run_loop,
52
- daemon=True,
53
- name="ApiClient-EventLoop"
54
- )
55
- cls._shared_loop_thread.start()
56
-
57
- # 等待 loop 启动
58
- while cls._shared_loop is None:
59
- time.sleep(0.01)
60
-
61
- # 发送请求
62
- def _make_request(self, method_name: str, request_data: BaseModel, response_model: Type[R]) -> R:
63
- url = f"{self._base_url}/api/{method_name}"
64
- headers = {}
65
- if self._token:
66
- headers["Authorization"] = f"Bearer {self._token}"
67
- try:
68
- # 发送post请求
69
- response = requests.post(url, json=request_data.model_dump(), headers=headers or None)
70
- # 检查响应状态码,如果不成功,则尝试解析错误信息并抛出异常
71
- if not response.ok:
72
- try:
73
- error_data = response.json()
74
- error_message = error_data.get("error", response.text)
75
- except ValueError:
76
- error_message = response.text
77
- raise requests.exceptions.HTTPError(f"HTTP error occurred: {response.status_code} - {error_message}", response=response)
78
- # 返回请求结果
79
- return response_model(**response.json())
80
- except requests.exceptions.RequestException as e:
81
- self._logger.error(f"An error occurred: {e}")
82
- raise
83
-
84
- # 异步发送请求
85
- async def _make_request_async(self, method_name: str, request_data: BaseModel, response_model: Type[R]) -> R:
86
- """
87
- 异步发送请求
88
-
89
- Args:
90
- method_name: API方法名
91
- request_data: 请求数据
92
- response_model: 响应模型
93
-
94
- Returns:
95
- 响应对象
96
- """
97
- if not HTTPX_AVAILABLE:
98
- raise RuntimeError("httpx library is required for async requests. Install it with: pip install httpx")
99
-
100
- url = f"{self._base_url}/api/{method_name}"
101
- headers = {}
102
- if self._token:
103
- headers["Authorization"] = f"Bearer {self._token}"
104
-
105
- # 懒加载创建async client
106
- if self._async_client is None:
107
- self._async_client = httpx.AsyncClient(timeout=30.0)
108
-
109
- try:
110
- # 发送异步post请求
111
- response = await self._async_client.post(
112
- url,
113
- json=request_data.model_dump(),
114
- headers=headers or None
115
- )
116
- # 检查响应状态码
117
- if not response.is_success:
118
- try:
119
- error_data = response.json()
120
- error_message = error_data.get("error", response.text)
121
- except ValueError:
122
- error_message = response.text
123
- raise httpx.HTTPStatusError(
124
- f"HTTP error occurred: {response.status_code} - {error_message}",
125
- request=response.request,
126
- response=response
127
- )
128
- # 返回请求结果
129
- return response_model(**response.json())
130
- except httpx.HTTPStatusError:
131
- raise
132
- except Exception as e:
133
- self._logger.error(f"An error occurred: {e}")
134
- raise
135
-
136
- # 回调方式发送请求
137
- def _make_request_callback(
138
- self,
139
- method_name: str,
140
- request_data: BaseModel,
141
- response_model: Type[R],
142
- on_success: Optional[Callable[[R], None]] = None,
143
- on_error: Optional[Callable[[Exception], None]] = None
144
- ) -> None:
145
- """
146
- 回调方式发送请求,使用共享的 event loop
147
-
148
- Args:
149
- method_name: API方法名
150
- request_data: 请求数据
151
- response_model: 响应模型
152
- on_success: 成功回调,接收响应对象,可选
153
- on_error: 错误回调,接收异常对象,可选
154
- """
155
- # 确保共享 loop 已启动
156
- self._ensure_shared_loop()
157
-
158
- async def _async_task():
159
- """异步任务"""
160
- try:
161
- result = await self._make_request_async(method_name, request_data, response_model)
162
- # 调用成功回调(如果提供)
163
- if on_success:
164
- on_success(result)
165
- except Exception as e:
166
- # 调用错误回调
167
- if on_error:
168
- on_error(e)
169
- else:
170
- self._logger.error(f"Callback request failed: {e}")
171
-
172
- # 将任务提交到共享的 event loop
173
- asyncio.run_coroutine_threadsafe(_async_task(), self._shared_loop)
174
-
175
- async def close_async(self):
176
- """关闭异步客户端连接"""
177
- if self._async_client is not None:
178
- await self._async_client.aclose()
179
- self._async_client = None
180
-
181
- async def __aenter__(self):
182
- """异步上下文管理器入口"""
183
- return self
184
-
185
- async def __aexit__(self, exc_type, exc_val, exc_tb):
186
- """异步上下文管理器出口"""
187
- await self.close_async()
1
+ from typing import Optional, Type, TypeVar, Callable
2
+ import asyncio
3
+ import threading
4
+ import time
5
+
6
+ import requests
7
+ try:
8
+ import httpx
9
+ HTTPX_AVAILABLE = True
10
+ except ImportError:
11
+ HTTPX_AVAILABLE = False
12
+
13
+ from kaq_quant_common.api.common.auth import get_auth_token
14
+ from kaq_quant_common.utils import logger_utils
15
+ from pydantic import BaseModel
16
+
17
+ R = TypeVar("R", bound=BaseModel)
18
+
19
+
20
+ class ApiClientBase:
21
+ """
22
+ api 客户端
23
+ """
24
+
25
+ # 类级别的共享 event loop 线程
26
+ _shared_loop: Optional[asyncio.AbstractEventLoop] = None
27
+ _shared_loop_thread: Optional[threading.Thread] = None
28
+ _loop_lock = threading.Lock()
29
+
30
+ def __init__(self, base_url: str, token: Optional[str] = None):
31
+ self._base_url = base_url.rstrip("/")
32
+ self._token = token if token is not None else get_auth_token()
33
+ self._logger = logger_utils.get_logger(self)
34
+ # 异步客户端(懒加载)
35
+ self._async_client: Optional[httpx.AsyncClient] = None
36
+
37
+ @classmethod
38
+ def _ensure_shared_loop(cls):
39
+ """确保共享的 event loop 已创建并运行"""
40
+ if cls._shared_loop is None or not cls._shared_loop.is_running():
41
+ with cls._loop_lock:
42
+ # 双重检查
43
+ if cls._shared_loop is None or not cls._shared_loop.is_running():
44
+ def run_loop():
45
+ """在后台线程中运行 event loop"""
46
+ cls._shared_loop = asyncio.new_event_loop()
47
+ asyncio.set_event_loop(cls._shared_loop)
48
+ cls._shared_loop.run_forever()
49
+
50
+ cls._shared_loop_thread = threading.Thread(
51
+ target=run_loop,
52
+ daemon=True,
53
+ name="ApiClient-EventLoop"
54
+ )
55
+ cls._shared_loop_thread.start()
56
+
57
+ # 等待 loop 启动
58
+ while cls._shared_loop is None:
59
+ time.sleep(0.01)
60
+
61
+ # 发送请求
62
+ def _make_request(self, method_name: str, request_data: BaseModel, response_model: Type[R]) -> R:
63
+ url = f"{self._base_url}/api/{method_name}"
64
+ headers = {}
65
+ if self._token:
66
+ headers["Authorization"] = f"Bearer {self._token}"
67
+ try:
68
+ # 发送post请求
69
+ response = requests.post(url, json=request_data.model_dump(), headers=headers or None)
70
+ # 检查响应状态码,如果不成功,则尝试解析错误信息并抛出异常
71
+ if not response.ok:
72
+ try:
73
+ error_data = response.json()
74
+ error_message = error_data.get("error", response.text)
75
+ except ValueError:
76
+ error_message = response.text
77
+ raise requests.exceptions.HTTPError(f"HTTP error occurred: {response.status_code} - {error_message}", response=response)
78
+ # 返回请求结果
79
+ return response_model(**response.json())
80
+ except requests.exceptions.RequestException as e:
81
+ self._logger.error(f"An error occurred: {e}")
82
+ raise
83
+
84
+ # 异步发送请求
85
+ async def _make_request_async(self, method_name: str, request_data: BaseModel, response_model: Type[R]) -> R:
86
+ """
87
+ 异步发送请求
88
+
89
+ Args:
90
+ method_name: API方法名
91
+ request_data: 请求数据
92
+ response_model: 响应模型
93
+
94
+ Returns:
95
+ 响应对象
96
+ """
97
+ if not HTTPX_AVAILABLE:
98
+ raise RuntimeError("httpx library is required for async requests. Install it with: pip install httpx")
99
+
100
+ url = f"{self._base_url}/api/{method_name}"
101
+ headers = {}
102
+ if self._token:
103
+ headers["Authorization"] = f"Bearer {self._token}"
104
+
105
+ # 懒加载创建async client
106
+ if self._async_client is None:
107
+ self._async_client = httpx.AsyncClient(timeout=30.0)
108
+
109
+ try:
110
+ # 发送异步post请求
111
+ response = await self._async_client.post(
112
+ url,
113
+ json=request_data.model_dump(),
114
+ headers=headers or None
115
+ )
116
+ # 检查响应状态码
117
+ if not response.is_success:
118
+ try:
119
+ error_data = response.json()
120
+ error_message = error_data.get("error", response.text)
121
+ except ValueError:
122
+ error_message = response.text
123
+ raise httpx.HTTPStatusError(
124
+ f"HTTP error occurred: {response.status_code} - {error_message}",
125
+ request=response.request,
126
+ response=response
127
+ )
128
+ # 返回请求结果
129
+ return response_model(**response.json())
130
+ except httpx.HTTPStatusError:
131
+ raise
132
+ except Exception as e:
133
+ self._logger.error(f"An error occurred: {e}")
134
+ raise
135
+
136
+ # 回调方式发送请求
137
+ def _make_request_callback(
138
+ self,
139
+ method_name: str,
140
+ request_data: BaseModel,
141
+ response_model: Type[R],
142
+ on_success: Optional[Callable[[R], None]] = None,
143
+ on_error: Optional[Callable[[Exception], None]] = None
144
+ ) -> None:
145
+ """
146
+ 回调方式发送请求,使用共享的 event loop
147
+
148
+ Args:
149
+ method_name: API方法名
150
+ request_data: 请求数据
151
+ response_model: 响应模型
152
+ on_success: 成功回调,接收响应对象,可选
153
+ on_error: 错误回调,接收异常对象,可选
154
+ """
155
+ # 确保共享 loop 已启动
156
+ self._ensure_shared_loop()
157
+
158
+ async def _async_task():
159
+ """异步任务"""
160
+ try:
161
+ result = await self._make_request_async(method_name, request_data, response_model)
162
+ # 调用成功回调(如果提供)
163
+ if on_success:
164
+ on_success(result)
165
+ except Exception as e:
166
+ # 调用错误回调
167
+ if on_error:
168
+ on_error(e)
169
+ else:
170
+ self._logger.error(f"Callback request failed: {e}")
171
+
172
+ # 将任务提交到共享的 event loop
173
+ asyncio.run_coroutine_threadsafe(_async_task(), self._shared_loop)
174
+
175
+ async def close_async(self):
176
+ """关闭异步客户端连接"""
177
+ if self._async_client is not None:
178
+ await self._async_client.aclose()
179
+ self._async_client = None
180
+
181
+ async def __aenter__(self):
182
+ """异步上下文管理器入口"""
183
+ return self
184
+
185
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
186
+ """异步上下文管理器出口"""
187
+ await self.close_async()