springbootAI 2.2.0__tar.gz → 2.2.1__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.
- {springbootai-2.2.0 → springbootai-2.2.1}/CHANGELOG.md +8 -0
- {springbootai-2.2.0/springbootAI.egg-info → springbootai-2.2.1}/PKG-INFO +6 -6
- {springbootai-2.2.0 → springbootai-2.2.1}/README.md +5 -5
- {springbootai-2.2.0 → springbootai-2.2.1}/pyproject.toml +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/ai/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/csv/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/data/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/datasource/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/excel/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/i18n/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/__init__.py +2 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/ddl_auto.py +57 -24
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/mybatis_integration.py +2 -2
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/security/sensitive_data_masker.py +19 -2
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/security/sql_injection_detector.py +40 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/version.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/xml_parser/xml_parser.py +13 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/test/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/tx/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/utils/banner.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/validation/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/web/swagger.py +2 -2
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/web/web_context.py +6 -3
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/websocket/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.1/springbootAI.egg-info}/PKG-INFO +6 -6
- {springbootai-2.2.0 → springbootai-2.2.1}/springbootAI.egg-info/SOURCES.txt +2 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_ddl_auto.py +82 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_security.py +44 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_security_hardening.py +14 -0
- springbootai-2.2.1/tests/test_web_root_mapping.py +22 -0
- springbootai-2.2.1/tests/test_web_static_files.py +22 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/LICENSE +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/MANIFEST.in +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/SECURITY.md +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/requirements-ai.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/requirements-langgraph.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/requirements-mcp.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/requirements.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/setup.cfg +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/setup.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/ai/advisors.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/ai/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/ai/autoconfig.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/ai/core.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/ai/etl.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/ai/memory.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/ai/observability.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/ai/providers.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/ai/resilience.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/ai/tools.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/ai/vectorstore.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/annotations/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/annotations/cache.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/annotations/cloud.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/annotations/conditional.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/annotations/core.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/annotations/langchain.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/annotations/langgraph.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/annotations/messaging.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/aop/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/aop/aspect.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/aop/cloud_aop.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/aop/comprehensive_aop.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/aop/method_interceptor.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/aop/proxy_factory.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/cloud/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/cloud/discovery.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/cloud/feign.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/cloud/gateway.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/cloud/load_balancer.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/cloud/seata.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/cloud/seata_bridge.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/cloud/sentinel.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/cloud/tracer.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/cloud/transaction_store.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/config/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/config/binding.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/config/config_loader.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/context/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/context/application_context.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/context/bean_definition.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/context/bean_factory.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/context/registry.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/context/scanner.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/core/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/core/graceful_shutdown.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/core/typing_utils.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/csv/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/csv/converters.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/csv/easy_csv.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/csv/exceptions.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/csv/reader.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/csv/writer.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/data/page.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/data/repository.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/data/specification.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/datasource/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/datasource/context.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/datasource/dynamic.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/event/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/event/publisher.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/excel/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/excel/converters.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/excel/easy_excel.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/excel/exceptions.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/excel/reader.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/excel/style.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/excel/writer.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/i18n/accessor.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/i18n/auto_config.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/i18n/holder.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/i18n/locale.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/i18n/locale_resolver.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/i18n/message_source.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/i18n/middleware.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/i18n/properties.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/i18n/sources.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/adapters.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/agents/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/agents/services.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/autoconfig.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/callbacks/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/callbacks/handlers.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/chains/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/chains/services.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/core.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/indexes/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/indexes/index.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/loaders/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/loaders/loaders.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/memory/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/memory/memory.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/parsers/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/parsers/parsers.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/partners.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/prompts/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/prompts/templates.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/retrievers/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/retrievers/retrievers.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/tools/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/tools/tools.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/utilities/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/utilities/utils.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/vectorstores/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langchain/vectorstores/stores.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langgraph/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langgraph/annotation_runtime.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langgraph/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langgraph/autoconfig.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langgraph/checkpoint.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langgraph/config.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/langgraph/runtime.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/logging/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/logging/loguru_logger.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/main.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/mcp/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/mcp/annotation_runtime.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/mcp/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/mcp/autoconfig.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/mcp/client.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/mcp/config.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/mcp/server.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/messaging/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/messaging/rabbitmq.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/monitoring/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/monitoring/prometheus.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/database.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/migration.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/annotations/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/annotations/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/cache/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/cache/cache.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/cache/redis_cache.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/circuit_breaker/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/configuration.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/core/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/core/sql_session.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/core/sql_session_factory.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/dialect/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/dialect/dialect.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/dynamic_sql/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/interceptor/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/interceptor/interceptor.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/mapper/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/mapper/mapper.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/metrics/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/metrics/metrics.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/pool/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/pool/connection_pool.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/security/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/security/access_control.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/security/password_encoder.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/transaction/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/transaction/transaction.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/type_handler/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/type_handler/type_handler.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/xml_parser/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/retry/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/retry/recovery.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/retry/retry_annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/retry/retry_decorator.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/scheduling/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/scheduling/scheduler.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/security/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/security/jwt_utils.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/security/replay_protection.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/security/secret_manager.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/security/security_aop.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/security/security_context.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/test/slicing.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/tracing/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/tracing/skywalking.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/tx/events.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/tx/synchronization.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/utils/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/utils/bean_utils.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/utils/logger.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/utils/redis_client.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/validation/aop.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/validation/constraints.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/validation/exceptions.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/validation/validator.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/web/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/web/actuator.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/web/exception_handler.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/web/health.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/web/interceptor.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/web/result.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/websocket/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/websocket/broker.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/websocket/exceptions.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/websocket/handler.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/websocket/router.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/spring/websocket/session.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/springbootAI.egg-info/dependency_links.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/springbootAI.egg-info/entry_points.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/springbootAI.egg-info/requires.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/springbootAI.egg-info/top_level.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/sql/seata_undo_log.sql +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_actuator.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_ai_comprehensive.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_ai_declarative_annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_ai_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_annotation_combinations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_annotations_contract.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_aop_annotations_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_audit_time.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_bean_utils.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_cache_annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_cloud_embedded_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_component_init_error.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_conditional_annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_config_binding.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_config_fixes_185.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_connection_resilience.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_core_annotations_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_csv_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_data_repository.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_datasource_routing.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_declarative_aop_post_authorize_recover.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_di_config_event_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_excel_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_i18n_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_jpa_version_transient.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_langchain_comprehensive.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_langchain_ext.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_langchain_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_langgraph_comprehensive.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_langgraph_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_logging_config.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_mcp_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_new_features.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_orm_pymybatis_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_production_readiness.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_pymybatis_contract.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_seata_bridge.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_seata_durable_store.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_security_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_swagger_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_test_slicing.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_transactional_events.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_validation_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_web_annotations_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.1}/tests/test_websocket_module.py +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.2.1] - 2026-08-13
|
|
4
|
+
|
|
5
|
+
### 修复
|
|
6
|
+
|
|
7
|
+
- 修复 ORM `@Entity`/`@entity` 实体解析、DDL 字段推断和 MyBatis 连接池初始化。
|
|
8
|
+
- 加强 SQL 注入检测、敏感数据脱敏、Mapper XML 解析和 Web 根路由映射。
|
|
9
|
+
- 增加对应回归测试,确保实体描述符、容器参数和静态资源路由行为稳定。
|
|
10
|
+
|
|
3
11
|
本项目从 `2.1.0` 开始按 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 记录面向使用者的变化,并使用语义化版本号。
|
|
4
12
|
|
|
5
13
|
## [2.2.0] - 2026-08-12
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: springbootAI
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.1
|
|
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.
|
|
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.
|
|
319
|
-
| `spring.orm.pymybatis` | 2.2.
|
|
320
|
-
| `spring.ai` AI 模块 | 2.2.
|
|
321
|
-
| `spring.langchain` LangChain 模块 | 2.2.
|
|
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.
|
|
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.
|
|
185
|
-
| `spring.orm.pymybatis` | 2.2.
|
|
186
|
-
| `spring.ai` AI 模块 | 2.2.
|
|
187
|
-
| `spring.langchain` LangChain 模块 | 2.2.
|
|
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.
|
|
15
|
+
version = "2.2.1"
|
|
16
16
|
description = "Spring-style Python framework with Web, ORM, Cloud and AI integrations"
|
|
17
17
|
readme = "README.md"
|
|
18
18
|
license = "MIT"
|
|
@@ -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.
|
|
68
|
+
__version__ = "2.2.1"
|
|
69
69
|
|
|
70
70
|
__all__ = [
|
|
71
71
|
# Locale
|
|
@@ -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',
|
|
@@ -480,37 +480,53 @@ class DdlAutoManager:
|
|
|
480
480
|
col_meta = self._get_field_meta(df)
|
|
481
481
|
columns.append(col_meta)
|
|
482
482
|
else:
|
|
483
|
-
#
|
|
483
|
+
# 普通实体同时支持显式 self.xxx 赋值、类型注解和类级 Column 描述符。
|
|
484
|
+
# 只依赖源码中的 self.xxx 会漏掉循环赋值或动态赋值的实体。
|
|
484
485
|
init_fields = self._extract_init_fields(cls)
|
|
485
|
-
# 同时检查类级别的类型注解(__init__ 的参数注解)
|
|
486
486
|
init_hints = {}
|
|
487
487
|
try:
|
|
488
488
|
init_hints = get_type_hints(cls.__init__)
|
|
489
489
|
except Exception:
|
|
490
490
|
pass
|
|
491
|
-
|
|
492
|
-
|
|
491
|
+
try:
|
|
492
|
+
cls_annotations = get_type_hints(cls)
|
|
493
|
+
except Exception:
|
|
494
|
+
cls_annotations = getattr(cls, '__annotations__', {})
|
|
495
|
+
|
|
496
|
+
descriptor_fields = {}
|
|
497
|
+
for cls_base in reversed(cls.__mro__):
|
|
498
|
+
for attr_name, value in cls_base.__dict__.items():
|
|
499
|
+
if isinstance(value, (Column, Transient)) or hasattr(value, '__column__'):
|
|
500
|
+
descriptor_fields[attr_name] = value
|
|
501
|
+
|
|
502
|
+
field_names = dict(init_fields)
|
|
503
|
+
for attr_name in cls_annotations:
|
|
504
|
+
field_names.setdefault(attr_name, None)
|
|
505
|
+
for attr_name in descriptor_fields:
|
|
506
|
+
field_names.setdefault(attr_name, None)
|
|
507
|
+
|
|
508
|
+
for attr_name, default_val in field_names.items():
|
|
493
509
|
# @Transient 字段不持久化,跳过
|
|
494
510
|
if _is_transient_field(cls, attr_name):
|
|
495
511
|
continue
|
|
496
|
-
# 优先取 __init__ 注解,其次取类注解
|
|
497
512
|
py_type = init_hints.get(attr_name) or cls_annotations.get(attr_name)
|
|
498
513
|
# 解包 Optional[X]:Python 3.10 的 get_type_hints 会把带 None 默认值的
|
|
499
514
|
# 参数注解自动包装为 Optional[X],3.11+ 不再包装。此处统一解包为承载类型,
|
|
500
515
|
# 否则 _get_sql_type 在 3.10 上识别失败回退到 TEXT,导致数值列被当字符串存储。
|
|
501
516
|
py_type = unwrap_optional_type(py_type)
|
|
517
|
+
descriptor = descriptor_fields.get(attr_name)
|
|
518
|
+
if py_type is None and isinstance(descriptor, Id):
|
|
519
|
+
py_type = int
|
|
520
|
+
elif py_type is None and isinstance(descriptor, (CreateTime, UpdateTime)):
|
|
521
|
+
py_type = str
|
|
522
|
+
elif py_type is None and isinstance(descriptor, Column) \
|
|
523
|
+
and descriptor.default is not None:
|
|
524
|
+
py_type = type(descriptor.default)
|
|
502
525
|
if py_type is None:
|
|
503
526
|
py_type = type(default_val) if default_val is not None and default_val != "" else str
|
|
504
|
-
|
|
505
|
-
col_info
|
|
506
|
-
|
|
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
|
|
527
|
+
col_info = descriptor
|
|
528
|
+
if not isinstance(col_info, Column) and hasattr(col_info, '__column__'):
|
|
529
|
+
col_info = getattr(col_info, '__column__')
|
|
514
530
|
col_meta = self._build_column_meta(attr_name, py_type, col_info)
|
|
515
531
|
columns.append(col_meta)
|
|
516
532
|
|
|
@@ -1030,12 +1046,12 @@ class DdlAutoManager:
|
|
|
1030
1046
|
|
|
1031
1047
|
# ==================== 装饰器API(便捷使用) ====================
|
|
1032
1048
|
|
|
1033
|
-
def
|
|
1049
|
+
def Entity(table_name: str = "", indexes: List[Index] = None, comment: str = ""):
|
|
1034
1050
|
"""
|
|
1035
1051
|
@Entity 装饰器,标注一个类为JPA风格的实体类
|
|
1036
1052
|
|
|
1037
1053
|
Usage:
|
|
1038
|
-
@
|
|
1054
|
+
@Entity("sys_user")
|
|
1039
1055
|
class User:
|
|
1040
1056
|
def __init__(self, id: int = None, username: str = "", email: str = ""):
|
|
1041
1057
|
self.id = id
|
|
@@ -1048,13 +1064,36 @@ def entity(table_name: str = "", indexes: List[Index] = None, comment: str = "")
|
|
|
1048
1064
|
setattr(cls, '__table__', t)
|
|
1049
1065
|
if not table_name:
|
|
1050
1066
|
t.name = _camel_to_snake(cls.__name__)
|
|
1067
|
+
|
|
1068
|
+
# Descriptor-style entities do not need repetitive constructors.
|
|
1069
|
+
# Keep an explicitly declared __init__ unchanged for compatibility.
|
|
1070
|
+
if '__init__' not in cls.__dict__:
|
|
1071
|
+
fields = {}
|
|
1072
|
+
for cls_base in reversed(cls.__mro__):
|
|
1073
|
+
for name, value in cls_base.__dict__.items():
|
|
1074
|
+
if isinstance(value, Column):
|
|
1075
|
+
fields[name] = value
|
|
1076
|
+
|
|
1077
|
+
def __init__(self, **values):
|
|
1078
|
+
unknown = set(values) - set(fields)
|
|
1079
|
+
if unknown:
|
|
1080
|
+
names = ', '.join(sorted(unknown))
|
|
1081
|
+
raise TypeError(f"Unexpected entity field(s): {names}")
|
|
1082
|
+
for name, column in fields.items():
|
|
1083
|
+
setattr(self, name, values.get(name, column.default))
|
|
1084
|
+
|
|
1085
|
+
cls.__init__ = __init__
|
|
1051
1086
|
return cls
|
|
1052
1087
|
return decorator
|
|
1053
1088
|
|
|
1054
1089
|
|
|
1090
|
+
# Backward-compatible alias. New code should use the Spring-style @Entity name.
|
|
1091
|
+
entity = Entity
|
|
1092
|
+
|
|
1093
|
+
|
|
1055
1094
|
def table(name: str = "", indexes: List[Index] = None, comment: str = ""):
|
|
1056
1095
|
"""@Table 装饰器,标注实体类对应的表(与@entity功能相同,别名)"""
|
|
1057
|
-
return
|
|
1096
|
+
return Entity(name, indexes, comment)
|
|
1058
1097
|
|
|
1059
1098
|
|
|
1060
1099
|
# ==================== 全局集成 ====================
|
|
@@ -1106,12 +1145,6 @@ def init_ddl_auto(connection_pool, config: dict = None) -> Optional[DdlAutoManag
|
|
|
1106
1145
|
|
|
1107
1146
|
# 执行DDL
|
|
1108
1147
|
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
1148
|
manager.execute()
|
|
1116
1149
|
|
|
1117
1150
|
_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:
|
{springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/security/sensitive_data_masker.py
RENAMED
|
@@ -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
|
|
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
|
-
|
|
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
|
"""
|
{springbootai-2.2.0 → springbootai-2.2.1}/spring/orm/pymybatis/security/sql_injection_detector.py
RENAMED
|
@@ -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:
|
|
@@ -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,
|
|
@@ -11,7 +11,7 @@ class BannerPrinter:
|
|
|
11
11
|
|_| |___/
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
def __init__(self, version: str = "2.2.
|
|
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
|
|
|
@@ -268,7 +268,7 @@ class SwaggerConfig:
|
|
|
268
268
|
enabled: bool = True
|
|
269
269
|
title: str = "SpringBootAI Application"
|
|
270
270
|
description: str = ""
|
|
271
|
-
version: str = "2.2.
|
|
271
|
+
version: str = "2.2.1"
|
|
272
272
|
terms_of_service: str = ""
|
|
273
273
|
contact_name: str = ""
|
|
274
274
|
contact_email: str = ""
|
|
@@ -356,7 +356,7 @@ class SwaggerConfig:
|
|
|
356
356
|
enabled=api_docs_enabled,
|
|
357
357
|
title=_opt("title", "SpringBootAI Application"),
|
|
358
358
|
description=_opt("description", ""),
|
|
359
|
-
version=_opt("version", "2.2.
|
|
359
|
+
version=_opt("version", "2.2.1"),
|
|
360
360
|
terms_of_service=_opt("terms-of-service", ""),
|
|
361
361
|
contact_name=_opt("contact-name", "") or (contact.get("name", "") if isinstance(contact, dict) else ""),
|
|
362
362
|
contact_email=_opt("contact-email", "") or (contact.get("email", "") if isinstance(contact, dict) else ""),
|
|
@@ -270,10 +270,14 @@ class WebApplicationContext:
|
|
|
270
270
|
class_paths = class_path if isinstance(class_path, list) else [class_path]
|
|
271
271
|
methods = annotation.method or ['GET']
|
|
272
272
|
for raw_path in paths or ['']:
|
|
273
|
-
path = raw_path or '
|
|
273
|
+
path = raw_path or ''
|
|
274
274
|
prefix = class_paths[0] if class_paths else ''
|
|
275
275
|
if prefix:
|
|
276
|
-
path = prefix.rstrip('/')
|
|
276
|
+
path = prefix.rstrip('/')
|
|
277
|
+
if raw_path:
|
|
278
|
+
path += '/' + str(raw_path).lstrip('/')
|
|
279
|
+
elif not path:
|
|
280
|
+
path = '/'
|
|
277
281
|
endpoint = self._create_endpoint(controller_instance, method, path)
|
|
278
282
|
for http_method in methods:
|
|
279
283
|
self._add_route(http_method.lower(), path, endpoint, openapi_meta)
|
|
@@ -651,7 +655,6 @@ class WebApplicationContext:
|
|
|
651
655
|
if self._static_dir and os.path.isdir(self._static_dir):
|
|
652
656
|
from fastapi.staticfiles import StaticFiles
|
|
653
657
|
from fastapi.responses import FileResponse
|
|
654
|
-
import os
|
|
655
658
|
|
|
656
659
|
# 获取静态目录的绝对路径,用于路径安全验证
|
|
657
660
|
self._static_dir_abs = os.path.realpath(self._static_dir)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: springbootAI
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.1
|
|
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.
|
|
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.
|
|
319
|
-
| `spring.orm.pymybatis` | 2.2.
|
|
320
|
-
| `spring.ai` AI 模块 | 2.2.
|
|
321
|
-
| `spring.langchain` LangChain 模块 | 2.2.
|
|
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 适合什么场景
|