python-ddd-framework 0.3.2__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +96 -291
- 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 +126 -7
- 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/module.py +107 -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 +38 -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 +41 -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/{integration.py.jinja → hosted_services/order_integration_service.py.jinja} +8 -2
- 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 +16 -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 +40 -10
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +134 -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 +20 -1
- python_ddd_framework/hosted_services/catalog.py +6 -16
- python_ddd_framework/hosted_services/contracts.py +4 -0
- 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 +30 -1
- 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 +33 -0
- python_ddd_framework/messaging/__init__.py +25 -0
- python_ddd_framework/messaging/channel.py +205 -0
- python_ddd_framework/messaging/contracts.py +69 -0
- python_ddd_framework/messaging/module.py +101 -0
- python_ddd_framework/messaging/options.py +13 -0
- python_ddd_framework/messaging/receipt.py +27 -0
- python_ddd_framework/messaging/runtime.py +124 -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.4.0.dist-info}/METADATA +139 -44
- python_ddd_framework-0.4.0.dist-info/RECORD +488 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.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/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.4.0.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""受管理异步方法的共享描述、策略解释与参数编译。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
7
|
+
from dataclasses import dataclass, field, replace
|
|
8
|
+
from types import MappingProxyType
|
|
9
|
+
from typing import Any, cast, get_type_hints
|
|
10
|
+
|
|
11
|
+
from pydantic import ConfigDict, TypeAdapter, validate_call
|
|
12
|
+
|
|
13
|
+
from ..application_services.errors import (
|
|
14
|
+
ApplicationServiceDefinitionError,
|
|
15
|
+
)
|
|
16
|
+
from ..auditing.control import _AUDITING_ATTRIBUTE
|
|
17
|
+
from ..authorization import PermissionCatalog, PermissionName
|
|
18
|
+
from ..authorization.contracts import (
|
|
19
|
+
_ALLOW_ANONYMOUS_ATTRIBUTE,
|
|
20
|
+
_AUTHORIZATION_ATTRIBUTE,
|
|
21
|
+
_authorization_requirement,
|
|
22
|
+
_AuthorizationRequirement,
|
|
23
|
+
)
|
|
24
|
+
from ..authorization.errors import AuthorizationDefinitionError
|
|
25
|
+
from ..diagnostics import SourceLocation
|
|
26
|
+
from ..extensions.catalog import ExtensionCatalog
|
|
27
|
+
from ..invocation.interceptors import InterceptionStage, InterceptorRegistration, _InterceptorRule
|
|
28
|
+
from ..invocation.signature import _business_signature
|
|
29
|
+
from ..modularity import ModuleKey
|
|
30
|
+
from ..unit_of_work.options import _UNIT_OF_WORK_ATTRIBUTE, UnitOfWorkDeclaration, _declaration
|
|
31
|
+
from .policies import _VALIDATION_ATTRIBUTE, ValidationEnabled, _effective_switch
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True, slots=True)
|
|
35
|
+
class ServiceMethod:
|
|
36
|
+
name: str
|
|
37
|
+
source: SourceLocation
|
|
38
|
+
requires_authenticated_user: bool
|
|
39
|
+
permissions: tuple[PermissionName, ...]
|
|
40
|
+
allows_anonymous: bool
|
|
41
|
+
service: type[object]
|
|
42
|
+
implementation: type[object]
|
|
43
|
+
method: Callable[..., Awaitable[object]]
|
|
44
|
+
validation_enabled: bool
|
|
45
|
+
auditing_enabled: bool
|
|
46
|
+
policy_sources: tuple[SourceLocation, ...]
|
|
47
|
+
interceptors: tuple[InterceptorRegistration, ...] = ()
|
|
48
|
+
unit_of_work: UnitOfWorkDeclaration = field(default_factory=UnitOfWorkDeclaration)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
_ValidatedArguments = tuple[tuple[object, ...], dict[str, object]]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True, slots=True)
|
|
55
|
+
class _CompiledMethod:
|
|
56
|
+
name: str
|
|
57
|
+
contract_method: Callable[..., Awaitable[object]]
|
|
58
|
+
implementation_method: Callable[..., Awaitable[object]]
|
|
59
|
+
validator: Callable[..., Awaitable[_ValidatedArguments]]
|
|
60
|
+
parameter_names: frozenset[str]
|
|
61
|
+
authorization: _AuthorizationRequirement
|
|
62
|
+
descriptor: ServiceMethod
|
|
63
|
+
signature: inspect.Signature
|
|
64
|
+
implementation_signature: inspect.Signature
|
|
65
|
+
interceptor_steps: Mapping[InterceptionStage, tuple[InterceptorRegistration, ...]]
|
|
66
|
+
policy_fingerprint: tuple[object, ...]
|
|
67
|
+
result_adapter: TypeAdapter[object] | None = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _compile_method(
|
|
71
|
+
contract: type[object],
|
|
72
|
+
implementation: type[object],
|
|
73
|
+
name: str,
|
|
74
|
+
owner: ModuleKey,
|
|
75
|
+
implementation_owner: ModuleKey,
|
|
76
|
+
permission_catalog: PermissionCatalog | None,
|
|
77
|
+
interceptor_rules: tuple[_InterceptorRule, ...],
|
|
78
|
+
*,
|
|
79
|
+
validation_default: bool = True,
|
|
80
|
+
auditing_default: bool = True,
|
|
81
|
+
unit_of_work_default: UnitOfWorkDeclaration,
|
|
82
|
+
extensions: ExtensionCatalog,
|
|
83
|
+
) -> _CompiledMethod:
|
|
84
|
+
implementation_symbol = f"{implementation.__module__}.{implementation.__qualname__}"
|
|
85
|
+
contract_method = _require_async_instance_method(contract, name, role="contract")
|
|
86
|
+
implementation_method = _require_async_instance_method(
|
|
87
|
+
implementation,
|
|
88
|
+
name,
|
|
89
|
+
role="implementation",
|
|
90
|
+
)
|
|
91
|
+
contract_signature, annotations = _resolved_signature(contract_method)
|
|
92
|
+
implementation_signature, _ = _resolved_signature(implementation_method)
|
|
93
|
+
if contract_signature.return_annotation is inspect.Signature.empty:
|
|
94
|
+
raise ApplicationServiceDefinitionError(
|
|
95
|
+
symbol=f"{implementation_symbol}.{name}",
|
|
96
|
+
reason="public service methods must declare a return annotation",
|
|
97
|
+
)
|
|
98
|
+
if _business_signature(contract_signature) != _business_signature(implementation_signature):
|
|
99
|
+
raise ApplicationServiceDefinitionError(
|
|
100
|
+
symbol=f"{implementation_symbol}.{name}",
|
|
101
|
+
reason="implementation signature must exactly match its public contract",
|
|
102
|
+
)
|
|
103
|
+
declared_return = contract_signature.return_annotation
|
|
104
|
+
annotations = {name: extensions.annotation(value) for name, value in annotations.items()}
|
|
105
|
+
contract_signature = contract_signature.replace(
|
|
106
|
+
parameters=tuple(
|
|
107
|
+
parameter.replace(annotation=extensions.annotation(parameter.annotation))
|
|
108
|
+
for parameter in contract_signature.parameters.values()
|
|
109
|
+
),
|
|
110
|
+
return_annotation=extensions.annotation(declared_return),
|
|
111
|
+
)
|
|
112
|
+
try:
|
|
113
|
+
validator = _compile_argument_validator(
|
|
114
|
+
contract_method,
|
|
115
|
+
contract_signature,
|
|
116
|
+
annotations,
|
|
117
|
+
)
|
|
118
|
+
except Exception as original_error:
|
|
119
|
+
raise ApplicationServiceDefinitionError(
|
|
120
|
+
symbol=f"{contract.__module__}.{contract.__qualname__}.{name}",
|
|
121
|
+
reason=f"Pydantic could not compile the method: {type(original_error).__name__}",
|
|
122
|
+
) from None
|
|
123
|
+
source = _method_source(contract_method, owner)
|
|
124
|
+
authorization = _authorization_requirement(implementation, implementation_method)
|
|
125
|
+
unknown_permissions = tuple(
|
|
126
|
+
permission
|
|
127
|
+
for permission in authorization.permissions
|
|
128
|
+
if permission_catalog is None or permission not in permission_catalog
|
|
129
|
+
)
|
|
130
|
+
if unknown_permissions:
|
|
131
|
+
raise AuthorizationDefinitionError(
|
|
132
|
+
symbol=f"{contract.__module__}.{contract.__qualname__}.{name}",
|
|
133
|
+
reason=(
|
|
134
|
+
"authorization references unknown permissions: "
|
|
135
|
+
f"{tuple(str(item) for item in unknown_permissions)!r}"
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
descriptor = ServiceMethod(
|
|
139
|
+
name=name,
|
|
140
|
+
source=source,
|
|
141
|
+
requires_authenticated_user=authorization.requires_authenticated_user,
|
|
142
|
+
permissions=authorization.permissions,
|
|
143
|
+
allows_anonymous=authorization.allows_anonymous,
|
|
144
|
+
service=contract,
|
|
145
|
+
implementation=implementation,
|
|
146
|
+
method=implementation_method,
|
|
147
|
+
unit_of_work=_declaration(implementation, implementation_method)
|
|
148
|
+
if any(
|
|
149
|
+
hasattr(item, _UNIT_OF_WORK_ATTRIBUTE)
|
|
150
|
+
for item in (implementation, implementation_method)
|
|
151
|
+
)
|
|
152
|
+
else unit_of_work_default,
|
|
153
|
+
validation_enabled=_effective_switch(
|
|
154
|
+
implementation, implementation_method, _VALIDATION_ATTRIBUTE, validation_default
|
|
155
|
+
),
|
|
156
|
+
auditing_enabled=_effective_switch(
|
|
157
|
+
implementation, implementation_method, _AUDITING_ATTRIBUTE, auditing_default
|
|
158
|
+
),
|
|
159
|
+
policy_sources=_policy_sources(implementation, implementation_method, implementation_owner),
|
|
160
|
+
)
|
|
161
|
+
descriptor = replace(
|
|
162
|
+
descriptor, interceptors=_match_interceptors(descriptor, interceptor_rules)
|
|
163
|
+
)
|
|
164
|
+
return _CompiledMethod(
|
|
165
|
+
name=name,
|
|
166
|
+
contract_method=contract_method,
|
|
167
|
+
implementation_method=implementation_method,
|
|
168
|
+
validator=validator,
|
|
169
|
+
parameter_names=frozenset(tuple(contract_signature.parameters)[1:]),
|
|
170
|
+
authorization=authorization,
|
|
171
|
+
descriptor=descriptor,
|
|
172
|
+
signature=contract_signature,
|
|
173
|
+
implementation_signature=implementation_signature.replace(
|
|
174
|
+
parameters=tuple(
|
|
175
|
+
parameter.replace(annotation=extensions.annotation(parameter.annotation))
|
|
176
|
+
for parameter in implementation_signature.parameters.values()
|
|
177
|
+
),
|
|
178
|
+
return_annotation=extensions.annotation(implementation_signature.return_annotation),
|
|
179
|
+
),
|
|
180
|
+
result_adapter=TypeAdapter(contract_signature.return_annotation)
|
|
181
|
+
if contract_signature.return_annotation != declared_return
|
|
182
|
+
else None,
|
|
183
|
+
policy_fingerprint=_policy_fingerprint(implementation, implementation_method),
|
|
184
|
+
interceptor_steps=MappingProxyType(
|
|
185
|
+
{
|
|
186
|
+
stage: tuple(item for item in descriptor.interceptors if item.before is stage)
|
|
187
|
+
for stage in InterceptionStage
|
|
188
|
+
}
|
|
189
|
+
),
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
_POLICY_ATTRIBUTES = (
|
|
194
|
+
_UNIT_OF_WORK_ATTRIBUTE,
|
|
195
|
+
_AUTHORIZATION_ATTRIBUTE,
|
|
196
|
+
_ALLOW_ANONYMOUS_ATTRIBUTE,
|
|
197
|
+
_VALIDATION_ATTRIBUTE,
|
|
198
|
+
_AUDITING_ATTRIBUTE,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _policy_fingerprint(implementation: type[object], method: object) -> tuple[object, ...]:
|
|
203
|
+
return (
|
|
204
|
+
issubclass(implementation, ValidationEnabled),
|
|
205
|
+
tuple(
|
|
206
|
+
tuple(vars(target).get(attribute) for attribute in _POLICY_ATTRIBUTES)
|
|
207
|
+
for target in (*implementation.__mro__, method)
|
|
208
|
+
if any(attribute in vars(target) for attribute in _POLICY_ATTRIBUTES)
|
|
209
|
+
),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _policy_sources(
|
|
214
|
+
implementation: type[object], method: Callable[..., Any], owner: ModuleKey
|
|
215
|
+
) -> tuple[SourceLocation, ...]:
|
|
216
|
+
return tuple(
|
|
217
|
+
_method_source(target, owner)
|
|
218
|
+
for target in (*reversed(implementation.__mro__), method)
|
|
219
|
+
if any(attribute in vars(target) for attribute in _POLICY_ATTRIBUTES)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _match_interceptors(
|
|
224
|
+
method: ServiceMethod, rules: tuple[_InterceptorRule, ...]
|
|
225
|
+
) -> tuple[InterceptorRegistration, ...]:
|
|
226
|
+
matched: list[InterceptorRegistration] = []
|
|
227
|
+
for rule in rules:
|
|
228
|
+
try:
|
|
229
|
+
accepted = rule.match(method)
|
|
230
|
+
if not isinstance(accepted, bool):
|
|
231
|
+
raise TypeError("interceptor match must return bool")
|
|
232
|
+
except Exception as error:
|
|
233
|
+
raise ApplicationServiceDefinitionError(
|
|
234
|
+
symbol=rule.registration.source.symbol
|
|
235
|
+
or rule.registration.interceptor.__qualname__,
|
|
236
|
+
reason=(
|
|
237
|
+
f"interceptor match failed for {rule.registration.owner}: "
|
|
238
|
+
f"{type(error).__name__}"
|
|
239
|
+
),
|
|
240
|
+
) from None
|
|
241
|
+
if accepted:
|
|
242
|
+
matched.append(rule.registration)
|
|
243
|
+
return tuple(sorted(matched, key=lambda item: tuple(InterceptionStage).index(item.before)))
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _public_method_names(candidate: type[object], *, is_contract: bool) -> tuple[str, ...]:
|
|
247
|
+
names: dict[str, None] = {}
|
|
248
|
+
for owner in reversed(candidate.__mro__):
|
|
249
|
+
if owner is object:
|
|
250
|
+
continue
|
|
251
|
+
if is_contract and owner.__module__ == "typing":
|
|
252
|
+
continue
|
|
253
|
+
for name, value in owner.__dict__.items():
|
|
254
|
+
if name.startswith("_"):
|
|
255
|
+
continue
|
|
256
|
+
if inspect.isfunction(value) or isinstance(value, (staticmethod, classmethod)):
|
|
257
|
+
names[name] = None
|
|
258
|
+
return tuple(names)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _require_async_instance_method(
|
|
262
|
+
owner: type[object],
|
|
263
|
+
name: str,
|
|
264
|
+
*,
|
|
265
|
+
role: str,
|
|
266
|
+
) -> Callable[..., Awaitable[object]]:
|
|
267
|
+
symbol = f"{owner.__module__}.{owner.__qualname__}.{name}"
|
|
268
|
+
descriptor = inspect.getattr_static(owner, name)
|
|
269
|
+
if isinstance(descriptor, (staticmethod, classmethod)):
|
|
270
|
+
raise ApplicationServiceDefinitionError(
|
|
271
|
+
symbol=symbol,
|
|
272
|
+
reason=f"public {role} methods must be instance methods",
|
|
273
|
+
)
|
|
274
|
+
method = getattr(owner, name)
|
|
275
|
+
if not inspect.isfunction(method) or not inspect.iscoroutinefunction(method):
|
|
276
|
+
raise ApplicationServiceDefinitionError(
|
|
277
|
+
symbol=symbol,
|
|
278
|
+
reason=f"public {role} methods must use async def",
|
|
279
|
+
)
|
|
280
|
+
parameters = tuple(inspect.signature(method).parameters.values())
|
|
281
|
+
if not parameters or parameters[0].kind not in (
|
|
282
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
283
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
284
|
+
):
|
|
285
|
+
# Catalog 与 HTTP generator 都会剥离 receiver;缺少首个位置参数时不能伪装成实例方法。
|
|
286
|
+
raise ApplicationServiceDefinitionError(
|
|
287
|
+
symbol=symbol,
|
|
288
|
+
reason=f"public {role} methods must declare an instance receiver",
|
|
289
|
+
)
|
|
290
|
+
return cast(Callable[..., Awaitable[object]], method)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _resolved_signature(
|
|
294
|
+
method: Callable[..., Awaitable[object]],
|
|
295
|
+
) -> tuple[inspect.Signature, dict[str, object]]:
|
|
296
|
+
annotations = get_type_hints(method, include_extras=True)
|
|
297
|
+
signature = inspect.signature(method)
|
|
298
|
+
parameters = tuple(
|
|
299
|
+
parameter.replace(annotation=annotations.get(name, parameter.annotation))
|
|
300
|
+
for name, parameter in signature.parameters.items()
|
|
301
|
+
)
|
|
302
|
+
resolved = signature.replace(
|
|
303
|
+
parameters=parameters,
|
|
304
|
+
return_annotation=annotations.get("return", signature.return_annotation),
|
|
305
|
+
)
|
|
306
|
+
return resolved, annotations
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _compile_argument_validator(
|
|
310
|
+
method: Callable[..., Awaitable[object]],
|
|
311
|
+
signature: inspect.Signature,
|
|
312
|
+
annotations: Mapping[str, object],
|
|
313
|
+
) -> Callable[..., Awaitable[_ValidatedArguments]]:
|
|
314
|
+
async def capture(*args: object, **kwargs: object) -> _ValidatedArguments:
|
|
315
|
+
# 第一个值是 validation-only self sentinel;业务 implementation 尚未解析或执行。
|
|
316
|
+
return tuple(args[1:]), dict(kwargs)
|
|
317
|
+
|
|
318
|
+
capture.__name__ = method.__name__
|
|
319
|
+
capture.__qualname__ = method.__qualname__
|
|
320
|
+
cast(Any, capture).__signature__ = signature
|
|
321
|
+
capture.__annotations__ = dict(annotations)
|
|
322
|
+
validated = validate_call(
|
|
323
|
+
config=ConfigDict(
|
|
324
|
+
arbitrary_types_allowed=True,
|
|
325
|
+
validate_by_name=True,
|
|
326
|
+
hide_input_in_errors=True,
|
|
327
|
+
)
|
|
328
|
+
)(capture)
|
|
329
|
+
return cast(Callable[..., Awaitable[_ValidatedArguments]], validated)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _method_source(method: Any, owner: ModuleKey) -> SourceLocation:
|
|
333
|
+
file_name: str | None = None
|
|
334
|
+
line: int | None = None
|
|
335
|
+
try:
|
|
336
|
+
file_name = inspect.getsourcefile(method)
|
|
337
|
+
_, line = inspect.getsourcelines(method)
|
|
338
|
+
except (OSError, TypeError):
|
|
339
|
+
pass
|
|
340
|
+
return SourceLocation(
|
|
341
|
+
owner=str(owner),
|
|
342
|
+
file=file_name,
|
|
343
|
+
line=line,
|
|
344
|
+
symbol=f"{method.__module__}.{method.__qualname__}",
|
|
345
|
+
)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Application-local 调用上下文、准入和入口编译的能力 owner。"""
|
|
2
|
+
|
|
3
|
+
from dishka import DEFAULT_COMPONENT, DependencyKey, Provider, Scope
|
|
4
|
+
|
|
5
|
+
from ..application_services.errors import ApplicationServiceDefinitionError
|
|
6
|
+
from ..application_services.invocation import _InvocationRuntime
|
|
7
|
+
from ..extensions.module import ExtensionsModule
|
|
8
|
+
from ..invocation.interceptors import _InterceptorRule
|
|
9
|
+
from ..lifecycle import ConfigureContext
|
|
10
|
+
from ..modularity import AppModule
|
|
11
|
+
from ..services.composition import (
|
|
12
|
+
ServiceBindingContext,
|
|
13
|
+
ServiceComposition,
|
|
14
|
+
ServiceValidationContext,
|
|
15
|
+
)
|
|
16
|
+
from .entries import _validate_entry
|
|
17
|
+
from .interception import InvocationEntry, _EntryInterceptionCatalog
|
|
18
|
+
from .managed_services import _ManagedServices
|
|
19
|
+
from .validation import _validate_interceptor_scopes
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class InvocationModule(AppModule):
|
|
23
|
+
dependencies = (ExtensionsModule,)
|
|
24
|
+
catalog: _EntryInterceptionCatalog
|
|
25
|
+
invocation_runtime: _InvocationRuntime
|
|
26
|
+
|
|
27
|
+
def __init__(self) -> None:
|
|
28
|
+
self._managed_services = _ManagedServices()
|
|
29
|
+
self.entries: list[InvocationEntry] = []
|
|
30
|
+
self._rules: list[_InterceptorRule] = []
|
|
31
|
+
self._targets: dict[type[object], type[object]] = {}
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def rules(self) -> tuple[_InterceptorRule, ...]:
|
|
35
|
+
return tuple(self._rules)
|
|
36
|
+
|
|
37
|
+
def configure(self, context: ConfigureContext) -> None:
|
|
38
|
+
context.services.add_composition(
|
|
39
|
+
ServiceComposition(
|
|
40
|
+
bind=self._bind,
|
|
41
|
+
finalize=self._finalize,
|
|
42
|
+
decorate=self._decorate,
|
|
43
|
+
validate=self._validate,
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def _bind(self, context: ServiceBindingContext) -> None:
|
|
48
|
+
application = context.application
|
|
49
|
+
runtime = _InvocationRuntime(
|
|
50
|
+
application_id=application.id,
|
|
51
|
+
state_getter=lambda: application.state,
|
|
52
|
+
)
|
|
53
|
+
self.invocation_runtime = runtime
|
|
54
|
+
self.runtime = runtime.runtime_participant()
|
|
55
|
+
provider = Provider()
|
|
56
|
+
provider.provide(lambda: runtime, provides=_InvocationRuntime, scope=Scope.APP)
|
|
57
|
+
provider.provide(runtime.context_for_action, scope=Scope.ACTION)
|
|
58
|
+
provider.provide(lambda: self.catalog, provides=_EntryInterceptionCatalog, scope=Scope.APP)
|
|
59
|
+
context.add_provider(provider)
|
|
60
|
+
|
|
61
|
+
def _finalize(self, context: ServiceBindingContext) -> None:
|
|
62
|
+
for entry in self.entries:
|
|
63
|
+
if isinstance(entry.target, type):
|
|
64
|
+
factory = context.arbitration.single_factory(
|
|
65
|
+
DependencyKey(entry.target, DEFAULT_COMPONENT),
|
|
66
|
+
)
|
|
67
|
+
if factory is None:
|
|
68
|
+
raise ApplicationServiceDefinitionError(
|
|
69
|
+
symbol=entry.target.__qualname__,
|
|
70
|
+
reason="entry requires a single binding",
|
|
71
|
+
)
|
|
72
|
+
self._targets[entry.target] = (
|
|
73
|
+
factory.source if isinstance(factory.source, type) else entry.target
|
|
74
|
+
)
|
|
75
|
+
from ..authorization.module import AuthorizationModule
|
|
76
|
+
|
|
77
|
+
permissions = (
|
|
78
|
+
context.modules.instance(AuthorizationModule).catalog
|
|
79
|
+
if any(
|
|
80
|
+
item.module_type is AuthorizationModule for item in context.modules.dependency_order
|
|
81
|
+
)
|
|
82
|
+
else None
|
|
83
|
+
)
|
|
84
|
+
self.catalog = _EntryInterceptionCatalog(
|
|
85
|
+
tuple(self.entries),
|
|
86
|
+
self.rules,
|
|
87
|
+
self._targets,
|
|
88
|
+
permissions,
|
|
89
|
+
context.modules.instance(ExtensionsModule).catalog,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def _validate(self, context: ServiceValidationContext) -> None:
|
|
93
|
+
self._managed_services.validate(context)
|
|
94
|
+
_validate_interceptor_scopes(context.native_graph, self.catalog.interceptor_scopes)
|
|
95
|
+
for entry in self.entries:
|
|
96
|
+
target = entry.target
|
|
97
|
+
if isinstance(target, type):
|
|
98
|
+
target = self._targets[target]
|
|
99
|
+
_validate_entry(
|
|
100
|
+
context.container,
|
|
101
|
+
target,
|
|
102
|
+
entry.method,
|
|
103
|
+
entry.business_parameters,
|
|
104
|
+
entry.method_scope,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def _decorate(self, context: ServiceBindingContext) -> None:
|
|
108
|
+
self._managed_services.decorate(
|
|
109
|
+
context,
|
|
110
|
+
self.invocation_runtime,
|
|
111
|
+
self.rules,
|
|
112
|
+
{entry.target for entry in self.entries} | set(self._targets.values()),
|
|
113
|
+
)
|
|
@@ -4,7 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import TypeVar
|
|
6
6
|
|
|
7
|
-
from .errors import ApplicationServiceDefinitionError
|
|
7
|
+
from ..application_services.errors import ApplicationServiceDefinitionError
|
|
8
|
+
from ..modularity import ModuleRef, ModuleRequirement
|
|
8
9
|
|
|
9
10
|
_T = TypeVar("_T")
|
|
10
11
|
_VALIDATION_ATTRIBUTE = "__python_ddd_framework_validation_enabled__"
|
|
@@ -44,3 +45,9 @@ def _effective_switch(
|
|
|
44
45
|
)
|
|
45
46
|
return value
|
|
46
47
|
return default
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ValidationEnabled:
|
|
51
|
+
__validation_module__ = ModuleRequirement(
|
|
52
|
+
ModuleRef("python_ddd_framework.invocation.module:InvocationModule")
|
|
53
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""根据已编译入口所需 scope 核对原生 DI 图。"""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
|
|
5
|
+
from dishka import Scope
|
|
6
|
+
|
|
7
|
+
from ..application_services.errors import ApplicationServiceDefinitionError
|
|
8
|
+
from ..invocation.interceptors import InterceptorRegistration
|
|
9
|
+
from ..services.native_graph import _NativeServiceGraph
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _validate_interceptor_scopes(
|
|
13
|
+
graph: _NativeServiceGraph,
|
|
14
|
+
requirements: Iterable[tuple[InterceptorRegistration, Scope]],
|
|
15
|
+
) -> None:
|
|
16
|
+
for registration, available in requirements:
|
|
17
|
+
scope = graph.interceptor_scope(registration.interceptor)
|
|
18
|
+
if scope not in (Scope.APP, Scope.REQUEST, Scope.ACTION) or scope > available:
|
|
19
|
+
raise ApplicationServiceDefinitionError(
|
|
20
|
+
symbol=registration.interceptor.__qualname__,
|
|
21
|
+
reason=(
|
|
22
|
+
f"interceptor scope is unavailable before {registration.before.value}; "
|
|
23
|
+
f"owner={registration.owner}"
|
|
24
|
+
),
|
|
25
|
+
)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from .composition import ConfigureContext, PostConfigureContext, PreConfigureContext
|
|
4
4
|
from .context import ApplicationView, LifecycleContext
|
|
5
|
+
from .participants import RuntimeParticipant
|
|
5
6
|
from .runtime import (
|
|
6
7
|
InitializeContext,
|
|
7
8
|
PostInitializeContext,
|
|
@@ -20,6 +21,7 @@ __all__ = (
|
|
|
20
21
|
"PostInitializeContext",
|
|
21
22
|
"PreConfigureContext",
|
|
22
23
|
"PreInitializeContext",
|
|
24
|
+
"RuntimeParticipant",
|
|
23
25
|
"ShutdownContext",
|
|
24
26
|
"ShutdownReason",
|
|
25
27
|
)
|
|
@@ -5,7 +5,8 @@ from __future__ import annotations
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
|
|
7
7
|
from ..configuration import ConfigurationRoot
|
|
8
|
-
from ..
|
|
8
|
+
from ..modularity import ModuleRegistry
|
|
9
|
+
from ..modularity.discovery import _DiscoveredType
|
|
9
10
|
from ..options import PreOptionsSnapshot
|
|
10
11
|
from ..options.contribution import _OptionsContributor
|
|
11
12
|
from ..services import ServiceContributor
|
|
@@ -20,15 +21,17 @@ class PreConfigureContext(LifecycleContext):
|
|
|
20
21
|
|
|
21
22
|
@dataclass(frozen=True, slots=True)
|
|
22
23
|
class ConfigureContext(LifecycleContext):
|
|
24
|
+
modules: ModuleRegistry
|
|
25
|
+
discoveries: tuple[_DiscoveredType, ...]
|
|
23
26
|
configuration: ConfigurationRoot
|
|
24
27
|
pre_options: PreOptionsSnapshot
|
|
25
28
|
configure: _OptionsContributor
|
|
26
29
|
services: ServiceContributor
|
|
27
|
-
events: EventContributor
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
@dataclass(frozen=True, slots=True)
|
|
31
33
|
class PostConfigureContext(LifecycleContext):
|
|
34
|
+
modules: ModuleRegistry
|
|
32
35
|
configuration: ConfigurationRoot
|
|
33
36
|
pre_options: PreOptionsSnapshot
|
|
34
37
|
configure: _OptionsContributor
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""能力贡献的运行生命周期;内核只编排回调,不解释具体协调器。"""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Awaitable, Callable
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from ..errors.lifecycle import ApplicationRuntimeError
|
|
7
|
+
from .state import ShutdownReason
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class RuntimeParticipant:
|
|
12
|
+
start: Callable[[], Awaitable[None]]
|
|
13
|
+
stop: Callable[[ShutdownReason], Awaitable[tuple[BaseException, ...]]]
|
|
14
|
+
prepare: Callable[[bool], None] | None = None
|
|
15
|
+
close_admission: Callable[[], None] | None = None
|
|
16
|
+
ready: Callable[[], bool] | None = None
|
|
17
|
+
failure: Callable[[], Awaitable[ApplicationRuntimeError] | None] | None = None
|
|
18
|
+
drain: Callable[[], Awaitable[None]] | None = None
|
|
19
|
+
|
|
20
|
+
def __post_init__(self) -> None:
|
|
21
|
+
if not callable(self.start) or not callable(self.stop):
|
|
22
|
+
raise TypeError("runtime start and stop must be callable")
|
|
23
|
+
if any(
|
|
24
|
+
callback is not None and not callable(callback)
|
|
25
|
+
for callback in (
|
|
26
|
+
self.prepare,
|
|
27
|
+
self.close_admission,
|
|
28
|
+
self.ready,
|
|
29
|
+
self.failure,
|
|
30
|
+
self.drain,
|
|
31
|
+
)
|
|
32
|
+
):
|
|
33
|
+
raise TypeError("runtime callbacks must be callable")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""有界进程内消息与显式状态快照合并。"""
|
|
2
|
+
|
|
3
|
+
from .channel import MessageChannel
|
|
4
|
+
from .contracts import (
|
|
5
|
+
MessageChannelDefinition,
|
|
6
|
+
MessageHandler,
|
|
7
|
+
MessageOutcome,
|
|
8
|
+
MessageRejectedError,
|
|
9
|
+
MessageState,
|
|
10
|
+
)
|
|
11
|
+
from .module import MessagingModule
|
|
12
|
+
from .options import MessagingOptions
|
|
13
|
+
from .receipt import MessageReceipt
|
|
14
|
+
|
|
15
|
+
__all__ = (
|
|
16
|
+
"MessageChannel",
|
|
17
|
+
"MessageChannelDefinition",
|
|
18
|
+
"MessageHandler",
|
|
19
|
+
"MessageOutcome",
|
|
20
|
+
"MessageReceipt",
|
|
21
|
+
"MessageRejectedError",
|
|
22
|
+
"MessageState",
|
|
23
|
+
"MessagingModule",
|
|
24
|
+
"MessagingOptions",
|
|
25
|
+
)
|