springbootAI 2.2.0__tar.gz → 2.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. {springbootai-2.2.0 → springbootai-2.2.2}/CHANGELOG.md +24 -0
  2. {springbootai-2.2.0/springbootAI.egg-info → springbootai-2.2.2}/PKG-INFO +6 -6
  3. {springbootai-2.2.0 → springbootai-2.2.2}/README.md +5 -5
  4. {springbootai-2.2.0 → springbootai-2.2.2}/pyproject.toml +1 -1
  5. {springbootai-2.2.0 → springbootai-2.2.2}/spring/__init__.py +1 -1
  6. {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/__init__.py +1 -1
  7. {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/__init__.py +1 -1
  8. {springbootai-2.2.0 → springbootai-2.2.2}/spring/data/__init__.py +1 -1
  9. {springbootai-2.2.0 → springbootai-2.2.2}/spring/datasource/__init__.py +1 -1
  10. {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/__init__.py +1 -1
  11. {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/__init__.py +1 -1
  12. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/__init__.py +1 -1
  13. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/__init__.py +2 -0
  14. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/ddl_auto.py +147 -36
  15. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/mybatis_integration.py +2 -2
  16. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/__init__.py +1 -1
  17. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/security/sensitive_data_masker.py +19 -2
  18. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/security/sql_injection_detector.py +40 -1
  19. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/version.py +1 -1
  20. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/xml_parser/xml_parser.py +13 -0
  21. {springbootai-2.2.0 → springbootai-2.2.2}/spring/test/__init__.py +1 -1
  22. {springbootai-2.2.0 → springbootai-2.2.2}/spring/tx/__init__.py +1 -1
  23. {springbootai-2.2.0 → springbootai-2.2.2}/spring/utils/banner.py +1 -1
  24. {springbootai-2.2.0 → springbootai-2.2.2}/spring/validation/__init__.py +1 -1
  25. {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/swagger.py +2 -2
  26. {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/web_context.py +6 -3
  27. {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/__init__.py +1 -1
  28. {springbootai-2.2.0 → springbootai-2.2.2/springbootAI.egg-info}/PKG-INFO +6 -6
  29. {springbootai-2.2.0 → springbootai-2.2.2}/springbootAI.egg-info/SOURCES.txt +2 -0
  30. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_ddl_auto.py +82 -0
  31. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_security.py +44 -0
  32. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_security_hardening.py +14 -0
  33. springbootai-2.2.2/tests/test_web_root_mapping.py +22 -0
  34. springbootai-2.2.2/tests/test_web_static_files.py +22 -0
  35. {springbootai-2.2.0 → springbootai-2.2.2}/LICENSE +0 -0
  36. {springbootai-2.2.0 → springbootai-2.2.2}/MANIFEST.in +0 -0
  37. {springbootai-2.2.0 → springbootai-2.2.2}/SECURITY.md +0 -0
  38. {springbootai-2.2.0 → springbootai-2.2.2}/requirements-ai.txt +0 -0
  39. {springbootai-2.2.0 → springbootai-2.2.2}/requirements-langgraph.txt +0 -0
  40. {springbootai-2.2.0 → springbootai-2.2.2}/requirements-mcp.txt +0 -0
  41. {springbootai-2.2.0 → springbootai-2.2.2}/requirements.txt +0 -0
  42. {springbootai-2.2.0 → springbootai-2.2.2}/setup.cfg +0 -0
  43. {springbootai-2.2.0 → springbootai-2.2.2}/setup.py +0 -0
  44. {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/advisors.py +0 -0
  45. {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/annotations.py +0 -0
  46. {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/autoconfig.py +0 -0
  47. {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/core.py +0 -0
  48. {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/etl.py +0 -0
  49. {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/memory.py +0 -0
  50. {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/observability.py +0 -0
  51. {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/providers.py +0 -0
  52. {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/resilience.py +0 -0
  53. {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/tools.py +0 -0
  54. {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/vectorstore.py +0 -0
  55. {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/__init__.py +0 -0
  56. {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/cache.py +0 -0
  57. {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/cloud.py +0 -0
  58. {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/conditional.py +0 -0
  59. {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/core.py +0 -0
  60. {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/langchain.py +0 -0
  61. {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/langgraph.py +0 -0
  62. {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/messaging.py +0 -0
  63. {springbootai-2.2.0 → springbootai-2.2.2}/spring/aop/__init__.py +0 -0
  64. {springbootai-2.2.0 → springbootai-2.2.2}/spring/aop/aspect.py +0 -0
  65. {springbootai-2.2.0 → springbootai-2.2.2}/spring/aop/cloud_aop.py +0 -0
  66. {springbootai-2.2.0 → springbootai-2.2.2}/spring/aop/comprehensive_aop.py +0 -0
  67. {springbootai-2.2.0 → springbootai-2.2.2}/spring/aop/method_interceptor.py +0 -0
  68. {springbootai-2.2.0 → springbootai-2.2.2}/spring/aop/proxy_factory.py +0 -0
  69. {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/__init__.py +0 -0
  70. {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/discovery.py +0 -0
  71. {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/feign.py +0 -0
  72. {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/gateway.py +0 -0
  73. {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/load_balancer.py +0 -0
  74. {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/seata.py +0 -0
  75. {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/seata_bridge.py +0 -0
  76. {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/sentinel.py +0 -0
  77. {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/tracer.py +0 -0
  78. {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/transaction_store.py +0 -0
  79. {springbootai-2.2.0 → springbootai-2.2.2}/spring/config/__init__.py +0 -0
  80. {springbootai-2.2.0 → springbootai-2.2.2}/spring/config/binding.py +0 -0
  81. {springbootai-2.2.0 → springbootai-2.2.2}/spring/config/config_loader.py +0 -0
  82. {springbootai-2.2.0 → springbootai-2.2.2}/spring/context/__init__.py +0 -0
  83. {springbootai-2.2.0 → springbootai-2.2.2}/spring/context/application_context.py +0 -0
  84. {springbootai-2.2.0 → springbootai-2.2.2}/spring/context/bean_definition.py +0 -0
  85. {springbootai-2.2.0 → springbootai-2.2.2}/spring/context/bean_factory.py +0 -0
  86. {springbootai-2.2.0 → springbootai-2.2.2}/spring/context/registry.py +0 -0
  87. {springbootai-2.2.0 → springbootai-2.2.2}/spring/context/scanner.py +0 -0
  88. {springbootai-2.2.0 → springbootai-2.2.2}/spring/core/__init__.py +0 -0
  89. {springbootai-2.2.0 → springbootai-2.2.2}/spring/core/graceful_shutdown.py +0 -0
  90. {springbootai-2.2.0 → springbootai-2.2.2}/spring/core/typing_utils.py +0 -0
  91. {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/annotations.py +0 -0
  92. {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/converters.py +0 -0
  93. {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/easy_csv.py +0 -0
  94. {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/exceptions.py +0 -0
  95. {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/reader.py +0 -0
  96. {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/writer.py +0 -0
  97. {springbootai-2.2.0 → springbootai-2.2.2}/spring/data/page.py +0 -0
  98. {springbootai-2.2.0 → springbootai-2.2.2}/spring/data/repository.py +0 -0
  99. {springbootai-2.2.0 → springbootai-2.2.2}/spring/data/specification.py +0 -0
  100. {springbootai-2.2.0 → springbootai-2.2.2}/spring/datasource/annotations.py +0 -0
  101. {springbootai-2.2.0 → springbootai-2.2.2}/spring/datasource/context.py +0 -0
  102. {springbootai-2.2.0 → springbootai-2.2.2}/spring/datasource/dynamic.py +0 -0
  103. {springbootai-2.2.0 → springbootai-2.2.2}/spring/event/__init__.py +0 -0
  104. {springbootai-2.2.0 → springbootai-2.2.2}/spring/event/publisher.py +0 -0
  105. {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/annotations.py +0 -0
  106. {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/converters.py +0 -0
  107. {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/easy_excel.py +0 -0
  108. {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/exceptions.py +0 -0
  109. {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/reader.py +0 -0
  110. {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/style.py +0 -0
  111. {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/writer.py +0 -0
  112. {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/accessor.py +0 -0
  113. {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/auto_config.py +0 -0
  114. {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/holder.py +0 -0
  115. {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/locale.py +0 -0
  116. {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/locale_resolver.py +0 -0
  117. {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/message_source.py +0 -0
  118. {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/middleware.py +0 -0
  119. {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/properties.py +0 -0
  120. {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/sources.py +0 -0
  121. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/adapters.py +0 -0
  122. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/agents/__init__.py +0 -0
  123. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/agents/services.py +0 -0
  124. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/annotations.py +0 -0
  125. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/autoconfig.py +0 -0
  126. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/callbacks/__init__.py +0 -0
  127. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/callbacks/handlers.py +0 -0
  128. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/chains/__init__.py +0 -0
  129. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/chains/services.py +0 -0
  130. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/core.py +0 -0
  131. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/indexes/__init__.py +0 -0
  132. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/indexes/index.py +0 -0
  133. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/loaders/__init__.py +0 -0
  134. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/loaders/loaders.py +0 -0
  135. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/memory/__init__.py +0 -0
  136. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/memory/memory.py +0 -0
  137. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/parsers/__init__.py +0 -0
  138. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/parsers/parsers.py +0 -0
  139. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/partners.py +0 -0
  140. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/prompts/__init__.py +0 -0
  141. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/prompts/templates.py +0 -0
  142. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/retrievers/__init__.py +0 -0
  143. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/retrievers/retrievers.py +0 -0
  144. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/tools/__init__.py +0 -0
  145. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/tools/tools.py +0 -0
  146. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/utilities/__init__.py +0 -0
  147. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/utilities/utils.py +0 -0
  148. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/vectorstores/__init__.py +0 -0
  149. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/vectorstores/stores.py +0 -0
  150. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/__init__.py +0 -0
  151. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/annotation_runtime.py +0 -0
  152. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/annotations.py +0 -0
  153. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/autoconfig.py +0 -0
  154. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/checkpoint.py +0 -0
  155. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/config.py +0 -0
  156. {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/runtime.py +0 -0
  157. {springbootai-2.2.0 → springbootai-2.2.2}/spring/logging/__init__.py +0 -0
  158. {springbootai-2.2.0 → springbootai-2.2.2}/spring/logging/loguru_logger.py +0 -0
  159. {springbootai-2.2.0 → springbootai-2.2.2}/spring/main.py +0 -0
  160. {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/__init__.py +0 -0
  161. {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/annotation_runtime.py +0 -0
  162. {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/annotations.py +0 -0
  163. {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/autoconfig.py +0 -0
  164. {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/client.py +0 -0
  165. {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/config.py +0 -0
  166. {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/server.py +0 -0
  167. {springbootai-2.2.0 → springbootai-2.2.2}/spring/messaging/__init__.py +0 -0
  168. {springbootai-2.2.0 → springbootai-2.2.2}/spring/messaging/rabbitmq.py +0 -0
  169. {springbootai-2.2.0 → springbootai-2.2.2}/spring/monitoring/__init__.py +0 -0
  170. {springbootai-2.2.0 → springbootai-2.2.2}/spring/monitoring/prometheus.py +0 -0
  171. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/database.py +0 -0
  172. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/migration.py +0 -0
  173. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/annotations/__init__.py +0 -0
  174. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/annotations/annotations.py +0 -0
  175. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/cache/__init__.py +0 -0
  176. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/cache/cache.py +0 -0
  177. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/cache/redis_cache.py +0 -0
  178. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/circuit_breaker/__init__.py +0 -0
  179. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +0 -0
  180. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/configuration.py +0 -0
  181. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/core/__init__.py +0 -0
  182. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/core/sql_session.py +0 -0
  183. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/core/sql_session_factory.py +0 -0
  184. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/dialect/__init__.py +0 -0
  185. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/dialect/dialect.py +0 -0
  186. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/dynamic_sql/__init__.py +0 -0
  187. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +0 -0
  188. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/interceptor/__init__.py +0 -0
  189. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/interceptor/interceptor.py +0 -0
  190. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/mapper/__init__.py +0 -0
  191. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/mapper/mapper.py +0 -0
  192. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/metrics/__init__.py +0 -0
  193. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/metrics/metrics.py +0 -0
  194. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/pool/__init__.py +0 -0
  195. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/pool/connection_pool.py +0 -0
  196. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/security/__init__.py +0 -0
  197. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/security/access_control.py +0 -0
  198. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/security/password_encoder.py +0 -0
  199. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/transaction/__init__.py +0 -0
  200. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/transaction/transaction.py +0 -0
  201. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/type_handler/__init__.py +0 -0
  202. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/type_handler/type_handler.py +0 -0
  203. {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/xml_parser/__init__.py +0 -0
  204. {springbootai-2.2.0 → springbootai-2.2.2}/spring/retry/__init__.py +0 -0
  205. {springbootai-2.2.0 → springbootai-2.2.2}/spring/retry/recovery.py +0 -0
  206. {springbootai-2.2.0 → springbootai-2.2.2}/spring/retry/retry_annotations.py +0 -0
  207. {springbootai-2.2.0 → springbootai-2.2.2}/spring/retry/retry_decorator.py +0 -0
  208. {springbootai-2.2.0 → springbootai-2.2.2}/spring/scheduling/__init__.py +0 -0
  209. {springbootai-2.2.0 → springbootai-2.2.2}/spring/scheduling/scheduler.py +0 -0
  210. {springbootai-2.2.0 → springbootai-2.2.2}/spring/security/__init__.py +0 -0
  211. {springbootai-2.2.0 → springbootai-2.2.2}/spring/security/jwt_utils.py +0 -0
  212. {springbootai-2.2.0 → springbootai-2.2.2}/spring/security/replay_protection.py +0 -0
  213. {springbootai-2.2.0 → springbootai-2.2.2}/spring/security/secret_manager.py +0 -0
  214. {springbootai-2.2.0 → springbootai-2.2.2}/spring/security/security_aop.py +0 -0
  215. {springbootai-2.2.0 → springbootai-2.2.2}/spring/security/security_context.py +0 -0
  216. {springbootai-2.2.0 → springbootai-2.2.2}/spring/test/slicing.py +0 -0
  217. {springbootai-2.2.0 → springbootai-2.2.2}/spring/tracing/__init__.py +0 -0
  218. {springbootai-2.2.0 → springbootai-2.2.2}/spring/tracing/skywalking.py +0 -0
  219. {springbootai-2.2.0 → springbootai-2.2.2}/spring/tx/events.py +0 -0
  220. {springbootai-2.2.0 → springbootai-2.2.2}/spring/tx/synchronization.py +0 -0
  221. {springbootai-2.2.0 → springbootai-2.2.2}/spring/utils/__init__.py +0 -0
  222. {springbootai-2.2.0 → springbootai-2.2.2}/spring/utils/bean_utils.py +0 -0
  223. {springbootai-2.2.0 → springbootai-2.2.2}/spring/utils/logger.py +0 -0
  224. {springbootai-2.2.0 → springbootai-2.2.2}/spring/utils/redis_client.py +0 -0
  225. {springbootai-2.2.0 → springbootai-2.2.2}/spring/validation/aop.py +0 -0
  226. {springbootai-2.2.0 → springbootai-2.2.2}/spring/validation/constraints.py +0 -0
  227. {springbootai-2.2.0 → springbootai-2.2.2}/spring/validation/exceptions.py +0 -0
  228. {springbootai-2.2.0 → springbootai-2.2.2}/spring/validation/validator.py +0 -0
  229. {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/__init__.py +0 -0
  230. {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/actuator.py +0 -0
  231. {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/exception_handler.py +0 -0
  232. {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/health.py +0 -0
  233. {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/interceptor.py +0 -0
  234. {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/result.py +0 -0
  235. {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/annotations.py +0 -0
  236. {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/broker.py +0 -0
  237. {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/exceptions.py +0 -0
  238. {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/handler.py +0 -0
  239. {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/router.py +0 -0
  240. {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/session.py +0 -0
  241. {springbootai-2.2.0 → springbootai-2.2.2}/springbootAI.egg-info/dependency_links.txt +0 -0
  242. {springbootai-2.2.0 → springbootai-2.2.2}/springbootAI.egg-info/entry_points.txt +0 -0
  243. {springbootai-2.2.0 → springbootai-2.2.2}/springbootAI.egg-info/requires.txt +0 -0
  244. {springbootai-2.2.0 → springbootai-2.2.2}/springbootAI.egg-info/top_level.txt +0 -0
  245. {springbootai-2.2.0 → springbootai-2.2.2}/sql/seata_undo_log.sql +0 -0
  246. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_actuator.py +0 -0
  247. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_ai_comprehensive.py +0 -0
  248. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_ai_declarative_annotations.py +0 -0
  249. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_ai_module.py +0 -0
  250. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_annotation_combinations.py +0 -0
  251. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_annotations_contract.py +0 -0
  252. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_aop_annotations_full.py +0 -0
  253. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_audit_time.py +0 -0
  254. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_bean_utils.py +0 -0
  255. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_cache_annotations.py +0 -0
  256. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_cloud_embedded_full.py +0 -0
  257. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_component_init_error.py +0 -0
  258. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_conditional_annotations.py +0 -0
  259. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_config_binding.py +0 -0
  260. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_config_fixes_185.py +0 -0
  261. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_connection_resilience.py +0 -0
  262. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_core_annotations_full.py +0 -0
  263. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_csv_module.py +0 -0
  264. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_data_repository.py +0 -0
  265. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_datasource_routing.py +0 -0
  266. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_declarative_aop_post_authorize_recover.py +0 -0
  267. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_di_config_event_full.py +0 -0
  268. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_excel_module.py +0 -0
  269. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_i18n_module.py +0 -0
  270. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_jpa_version_transient.py +0 -0
  271. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_langchain_comprehensive.py +0 -0
  272. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_langchain_ext.py +0 -0
  273. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_langchain_module.py +0 -0
  274. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_langgraph_comprehensive.py +0 -0
  275. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_langgraph_module.py +0 -0
  276. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_logging_config.py +0 -0
  277. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_mcp_module.py +0 -0
  278. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_new_features.py +0 -0
  279. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_orm_pymybatis_full.py +0 -0
  280. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_production_readiness.py +0 -0
  281. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_pymybatis_contract.py +0 -0
  282. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_seata_bridge.py +0 -0
  283. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_seata_durable_store.py +0 -0
  284. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_security_full.py +0 -0
  285. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_swagger_module.py +0 -0
  286. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_test_slicing.py +0 -0
  287. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_transactional_events.py +0 -0
  288. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_validation_module.py +0 -0
  289. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_web_annotations_full.py +0 -0
  290. {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_websocket_module.py +0 -0
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.2.2] - 2026-08-13
4
+
5
+ ### 新增
6
+
7
+ - ORM `@Entity` / `@Table` 注解对齐 Java JPA 分离风格,支持三种写法(均向后兼容):
8
+
9
+ | 写法 | 说明 | 对齐 Java |
10
+ |------|------|-----------|
11
+ | `@Entity` + `@Table(...)` | 分离风格(推荐) | `@Entity` + `@Table` |
12
+ | `@Entity` 无括号 | 表名自动推导为 snake_case | `@Entity`(无 `@Table`) |
13
+ | `@Entity("name", ...)` | 一体化风格(完全兼容) | 原有写法不变 |
14
+
15
+ - `Table` 类新增 `__call__`,可直接作为类装饰器使用(`@Table(name=..., indexes=[...], comment=...)`)。
16
+ - `@Entity` 支持无括号形式(`@Entity`),检测到已有 `@Table` 时不覆盖表元数据。
17
+ - 提取 `_auto_generate_init` 辅助函数,消除 `Entity` 与 `Table.__call__` 间的重复代码。
18
+
19
+ ## [2.2.1] - 2026-08-13
20
+
21
+ ### 修复
22
+
23
+ - 修复 ORM `@Entity`/`@entity` 实体解析、DDL 字段推断和 MyBatis 连接池初始化。
24
+ - 加强 SQL 注入检测、敏感数据脱敏、Mapper XML 解析和 Web 根路由映射。
25
+ - 增加对应回归测试,确保实体描述符、容器参数和静态资源路由行为稳定。
26
+
3
27
  本项目从 `2.1.0` 开始按 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 记录面向使用者的变化,并使用语义化版本号。
4
28
 
5
29
  ## [2.2.0] - 2026-08-12
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: springbootAI
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: Spring-style Python framework with Web, ORM, Cloud and AI integrations
5
5
  Author-email: YuConggen <1516933915@qq.com>
6
6
  License-Expression: MIT
@@ -151,7 +151,7 @@ SpringBootAI 是一个采用 Spring 风格注解和分层结构的 Python 应用
151
151
  | AI 与编排 | 模型调用、Tools、RAG、Chain、状态图、MCP client/server | LangChain / LangGraph / 官方 MCP SDK |
152
152
  | 生产治理 | 健康检查、Prometheus、限流熔断、追踪、Swagger | prometheus-client / OpenTelemetry / OpenAPI |
153
153
 
154
- 当前版本是 `2.2.0`;支持 Python 3.10、3.11 和 3.12,许可证为 MIT。项目仍标记为 Beta。用于公网高并发、合规敏感或支付/订单/库存等核心系统前,必须完成目标数据库、流量模型、故障恢复和安全基线验证。内嵌 Gateway 适合内部路由,不替代公网 Nginx/Kong/WAF;Seata `distributed` 当前验证的是官方 TC + TCC 回调,不会为 Python 数据库操作自动生成 AT `undo_log`。
154
+ 当前版本是 `2.2.1`;支持 Python 3.10、3.11 和 3.12,许可证为 MIT。项目仍标记为 Beta。用于公网高并发、合规敏感或支付/订单/库存等核心系统前,必须完成目标数据库、流量模型、故障恢复和安全基线验证。内嵌 Gateway 适合内部路由,不替代公网 Nginx/Kong/WAF;Seata `distributed` 当前验证的是官方 TC + TCC 回调,不会为 Python 数据库操作自动生成 AT `undo_log`。
155
155
 
156
156
  [新手指南](https://github.com/YUCONGGEN/springbootAI/blob/master/doc/BEGINNER_GUIDE.md) | [全部文档](https://github.com/YUCONGGEN/springbootAI/tree/master/doc) | [变更日志](https://github.com/YUCONGGEN/springbootAI/blob/master/CHANGELOG.md) | [安全报告](https://github.com/YUCONGGEN/springbootAI/blob/master/SECURITY.md) | [发布检查](https://github.com/YUCONGGEN/springbootAI/blob/master/doc/RELEASE_CHECKLIST.md)
157
157
 
@@ -315,10 +315,10 @@ SpringBootAI 是一个 **Python Web 框架**。它把 Java Spring Boot 的"注
315
315
 
316
316
  | 组件 | 当前版本 |
317
317
  |------|----------|
318
- | `spring` 框架 API | 2.2.0 |
319
- | `spring.orm.pymybatis` | 2.2.0 |
320
- | `spring.ai` AI 模块 | 2.2.0 |
321
- | `spring.langchain` LangChain 模块 | 2.2.0 |
318
+ | `spring` 框架 API | 2.2.1 |
319
+ | `spring.orm.pymybatis` | 2.2.1 |
320
+ | `spring.ai` AI 模块 | 2.2.1 |
321
+ | `spring.langchain` LangChain 模块 | 2.2.1 |
322
322
  | Python | 3.10+ |
323
323
 
324
324
  ### 1.4 适合什么场景
@@ -17,7 +17,7 @@ SpringBootAI 是一个采用 Spring 风格注解和分层结构的 Python 应用
17
17
  | AI 与编排 | 模型调用、Tools、RAG、Chain、状态图、MCP client/server | LangChain / LangGraph / 官方 MCP SDK |
18
18
  | 生产治理 | 健康检查、Prometheus、限流熔断、追踪、Swagger | prometheus-client / OpenTelemetry / OpenAPI |
19
19
 
20
- 当前版本是 `2.2.0`;支持 Python 3.10、3.11 和 3.12,许可证为 MIT。项目仍标记为 Beta。用于公网高并发、合规敏感或支付/订单/库存等核心系统前,必须完成目标数据库、流量模型、故障恢复和安全基线验证。内嵌 Gateway 适合内部路由,不替代公网 Nginx/Kong/WAF;Seata `distributed` 当前验证的是官方 TC + TCC 回调,不会为 Python 数据库操作自动生成 AT `undo_log`。
20
+ 当前版本是 `2.2.1`;支持 Python 3.10、3.11 和 3.12,许可证为 MIT。项目仍标记为 Beta。用于公网高并发、合规敏感或支付/订单/库存等核心系统前,必须完成目标数据库、流量模型、故障恢复和安全基线验证。内嵌 Gateway 适合内部路由,不替代公网 Nginx/Kong/WAF;Seata `distributed` 当前验证的是官方 TC + TCC 回调,不会为 Python 数据库操作自动生成 AT `undo_log`。
21
21
 
22
22
  [新手指南](https://github.com/YUCONGGEN/springbootAI/blob/master/doc/BEGINNER_GUIDE.md) | [全部文档](https://github.com/YUCONGGEN/springbootAI/tree/master/doc) | [变更日志](https://github.com/YUCONGGEN/springbootAI/blob/master/CHANGELOG.md) | [安全报告](https://github.com/YUCONGGEN/springbootAI/blob/master/SECURITY.md) | [发布检查](https://github.com/YUCONGGEN/springbootAI/blob/master/doc/RELEASE_CHECKLIST.md)
23
23
 
@@ -181,10 +181,10 @@ SpringBootAI 是一个 **Python Web 框架**。它把 Java Spring Boot 的"注
181
181
 
182
182
  | 组件 | 当前版本 |
183
183
  |------|----------|
184
- | `spring` 框架 API | 2.2.0 |
185
- | `spring.orm.pymybatis` | 2.2.0 |
186
- | `spring.ai` AI 模块 | 2.2.0 |
187
- | `spring.langchain` LangChain 模块 | 2.2.0 |
184
+ | `spring` 框架 API | 2.2.1 |
185
+ | `spring.orm.pymybatis` | 2.2.1 |
186
+ | `spring.ai` AI 模块 | 2.2.1 |
187
+ | `spring.langchain` LangChain 模块 | 2.2.1 |
188
188
  | Python | 3.10+ |
189
189
 
190
190
  ### 1.4 适合什么场景
@@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
12
12
  [project]
13
13
  # 发行名 springbootAI(PyPI 包名,pip install springbootAI);导入包名仍为 spring
14
14
  name = "springbootAI"
15
- version = "2.2.0"
15
+ version = "2.2.2"
16
16
  description = "Spring-style Python framework with Web, ORM, Cloud and AI integrations"
17
17
  readme = "README.md"
18
18
  license = "MIT"
@@ -5,7 +5,7 @@ from .config import *
5
5
  from .utils import *
6
6
  from .main import create_app, run, SpringApplication, run_cli
7
7
 
8
- __version__ = "2.2.0"
8
+ __version__ = "2.2.1"
9
9
  __author__ = "yuconggen"
10
10
  __license__ = "MIT"
11
11
 
@@ -47,7 +47,7 @@ from spring.ai.resilience import (
47
47
  from spring.ai.observability import AIMetrics, ai_metrics
48
48
  from spring.ai.autoconfig import AIProperties, bind_ai_config, configure_ai
49
49
 
50
- __version__ = "2.2.0"
50
+ __version__ = "2.2.1"
51
51
 
52
52
  __all__ = [
53
53
  # core
@@ -33,7 +33,7 @@ from .reader import CsvReader
33
33
  from .writer import CsvWriter
34
34
  from .easy_csv import EasyCsv, read_csv, write_csv
35
35
 
36
- __version__ = "2.2.0"
36
+ __version__ = "2.2.1"
37
37
 
38
38
  __all__ = [
39
39
  # 异常
@@ -39,7 +39,7 @@ from spring.data.repository import (
39
39
  get_data_repository_entity,
40
40
  )
41
41
 
42
- __version__ = "2.2.0"
42
+ __version__ = "2.2.1"
43
43
 
44
44
  __all__ = [
45
45
  # 分页/排序
@@ -43,7 +43,7 @@ from .annotations import (
43
43
  ds_route_decorator, ds_decorator_factory, apply_ds_annotations, is_slave_placeholder,
44
44
  )
45
45
 
46
- __version__ = "2.2.0"
46
+ __version__ = "2.2.1"
47
47
 
48
48
  __all__ = [
49
49
  "DataSourceContextHolder",
@@ -33,7 +33,7 @@ from .reader import ExcelReader
33
33
  from .writer import ExcelWriter
34
34
  from .easy_excel import EasyExcel, read_excel, write_excel
35
35
 
36
- __version__ = "2.2.0"
36
+ __version__ = "2.2.1"
37
37
 
38
38
  __all__ = [
39
39
  # 异常
@@ -65,7 +65,7 @@ from .properties import load_properties, parse_properties
65
65
  from .middleware import LocaleResolverMiddleware, get_request_locale
66
66
  from .auto_config import MessageSourceAutoConfiguration, configure_message_source
67
67
 
68
- __version__ = "2.2.0"
68
+ __version__ = "2.2.1"
69
69
 
70
70
  __all__ = [
71
71
  # Locale
@@ -90,7 +90,7 @@ from spring.langchain.annotations import (
90
90
  bind_langchain_client,
91
91
  )
92
92
 
93
- __version__ = "2.2.0"
93
+ __version__ = "2.2.1"
94
94
 
95
95
  __all__ = [
96
96
  # core
@@ -146,6 +146,7 @@ from spring.orm.ddl_auto import (
146
146
  UpdateTime,
147
147
  Transient,
148
148
  Index,
149
+ Entity,
149
150
  entity,
150
151
  table as table_decorator,
151
152
  column as column_decorator,
@@ -247,6 +248,7 @@ __all__ = [
247
248
  'UpdateTime',
248
249
  'Transient',
249
250
  'Index',
251
+ 'Entity',
250
252
  'entity',
251
253
  'table_decorator',
252
254
  'column_decorator',
@@ -244,12 +244,47 @@ class Transient:
244
244
 
245
245
 
246
246
  class Table:
247
- """表注解"""
247
+ """``@Table`` 表注解(对齐 JPA ``javax.persistence.Table``)。
248
+
249
+ 既可作为元数据类使用(内部 ``EntityTable`` 解析),也可直接作为类装饰器使用。
250
+ 作为装饰器时等价于 JPA 的 ``@Table``,与 ``@Entity`` 组合使用::
251
+
252
+ @Entity
253
+ @Table(name="sys_user", indexes=[Index("idx_name", ["name"])], comment="用户表")
254
+ class User:
255
+ id: int = Id()
256
+ name: str = Column(length=50)
257
+
258
+ 也可单独使用(隐含 ``@Entity`` 语义,与 ``@Entity("sys_user")`` 等价)::
259
+
260
+ @Table(name="sys_user", comment="用户表")
261
+ class User:
262
+ id: int = Id()
263
+ """
248
264
  def __init__(self, name: str = "", indexes: List['Index'] = None, comment: str = ""):
249
265
  self.name = name
250
266
  self.indexes = indexes or []
251
267
  self.comment = comment
252
268
 
269
+ def __call__(self, cls):
270
+ """装饰器形式:将表元数据挂载到类上并标记为实体,返回类本身。
271
+
272
+ 装饰器应用顺序(Python 底向上)::
273
+
274
+ @Entity # 外层,后执行
275
+ @Table(...) # 内层,先执行
276
+ class User: ...
277
+
278
+ ``@Table`` 先设置 ``__table__`` 与 ``__entity__``,随后 ``@Entity``
279
+ 检测到已有 ``__table__`` 则不覆盖。
280
+ """
281
+ if not self.name:
282
+ self.name = _camel_to_snake(cls.__name__)
283
+ setattr(cls, '__table__', self)
284
+ setattr(cls, '__entity__', True)
285
+ _auto_generate_init(cls)
286
+ return cls
287
+
253
288
 
254
289
  class Index:
255
290
  """索引定义"""
@@ -480,37 +515,53 @@ class DdlAutoManager:
480
515
  col_meta = self._get_field_meta(df)
481
516
  columns.append(col_meta)
482
517
  else:
483
- # 处理普通类:从 __init__ 方法中提取 self.xxx 字段
518
+ # 普通实体同时支持显式 self.xxx 赋值、类型注解和类级 Column 描述符。
519
+ # 只依赖源码中的 self.xxx 会漏掉循环赋值或动态赋值的实体。
484
520
  init_fields = self._extract_init_fields(cls)
485
- # 同时检查类级别的类型注解(__init__ 的参数注解)
486
521
  init_hints = {}
487
522
  try:
488
523
  init_hints = get_type_hints(cls.__init__)
489
524
  except Exception:
490
525
  pass
491
- cls_annotations = getattr(cls, '__annotations__', {})
492
- for attr_name, default_val in init_fields.items():
526
+ try:
527
+ cls_annotations = get_type_hints(cls)
528
+ except Exception:
529
+ cls_annotations = getattr(cls, '__annotations__', {})
530
+
531
+ descriptor_fields = {}
532
+ for cls_base in reversed(cls.__mro__):
533
+ for attr_name, value in cls_base.__dict__.items():
534
+ if isinstance(value, (Column, Transient)) or hasattr(value, '__column__'):
535
+ descriptor_fields[attr_name] = value
536
+
537
+ field_names = dict(init_fields)
538
+ for attr_name in cls_annotations:
539
+ field_names.setdefault(attr_name, None)
540
+ for attr_name in descriptor_fields:
541
+ field_names.setdefault(attr_name, None)
542
+
543
+ for attr_name, default_val in field_names.items():
493
544
  # @Transient 字段不持久化,跳过
494
545
  if _is_transient_field(cls, attr_name):
495
546
  continue
496
- # 优先取 __init__ 注解,其次取类注解
497
547
  py_type = init_hints.get(attr_name) or cls_annotations.get(attr_name)
498
548
  # 解包 Optional[X]:Python 3.10 的 get_type_hints 会把带 None 默认值的
499
549
  # 参数注解自动包装为 Optional[X],3.11+ 不再包装。此处统一解包为承载类型,
500
550
  # 否则 _get_sql_type 在 3.10 上识别失败回退到 TEXT,导致数值列被当字符串存储。
501
551
  py_type = unwrap_optional_type(py_type)
552
+ descriptor = descriptor_fields.get(attr_name)
553
+ if py_type is None and isinstance(descriptor, Id):
554
+ py_type = int
555
+ elif py_type is None and isinstance(descriptor, (CreateTime, UpdateTime)):
556
+ py_type = str
557
+ elif py_type is None and isinstance(descriptor, Column) \
558
+ and descriptor.default is not None:
559
+ py_type = type(descriptor.default)
502
560
  if py_type is None:
503
561
  py_type = type(default_val) if default_val is not None and default_val != "" else str
504
- # 检查Column注解
505
- col_info = None
506
- for cls_base in cls.__mro__:
507
- if attr_name in cls_base.__dict__:
508
- cval = cls_base.__dict__[attr_name]
509
- if isinstance(cval, Column):
510
- col_info = cval
511
- elif hasattr(cval, '__column__'):
512
- col_info = getattr(cval, '__column__')
513
- break
562
+ col_info = descriptor
563
+ if not isinstance(col_info, Column) and hasattr(col_info, '__column__'):
564
+ col_info = getattr(col_info, '__column__')
514
565
  col_meta = self._build_column_meta(attr_name, py_type, col_info)
515
566
  columns.append(col_meta)
516
567
 
@@ -1030,31 +1081,97 @@ class DdlAutoManager:
1030
1081
 
1031
1082
  # ==================== 装饰器API(便捷使用) ====================
1032
1083
 
1033
- def entity(table_name: str = "", indexes: List[Index] = None, comment: str = ""):
1084
+ def _auto_generate_init(cls):
1085
+ """为描述符风格实体类自动生成 ``__init__``(若类未显式声明)。
1086
+
1087
+ 扫描 MRO 中所有 ``Column`` 实例属性,生成关键字构造器。
1088
+ 若类已显式声明 ``__init__`` 则跳过,保持兼容。
1034
1089
  """
1035
- @Entity 装饰器,标注一个类为JPA风格的实体类
1036
-
1037
- Usage:
1038
- @entity("sys_user")
1090
+ if '__init__' in cls.__dict__:
1091
+ return
1092
+ fields = {}
1093
+ for cls_base in reversed(cls.__mro__):
1094
+ for name, value in cls_base.__dict__.items():
1095
+ if isinstance(value, Column):
1096
+ fields[name] = value
1097
+
1098
+ def __init__(self, **values):
1099
+ unknown = set(values) - set(fields)
1100
+ if unknown:
1101
+ names = ', '.join(sorted(unknown))
1102
+ raise TypeError(f"Unexpected entity field(s): {names}")
1103
+ for name, column in fields.items():
1104
+ setattr(self, name, values.get(name, column.default))
1105
+
1106
+ cls.__init__ = __init__
1107
+
1108
+
1109
+ def Entity(table_name: str = "", indexes: List[Index] = None, comment: str = ""):
1110
+ """``@Entity`` 装饰器,标注一个类为 JPA 风格的实体类。
1111
+
1112
+ 三种用法(均向后兼容):
1113
+
1114
+ 1. **Java JPA 风格**(``@Entity`` + ``@Table`` 分离,推荐)::
1115
+
1116
+ @Entity
1117
+ @Table(name="sys_user", indexes=[Index("idx_name", ["name"])], comment="用户表")
1118
+ class User:
1119
+ id: int = Id()
1120
+ name: str = Column(length=50)
1121
+
1122
+ 2. **简化风格**(仅 ``@Entity``,表名自动推导为类名 snake_case)::
1123
+
1124
+ @Entity
1125
+ class User:
1126
+ id: int = Id()
1127
+ name: str = Column(length=50)
1128
+ # 表名自动推导为 "user"
1129
+
1130
+ 3. **一体化风格**(当前写法,完全兼容)::
1131
+
1132
+ @Entity("sys_user", indexes=[...], comment="用户表")
1039
1133
  class User:
1040
- def __init__(self, id: int = None, username: str = "", email: str = ""):
1041
- self.id = id
1042
- self.username = username
1043
- self.email = email
1134
+ id: int = Id()
1135
+ name: str = Column(length=50)
1044
1136
  """
1045
- t = Table(name=table_name, indexes=indexes, comment=comment)
1137
+ # 支持 @Entity 无括号形式:直接接收类
1138
+ if inspect.isclass(table_name):
1139
+ cls = table_name
1140
+ setattr(cls, '__entity__', True)
1141
+ # 未显式指定表信息时,不覆盖已有 @Table 设置的 __table__
1142
+ existing = getattr(cls, '__table__', None)
1143
+ if not isinstance(existing, Table):
1144
+ setattr(cls, '__table__', Table(name=_camel_to_snake(cls.__name__)))
1145
+ _auto_generate_init(cls)
1146
+ return cls
1147
+
1148
+ # @Entity("table_name", ...) 或 @Entity() 形式
1149
+ has_explicit_meta = bool(table_name or indexes or comment)
1150
+ t = Table(name=table_name, indexes=indexes, comment=comment) if has_explicit_meta else None
1151
+
1046
1152
  def decorator(cls):
1047
1153
  setattr(cls, '__entity__', True)
1048
- setattr(cls, '__table__', t)
1049
- if not table_name:
1050
- t.name = _camel_to_snake(cls.__name__)
1154
+ if t is not None:
1155
+ if not t.name:
1156
+ t.name = _camel_to_snake(cls.__name__)
1157
+ setattr(cls, '__table__', t)
1158
+ else:
1159
+ # 无显式表信息:不覆盖已有 @Table 设置
1160
+ existing = getattr(cls, '__table__', None)
1161
+ if not isinstance(existing, Table):
1162
+ setattr(cls, '__table__', Table(name=_camel_to_snake(cls.__name__)))
1163
+ _auto_generate_init(cls)
1051
1164
  return cls
1052
1165
  return decorator
1053
1166
 
1054
1167
 
1168
+ # Backward-compatible alias. New code should use the Spring-style @Entity name.
1169
+ entity = Entity
1170
+
1171
+
1055
1172
  def table(name: str = "", indexes: List[Index] = None, comment: str = ""):
1056
1173
  """@Table 装饰器,标注实体类对应的表(与@entity功能相同,别名)"""
1057
- return entity(name, indexes, comment)
1174
+ return Entity(name, indexes, comment)
1058
1175
 
1059
1176
 
1060
1177
  # ==================== 全局集成 ====================
@@ -1106,12 +1223,6 @@ def init_ddl_auto(connection_pool, config: dict = None) -> Optional[DdlAutoManag
1106
1223
 
1107
1224
  # 执行DDL
1108
1225
  if manager._entities:
1109
- # 临时关闭DDL阻断
1110
- try:
1111
- if hasattr(connection_pool, 'config'):
1112
- connection_pool.config['security_block_ddl'] = False
1113
- except Exception:
1114
- pass
1115
1226
  manager.execute()
1116
1227
 
1117
1228
  _global_ddl_manager = manager
@@ -247,9 +247,9 @@ class MyBatisConfigurer:
247
247
  try:
248
248
  from spring.orm.ddl_auto import init_ddl_auto
249
249
  # 获取连接池
250
- pool = None
250
+ pool = getattr(self.sql_session_factory, 'connection_pool', None)
251
251
  if hasattr(self.sql_session_factory, 'configuration'):
252
- pool = getattr(self.sql_session_factory.configuration, 'pool', None)
252
+ pool = pool or getattr(self.sql_session_factory.configuration, 'pool', None)
253
253
  if pool is None and hasattr(self.sql_session_factory, '_pool'):
254
254
  pool = self.sql_session_factory._pool
255
255
  if pool is not None:
@@ -34,7 +34,7 @@ from .security import SensitiveDataMasker, SQLInjectionDetector
34
34
  from .interceptor import Interceptor
35
35
  from .type_handler import TypeHandler
36
36
 
37
- __version__ = "2.2.0"
37
+ __version__ = "2.2.1"
38
38
  __author__ = "PyMyBatis Team"
39
39
 
40
40
  # 基础导出列表
@@ -5,6 +5,7 @@ PyMyBatis敏感数据脱敏模块
5
5
  """
6
6
 
7
7
  import re
8
+ from copy import copy
8
9
  from typing import Any, Dict, Optional, Callable
9
10
  from enum import Enum
10
11
 
@@ -159,6 +160,7 @@ class SensitiveDataMasker:
159
160
  if strategy is None:
160
161
  return value
161
162
 
163
+ original_value = value
162
164
  # 转换为字符串进行脱敏
163
165
  if not isinstance(value, str):
164
166
  value = str(value)
@@ -170,7 +172,7 @@ class SensitiveDataMasker:
170
172
  # 如果匹配失败,使用替换策略或返回原值
171
173
  if strategy.pattern.pattern == r'.+':
172
174
  return strategy.replacer(None)
173
- return value
175
+ return original_value
174
176
 
175
177
  def mask_dict(self, data: Dict[str, Any], field_types: Optional[Dict[str, SensitiveDataType]] = None) -> Dict[str, Any]:
176
178
  """
@@ -227,7 +229,22 @@ class SensitiveDataMasker:
227
229
  if not self.enabled:
228
230
  return data_list
229
231
 
230
- return [self.mask_dict(item, field_types) for item in data_list]
232
+ masked = []
233
+ for item in data_list:
234
+ if isinstance(item, dict):
235
+ masked.append(self.mask_dict(item, field_types))
236
+ elif hasattr(item, '__dict__'):
237
+ clone = copy(item)
238
+ attributes = {
239
+ key: value for key, value in vars(item).items()
240
+ if not key.startswith('_')
241
+ }
242
+ for key, value in self.mask_dict(attributes, field_types).items():
243
+ setattr(clone, key, value)
244
+ masked.append(clone)
245
+ else:
246
+ masked.append(item)
247
+ return masked
231
248
 
232
249
  def mask_log(self, log_message: str) -> str:
233
250
  """
@@ -11,6 +11,7 @@ PyMyBatis SQL注入检测模块
11
11
 
12
12
  import re
13
13
  import logging
14
+ from collections.abc import Mapping, Sequence, Set as AbstractSet
14
15
  from typing import Optional, Any, Dict, Set
15
16
  from enum import Enum
16
17
 
@@ -458,7 +459,11 @@ class SQLInjectionDetector:
458
459
 
459
460
  def is_safe(self, value: Any) -> bool:
460
461
  """
461
- 判断值是否安全
462
+ 判断值是否安全。
463
+
464
+ Mapper XML 支持通过 ``#{entity.field}`` 绑定实体对象,因此这里需要检查
465
+ 容器中的叶子值,而不是检查对象的 ``repr``(其中通常包含内存地址)。
466
+ 循环引用通过对象 id 集合截断。
462
467
 
463
468
  Args:
464
469
  value: 待检测的值
@@ -466,6 +471,40 @@ class SQLInjectionDetector:
466
471
  Returns:
467
472
  是否安全
468
473
  """
474
+ return self._is_safe_value(value, set())
475
+
476
+ def _is_safe_value(self, value: Any, visited: Set[int]) -> bool:
477
+ if value is None or isinstance(value, (str, bytes, int, float, bool)):
478
+ return not self.is_blocked(value)
479
+
480
+ if isinstance(value, Mapping):
481
+ object_id = id(value)
482
+ if object_id in visited:
483
+ return True
484
+ visited.add(object_id)
485
+ return all(self._is_safe_value(item, visited) for item in value.values())
486
+
487
+ if isinstance(value, (Sequence, AbstractSet)) and not isinstance(
488
+ value, (str, bytes, bytearray)
489
+ ):
490
+ object_id = id(value)
491
+ if object_id in visited:
492
+ return True
493
+ visited.add(object_id)
494
+ return all(self._is_safe_value(item, visited) for item in value)
495
+
496
+ attributes = getattr(value, '__dict__', None)
497
+ if isinstance(attributes, Mapping):
498
+ object_id = id(value)
499
+ if object_id in visited:
500
+ return True
501
+ visited.add(object_id)
502
+ return all(
503
+ self._is_safe_value(item, visited)
504
+ for key, item in attributes.items()
505
+ if not str(key).startswith('_')
506
+ )
507
+
469
508
  return not self.is_blocked(value)
470
509
 
471
510
  def sanitize(self, value: Any) -> Any:
@@ -2,7 +2,7 @@
2
2
  PyMyBatis版本信息
3
3
  """
4
4
 
5
- __version__ = '2.2.0'
5
+ __version__ = '2.2.2'
6
6
  __author__ = 'PyMyBatis Team'
7
7
  __email__ = 'dev@pymybatis.org'
8
8
  __license__ = 'MIT'
@@ -282,6 +282,19 @@ class XmlParser:
282
282
 
283
283
  @classmethod
284
284
  def _secure_fromstring(cls, xml_content: str):
285
+ # MyBatis mapper files conventionally contain this fixed public DTD.
286
+ # It is metadata only for this parser, so strip the exact allowlisted
287
+ # declaration without resolving the remote system identifier. Any
288
+ # other DTD or entity declaration remains forbidden by defusedxml.
289
+ xml_content = re.sub(
290
+ r'<!DOCTYPE\s+mapper\s+PUBLIC\s+'
291
+ r'["\']-//mybatis\.org//DTD Mapper 3\.0//EN["\']\s+'
292
+ r'["\']https?://mybatis\.org/dtd/mybatis-3-mapper\.dtd["\']\s*>',
293
+ '',
294
+ xml_content,
295
+ count=1,
296
+ flags=re.IGNORECASE,
297
+ )
285
298
  return DefusedET.fromstring(
286
299
  cls._normalize_comparison_operators(xml_content),
287
300
  forbid_dtd=True,
@@ -34,7 +34,7 @@
34
34
  """
35
35
  from .slicing import TestPool, SpringBootTest, WebMvcTest, DataJpaTest
36
36
 
37
- __version__ = "2.2.0"
37
+ __version__ = "2.2.1"
38
38
 
39
39
  __all__ = [
40
40
  "TestPool",
@@ -39,7 +39,7 @@ from .synchronization import (
39
39
  )
40
40
  from .events import TransactionalEventListener, TransactionalEventPublisher
41
41
 
42
- __version__ = "2.2.0"
42
+ __version__ = "2.2.1"
43
43
 
44
44
  __all__ = [
45
45
  "TransactionPhase",
@@ -11,7 +11,7 @@ class BannerPrinter:
11
11
  |_| |___/
12
12
  """
13
13
 
14
- def __init__(self, version: str = "2.2.0"): # v1.8.7 修复 import 时提前创建 logs/ 目录
14
+ def __init__(self, version: str = "2.2.1"): # v1.8.7 修复 import 时提前创建 logs/ 目录
15
15
  self.version = version
16
16
  self.logger = SpringLogger()
17
17
 
@@ -34,7 +34,7 @@ from .constraints import (
34
34
  from .validator import BeanValidator
35
35
  from .aop import BeanValidate, bean_validate_decorator
36
36
 
37
- __version__ = "2.2.0"
37
+ __version__ = "2.2.1"
38
38
 
39
39
  __all__ = [
40
40
  # 异常