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
@@ -0,0 +1,357 @@
1
+ """SpringBootAI Bean Validation 约束注解 —— 字段级描述符。
2
+
3
+ 设计原则:**复用项目既有范式,不重复造轮子**。本模块的字段级约束完全镜像 ORM 层
4
+ ``spring/orm/ddl_auto.py`` 的 ``Column``/``Id`` 与 Excel 层 ``spring/excel/annotations.py``
5
+ 的 ``ExcelProperty`` 元数据描述符范式:
6
+
7
+ - 字段级约束以**类属性描述符**形式声明(推荐),Python 自动回填字段名;也支持**函数装饰器**
8
+ 形式(镜像 ``column()`` / ``ExcelProperty.__call__``)。
9
+ - 元数据通过 ``cls.__mro__`` 反射读取(与 ``Column``/``__column__`` 一致),由
10
+ ``BeanValidator`` 统一收集并校验。
11
+
12
+ 对齐 Jakarta Bean Validation(``javax.validation.constraints``)的核心约束:
13
+ ``@NotNull`` / ``@NotBlank`` / ``@NotEmpty`` / ``@Size`` / ``@Min`` / ``@Max`` /
14
+ ``@Pattern`` / ``@Email`` / ``@Positive`` / ``@PositiveOrZero`` / ``@Negative`` /
15
+ ``@NegativeOrZero`` / ``@AssertTrue`` / ``@AssertFalse``。
16
+
17
+ 与 Java 的差异(已标注):
18
+ - Java Bean Validation 是 JSR-380 标准 + Hibernate Validator 实现,运行时通过反射读注解;
19
+ 本模块同样反射读字段描述符,但不依赖 JPA/Pydantic,适用于任意 Python 对象。
20
+ - 约束只支持字段级(Java 还支持方法参数级/返回值级),方法级校验由 ``@BeanValidate`` AOP
21
+ 切面对参数对象整体校验实现(见 ``validator.py``)。
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import re
26
+ from typing import Any, Callable, Optional
27
+
28
+
29
+ class Constraint:
30
+ """所有字段约束的基类(镜像 ORM ``Column`` 描述符范式)。
31
+
32
+ 子类需实现 ``_check(value) -> Optional[str]``,返回违规消息字符串;通过返回 ``None``
33
+ 表示通过。``message`` 可由用户覆盖。
34
+
35
+ 两种使用方式(与 ``Column``/``ExcelProperty`` 一致):
36
+
37
+ 1. 类属性描述符(推荐)::
38
+
39
+ class User:
40
+ name = NotBlank(message="姓名不能为空")
41
+ age = Min(0, message="年龄不能为负")
42
+ def __init__(self, name=None, age=None): ...
43
+
44
+ 2. 函数装饰器(镜像 ``@column``)::
45
+
46
+ @NotBlank()
47
+ def name(self): ...
48
+ """
49
+
50
+ # 约束名,子类覆盖(用于校验报告归类)
51
+ constraint_name: str = "Constraint"
52
+
53
+ def __init__(self, message: Optional[str] = None):
54
+ self.message = message
55
+ self.attr_name: str = ""
56
+
57
+ def __set_name__(self, owner: type, name: str) -> None:
58
+ """类属性描述符形式时,Python 自动回填字段名(镜像 ``ExcelProperty``)。"""
59
+ self.attr_name = name
60
+
61
+ def __call__(self, target: Callable) -> Callable:
62
+ """函数装饰器形式:``@NotBlank()``,把约束挂到 ``__bean_constraint__`` 列表。
63
+
64
+ 镜像 ORM ``column()`` 的 ``setattr(f, '__column__', col)`` 与
65
+ ``ExcelProperty.__call__`` 的 ``setattr(target, '__excel_property__', self)``。
66
+ 一个方法上可叠加多个约束(用列表累积,而非覆盖)。
67
+ """
68
+ existing = getattr(target, "__bean_constraint__", None)
69
+ if isinstance(existing, list):
70
+ existing.append(self)
71
+ else:
72
+ setattr(target, "__bean_constraint__", [self])
73
+ if not self.attr_name:
74
+ self.attr_name = getattr(target, "__name__", "")
75
+ return target
76
+
77
+ def _check(self, value: Any) -> Optional[str]:
78
+ """子类实现:返回违规消息(None 表示通过)。"""
79
+ raise NotImplementedError
80
+
81
+ def validate(self, value: Any) -> Optional[str]:
82
+ """对外校验入口:返回违规消息或 None。
83
+
84
+ 默认消息优先取用户自定义 ``message``,否则取 ``_check`` 返回的默认消息。
85
+ """
86
+ msg = self._check(value)
87
+ if msg is None:
88
+ return None
89
+ return self.message if self.message else msg
90
+
91
+ def __repr__(self) -> str:
92
+ return f"{type(self).__name__}(attr={self.attr_name!r})"
93
+
94
+
95
+ # ==================== 非空类约束 ====================
96
+
97
+ class NotNull(Constraint):
98
+ """``@NotNull``:值不能为 None(允许空字符串/空集合)。
99
+
100
+ 对齐 ``javax.validation.constraints.NotNull``。
101
+ """
102
+ constraint_name = "NotNull"
103
+
104
+ def _check(self, value: Any) -> Optional[str]:
105
+ if value is None:
106
+ return "不能为 null"
107
+ return None
108
+
109
+
110
+ class NotBlank(Constraint):
111
+ """``@NotBlank``:字符串不能为 None 且去除首尾空白后长度 > 0。
112
+
113
+ 对齐 ``javax.validation.constraints.NotBlank``(仅作用于字符串)。
114
+ """
115
+ constraint_name = "NotBlank"
116
+
117
+ def _check(self, value: Any) -> Optional[str]:
118
+ if value is None:
119
+ return "不能为空"
120
+ if not isinstance(value, str):
121
+ return None # 非字符串交给其他约束(如 NotNull)
122
+ if value.strip() == "":
123
+ return "不能为空白"
124
+ return None
125
+
126
+
127
+ class NotEmpty(Constraint):
128
+ """``@NotEmpty``:不能为 None 且 size > 0(字符串/集合/字典/数组)。
129
+
130
+ 对齐 ``javax.validation.constraints.NotEmpty``。
131
+ """
132
+ constraint_name = "NotEmpty"
133
+
134
+ def _check(self, value: Any) -> Optional[str]:
135
+ if value is None:
136
+ return "不能为空"
137
+ try:
138
+ if len(value) == 0:
139
+ return "长度/大小必须大于 0"
140
+ except TypeError:
141
+ return None # 无 len() 的对象,不强制
142
+ return None
143
+
144
+
145
+ # ==================== 长度/大小约束 ====================
146
+
147
+ class Size(Constraint):
148
+ """``@Size``:字符串/集合/数组长度在 ``[min, max]`` 区间。
149
+
150
+ 对齐 ``javax.validation.constraints.Size``。``min`` 默认 0,``max`` 默认 2^31-1。
151
+ """
152
+ constraint_name = "Size"
153
+
154
+ def __init__(self, min: int = 0, max: int = 2 ** 31 - 1, message: Optional[str] = None):
155
+ super().__init__(message=message)
156
+ if min < 0:
157
+ raise ValueError("@Size min 不能为负")
158
+ if max < min:
159
+ raise ValueError("@Size max 不能小于 min")
160
+ self.min = min
161
+ self.max = max
162
+
163
+ def _check(self, value: Any) -> Optional[str]:
164
+ if value is None:
165
+ return None # null 交给 @NotNull
166
+ try:
167
+ length = len(value)
168
+ except TypeError:
169
+ return None # 无 len() 的对象,不强制
170
+ if length < self.min or length > self.max:
171
+ return f"长度必须在 {self.min} 到 {self.max} 之间(实际 {length})"
172
+ return None
173
+
174
+
175
+ # ==================== 数值范围约束 ====================
176
+
177
+ class Min(Constraint):
178
+ """``@Min``:数值 >= value。对齐 ``javax.validation.constraints.Min``。"""
179
+ constraint_name = "Min"
180
+
181
+ def __init__(self, value: Any, message: Optional[str] = None):
182
+ super().__init__(message=message)
183
+ self.value = value
184
+
185
+ def _check(self, value: Any) -> Optional[str]:
186
+ if value is None:
187
+ return None
188
+ try:
189
+ if float(value) < float(self.value):
190
+ return f"必须大于等于 {self.value}"
191
+ except (TypeError, ValueError):
192
+ return None # 非数值,不强制
193
+ return None
194
+
195
+
196
+ class Max(Constraint):
197
+ """``@Max``:数值 <= value。对齐 ``javax.validation.constraints.Max``。"""
198
+ constraint_name = "Max"
199
+
200
+ def __init__(self, value: Any, message: Optional[str] = None):
201
+ super().__init__(message=message)
202
+ self.value = value
203
+
204
+ def _check(self, value: Any) -> Optional[str]:
205
+ if value is None:
206
+ return None
207
+ try:
208
+ if float(value) > float(self.value):
209
+ return f"必须小于等于 {self.value}"
210
+ except (TypeError, ValueError):
211
+ return None
212
+ return None
213
+
214
+
215
+ class Positive(Constraint):
216
+ """``@Positive``:数值 > 0。对齐 ``javax.validation.constraints.Positive``。"""
217
+ constraint_name = "Positive"
218
+
219
+ def _check(self, value: Any) -> Optional[str]:
220
+ if value is None:
221
+ return None
222
+ try:
223
+ if float(value) <= 0:
224
+ return "必须为正数"
225
+ except (TypeError, ValueError):
226
+ return None
227
+ return None
228
+
229
+
230
+ class PositiveOrZero(Constraint):
231
+ """``@PositiveOrZero``:数值 >= 0。"""
232
+ constraint_name = "PositiveOrZero"
233
+
234
+ def _check(self, value: Any) -> Optional[str]:
235
+ if value is None:
236
+ return None
237
+ try:
238
+ if float(value) < 0:
239
+ return "必须大于等于 0"
240
+ except (TypeError, ValueError):
241
+ return None
242
+ return None
243
+
244
+
245
+ class Negative(Constraint):
246
+ """``@Negative``:数值 < 0。"""
247
+ constraint_name = "Negative"
248
+
249
+ def _check(self, value: Any) -> Optional[str]:
250
+ if value is None:
251
+ return None
252
+ try:
253
+ if float(value) >= 0:
254
+ return "必须为负数"
255
+ except (TypeError, ValueError):
256
+ return None
257
+ return None
258
+
259
+
260
+ class NegativeOrZero(Constraint):
261
+ """``@NegativeOrZero``:数值 <= 0。"""
262
+ constraint_name = "NegativeOrZero"
263
+
264
+ def _check(self, value: Any) -> Optional[str]:
265
+ if value is None:
266
+ return None
267
+ try:
268
+ if float(value) > 0:
269
+ return "必须小于等于 0"
270
+ except (TypeError, ValueError):
271
+ return None
272
+ return None
273
+
274
+
275
+ # ==================== 字符串格式约束 ====================
276
+
277
+ class Pattern(Constraint):
278
+ """``@Pattern``:字符串匹配正则。对齐 ``javax.validation.constraints.Pattern``。"""
279
+ constraint_name = "Pattern"
280
+
281
+ def __init__(self, regex: str, message: Optional[str] = None):
282
+ super().__init__(message=message)
283
+ try:
284
+ self._compiled = re.compile(regex)
285
+ except re.error as e:
286
+ raise ValueError(f"@Pattern 正则非法: {e}") from e
287
+ self.regex = regex
288
+
289
+ def _check(self, value: Any) -> Optional[str]:
290
+ if value is None:
291
+ return None
292
+ if not isinstance(value, str):
293
+ value = str(value)
294
+ if not self._compiled.search(value):
295
+ return f"不匹配模式 {self.regex!r}"
296
+ return None
297
+
298
+
299
+ # 常见邮箱正则(与 Hibernate Validator Email 推荐一致,宽松版本)
300
+ _EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
301
+
302
+
303
+ class Email(Constraint):
304
+ """``@Email``:字符串为合法邮箱格式。对齐 ``javax.validation.constraints.Email``。
305
+
306
+ 采用宽松邮箱正则(``local@domain.tld``),不追求 RFC 5322 完整覆盖,
307
+ 生产环境如需更严格校验请配合 ``@Pattern`` 自定义。
308
+ """
309
+ constraint_name = "Email"
310
+
311
+ def _check(self, value: Any) -> Optional[str]:
312
+ if value is None:
313
+ return None
314
+ if not isinstance(value, str):
315
+ value = str(value)
316
+ if value == "":
317
+ return None # 空串交给 @NotBlank
318
+ if not _EMAIL_RE.match(value):
319
+ return "邮箱格式不合法"
320
+ return None
321
+
322
+
323
+ # ==================== 布尔断言约束 ====================
324
+
325
+ class AssertTrue(Constraint):
326
+ """``@AssertTrue``:值必须为 True(或真值)。对齐 ``javax.validation.constraints.AssertTrue``。"""
327
+ constraint_name = "AssertTrue"
328
+
329
+ def _check(self, value: Any) -> Optional[str]:
330
+ if value is None:
331
+ return None
332
+ if not bool(value):
333
+ return "必须为 true"
334
+ return None
335
+
336
+
337
+ class AssertFalse(Constraint):
338
+ """``@AssertFalse``:值必须为 False(或假值)。对齐 ``javax.validation.constraints.AssertFalse``。"""
339
+ constraint_name = "AssertFalse"
340
+
341
+ def _check(self, value: Any) -> Optional[str]:
342
+ if value is None:
343
+ return None
344
+ if bool(value):
345
+ return "必须为 false"
346
+ return None
347
+
348
+
349
+ __all__ = [
350
+ "Constraint",
351
+ "NotNull", "NotBlank", "NotEmpty",
352
+ "Size",
353
+ "Min", "Max",
354
+ "Positive", "PositiveOrZero", "Negative", "NegativeOrZero",
355
+ "Pattern", "Email",
356
+ "AssertTrue", "AssertFalse",
357
+ ]
@@ -0,0 +1,55 @@
1
+ """SpringBootAI Bean Validation 异常定义。
2
+
3
+ 设计对齐 Jakarta Bean Validation(Hibernate Validator)的错误语义:
4
+ - ``ValidationError``:校验失败时抛出的汇总异常,包含全部约束违反。
5
+ - ``ConstraintViolation``:单条约束违反信息(字段、值、消息、约束类型)。
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, List
10
+
11
+
12
+ class ConstraintViolation:
13
+ """单条约束违反记录。
14
+
15
+ 属性:
16
+ attr_name: 违反约束的字段名。
17
+ value: 被校验的实际值。
18
+ constraint: 触发违反的约束注解实例(如 ``NotNull``/``Size``)。
19
+ message: 人可读的违规描述。
20
+ """
21
+
22
+ __slots__ = ("attr_name", "value", "constraint", "message")
23
+
24
+ def __init__(self, attr_name: str, value: Any, constraint: Any, message: str):
25
+ self.attr_name = attr_name
26
+ self.value = value
27
+ self.constraint = constraint
28
+ self.message = message
29
+
30
+ def __repr__(self) -> str:
31
+ cname = type(self.constraint).__name__
32
+ return (f"ConstraintViolation(attr={self.attr_name!r}, "
33
+ f"constraint={cname}, value={self.value!r}, message={self.message!r})")
34
+
35
+ def __str__(self) -> str:
36
+ return f"{self.attr_name}: {self.message}"
37
+
38
+
39
+ class ValidationError(Exception):
40
+ """Bean Validation 校验失败异常。
41
+
42
+ 汇总一次校验产生的所有 ``ConstraintViolation``,便于上层统一处理或批量回显。
43
+ """
44
+
45
+ def __init__(self, violations: List[ConstraintViolation]):
46
+ self.violations: List[ConstraintViolation] = list(violations)
47
+ lines = "; ".join(str(v) for v in self.violations) if self.violations else "校验失败"
48
+ super().__init__(lines)
49
+
50
+ @property
51
+ def messages(self) -> List[str]:
52
+ return [v.message for v in self.violations]
53
+
54
+
55
+ __all__ = ["ConstraintViolation", "ValidationError"]
@@ -0,0 +1,139 @@
1
+ """SpringBootAI Bean Validation 验证器。
2
+
3
+ ``BeanValidator`` 反射实体类的字段约束(``Constraint`` 描述符或 ``__bean_constraint__``
4
+ 函数装饰器),对一个对象实例执行全部约束校验,收集 ``ConstraintViolation``。
5
+
6
+ 镜像 ORM ``DdlAutoManager._parse_entity`` 与 Excel ``parse_excel_columns`` 的反射范式:
7
+ 遍历 ``cls.__mro__`` 的 ``__dict__``,自底向上收集每个字段的约束列表,子类覆盖父类。
8
+
9
+ 用法::
10
+
11
+ from spring.validation import BeanValidator, NotBlank, Min
12
+
13
+ class User:
14
+ name = NotBlank(message="姓名不能为空")
15
+ age = Min(0)
16
+ def __init__(self, name=None, age=None):
17
+ self.name = name; self.age = age
18
+
19
+ violations = BeanValidator.validate(User(name="", age=-1))
20
+ if violations:
21
+ ... # 处理违规
22
+ BeanValidator.validate_or_raise(User(name="Tom", age=18)) # 通过则不抛错
23
+
24
+ 方法级校验由 ``@BeanValidate`` AOP 切面驱动(注册到 ``comprehensive_aop``),见模块 ``__init__``。
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import inspect
29
+ from typing import Any, Dict, List, Optional, Type
30
+
31
+ from .constraints import Constraint
32
+ from .exceptions import ConstraintViolation, ValidationError
33
+
34
+
35
+ def _collect_constraints(cls: Type) -> Dict[str, List[Constraint]]:
36
+ """反射收集类(含 MRO 父类)每个字段的约束列表。
37
+
38
+ 镜像 ORM ``_parse_entity`` 与 Excel ``parse_excel_columns`` 的 MRO 遍历:
39
+ 自底向上(``reversed(cls.__mro__)``),子类约束覆盖父类同名字段。
40
+ 支持两种声明形式:
41
+ 1. 类属性 ``Constraint`` 描述符(如 ``name = NotBlank()``)。
42
+ 2. 函数上的 ``__bean_constraint__`` 列表(``@NotBlank() def name(self): ...``)。
43
+ """
44
+ collected: Dict[str, List[Constraint]] = {}
45
+
46
+ for base in reversed(cls.__mro__):
47
+ if base is object:
48
+ continue
49
+ for attr_name, value in vars(base).items():
50
+ if attr_name.startswith("__"):
51
+ continue
52
+ constraints: List[Constraint] = []
53
+ # 形式1:类属性 Constraint 描述符实例
54
+ if isinstance(value, Constraint):
55
+ if not value.attr_name:
56
+ value.attr_name = attr_name
57
+ constraints.append(value)
58
+ # 形式2:函数装饰器上的 __bean_constraint__ 列表
59
+ elif hasattr(value, "__bean_constraint__"):
60
+ clist = getattr(value, "__bean_constraint__")
61
+ if isinstance(clist, list):
62
+ for c in clist:
63
+ if isinstance(c, Constraint) and not c.attr_name:
64
+ c.attr_name = attr_name
65
+ constraints.append(c)
66
+ if constraints:
67
+ # 子类覆盖父类同名字段(与 ORM/Excel 解析一致)
68
+ collected[attr_name] = constraints
69
+ return collected
70
+
71
+
72
+ def _get_field_value(obj: Any, attr_name: str) -> Any:
73
+ """从对象实例取字段值:优先 ``getattr``,失败则 None。"""
74
+ try:
75
+ return getattr(obj, attr_name)
76
+ except AttributeError:
77
+ return None
78
+
79
+
80
+ class BeanValidator:
81
+ """Bean Validation 校验器(静态方法风格,无状态,可直接调用)。
82
+
83
+ 设计为无状态工具类,对齐 Jakarta ``Validator`` 接口的 ``validate`` 语义,
84
+ 但简化为静态方法,避免在未接入 IoC 容器的场景下还需手动构造实例。
85
+ """
86
+
87
+ @staticmethod
88
+ def get_constraints(cls: Type) -> Dict[str, List[Constraint]]:
89
+ """返回类上声明的字段约束映射(公开 API,便于调试/报告)。"""
90
+ return _collect_constraints(cls)
91
+
92
+ @staticmethod
93
+ def validate(obj: Any, groups: Optional[List[type]] = None) -> List[ConstraintViolation]:
94
+ """校验对象实例,返回全部约束违反列表(通过则返回空列表)。
95
+
96
+ Args:
97
+ obj: 待校验对象。若是类,则按无实例处理(仅字段无值,仅 NotNull 类会触发)。
98
+ groups: 校验分组(对齐 Jakarta Validation groups)。当前实现:约束未声明 groups
99
+ 时始终执行;声明了 groups 时仅当传入 groups 命中才执行。
100
+ (分组功能为兼容预留,约束默认不限定分组。)
101
+ """
102
+ if obj is None:
103
+ return []
104
+ cls = obj if isinstance(obj, type) else type(obj)
105
+ constraints_map = _collect_constraints(cls)
106
+
107
+ violations: List[ConstraintViolation] = []
108
+ for attr_name, constraints in constraints_map.items():
109
+ value = None if isinstance(obj, type) else _get_field_value(obj, attr_name)
110
+ for constraint in constraints:
111
+ # 分组过滤:约束可选声明 groups
112
+ cgroups = getattr(constraint, "groups", None) or []
113
+ if cgroups:
114
+ if not groups or not any(g in cgroups for g in groups):
115
+ continue
116
+ msg = constraint.validate(value)
117
+ if msg is not None:
118
+ violations.append(ConstraintViolation(
119
+ attr_name=attr_name,
120
+ value=value,
121
+ constraint=constraint,
122
+ message=msg,
123
+ ))
124
+ return violations
125
+
126
+ @staticmethod
127
+ def validate_or_raise(obj: Any, groups: Optional[List[type]] = None) -> None:
128
+ """校验对象实例,存在违反则抛出 ``ValidationError``;通过则无返回。"""
129
+ violations = BeanValidator.validate(obj, groups=groups)
130
+ if violations:
131
+ raise ValidationError(violations)
132
+
133
+ @staticmethod
134
+ def is_valid(obj: Any, groups: Optional[List[type]] = None) -> bool:
135
+ """便捷判断:是否通过全部约束。"""
136
+ return not BeanValidator.validate(obj, groups=groups)
137
+
138
+
139
+ __all__ = ["BeanValidator", "_collect_constraints"]
spring/web/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ from .web_context import WebApplicationContext
2
+ from .result import Result
3
+ from .interceptor import HandlerInterceptor, InterceptorRegistry
4
+ from .exception_handler import GlobalExceptionHandler
5
+
6
+ __all__ = [
7
+ "WebApplicationContext",
8
+ "Result",
9
+ "HandlerInterceptor",
10
+ "InterceptorRegistry",
11
+ "GlobalExceptionHandler",
12
+ ]