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.
- spring/__init__.py +66 -0
- spring/ai/__init__.py +78 -0
- spring/ai/advisors.py +139 -0
- spring/ai/annotations.py +74 -0
- spring/ai/autoconfig.py +481 -0
- spring/ai/core.py +391 -0
- spring/ai/etl.py +188 -0
- spring/ai/memory.py +109 -0
- spring/ai/observability.py +129 -0
- spring/ai/providers.py +789 -0
- spring/ai/resilience.py +258 -0
- spring/ai/tools.py +106 -0
- spring/ai/vectorstore.py +303 -0
- spring/annotations/__init__.py +188 -0
- spring/annotations/cache.py +126 -0
- spring/annotations/cloud.py +207 -0
- spring/annotations/conditional.py +272 -0
- spring/annotations/core.py +864 -0
- spring/annotations/messaging.py +107 -0
- spring/aop/__init__.py +4 -0
- spring/aop/cloud_aop.py +404 -0
- spring/aop/comprehensive_aop.py +1015 -0
- spring/aop/method_interceptor.py +19 -0
- spring/aop/proxy_factory.py +55 -0
- spring/cloud/__init__.py +76 -0
- spring/cloud/discovery.py +364 -0
- spring/cloud/feign.py +469 -0
- spring/cloud/gateway.py +452 -0
- spring/cloud/load_balancer.py +149 -0
- spring/cloud/seata.py +557 -0
- spring/cloud/sentinel.py +525 -0
- spring/cloud/tracer.py +337 -0
- spring/config/__init__.py +21 -0
- spring/config/binding.py +206 -0
- spring/config/config_loader.py +405 -0
- spring/context/__init__.py +13 -0
- spring/context/application_context.py +589 -0
- spring/context/bean_definition.py +70 -0
- spring/context/bean_factory.py +1052 -0
- spring/context/registry.py +58 -0
- spring/context/scanner.py +106 -0
- spring/core/__init__.py +3 -0
- spring/core/graceful_shutdown.py +196 -0
- spring/core/typing_utils.py +50 -0
- spring/csv/__init__.py +52 -0
- spring/csv/annotations.py +402 -0
- spring/csv/converters.py +69 -0
- spring/csv/easy_csv.py +95 -0
- spring/csv/exceptions.py +27 -0
- spring/csv/reader.py +195 -0
- spring/csv/writer.py +155 -0
- spring/data/__init__.py +54 -0
- spring/data/page.py +181 -0
- spring/data/repository.py +274 -0
- spring/data/specification.py +228 -0
- spring/datasource/__init__.py +66 -0
- spring/datasource/annotations.py +133 -0
- spring/datasource/context.py +69 -0
- spring/datasource/dynamic.py +148 -0
- spring/event/__init__.py +7 -0
- spring/event/publisher.py +69 -0
- spring/excel/__init__.py +51 -0
- spring/excel/annotations.py +405 -0
- spring/excel/converters.py +231 -0
- spring/excel/easy_excel.py +94 -0
- spring/excel/exceptions.py +31 -0
- spring/excel/reader.py +254 -0
- spring/excel/style.py +95 -0
- spring/excel/writer.py +197 -0
- spring/i18n/__init__.py +97 -0
- spring/i18n/accessor.py +94 -0
- spring/i18n/auto_config.py +177 -0
- spring/i18n/holder.py +106 -0
- spring/i18n/locale.py +152 -0
- spring/i18n/locale_resolver.py +367 -0
- spring/i18n/message_source.py +250 -0
- spring/i18n/middleware.py +79 -0
- spring/i18n/properties.py +168 -0
- spring/i18n/sources.py +255 -0
- spring/logging/__init__.py +1 -0
- spring/logging/loguru_logger.py +228 -0
- spring/main.py +378 -0
- spring/messaging/__init__.py +1 -0
- spring/messaging/rabbitmq.py +302 -0
- spring/monitoring/__init__.py +1 -0
- spring/monitoring/prometheus.py +199 -0
- spring/orm/__init__.py +258 -0
- spring/orm/database.py +222 -0
- spring/orm/ddl_auto.py +1217 -0
- spring/orm/migration.py +419 -0
- spring/orm/mybatis_integration.py +400 -0
- spring/orm/pymybatis/__init__.py +86 -0
- spring/orm/pymybatis/annotations/__init__.py +30 -0
- spring/orm/pymybatis/annotations/annotations.py +332 -0
- spring/orm/pymybatis/cache/__init__.py +47 -0
- spring/orm/pymybatis/cache/cache.py +371 -0
- spring/orm/pymybatis/cache/redis_cache.py +434 -0
- spring/orm/pymybatis/circuit_breaker/__init__.py +21 -0
- spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +424 -0
- spring/orm/pymybatis/configuration.py +525 -0
- spring/orm/pymybatis/core/__init__.py +10 -0
- spring/orm/pymybatis/core/sql_session.py +1382 -0
- spring/orm/pymybatis/core/sql_session_factory.py +76 -0
- spring/orm/pymybatis/dialect/__init__.py +9 -0
- spring/orm/pymybatis/dialect/dialect.py +445 -0
- spring/orm/pymybatis/dynamic_sql/__init__.py +9 -0
- spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +900 -0
- spring/orm/pymybatis/interceptor/__init__.py +31 -0
- spring/orm/pymybatis/interceptor/interceptor.py +427 -0
- spring/orm/pymybatis/mapper/__init__.py +9 -0
- spring/orm/pymybatis/mapper/mapper.py +540 -0
- spring/orm/pymybatis/metrics/__init__.py +41 -0
- spring/orm/pymybatis/metrics/metrics.py +595 -0
- spring/orm/pymybatis/pool/__init__.py +9 -0
- spring/orm/pymybatis/pool/connection_pool.py +711 -0
- spring/orm/pymybatis/security/__init__.py +19 -0
- spring/orm/pymybatis/security/access_control.py +415 -0
- spring/orm/pymybatis/security/password_encoder.py +293 -0
- spring/orm/pymybatis/security/sensitive_data_masker.py +326 -0
- spring/orm/pymybatis/security/sql_injection_detector.py +675 -0
- spring/orm/pymybatis/transaction/__init__.py +9 -0
- spring/orm/pymybatis/transaction/transaction.py +288 -0
- spring/orm/pymybatis/type_handler/__init__.py +37 -0
- spring/orm/pymybatis/type_handler/type_handler.py +473 -0
- spring/orm/pymybatis/version.py +9 -0
- spring/orm/pymybatis/xml_parser/__init__.py +9 -0
- spring/orm/pymybatis/xml_parser/xml_parser.py +761 -0
- spring/retry/__init__.py +12 -0
- spring/retry/retry_annotations.py +71 -0
- spring/retry/retry_decorator.py +155 -0
- spring/scheduling/__init__.py +3 -0
- spring/scheduling/scheduler.py +389 -0
- spring/security/__init__.py +39 -0
- spring/security/jwt_utils.py +281 -0
- spring/security/replay_protection.py +206 -0
- spring/security/secret_manager.py +226 -0
- spring/security/security_aop.py +248 -0
- spring/security/security_context.py +172 -0
- spring/test/__init__.py +45 -0
- spring/test/slicing.py +341 -0
- spring/tracing/__init__.py +11 -0
- spring/tracing/skywalking.py +229 -0
- spring/tx/__init__.py +52 -0
- spring/tx/events.py +172 -0
- spring/tx/synchronization.py +143 -0
- spring/utils/__init__.py +5 -0
- spring/utils/banner.py +32 -0
- spring/utils/logger.py +73 -0
- spring/utils/redis_client.py +526 -0
- spring/validation/__init__.py +55 -0
- spring/validation/aop.py +141 -0
- spring/validation/constraints.py +357 -0
- spring/validation/exceptions.py +55 -0
- spring/validation/validator.py +139 -0
- spring/web/__init__.py +12 -0
- spring/web/actuator.py +319 -0
- spring/web/exception_handler.py +61 -0
- spring/web/health.py +399 -0
- spring/web/interceptor.py +91 -0
- spring/web/result.py +44 -0
- spring/web/swagger.py +601 -0
- spring/web/web_context.py +755 -0
- spring/websocket/__init__.py +86 -0
- spring/websocket/annotations.py +169 -0
- spring/websocket/broker.py +238 -0
- spring/websocket/exceptions.py +26 -0
- spring/websocket/handler.py +243 -0
- spring/websocket/router.py +526 -0
- spring/websocket/session.py +216 -0
- springbootai-1.8.0.dist-info/METADATA +2796 -0
- springbootai-1.8.0.dist-info/RECORD +175 -0
- springbootai-1.8.0.dist-info/WHEEL +5 -0
- springbootai-1.8.0.dist-info/entry_points.txt +2 -0
- springbootai-1.8.0.dist-info/licenses/LICENSE +7 -0
- springbootai-1.8.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,864 @@
|
|
|
1
|
+
from typing import Optional, Union, Dict, Any, Type, List, Callable, Tuple
|
|
2
|
+
import inspect
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SpringAnnotation:
|
|
6
|
+
_annotation_type: str = "base"
|
|
7
|
+
|
|
8
|
+
def __new__(cls, *args, **kwargs):
|
|
9
|
+
# 如果第一个参数是类或函数,且不是内置类型,说明是@Annotation形式(不带括号)
|
|
10
|
+
if args and (isinstance(args[0], type) or callable(args[0])):
|
|
11
|
+
target = args[0]
|
|
12
|
+
# 排除内置类型(如ValueError, Exception等),它们是注解的参数,不是目标
|
|
13
|
+
if isinstance(target, type) and target.__module__ in ('builtins', '__builtin__'):
|
|
14
|
+
# 这是内置类型,作为注解参数处理
|
|
15
|
+
return super().__new__(cls)
|
|
16
|
+
# 创建注解实例
|
|
17
|
+
instance = super().__new__(cls)
|
|
18
|
+
# 应用注解
|
|
19
|
+
instance.__init__(*args[1:], **kwargs)
|
|
20
|
+
# ``hasattr`` also sees annotations inherited from a base class.
|
|
21
|
+
# Each decorated target needs its own list, otherwise decorating a
|
|
22
|
+
# subclass silently mutates the parent's Spring metadata.
|
|
23
|
+
if '__spring_annotations__' not in target.__dict__:
|
|
24
|
+
target.__spring_annotations__ = []
|
|
25
|
+
target.__spring_annotations__.append(instance)
|
|
26
|
+
instance._original_class = target
|
|
27
|
+
# 返回原始类,而不是注解实例
|
|
28
|
+
return target
|
|
29
|
+
# 否则是@Annotation()形式,返回注解实例
|
|
30
|
+
return super().__new__(cls)
|
|
31
|
+
|
|
32
|
+
def __init__(self, **kwargs):
|
|
33
|
+
self.__dict__.update(kwargs)
|
|
34
|
+
self._original_class = None
|
|
35
|
+
|
|
36
|
+
def __call__(self, target: Union[Type, Callable]) -> Union[Type, Callable]:
|
|
37
|
+
if '__spring_annotations__' not in target.__dict__:
|
|
38
|
+
target.__spring_annotations__ = []
|
|
39
|
+
target.__spring_annotations__.append(self)
|
|
40
|
+
self._original_class = target
|
|
41
|
+
return target
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_spring_annotations(target: Any) -> List["SpringAnnotation"]:
|
|
45
|
+
"""Return annotations declared directly on *target* in declaration order.
|
|
46
|
+
|
|
47
|
+
Spring's metadata is intentionally not merged from base classes. This
|
|
48
|
+
helper gives scanners and integrations one consistent read path and keeps
|
|
49
|
+
inherited annotations from being mistaken for declarations on a subclass.
|
|
50
|
+
"""
|
|
51
|
+
return list(getattr(target, "__dict__", {}).get("__spring_annotations__", []))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ApplicationEvent:
|
|
55
|
+
"""Base class for events published through the application context."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, source: Any = None):
|
|
58
|
+
self.source = source
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class EventListener(SpringAnnotation):
|
|
62
|
+
"""Mark a bean method as an application event listener."""
|
|
63
|
+
|
|
64
|
+
_annotation_type = "event_listener"
|
|
65
|
+
|
|
66
|
+
def __new__(cls, *args, **kwargs):
|
|
67
|
+
# ``@EventListener(MyEvent)`` is a common shorthand. The base
|
|
68
|
+
# annotation treats a positional class as a decorator target, so
|
|
69
|
+
# event classes need to be recognized before delegating to it.
|
|
70
|
+
if args and isinstance(args[0], type):
|
|
71
|
+
try:
|
|
72
|
+
if issubclass(args[0], ApplicationEvent):
|
|
73
|
+
return object.__new__(cls)
|
|
74
|
+
except TypeError:
|
|
75
|
+
pass
|
|
76
|
+
return super().__new__(cls, *args, **kwargs)
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
event_type: Optional[Type[ApplicationEvent]] = None,
|
|
81
|
+
order: int = 0,
|
|
82
|
+
):
|
|
83
|
+
super().__init__(event_type=event_type, order=order)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class SpringBootApplication(SpringAnnotation):
|
|
87
|
+
_annotation_type = "boot"
|
|
88
|
+
|
|
89
|
+
def __init__(self, scan_base_packages: Optional[List[str]] = None):
|
|
90
|
+
super().__init__(scan_base_packages=scan_base_packages)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ComponentScan(SpringAnnotation):
|
|
94
|
+
_annotation_type = "scan"
|
|
95
|
+
|
|
96
|
+
def __init__(self, base_packages: Optional[List[str]] = None):
|
|
97
|
+
super().__init__(base_packages=base_packages)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class RestController(SpringAnnotation):
|
|
101
|
+
_annotation_type = "controller"
|
|
102
|
+
|
|
103
|
+
def __init__(self, value: str = ""):
|
|
104
|
+
super().__init__(value=value)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class Controller(SpringAnnotation):
|
|
108
|
+
_annotation_type = "controller"
|
|
109
|
+
|
|
110
|
+
def __init__(self, value: str = ""):
|
|
111
|
+
super().__init__(value=value)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class RequestMapping(SpringAnnotation):
|
|
115
|
+
_annotation_type = "mapping"
|
|
116
|
+
|
|
117
|
+
def __init__(
|
|
118
|
+
self,
|
|
119
|
+
path: Union[str, List[str]] = "",
|
|
120
|
+
method: Optional[Union[str, List[str]]] = None,
|
|
121
|
+
consumes: Optional[str] = None,
|
|
122
|
+
produces: Optional[str] = None,
|
|
123
|
+
value: Optional[Union[str, List[str]]] = None,
|
|
124
|
+
):
|
|
125
|
+
if value is not None:
|
|
126
|
+
if path:
|
|
127
|
+
raise TypeError("RequestMapping 的 path 和 value 只能设置一个")
|
|
128
|
+
path = value
|
|
129
|
+
if isinstance(method, str):
|
|
130
|
+
method = [method]
|
|
131
|
+
super().__init__(path=path, method=[m.upper() for m in (method or [])], consumes=consumes, produces=produces)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class GetMapping(SpringAnnotation):
|
|
135
|
+
_annotation_type = "mapping"
|
|
136
|
+
|
|
137
|
+
def __init__(
|
|
138
|
+
self,
|
|
139
|
+
path: Union[str, List[str]] = "",
|
|
140
|
+
consumes: Optional[str] = None,
|
|
141
|
+
produces: Optional[str] = None,
|
|
142
|
+
value: Optional[Union[str, List[str]]] = None,
|
|
143
|
+
):
|
|
144
|
+
if value is not None:
|
|
145
|
+
if path:
|
|
146
|
+
raise TypeError("GetMapping 的 path 和 value 只能设置一个")
|
|
147
|
+
path = value
|
|
148
|
+
super().__init__(path=path, method=["GET"], consumes=consumes, produces=produces)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class PostMapping(SpringAnnotation):
|
|
152
|
+
_annotation_type = "mapping"
|
|
153
|
+
|
|
154
|
+
def __init__(
|
|
155
|
+
self,
|
|
156
|
+
path: Union[str, List[str]] = "",
|
|
157
|
+
consumes: Optional[str] = None,
|
|
158
|
+
produces: Optional[str] = None,
|
|
159
|
+
value: Optional[Union[str, List[str]]] = None,
|
|
160
|
+
):
|
|
161
|
+
if value is not None:
|
|
162
|
+
if path:
|
|
163
|
+
raise TypeError("PostMapping 的 path 和 value 只能设置一个")
|
|
164
|
+
path = value
|
|
165
|
+
super().__init__(path=path, method=["POST"], consumes=consumes, produces=produces)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class PutMapping(SpringAnnotation):
|
|
169
|
+
_annotation_type = "mapping"
|
|
170
|
+
|
|
171
|
+
def __init__(
|
|
172
|
+
self,
|
|
173
|
+
path: Union[str, List[str]] = "",
|
|
174
|
+
consumes: Optional[str] = None,
|
|
175
|
+
produces: Optional[str] = None,
|
|
176
|
+
value: Optional[Union[str, List[str]]] = None,
|
|
177
|
+
):
|
|
178
|
+
if value is not None:
|
|
179
|
+
if path:
|
|
180
|
+
raise TypeError("PutMapping 的 path 和 value 只能设置一个")
|
|
181
|
+
path = value
|
|
182
|
+
super().__init__(path=path, method=["PUT"], consumes=consumes, produces=produces)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class PatchMapping(SpringAnnotation):
|
|
186
|
+
_annotation_type = "mapping"
|
|
187
|
+
|
|
188
|
+
def __init__(
|
|
189
|
+
self,
|
|
190
|
+
path: Union[str, List[str]] = "",
|
|
191
|
+
consumes: Optional[str] = None,
|
|
192
|
+
produces: Optional[str] = None,
|
|
193
|
+
value: Optional[Union[str, List[str]]] = None,
|
|
194
|
+
):
|
|
195
|
+
if value is not None:
|
|
196
|
+
if path:
|
|
197
|
+
raise TypeError("PatchMapping 的 path 和 value 只能设置一个")
|
|
198
|
+
path = value
|
|
199
|
+
super().__init__(path=path, method=["PATCH"], consumes=consumes, produces=produces)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class DeleteMapping(SpringAnnotation):
|
|
203
|
+
_annotation_type = "mapping"
|
|
204
|
+
|
|
205
|
+
def __init__(
|
|
206
|
+
self,
|
|
207
|
+
path: Union[str, List[str]] = "",
|
|
208
|
+
consumes: Optional[str] = None,
|
|
209
|
+
produces: Optional[str] = None,
|
|
210
|
+
value: Optional[Union[str, List[str]]] = None,
|
|
211
|
+
):
|
|
212
|
+
if value is not None:
|
|
213
|
+
if path:
|
|
214
|
+
raise TypeError("DeleteMapping 的 path 和 value 只能设置一个")
|
|
215
|
+
path = value
|
|
216
|
+
super().__init__(path=path, method=["DELETE"], consumes=consumes, produces=produces)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class Service(SpringAnnotation):
|
|
220
|
+
_annotation_type = "component"
|
|
221
|
+
|
|
222
|
+
def __init__(self, value: str = ""):
|
|
223
|
+
super().__init__(value=value)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class Component(SpringAnnotation):
|
|
227
|
+
_annotation_type = "component"
|
|
228
|
+
|
|
229
|
+
def __init__(self, value: str = ""):
|
|
230
|
+
super().__init__(value=value)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class Repository(SpringAnnotation):
|
|
234
|
+
_annotation_type = "component"
|
|
235
|
+
|
|
236
|
+
def __init__(self, value: str = ""):
|
|
237
|
+
super().__init__(value=value)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class Autowired(SpringAnnotation):
|
|
241
|
+
_annotation_type = "inject"
|
|
242
|
+
|
|
243
|
+
def __init__(self, required: bool = True):
|
|
244
|
+
super().__init__(required=required)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class Qualifier(SpringAnnotation):
|
|
248
|
+
_annotation_type = "qualifier"
|
|
249
|
+
|
|
250
|
+
def __init__(self, value: str):
|
|
251
|
+
super().__init__(value=value)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class Configuration(SpringAnnotation):
|
|
255
|
+
_annotation_type = "configuration"
|
|
256
|
+
|
|
257
|
+
def __init__(self, proxyBeanMethods: bool = True, proxy_bean_methods: Optional[bool] = None):
|
|
258
|
+
if proxy_bean_methods is not None:
|
|
259
|
+
proxyBeanMethods = proxy_bean_methods
|
|
260
|
+
super().__init__(proxyBeanMethods=proxyBeanMethods)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class Scope(SpringAnnotation):
|
|
264
|
+
"""Declare a Bean scope (``singleton`` or ``prototype``)."""
|
|
265
|
+
|
|
266
|
+
_annotation_type = "scope"
|
|
267
|
+
|
|
268
|
+
def __init__(self, value: str = "singleton"):
|
|
269
|
+
normalized = str(value).lower()
|
|
270
|
+
if normalized not in {"singleton", "prototype"}:
|
|
271
|
+
raise ValueError("Scope 仅支持 singleton 或 prototype")
|
|
272
|
+
super().__init__(value=normalized)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class Bean(SpringAnnotation):
|
|
276
|
+
_annotation_type = "bean"
|
|
277
|
+
|
|
278
|
+
def __init__(
|
|
279
|
+
self,
|
|
280
|
+
name: Optional[str] = None,
|
|
281
|
+
scope: str = "singleton",
|
|
282
|
+
init_method: Optional[str] = None,
|
|
283
|
+
destroy_method: Optional[str] = None,
|
|
284
|
+
):
|
|
285
|
+
super().__init__(name=name, scope=scope, init_method=init_method, destroy_method=destroy_method)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class Value(SpringAnnotation):
|
|
289
|
+
_annotation_type = "value"
|
|
290
|
+
|
|
291
|
+
def __init__(self, value: str, default: Any = None):
|
|
292
|
+
super().__init__(value=value, default=default)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class ConfigurationProperties(SpringAnnotation):
|
|
296
|
+
_annotation_type = "properties"
|
|
297
|
+
|
|
298
|
+
def __init__(self, prefix: str):
|
|
299
|
+
super().__init__(prefix=prefix)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class RequestParam:
|
|
303
|
+
_annotation_type = "param"
|
|
304
|
+
|
|
305
|
+
def __init__(
|
|
306
|
+
self,
|
|
307
|
+
name: Optional[str] = None,
|
|
308
|
+
required: bool = True,
|
|
309
|
+
default: Any = None,
|
|
310
|
+
value: Optional[str] = None,
|
|
311
|
+
):
|
|
312
|
+
self.name = value if value is not None else name
|
|
313
|
+
self.required = required
|
|
314
|
+
self.default = default
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class PathVariable:
|
|
318
|
+
_annotation_type = "param"
|
|
319
|
+
|
|
320
|
+
def __init__(self, name: Optional[str] = None, required: bool = True, value: Optional[str] = None):
|
|
321
|
+
self.name = value if value is not None else name
|
|
322
|
+
self.required = required
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class RequestBody:
|
|
326
|
+
_annotation_type = "param"
|
|
327
|
+
|
|
328
|
+
def __init__(self, required: bool = True, value: Optional[bool] = None):
|
|
329
|
+
if value is not None:
|
|
330
|
+
required = value
|
|
331
|
+
self.required = required
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class Valid(SpringAnnotation):
|
|
335
|
+
"""Mark a request-body parameter for FastAPI/Pydantic validation.
|
|
336
|
+
|
|
337
|
+
Validation groups are retained as migration metadata. Field validation is
|
|
338
|
+
performed by the annotated Pydantic model at request time.
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
_annotation_type = "param"
|
|
342
|
+
|
|
343
|
+
def __init__(self, groups: Optional[List[Type]] = None):
|
|
344
|
+
super().__init__(groups=groups or [])
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class Validated(SpringAnnotation):
|
|
348
|
+
"""Request-body validation marker that keeps optional group metadata."""
|
|
349
|
+
|
|
350
|
+
_annotation_type = "param"
|
|
351
|
+
|
|
352
|
+
def __init__(self, groups: Optional[List[Type]] = None):
|
|
353
|
+
super().__init__(groups=groups or [])
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class CrossOrigin(SpringAnnotation):
|
|
357
|
+
_annotation_type = "cors"
|
|
358
|
+
|
|
359
|
+
def __init__(
|
|
360
|
+
self,
|
|
361
|
+
origins: Optional[List[str]] = None,
|
|
362
|
+
methods: Optional[List[str]] = None,
|
|
363
|
+
allowedHeaders: Optional[List[str]] = None,
|
|
364
|
+
allowCredentials: bool = False,
|
|
365
|
+
maxAge: int = 3600,
|
|
366
|
+
allowed_headers: Optional[List[str]] = None,
|
|
367
|
+
allow_credentials: Optional[bool] = None,
|
|
368
|
+
max_age: Optional[int] = None,
|
|
369
|
+
):
|
|
370
|
+
if allowed_headers is not None:
|
|
371
|
+
allowedHeaders = allowed_headers
|
|
372
|
+
if allow_credentials is not None:
|
|
373
|
+
allowCredentials = allow_credentials
|
|
374
|
+
if max_age is not None:
|
|
375
|
+
maxAge = max_age
|
|
376
|
+
super().__init__(
|
|
377
|
+
origins=origins or ["*"],
|
|
378
|
+
methods=methods or ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
379
|
+
allowedHeaders=allowedHeaders or ["*"],
|
|
380
|
+
allowCredentials=allowCredentials,
|
|
381
|
+
maxAge=maxAge,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class ControllerAdvice(SpringAnnotation):
|
|
386
|
+
_annotation_type = "advice"
|
|
387
|
+
|
|
388
|
+
def __init__(self):
|
|
389
|
+
super().__init__()
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class ExceptionHandler(SpringAnnotation):
|
|
393
|
+
_annotation_type = "exception_handler"
|
|
394
|
+
|
|
395
|
+
def __init__(self, *exceptions: Type[Exception], value: Optional[List[Type[Exception]]] = None):
|
|
396
|
+
if value:
|
|
397
|
+
exceptions = tuple(value)
|
|
398
|
+
super().__init__(
|
|
399
|
+
value=list(exceptions) if exceptions else [],
|
|
400
|
+
exceptions=exceptions
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
class Slf4j(SpringAnnotation):
|
|
405
|
+
_annotation_type = "logging"
|
|
406
|
+
|
|
407
|
+
def __init__(self, logger_name: Optional[str] = None):
|
|
408
|
+
super().__init__(logger_name=logger_name)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class LogExecutionTime(SpringAnnotation):
|
|
412
|
+
_annotation_type = "logging"
|
|
413
|
+
|
|
414
|
+
def __init__(self, log_level: str = "info"):
|
|
415
|
+
super().__init__(log_level=log_level)
|
|
416
|
+
|
|
417
|
+
def __call__(self, func: Callable) -> Callable:
|
|
418
|
+
super().__call__(func)
|
|
419
|
+
|
|
420
|
+
import time
|
|
421
|
+
import functools
|
|
422
|
+
|
|
423
|
+
if inspect.iscoroutinefunction(func):
|
|
424
|
+
@functools.wraps(func)
|
|
425
|
+
async def async_wrapper(*args, **kwargs):
|
|
426
|
+
start_time = time.time()
|
|
427
|
+
try:
|
|
428
|
+
return await func(*args, **kwargs)
|
|
429
|
+
finally:
|
|
430
|
+
execution_time = time.time() - start_time
|
|
431
|
+
logger = self._get_logger(func)
|
|
432
|
+
log_method = getattr(logger, self.log_level.lower(), logger.info)
|
|
433
|
+
log_method(
|
|
434
|
+
f"Execution time for {func.__name__}: {execution_time:.4f}s"
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
return async_wrapper
|
|
438
|
+
|
|
439
|
+
@functools.wraps(func)
|
|
440
|
+
def wrapper(*args, **kwargs):
|
|
441
|
+
start_time = time.time()
|
|
442
|
+
try:
|
|
443
|
+
return func(*args, **kwargs)
|
|
444
|
+
finally:
|
|
445
|
+
execution_time = time.time() - start_time
|
|
446
|
+
logger = self._get_logger(func)
|
|
447
|
+
log_method = getattr(logger, self.log_level.lower(), logger.info)
|
|
448
|
+
log_method(f"Execution time for {func.__name__}: {execution_time:.4f}s")
|
|
449
|
+
|
|
450
|
+
return wrapper
|
|
451
|
+
|
|
452
|
+
def _get_logger(self, func: Callable) -> Any:
|
|
453
|
+
from spring.utils.logger import get_logger
|
|
454
|
+
return get_logger(func.__module__)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
class PostConstruct(SpringAnnotation):
|
|
458
|
+
_annotation_type = "lifecycle"
|
|
459
|
+
|
|
460
|
+
def __init__(self):
|
|
461
|
+
super().__init__()
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class PreDestroy(SpringAnnotation):
|
|
465
|
+
_annotation_type = "lifecycle"
|
|
466
|
+
|
|
467
|
+
def __init__(self):
|
|
468
|
+
super().__init__()
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
class Primary(SpringAnnotation):
|
|
472
|
+
_annotation_type = "primary"
|
|
473
|
+
|
|
474
|
+
def __init__(self):
|
|
475
|
+
super().__init__()
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
class Profile(SpringAnnotation):
|
|
479
|
+
_annotation_type = "profile"
|
|
480
|
+
|
|
481
|
+
def __init__(self, value: Union[str, List[str]]):
|
|
482
|
+
if isinstance(value, str):
|
|
483
|
+
value = [value]
|
|
484
|
+
super().__init__(value=value)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
class Lazy(SpringAnnotation):
|
|
488
|
+
_annotation_type = "lazy"
|
|
489
|
+
|
|
490
|
+
def __init__(self, value: bool = True):
|
|
491
|
+
super().__init__(value=value)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
class RequestHeader:
|
|
495
|
+
_annotation_type = "param"
|
|
496
|
+
|
|
497
|
+
def __init__(
|
|
498
|
+
self,
|
|
499
|
+
name: Optional[str] = None,
|
|
500
|
+
required: bool = True,
|
|
501
|
+
default: Any = None,
|
|
502
|
+
value: Optional[str] = None,
|
|
503
|
+
):
|
|
504
|
+
self.name = value if value is not None else name
|
|
505
|
+
self.required = required
|
|
506
|
+
self.default = default
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
class CookieValue:
|
|
510
|
+
_annotation_type = "param"
|
|
511
|
+
|
|
512
|
+
def __init__(
|
|
513
|
+
self,
|
|
514
|
+
name: Optional[str] = None,
|
|
515
|
+
required: bool = True,
|
|
516
|
+
default: Any = None,
|
|
517
|
+
value: Optional[str] = None,
|
|
518
|
+
):
|
|
519
|
+
self.name = value if value is not None else name
|
|
520
|
+
self.required = required
|
|
521
|
+
self.default = default
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
class ResponseStatus(SpringAnnotation):
|
|
525
|
+
_annotation_type = "response"
|
|
526
|
+
|
|
527
|
+
def __init__(self, code: int, reason: str = ""):
|
|
528
|
+
super().__init__(code=code, reason=reason)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
class Transactional(SpringAnnotation):
|
|
532
|
+
_annotation_type = "aop"
|
|
533
|
+
|
|
534
|
+
def __init__(
|
|
535
|
+
self,
|
|
536
|
+
propagation: str = "REQUIRED",
|
|
537
|
+
rollback_for: Optional[List[Type[Exception]]] = None,
|
|
538
|
+
no_rollback_for: Optional[List[Type[Exception]]] = None,
|
|
539
|
+
):
|
|
540
|
+
super().__init__(
|
|
541
|
+
propagation=propagation,
|
|
542
|
+
rollback_for=rollback_for or [],
|
|
543
|
+
no_rollback_for=no_rollback_for or [],
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
class Cacheable(SpringAnnotation):
|
|
548
|
+
_annotation_type = "aop"
|
|
549
|
+
|
|
550
|
+
def __init__(
|
|
551
|
+
self,
|
|
552
|
+
value: str,
|
|
553
|
+
key: Optional[str] = None,
|
|
554
|
+
condition: Optional[str] = None,
|
|
555
|
+
):
|
|
556
|
+
super().__init__(value=value, key=key, condition=condition)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
class Retryable(SpringAnnotation):
|
|
560
|
+
_annotation_type = "aop"
|
|
561
|
+
|
|
562
|
+
def __init__(
|
|
563
|
+
self,
|
|
564
|
+
value: Optional[Tuple[Type[Exception], ...]] = None,
|
|
565
|
+
max_retries: int = 3,
|
|
566
|
+
backoff: Optional[Union['Backoff', int, float]] = None,
|
|
567
|
+
exclude: Optional[Tuple[Type[Exception], ...]] = None,
|
|
568
|
+
recover: str = "",
|
|
569
|
+
max_attempts: Optional[int] = None,
|
|
570
|
+
):
|
|
571
|
+
from spring.retry.retry_annotations import Backoff as RetryBackoff
|
|
572
|
+
|
|
573
|
+
if max_attempts is not None:
|
|
574
|
+
if max_retries != 3 and max_retries != max_attempts:
|
|
575
|
+
raise ValueError("max_retries 与 max_attempts 不能设置为不同值")
|
|
576
|
+
max_retries = max_attempts
|
|
577
|
+
if max_retries <= 0:
|
|
578
|
+
raise ValueError("max_retries 必须大于0")
|
|
579
|
+
if isinstance(backoff, (int, float)):
|
|
580
|
+
if backoff < 0:
|
|
581
|
+
raise ValueError("backoff 延迟不能小于0")
|
|
582
|
+
backoff = RetryBackoff(
|
|
583
|
+
delay=int(backoff),
|
|
584
|
+
max_delay=int(backoff),
|
|
585
|
+
multiplier=1.0,
|
|
586
|
+
random_factor=0.0,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
super().__init__(
|
|
590
|
+
value=value or (Exception,),
|
|
591
|
+
max_retries=max_retries,
|
|
592
|
+
backoff=backoff or RetryBackoff(),
|
|
593
|
+
exclude=exclude or (),
|
|
594
|
+
recover=recover,
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
class Async(SpringAnnotation):
|
|
599
|
+
_annotation_type = "aop"
|
|
600
|
+
|
|
601
|
+
def __init__(self):
|
|
602
|
+
super().__init__()
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
class Scheduled(SpringAnnotation):
|
|
606
|
+
_annotation_type = "scheduling"
|
|
607
|
+
|
|
608
|
+
def __init__(
|
|
609
|
+
self,
|
|
610
|
+
fixed_rate: Optional[int] = None,
|
|
611
|
+
fixed_delay: Optional[int] = None,
|
|
612
|
+
cron: Optional[str] = None,
|
|
613
|
+
initial_delay: int = 0,
|
|
614
|
+
):
|
|
615
|
+
configured = [fixed_rate is not None, fixed_delay is not None, cron is not None]
|
|
616
|
+
if sum(configured) != 1:
|
|
617
|
+
raise ValueError("Scheduled 必须且只能设置 fixed_rate、fixed_delay 或 cron 之一")
|
|
618
|
+
if fixed_rate is not None and fixed_rate <= 0:
|
|
619
|
+
raise ValueError("fixed_rate 必须大于0")
|
|
620
|
+
if fixed_delay is not None and fixed_delay <= 0:
|
|
621
|
+
raise ValueError("fixed_delay 必须大于0")
|
|
622
|
+
if initial_delay < 0:
|
|
623
|
+
raise ValueError("initial_delay 不能小于0")
|
|
624
|
+
super().__init__(
|
|
625
|
+
fixed_rate=fixed_rate,
|
|
626
|
+
fixed_delay=fixed_delay,
|
|
627
|
+
cron=cron,
|
|
628
|
+
initial_delay=initial_delay,
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
class AsyncResult(SpringAnnotation):
|
|
633
|
+
_annotation_type = "async"
|
|
634
|
+
|
|
635
|
+
def __init__(self, value: Any = None):
|
|
636
|
+
super().__init__(value=value)
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
# ==================== 进阶骚操作注解 ====================
|
|
640
|
+
|
|
641
|
+
class RateLimit(SpringAnnotation):
|
|
642
|
+
"""接口限流注解"""
|
|
643
|
+
_annotation_type = "aop"
|
|
644
|
+
|
|
645
|
+
def __init__(
|
|
646
|
+
self,
|
|
647
|
+
max_requests: int = 100,
|
|
648
|
+
time_window: int = 60,
|
|
649
|
+
key: str = None,
|
|
650
|
+
):
|
|
651
|
+
super().__init__(
|
|
652
|
+
max_requests=max_requests,
|
|
653
|
+
time_window=time_window,
|
|
654
|
+
key=key,
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
class CircuitBreaker(SpringAnnotation):
|
|
659
|
+
"""熔断器注解"""
|
|
660
|
+
_annotation_type = "aop"
|
|
661
|
+
|
|
662
|
+
def __init__(
|
|
663
|
+
self,
|
|
664
|
+
failure_threshold: int = 5,
|
|
665
|
+
recovery_timeout: int = 30,
|
|
666
|
+
fallback_method: str = None,
|
|
667
|
+
):
|
|
668
|
+
super().__init__(
|
|
669
|
+
failure_threshold=failure_threshold,
|
|
670
|
+
recovery_timeout=recovery_timeout,
|
|
671
|
+
fallback_method=fallback_method,
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
class Idempotent(SpringAnnotation):
|
|
676
|
+
"""幂等性注解"""
|
|
677
|
+
_annotation_type = "aop"
|
|
678
|
+
|
|
679
|
+
def __init__(
|
|
680
|
+
self,
|
|
681
|
+
key: str = None,
|
|
682
|
+
expire: int = 300,
|
|
683
|
+
prefix: str = "idempotent",
|
|
684
|
+
):
|
|
685
|
+
super().__init__(
|
|
686
|
+
key=key,
|
|
687
|
+
expire=expire,
|
|
688
|
+
prefix=prefix,
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
class AuditLog(SpringAnnotation):
|
|
693
|
+
"""审计日志注解"""
|
|
694
|
+
_annotation_type = "aop"
|
|
695
|
+
|
|
696
|
+
def __init__(
|
|
697
|
+
self,
|
|
698
|
+
action: str = "",
|
|
699
|
+
target: str = "",
|
|
700
|
+
detail: str = "",
|
|
701
|
+
level: str = "INFO",
|
|
702
|
+
):
|
|
703
|
+
super().__init__(
|
|
704
|
+
action=action,
|
|
705
|
+
target=target,
|
|
706
|
+
detail=detail,
|
|
707
|
+
level=level,
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
class FeatureToggle(SpringAnnotation):
|
|
712
|
+
"""功能开关注解"""
|
|
713
|
+
_annotation_type = "aop"
|
|
714
|
+
|
|
715
|
+
def __init__(
|
|
716
|
+
self,
|
|
717
|
+
name: str,
|
|
718
|
+
default: bool = False,
|
|
719
|
+
):
|
|
720
|
+
super().__init__(
|
|
721
|
+
name=name,
|
|
722
|
+
default=default,
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
class Lock(SpringAnnotation):
|
|
727
|
+
"""分布式锁注解"""
|
|
728
|
+
_annotation_type = "aop"
|
|
729
|
+
|
|
730
|
+
def __init__(
|
|
731
|
+
self,
|
|
732
|
+
key: str = None,
|
|
733
|
+
expire: int = 10,
|
|
734
|
+
wait_timeout: int = 5,
|
|
735
|
+
prefix: str = "lock",
|
|
736
|
+
):
|
|
737
|
+
super().__init__(
|
|
738
|
+
key=key,
|
|
739
|
+
expire=expire,
|
|
740
|
+
wait_timeout=wait_timeout,
|
|
741
|
+
prefix=prefix,
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
class Metrics(SpringAnnotation):
|
|
746
|
+
"""指标监控注解"""
|
|
747
|
+
_annotation_type = "aop"
|
|
748
|
+
|
|
749
|
+
def __init__(
|
|
750
|
+
self,
|
|
751
|
+
name: str = None,
|
|
752
|
+
tags: List[str] = None,
|
|
753
|
+
):
|
|
754
|
+
super().__init__(
|
|
755
|
+
name=name,
|
|
756
|
+
tags=tags or [],
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
class Synchronized(SpringAnnotation):
|
|
761
|
+
"""方法同步注解"""
|
|
762
|
+
_annotation_type = "aop"
|
|
763
|
+
|
|
764
|
+
def __init__(
|
|
765
|
+
self,
|
|
766
|
+
lock_name: str = None,
|
|
767
|
+
):
|
|
768
|
+
super().__init__(
|
|
769
|
+
lock_name=lock_name,
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
class Validate(SpringAnnotation):
|
|
774
|
+
"""参数校验注解"""
|
|
775
|
+
_annotation_type = "param"
|
|
776
|
+
|
|
777
|
+
def __init__(
|
|
778
|
+
self,
|
|
779
|
+
field: str = None,
|
|
780
|
+
min_length: int = None,
|
|
781
|
+
max_length: int = None,
|
|
782
|
+
min: float = None,
|
|
783
|
+
max: float = None,
|
|
784
|
+
regex: str = None,
|
|
785
|
+
message: str = None,
|
|
786
|
+
):
|
|
787
|
+
super().__init__(
|
|
788
|
+
field=field,
|
|
789
|
+
min_length=min_length,
|
|
790
|
+
max_length=max_length,
|
|
791
|
+
min=min,
|
|
792
|
+
max=max,
|
|
793
|
+
regex=regex,
|
|
794
|
+
message=message,
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
class Trace(SpringAnnotation):
|
|
799
|
+
"""分布式追踪注解"""
|
|
800
|
+
_annotation_type = "aop"
|
|
801
|
+
|
|
802
|
+
def __init__(
|
|
803
|
+
self,
|
|
804
|
+
trace_id_key: str = "X-Trace-ID",
|
|
805
|
+
span_name: str = None,
|
|
806
|
+
):
|
|
807
|
+
super().__init__(
|
|
808
|
+
trace_id_key=trace_id_key,
|
|
809
|
+
span_name=span_name,
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
# ==================== 安全注解 ====================
|
|
814
|
+
|
|
815
|
+
class PreAuthorize(SpringAnnotation):
|
|
816
|
+
"""
|
|
817
|
+
方法级权限控制注解
|
|
818
|
+
支持表达式:
|
|
819
|
+
- hasRole('ROLE_ADMIN')
|
|
820
|
+
- hasAnyRole('ROLE_ADMIN', 'ROLE_USER')
|
|
821
|
+
- hasPermission('user:read')
|
|
822
|
+
- hasAnyPermission('user:read', 'user:write')
|
|
823
|
+
|
|
824
|
+
使用示例:
|
|
825
|
+
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
|
826
|
+
def delete_user(self, user_id: int):
|
|
827
|
+
pass
|
|
828
|
+
"""
|
|
829
|
+
_annotation_type = "security"
|
|
830
|
+
|
|
831
|
+
def __init__(self, value: str):
|
|
832
|
+
super().__init__(value=value)
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
class Secured(SpringAnnotation):
|
|
836
|
+
"""
|
|
837
|
+
角色权限控制注解
|
|
838
|
+
检查当前用户是否拥有指定角色中的任一角色
|
|
839
|
+
|
|
840
|
+
使用示例:
|
|
841
|
+
@Secured(["ROLE_ADMIN", "ROLE_USER"])
|
|
842
|
+
def update_user(self, user_id: int):
|
|
843
|
+
pass
|
|
844
|
+
"""
|
|
845
|
+
_annotation_type = "security"
|
|
846
|
+
|
|
847
|
+
def __init__(self, value: List[str]):
|
|
848
|
+
super().__init__(value=value)
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
class Authenticate(SpringAnnotation):
|
|
852
|
+
"""
|
|
853
|
+
认证注解
|
|
854
|
+
从请求头中获取 JWT Token 并验证,设置安全上下文
|
|
855
|
+
|
|
856
|
+
使用示例:
|
|
857
|
+
@Authenticate
|
|
858
|
+
def get_user_profile(self):
|
|
859
|
+
pass
|
|
860
|
+
"""
|
|
861
|
+
_annotation_type = "security"
|
|
862
|
+
|
|
863
|
+
def __init__(self):
|
|
864
|
+
super().__init__()
|