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,133 @@
1
+ """多数据源注解(对齐 ``dynamic-datasource-spring-boot-starter`` 的 ``@DS``/``@Master``/``@Slave``)。
2
+
3
+ 注解在方法执行期间设置 ``DataSourceContextHolder`` 路由键,退出后自动复位,使受管/非受管
4
+ Bean 方法内的数据访问自动走向指定数据源。
5
+
6
+ 设计:
7
+ - **复用既有注解基类**:``@DS`` 继承 ``SpringAnnotation``,元数据挂到 ``__spring_annotations__``。
8
+ - **薄 AOP 包装**:``ds_route_decorator`` 为方法切面,``@DS``/``@Master``/``@Slave`` 通过
9
+ ``comprehensive_aop.ANNOTATION_DECORATORS`` 注册即可接入受管 Bean 包装链路;
10
+ 非受管场景用 ``apply_ds_annotations`` 手动包装(与既有 ``@Validate``/``@Cacheable`` 一致)。
11
+ - **嵌套复位**:用 ``ContextVar.reset(token)`` 保证内层方法退出后恢复外层路由键,
12
+ 对齐 Spring ``AbstractRoutingDataSource`` 的栈式语义。
13
+
14
+ 与 Java 的差异:
15
+ - ``dynamic-datasource`` starter 用 ``ThreadLocal`` + AOP;这里用 ``ContextVar`` 兼容协程。
16
+ - ``@Master``/``@Slave`` 为 ``@DS`` 的语义快捷方式,路由键分别为 ``"master"`` 与 ``"@slave"`` 占位
17
+ (``@slave`` 占位由 ``DynamicRoutingDataSource`` 在切面内解析为轮询选定的具体 slave 键)。
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import functools
22
+ import inspect
23
+ from typing import Any, Callable, Optional
24
+
25
+ from spring.annotations.core import SpringAnnotation
26
+ from .context import DataSourceContextHolder, routing_scope
27
+
28
+ # 占位路由键:``@Slave`` 标记进入从库轮询逻辑,由装饰器解析为具体 slave。
29
+ _SLAVE_PLACEHOLDER = "@__slave__"
30
+
31
+
32
+ class DS(SpringAnnotation):
33
+ """``@DS("name")`` 指定方法走具名数据源。``@DS`` 不带参数等价于默认(master)。"""
34
+
35
+ _annotation_type = "datasource"
36
+
37
+ def __init__(self, value: str = ""):
38
+ super().__init__(value=value)
39
+
40
+
41
+ class Master(SpringAnnotation):
42
+ """``@Master`` 显式走主库(等价于 ``@DS("master")``)。"""
43
+
44
+ _annotation_type = "datasource"
45
+
46
+ def __init__(self):
47
+ super().__init__(value="master")
48
+
49
+
50
+ class Slave(SpringAnnotation):
51
+ """``@Slave`` 走从库(负载均衡轮询,由 ``DynamicRoutingDataSource`` 解析)。"""
52
+
53
+ _annotation_type = "datasource"
54
+
55
+ def __init__(self):
56
+ super().__init__(value=_SLAVE_PLACEHOLDER)
57
+
58
+
59
+ def _resolve_routing_key(ds_annotation: SpringAnnotation) -> Optional[str]:
60
+ """从注解实例解析路由键。
61
+
62
+ ``@Slave`` 始终返回占位键,由 ``DynamicRoutingDataSource`` 在连接时解析为轮询 slave;
63
+ 若未接入动态数据源,占位键命中不到任何池,``determine_target_data_source`` 回退默认目标,
64
+ 行为安全。``@DS("")``/``@DS`` 不带参数返回 ``None``(默认目标)。
65
+ """
66
+ value = getattr(ds_annotation, "value", "") or ""
67
+ if value == _SLAVE_PLACEHOLDER:
68
+ return _SLAVE_PLACEHOLDER
69
+ return value or None
70
+
71
+
72
+ def ds_route_decorator(method: Callable, ds_annotation: SpringAnnotation) -> Callable:
73
+ """``@DS``/``@Master``/``@Slave`` 的方法级切面:进入设路由键,退出复位。
74
+
75
+ 保留双参数签名供手写调用;``comprehensive_aop`` 注册使用 ``ds_decorator_factory`` 工厂形式。
76
+ """
77
+ routing_key = _resolve_routing_key(ds_annotation)
78
+
79
+ if inspect.iscoroutinefunction(method):
80
+ @functools.wraps(method)
81
+ async def async_wrapper(*args, **kwargs):
82
+ with routing_scope(routing_key):
83
+ return await method(*args, **kwargs)
84
+ return async_wrapper
85
+
86
+ @functools.wraps(method)
87
+ def wrapper(*args, **kwargs):
88
+ with routing_scope(routing_key):
89
+ return method(*args, **kwargs)
90
+ return wrapper
91
+
92
+
93
+ def ds_decorator_factory(ds_annotation: SpringAnnotation) -> Callable:
94
+ """AOP 装饰器工厂(对齐 ``comprehensive_aop.ANNOTATION_DECORATORS`` 注册约定)。
95
+
96
+ ``comprehensive_aop.apply_annotations`` 调用形如 ``factory(annotation)(method)``,
97
+ 本工厂返回的装饰器会把方法包装为路由键作用域切面。
98
+ """
99
+ def decorator(method: Callable) -> Callable:
100
+ return ds_route_decorator(method, ds_annotation)
101
+ return decorator
102
+
103
+
104
+ def apply_ds_annotations(instance: Any) -> Any:
105
+ """为非受管实例上带 ``@DS``/``@Master``/``@Slave`` 的方法手动应用切面。
106
+
107
+ 受管 Bean 由 ``comprehensive_aop`` 经 ``ANNOTATION_DECORATORS`` 注册自动包装,
108
+ 无需调用本函数。对齐既有 ``apply_annotations`` 的使用模式。
109
+ """
110
+ cls = instance.__class__
111
+ for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
112
+ annotations = getattr(method, "__spring_annotations__", [])
113
+ ds_ann = next(
114
+ (a for a in annotations if isinstance(a, (DS, Master, Slave))),
115
+ None,
116
+ )
117
+ if ds_ann is None:
118
+ continue
119
+ # ds_route_decorator 内部已用 functools.wraps;这里直接绑定到实例
120
+ wrapped = ds_route_decorator(method, ds_ann)
121
+ setattr(instance, name, wrapped.__get__(instance))
122
+ return instance
123
+
124
+
125
+ def is_slave_placeholder(routing_key: Optional[str]) -> bool:
126
+ """供 ``DynamicRoutingDataSource`` 判断当前路由键是否为 ``@Slave`` 占位。"""
127
+ return routing_key == _SLAVE_PLACEHOLDER
128
+
129
+
130
+ __all__ = [
131
+ "DS", "Master", "Slave",
132
+ "ds_route_decorator", "apply_ds_annotations", "is_slave_placeholder",
133
+ ]
@@ -0,0 +1,69 @@
1
+ """多数据源路由上下文(对齐 Spring ``AbstractRoutingDataSource``)。
2
+
3
+ ``DataSourceContextHolder`` 用 ``ContextVar`` 保存当前线程/协程的路由键,
4
+ ``DynamicRoutingDataSource`` 在 ``get_connection`` 时读取该键决定走向哪个物理数据源。
5
+
6
+ 对齐 Spring:
7
+ - Spring 用 ``ThreadLocal<Object>``;Python 用 ``ContextVar`` 以兼容 ``asyncio`` 协程。
8
+ - ``@DS`` / ``@Master`` / ``@Slave`` 注解在方法执行期间设置路由键,退出后复位(对齐
9
+ ``AbstractRoutingDataSource.determineCurrentLookupKey`` + AOP 切面)。
10
+
11
+ 与 Java 的差异:
12
+ - Python ``ContextVar`` 的 ``set`` 返回 token,复位用 ``reset(token)``,天然支持嵌套调用
13
+ (内层方法退出后自动恢复外层路由键),比 Spring 的 ``ThreadLocal`` 手动 push/pop 更简洁。
14
+ """
15
+ from __future__ import annotations
16
+
17
+ from contextvars import ContextVar
18
+ from typing import Optional
19
+
20
+ # 当前路由键:None 表示使用默认数据源(master)
21
+ _routing_key: ContextVar[Optional[str]] = ContextVar(
22
+ "spring_datasource_routing_key", default=None
23
+ )
24
+
25
+
26
+ class DataSourceContextHolder:
27
+ """线程/协程安全的数据源路由键持有器(静态方法风格,对齐 Spring 同名类)。"""
28
+
29
+ @staticmethod
30
+ def get() -> Optional[str]:
31
+ """返回当前路由键;未设置返回 ``None``。"""
32
+ return _routing_key.get()
33
+
34
+ @staticmethod
35
+ def set(routing_key: Optional[str]):
36
+ """设置路由键,返回用于复位的 token。"""
37
+ return _routing_key.set(routing_key)
38
+
39
+ @staticmethod
40
+ def reset(token) -> None:
41
+ """用 ``set`` 返回的 token 复位路由键。"""
42
+ _routing_key.reset(token)
43
+
44
+ @staticmethod
45
+ def clear() -> None:
46
+ """强制清空路由键(慎用,会丢失嵌套层级)。"""
47
+ _routing_key.set(None)
48
+
49
+
50
+ class _RoutingKeyScope:
51
+ """``with`` 语法糖:进入设置路由键,退出复位。供 ``@DS`` AOP 与手写代码共用。"""
52
+
53
+ def __init__(self, routing_key: Optional[str]):
54
+ self._routing_key = routing_key
55
+ self._token = None
56
+
57
+ def __enter__(self):
58
+ self._token = DataSourceContextHolder.set(self._routing_key)
59
+ return self
60
+
61
+ def __exit__(self, exc_type, exc_val, exc_tb):
62
+ if self._token is not None:
63
+ DataSourceContextHolder.reset(self._token)
64
+ return False
65
+
66
+
67
+ def routing_scope(routing_key: Optional[str]) -> _RoutingKeyScope:
68
+ """便捷工厂:``with routing_scope("slave"): ...``。"""
69
+ return _RoutingKeyScope(routing_key)
@@ -0,0 +1,148 @@
1
+ """动态路由数据源(对齐 Spring ``AbstractRoutingDataSource``)。
2
+
3
+ ``DynamicRoutingDataSource`` 持有多个具名物理数据源(连接池),在 ``get_connection`` 时
4
+ 依据 ``DataSourceContextHolder`` 的路由键选择目标池,无路由键时走默认目标(master)。
5
+
6
+ 设计:
7
+ - **接口兼容**:实现 ``get_connection`` / ``return_connection`` / ``get_pool_stats``,
8
+ 可作为 ``SqlSessionFactory.connection_pool`` 的 drop-in 替换,无需改动既有 ORM 调用链。
9
+ - **从库负载均衡**:多个 slave 用轮询(round-robin)选择,对齐 Spring ``loadBalance`` 语义。
10
+ - **故障转移**:路由键指向的池不存在时回退到默认目标并记录告警,避免业务中断。
11
+ - **连接归还**:归还时按连接记录的来源池路由,确保借还一致。
12
+
13
+ 与 Java 的差异:
14
+ - Spring 的 ``AbstractRoutingDataSource`` 是 ``DataSource`` 接口实现;这里对齐项目既有
15
+ ``ConnectionPool`` 接口(``get_connection``/``return_connection``),不引入 JDBC 概念。
16
+ - 不实现 Spring 的 ``lenientFallback`` 配置项,统一回退到默认目标(更安全)。
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import itertools
21
+ import logging
22
+ import threading
23
+ from typing import Any, Dict, List, Optional
24
+
25
+ from .context import DataSourceContextHolder
26
+
27
+ logger = logging.getLogger("Spring.DataSource.Dynamic")
28
+
29
+
30
+ class DynamicRoutingDataSource:
31
+ """动态路由数据源:按路由键在多个物理连接池间路由。
32
+
33
+ Args:
34
+ target_data_sources: 具名物理池映射,如 ``{"slave_1": pool1, "slave_2": pool2}``。
35
+ default_target_data_source: 默认目标池(路由键为 None 或未命中时使用),通常为 master。
36
+ slave_keys: 标记为从库的路由键列表,用于 ``@Slave`` 注解做负载均衡轮询。
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ target_data_sources: Dict[str, Any],
42
+ default_target_data_source: Any,
43
+ slave_keys: Optional[List[str]] = None,
44
+ ):
45
+ if not isinstance(target_data_sources, dict) or not target_data_sources:
46
+ raise ValueError("target_data_sources 必须是非空映射")
47
+ if default_target_data_source is None:
48
+ raise ValueError("default_target_data_source 不能为空")
49
+ self._target_data_sources: Dict[str, Any] = dict(target_data_sources)
50
+ self._default_target = default_target_data_source
51
+ self._slave_keys: List[str] = list(slave_keys) if slave_keys else []
52
+ self._lock = threading.Lock()
53
+ # 轮询计数器:每个 slave_key 一个独立游标,分散热点
54
+ self._slave_iterators: Dict[str, itertools.cycle] = {
55
+ key: itertools.cycle(self._slave_keys) for key in self._slave_keys
56
+ } if self._slave_keys else {}
57
+ # 仅对 slave_keys 做轮询共享一个 cycle,简化为单一游标
58
+ self._slave_cycle = itertools.cycle(self._slave_keys) if self._slave_keys else None
59
+ # 侧表:连接不支持附加属性时(如 dict/原生对象)按 id 记录来源池,归还时清理。
60
+ self._connection_sources: Dict[int, Any] = {}
61
+
62
+ # ==================== 路由核心 ====================
63
+
64
+ def determine_target_data_source(self) -> Any:
65
+ """依据当前路由键选择目标池;未命中回退到默认目标。
66
+
67
+ ``@Slave`` 占位键触发从库轮询;显式具名键直接命中对应池。
68
+ """
69
+ from .annotations import is_slave_placeholder
70
+ routing_key = DataSourceContextHolder.get()
71
+ if routing_key is None:
72
+ return self._default_target
73
+ # @Slave 占位:走从库轮询
74
+ if is_slave_placeholder(routing_key):
75
+ return self.determine_slave_data_source()
76
+ pool = self._target_data_sources.get(routing_key)
77
+ if pool is None:
78
+ logger.warning(
79
+ "路由键 '%s' 未找到对应数据源,回退到默认目标", routing_key
80
+ )
81
+ return self._default_target
82
+ return pool
83
+
84
+ def determine_slave_data_source(self) -> Any:
85
+ """从 slave 池中轮询选择一个;无 slave 配置则回退到默认目标。"""
86
+ if self._slave_cycle is None:
87
+ return self._default_target
88
+ with self._lock:
89
+ slave_key = next(self._slave_cycle)
90
+ pool = self._target_data_sources.get(slave_key)
91
+ return pool if pool is not None else self._default_target
92
+
93
+ # ==================== 连接池接口(drop-in 替换) ====================
94
+
95
+ def get_connection(self) -> Any:
96
+ """获取连接:按路由键选择池,借用其 ``get_connection``。"""
97
+ pool = self.determine_target_data_source()
98
+ connection = pool.get_connection()
99
+ # 在连接上记录来源池,便于归还时路由;不支持属性设置时回退到侧表(按 id)
100
+ try:
101
+ connection.__spring_ds_source__ = pool
102
+ except (AttributeError, TypeError):
103
+ with self._lock:
104
+ self._connection_sources[id(connection)] = pool
105
+ return connection
106
+
107
+ def return_connection(self, pooled_conn: Any) -> None:
108
+ """归还连接:优先按连接上记录的来源池归还,未知则回到默认目标。"""
109
+ source_pool = getattr(pooled_conn, "__spring_ds_source__", None)
110
+ if source_pool is None:
111
+ with self._lock:
112
+ source_pool = self._connection_sources.pop(id(pooled_conn), None)
113
+ target = source_pool if source_pool is not None else self._default_target
114
+ try:
115
+ target.return_connection(pooled_conn)
116
+ finally:
117
+ try:
118
+ delattr(pooled_conn, "__spring_ds_source__")
119
+ except (AttributeError, TypeError):
120
+ pass
121
+
122
+ def get_pool_stats(self) -> Dict[str, Any]:
123
+ """返回各物理池的统计快照。"""
124
+ stats: Dict[str, Any] = {}
125
+ stats["__default__"] = self._safe_stats(self._default_target)
126
+ for key, pool in self._target_data_sources.items():
127
+ stats[key] = self._safe_stats(pool)
128
+ return stats
129
+
130
+ @staticmethod
131
+ def _safe_stats(pool: Any) -> Dict[str, Any]:
132
+ try:
133
+ result = pool.get_pool_stats()
134
+ return result if isinstance(result, dict) else {}
135
+ except Exception as exc: # pragma: no cover - 防御性
136
+ return {"error": str(exc)}
137
+
138
+ # ==================== 运维辅助 ====================
139
+
140
+ def get_target_data_sources(self) -> Dict[str, Any]:
141
+ """返回具名物理池映射(只读视图)。"""
142
+ return dict(self._target_data_sources)
143
+
144
+ def get_default_target_data_source(self) -> Any:
145
+ return self._default_target
146
+
147
+ def get_slave_keys(self) -> List[str]:
148
+ return list(self._slave_keys)
@@ -0,0 +1,7 @@
1
+ """Application event API."""
2
+
3
+ from spring.annotations.core import ApplicationEvent, EventListener
4
+ from .publisher import ApplicationEventPublisher
5
+
6
+ __all__ = ["ApplicationEvent", "EventListener", "ApplicationEventPublisher"]
7
+
@@ -0,0 +1,69 @@
1
+ """Synchronous application event publication for managed beans."""
2
+
3
+ import asyncio
4
+ import inspect
5
+ import threading
6
+ from typing import Any, Callable, List, Optional, Tuple, Type
7
+
8
+ from spring.annotations.core import ApplicationEvent
9
+
10
+
11
+ ListenerEntry = Tuple[Optional[Type[ApplicationEvent]], Callable, int, int]
12
+
13
+
14
+ class ApplicationEventPublisher:
15
+ """Publish events to listeners registered by the application context."""
16
+
17
+ def __init__(self):
18
+ self._listeners: List[ListenerEntry] = []
19
+ self._lock = threading.RLock()
20
+ self._sequence = 0
21
+
22
+ def add_listener(
23
+ self,
24
+ callback: Callable,
25
+ event_type: Optional[Type[ApplicationEvent]] = None,
26
+ order: int = 0,
27
+ ) -> None:
28
+ with self._lock:
29
+ self._sequence += 1
30
+ self._listeners.append((event_type, callback, order, self._sequence))
31
+ self._listeners.sort(key=lambda item: (item[2], item[3]))
32
+
33
+ def remove_listener(self, callback: Callable) -> None:
34
+ with self._lock:
35
+ self._listeners = [
36
+ entry for entry in self._listeners if entry[1] != callback
37
+ ]
38
+
39
+ def publish_event(self, event: Any) -> ApplicationEvent:
40
+ if not isinstance(event, ApplicationEvent):
41
+ event = ApplicationEvent(source=event)
42
+
43
+ with self._lock:
44
+ listeners = list(self._listeners)
45
+
46
+ for event_type, callback, _, _ in listeners:
47
+ if event_type is not None and not isinstance(event, event_type):
48
+ continue
49
+ result = callback(event)
50
+ if inspect.isawaitable(result):
51
+ self._finish_awaitable(result)
52
+ return event
53
+
54
+ @staticmethod
55
+ def _finish_awaitable(awaitable) -> None:
56
+ try:
57
+ loop = asyncio.get_running_loop()
58
+ except RuntimeError:
59
+ asyncio.run(awaitable)
60
+ else:
61
+ loop.create_task(awaitable)
62
+
63
+ def listener_count(self) -> int:
64
+ with self._lock:
65
+ return len(self._listeners)
66
+
67
+ def clear(self) -> None:
68
+ with self._lock:
69
+ self._listeners.clear()
@@ -0,0 +1,51 @@
1
+ """SpringBootAI Excel 模块 —— 注解驱动的 Excel 读写(对齐 alibaba EasyExcel)。
2
+
3
+ 模块组成:
4
+ - annotations: ``@ExcelProperty`` / ``@ExcelIgnore`` / ``@excel_sheet`` 字段+类级注解
5
+ (复用 ORM ``Column``/``@entity`` 元数据描述符范式)
6
+ - converters: ``Converter`` 接口 + 内置 int/float/bool/str/date/Decimal 转换器(按类型自动选择)
7
+ - reader: ``ExcelReader`` 读取引擎(表头映射/类型转换/多 sheet/head_row_number)
8
+ - writer: ``ExcelWriter`` 写入引擎(表头/顺序/样式/大数字防丢精度/多 sheet)
9
+ - easy_excel: ``EasyExcel`` 流式构建入口(对齐 alibaba EasyExcel API)
10
+ - style: 默认表头/内容样式
11
+ - exceptions: ``ExcelError`` 异常族
12
+
13
+ 安装(可选依赖)::
14
+
15
+ pip install springbootAI[excel] # 同时安装 openpyxl
16
+ pip install springbootAI[ai] # AI 模块
17
+ pip install springbootAI[full] # 全部可选依赖
18
+
19
+ 注解声明无需 openpyxl;仅 read/write 时检测,未安装抛 ``ExcelDependencyError`` 提示安装。
20
+ """
21
+ from .exceptions import (
22
+ ExcelError, ExcelPropertyError, ExcelReadError, ExcelWriteError, ExcelDependencyError,
23
+ )
24
+ from .annotations import (
25
+ ExcelProperty, ExcelIgnore, ExcelSheet, excel_sheet,
26
+ ExcelColumnModel, parse_excel_columns,
27
+ )
28
+ from .converters import (
29
+ Converter, StringConverter, IntegerConverter, FloatConverter,
30
+ BooleanConverter, DateStringConverter, BigDecimalConverter, resolve_converter,
31
+ )
32
+ from .reader import ExcelReader
33
+ from .writer import ExcelWriter
34
+ from .easy_excel import EasyExcel, read_excel, write_excel
35
+
36
+ __version__ = "1.0.0"
37
+
38
+ __all__ = [
39
+ # 异常
40
+ "ExcelError", "ExcelPropertyError", "ExcelReadError", "ExcelWriteError",
41
+ "ExcelDependencyError",
42
+ # 注解
43
+ "ExcelProperty", "ExcelIgnore", "ExcelSheet", "excel_sheet",
44
+ "ExcelColumnModel", "parse_excel_columns",
45
+ # 转换器
46
+ "Converter", "StringConverter", "IntegerConverter", "FloatConverter",
47
+ "BooleanConverter", "DateStringConverter", "BigDecimalConverter", "resolve_converter",
48
+ # 引擎
49
+ "ExcelReader", "ExcelWriter", "EasyExcel", "read_excel", "write_excel",
50
+ "__version__",
51
+ ]