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
|
@@ -1,67 +1,29 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Application 状态、通用生命周期和容器所有权。"""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from typing import Concatenate, Never, ParamSpec, TypeAlias, TypeVar, cast
|
|
9
|
-
from uuid import UUID, uuid4
|
|
6
|
+
from typing import Never
|
|
7
|
+
from uuid import UUID
|
|
10
8
|
|
|
11
|
-
from dishka import AsyncContainer
|
|
9
|
+
from dishka import AsyncContainer
|
|
12
10
|
|
|
13
|
-
from ..application_services import (
|
|
14
|
-
ApplicationInvocationRejectedError,
|
|
15
|
-
ApplicationServiceCatalog,
|
|
16
|
-
ApplicationServiceContract,
|
|
17
|
-
ApplicationServiceRegistration,
|
|
18
|
-
)
|
|
19
|
-
from ..application_services.dispatcher import _ApplicationServiceDispatcher
|
|
20
|
-
from ..application_services.invocation import (
|
|
21
|
-
_ScopedApplicationServiceInvoker,
|
|
22
|
-
)
|
|
23
|
-
from ..authorization import CurrentUser, PermissionCatalog
|
|
24
|
-
from ..background_jobs import BackgroundJobCatalog
|
|
25
|
-
from ..background_workers import BackgroundWorkerCatalog
|
|
26
|
-
from ..caching import CacheCatalog
|
|
27
11
|
from ..configuration import ConfigurationRoot
|
|
28
12
|
from ..diagnostics import DiagnosticSnapshot
|
|
29
13
|
from ..errors import (
|
|
30
14
|
ApplicationRuntimeError,
|
|
31
15
|
ApplicationStartError,
|
|
32
|
-
ContainerCleanupError,
|
|
33
16
|
InvalidApplicationStateError,
|
|
34
17
|
LifecycleInvocationError,
|
|
35
18
|
)
|
|
36
|
-
from ..errors.lifecycle import
|
|
37
|
-
_BackgroundExecutionStartFailure,
|
|
38
|
-
_exclude_runtime_failure,
|
|
39
|
-
)
|
|
40
|
-
from ..events import LocalEventCatalog
|
|
41
|
-
from ..hosted_services import (
|
|
42
|
-
ApplicationReadinessSnapshot,
|
|
43
|
-
HostedServiceCatalog,
|
|
44
|
-
HostedServiceLifecycleError,
|
|
45
|
-
)
|
|
46
|
-
from ..invocation.function_runtime import _invoke_callable
|
|
19
|
+
from ..errors.lifecycle import _exclude_runtime_failure
|
|
47
20
|
from ..lifecycle import ApplicationState, ShutdownReason
|
|
48
21
|
from ..modularity import ModuleRegistry
|
|
49
22
|
from ..options import OptionsSnapshot
|
|
50
23
|
from ..services import ServiceCatalog
|
|
51
|
-
from ..settings import SettingCatalog
|
|
52
24
|
from .runtime_hooks import _RuntimeLifecycle
|
|
53
25
|
from .state import _ApplicationStateRoot
|
|
54
26
|
|
|
55
|
-
_P = ParamSpec("_P")
|
|
56
|
-
_R = TypeVar("_R")
|
|
57
|
-
_TContract = TypeVar("_TContract", bound=ApplicationServiceContract)
|
|
58
|
-
_StartCleanupFailure: TypeAlias = (
|
|
59
|
-
LifecycleInvocationError
|
|
60
|
-
| ContainerCleanupError
|
|
61
|
-
| _BackgroundExecutionStartFailure
|
|
62
|
-
| HostedServiceLifecycleError
|
|
63
|
-
)
|
|
64
|
-
|
|
65
27
|
|
|
66
28
|
class Application:
|
|
67
29
|
"""拥有单次 build 的 Module instances、状态、transition 与 diagnostics。"""
|
|
@@ -96,40 +58,10 @@ class Application:
|
|
|
96
58
|
return self._root.require_service_catalog()
|
|
97
59
|
|
|
98
60
|
@property
|
|
99
|
-
def
|
|
100
|
-
return self.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def event_catalog(self) -> LocalEventCatalog:
|
|
104
|
-
return self._root.require_event_catalog()
|
|
105
|
-
|
|
106
|
-
@property
|
|
107
|
-
def permission_catalog(self) -> PermissionCatalog:
|
|
108
|
-
return self._root.require_permission_catalog()
|
|
109
|
-
|
|
110
|
-
@property
|
|
111
|
-
def cache_catalog(self) -> CacheCatalog:
|
|
112
|
-
return self._root.require_cache_catalog()
|
|
113
|
-
|
|
114
|
-
@property
|
|
115
|
-
def setting_catalog(self) -> SettingCatalog:
|
|
116
|
-
return self._root.require_setting_catalog()
|
|
117
|
-
|
|
118
|
-
@property
|
|
119
|
-
def background_worker_catalog(self) -> BackgroundWorkerCatalog:
|
|
120
|
-
return self._root.require_background_worker_catalog()
|
|
121
|
-
|
|
122
|
-
@property
|
|
123
|
-
def background_job_catalog(self) -> BackgroundJobCatalog:
|
|
124
|
-
return self._root.require_background_job_catalog()
|
|
125
|
-
|
|
126
|
-
@property
|
|
127
|
-
def hosted_service_catalog(self) -> HostedServiceCatalog:
|
|
128
|
-
return self._root.require_hosted_service_catalog()
|
|
129
|
-
|
|
130
|
-
@property
|
|
131
|
-
def readiness(self) -> ApplicationReadinessSnapshot:
|
|
132
|
-
return self._root.readiness()
|
|
61
|
+
def is_ready(self) -> bool:
|
|
62
|
+
return self.state is ApplicationState.RUNNING and all(
|
|
63
|
+
participant.ready is None or participant.ready() for participant in self._root.runtimes
|
|
64
|
+
)
|
|
133
65
|
|
|
134
66
|
@property
|
|
135
67
|
def diagnostics(self) -> DiagnosticSnapshot:
|
|
@@ -148,41 +80,27 @@ class Application:
|
|
|
148
80
|
allowed=(ApplicationState.COMPOSED,),
|
|
149
81
|
)
|
|
150
82
|
self._root.state = ApplicationState.STARTING
|
|
151
|
-
if not run_services:
|
|
152
|
-
# 数据库 CLI 只需要 Module 初始化及正式 invocation;不打开后台生产者或外部 SDK。
|
|
153
|
-
self._root.require_background_coordinator().disable_for_database()
|
|
154
83
|
lifecycle = _RuntimeLifecycle(self._root)
|
|
155
84
|
started = False
|
|
156
85
|
try:
|
|
157
86
|
try:
|
|
87
|
+
self._root.registry.assert_declarations_unchanged()
|
|
88
|
+
for participant in self._root.runtimes:
|
|
89
|
+
if participant.prepare is not None:
|
|
90
|
+
participant.prepare(run_services)
|
|
158
91
|
await lifecycle.start_modules()
|
|
159
|
-
|
|
92
|
+
self._root.registry.assert_declarations_unchanged()
|
|
93
|
+
# 全部 Module 初始化屏障完成后才开放运行入口;部分启动也必须逆序清理。
|
|
94
|
+
for participant in self._root.runtimes:
|
|
95
|
+
self._root.touched_runtimes.append(participant)
|
|
96
|
+
await participant.start()
|
|
97
|
+
except BaseException as primary:
|
|
160
98
|
await self._fail_start(lifecycle, primary=primary)
|
|
161
|
-
self._root.require_invocation_runtime().start_accepting()
|
|
162
|
-
try:
|
|
163
|
-
if run_services:
|
|
164
|
-
await self._root.require_hosted_service_coordinator().start()
|
|
165
|
-
except BaseException as original_error:
|
|
166
|
-
hosted_failure = (
|
|
167
|
-
original_error
|
|
168
|
-
if isinstance(original_error, HostedServiceLifecycleError)
|
|
169
|
-
else HostedServiceLifecycleError(
|
|
170
|
-
service="hosted-service-coordinator",
|
|
171
|
-
operation="start",
|
|
172
|
-
error_type=type(original_error).__name__,
|
|
173
|
-
)
|
|
174
|
-
)
|
|
175
|
-
await self._fail_start(lifecycle, primary=hosted_failure)
|
|
176
|
-
try:
|
|
177
|
-
await self._root.require_background_coordinator().start()
|
|
178
|
-
except BaseException as original_error:
|
|
179
|
-
background_failure = _BackgroundExecutionStartFailure(original_error=original_error)
|
|
180
|
-
await self._fail_start(lifecycle, primary=background_failure)
|
|
181
99
|
self._root.state = ApplicationState.RUNNING
|
|
182
100
|
started = True
|
|
183
101
|
finally:
|
|
184
102
|
self._root.release_transition(token)
|
|
185
|
-
if started and (self._root.
|
|
103
|
+
if started and any(item.failure is not None for item in self._root.runtimes):
|
|
186
104
|
self._root.runtime_monitor_task = asyncio.create_task(
|
|
187
105
|
self._monitor_runtime_failure(),
|
|
188
106
|
name=f"python-ddd-framework-runtime-monitor:{self.id}",
|
|
@@ -192,9 +110,7 @@ class Application:
|
|
|
192
110
|
self,
|
|
193
111
|
lifecycle: _RuntimeLifecycle,
|
|
194
112
|
*,
|
|
195
|
-
primary:
|
|
196
|
-
| HostedServiceLifecycleError
|
|
197
|
-
| _BackgroundExecutionStartFailure,
|
|
113
|
+
primary: BaseException,
|
|
198
114
|
) -> Never:
|
|
199
115
|
self._root.state = ApplicationState.ROLLING_BACK
|
|
200
116
|
# 回滚 task 由本次 start 强持有;重复取消只能在所有资源实际退出后返回。
|
|
@@ -217,64 +133,50 @@ class Application:
|
|
|
217
133
|
self,
|
|
218
134
|
lifecycle: _RuntimeLifecycle,
|
|
219
135
|
*,
|
|
220
|
-
primary:
|
|
221
|
-
| HostedServiceLifecycleError
|
|
222
|
-
| _BackgroundExecutionStartFailure,
|
|
136
|
+
primary: BaseException,
|
|
223
137
|
) -> ApplicationStartError:
|
|
138
|
+
runtime_failures = await self._stop_runtimes(ShutdownReason.START_FAILURE)
|
|
139
|
+
module_failures = await lifecycle.shutdown_touched(reason=ShutdownReason.START_FAILURE)
|
|
140
|
+
container_failures = await lifecycle.close_container(
|
|
141
|
+
exception=primary.original_error
|
|
142
|
+
if isinstance(primary, LifecycleInvocationError)
|
|
143
|
+
else primary
|
|
144
|
+
)
|
|
224
145
|
if isinstance(primary, LifecycleInvocationError):
|
|
225
|
-
module_failures = await lifecycle.shutdown_touched(reason=ShutdownReason.START_FAILURE)
|
|
226
|
-
container_failures = await lifecycle.close_container(exception=primary.original_error)
|
|
227
|
-
cleanup_failures: tuple[LifecycleInvocationError | ContainerCleanupError, ...] = (
|
|
228
|
-
*module_failures,
|
|
229
|
-
*container_failures,
|
|
230
|
-
)
|
|
231
146
|
self._root.journal.complete_failure(
|
|
232
147
|
primary.failure_id,
|
|
233
|
-
cleanup_failure_ids=tuple(failure.failure_id for failure in
|
|
234
|
-
|
|
235
|
-
return ApplicationStartError(
|
|
236
|
-
primary=primary,
|
|
237
|
-
cleanup_failures=cleanup_failures,
|
|
148
|
+
cleanup_failure_ids=tuple(failure.failure_id for failure in module_failures)
|
|
149
|
+
+ tuple(failure.failure_id for failure in container_failures),
|
|
238
150
|
)
|
|
239
151
|
return ApplicationStartError(
|
|
240
152
|
primary=primary,
|
|
241
|
-
cleanup_failures=
|
|
153
|
+
cleanup_failures=(*runtime_failures, *module_failures, *container_failures),
|
|
242
154
|
)
|
|
243
155
|
|
|
244
|
-
async def
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
)
|
|
269
|
-
)
|
|
270
|
-
else:
|
|
271
|
-
cleanup_failures.extend(hosted_failures)
|
|
272
|
-
await self._root.require_invocation_runtime().stop_accepting_and_wait()
|
|
273
|
-
cleanup_failures.extend(
|
|
274
|
-
await lifecycle.shutdown_touched(reason=ShutdownReason.START_FAILURE)
|
|
275
|
-
)
|
|
276
|
-
cleanup_failures.extend(await lifecycle.close_container(exception=primary))
|
|
277
|
-
return tuple(cleanup_failures)
|
|
156
|
+
async def _stop_runtimes(self, reason: ShutdownReason) -> tuple[BaseException, ...]:
|
|
157
|
+
failures: list[BaseException] = []
|
|
158
|
+
# 先统一关闭外部准入,再逆序排空依赖;一个能力失败不能阻止其余能力释放资源。
|
|
159
|
+
# stop 只交给触达过 start 的参与者;预启动入口已接受的资源由独立 drain 收口。
|
|
160
|
+
participants = tuple(reversed(self._root.touched_runtimes))
|
|
161
|
+
for participant in participants:
|
|
162
|
+
if participant.close_admission is not None:
|
|
163
|
+
try:
|
|
164
|
+
participant.close_admission()
|
|
165
|
+
except BaseException as error:
|
|
166
|
+
failures.append(error)
|
|
167
|
+
for participant in participants:
|
|
168
|
+
try:
|
|
169
|
+
failures.extend(await participant.stop(reason))
|
|
170
|
+
except BaseException as error:
|
|
171
|
+
failures.append(error)
|
|
172
|
+
self._root.touched_runtimes.clear()
|
|
173
|
+
for participant in reversed(self._root.runtimes):
|
|
174
|
+
if participant.drain is not None:
|
|
175
|
+
try:
|
|
176
|
+
await participant.drain()
|
|
177
|
+
except BaseException as error:
|
|
178
|
+
failures.append(error)
|
|
179
|
+
return tuple(failures)
|
|
278
180
|
|
|
279
181
|
async def stop(self) -> None:
|
|
280
182
|
if self._root.state in (
|
|
@@ -318,21 +220,7 @@ class Application:
|
|
|
318
220
|
if self._root.runtime_failure is not None
|
|
319
221
|
else ShutdownReason.NORMAL
|
|
320
222
|
)
|
|
321
|
-
|
|
322
|
-
# 关闭新工作入口,等待消费者退出后再逆序停止 HostedService。
|
|
323
|
-
hosted.close_admission()
|
|
324
|
-
infrastructure_failures: list[BaseException] = []
|
|
325
|
-
try:
|
|
326
|
-
await self._root.require_background_coordinator().stop()
|
|
327
|
-
except BaseException as background_error:
|
|
328
|
-
infrastructure_failures.append(background_error)
|
|
329
|
-
try:
|
|
330
|
-
hosted_failures = await hosted.stop(reason)
|
|
331
|
-
except BaseException as hosted_error:
|
|
332
|
-
infrastructure_failures.append(hosted_error)
|
|
333
|
-
else:
|
|
334
|
-
infrastructure_failures.extend(hosted_failures)
|
|
335
|
-
await self._root.require_invocation_runtime().stop_accepting_and_wait()
|
|
223
|
+
infrastructure_failures = list(await self._stop_runtimes(reason))
|
|
336
224
|
module_failures = await lifecycle.shutdown_touched(
|
|
337
225
|
reason=reason,
|
|
338
226
|
)
|
|
@@ -398,6 +286,10 @@ class Application:
|
|
|
398
286
|
allowed=allowed,
|
|
399
287
|
)
|
|
400
288
|
await self._root.termination_event.wait()
|
|
289
|
+
monitor = self._root.runtime_monitor_task
|
|
290
|
+
if monitor is not None and monitor is not asyncio.current_task():
|
|
291
|
+
# 终态发布与监督器退出相邻;调用者取消等待不能取消仍在收口的监督任务。
|
|
292
|
+
await asyncio.shield(asyncio.gather(monitor, return_exceptions=True))
|
|
401
293
|
failure = self._root.termination_failure
|
|
402
294
|
if failure is not None:
|
|
403
295
|
raise failure
|
|
@@ -405,8 +297,9 @@ class Application:
|
|
|
405
297
|
async def _monitor_runtime_failure(self) -> None:
|
|
406
298
|
current = asyncio.current_task()
|
|
407
299
|
try:
|
|
408
|
-
|
|
409
|
-
failure
|
|
300
|
+
failure = await self._wait_for_runtime_failure()
|
|
301
|
+
if failure is None:
|
|
302
|
+
return
|
|
410
303
|
if self._root.runtime_failure is None:
|
|
411
304
|
self._root.runtime_failure = ApplicationRuntimeError(
|
|
412
305
|
component=failure.component,
|
|
@@ -425,125 +318,37 @@ class Application:
|
|
|
425
318
|
if self._root.runtime_monitor_task is current:
|
|
426
319
|
self._root.runtime_monitor_task = None
|
|
427
320
|
|
|
428
|
-
async def
|
|
429
|
-
|
|
430
|
-
function: Callable[..., Awaitable[_R]],
|
|
431
|
-
/,
|
|
432
|
-
*args: object,
|
|
433
|
-
**kwargs: object,
|
|
434
|
-
) -> _R:
|
|
435
|
-
"""调用异步入口,业务实参显式传入,服务参数由最终 Dishka 图补齐。"""
|
|
436
|
-
return await self.call_as(
|
|
437
|
-
self._root.require_invocation_runtime().current_user(),
|
|
438
|
-
function,
|
|
439
|
-
*args,
|
|
440
|
-
**kwargs,
|
|
441
|
-
)
|
|
442
|
-
|
|
443
|
-
async def call_as(
|
|
444
|
-
self,
|
|
445
|
-
current_user: CurrentUser,
|
|
446
|
-
function: Callable[..., Awaitable[_R]],
|
|
447
|
-
/,
|
|
448
|
-
*args: object,
|
|
449
|
-
**kwargs: object,
|
|
450
|
-
) -> _R:
|
|
451
|
-
"""可信身份与调用许可覆盖 REQUEST 创建、业务执行和最终资源释放。"""
|
|
452
|
-
runtime = self._root.require_invocation_runtime()
|
|
453
|
-
return await _invoke_callable(
|
|
454
|
-
self._root.require_container(),
|
|
455
|
-
runtime,
|
|
456
|
-
current_user,
|
|
457
|
-
str(uuid4()),
|
|
458
|
-
function,
|
|
459
|
-
args,
|
|
460
|
-
kwargs,
|
|
461
|
-
)
|
|
462
|
-
|
|
463
|
-
async def invoke(
|
|
464
|
-
self,
|
|
465
|
-
method: Callable[Concatenate[_TContract, _P], Awaitable[_R]],
|
|
466
|
-
/,
|
|
467
|
-
*args: _P.args,
|
|
468
|
-
**kwargs: _P.kwargs,
|
|
469
|
-
) -> _R:
|
|
470
|
-
"""匿名调用公开 Protocol method;可信 caller 需显式使用 invoke_as。"""
|
|
471
|
-
|
|
472
|
-
return await self.invoke_as(CurrentUser.anonymous(), method, *args, **kwargs)
|
|
473
|
-
|
|
474
|
-
async def invoke_as(
|
|
475
|
-
self,
|
|
476
|
-
current_user: CurrentUser,
|
|
477
|
-
method: Callable[Concatenate[_TContract, _P], Awaitable[_R]],
|
|
478
|
-
/,
|
|
479
|
-
*args: _P.args,
|
|
480
|
-
**kwargs: _P.kwargs,
|
|
481
|
-
) -> _R:
|
|
482
|
-
"""由可信 Host/test 传入已验证身份,身份只绑定当前 Application 与 asyncio Task。"""
|
|
483
|
-
|
|
484
|
-
if self._root.state not in (ApplicationState.STARTING, ApplicationState.RUNNING):
|
|
485
|
-
raise ApplicationInvocationRejectedError(
|
|
486
|
-
reason=f"Application must be running, got {self._root.state.value}"
|
|
487
|
-
)
|
|
488
|
-
container = self._root.require_container()
|
|
489
|
-
invoker = await container.get(_ScopedApplicationServiceInvoker)
|
|
490
|
-
result = await invoker.invoke(
|
|
491
|
-
container,
|
|
492
|
-
current_user,
|
|
493
|
-
str(uuid4()),
|
|
494
|
-
cast(Callable[..., Awaitable[object]], method),
|
|
495
|
-
cast(tuple[object, ...], args),
|
|
496
|
-
cast(dict[str, object], kwargs),
|
|
497
|
-
)
|
|
498
|
-
return cast(_R, result)
|
|
499
|
-
|
|
500
|
-
@asynccontextmanager
|
|
501
|
-
async def _invocation_lease_for_integration(self) -> AsyncIterator[None]:
|
|
502
|
-
"""Web/internal entry 共用的 Application-local drain lease。"""
|
|
503
|
-
|
|
504
|
-
async with self._root.require_invocation_runtime().outer_lease():
|
|
505
|
-
yield
|
|
506
|
-
|
|
507
|
-
@asynccontextmanager
|
|
508
|
-
async def _http_request_lease_for_integration(self) -> AsyncIterator[None]:
|
|
509
|
-
# Phase 2 health contract 允许 composed/pre-start probe,但该 request 仍参与 stop drain;
|
|
510
|
-
# lease 中的 capability 会让随后进入 dispatcher 的业务 route 继续 fail closed。
|
|
511
|
-
async with self._root.require_invocation_runtime().outer_lease(allow_composed_request=True):
|
|
512
|
-
yield
|
|
513
|
-
|
|
514
|
-
@contextmanager
|
|
515
|
-
def _caller_context_for_integration(
|
|
516
|
-
self,
|
|
517
|
-
current_user: CurrentUser,
|
|
518
|
-
correlation_id: str,
|
|
519
|
-
) -> Iterator[None]:
|
|
520
|
-
"""HTTP bridge 的窄化入口;不公开 mutable principal 或 process-global current app。"""
|
|
521
|
-
|
|
522
|
-
runtime = self._root.require_invocation_runtime()
|
|
523
|
-
token = runtime.bind_caller(current_user, correlation_id)
|
|
321
|
+
async def _wait_for_runtime_failure(self) -> ApplicationRuntimeError | None:
|
|
322
|
+
watchers = []
|
|
524
323
|
try:
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
324
|
+
for participant in self._root.runtimes:
|
|
325
|
+
if participant.failure is not None:
|
|
326
|
+
pending = participant.failure()
|
|
327
|
+
if pending is not None:
|
|
328
|
+
watchers.append(asyncio.ensure_future(pending))
|
|
329
|
+
if not watchers:
|
|
330
|
+
return None
|
|
331
|
+
done, _ = await asyncio.wait(watchers, return_when=asyncio.FIRST_COMPLETED)
|
|
332
|
+
failure = next(task.result() for task in watchers if task in done)
|
|
333
|
+
if not isinstance(failure, ApplicationRuntimeError):
|
|
334
|
+
raise TypeError("runtime failure callback must return ApplicationRuntimeError")
|
|
335
|
+
return failure
|
|
336
|
+
except (Exception, asyncio.CancelledError) as error:
|
|
337
|
+
monitor = asyncio.current_task()
|
|
338
|
+
if (
|
|
339
|
+
isinstance(error, asyncio.CancelledError)
|
|
340
|
+
and monitor is not None
|
|
341
|
+
and monitor.cancelling()
|
|
342
|
+
):
|
|
343
|
+
# 正式 stop 取消的是 monitor;子等待者取消不能冒充应用的停止请求。
|
|
344
|
+
raise
|
|
345
|
+
# 扩展监督器自身失效也必须终止应用;错误正文不跨越公共终态边界。
|
|
346
|
+
return ApplicationRuntimeError(
|
|
347
|
+
component="runtime-supervision", error_type=type(error).__name__
|
|
539
348
|
)
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
with self._caller_context_for_integration(CurrentUser.anonymous(), str(uuid4())):
|
|
547
|
-
async with container(scope=Scope.REQUEST) as request_container:
|
|
548
|
-
dispatcher = await request_container.get(_ApplicationServiceDispatcher)
|
|
549
|
-
return await dispatcher.invoke(service, method, (), {})
|
|
349
|
+
finally:
|
|
350
|
+
# 每个监督任务都归当前 Application;正常 stop 和 fatal 竞争也不遗留等待者。
|
|
351
|
+
for watcher in watchers:
|
|
352
|
+
if not watcher.done():
|
|
353
|
+
watcher.cancel()
|
|
354
|
+
await asyncio.gather(*watchers, return_exceptions=True)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Single-Application
|
|
1
|
+
"""Single-Application 核心状态、转换 ownership 与诊断视图。"""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -10,58 +10,36 @@ from uuid import UUID
|
|
|
10
10
|
|
|
11
11
|
from dishka import AsyncContainer
|
|
12
12
|
|
|
13
|
-
from ..application_services import ApplicationServiceCatalog
|
|
14
|
-
from ..application_services.invocation import _InvocationRuntime
|
|
15
|
-
from ..authorization import PermissionCatalog
|
|
16
|
-
from ..background_jobs import BackgroundJobCatalog
|
|
17
|
-
from ..background_workers import BackgroundWorkerCatalog
|
|
18
|
-
from ..background_workers.runtime import _BackgroundCoordinator
|
|
19
|
-
from ..caching import CacheCatalog
|
|
20
13
|
from ..configuration import ConfigurationRoot
|
|
21
14
|
from ..diagnostics import LifecyclePhase, LifecycleStage
|
|
22
15
|
from ..diagnostics.journal import _DiagnosticJournal
|
|
23
16
|
from ..errors import ApplicationRuntimeError, InvalidApplicationStateError, LifecycleInvocationError
|
|
24
|
-
from ..
|
|
25
|
-
from ..events.contribution import EventContributionRegistry
|
|
26
|
-
from ..hosted_services import ApplicationReadinessSnapshot, HostedServiceCatalog
|
|
27
|
-
from ..hosted_services.runtime import _HostedServiceCoordinator
|
|
28
|
-
from ..lifecycle import ApplicationState, ApplicationView
|
|
17
|
+
from ..lifecycle import ApplicationState, ApplicationView, RuntimeParticipant
|
|
29
18
|
from ..modularity import ModuleDescriptor, ModuleKey, ModuleRegistry
|
|
19
|
+
from ..modularity.discovery import _DiscoveredType
|
|
30
20
|
from ..options import OptionsSnapshot, PreOptionsSnapshot
|
|
31
21
|
from ..options.registry import _OptionsRegistry
|
|
32
22
|
from ..services import ServiceCatalog
|
|
33
23
|
from ..services.contribution import ServiceContributionRegistry
|
|
34
|
-
from ..settings import SettingCatalog
|
|
35
24
|
|
|
36
25
|
LIFECYCLE_ERROR_TYPE = LifecycleInvocationError.__name__
|
|
37
26
|
|
|
38
27
|
|
|
39
28
|
@dataclass(slots=True)
|
|
40
29
|
class _ApplicationStateRoot:
|
|
41
|
-
"""
|
|
30
|
+
"""只拥有核心状态;能力的 Catalog、实例和协调器分别归图内的 Module。"""
|
|
42
31
|
|
|
43
32
|
application_id: UUID
|
|
44
33
|
registry: ModuleRegistry
|
|
34
|
+
discoveries: tuple[_DiscoveredType, ...]
|
|
45
35
|
configuration: ConfigurationRoot
|
|
46
36
|
pre_options_registry: _OptionsRegistry
|
|
47
37
|
options_registry: _OptionsRegistry
|
|
48
38
|
service_registry: ServiceContributionRegistry
|
|
49
|
-
event_registry: EventContributionRegistry
|
|
50
|
-
permission_catalog: PermissionCatalog
|
|
51
|
-
cache_catalog: CacheCatalog
|
|
52
|
-
setting_catalog: SettingCatalog
|
|
53
|
-
background_worker_catalog: BackgroundWorkerCatalog
|
|
54
|
-
background_job_catalog: BackgroundJobCatalog
|
|
55
|
-
hosted_service_catalog: HostedServiceCatalog
|
|
56
39
|
journal: _DiagnosticJournal
|
|
57
40
|
pre_options: PreOptionsSnapshot | None = None
|
|
58
41
|
options: OptionsSnapshot | None = None
|
|
59
42
|
service_catalog: ServiceCatalog | None = None
|
|
60
|
-
application_service_catalog: ApplicationServiceCatalog | None = None
|
|
61
|
-
event_catalog: LocalEventCatalog | None = None
|
|
62
|
-
invocation_runtime: _InvocationRuntime | None = None
|
|
63
|
-
background_coordinator: _BackgroundCoordinator | None = None
|
|
64
|
-
hosted_service_coordinator: _HostedServiceCoordinator | None = None
|
|
65
43
|
container: AsyncContainer | None = None
|
|
66
44
|
state: ApplicationState = ApplicationState.COMPOSED
|
|
67
45
|
transition_token: object | None = None
|
|
@@ -72,6 +50,8 @@ class _ApplicationStateRoot:
|
|
|
72
50
|
runtime_failure: ApplicationRuntimeError | None = None
|
|
73
51
|
termination_failure: BaseException | None = None
|
|
74
52
|
runtime_monitor_task: asyncio.Task[None] | None = None
|
|
53
|
+
runtimes: tuple[RuntimeParticipant, ...] = ()
|
|
54
|
+
touched_runtimes: list[RuntimeParticipant] = field(default_factory=list)
|
|
75
55
|
|
|
76
56
|
def require_pre_options(self) -> PreOptionsSnapshot:
|
|
77
57
|
if self.pre_options is None:
|
|
@@ -95,54 +75,6 @@ class _ApplicationStateRoot:
|
|
|
95
75
|
raise RuntimeError("Dishka container is unavailable after Application cleanup")
|
|
96
76
|
return self.container
|
|
97
77
|
|
|
98
|
-
def require_application_service_catalog(self) -> ApplicationServiceCatalog:
|
|
99
|
-
if self.application_service_catalog is None:
|
|
100
|
-
raise RuntimeError(
|
|
101
|
-
"Application Service catalog is unavailable before composition completes"
|
|
102
|
-
)
|
|
103
|
-
return self.application_service_catalog
|
|
104
|
-
|
|
105
|
-
def require_event_catalog(self) -> LocalEventCatalog:
|
|
106
|
-
if self.event_catalog is None:
|
|
107
|
-
raise RuntimeError("Local Event catalog is unavailable before composition completes")
|
|
108
|
-
return self.event_catalog
|
|
109
|
-
|
|
110
|
-
def require_permission_catalog(self) -> PermissionCatalog:
|
|
111
|
-
return self.permission_catalog
|
|
112
|
-
|
|
113
|
-
def require_cache_catalog(self) -> CacheCatalog:
|
|
114
|
-
return self.cache_catalog
|
|
115
|
-
|
|
116
|
-
def require_setting_catalog(self) -> SettingCatalog:
|
|
117
|
-
return self.setting_catalog
|
|
118
|
-
|
|
119
|
-
def require_background_worker_catalog(self) -> BackgroundWorkerCatalog:
|
|
120
|
-
return self.background_worker_catalog
|
|
121
|
-
|
|
122
|
-
def require_background_job_catalog(self) -> BackgroundJobCatalog:
|
|
123
|
-
return self.background_job_catalog
|
|
124
|
-
|
|
125
|
-
def require_invocation_runtime(self) -> _InvocationRuntime:
|
|
126
|
-
if self.invocation_runtime is None:
|
|
127
|
-
raise RuntimeError("Application invocation runtime is unavailable")
|
|
128
|
-
return self.invocation_runtime
|
|
129
|
-
|
|
130
|
-
def require_background_coordinator(self) -> _BackgroundCoordinator:
|
|
131
|
-
if self.background_coordinator is None:
|
|
132
|
-
raise RuntimeError("Application background coordinator is unavailable")
|
|
133
|
-
return self.background_coordinator
|
|
134
|
-
|
|
135
|
-
def require_hosted_service_catalog(self) -> HostedServiceCatalog:
|
|
136
|
-
return self.hosted_service_catalog
|
|
137
|
-
|
|
138
|
-
def require_hosted_service_coordinator(self) -> _HostedServiceCoordinator:
|
|
139
|
-
if self.hosted_service_coordinator is None:
|
|
140
|
-
raise RuntimeError("Hosted Service coordinator is unavailable")
|
|
141
|
-
return self.hosted_service_coordinator
|
|
142
|
-
|
|
143
|
-
def readiness(self) -> ApplicationReadinessSnapshot:
|
|
144
|
-
return self.require_hosted_service_coordinator().readiness()
|
|
145
|
-
|
|
146
78
|
def view(self, state: ApplicationState | None) -> ApplicationView:
|
|
147
79
|
return ApplicationView(
|
|
148
80
|
application_id=self.application_id,
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
from
|
|
2
|
-
from .catalog import (
|
|
3
|
-
ApplicationServiceCatalog,
|
|
4
|
-
ApplicationServiceMethod,
|
|
5
|
-
ApplicationServiceRegistration,
|
|
6
|
-
)
|
|
1
|
+
from .catalog import ApplicationServiceCatalog, ApplicationServiceRegistration
|
|
7
2
|
from .contracts import (
|
|
8
3
|
ApplicationService,
|
|
9
4
|
ApplicationServiceContract,
|
|
@@ -20,35 +15,24 @@ from .errors import (
|
|
|
20
15
|
CrossTaskInvocationError,
|
|
21
16
|
NestedConnectionError,
|
|
22
17
|
)
|
|
23
|
-
from .
|
|
24
|
-
from .interceptors import InterceptionStage, Interceptor, InterceptorRegistration
|
|
25
|
-
from .policies import disable_validation, enable_validation
|
|
26
|
-
from .seeding import DataSeedContributor, DataSeeder, DataSeedError
|
|
18
|
+
from .pagination import PagedAndSortedResultRequestDto, PagedResult, PagedResultRequestDto
|
|
27
19
|
|
|
28
20
|
__all__ = (
|
|
29
21
|
"ApplicationInvocationCleanupError",
|
|
30
|
-
"ApplicationInvocationContext",
|
|
31
22
|
"ApplicationInvocationRejectedError",
|
|
32
23
|
"ApplicationService",
|
|
33
24
|
"ApplicationServiceCatalog",
|
|
34
25
|
"ApplicationServiceContract",
|
|
35
26
|
"ApplicationServiceDefinitionError",
|
|
36
|
-
"ApplicationServiceMethod",
|
|
37
27
|
"ApplicationServiceMethodError",
|
|
38
28
|
"ApplicationServiceRegistration",
|
|
39
29
|
"ApplicationServiceValidationError",
|
|
40
30
|
"ApplicationServiceValidationIssue",
|
|
41
31
|
"CrossTaskInvocationError",
|
|
42
|
-
"DataSeedContributor",
|
|
43
|
-
"DataSeedError",
|
|
44
|
-
"DataSeeder",
|
|
45
32
|
"IntegrationService",
|
|
46
|
-
"InterceptionStage",
|
|
47
|
-
"Interceptor",
|
|
48
|
-
"InterceptorRegistration",
|
|
49
|
-
"MethodInvocation",
|
|
50
33
|
"NestedConnectionError",
|
|
34
|
+
"PagedAndSortedResultRequestDto",
|
|
35
|
+
"PagedResult",
|
|
36
|
+
"PagedResultRequestDto",
|
|
51
37
|
"application_service",
|
|
52
|
-
"disable_validation",
|
|
53
|
-
"enable_validation",
|
|
54
38
|
)
|