springbootAI 1.8.0__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 (175) hide show
  1. spring/__init__.py +66 -0
  2. spring/ai/__init__.py +78 -0
  3. spring/ai/advisors.py +139 -0
  4. spring/ai/annotations.py +74 -0
  5. spring/ai/autoconfig.py +481 -0
  6. spring/ai/core.py +391 -0
  7. spring/ai/etl.py +188 -0
  8. spring/ai/memory.py +109 -0
  9. spring/ai/observability.py +129 -0
  10. spring/ai/providers.py +789 -0
  11. spring/ai/resilience.py +258 -0
  12. spring/ai/tools.py +106 -0
  13. spring/ai/vectorstore.py +303 -0
  14. spring/annotations/__init__.py +188 -0
  15. spring/annotations/cache.py +126 -0
  16. spring/annotations/cloud.py +207 -0
  17. spring/annotations/conditional.py +272 -0
  18. spring/annotations/core.py +864 -0
  19. spring/annotations/messaging.py +107 -0
  20. spring/aop/__init__.py +4 -0
  21. spring/aop/cloud_aop.py +404 -0
  22. spring/aop/comprehensive_aop.py +1015 -0
  23. spring/aop/method_interceptor.py +19 -0
  24. spring/aop/proxy_factory.py +55 -0
  25. spring/cloud/__init__.py +76 -0
  26. spring/cloud/discovery.py +364 -0
  27. spring/cloud/feign.py +469 -0
  28. spring/cloud/gateway.py +452 -0
  29. spring/cloud/load_balancer.py +149 -0
  30. spring/cloud/seata.py +557 -0
  31. spring/cloud/sentinel.py +525 -0
  32. spring/cloud/tracer.py +337 -0
  33. spring/config/__init__.py +21 -0
  34. spring/config/binding.py +206 -0
  35. spring/config/config_loader.py +405 -0
  36. spring/context/__init__.py +13 -0
  37. spring/context/application_context.py +589 -0
  38. spring/context/bean_definition.py +70 -0
  39. spring/context/bean_factory.py +1052 -0
  40. spring/context/registry.py +58 -0
  41. spring/context/scanner.py +106 -0
  42. spring/core/__init__.py +3 -0
  43. spring/core/graceful_shutdown.py +196 -0
  44. spring/core/typing_utils.py +50 -0
  45. spring/csv/__init__.py +52 -0
  46. spring/csv/annotations.py +402 -0
  47. spring/csv/converters.py +69 -0
  48. spring/csv/easy_csv.py +95 -0
  49. spring/csv/exceptions.py +27 -0
  50. spring/csv/reader.py +195 -0
  51. spring/csv/writer.py +155 -0
  52. spring/data/__init__.py +54 -0
  53. spring/data/page.py +181 -0
  54. spring/data/repository.py +274 -0
  55. spring/data/specification.py +228 -0
  56. spring/datasource/__init__.py +66 -0
  57. spring/datasource/annotations.py +133 -0
  58. spring/datasource/context.py +69 -0
  59. spring/datasource/dynamic.py +148 -0
  60. spring/event/__init__.py +7 -0
  61. spring/event/publisher.py +69 -0
  62. spring/excel/__init__.py +51 -0
  63. spring/excel/annotations.py +405 -0
  64. spring/excel/converters.py +231 -0
  65. spring/excel/easy_excel.py +94 -0
  66. spring/excel/exceptions.py +31 -0
  67. spring/excel/reader.py +254 -0
  68. spring/excel/style.py +95 -0
  69. spring/excel/writer.py +197 -0
  70. spring/i18n/__init__.py +97 -0
  71. spring/i18n/accessor.py +94 -0
  72. spring/i18n/auto_config.py +177 -0
  73. spring/i18n/holder.py +106 -0
  74. spring/i18n/locale.py +152 -0
  75. spring/i18n/locale_resolver.py +367 -0
  76. spring/i18n/message_source.py +250 -0
  77. spring/i18n/middleware.py +79 -0
  78. spring/i18n/properties.py +168 -0
  79. spring/i18n/sources.py +255 -0
  80. spring/logging/__init__.py +1 -0
  81. spring/logging/loguru_logger.py +228 -0
  82. spring/main.py +378 -0
  83. spring/messaging/__init__.py +1 -0
  84. spring/messaging/rabbitmq.py +302 -0
  85. spring/monitoring/__init__.py +1 -0
  86. spring/monitoring/prometheus.py +199 -0
  87. spring/orm/__init__.py +258 -0
  88. spring/orm/database.py +222 -0
  89. spring/orm/ddl_auto.py +1217 -0
  90. spring/orm/migration.py +419 -0
  91. spring/orm/mybatis_integration.py +400 -0
  92. spring/orm/pymybatis/__init__.py +86 -0
  93. spring/orm/pymybatis/annotations/__init__.py +30 -0
  94. spring/orm/pymybatis/annotations/annotations.py +332 -0
  95. spring/orm/pymybatis/cache/__init__.py +47 -0
  96. spring/orm/pymybatis/cache/cache.py +371 -0
  97. spring/orm/pymybatis/cache/redis_cache.py +434 -0
  98. spring/orm/pymybatis/circuit_breaker/__init__.py +21 -0
  99. spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +424 -0
  100. spring/orm/pymybatis/configuration.py +525 -0
  101. spring/orm/pymybatis/core/__init__.py +10 -0
  102. spring/orm/pymybatis/core/sql_session.py +1382 -0
  103. spring/orm/pymybatis/core/sql_session_factory.py +76 -0
  104. spring/orm/pymybatis/dialect/__init__.py +9 -0
  105. spring/orm/pymybatis/dialect/dialect.py +445 -0
  106. spring/orm/pymybatis/dynamic_sql/__init__.py +9 -0
  107. spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +900 -0
  108. spring/orm/pymybatis/interceptor/__init__.py +31 -0
  109. spring/orm/pymybatis/interceptor/interceptor.py +427 -0
  110. spring/orm/pymybatis/mapper/__init__.py +9 -0
  111. spring/orm/pymybatis/mapper/mapper.py +540 -0
  112. spring/orm/pymybatis/metrics/__init__.py +41 -0
  113. spring/orm/pymybatis/metrics/metrics.py +595 -0
  114. spring/orm/pymybatis/pool/__init__.py +9 -0
  115. spring/orm/pymybatis/pool/connection_pool.py +711 -0
  116. spring/orm/pymybatis/security/__init__.py +19 -0
  117. spring/orm/pymybatis/security/access_control.py +415 -0
  118. spring/orm/pymybatis/security/password_encoder.py +293 -0
  119. spring/orm/pymybatis/security/sensitive_data_masker.py +326 -0
  120. spring/orm/pymybatis/security/sql_injection_detector.py +675 -0
  121. spring/orm/pymybatis/transaction/__init__.py +9 -0
  122. spring/orm/pymybatis/transaction/transaction.py +288 -0
  123. spring/orm/pymybatis/type_handler/__init__.py +37 -0
  124. spring/orm/pymybatis/type_handler/type_handler.py +473 -0
  125. spring/orm/pymybatis/version.py +9 -0
  126. spring/orm/pymybatis/xml_parser/__init__.py +9 -0
  127. spring/orm/pymybatis/xml_parser/xml_parser.py +761 -0
  128. spring/retry/__init__.py +12 -0
  129. spring/retry/retry_annotations.py +71 -0
  130. spring/retry/retry_decorator.py +155 -0
  131. spring/scheduling/__init__.py +3 -0
  132. spring/scheduling/scheduler.py +389 -0
  133. spring/security/__init__.py +39 -0
  134. spring/security/jwt_utils.py +281 -0
  135. spring/security/replay_protection.py +206 -0
  136. spring/security/secret_manager.py +226 -0
  137. spring/security/security_aop.py +248 -0
  138. spring/security/security_context.py +172 -0
  139. spring/test/__init__.py +45 -0
  140. spring/test/slicing.py +341 -0
  141. spring/tracing/__init__.py +11 -0
  142. spring/tracing/skywalking.py +229 -0
  143. spring/tx/__init__.py +52 -0
  144. spring/tx/events.py +172 -0
  145. spring/tx/synchronization.py +143 -0
  146. spring/utils/__init__.py +5 -0
  147. spring/utils/banner.py +32 -0
  148. spring/utils/logger.py +73 -0
  149. spring/utils/redis_client.py +526 -0
  150. spring/validation/__init__.py +55 -0
  151. spring/validation/aop.py +141 -0
  152. spring/validation/constraints.py +357 -0
  153. spring/validation/exceptions.py +55 -0
  154. spring/validation/validator.py +139 -0
  155. spring/web/__init__.py +12 -0
  156. spring/web/actuator.py +319 -0
  157. spring/web/exception_handler.py +61 -0
  158. spring/web/health.py +399 -0
  159. spring/web/interceptor.py +91 -0
  160. spring/web/result.py +44 -0
  161. spring/web/swagger.py +601 -0
  162. spring/web/web_context.py +755 -0
  163. spring/websocket/__init__.py +86 -0
  164. spring/websocket/annotations.py +169 -0
  165. spring/websocket/broker.py +238 -0
  166. spring/websocket/exceptions.py +26 -0
  167. spring/websocket/handler.py +243 -0
  168. spring/websocket/router.py +526 -0
  169. spring/websocket/session.py +216 -0
  170. springbootai-1.8.0.dist-info/METADATA +2796 -0
  171. springbootai-1.8.0.dist-info/RECORD +175 -0
  172. springbootai-1.8.0.dist-info/WHEEL +5 -0
  173. springbootai-1.8.0.dist-info/entry_points.txt +2 -0
  174. springbootai-1.8.0.dist-info/licenses/LICENSE +7 -0
  175. springbootai-1.8.0.dist-info/top_level.txt +1 -0
spring/cloud/feign.py ADDED
@@ -0,0 +1,469 @@
1
+ """
2
+ Feign远程调用模块
3
+ 提供声明式HTTP客户端功能
4
+ """
5
+ import requests
6
+ import logging
7
+ import json
8
+ import inspect
9
+ from dataclasses import asdict, is_dataclass
10
+ from typing import Dict, Any, Optional, Type, Callable
11
+ from starlette.concurrency import run_in_threadpool
12
+ from spring.cloud.load_balancer import LoadBalancer
13
+
14
+ logger = logging.getLogger("Spring.Cloud.Feign")
15
+
16
+
17
+ class FeignClientProxy:
18
+ """Feign客户端代理"""
19
+
20
+ def __init__(
21
+ self,
22
+ service_name: str,
23
+ path: str = "",
24
+ url: str = "",
25
+ fallback: Type = None,
26
+ fallback_factory: Type = None,
27
+ timeout: float = 30,
28
+ pool_connections: int = 20,
29
+ pool_maxsize: int = 100,
30
+ ):
31
+ self.service_name = service_name
32
+ self.path = path
33
+ self.url = url
34
+ self.fallback = fallback
35
+ self.fallback_factory = fallback_factory
36
+ self.timeout = timeout
37
+ self._load_balancer = LoadBalancer()
38
+ self._session = requests.Session()
39
+ adapter = requests.adapters.HTTPAdapter(
40
+ pool_connections=max(1, int(pool_connections)),
41
+ pool_maxsize=max(1, int(pool_maxsize)),
42
+ max_retries=0,
43
+ pool_block=True,
44
+ )
45
+ self._session.mount("http://", adapter)
46
+ self._session.mount("https://", adapter)
47
+
48
+ def close(self) -> None:
49
+ self._session.close()
50
+
51
+ def __enter__(self) -> "FeignClientProxy":
52
+ return self
53
+
54
+ def __exit__(self, exc_type, exc_value, traceback) -> None:
55
+ self.close()
56
+
57
+ def _get_base_url(self) -> str:
58
+ """获取基础URL"""
59
+ if self.url:
60
+ return self.url
61
+
62
+ # 使用负载均衡获取服务实例
63
+ instances = self._load_balancer.get_instances(self.service_name)
64
+ if not instances:
65
+ raise Exception(f"No instances available for service: {self.service_name}")
66
+
67
+ instance = self._load_balancer.select_instance(instances)
68
+ return f"http://{instance['ip']}:{instance['port']}"
69
+
70
+ def _build_url(self, endpoint: str) -> str:
71
+ """构建完整URL"""
72
+ base_url = self._get_base_url()
73
+ full_path = self.path.rstrip('/') + '/' + endpoint.lstrip('/')
74
+ return f"{base_url.rstrip('/')}/{full_path.lstrip('/')}"
75
+
76
+ @staticmethod
77
+ def _jsonable(value: Any) -> Any:
78
+ if is_dataclass(value) and not isinstance(value, type):
79
+ return asdict(value)
80
+ if hasattr(value, 'model_dump') and callable(value.model_dump):
81
+ return value.model_dump()
82
+ if hasattr(value, 'dict') and callable(value.dict):
83
+ return value.dict()
84
+ return value
85
+
86
+ def _call_fallback(self, fallback_method: Optional[str], error: Exception, args, kwargs):
87
+ if not self.fallback:
88
+ raise error
89
+ try:
90
+ if self.fallback_factory:
91
+ factory = self.fallback_factory()
92
+ fallback_instance = factory.create(error) if hasattr(factory, 'create') else factory(error)
93
+ else:
94
+ fallback_instance = self.fallback()
95
+ except TypeError:
96
+ fallback_instance = self.fallback()
97
+ method = getattr(fallback_instance, fallback_method or '', None)
98
+ if not callable(method):
99
+ raise error
100
+ return method(*args, **kwargs)
101
+
102
+ def request(
103
+ self,
104
+ method: str,
105
+ endpoint: str,
106
+ *,
107
+ params: Optional[Dict[str, Any]] = None,
108
+ json_data: Any = None,
109
+ data: Any = None,
110
+ headers: Optional[Dict[str, str]] = None,
111
+ timeout: Optional[float] = None,
112
+ fallback_method: Optional[str] = None,
113
+ call_args: tuple = (),
114
+ call_kwargs: Optional[dict] = None,
115
+ ) -> Any:
116
+ """Execute a declared Feign request and invoke its fallback if needed."""
117
+ url = self._build_url(endpoint)
118
+ call_kwargs = call_kwargs or {}
119
+
120
+ # 自动注入分布式事务XID头
121
+ req_headers = dict(headers) if headers else {}
122
+ try:
123
+ from spring.cloud.seata import seata_manager
124
+ xid = seata_manager.get_current_tx_id()
125
+ if xid:
126
+ seata_manager.inject_xid_headers(req_headers, xid)
127
+ except Exception:
128
+ pass
129
+
130
+ # 自动注入追踪头(W3C traceparent)
131
+ try:
132
+ from spring.cloud.tracer import get_tracer
133
+ tracer = get_tracer()
134
+ if tracer.enabled:
135
+ tracer.inject_headers(req_headers)
136
+ except Exception:
137
+ pass
138
+
139
+ try:
140
+ response = self._session.request(
141
+ method.upper(),
142
+ url,
143
+ params=params,
144
+ json=self._jsonable(json_data) if json_data is not None else None,
145
+ data=data,
146
+ headers=req_headers,
147
+ timeout=self.timeout if timeout is None else timeout,
148
+ )
149
+ response.raise_for_status()
150
+ if not response.content:
151
+ return None
152
+ try:
153
+ return response.json()
154
+ except (ValueError, json.JSONDecodeError):
155
+ return response.text
156
+ except Exception as error:
157
+ logger.error("Feign %s request failed: %s, error: %s", method, url, error)
158
+ return self._call_fallback(fallback_method, error, call_args, call_kwargs)
159
+
160
+ async def arequest(self, method: str, endpoint: str, **kwargs) -> Any:
161
+ """Execute the synchronous requests client without blocking the ASGI loop."""
162
+ return await run_in_threadpool(self.request, method, endpoint, **kwargs)
163
+
164
+ def get(self, endpoint: str, params: Dict[str, Any] = None, headers: Dict[str, str] = None) -> Any:
165
+ """
166
+ 发送GET请求
167
+
168
+ Args:
169
+ endpoint: 端点路径
170
+ params: 查询参数
171
+ headers: 请求头
172
+
173
+ Returns:
174
+ 响应数据
175
+ """
176
+ url = self._build_url(endpoint)
177
+
178
+ try:
179
+ response = self._session.get(
180
+ url, params=params, headers=headers, timeout=self.timeout
181
+ )
182
+ response.raise_for_status()
183
+
184
+ try:
185
+ return response.json()
186
+ except json.JSONDecodeError:
187
+ return response.text
188
+ except Exception as e:
189
+ logger.error(f"Feign GET request failed: {url}, error: {e}")
190
+
191
+ # 尝试降级处理
192
+ if self.fallback:
193
+ fallback_instance = self.fallback()
194
+ method = getattr(fallback_instance, endpoint.replace('/', '_'), None)
195
+ if method and callable(method):
196
+ return method(params=params, headers=headers)
197
+
198
+ raise
199
+
200
+ def post(self, endpoint: str, data: Dict[str, Any] = None, json_data: Dict[str, Any] = None,
201
+ headers: Dict[str, str] = None) -> Any:
202
+ """
203
+ 发送POST请求
204
+
205
+ Args:
206
+ endpoint: 端点路径
207
+ data: 表单数据
208
+ json_data: JSON数据
209
+ headers: 请求头
210
+
211
+ Returns:
212
+ 响应数据
213
+ """
214
+ url = self._build_url(endpoint)
215
+
216
+ try:
217
+ response = self._session.post(
218
+ url, data=data, json=json_data, headers=headers, timeout=self.timeout
219
+ )
220
+ response.raise_for_status()
221
+
222
+ try:
223
+ return response.json()
224
+ except json.JSONDecodeError:
225
+ return response.text
226
+ except Exception as e:
227
+ logger.error(f"Feign POST request failed: {url}, error: {e}")
228
+
229
+ if self.fallback:
230
+ fallback_instance = self.fallback()
231
+ method = getattr(fallback_instance, endpoint.replace('/', '_'), None)
232
+ if method and callable(method):
233
+ return method(data=data, json_data=json_data, headers=headers)
234
+
235
+ raise
236
+
237
+ def put(self, endpoint: str, data: Dict[str, Any] = None, json_data: Dict[str, Any] = None,
238
+ headers: Dict[str, str] = None) -> Any:
239
+ """
240
+ 发送PUT请求
241
+
242
+ Args:
243
+ endpoint: 端点路径
244
+ data: 表单数据
245
+ json_data: JSON数据
246
+ headers: 请求头
247
+
248
+ Returns:
249
+ 响应数据
250
+ """
251
+ url = self._build_url(endpoint)
252
+
253
+ try:
254
+ response = self._session.put(
255
+ url, data=data, json=json_data, headers=headers, timeout=self.timeout
256
+ )
257
+ response.raise_for_status()
258
+
259
+ try:
260
+ return response.json()
261
+ except json.JSONDecodeError:
262
+ return response.text
263
+ except Exception as e:
264
+ logger.error(f"Feign PUT request failed: {url}, error: {e}")
265
+
266
+ if self.fallback:
267
+ fallback_instance = self.fallback()
268
+ method = getattr(fallback_instance, endpoint.replace('/', '_'), None)
269
+ if method and callable(method):
270
+ return method(data=data, json_data=json_data, headers=headers)
271
+
272
+ raise
273
+
274
+ def delete(self, endpoint: str, params: Dict[str, Any] = None, headers: Dict[str, str] = None) -> Any:
275
+ """
276
+ 发送DELETE请求
277
+
278
+ Args:
279
+ endpoint: 端点路径
280
+ params: 查询参数
281
+ headers: 请求头
282
+
283
+ Returns:
284
+ 响应数据
285
+ """
286
+ url = self._build_url(endpoint)
287
+
288
+ try:
289
+ response = self._session.delete(
290
+ url, params=params, headers=headers, timeout=self.timeout
291
+ )
292
+ response.raise_for_status()
293
+
294
+ try:
295
+ return response.json()
296
+ except json.JSONDecodeError:
297
+ return response.text
298
+ except Exception as e:
299
+ logger.error(f"Feign DELETE request failed: {url}, error: {e}")
300
+
301
+ if self.fallback:
302
+ fallback_instance = self.fallback()
303
+ method = getattr(fallback_instance, endpoint.replace('/', '_'), None)
304
+ if method and callable(method):
305
+ return method(params=params, headers=headers)
306
+
307
+ raise
308
+
309
+
310
+ class FeignClientFactory:
311
+ """Feign客户端工厂"""
312
+
313
+ _clients: Dict[str, FeignClientProxy] = {}
314
+
315
+ @classmethod
316
+ def get_client(cls, service_name: str) -> FeignClientProxy:
317
+ """
318
+ 获取Feign客户端
319
+
320
+ Args:
321
+ service_name: 服务名称
322
+
323
+ Returns:
324
+ Feign客户端代理
325
+ """
326
+ if service_name not in cls._clients:
327
+ cls._clients[service_name] = FeignClientProxy(service_name)
328
+
329
+ return cls._clients[service_name]
330
+
331
+ @classmethod
332
+ def register_client(cls, service_name: str, client: FeignClientProxy):
333
+ """
334
+ 注册Feign客户端
335
+
336
+ Args:
337
+ service_name: 服务名称
338
+ client: Feign客户端代理
339
+ """
340
+ cls._clients[service_name] = client
341
+
342
+ @classmethod
343
+ def close_all(cls) -> None:
344
+ for client in cls._clients.values():
345
+ client.close()
346
+ cls._clients.clear()
347
+
348
+
349
+ def create_feign_client(service_name: str, path: str = "", url: str = "",
350
+ fallback: Type = None,
351
+ fallback_factory: Type = None,
352
+ timeout: float = 30) -> FeignClientProxy:
353
+ """
354
+ 创建Feign客户端
355
+
356
+ Args:
357
+ service_name: 服务名称
358
+ path: 路径前缀
359
+ url: 直接URL(调试用)
360
+ fallback: 降级实现类
361
+
362
+ Returns:
363
+ Feign客户端代理
364
+ """
365
+ return FeignClientProxy(service_name, path, url, fallback, fallback_factory, timeout)
366
+
367
+
368
+ def create_declared_feign_client(client_class: Type, annotation: Any) -> Any:
369
+ """Create a typed proxy from a ``@FeignClient`` class declaration.
370
+
371
+ Method mappings use the same SpringBootAI ``@RequestMapping`` family as web
372
+ controllers. The generated object subclasses the declaration class, so
373
+ normal type-based IoC injection and ``isinstance`` checks continue to work.
374
+ """
375
+ from spring.annotations.core import (
376
+ RequestMapping, GetMapping, PostMapping, PutMapping,
377
+ PatchMapping, DeleteMapping, RequestParam, PathVariable,
378
+ RequestBody, RequestHeader,
379
+ )
380
+
381
+ proxy = FeignClientProxy(
382
+ annotation.value,
383
+ path=annotation.path,
384
+ url=annotation.url,
385
+ fallback=annotation.fallback,
386
+ fallback_factory=annotation.fallback_factory,
387
+ )
388
+
389
+ mappings = (RequestMapping, GetMapping, PostMapping, PutMapping, PatchMapping, DeleteMapping)
390
+ generated = {}
391
+ for method_name, method in inspect.getmembers(client_class, inspect.isfunction):
392
+ mapping = next((item for item in getattr(method, '__spring_annotations__', []) if isinstance(item, mappings)), None)
393
+ if mapping is None:
394
+ continue
395
+ raw_path = mapping.path
396
+ endpoint_path = raw_path[0] if isinstance(raw_path, list) else raw_path
397
+ endpoint_path = endpoint_path or method_name
398
+ http_method = (mapping.method or ['GET'])[0].upper()
399
+ signature = inspect.signature(method)
400
+ parameters = [p for p in signature.parameters.values() if p.name != 'self']
401
+
402
+ def make_call(name, endpoint, verb, original, params):
403
+ def call(self, *args, **kwargs):
404
+ bound = inspect.signature(original).bind_partial(None, *args, **kwargs)
405
+ bound.apply_defaults()
406
+ values = dict(bound.arguments)
407
+ values.pop('self', None)
408
+ path_values = {}
409
+ query = {}
410
+ body = None
411
+ headers = {}
412
+ for parameter in params:
413
+ value = values.get(parameter.name)
414
+ marker = parameter.default
415
+ if isinstance(marker, PathVariable) or '{' + parameter.name + '}' in endpoint:
416
+ path_values[marker.name if isinstance(marker, PathVariable) and marker.name else parameter.name] = value
417
+ elif isinstance(marker, RequestHeader):
418
+ headers[marker.name or parameter.name.replace('_', '-')] = value
419
+ elif isinstance(marker, RequestBody):
420
+ body = value
421
+ elif isinstance(marker, RequestParam):
422
+ query[marker.name or parameter.name] = value
423
+ elif verb in {'GET', 'DELETE'}:
424
+ query[parameter.name] = value
425
+ elif body is None:
426
+ body = value
427
+ else:
428
+ query[parameter.name] = value
429
+ try:
430
+ endpoint_rendered = endpoint.format(**path_values)
431
+ except KeyError as exc:
432
+ raise ValueError(f"Feign 路径缺少参数: {exc.args[0]}") from exc
433
+ return proxy.request(
434
+ verb,
435
+ endpoint_rendered,
436
+ params=query or None,
437
+ json_data=body if verb not in {'GET', 'DELETE'} else None,
438
+ headers=headers or None,
439
+ timeout=proxy.timeout,
440
+ fallback_method=name,
441
+ call_args=args,
442
+ call_kwargs=kwargs,
443
+ )
444
+ call.__name__ = name
445
+ call.__doc__ = getattr(original, '__doc__', None)
446
+ if inspect.iscoroutinefunction(original):
447
+ async def async_call(self, *args, **kwargs):
448
+ return await run_in_threadpool(call, self, *args, **kwargs)
449
+ async_call.__name__ = name
450
+ async_call.__doc__ = call.__doc__
451
+ return async_call
452
+ return call
453
+
454
+ generated[method_name] = make_call(method_name, endpoint_path, http_method, method, parameters)
455
+
456
+ original_destroy = getattr(client_class, 'destroy', None)
457
+
458
+ def destroy(self):
459
+ try:
460
+ if callable(original_destroy):
461
+ original_destroy(self)
462
+ finally:
463
+ proxy.close()
464
+
465
+ generated['destroy'] = destroy
466
+ implementation = type(f"{client_class.__name__}FeignProxy", (client_class,), generated)
467
+ instance = implementation()
468
+ instance.__feign_proxy__ = proxy
469
+ return instance