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
|
@@ -9,66 +9,71 @@ from typing import cast
|
|
|
9
9
|
|
|
10
10
|
from dishka import AsyncContainer, Scope
|
|
11
11
|
|
|
12
|
-
from ..application_services.
|
|
13
|
-
|
|
12
|
+
from ..application_services.invocation import _InvocationRuntime
|
|
13
|
+
from ..authorization import PermissionCatalog
|
|
14
|
+
from ..extensions.catalog import ExtensionCatalog
|
|
15
|
+
from ..invocation.execution import _InterceptorPipeline
|
|
16
|
+
from ..invocation.interceptors import InterceptionStage, InterceptorRegistration, _InterceptorRule
|
|
17
|
+
from ..invocation.methods import (
|
|
18
|
+
ServiceMethod,
|
|
19
|
+
_compile_method,
|
|
20
|
+
_CompiledMethod,
|
|
14
21
|
_match_interceptors,
|
|
15
22
|
_method_source,
|
|
23
|
+
_policy_fingerprint,
|
|
16
24
|
)
|
|
17
|
-
from ..application_services.execution import _InterceptorPipeline
|
|
18
|
-
from ..application_services.interceptors import (
|
|
19
|
-
InterceptionStage,
|
|
20
|
-
InterceptorRegistration,
|
|
21
|
-
_InterceptorRule,
|
|
22
|
-
)
|
|
23
|
-
from ..application_services.invocation import _InvocationRuntime
|
|
24
|
-
from ..background_jobs import BackgroundJobCatalog
|
|
25
|
-
from ..background_workers import BackgroundWorkerCatalog
|
|
26
|
-
from ..events.catalog import LocalEventCatalog
|
|
27
|
-
from ..events.contracts import LocalEventPhase
|
|
28
25
|
from ..modularity import ModuleKey
|
|
29
26
|
from ..unit_of_work.options import _UNIT_OF_WORK_ATTRIBUTE, UnitOfWorkDeclaration, _declaration
|
|
30
27
|
from .callables import _call_in_scope
|
|
31
28
|
|
|
32
29
|
|
|
30
|
+
@dataclass(frozen=True, slots=True)
|
|
31
|
+
class InvocationEntry:
|
|
32
|
+
target: type[object] | Callable[..., Awaitable[None]]
|
|
33
|
+
method: str
|
|
34
|
+
owner: ModuleKey
|
|
35
|
+
business_parameters: int
|
|
36
|
+
outer_scope: Scope = Scope.REQUEST
|
|
37
|
+
method_scope: Scope = Scope.ACTION
|
|
38
|
+
unit_of_work_default: UnitOfWorkDeclaration | None = None
|
|
39
|
+
managed_method: bool = False
|
|
40
|
+
|
|
41
|
+
|
|
33
42
|
@dataclass(frozen=True, slots=True)
|
|
34
43
|
class _CompiledEntry:
|
|
35
|
-
descriptor:
|
|
44
|
+
descriptor: ServiceMethod
|
|
36
45
|
steps: Mapping[InterceptionStage, tuple[InterceptorRegistration, ...]]
|
|
37
46
|
outer_scope: Scope
|
|
38
47
|
method_scope: Scope
|
|
48
|
+
managed: _CompiledMethod | None = None
|
|
39
49
|
|
|
40
50
|
|
|
41
51
|
class _EntryInterceptionCatalog:
|
|
42
52
|
def __init__(
|
|
43
53
|
self,
|
|
44
|
-
|
|
54
|
+
declarations: tuple[InvocationEntry, ...],
|
|
45
55
|
rules: tuple[_InterceptorRule, ...],
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
implementations: Mapping[type[object], type[object]],
|
|
57
|
+
permission_catalog: PermissionCatalog | None,
|
|
58
|
+
extensions: ExtensionCatalog,
|
|
48
59
|
) -> None:
|
|
49
60
|
entries: dict[tuple[object, str], _CompiledEntry] = {}
|
|
50
|
-
for
|
|
61
|
+
for declaration in declarations:
|
|
51
62
|
self._add(
|
|
52
63
|
entries,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
declaration.target,
|
|
65
|
+
declaration.method,
|
|
66
|
+
declaration.owner,
|
|
56
67
|
rules,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"handle",
|
|
67
|
-
subscription.owner,
|
|
68
|
-
rules,
|
|
69
|
-
False,
|
|
70
|
-
outer_scope=scope,
|
|
71
|
-
method_scope=scope,
|
|
68
|
+
declaration.unit_of_work_default,
|
|
69
|
+
implementation=implementations.get(declaration.target)
|
|
70
|
+
if isinstance(declaration.target, type)
|
|
71
|
+
else None,
|
|
72
|
+
outer_scope=declaration.outer_scope,
|
|
73
|
+
method_scope=declaration.method_scope,
|
|
74
|
+
managed_method=declaration.managed_method,
|
|
75
|
+
permission_catalog=permission_catalog,
|
|
76
|
+
extensions=extensions,
|
|
72
77
|
)
|
|
73
78
|
self._entries: Mapping[tuple[object, str], _CompiledEntry] = MappingProxyType(entries)
|
|
74
79
|
|
|
@@ -92,13 +97,19 @@ class _EntryInterceptionCatalog:
|
|
|
92
97
|
name: str,
|
|
93
98
|
owner: ModuleKey,
|
|
94
99
|
rules: tuple[_InterceptorRule, ...],
|
|
95
|
-
|
|
100
|
+
unit_of_work_default: UnitOfWorkDeclaration | None,
|
|
96
101
|
*,
|
|
102
|
+
implementation: type[object] | None,
|
|
97
103
|
outer_scope: Scope = Scope.REQUEST,
|
|
98
104
|
method_scope: Scope = Scope.ACTION,
|
|
105
|
+
managed_method: bool = False,
|
|
106
|
+
permission_catalog: PermissionCatalog | None = None,
|
|
107
|
+
extensions: ExtensionCatalog,
|
|
99
108
|
) -> None:
|
|
100
109
|
if (target, name) in entries:
|
|
101
110
|
current = entries[target, name]
|
|
111
|
+
if (current.managed is not None) != managed_method:
|
|
112
|
+
raise TypeError("The same entry cannot mix managed and lifecycle-only policies")
|
|
102
113
|
# 同一 handler 可订阅两个 phase;构建时使用所有实际入口的最窄解析边界。
|
|
103
114
|
entries[target, name] = replace(
|
|
104
115
|
current,
|
|
@@ -106,15 +117,38 @@ class _EntryInterceptionCatalog:
|
|
|
106
117
|
method_scope=min(current.method_scope, method_scope),
|
|
107
118
|
)
|
|
108
119
|
return
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
implementation = implementation or (target if isinstance(target, type) else type(target))
|
|
121
|
+
if managed_method:
|
|
122
|
+
if not isinstance(target, type):
|
|
123
|
+
raise TypeError("Managed entry requires a class binding")
|
|
124
|
+
compiled = _compile_method(
|
|
125
|
+
implementation,
|
|
126
|
+
implementation,
|
|
127
|
+
name,
|
|
128
|
+
owner,
|
|
129
|
+
owner,
|
|
130
|
+
permission_catalog,
|
|
131
|
+
rules,
|
|
132
|
+
auditing_default=False,
|
|
133
|
+
unit_of_work_default=unit_of_work_default or UnitOfWorkDeclaration(),
|
|
134
|
+
extensions=extensions,
|
|
135
|
+
)
|
|
136
|
+
entries[target, name] = _CompiledEntry(
|
|
137
|
+
compiled.descriptor,
|
|
138
|
+
compiled.interceptor_steps,
|
|
139
|
+
outer_scope,
|
|
140
|
+
method_scope,
|
|
141
|
+
compiled,
|
|
142
|
+
)
|
|
143
|
+
return
|
|
144
|
+
function = getattr(implementation, name) if isinstance(target, type) else target
|
|
111
145
|
declaration = _declaration(implementation, function)
|
|
112
|
-
if
|
|
146
|
+
if unit_of_work_default is not None and not any(
|
|
113
147
|
hasattr(item, _UNIT_OF_WORK_ATTRIBUTE) for item in (implementation, function)
|
|
114
148
|
):
|
|
115
|
-
declaration =
|
|
149
|
+
declaration = unit_of_work_default
|
|
116
150
|
source = _method_source(function, owner)
|
|
117
|
-
descriptor =
|
|
151
|
+
descriptor = ServiceMethod(
|
|
118
152
|
name=name,
|
|
119
153
|
source=source,
|
|
120
154
|
requires_authenticated_user=False,
|
|
@@ -152,6 +186,10 @@ class _EntryInterceptionCatalog:
|
|
|
152
186
|
connection_name: str | None = None,
|
|
153
187
|
) -> object:
|
|
154
188
|
entry = self._entries[target, name]
|
|
189
|
+
if entry.managed is not None:
|
|
190
|
+
return await _invoke_managed_entry(
|
|
191
|
+
entry.managed, runtime, container, target, args, connection_name
|
|
192
|
+
)
|
|
155
193
|
pipeline = _InterceptorPipeline(entry.descriptor, entry.steps, args, {})
|
|
156
194
|
|
|
157
195
|
async def call(scope: AsyncContainer) -> object:
|
|
@@ -191,3 +229,36 @@ class _EntryInterceptionCatalog:
|
|
|
191
229
|
return await pipeline.run(stages[index], container, lambda: run(index + 1))
|
|
192
230
|
|
|
193
231
|
return await run(0)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
async def _invoke_managed_entry(
|
|
235
|
+
method: _CompiledMethod,
|
|
236
|
+
runtime: _InvocationRuntime,
|
|
237
|
+
container: AsyncContainer,
|
|
238
|
+
target: object,
|
|
239
|
+
args: tuple[object, ...],
|
|
240
|
+
connection_name: str | None,
|
|
241
|
+
) -> object:
|
|
242
|
+
from .dispatcher import _MethodDispatcher
|
|
243
|
+
|
|
244
|
+
implementation = method.descriptor.implementation
|
|
245
|
+
if (
|
|
246
|
+
getattr(implementation, method.name) is not method.implementation_method
|
|
247
|
+
or _policy_fingerprint(implementation, method.implementation_method)
|
|
248
|
+
!= method.policy_fingerprint
|
|
249
|
+
):
|
|
250
|
+
raise TypeError("Managed entry declaration changed after compilation")
|
|
251
|
+
if connection_name is None:
|
|
252
|
+
raise ValueError("Managed entry requires an execution connection")
|
|
253
|
+
|
|
254
|
+
async def invoke_target(
|
|
255
|
+
scope: AsyncContainer, arguments: tuple[object, ...], kwargs: dict[str, object]
|
|
256
|
+
) -> object:
|
|
257
|
+
instance = await scope.get(cast(type[object], target))
|
|
258
|
+
return await cast(Callable[..., Awaitable[object]], getattr(instance, method.name))(
|
|
259
|
+
*arguments, **kwargs
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
return await _MethodDispatcher(container, runtime).invoke_method(
|
|
263
|
+
method, args, {}, invoke_target, connection_name
|
|
264
|
+
)
|
|
@@ -12,7 +12,7 @@ from ..diagnostics import SourceLocation
|
|
|
12
12
|
from ..modularity import ModuleKey
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
-
from .
|
|
15
|
+
from ..invocation.methods import ServiceMethod
|
|
16
16
|
from .execution import MethodInvocation
|
|
17
17
|
|
|
18
18
|
|
|
@@ -41,4 +41,4 @@ class InterceptorRegistration:
|
|
|
41
41
|
@dataclass(frozen=True, slots=True)
|
|
42
42
|
class _InterceptorRule:
|
|
43
43
|
registration: InterceptorRegistration
|
|
44
|
-
match: Callable[[
|
|
44
|
+
match: Callable[[ServiceMethod], bool]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""最终普通 DI 绑定的方法解释、同 scope 原生装饰与图证明。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from functools import wraps
|
|
7
|
+
|
|
8
|
+
from dishka import AsyncContainer, Scope
|
|
9
|
+
|
|
10
|
+
from ..application_services.invocation import _InvocationRuntime, _managed_request
|
|
11
|
+
from ..unit_of_work.options import (
|
|
12
|
+
_DEFAULT_CONNECTION_NAME,
|
|
13
|
+
)
|
|
14
|
+
from .contracts import ApplicationInvocationContext
|
|
15
|
+
from .dispatcher import _MethodDispatcher
|
|
16
|
+
from .methods import (
|
|
17
|
+
_CompiledMethod,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class _ManagedServiceProxy:
|
|
22
|
+
"""只转交被标记的方法;实例、未标记成员和 finalizer 仍属于原生绑定。"""
|
|
23
|
+
|
|
24
|
+
__slots__ = (
|
|
25
|
+
"__auditing",
|
|
26
|
+
"__container",
|
|
27
|
+
"__instance",
|
|
28
|
+
"__methods",
|
|
29
|
+
"__runtime",
|
|
30
|
+
"__unit_of_work",
|
|
31
|
+
)
|
|
32
|
+
__instance: object
|
|
33
|
+
__container: AsyncContainer
|
|
34
|
+
__runtime: _InvocationRuntime
|
|
35
|
+
__methods: Mapping[str, _CompiledMethod]
|
|
36
|
+
__unit_of_work: bool
|
|
37
|
+
__auditing: bool
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
instance: object,
|
|
42
|
+
container: AsyncContainer,
|
|
43
|
+
runtime: _InvocationRuntime,
|
|
44
|
+
methods: Mapping[str, _CompiledMethod],
|
|
45
|
+
*,
|
|
46
|
+
unit_of_work: bool,
|
|
47
|
+
auditing: bool,
|
|
48
|
+
) -> None:
|
|
49
|
+
# 仅初始化代理自身;之后业务属性写入必须与方法使用同一个原生实例。
|
|
50
|
+
object.__setattr__(self, "_ManagedServiceProxy__instance", instance)
|
|
51
|
+
object.__setattr__(self, "_ManagedServiceProxy__container", container)
|
|
52
|
+
object.__setattr__(self, "_ManagedServiceProxy__runtime", runtime)
|
|
53
|
+
object.__setattr__(self, "_ManagedServiceProxy__methods", methods)
|
|
54
|
+
object.__setattr__(self, "_ManagedServiceProxy__unit_of_work", unit_of_work)
|
|
55
|
+
object.__setattr__(self, "_ManagedServiceProxy__auditing", auditing)
|
|
56
|
+
|
|
57
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
58
|
+
setattr(self.__instance, name, value)
|
|
59
|
+
|
|
60
|
+
def __delattr__(self, name: str) -> None:
|
|
61
|
+
delattr(self.__instance, name)
|
|
62
|
+
|
|
63
|
+
def __getattr__(self, name: str) -> object:
|
|
64
|
+
method = self.__methods.get(name)
|
|
65
|
+
if method is None:
|
|
66
|
+
return getattr(self.__instance, name)
|
|
67
|
+
bound = getattr(self.__instance, name)
|
|
68
|
+
|
|
69
|
+
@wraps(bound)
|
|
70
|
+
async def invoke(*args: object, **kwargs: object) -> object:
|
|
71
|
+
async def target(
|
|
72
|
+
_: AsyncContainer, args: tuple[object, ...], kwargs: dict[str, object]
|
|
73
|
+
) -> object:
|
|
74
|
+
return await method.implementation_method(self.__instance, *args, **kwargs)
|
|
75
|
+
|
|
76
|
+
runtime = self.__runtime
|
|
77
|
+
async with runtime.outer_lease():
|
|
78
|
+
ambient = runtime.current()
|
|
79
|
+
current = (
|
|
80
|
+
ambient.action_container
|
|
81
|
+
if ambient is not None
|
|
82
|
+
else runtime.current_call_scope()
|
|
83
|
+
)
|
|
84
|
+
connection = (
|
|
85
|
+
ambient.context.connection_name
|
|
86
|
+
if ambient is not None
|
|
87
|
+
else _DEFAULT_CONNECTION_NAME
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
async def call(container: AsyncContainer) -> object:
|
|
91
|
+
action_connection = (
|
|
92
|
+
(await container.get(ApplicationInvocationContext)).connection_name
|
|
93
|
+
if container.scope is Scope.ACTION
|
|
94
|
+
else connection
|
|
95
|
+
)
|
|
96
|
+
dispatcher = _MethodDispatcher(
|
|
97
|
+
container,
|
|
98
|
+
runtime,
|
|
99
|
+
unit_of_work_enabled=self.__unit_of_work,
|
|
100
|
+
auditing_enabled=self.__auditing,
|
|
101
|
+
)
|
|
102
|
+
return await dispatcher.invoke_method(
|
|
103
|
+
method, args, kwargs, target, action_connection
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if current is not None:
|
|
107
|
+
return await call(current)
|
|
108
|
+
if self.__container.scope is not Scope.APP:
|
|
109
|
+
with runtime.call_scope(self.__container):
|
|
110
|
+
return await call(self.__container)
|
|
111
|
+
# APP 对象不捕获某次 REQUEST;每次独立调用创建并排空自己的 scope。
|
|
112
|
+
async with _managed_request(self.__container) as request:
|
|
113
|
+
with runtime.call_scope(request):
|
|
114
|
+
return await call(request)
|
|
115
|
+
|
|
116
|
+
return invoke
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""最终普通 DI 绑定的方法解释、同 scope 原生装饰与图证明。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from types import MappingProxyType
|
|
8
|
+
from typing import ParamSpec, TypeVar, TypeVarTuple, get_origin
|
|
9
|
+
|
|
10
|
+
from dishka import AsyncContainer, DependencyKey, Provider, Scope
|
|
11
|
+
from dishka.dependency_source import Factory
|
|
12
|
+
from dishka.entities.factory_type import FactoryType
|
|
13
|
+
|
|
14
|
+
from ..application_services.contracts import ApplicationService, ApplicationServiceContract
|
|
15
|
+
from ..application_services.errors import ApplicationServiceDefinitionError
|
|
16
|
+
from ..application_services.invocation import _InvocationRuntime
|
|
17
|
+
from ..authorization.contracts import _AUTHORIZATION_ATTRIBUTE
|
|
18
|
+
from ..extensions.module import ExtensionsModule
|
|
19
|
+
from ..invocation.interceptors import InterceptionStage, _InterceptorRule
|
|
20
|
+
from ..modularity.discovery import _require_type_dependencies
|
|
21
|
+
from ..services.composition import ServiceBindingContext, ServiceValidationContext
|
|
22
|
+
from ..services.registration import RegistrationDecision
|
|
23
|
+
from ..unit_of_work.options import (
|
|
24
|
+
_UNIT_OF_WORK_ATTRIBUTE,
|
|
25
|
+
UnitOfWorkDeclaration,
|
|
26
|
+
)
|
|
27
|
+
from .managed_proxy import _ManagedServiceProxy
|
|
28
|
+
from .methods import (
|
|
29
|
+
_compile_method,
|
|
30
|
+
_CompiledMethod,
|
|
31
|
+
_policy_fingerprint,
|
|
32
|
+
_public_method_names,
|
|
33
|
+
_require_async_instance_method,
|
|
34
|
+
)
|
|
35
|
+
from .policies import ValidationEnabled
|
|
36
|
+
from .validation import _validate_interceptor_scopes
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _managed_names(candidate: type[object]) -> tuple[str, ...]:
|
|
40
|
+
attributes = (_UNIT_OF_WORK_ATTRIBUTE, _AUTHORIZATION_ATTRIBUTE)
|
|
41
|
+
class_marked = issubclass(candidate, ValidationEnabled) or any(
|
|
42
|
+
hasattr(candidate, attribute) for attribute in attributes
|
|
43
|
+
)
|
|
44
|
+
names: list[str] = []
|
|
45
|
+
# 类级标记覆盖公开方法;显式方法标记即使位于非公开方法也必须接受形态校验。
|
|
46
|
+
members = {
|
|
47
|
+
name: value for base in reversed(candidate.__mro__) for name, value in vars(base).items()
|
|
48
|
+
}
|
|
49
|
+
public = _public_method_names(candidate, is_contract=False)
|
|
50
|
+
for name, value in members.items():
|
|
51
|
+
targets = (
|
|
52
|
+
(value, value.__func__) if isinstance(value, (staticmethod, classmethod)) else (value,)
|
|
53
|
+
)
|
|
54
|
+
marked = any(hasattr(target, attribute) for target in targets for attribute in attributes)
|
|
55
|
+
if marked or (class_marked and name in public):
|
|
56
|
+
_require_async_instance_method(candidate, name, role="managed service")
|
|
57
|
+
names.append(name)
|
|
58
|
+
return tuple(names)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _assert_methods(candidate: type[object], methods: Mapping[str, _CompiledMethod]) -> None:
|
|
62
|
+
if tuple(methods) != _managed_names(candidate) or any(
|
|
63
|
+
getattr(candidate, name) is not method.implementation_method
|
|
64
|
+
or _policy_fingerprint(candidate, method.implementation_method) != method.policy_fingerprint
|
|
65
|
+
for name, method in methods.items()
|
|
66
|
+
):
|
|
67
|
+
raise ApplicationServiceDefinitionError(
|
|
68
|
+
symbol=candidate.__qualname__,
|
|
69
|
+
reason="managed method declaration differs from the compiled binding",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True, slots=True)
|
|
74
|
+
class _ManagedBinding:
|
|
75
|
+
key: DependencyKey
|
|
76
|
+
factory: Factory
|
|
77
|
+
implementation: type[object]
|
|
78
|
+
methods: Mapping[str, _CompiledMethod]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class _ManagedServices:
|
|
82
|
+
def __init__(self) -> None:
|
|
83
|
+
self._bindings: list[tuple[_ManagedBinding, object]] = []
|
|
84
|
+
|
|
85
|
+
def decorate(
|
|
86
|
+
self,
|
|
87
|
+
context: ServiceBindingContext,
|
|
88
|
+
runtime: _InvocationRuntime,
|
|
89
|
+
rules: tuple[_InterceptorRule, ...],
|
|
90
|
+
entry_targets: set[object],
|
|
91
|
+
) -> None:
|
|
92
|
+
from ..auditing.module import AuditingModule
|
|
93
|
+
from ..authorization.module import AuthorizationModule
|
|
94
|
+
from ..unit_of_work.module import UnitOfWorkModule
|
|
95
|
+
|
|
96
|
+
modules = {descriptor.module_type for descriptor in context.modules.dependency_order}
|
|
97
|
+
permissions = (
|
|
98
|
+
context.modules.instance(AuthorizationModule).catalog
|
|
99
|
+
if AuthorizationModule in modules
|
|
100
|
+
else None
|
|
101
|
+
)
|
|
102
|
+
capabilities = dict(
|
|
103
|
+
unit_of_work=UnitOfWorkModule in modules, auditing=AuditingModule in modules
|
|
104
|
+
)
|
|
105
|
+
# 来源 owner 来自已仲裁贡献或派生记录,不另建服务类型清单。
|
|
106
|
+
contributions = [
|
|
107
|
+
(draft.key, draft.contribution.owner, draft.contribution.source, draft.factory)
|
|
108
|
+
for draft in context.arbitration.drafts
|
|
109
|
+
if draft.factory is not None and draft.decision is RegistrationDecision.ACCEPTED
|
|
110
|
+
]
|
|
111
|
+
for descriptor, inspector, provider, _ in context._provider_checks:
|
|
112
|
+
for item in inspector.inspect(provider).factories:
|
|
113
|
+
contributions.append(
|
|
114
|
+
(item.key, descriptor.key, descriptor.declaration_source, item.factory)
|
|
115
|
+
)
|
|
116
|
+
grouped: dict[DependencyKey, list[_ManagedBinding]] = {}
|
|
117
|
+
# 只消费仲裁胜出的 factory,原生 multi 的每项仍保留自己的最终实现事实。
|
|
118
|
+
for key, owner, source, factory in contributions:
|
|
119
|
+
factory_type = get_origin(factory.source) or factory.source
|
|
120
|
+
candidate = factory_type if isinstance(factory_type, type) else key.type_hint
|
|
121
|
+
candidate = get_origin(candidate) or candidate
|
|
122
|
+
if (
|
|
123
|
+
factory.type is FactoryType.ALIAS
|
|
124
|
+
or not isinstance(candidate, type)
|
|
125
|
+
or issubclass(candidate, ApplicationService)
|
|
126
|
+
or ApplicationServiceContract in candidate.__mro__
|
|
127
|
+
or candidate in entry_targets
|
|
128
|
+
):
|
|
129
|
+
continue
|
|
130
|
+
names = _managed_names(candidate)
|
|
131
|
+
if names:
|
|
132
|
+
_require_type_dependencies(context.modules, owner, candidate, source=source)
|
|
133
|
+
service = key.type_hint if isinstance(key.type_hint, type) else candidate
|
|
134
|
+
methods = MappingProxyType(
|
|
135
|
+
{
|
|
136
|
+
name: _compile_method(
|
|
137
|
+
service if hasattr(service, name) else candidate,
|
|
138
|
+
candidate,
|
|
139
|
+
name,
|
|
140
|
+
owner,
|
|
141
|
+
owner,
|
|
142
|
+
permissions,
|
|
143
|
+
rules,
|
|
144
|
+
validation_default=issubclass(candidate, ValidationEnabled),
|
|
145
|
+
auditing_default=False,
|
|
146
|
+
unit_of_work_default=UnitOfWorkDeclaration(disabled=True),
|
|
147
|
+
extensions=context.modules.instance(ExtensionsModule).catalog,
|
|
148
|
+
)
|
|
149
|
+
for name in names
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
# 闭合 key 不能使仍含类型参数的方法悄悄退化成 Any 验证。
|
|
153
|
+
for method in methods.values():
|
|
154
|
+
annotations = (
|
|
155
|
+
*(parameter.annotation for parameter in method.signature.parameters.values()),
|
|
156
|
+
method.signature.return_annotation,
|
|
157
|
+
)
|
|
158
|
+
if any(
|
|
159
|
+
isinstance(annotation, (TypeVar, ParamSpec, TypeVarTuple))
|
|
160
|
+
or getattr(annotation, "__parameters__", ())
|
|
161
|
+
for annotation in annotations
|
|
162
|
+
):
|
|
163
|
+
raise ApplicationServiceDefinitionError(
|
|
164
|
+
symbol=f"{candidate.__qualname__}.{method.name}",
|
|
165
|
+
reason="managed methods require concrete annotations",
|
|
166
|
+
)
|
|
167
|
+
grouped.setdefault(key, []).append(_ManagedBinding(key, factory, candidate, methods))
|
|
168
|
+
for key, bindings in grouped.items():
|
|
169
|
+
if not any(
|
|
170
|
+
binding.methods
|
|
171
|
+
or binding.factory.type is FactoryType.CONTEXT
|
|
172
|
+
or not isinstance(
|
|
173
|
+
get_origin(binding.factory.source) or binding.factory.source, type
|
|
174
|
+
)
|
|
175
|
+
for binding in bindings
|
|
176
|
+
):
|
|
177
|
+
continue
|
|
178
|
+
decorator = self._decorator(key, runtime, tuple(bindings), **capabilities)
|
|
179
|
+
decorated = Provider(component=key.component)
|
|
180
|
+
decorated.decorate(decorator)
|
|
181
|
+
context._add_method_decorator(decorated)
|
|
182
|
+
self._bindings.extend((binding, decorator) for binding in bindings)
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def _decorator(
|
|
186
|
+
key: DependencyKey,
|
|
187
|
+
runtime: _InvocationRuntime,
|
|
188
|
+
bindings: tuple[_ManagedBinding, ...],
|
|
189
|
+
*,
|
|
190
|
+
unit_of_work: bool,
|
|
191
|
+
auditing: bool,
|
|
192
|
+
) -> Callable[..., object]:
|
|
193
|
+
def decorate(instance: object, container: AsyncContainer) -> object:
|
|
194
|
+
# 外部 Provider 的实际返回对象必须符合已编译策略。
|
|
195
|
+
if not any(binding.methods for binding in bindings):
|
|
196
|
+
if isinstance(instance, ApplicationService) or _managed_names(type(instance)):
|
|
197
|
+
raise ApplicationServiceDefinitionError(
|
|
198
|
+
symbol=str(key), reason="factory result has uncompiled managed policies"
|
|
199
|
+
)
|
|
200
|
+
return instance
|
|
201
|
+
binding = next(
|
|
202
|
+
(item for item in bindings if type(instance) is item.implementation), None
|
|
203
|
+
)
|
|
204
|
+
if binding is None:
|
|
205
|
+
raise ApplicationServiceDefinitionError(
|
|
206
|
+
symbol=str(key), reason="factory result differs from the compiled binding"
|
|
207
|
+
)
|
|
208
|
+
methods = binding.methods
|
|
209
|
+
_assert_methods(type(instance), methods)
|
|
210
|
+
if not methods:
|
|
211
|
+
return instance
|
|
212
|
+
return _ManagedServiceProxy(
|
|
213
|
+
instance, container, runtime, methods, unit_of_work=unit_of_work, auditing=auditing
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# 原生 decorate 用注解识别被装饰 key,依赖顺序是 instance -> 当前 scope 容器。
|
|
217
|
+
decorate.__annotations__ = {
|
|
218
|
+
"instance": key.type_hint,
|
|
219
|
+
"container": AsyncContainer,
|
|
220
|
+
"return": key.type_hint,
|
|
221
|
+
}
|
|
222
|
+
return decorate
|
|
223
|
+
|
|
224
|
+
def validate(self, context: ServiceValidationContext) -> None:
|
|
225
|
+
graph = context.native_graph
|
|
226
|
+
for binding, decorator in self._bindings:
|
|
227
|
+
_assert_methods(binding.implementation, binding.methods)
|
|
228
|
+
# collect 可移动 key.depth;真实装饰和底层 factory 身份仍须一一对应。
|
|
229
|
+
if not any(
|
|
230
|
+
row.factory.source is decorator
|
|
231
|
+
and row.scope is binding.factory.scope
|
|
232
|
+
and row.factory.cache == binding.factory.cache
|
|
233
|
+
and graph.effective_factory(row).source is binding.factory.source
|
|
234
|
+
for row in graph
|
|
235
|
+
):
|
|
236
|
+
raise ApplicationServiceDefinitionError(
|
|
237
|
+
symbol=str(binding.key),
|
|
238
|
+
reason="managed decorator differs from final native binding",
|
|
239
|
+
)
|
|
240
|
+
_validate_interceptor_scopes(
|
|
241
|
+
graph,
|
|
242
|
+
(
|
|
243
|
+
(
|
|
244
|
+
rule,
|
|
245
|
+
Scope.ACTION if rule.before is InterceptionStage.METHOD else Scope.REQUEST,
|
|
246
|
+
)
|
|
247
|
+
for method in binding.methods.values()
|
|
248
|
+
for rule in method.descriptor.interceptors
|
|
249
|
+
),
|
|
250
|
+
)
|