python-ddd-framework 0.3.2__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_ddd_framework/__init__.py +139 -21
- python_ddd_framework/application/builder.py +23 -78
- python_ddd_framework/application/composition.py +4 -22
- python_ddd_framework/application/runtime.py +120 -288
- python_ddd_framework/application/state.py +7 -75
- python_ddd_framework/application_services/__init__.py +5 -21
- python_ddd_framework/{services/application_bindings.py → application_services/bindings.py} +52 -9
- python_ddd_framework/application_services/catalog.py +36 -288
- python_ddd_framework/application_services/contracts.py +14 -13
- python_ddd_framework/application_services/dispatcher.py +19 -269
- python_ddd_framework/application_services/invocation.py +197 -15
- python_ddd_framework/application_services/module.py +168 -0
- python_ddd_framework/application_services/pagination.py +21 -0
- python_ddd_framework/auditing/control.py +1 -1
- python_ddd_framework/auditing/module.py +15 -0
- python_ddd_framework/authorization/__init__.py +2 -0
- python_ddd_framework/authorization/contracts.py +7 -2
- python_ddd_framework/authorization/definitions.py +5 -0
- python_ddd_framework/authorization/module.py +40 -0
- python_ddd_framework/authorization/options.py +7 -0
- python_ddd_framework/background_execution/child.py +13 -3
- python_ddd_framework/background_execution/declarations.py +45 -0
- python_ddd_framework/background_execution/lifecycle.py +12 -0
- python_ddd_framework/background_execution/local.py +20 -1
- python_ddd_framework/background_execution/module.py +107 -1
- python_ddd_framework/background_execution/processes.py +11 -1
- python_ddd_framework/background_jobs/catalog.py +59 -42
- python_ddd_framework/background_jobs/contracts.py +16 -19
- python_ddd_framework/background_jobs/pgqueuer/enqueue.py +4 -0
- python_ddd_framework/background_jobs/pgqueuer/module.py +2 -1
- python_ddd_framework/background_jobs/pgqueuer/runtime.py +7 -7
- python_ddd_framework/background_workers/catalog.py +23 -36
- python_ddd_framework/background_workers/contracts.py +3 -0
- python_ddd_framework/background_workers/execution.py +1 -1
- python_ddd_framework/background_workers/runtime.py +47 -1
- python_ddd_framework/caching/__init__.py +7 -6
- python_ddd_framework/caching/contracts.py +65 -67
- python_ddd_framework/caching/errors.py +0 -7
- python_ddd_framework/caching/module.py +18 -0
- python_ddd_framework/caching/naming.py +29 -0
- python_ddd_framework/caching/options.py +38 -0
- python_ddd_framework/caching/unit_of_work.py +46 -0
- python_ddd_framework/cli/__init__.py +31 -6
- python_ddd_framework/cli/project.py +68 -6
- python_ddd_framework/cli/runtime.py +1 -1
- python_ddd_framework/developer_kit/generation.py +75 -22
- python_ddd_framework/developer_kit/project_metadata.py +2 -4
- python_ddd_framework/developer_kit/templates/basic/cookiecutter.json +4 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/README.md +45 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/conversion_service.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/module.py.jinja +38 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/default_conversion_service.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +42 -49
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +45 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +8 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +35 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +5 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +15 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +28 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +26 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +23 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_integration_service.py.jinja +71 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +16 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +16 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +105 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{options.py.jinja → options/order_options.py.jinja} +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +65 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +35 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +41 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +6 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +39 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +12 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +12 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{orders.py.jinja → entities/order.py.jinja} +10 -18
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +42 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +22 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +23 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{settings.py.jinja → settings/approval_settings.py.jinja} +3 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +3 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +6 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +11 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +26 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +40 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/{permissions.py.jinja → permissions/order_permission_provider.py.jinja} +6 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +18 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +15 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +5 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +85 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/{files.py.jinja → routers/order_files.py.jinja} +23 -20
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +44 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +1 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/base.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/{orders.py.jinja → order_model.py.jinja} +7 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +42 -5
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +79 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +4 -3
- python_ddd_framework/developer_kit/templates/project/cookiecutter.json +1 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +23 -3
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +29 -22
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{app.development.yaml.jinja → backend/app.development.yaml.jinja} +2 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{pyproject.toml.jinja → backend/pyproject.toml.jinja} +1 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/main.py.jinja +2 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/module.py.jinja +40 -3
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +42 -11
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +144 -23
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/scripts/.gitkeep +0 -0
- python_ddd_framework/distributed_lock/contracts.py +4 -1
- python_ddd_framework/domain/__init__.py +2 -0
- python_ddd_framework/domain/services.py +11 -0
- python_ddd_framework/errors/business.py +10 -1
- python_ddd_framework/errors/lifecycle.py +3 -31
- python_ddd_framework/events/__init__.py +5 -2
- python_ddd_framework/events/aggregate.py +3 -3
- python_ddd_framework/events/catalog.py +12 -11
- python_ddd_framework/events/contracts.py +6 -1
- python_ddd_framework/events/contribution.py +1 -52
- python_ddd_framework/events/entities.py +23 -0
- python_ddd_framework/events/module.py +97 -0
- python_ddd_framework/events/runtime.py +80 -29
- python_ddd_framework/events/subscription.py +36 -0
- python_ddd_framework/events/unit_of_work.py +25 -0
- python_ddd_framework/extensions/__init__.py +22 -0
- python_ddd_framework/extensions/catalog.py +198 -0
- python_ddd_framework/extensions/contracts.py +85 -0
- python_ddd_framework/extensions/models.py +142 -0
- python_ddd_framework/extensions/module.py +21 -0
- python_ddd_framework/fastapi/__init__.py +2 -0
- python_ddd_framework/fastapi/action.py +1 -2
- python_ddd_framework/fastapi/adapter.py +16 -0
- python_ddd_framework/fastapi/application_services.py +43 -25
- python_ddd_framework/fastapi/background.py +2 -1
- python_ddd_framework/fastapi/contracts.py +26 -8
- python_ddd_framework/fastapi/health.py +1 -1
- python_ddd_framework/fastapi/module.py +14 -0
- python_ddd_framework/fastapi/parameters.py +3 -18
- python_ddd_framework/fastapi/realtime/connection.py +10 -7
- python_ddd_framework/fastapi/realtime/module.py +3 -0
- python_ddd_framework/fastapi/realtime/runtime.py +10 -24
- python_ddd_framework/fastapi/request_context.py +2 -1
- python_ddd_framework/fastapi/routing.py +2 -1
- python_ddd_framework/fastapi/server.py +8 -0
- python_ddd_framework/fastapi/settings.py +2 -1
- python_ddd_framework/hosted_services/bridge.py +95 -17
- python_ddd_framework/hosted_services/catalog.py +6 -16
- python_ddd_framework/hosted_services/contracts.py +9 -1
- python_ddd_framework/hosted_services/declarations.py +15 -0
- python_ddd_framework/hosted_services/module.py +69 -0
- python_ddd_framework/hosted_services/runtime.py +101 -18
- python_ddd_framework/identity/__init__.py +24 -0
- python_ddd_framework/identity/application.py +38 -90
- python_ddd_framework/identity/contracts.py +181 -16
- python_ddd_framework/identity/http_api.py +79 -2
- python_ddd_framework/identity/management.py +166 -0
- python_ddd_framework/identity/module.py +12 -4
- python_ddd_framework/identity/services.py +29 -16
- python_ddd_framework/identity/sqlalchemy/migrations/0003_extra_properties.py +26 -0
- python_ddd_framework/identity/sqlalchemy/migrations/0004_concurrency_version.py +29 -0
- python_ddd_framework/identity/sqlalchemy/models.py +21 -2
- python_ddd_framework/identity/sqlalchemy/module.py +11 -2
- python_ddd_framework/identity/sqlalchemy/repository.py +458 -0
- python_ddd_framework/identity/sqlalchemy/session_security.py +88 -0
- python_ddd_framework/identity/sqlalchemy/stores.py +69 -290
- python_ddd_framework/identity/tokens.py +2 -5
- python_ddd_framework/{application_services/validation.py → invocation/arguments.py} +8 -6
- python_ddd_framework/invocation/contribution.py +61 -0
- python_ddd_framework/invocation/dispatcher.py +291 -0
- python_ddd_framework/invocation/entries.py +2 -25
- python_ddd_framework/invocation/entrypoints.py +126 -0
- python_ddd_framework/{application_services → invocation}/execution.py +8 -5
- python_ddd_framework/invocation/function_runtime.py +3 -1
- python_ddd_framework/invocation/interception.py +113 -42
- python_ddd_framework/{application_services → invocation}/interceptors.py +2 -2
- python_ddd_framework/invocation/managed_proxy.py +116 -0
- python_ddd_framework/invocation/managed_services.py +250 -0
- python_ddd_framework/invocation/methods.py +345 -0
- python_ddd_framework/invocation/module.py +113 -0
- python_ddd_framework/{application_services → invocation}/policies.py +8 -1
- python_ddd_framework/invocation/validation.py +25 -0
- python_ddd_framework/lifecycle/__init__.py +2 -0
- python_ddd_framework/lifecycle/composition.py +5 -2
- python_ddd_framework/lifecycle/participants.py +37 -0
- python_ddd_framework/messaging/__init__.py +25 -0
- python_ddd_framework/messaging/channel.py +232 -0
- python_ddd_framework/messaging/contracts.py +78 -0
- python_ddd_framework/messaging/module.py +114 -0
- python_ddd_framework/messaging/options.py +13 -0
- python_ddd_framework/messaging/receipt.py +27 -0
- python_ddd_framework/messaging/runtime.py +139 -0
- python_ddd_framework/modularity/__init__.py +11 -1
- python_ddd_framework/modularity/contracts.py +47 -16
- python_ddd_framework/modularity/discovery.py +21 -2
- python_ddd_framework/modularity/graph.py +45 -1
- python_ddd_framework/modularity/registry.py +83 -3
- python_ddd_framework/notifications/__init__.py +2 -0
- python_ddd_framework/notifications/catalog.py +3 -2
- python_ddd_framework/notifications/declarations.py +18 -0
- python_ddd_framework/notifications/module.py +21 -0
- python_ddd_framework/observability/logging.py +3 -0
- python_ddd_framework/observability/tracing.py +3 -0
- python_ddd_framework/options/contribution.py +12 -0
- python_ddd_framework/options/registry.py +44 -11
- python_ddd_framework/realtime/__init__.py +2 -1
- python_ddd_framework/realtime/contracts.py +3 -7
- python_ddd_framework/realtime/messages.py +26 -1
- python_ddd_framework/redis/cache.py +243 -0
- python_ddd_framework/redis/cache_scripts.py +40 -0
- python_ddd_framework/redis/distributed_lock.py +10 -2
- python_ddd_framework/redis/module.py +13 -4
- python_ddd_framework/redis/notifications.py +5 -2
- python_ddd_framework/redis/runtime.py +1 -79
- python_ddd_framework/seeding/__init__.py +13 -0
- python_ddd_framework/seeding/contracts.py +45 -0
- python_ddd_framework/seeding/module.py +73 -0
- python_ddd_framework/seeding/runtime.py +63 -0
- python_ddd_framework/services/arbitration.py +23 -0
- python_ddd_framework/services/binding.py +2 -3
- python_ddd_framework/services/composition.py +108 -0
- python_ddd_framework/services/contribution.py +44 -54
- python_ddd_framework/services/convention.py +53 -181
- python_ddd_framework/services/fixed_lifetime.py +12 -15
- python_ddd_framework/services/framework_provider.py +3 -158
- python_ddd_framework/services/native_graph.py +35 -60
- python_ddd_framework/services/provider.py +59 -4
- python_ddd_framework/services/repository.py +6 -0
- python_ddd_framework/services/runtime.py +129 -215
- python_ddd_framework/settings/catalog.py +5 -4
- python_ddd_framework/settings/declarations.py +18 -0
- python_ddd_framework/settings/definitions.py +5 -0
- python_ddd_framework/settings/manager.py +1 -1
- python_ddd_framework/settings/module.py +50 -1
- python_ddd_framework/settings/notifications.py +4 -2
- python_ddd_framework/settings/refresh_module.py +9 -2
- python_ddd_framework/settings/sqlalchemy/module.py +2 -1
- python_ddd_framework/sqlalchemy/__init__.py +12 -0
- python_ddd_framework/sqlalchemy/alembic_runtime/env.py +1 -0
- python_ddd_framework/sqlalchemy/auditing.py +7 -1
- python_ddd_framework/sqlalchemy/extensions.py +186 -0
- python_ddd_framework/sqlalchemy/metadata.py +110 -6
- python_ddd_framework/sqlalchemy/migration.py +61 -8
- python_ddd_framework/sqlalchemy/migration_operations.py +86 -0
- python_ddd_framework/sqlalchemy/module.py +14 -11
- python_ddd_framework/sqlalchemy/module_migration.py +71 -3
- python_ddd_framework/sqlalchemy/repository.py +217 -13
- python_ddd_framework/sqlalchemy/session_provider.py +14 -1
- python_ddd_framework/testing/runtime.py +5 -4
- python_ddd_framework/unit_of_work/__init__.py +2 -1
- python_ddd_framework/unit_of_work/contracts.py +6 -7
- python_ddd_framework/unit_of_work/errors.py +11 -0
- python_ddd_framework/unit_of_work/lifecycle.py +19 -0
- python_ddd_framework/unit_of_work/manager.py +86 -32
- python_ddd_framework/unit_of_work/module.py +61 -0
- python_ddd_framework/unit_of_work/options.py +7 -1
- python_ddd_framework/validation/__init__.py +1 -0
- python_ddd_framework/validation/models.py +37 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/METADATA +147 -44
- python_ddd_framework-0.5.1.dist-info/RECORD +488 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/WHEEL +1 -1
- python_ddd_framework/application_services/seeding.py +0 -72
- python_ddd_framework/caching/catalog.py +0 -73
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +0 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +0 -24
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration.py.jinja +0 -41
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +0 -83
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +0 -84
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +0 -35
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +0 -14
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +0 -19
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +0 -24
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +0 -36
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +0 -49
- python_ddd_framework-0.3.2.dist-info/RECORD +0 -355
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.dockerignore → backend/.dockerignore} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.gitignore → backend/.gitignore} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.python-version → backend/.python-version} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{Dockerfile → backend/Dockerfile} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.dev.yaml.jinja → backend/compose.dev.yaml.jinja} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.production.yaml.jinja → backend/compose.production.yaml.jinja} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/__init__.py.jinja +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/conftest.py.jinja +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/host/test_http.py.jinja +0 -0
- /python_ddd_framework/{application_services → invocation}/signature.py +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.5.1.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
|
@@ -1,67 +1,31 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Application 状态、通用生命周期和容器所有权。"""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
from collections.abc import
|
|
7
|
-
from
|
|
8
|
-
from typing import
|
|
9
|
-
from uuid import UUID
|
|
10
|
-
|
|
11
|
-
from dishka import AsyncContainer
|
|
12
|
-
|
|
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
|
|
6
|
+
from collections.abc import Awaitable, Callable, Iterable
|
|
7
|
+
from functools import partial
|
|
8
|
+
from typing import Never
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
|
|
11
|
+
from dishka import AsyncContainer
|
|
12
|
+
|
|
27
13
|
from ..configuration import ConfigurationRoot
|
|
28
14
|
from ..diagnostics import DiagnosticSnapshot
|
|
29
15
|
from ..errors import (
|
|
30
16
|
ApplicationRuntimeError,
|
|
31
17
|
ApplicationStartError,
|
|
32
|
-
ContainerCleanupError,
|
|
33
18
|
InvalidApplicationStateError,
|
|
34
19
|
LifecycleInvocationError,
|
|
35
20
|
)
|
|
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
|
|
21
|
+
from ..errors.lifecycle import _exclude_runtime_failure
|
|
47
22
|
from ..lifecycle import ApplicationState, ShutdownReason
|
|
48
23
|
from ..modularity import ModuleRegistry
|
|
49
24
|
from ..options import OptionsSnapshot
|
|
50
25
|
from ..services import ServiceCatalog
|
|
51
|
-
from ..settings import SettingCatalog
|
|
52
26
|
from .runtime_hooks import _RuntimeLifecycle
|
|
53
27
|
from .state import _ApplicationStateRoot
|
|
54
28
|
|
|
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
29
|
|
|
66
30
|
class Application:
|
|
67
31
|
"""拥有单次 build 的 Module instances、状态、transition 与 diagnostics。"""
|
|
@@ -96,40 +60,10 @@ class Application:
|
|
|
96
60
|
return self._root.require_service_catalog()
|
|
97
61
|
|
|
98
62
|
@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()
|
|
63
|
+
def is_ready(self) -> bool:
|
|
64
|
+
return self.state is ApplicationState.RUNNING and all(
|
|
65
|
+
participant.ready is None or participant.ready() for participant in self._root.runtimes
|
|
66
|
+
)
|
|
133
67
|
|
|
134
68
|
@property
|
|
135
69
|
def diagnostics(self) -> DiagnosticSnapshot:
|
|
@@ -148,41 +82,27 @@ class Application:
|
|
|
148
82
|
allowed=(ApplicationState.COMPOSED,),
|
|
149
83
|
)
|
|
150
84
|
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
85
|
lifecycle = _RuntimeLifecycle(self._root)
|
|
155
86
|
started = False
|
|
156
87
|
try:
|
|
157
88
|
try:
|
|
89
|
+
self._root.registry.assert_declarations_unchanged()
|
|
90
|
+
for participant in self._root.runtimes:
|
|
91
|
+
if participant.prepare is not None:
|
|
92
|
+
participant.prepare(run_services)
|
|
158
93
|
await lifecycle.start_modules()
|
|
159
|
-
|
|
94
|
+
self._root.registry.assert_declarations_unchanged()
|
|
95
|
+
# 全部 Module 初始化屏障完成后才开放运行入口;部分启动也必须逆序清理。
|
|
96
|
+
for participant in self._root.runtimes:
|
|
97
|
+
self._root.touched_runtimes.append(participant)
|
|
98
|
+
await participant.start()
|
|
99
|
+
except BaseException as primary:
|
|
160
100
|
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
101
|
self._root.state = ApplicationState.RUNNING
|
|
182
102
|
started = True
|
|
183
103
|
finally:
|
|
184
104
|
self._root.release_transition(token)
|
|
185
|
-
if started and (self._root.
|
|
105
|
+
if started and any(item.failure is not None for item in self._root.runtimes):
|
|
186
106
|
self._root.runtime_monitor_task = asyncio.create_task(
|
|
187
107
|
self._monitor_runtime_failure(),
|
|
188
108
|
name=f"python-ddd-framework-runtime-monitor:{self.id}",
|
|
@@ -192,9 +112,7 @@ class Application:
|
|
|
192
112
|
self,
|
|
193
113
|
lifecycle: _RuntimeLifecycle,
|
|
194
114
|
*,
|
|
195
|
-
primary:
|
|
196
|
-
| HostedServiceLifecycleError
|
|
197
|
-
| _BackgroundExecutionStartFailure,
|
|
115
|
+
primary: BaseException,
|
|
198
116
|
) -> Never:
|
|
199
117
|
self._root.state = ApplicationState.ROLLING_BACK
|
|
200
118
|
# 回滚 task 由本次 start 强持有;重复取消只能在所有资源实际退出后返回。
|
|
@@ -217,64 +135,48 @@ class Application:
|
|
|
217
135
|
self,
|
|
218
136
|
lifecycle: _RuntimeLifecycle,
|
|
219
137
|
*,
|
|
220
|
-
primary:
|
|
221
|
-
| HostedServiceLifecycleError
|
|
222
|
-
| _BackgroundExecutionStartFailure,
|
|
138
|
+
primary: BaseException,
|
|
223
139
|
) -> ApplicationStartError:
|
|
140
|
+
runtime_failures = await self._stop_runtimes(ShutdownReason.START_FAILURE)
|
|
141
|
+
module_failures = await lifecycle.shutdown_touched(reason=ShutdownReason.START_FAILURE)
|
|
142
|
+
container_failures = await lifecycle.close_container(
|
|
143
|
+
exception=primary.original_error
|
|
144
|
+
if isinstance(primary, LifecycleInvocationError)
|
|
145
|
+
else primary
|
|
146
|
+
)
|
|
224
147
|
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
148
|
self._root.journal.complete_failure(
|
|
232
149
|
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,
|
|
150
|
+
cleanup_failure_ids=tuple(failure.failure_id for failure in module_failures)
|
|
151
|
+
+ tuple(failure.failure_id for failure in container_failures),
|
|
238
152
|
)
|
|
239
153
|
return ApplicationStartError(
|
|
240
154
|
primary=primary,
|
|
241
|
-
cleanup_failures=
|
|
155
|
+
cleanup_failures=(*runtime_failures, *module_failures, *container_failures),
|
|
242
156
|
)
|
|
243
157
|
|
|
244
|
-
async def
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
# 消费者必须先实际退出,才可释放它们依赖的 HostedService。
|
|
254
|
-
try:
|
|
255
|
-
await self._root.require_background_coordinator().stop()
|
|
256
|
-
except BaseException as background_error:
|
|
257
|
-
cleanup_failures.append(
|
|
258
|
-
_BackgroundExecutionStartFailure(original_error=background_error)
|
|
158
|
+
async def _stop_runtimes(self, reason: ShutdownReason) -> tuple[BaseException, ...]:
|
|
159
|
+
failures: list[BaseException] = []
|
|
160
|
+
# 每个阶段都是全局屏障;收尾与排空完成前,不释放任何参与者的依赖资源。
|
|
161
|
+
# 用户钩子只交给触达过 start 的参与者;预启动请求仍由独立 drain 收口。
|
|
162
|
+
participants = tuple(reversed(self._root.touched_runtimes))
|
|
163
|
+
failures.extend(_call_shutdown_callbacks(item.quiesce for item in participants))
|
|
164
|
+
failures.extend(
|
|
165
|
+
await _await_shutdown_callbacks(
|
|
166
|
+
partial(item.stopping, reason) for item in participants if item.stopping is not None
|
|
259
167
|
)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
service="hosted-service-coordinator",
|
|
266
|
-
operation="stop",
|
|
267
|
-
error_type=type(hosted_error).__name__,
|
|
268
|
-
)
|
|
168
|
+
)
|
|
169
|
+
failures.extend(_call_shutdown_callbacks(item.close_admission for item in participants))
|
|
170
|
+
failures.extend(
|
|
171
|
+
await _await_shutdown_callbacks(
|
|
172
|
+
item.drain for item in reversed(self._root.runtimes) if item.drain is not None
|
|
269
173
|
)
|
|
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
174
|
)
|
|
276
|
-
|
|
277
|
-
|
|
175
|
+
failures.extend(
|
|
176
|
+
await _await_shutdown_callbacks(partial(item.stop, reason) for item in participants)
|
|
177
|
+
)
|
|
178
|
+
self._root.touched_runtimes.clear()
|
|
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,64 @@ 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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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}"
|
|
321
|
+
async def _wait_for_runtime_failure(self) -> ApplicationRuntimeError | None:
|
|
322
|
+
watchers = []
|
|
323
|
+
try:
|
|
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__
|
|
487
348
|
)
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
|
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)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _call_shutdown_callbacks(
|
|
358
|
+
callbacks: Iterable[Callable[[], None] | None],
|
|
359
|
+
) -> tuple[BaseException, ...]:
|
|
360
|
+
failures: list[BaseException] = []
|
|
361
|
+
for callback in callbacks:
|
|
362
|
+
if callback is not None:
|
|
363
|
+
try:
|
|
364
|
+
callback()
|
|
365
|
+
except BaseException as error:
|
|
366
|
+
failures.append(error)
|
|
367
|
+
return tuple(failures)
|
|
513
368
|
|
|
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
369
|
|
|
522
|
-
|
|
523
|
-
|
|
370
|
+
async def _await_shutdown_callbacks(
|
|
371
|
+
callbacks: Iterable[Callable[[], Awaitable[tuple[BaseException, ...] | None]]],
|
|
372
|
+
) -> tuple[BaseException, ...]:
|
|
373
|
+
failures: list[BaseException] = []
|
|
374
|
+
for callback in callbacks:
|
|
524
375
|
try:
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
registration: ApplicationServiceRegistration,
|
|
532
|
-
method_name: str,
|
|
533
|
-
) -> object:
|
|
534
|
-
"""框架内部 contributor 仍走 dispatcher,但不发布 public single Contract。"""
|
|
535
|
-
|
|
536
|
-
if self._root.state not in (ApplicationState.STARTING, ApplicationState.RUNNING):
|
|
537
|
-
raise ApplicationInvocationRejectedError(
|
|
538
|
-
reason=f"Application must be running, got {self._root.state.value}"
|
|
539
|
-
)
|
|
540
|
-
service = self.application_service_catalog._for_registration(registration)
|
|
541
|
-
method = service.methods.get(method_name)
|
|
542
|
-
if method is None:
|
|
543
|
-
raise AssertionError("internal contributor method is absent from its catalog entry")
|
|
544
|
-
container = self._root.require_container()
|
|
545
|
-
async with self._invocation_lease_for_integration():
|
|
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, (), {})
|
|
376
|
+
result = await callback()
|
|
377
|
+
if result is not None:
|
|
378
|
+
failures.extend(result)
|
|
379
|
+
except BaseException as error:
|
|
380
|
+
failures.append(error)
|
|
381
|
+
return tuple(failures)
|
|
@@ -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,
|