python-ddd-framework 0.3.2__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_ddd_framework/__init__.py +139 -21
- python_ddd_framework/application/builder.py +23 -78
- python_ddd_framework/application/composition.py +4 -22
- python_ddd_framework/application/runtime.py +120 -288
- python_ddd_framework/application/state.py +7 -75
- python_ddd_framework/application_services/__init__.py +5 -21
- python_ddd_framework/{services/application_bindings.py → application_services/bindings.py} +52 -9
- python_ddd_framework/application_services/catalog.py +36 -288
- python_ddd_framework/application_services/contracts.py +14 -13
- python_ddd_framework/application_services/dispatcher.py +19 -269
- python_ddd_framework/application_services/invocation.py +197 -15
- python_ddd_framework/application_services/module.py +168 -0
- python_ddd_framework/application_services/pagination.py +21 -0
- python_ddd_framework/auditing/control.py +1 -1
- python_ddd_framework/auditing/module.py +15 -0
- python_ddd_framework/authorization/__init__.py +2 -0
- python_ddd_framework/authorization/contracts.py +7 -2
- python_ddd_framework/authorization/definitions.py +5 -0
- python_ddd_framework/authorization/module.py +40 -0
- python_ddd_framework/authorization/options.py +7 -0
- python_ddd_framework/background_execution/child.py +13 -3
- python_ddd_framework/background_execution/declarations.py +45 -0
- python_ddd_framework/background_execution/lifecycle.py +12 -0
- python_ddd_framework/background_execution/local.py +20 -1
- python_ddd_framework/background_execution/module.py +107 -1
- python_ddd_framework/background_execution/processes.py +11 -1
- python_ddd_framework/background_jobs/catalog.py +59 -42
- python_ddd_framework/background_jobs/contracts.py +16 -19
- python_ddd_framework/background_jobs/pgqueuer/enqueue.py +4 -0
- python_ddd_framework/background_jobs/pgqueuer/module.py +2 -1
- python_ddd_framework/background_jobs/pgqueuer/runtime.py +7 -7
- python_ddd_framework/background_workers/catalog.py +23 -36
- python_ddd_framework/background_workers/contracts.py +3 -0
- python_ddd_framework/background_workers/execution.py +1 -1
- python_ddd_framework/background_workers/runtime.py +47 -1
- python_ddd_framework/caching/__init__.py +7 -6
- python_ddd_framework/caching/contracts.py +65 -67
- python_ddd_framework/caching/errors.py +0 -7
- python_ddd_framework/caching/module.py +18 -0
- python_ddd_framework/caching/naming.py +29 -0
- python_ddd_framework/caching/options.py +38 -0
- python_ddd_framework/caching/unit_of_work.py +46 -0
- python_ddd_framework/cli/__init__.py +31 -6
- python_ddd_framework/cli/project.py +68 -6
- python_ddd_framework/cli/runtime.py +1 -1
- python_ddd_framework/developer_kit/generation.py +75 -22
- python_ddd_framework/developer_kit/project_metadata.py +2 -4
- python_ddd_framework/developer_kit/templates/basic/cookiecutter.json +4 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/README.md +45 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/conversion_service.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/module.py.jinja +38 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/default_conversion_service.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +42 -49
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +45 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +8 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +35 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +5 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +15 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +28 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +26 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +23 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_integration_service.py.jinja +71 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +16 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +16 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +105 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{options.py.jinja → options/order_options.py.jinja} +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +65 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +35 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +41 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +6 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +39 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +12 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +12 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{orders.py.jinja → entities/order.py.jinja} +10 -18
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +42 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +22 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +23 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{settings.py.jinja → settings/approval_settings.py.jinja} +3 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +3 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +6 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +11 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +26 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +40 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/{permissions.py.jinja → permissions/order_permission_provider.py.jinja} +6 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +18 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +15 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +5 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +85 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/{files.py.jinja → routers/order_files.py.jinja} +23 -20
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +44 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +1 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/base.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/{orders.py.jinja → order_model.py.jinja} +7 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +42 -5
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +79 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +4 -3
- python_ddd_framework/developer_kit/templates/project/cookiecutter.json +1 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +23 -3
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +29 -22
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{app.development.yaml.jinja → backend/app.development.yaml.jinja} +2 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{pyproject.toml.jinja → backend/pyproject.toml.jinja} +1 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/main.py.jinja +2 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/module.py.jinja +40 -3
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +42 -11
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +144 -23
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/scripts/.gitkeep +0 -0
- python_ddd_framework/distributed_lock/contracts.py +4 -1
- python_ddd_framework/domain/__init__.py +2 -0
- python_ddd_framework/domain/services.py +11 -0
- python_ddd_framework/errors/business.py +10 -1
- python_ddd_framework/errors/lifecycle.py +3 -31
- python_ddd_framework/events/__init__.py +5 -2
- python_ddd_framework/events/aggregate.py +3 -3
- python_ddd_framework/events/catalog.py +12 -11
- python_ddd_framework/events/contracts.py +6 -1
- python_ddd_framework/events/contribution.py +1 -52
- python_ddd_framework/events/entities.py +23 -0
- python_ddd_framework/events/module.py +97 -0
- python_ddd_framework/events/runtime.py +80 -29
- python_ddd_framework/events/subscription.py +36 -0
- python_ddd_framework/events/unit_of_work.py +25 -0
- python_ddd_framework/extensions/__init__.py +22 -0
- python_ddd_framework/extensions/catalog.py +198 -0
- python_ddd_framework/extensions/contracts.py +85 -0
- python_ddd_framework/extensions/models.py +142 -0
- python_ddd_framework/extensions/module.py +21 -0
- python_ddd_framework/fastapi/__init__.py +2 -0
- python_ddd_framework/fastapi/action.py +1 -2
- python_ddd_framework/fastapi/adapter.py +16 -0
- python_ddd_framework/fastapi/application_services.py +43 -25
- python_ddd_framework/fastapi/background.py +2 -1
- python_ddd_framework/fastapi/contracts.py +26 -8
- python_ddd_framework/fastapi/health.py +1 -1
- python_ddd_framework/fastapi/module.py +14 -0
- python_ddd_framework/fastapi/parameters.py +3 -18
- python_ddd_framework/fastapi/realtime/connection.py +10 -7
- python_ddd_framework/fastapi/realtime/module.py +3 -0
- python_ddd_framework/fastapi/realtime/runtime.py +10 -24
- python_ddd_framework/fastapi/request_context.py +2 -1
- python_ddd_framework/fastapi/routing.py +2 -1
- python_ddd_framework/fastapi/server.py +8 -0
- python_ddd_framework/fastapi/settings.py +2 -1
- python_ddd_framework/hosted_services/bridge.py +95 -17
- python_ddd_framework/hosted_services/catalog.py +6 -16
- python_ddd_framework/hosted_services/contracts.py +9 -1
- python_ddd_framework/hosted_services/declarations.py +15 -0
- python_ddd_framework/hosted_services/module.py +69 -0
- python_ddd_framework/hosted_services/runtime.py +101 -18
- python_ddd_framework/identity/__init__.py +24 -0
- python_ddd_framework/identity/application.py +38 -90
- python_ddd_framework/identity/contracts.py +181 -16
- python_ddd_framework/identity/http_api.py +79 -2
- python_ddd_framework/identity/management.py +166 -0
- python_ddd_framework/identity/module.py +12 -4
- python_ddd_framework/identity/services.py +29 -16
- python_ddd_framework/identity/sqlalchemy/migrations/0003_extra_properties.py +26 -0
- python_ddd_framework/identity/sqlalchemy/migrations/0004_concurrency_version.py +29 -0
- python_ddd_framework/identity/sqlalchemy/models.py +21 -2
- python_ddd_framework/identity/sqlalchemy/module.py +11 -2
- python_ddd_framework/identity/sqlalchemy/repository.py +458 -0
- python_ddd_framework/identity/sqlalchemy/session_security.py +88 -0
- python_ddd_framework/identity/sqlalchemy/stores.py +69 -290
- python_ddd_framework/identity/tokens.py +2 -5
- python_ddd_framework/{application_services/validation.py → invocation/arguments.py} +8 -6
- python_ddd_framework/invocation/contribution.py +61 -0
- python_ddd_framework/invocation/dispatcher.py +291 -0
- python_ddd_framework/invocation/entries.py +2 -25
- python_ddd_framework/invocation/entrypoints.py +126 -0
- python_ddd_framework/{application_services → invocation}/execution.py +8 -5
- python_ddd_framework/invocation/function_runtime.py +3 -1
- python_ddd_framework/invocation/interception.py +113 -42
- python_ddd_framework/{application_services → invocation}/interceptors.py +2 -2
- python_ddd_framework/invocation/managed_proxy.py +116 -0
- python_ddd_framework/invocation/managed_services.py +250 -0
- python_ddd_framework/invocation/methods.py +345 -0
- python_ddd_framework/invocation/module.py +113 -0
- python_ddd_framework/{application_services → invocation}/policies.py +8 -1
- python_ddd_framework/invocation/validation.py +25 -0
- python_ddd_framework/lifecycle/__init__.py +2 -0
- python_ddd_framework/lifecycle/composition.py +5 -2
- python_ddd_framework/lifecycle/participants.py +37 -0
- python_ddd_framework/messaging/__init__.py +25 -0
- python_ddd_framework/messaging/channel.py +232 -0
- python_ddd_framework/messaging/contracts.py +78 -0
- python_ddd_framework/messaging/module.py +114 -0
- python_ddd_framework/messaging/options.py +13 -0
- python_ddd_framework/messaging/receipt.py +27 -0
- python_ddd_framework/messaging/runtime.py +139 -0
- python_ddd_framework/modularity/__init__.py +11 -1
- python_ddd_framework/modularity/contracts.py +47 -16
- python_ddd_framework/modularity/discovery.py +21 -2
- python_ddd_framework/modularity/graph.py +45 -1
- python_ddd_framework/modularity/registry.py +83 -3
- python_ddd_framework/notifications/__init__.py +2 -0
- python_ddd_framework/notifications/catalog.py +3 -2
- python_ddd_framework/notifications/declarations.py +18 -0
- python_ddd_framework/notifications/module.py +21 -0
- python_ddd_framework/observability/logging.py +3 -0
- python_ddd_framework/observability/tracing.py +3 -0
- python_ddd_framework/options/contribution.py +12 -0
- python_ddd_framework/options/registry.py +44 -11
- python_ddd_framework/realtime/__init__.py +2 -1
- python_ddd_framework/realtime/contracts.py +3 -7
- python_ddd_framework/realtime/messages.py +26 -1
- python_ddd_framework/redis/cache.py +243 -0
- python_ddd_framework/redis/cache_scripts.py +40 -0
- python_ddd_framework/redis/distributed_lock.py +10 -2
- python_ddd_framework/redis/module.py +13 -4
- python_ddd_framework/redis/notifications.py +5 -2
- python_ddd_framework/redis/runtime.py +1 -79
- python_ddd_framework/seeding/__init__.py +13 -0
- python_ddd_framework/seeding/contracts.py +45 -0
- python_ddd_framework/seeding/module.py +73 -0
- python_ddd_framework/seeding/runtime.py +63 -0
- python_ddd_framework/services/arbitration.py +23 -0
- python_ddd_framework/services/binding.py +2 -3
- python_ddd_framework/services/composition.py +108 -0
- python_ddd_framework/services/contribution.py +44 -54
- python_ddd_framework/services/convention.py +53 -181
- python_ddd_framework/services/fixed_lifetime.py +12 -15
- python_ddd_framework/services/framework_provider.py +3 -158
- python_ddd_framework/services/native_graph.py +35 -60
- python_ddd_framework/services/provider.py +59 -4
- python_ddd_framework/services/repository.py +6 -0
- python_ddd_framework/services/runtime.py +129 -215
- python_ddd_framework/settings/catalog.py +5 -4
- python_ddd_framework/settings/declarations.py +18 -0
- python_ddd_framework/settings/definitions.py +5 -0
- python_ddd_framework/settings/manager.py +1 -1
- python_ddd_framework/settings/module.py +50 -1
- python_ddd_framework/settings/notifications.py +4 -2
- python_ddd_framework/settings/refresh_module.py +9 -2
- python_ddd_framework/settings/sqlalchemy/module.py +2 -1
- python_ddd_framework/sqlalchemy/__init__.py +12 -0
- python_ddd_framework/sqlalchemy/alembic_runtime/env.py +1 -0
- python_ddd_framework/sqlalchemy/auditing.py +7 -1
- python_ddd_framework/sqlalchemy/extensions.py +186 -0
- python_ddd_framework/sqlalchemy/metadata.py +110 -6
- python_ddd_framework/sqlalchemy/migration.py +61 -8
- python_ddd_framework/sqlalchemy/migration_operations.py +86 -0
- python_ddd_framework/sqlalchemy/module.py +14 -11
- python_ddd_framework/sqlalchemy/module_migration.py +71 -3
- python_ddd_framework/sqlalchemy/repository.py +217 -13
- python_ddd_framework/sqlalchemy/session_provider.py +14 -1
- python_ddd_framework/testing/runtime.py +5 -4
- python_ddd_framework/unit_of_work/__init__.py +2 -1
- python_ddd_framework/unit_of_work/contracts.py +6 -7
- python_ddd_framework/unit_of_work/errors.py +11 -0
- python_ddd_framework/unit_of_work/lifecycle.py +19 -0
- python_ddd_framework/unit_of_work/manager.py +86 -32
- python_ddd_framework/unit_of_work/module.py +61 -0
- python_ddd_framework/unit_of_work/options.py +7 -1
- python_ddd_framework/validation/__init__.py +1 -0
- python_ddd_framework/validation/models.py +37 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/METADATA +147 -44
- python_ddd_framework-0.5.1.dist-info/RECORD +488 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/WHEEL +1 -1
- python_ddd_framework/application_services/seeding.py +0 -72
- python_ddd_framework/caching/catalog.py +0 -73
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +0 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +0 -24
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration.py.jinja +0 -41
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +0 -83
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +0 -84
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +0 -35
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +0 -14
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +0 -19
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +0 -24
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +0 -36
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +0 -49
- python_ddd_framework-0.3.2.dist-info/RECORD +0 -355
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.dockerignore → backend/.dockerignore} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.gitignore → backend/.gitignore} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.python-version → backend/.python-version} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{Dockerfile → backend/Dockerfile} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.dev.yaml.jinja → backend/compose.dev.yaml.jinja} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.production.yaml.jinja → backend/compose.production.yaml.jinja} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/__init__.py.jinja +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/conftest.py.jinja +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/host/test_http.py.jinja +0 -0
- /python_ddd_framework/{application_services → invocation}/signature.py +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
|
@@ -3,17 +3,21 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
from collections.abc import AsyncIterator
|
|
7
|
-
from contextlib import asynccontextmanager
|
|
6
|
+
from collections.abc import AsyncIterator, Callable
|
|
7
|
+
from contextlib import AsyncExitStack, asynccontextmanager
|
|
8
8
|
from contextvars import ContextVar
|
|
9
|
+
from functools import partial
|
|
9
10
|
from typing import TYPE_CHECKING
|
|
10
11
|
from uuid import uuid4
|
|
11
12
|
|
|
12
|
-
from dishka import Scope
|
|
13
|
+
from dishka import AsyncContainer, Scope
|
|
13
14
|
|
|
14
15
|
from ..invocation import ApplicationInvocationContext
|
|
15
16
|
from .contracts import UnitOfWork, _UnitOfWorkFactory, _UnitOfWorkParticipant
|
|
17
|
+
from .errors import UnitOfWorkCompletionError
|
|
18
|
+
from .lifecycle import UnitOfWorkLifecycleFactory
|
|
16
19
|
from .options import (
|
|
20
|
+
_DEFAULT_CONNECTION_NAME,
|
|
17
21
|
UnitOfWorkDeclaration,
|
|
18
22
|
UnitOfWorkOptions,
|
|
19
23
|
UnitOfWorkTransactionBehavior,
|
|
@@ -22,7 +26,6 @@ from .options import (
|
|
|
22
26
|
|
|
23
27
|
if TYPE_CHECKING:
|
|
24
28
|
from ..application_services.invocation import _InvocationRuntime
|
|
25
|
-
from ..events import LocalEventCatalog
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
class UnitOfWorkManager:
|
|
@@ -31,12 +34,14 @@ class UnitOfWorkManager:
|
|
|
31
34
|
runtime: _InvocationRuntime,
|
|
32
35
|
factory: _UnitOfWorkFactory,
|
|
33
36
|
options: UnitOfWorkOptions,
|
|
34
|
-
|
|
37
|
+
lifecycles: tuple[UnitOfWorkLifecycleFactory, ...],
|
|
38
|
+
container: AsyncContainer,
|
|
35
39
|
) -> None:
|
|
36
40
|
self._runtime = runtime
|
|
37
41
|
self._factory = factory
|
|
38
42
|
self._options = options
|
|
39
|
-
self.
|
|
43
|
+
self._lifecycles = lifecycles
|
|
44
|
+
self._container = container
|
|
40
45
|
self._ambient: ContextVar[UnitOfWork | None] = ContextVar(
|
|
41
46
|
f"python_ddd_framework_uow_{runtime.application_id}", default=None
|
|
42
47
|
)
|
|
@@ -60,7 +65,6 @@ class UnitOfWorkManager:
|
|
|
60
65
|
self, connection_name: str | None, declaration: UnitOfWorkDeclaration, method_name: str
|
|
61
66
|
) -> _ResolvedUnitOfWorkOptions:
|
|
62
67
|
from ..application_services.contracts import (
|
|
63
|
-
_DEFAULT_CONNECTION_NAME,
|
|
64
68
|
_validate_connection_name,
|
|
65
69
|
)
|
|
66
70
|
|
|
@@ -141,48 +145,78 @@ class UnitOfWorkManager:
|
|
|
141
145
|
participant._released = True
|
|
142
146
|
return
|
|
143
147
|
async with self._runtime.outer_lease():
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
owned: UnitOfWork | None = None
|
|
149
|
+
async with self._execution_scope(
|
|
150
|
+
options.connection_name, committed=lambda: owned is not None and owned.committed
|
|
151
|
+
):
|
|
152
|
+
async with self._own_work(options) as work:
|
|
153
|
+
owned = work
|
|
154
|
+
yield work
|
|
155
|
+
|
|
156
|
+
@asynccontextmanager
|
|
157
|
+
async def _execution_scope(
|
|
158
|
+
self, connection_name: str, *, committed: Callable[[], bool] = lambda: False
|
|
159
|
+
) -> AsyncIterator[AsyncContainer]:
|
|
160
|
+
from ..application_services.invocation import (
|
|
161
|
+
_InvocationState,
|
|
162
|
+
_managed_action,
|
|
163
|
+
_managed_request,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
execution = self._runtime.current()
|
|
167
|
+
if execution is not None:
|
|
168
|
+
# 复用执行身份,不以新事务改写原生 ACTION context/cache。
|
|
169
|
+
if execution.owner_task is not asyncio.current_task():
|
|
170
|
+
from ..application_services.errors import CrossTaskInvocationError
|
|
171
|
+
|
|
172
|
+
raise CrossTaskInvocationError
|
|
173
|
+
yield execution.action_container
|
|
174
|
+
return
|
|
175
|
+
parent = self._runtime.current_call_scope() or self._container
|
|
176
|
+
async with AsyncExitStack() as stack:
|
|
177
|
+
if parent.scope is Scope.APP:
|
|
178
|
+
parent = await stack.enter_async_context(_managed_request(parent))
|
|
179
|
+
context = (
|
|
180
|
+
await parent.get(ApplicationInvocationContext)
|
|
181
|
+
if parent.scope is Scope.ACTION
|
|
182
|
+
else self._runtime._new_context(connection_name)
|
|
183
|
+
)
|
|
184
|
+
action = await stack.enter_async_context(
|
|
185
|
+
_managed_action(parent, context, committed=committed)
|
|
186
|
+
)
|
|
187
|
+
task = asyncio.current_task()
|
|
188
|
+
assert task is not None
|
|
189
|
+
token = self._runtime.bind(_InvocationState(context, task, action))
|
|
190
|
+
try:
|
|
191
|
+
yield action
|
|
192
|
+
finally:
|
|
193
|
+
self._runtime.reset(token)
|
|
146
194
|
|
|
147
195
|
@asynccontextmanager
|
|
148
196
|
async def _own_work(self, options: _ResolvedUnitOfWorkOptions) -> AsyncIterator[UnitOfWork]:
|
|
149
197
|
from ..application_services.errors import ApplicationInvocationCleanupError
|
|
150
|
-
from ..events.runtime import _LocalEventQueue
|
|
151
198
|
|
|
152
199
|
execution = self._runtime.current()
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if execution is not None
|
|
156
|
-
else self._runtime.current_call_scope()
|
|
157
|
-
)
|
|
158
|
-
if container is None:
|
|
159
|
-
raise RuntimeError("UnitOfWork.begin requires a managed execution scope")
|
|
200
|
+
assert execution is not None
|
|
201
|
+
container = execution.action_container
|
|
160
202
|
context = ApplicationInvocationContext(
|
|
161
203
|
application_id=self._runtime.application_id,
|
|
162
204
|
invocation_id=uuid4(),
|
|
163
|
-
parent_invocation_id=execution.context.invocation_id
|
|
205
|
+
parent_invocation_id=execution.context.invocation_id,
|
|
164
206
|
connection_name=options.connection_name,
|
|
165
207
|
)
|
|
166
208
|
work = self._factory.create()
|
|
167
209
|
work._options = options
|
|
168
|
-
work._event_queue = _LocalEventQueue(container, context, self._catalog)
|
|
169
|
-
|
|
170
|
-
async def after_commit() -> None:
|
|
171
|
-
# 新服务调用既不能复用旧事务,也不能落入旧 ACTION 的对象缓存。
|
|
172
|
-
token = self._ambient.set(None)
|
|
173
|
-
parent = container.parent_container if container.scope is Scope.ACTION else container
|
|
174
|
-
assert parent is not None
|
|
175
|
-
try:
|
|
176
|
-
with self._runtime.detached_execution(parent):
|
|
177
|
-
assert work._event_queue is not None
|
|
178
|
-
await work._event_queue._dispatch_after_commit()
|
|
179
|
-
finally:
|
|
180
|
-
self._ambient.reset(token)
|
|
181
210
|
|
|
182
|
-
work._after_commit =
|
|
211
|
+
work._after_commit = partial(self._after_commit, work, container)
|
|
183
212
|
token = self._ambient.set(work)
|
|
184
213
|
primary: BaseException | None = None
|
|
185
214
|
try:
|
|
215
|
+
# 创建参与者也属于事务 ownership:构造失败仍须回滚、关闭并恢复外层上下文。
|
|
216
|
+
# 加入外层的调用不会走这里,因而复用外层的参与者和完成顺序。
|
|
217
|
+
work._lifecycles = tuple(
|
|
218
|
+
factory.create(container, context) for factory in self._lifecycles
|
|
219
|
+
)
|
|
186
220
|
yield work
|
|
187
221
|
except BaseException as error:
|
|
188
222
|
primary = error
|
|
@@ -208,3 +242,23 @@ class UnitOfWorkManager:
|
|
|
208
242
|
raise failure from (primary or work.cleanup_failures[0])
|
|
209
243
|
if primary is not None:
|
|
210
244
|
raise primary
|
|
245
|
+
|
|
246
|
+
async def _after_commit(self, work: UnitOfWork, container: AsyncContainer) -> None:
|
|
247
|
+
# 新服务调用既不能复用旧事务,也不能落入旧 ACTION 的对象缓存。
|
|
248
|
+
token = self._ambient.set(None)
|
|
249
|
+
parent = container.parent_container if container.scope is Scope.ACTION else container
|
|
250
|
+
assert parent is not None
|
|
251
|
+
failures: list[Exception] = []
|
|
252
|
+
try:
|
|
253
|
+
with self._runtime.detached_execution(parent):
|
|
254
|
+
for lifecycle in work._lifecycles:
|
|
255
|
+
try:
|
|
256
|
+
await lifecycle.after_commit()
|
|
257
|
+
except Exception as error:
|
|
258
|
+
failures.append(error)
|
|
259
|
+
finally:
|
|
260
|
+
self._ambient.reset(token)
|
|
261
|
+
if len(failures) == 1 and getattr(failures[0], "committed", False):
|
|
262
|
+
raise failures[0]
|
|
263
|
+
if failures:
|
|
264
|
+
raise UnitOfWorkCompletionError(tuple(failures)) from failures[0]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""UoW 默认实现、选项和完成参与者的组装 owner。"""
|
|
2
|
+
|
|
3
|
+
from dishka import DEFAULT_COMPONENT, AsyncContainer, DependencyKey, Provider, Scope
|
|
4
|
+
|
|
5
|
+
from ..application_services.invocation import _InvocationRuntime
|
|
6
|
+
from ..invocation.module import InvocationModule
|
|
7
|
+
from ..lifecycle import ConfigureContext
|
|
8
|
+
from ..modularity import AppModule
|
|
9
|
+
from ..options import Options
|
|
10
|
+
from ..services.composition import ServiceBindingContext, ServiceComposition
|
|
11
|
+
from ..services.registration import RegistrationDecision, RegistrationKind
|
|
12
|
+
from .contracts import UnitOfWork, _UnitOfWorkFactory
|
|
13
|
+
from .lifecycle import UnitOfWorkLifecycleFactory
|
|
14
|
+
from .manager import UnitOfWorkManager
|
|
15
|
+
from .options import UnitOfWorkOptions
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class UnitOfWorkModule(AppModule):
|
|
19
|
+
dependencies = (InvocationModule,)
|
|
20
|
+
|
|
21
|
+
def configure(self, context: ConfigureContext) -> None:
|
|
22
|
+
context.configure.default(UnitOfWorkOptions, section="unit_of_work")
|
|
23
|
+
provider = Provider()
|
|
24
|
+
provider.provide(_UnitOfWorkFactory, scope=Scope.APP)
|
|
25
|
+
context.services.contribute(
|
|
26
|
+
provider,
|
|
27
|
+
kind=RegistrationKind.DEFAULT,
|
|
28
|
+
reason="UnitOfWork without persistence",
|
|
29
|
+
)
|
|
30
|
+
context.services.add_composition(ServiceComposition(bind=self._bind))
|
|
31
|
+
|
|
32
|
+
def _bind(self, context: ServiceBindingContext) -> None:
|
|
33
|
+
provider = Provider()
|
|
34
|
+
# 完成参与者可由事件等能力通过正式 multi binding 贡献;未选能力时使用空集合。
|
|
35
|
+
if not any(
|
|
36
|
+
draft.key == DependencyKey(list[UnitOfWorkLifecycleFactory], DEFAULT_COMPONENT)
|
|
37
|
+
and draft.decision is RegistrationDecision.ACCEPTED
|
|
38
|
+
for draft in context.arbitration.drafts
|
|
39
|
+
):
|
|
40
|
+
provider.provide(
|
|
41
|
+
lambda: [],
|
|
42
|
+
provides=list[UnitOfWorkLifecycleFactory],
|
|
43
|
+
scope=Scope.APP,
|
|
44
|
+
)
|
|
45
|
+
provider.provide(_manager, provides=UnitOfWorkManager, scope=Scope.APP)
|
|
46
|
+
provider.provide(_current, provides=UnitOfWork, scope=Scope.ACTION, cache=False)
|
|
47
|
+
context.add_provider(provider)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _manager(
|
|
51
|
+
container: AsyncContainer,
|
|
52
|
+
runtime: _InvocationRuntime,
|
|
53
|
+
factory: _UnitOfWorkFactory,
|
|
54
|
+
options: Options[UnitOfWorkOptions],
|
|
55
|
+
lifecycles: list[UnitOfWorkLifecycleFactory],
|
|
56
|
+
) -> UnitOfWorkManager:
|
|
57
|
+
return UnitOfWorkManager(runtime, factory, options.value, tuple(lifecycles), container)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _current(manager: UnitOfWorkManager) -> UnitOfWork:
|
|
61
|
+
return manager.require_current()
|
|
@@ -9,9 +9,11 @@ from typing import TypeVar
|
|
|
9
9
|
|
|
10
10
|
from pydantic import ConfigDict, Field, field_validator
|
|
11
11
|
|
|
12
|
+
from ..modularity import ModuleDeclaration, ModuleRef
|
|
12
13
|
from ..options import BaseOptions
|
|
13
14
|
|
|
14
15
|
_UNIT_OF_WORK_ATTRIBUTE = "__python_ddd_framework_unit_of_work__"
|
|
16
|
+
_DEFAULT_CONNECTION_NAME = "default"
|
|
15
17
|
_T = TypeVar("_T")
|
|
16
18
|
|
|
17
19
|
|
|
@@ -40,12 +42,16 @@ class UnitOfWorkOptions(BaseOptions):
|
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
@dataclass(frozen=True, slots=True)
|
|
43
|
-
class UnitOfWorkDeclaration:
|
|
45
|
+
class UnitOfWorkDeclaration(ModuleDeclaration):
|
|
44
46
|
disabled: bool = False
|
|
45
47
|
is_transactional: bool | None = None
|
|
46
48
|
isolation_level: str | None = None
|
|
47
49
|
command_timeout_ms: int | None = None
|
|
48
50
|
|
|
51
|
+
@property
|
|
52
|
+
def required_module(self) -> ModuleRef:
|
|
53
|
+
return ModuleRef("python_ddd_framework.unit_of_work.module:UnitOfWorkModule")
|
|
54
|
+
|
|
49
55
|
def __post_init__(self) -> None:
|
|
50
56
|
if not isinstance(self.disabled, bool):
|
|
51
57
|
raise TypeError("disabled must be bool")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""框架内部的原生值校验边界;不拥有业务规则、调用管线或应用状态。"""
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Pydantic 实例重验边界;保留业务 schema,不拥有传输或应用状态。"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, TypeVar, cast
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, TypeAdapter
|
|
6
|
+
from pydantic_core import CoreSchema, SchemaValidator
|
|
7
|
+
|
|
8
|
+
_TModel = TypeVar("_TModel", bound=BaseModel)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _revalidation_schema(value: Any) -> Any:
|
|
12
|
+
# 只复制 schema 容器;业务 class、默认值与配置保持原身份且不被改写。
|
|
13
|
+
# 嵌套 model/dataclass 也必须重验,不能复用它们默认跳过实例校验的 validator。
|
|
14
|
+
if isinstance(value, dict):
|
|
15
|
+
# 仅 schema 节点的 metadata/config/default 是非结构值;同名业务字段仍须遍历。
|
|
16
|
+
preserved = ("default", "metadata", "config") if isinstance(value.get("type"), str) else ()
|
|
17
|
+
result = {
|
|
18
|
+
key: item if key in preserved else _revalidation_schema(item)
|
|
19
|
+
for key, item in value.items()
|
|
20
|
+
}
|
|
21
|
+
if result.get("type") in ("model", "dataclass"):
|
|
22
|
+
result["revalidate_instances"] = "always"
|
|
23
|
+
return result
|
|
24
|
+
if isinstance(value, list):
|
|
25
|
+
return [_revalidation_schema(item) for item in value]
|
|
26
|
+
return value
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _revalidate_model(model_type: type[_TModel], value: _TModel) -> _TModel:
|
|
30
|
+
adapter = TypeAdapter(model_type)
|
|
31
|
+
validator = SchemaValidator(
|
|
32
|
+
cast(CoreSchema, _revalidation_schema(adapter.core_schema)), _use_prebuilt=False
|
|
33
|
+
)
|
|
34
|
+
# 原生深复制隔离调用者的可变值;dataclass/Secret 等仍以真实实例形态接受重验。
|
|
35
|
+
# 序列化输出不是 strict Python 校验输入,不能先 dump 再重建模型。
|
|
36
|
+
snapshot = value.model_copy(deep=True)
|
|
37
|
+
return cast(_TModel, validator.validate_python(snapshot, strict=True, by_name=True))
|