python-ddd-framework 0.3.2__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_ddd_framework/__init__.py +139 -21
- python_ddd_framework/application/builder.py +23 -78
- python_ddd_framework/application/composition.py +4 -22
- python_ddd_framework/application/runtime.py +96 -291
- python_ddd_framework/application/state.py +7 -75
- python_ddd_framework/application_services/__init__.py +5 -21
- python_ddd_framework/{services/application_bindings.py → application_services/bindings.py} +52 -9
- python_ddd_framework/application_services/catalog.py +36 -288
- python_ddd_framework/application_services/contracts.py +14 -13
- python_ddd_framework/application_services/dispatcher.py +19 -269
- python_ddd_framework/application_services/invocation.py +126 -7
- python_ddd_framework/application_services/module.py +168 -0
- python_ddd_framework/application_services/pagination.py +21 -0
- python_ddd_framework/auditing/control.py +1 -1
- python_ddd_framework/auditing/module.py +15 -0
- python_ddd_framework/authorization/__init__.py +2 -0
- python_ddd_framework/authorization/contracts.py +7 -2
- python_ddd_framework/authorization/definitions.py +5 -0
- python_ddd_framework/authorization/module.py +40 -0
- python_ddd_framework/authorization/options.py +7 -0
- python_ddd_framework/background_execution/child.py +13 -3
- python_ddd_framework/background_execution/declarations.py +45 -0
- python_ddd_framework/background_execution/lifecycle.py +12 -0
- python_ddd_framework/background_execution/module.py +107 -1
- python_ddd_framework/background_jobs/catalog.py +59 -42
- python_ddd_framework/background_jobs/contracts.py +16 -19
- python_ddd_framework/background_jobs/pgqueuer/enqueue.py +4 -0
- python_ddd_framework/background_jobs/pgqueuer/module.py +2 -1
- python_ddd_framework/background_jobs/pgqueuer/runtime.py +7 -7
- python_ddd_framework/background_workers/catalog.py +23 -36
- python_ddd_framework/background_workers/contracts.py +3 -0
- python_ddd_framework/background_workers/execution.py +1 -1
- python_ddd_framework/background_workers/runtime.py +38 -1
- python_ddd_framework/caching/__init__.py +7 -6
- python_ddd_framework/caching/contracts.py +65 -67
- python_ddd_framework/caching/errors.py +0 -7
- python_ddd_framework/caching/module.py +18 -0
- python_ddd_framework/caching/naming.py +29 -0
- python_ddd_framework/caching/options.py +38 -0
- python_ddd_framework/caching/unit_of_work.py +46 -0
- python_ddd_framework/cli/__init__.py +31 -6
- python_ddd_framework/cli/project.py +68 -6
- python_ddd_framework/cli/runtime.py +1 -1
- python_ddd_framework/developer_kit/generation.py +75 -22
- python_ddd_framework/developer_kit/project_metadata.py +2 -4
- python_ddd_framework/developer_kit/templates/basic/cookiecutter.json +4 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/README.md +45 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/conversion_service.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/module.py.jinja +38 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/default_conversion_service.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +41 -49
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +45 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +8 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +35 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +5 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +15 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +28 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +26 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +23 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{integration.py.jinja → hosted_services/order_integration_service.py.jinja} +8 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +16 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +16 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +105 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{options.py.jinja → options/order_options.py.jinja} +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +65 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +35 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +41 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +6 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +39 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +12 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +12 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{orders.py.jinja → entities/order.py.jinja} +10 -18
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +42 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +22 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +23 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{settings.py.jinja → settings/approval_settings.py.jinja} +3 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +3 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +6 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +11 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +26 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +40 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/{permissions.py.jinja → permissions/order_permission_provider.py.jinja} +6 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +18 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +15 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +5 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +85 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/{files.py.jinja → routers/order_files.py.jinja} +23 -20
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +44 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +1 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/base.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/{orders.py.jinja → order_model.py.jinja} +7 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +42 -5
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +79 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +4 -3
- python_ddd_framework/developer_kit/templates/project/cookiecutter.json +1 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +16 -3
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +29 -22
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{app.development.yaml.jinja → backend/app.development.yaml.jinja} +2 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{pyproject.toml.jinja → backend/pyproject.toml.jinja} +1 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/main.py.jinja +2 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/module.py.jinja +40 -3
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +40 -10
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +134 -23
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/scripts/.gitkeep +0 -0
- python_ddd_framework/distributed_lock/contracts.py +4 -1
- python_ddd_framework/domain/__init__.py +2 -0
- python_ddd_framework/domain/services.py +11 -0
- python_ddd_framework/errors/business.py +10 -1
- python_ddd_framework/errors/lifecycle.py +3 -31
- python_ddd_framework/events/__init__.py +5 -2
- python_ddd_framework/events/aggregate.py +3 -3
- python_ddd_framework/events/catalog.py +12 -11
- python_ddd_framework/events/contracts.py +6 -1
- python_ddd_framework/events/contribution.py +1 -52
- python_ddd_framework/events/entities.py +23 -0
- python_ddd_framework/events/module.py +97 -0
- python_ddd_framework/events/runtime.py +80 -29
- python_ddd_framework/events/subscription.py +36 -0
- python_ddd_framework/events/unit_of_work.py +25 -0
- python_ddd_framework/extensions/__init__.py +22 -0
- python_ddd_framework/extensions/catalog.py +198 -0
- python_ddd_framework/extensions/contracts.py +85 -0
- python_ddd_framework/extensions/models.py +142 -0
- python_ddd_framework/extensions/module.py +21 -0
- python_ddd_framework/fastapi/__init__.py +2 -0
- python_ddd_framework/fastapi/action.py +1 -2
- python_ddd_framework/fastapi/adapter.py +16 -0
- python_ddd_framework/fastapi/application_services.py +43 -25
- python_ddd_framework/fastapi/background.py +2 -1
- python_ddd_framework/fastapi/contracts.py +26 -8
- python_ddd_framework/fastapi/health.py +1 -1
- python_ddd_framework/fastapi/module.py +14 -0
- python_ddd_framework/fastapi/parameters.py +3 -18
- python_ddd_framework/fastapi/realtime/connection.py +10 -7
- python_ddd_framework/fastapi/realtime/module.py +3 -0
- python_ddd_framework/fastapi/realtime/runtime.py +10 -24
- python_ddd_framework/fastapi/request_context.py +2 -1
- python_ddd_framework/fastapi/routing.py +2 -1
- python_ddd_framework/fastapi/server.py +8 -0
- python_ddd_framework/fastapi/settings.py +2 -1
- python_ddd_framework/hosted_services/bridge.py +20 -1
- python_ddd_framework/hosted_services/catalog.py +6 -16
- python_ddd_framework/hosted_services/contracts.py +4 -0
- python_ddd_framework/hosted_services/declarations.py +15 -0
- python_ddd_framework/hosted_services/module.py +69 -0
- python_ddd_framework/hosted_services/runtime.py +30 -1
- python_ddd_framework/identity/__init__.py +24 -0
- python_ddd_framework/identity/application.py +38 -90
- python_ddd_framework/identity/contracts.py +181 -16
- python_ddd_framework/identity/http_api.py +79 -2
- python_ddd_framework/identity/management.py +166 -0
- python_ddd_framework/identity/module.py +12 -4
- python_ddd_framework/identity/services.py +29 -16
- python_ddd_framework/identity/sqlalchemy/migrations/0003_extra_properties.py +26 -0
- python_ddd_framework/identity/sqlalchemy/migrations/0004_concurrency_version.py +29 -0
- python_ddd_framework/identity/sqlalchemy/models.py +21 -2
- python_ddd_framework/identity/sqlalchemy/module.py +11 -2
- python_ddd_framework/identity/sqlalchemy/repository.py +458 -0
- python_ddd_framework/identity/sqlalchemy/session_security.py +88 -0
- python_ddd_framework/identity/sqlalchemy/stores.py +69 -290
- python_ddd_framework/identity/tokens.py +2 -5
- python_ddd_framework/{application_services/validation.py → invocation/arguments.py} +8 -6
- python_ddd_framework/invocation/contribution.py +61 -0
- python_ddd_framework/invocation/dispatcher.py +291 -0
- python_ddd_framework/invocation/entries.py +2 -25
- python_ddd_framework/invocation/entrypoints.py +126 -0
- python_ddd_framework/{application_services → invocation}/execution.py +8 -5
- python_ddd_framework/invocation/function_runtime.py +3 -1
- python_ddd_framework/invocation/interception.py +113 -42
- python_ddd_framework/{application_services → invocation}/interceptors.py +2 -2
- python_ddd_framework/invocation/managed_proxy.py +116 -0
- python_ddd_framework/invocation/managed_services.py +250 -0
- python_ddd_framework/invocation/methods.py +345 -0
- python_ddd_framework/invocation/module.py +113 -0
- python_ddd_framework/{application_services → invocation}/policies.py +8 -1
- python_ddd_framework/invocation/validation.py +25 -0
- python_ddd_framework/lifecycle/__init__.py +2 -0
- python_ddd_framework/lifecycle/composition.py +5 -2
- python_ddd_framework/lifecycle/participants.py +33 -0
- python_ddd_framework/messaging/__init__.py +25 -0
- python_ddd_framework/messaging/channel.py +205 -0
- python_ddd_framework/messaging/contracts.py +69 -0
- python_ddd_framework/messaging/module.py +101 -0
- python_ddd_framework/messaging/options.py +13 -0
- python_ddd_framework/messaging/receipt.py +27 -0
- python_ddd_framework/messaging/runtime.py +124 -0
- python_ddd_framework/modularity/__init__.py +11 -1
- python_ddd_framework/modularity/contracts.py +47 -16
- python_ddd_framework/modularity/discovery.py +21 -2
- python_ddd_framework/modularity/graph.py +45 -1
- python_ddd_framework/modularity/registry.py +83 -3
- python_ddd_framework/notifications/__init__.py +2 -0
- python_ddd_framework/notifications/catalog.py +3 -2
- python_ddd_framework/notifications/declarations.py +18 -0
- python_ddd_framework/notifications/module.py +21 -0
- python_ddd_framework/observability/logging.py +3 -0
- python_ddd_framework/observability/tracing.py +3 -0
- python_ddd_framework/options/contribution.py +12 -0
- python_ddd_framework/options/registry.py +44 -11
- python_ddd_framework/realtime/__init__.py +2 -1
- python_ddd_framework/realtime/contracts.py +3 -7
- python_ddd_framework/realtime/messages.py +26 -1
- python_ddd_framework/redis/cache.py +243 -0
- python_ddd_framework/redis/cache_scripts.py +40 -0
- python_ddd_framework/redis/distributed_lock.py +10 -2
- python_ddd_framework/redis/module.py +13 -4
- python_ddd_framework/redis/notifications.py +5 -2
- python_ddd_framework/redis/runtime.py +1 -79
- python_ddd_framework/seeding/__init__.py +13 -0
- python_ddd_framework/seeding/contracts.py +45 -0
- python_ddd_framework/seeding/module.py +73 -0
- python_ddd_framework/seeding/runtime.py +63 -0
- python_ddd_framework/services/arbitration.py +23 -0
- python_ddd_framework/services/binding.py +2 -3
- python_ddd_framework/services/composition.py +108 -0
- python_ddd_framework/services/contribution.py +44 -54
- python_ddd_framework/services/convention.py +53 -181
- python_ddd_framework/services/fixed_lifetime.py +12 -15
- python_ddd_framework/services/framework_provider.py +3 -158
- python_ddd_framework/services/native_graph.py +35 -60
- python_ddd_framework/services/provider.py +59 -4
- python_ddd_framework/services/repository.py +6 -0
- python_ddd_framework/services/runtime.py +129 -215
- python_ddd_framework/settings/catalog.py +5 -4
- python_ddd_framework/settings/declarations.py +18 -0
- python_ddd_framework/settings/definitions.py +5 -0
- python_ddd_framework/settings/manager.py +1 -1
- python_ddd_framework/settings/module.py +50 -1
- python_ddd_framework/settings/notifications.py +4 -2
- python_ddd_framework/settings/refresh_module.py +9 -2
- python_ddd_framework/settings/sqlalchemy/module.py +2 -1
- python_ddd_framework/sqlalchemy/__init__.py +12 -0
- python_ddd_framework/sqlalchemy/alembic_runtime/env.py +1 -0
- python_ddd_framework/sqlalchemy/auditing.py +7 -1
- python_ddd_framework/sqlalchemy/extensions.py +186 -0
- python_ddd_framework/sqlalchemy/metadata.py +110 -6
- python_ddd_framework/sqlalchemy/migration.py +61 -8
- python_ddd_framework/sqlalchemy/migration_operations.py +86 -0
- python_ddd_framework/sqlalchemy/module.py +14 -11
- python_ddd_framework/sqlalchemy/module_migration.py +71 -3
- python_ddd_framework/sqlalchemy/repository.py +217 -13
- python_ddd_framework/sqlalchemy/session_provider.py +14 -1
- python_ddd_framework/testing/runtime.py +5 -4
- python_ddd_framework/unit_of_work/__init__.py +2 -1
- python_ddd_framework/unit_of_work/contracts.py +6 -7
- python_ddd_framework/unit_of_work/errors.py +11 -0
- python_ddd_framework/unit_of_work/lifecycle.py +19 -0
- python_ddd_framework/unit_of_work/manager.py +86 -32
- python_ddd_framework/unit_of_work/module.py +61 -0
- python_ddd_framework/unit_of_work/options.py +7 -1
- python_ddd_framework/validation/__init__.py +1 -0
- python_ddd_framework/validation/models.py +37 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/METADATA +139 -44
- python_ddd_framework-0.4.0.dist-info/RECORD +488 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/WHEEL +1 -1
- python_ddd_framework/application_services/seeding.py +0 -72
- python_ddd_framework/caching/catalog.py +0 -73
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +0 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +0 -24
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +0 -83
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +0 -84
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +0 -35
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +0 -14
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +0 -19
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +0 -24
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +0 -36
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +0 -49
- python_ddd_framework-0.3.2.dist-info/RECORD +0 -355
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.dockerignore → backend/.dockerignore} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.gitignore → backend/.gitignore} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.python-version → backend/.python-version} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{Dockerfile → backend/Dockerfile} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.dev.yaml.jinja → backend/compose.dev.yaml.jinja} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.production.yaml.jinja → backend/compose.production.yaml.jinja} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/__init__.py.jinja +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/conftest.py.jinja +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/host/test_http.py.jinja +0 -0
- /python_ddd_framework/{application_services → invocation}/signature.py +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""受管理服务代理与业务管线编排;不拥有 Application admission 或 Session。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Awaitable, Callable
|
|
8
|
+
from datetime import UTC, datetime
|
|
9
|
+
from uuid import uuid4
|
|
10
|
+
|
|
11
|
+
from dishka import AsyncContainer
|
|
12
|
+
from dishka.exceptions import NoFactoryError
|
|
13
|
+
|
|
14
|
+
from ..application_services.errors import (
|
|
15
|
+
ApplicationServiceValidationError,
|
|
16
|
+
ApplicationServiceValidationIssue,
|
|
17
|
+
CrossTaskInvocationError,
|
|
18
|
+
NestedConnectionError,
|
|
19
|
+
)
|
|
20
|
+
from ..application_services.invocation import _current_task, _InvocationRuntime
|
|
21
|
+
from ..auditing import ApplicationServiceAuditRecord, AuditSink
|
|
22
|
+
from ..auditing.control import AuditingControl, AuditingOptions
|
|
23
|
+
from ..authorization import AuthorizationOptions, PermissionChecker
|
|
24
|
+
from ..authorization.contracts import _AuthorizationRequirement
|
|
25
|
+
from ..authorization.errors import (
|
|
26
|
+
PermissionCheckerUnavailableError,
|
|
27
|
+
PermissionDeniedError,
|
|
28
|
+
UnauthenticatedError,
|
|
29
|
+
)
|
|
30
|
+
from ..invocation.arguments import _validate_arguments
|
|
31
|
+
from ..invocation.execution import _InterceptorPipeline
|
|
32
|
+
from ..invocation.interceptors import InterceptionStage
|
|
33
|
+
from ..observability.context import invocation_span
|
|
34
|
+
from ..options import Options
|
|
35
|
+
from ..unit_of_work import UnitOfWorkManager
|
|
36
|
+
from .methods import _CompiledMethod
|
|
37
|
+
|
|
38
|
+
_LOGGER = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
_MethodTarget = Callable[[AsyncContainer, tuple[object, ...], dict[str, object]], Awaitable[object]]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class _MethodDispatcher:
|
|
44
|
+
"""共享调用阶段;实例解析和 HTTP 身份由对应入口拥有。"""
|
|
45
|
+
|
|
46
|
+
__slots__ = ("_auditing_enabled", "_container", "_runtime", "_unit_of_work_enabled")
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
container: AsyncContainer,
|
|
51
|
+
runtime: _InvocationRuntime,
|
|
52
|
+
*,
|
|
53
|
+
unit_of_work_enabled: bool = True,
|
|
54
|
+
auditing_enabled: bool = True,
|
|
55
|
+
) -> None:
|
|
56
|
+
self._container = container
|
|
57
|
+
self._runtime = runtime
|
|
58
|
+
self._unit_of_work_enabled = unit_of_work_enabled
|
|
59
|
+
self._auditing_enabled = auditing_enabled
|
|
60
|
+
|
|
61
|
+
async def invoke_method(
|
|
62
|
+
self,
|
|
63
|
+
method: _CompiledMethod,
|
|
64
|
+
args: tuple[object, ...],
|
|
65
|
+
kwargs: dict[str, object],
|
|
66
|
+
target: _MethodTarget,
|
|
67
|
+
connection_name: str,
|
|
68
|
+
) -> object:
|
|
69
|
+
pipeline = _InterceptorPipeline(method.descriptor, method.interceptor_steps, args, kwargs)
|
|
70
|
+
return await self._invoke_entry(
|
|
71
|
+
method,
|
|
72
|
+
pipeline,
|
|
73
|
+
lambda: self._invoke_validated(method, pipeline, target, connection_name),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
async def _invoke_validated(
|
|
77
|
+
self,
|
|
78
|
+
method: _CompiledMethod,
|
|
79
|
+
pipeline: _InterceptorPipeline,
|
|
80
|
+
target: _MethodTarget,
|
|
81
|
+
connection_name: str,
|
|
82
|
+
) -> object:
|
|
83
|
+
async def validate() -> object:
|
|
84
|
+
await self._validate_pipeline(method, pipeline)
|
|
85
|
+
return await pipeline.run(
|
|
86
|
+
InterceptionStage.UNIT_OF_WORK,
|
|
87
|
+
self._container,
|
|
88
|
+
lambda: self._invoke_work(method, pipeline, target, connection_name),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return await pipeline.run(InterceptionStage.VALIDATION, self._container, validate)
|
|
92
|
+
|
|
93
|
+
async def _invoke_work(
|
|
94
|
+
self,
|
|
95
|
+
method: _CompiledMethod,
|
|
96
|
+
pipeline: _InterceptorPipeline,
|
|
97
|
+
target: _MethodTarget,
|
|
98
|
+
connection_name: str,
|
|
99
|
+
) -> object:
|
|
100
|
+
async def invoke(container: AsyncContainer) -> object:
|
|
101
|
+
async def execute() -> object:
|
|
102
|
+
# METHOD 扩展改写参数后仍须通过同一个验证边界。
|
|
103
|
+
await self._validate_pipeline(method, pipeline)
|
|
104
|
+
return await target(container, pipeline.arguments.args, pipeline.arguments.kwargs)
|
|
105
|
+
|
|
106
|
+
result = await pipeline.run(InterceptionStage.METHOD, container, execute)
|
|
107
|
+
return (
|
|
108
|
+
method.result_adapter.validate_python(result) if method.result_adapter else result
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
ambient = self._runtime.current()
|
|
112
|
+
if ambient is not None:
|
|
113
|
+
if _current_task() is not ambient.owner_task:
|
|
114
|
+
raise CrossTaskInvocationError
|
|
115
|
+
if connection_name != ambient.context.connection_name:
|
|
116
|
+
raise NestedConnectionError(
|
|
117
|
+
outer=ambient.context.connection_name, requested=connection_name
|
|
118
|
+
)
|
|
119
|
+
if not self._unit_of_work_enabled:
|
|
120
|
+
return await invoke(ambient.action_container)
|
|
121
|
+
manager = await self._container.get(UnitOfWorkManager)
|
|
122
|
+
if method.descriptor.unit_of_work.disabled:
|
|
123
|
+
manager._check_current(connection_name)
|
|
124
|
+
return await invoke(ambient.action_container)
|
|
125
|
+
options = manager._resolve_options(
|
|
126
|
+
connection_name, method.descriptor.unit_of_work, method.name
|
|
127
|
+
)
|
|
128
|
+
async with manager._begin(options) as work:
|
|
129
|
+
result = await invoke(ambient.action_container)
|
|
130
|
+
await work.complete()
|
|
131
|
+
return result
|
|
132
|
+
return await self._runtime.run_action(
|
|
133
|
+
self._container,
|
|
134
|
+
connection_name=connection_name,
|
|
135
|
+
callback=invoke,
|
|
136
|
+
declaration=method.descriptor.unit_of_work,
|
|
137
|
+
method_name=method.name,
|
|
138
|
+
unit_of_work_enabled=self._unit_of_work_enabled,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
async def _invoke_entry(
|
|
142
|
+
self,
|
|
143
|
+
method: _CompiledMethod,
|
|
144
|
+
pipeline: _InterceptorPipeline,
|
|
145
|
+
callback: Callable[[], Awaitable[object]],
|
|
146
|
+
*,
|
|
147
|
+
defer_audit: Callable[[Callable[[BaseException | None], Awaitable[None]]], None]
|
|
148
|
+
| None = None,
|
|
149
|
+
) -> object:
|
|
150
|
+
async def authorize() -> object:
|
|
151
|
+
await self._authorize(method.authorization)
|
|
152
|
+
return await callback()
|
|
153
|
+
|
|
154
|
+
async def audit() -> object:
|
|
155
|
+
occurred_at = datetime.now(UTC)
|
|
156
|
+
started_ns = time.perf_counter_ns()
|
|
157
|
+
failure: BaseException | None = None
|
|
158
|
+
try:
|
|
159
|
+
return await pipeline.run(
|
|
160
|
+
InterceptionStage.AUTHORIZATION, self._container, authorize
|
|
161
|
+
)
|
|
162
|
+
except BaseException as error:
|
|
163
|
+
failure = error
|
|
164
|
+
raise
|
|
165
|
+
finally:
|
|
166
|
+
|
|
167
|
+
async def write(final_failure: BaseException | None) -> None:
|
|
168
|
+
await self._write_audit(
|
|
169
|
+
method,
|
|
170
|
+
occurred_at=occurred_at,
|
|
171
|
+
duration_ns=time.perf_counter_ns() - started_ns,
|
|
172
|
+
failure=final_failure,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if defer_audit is not None:
|
|
176
|
+
defer_audit(write)
|
|
177
|
+
else:
|
|
178
|
+
await write(failure)
|
|
179
|
+
|
|
180
|
+
async def trace() -> object:
|
|
181
|
+
with invocation_span(self._runtime.tracer, method.contract_method.__qualname__):
|
|
182
|
+
return await pipeline.run(InterceptionStage.AUDITING, self._container, audit)
|
|
183
|
+
|
|
184
|
+
return await pipeline.run(InterceptionStage.TRACING, self._container, trace)
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
async def _validate_pipeline(
|
|
188
|
+
method: _CompiledMethod,
|
|
189
|
+
pipeline: _InterceptorPipeline,
|
|
190
|
+
) -> None:
|
|
191
|
+
arguments = pipeline.arguments
|
|
192
|
+
if not arguments.needs_validation:
|
|
193
|
+
return
|
|
194
|
+
if method.descriptor.validation_enabled:
|
|
195
|
+
arguments.args, arguments.kwargs = await _validate_arguments(
|
|
196
|
+
method, arguments.args, arguments.kwargs
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
# 可选类型验证关闭后,必需参数与参数名仍属于公开调用契约。
|
|
200
|
+
try:
|
|
201
|
+
method.signature.bind(None, *arguments.args, **arguments.kwargs)
|
|
202
|
+
except TypeError:
|
|
203
|
+
raise ApplicationServiceValidationError(
|
|
204
|
+
service=method.descriptor.service,
|
|
205
|
+
method_name=method.name,
|
|
206
|
+
issues=(
|
|
207
|
+
ApplicationServiceValidationIssue(
|
|
208
|
+
location=(), error_type="arguments", message="Invalid method arguments"
|
|
209
|
+
),
|
|
210
|
+
),
|
|
211
|
+
) from None
|
|
212
|
+
arguments.mark_validated()
|
|
213
|
+
|
|
214
|
+
async def _write_audit(
|
|
215
|
+
self,
|
|
216
|
+
method: _CompiledMethod,
|
|
217
|
+
*,
|
|
218
|
+
occurred_at: datetime,
|
|
219
|
+
duration_ns: int,
|
|
220
|
+
failure: BaseException | None,
|
|
221
|
+
) -> None:
|
|
222
|
+
if not self._auditing_enabled or not method.descriptor.auditing_enabled:
|
|
223
|
+
return
|
|
224
|
+
options = (await self._container.get(Options[AuditingOptions])).value
|
|
225
|
+
control = await self._container.get(AuditingControl)
|
|
226
|
+
if not options.is_enabled or not control.is_enabled:
|
|
227
|
+
return
|
|
228
|
+
try:
|
|
229
|
+
sink = await self._container.get(AuditSink)
|
|
230
|
+
except NoFactoryError:
|
|
231
|
+
return
|
|
232
|
+
except Exception as audit_error:
|
|
233
|
+
_LOGGER.error(
|
|
234
|
+
"application service audit write failed",
|
|
235
|
+
exc_info=audit_error,
|
|
236
|
+
extra={
|
|
237
|
+
"audit_exception_type": type(audit_error).__name__,
|
|
238
|
+
"audit_contract": method.descriptor.service.__qualname__,
|
|
239
|
+
"audit_method": method.name,
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
return
|
|
243
|
+
try:
|
|
244
|
+
current_user = self._runtime.current_user()
|
|
245
|
+
correlation_id = self._runtime.correlation_id() or str(uuid4())
|
|
246
|
+
await sink.write_application_service(
|
|
247
|
+
ApplicationServiceAuditRecord(
|
|
248
|
+
occurred_at=occurred_at,
|
|
249
|
+
user_id=current_user.user_id,
|
|
250
|
+
session_id=current_user.session_id,
|
|
251
|
+
correlation_id=correlation_id,
|
|
252
|
+
contract=(
|
|
253
|
+
f"{method.descriptor.service.__module__}.{method.descriptor.service.__qualname__}"
|
|
254
|
+
),
|
|
255
|
+
method=method.name,
|
|
256
|
+
succeeded=failure is None,
|
|
257
|
+
duration_ns=duration_ns,
|
|
258
|
+
exception_type=None if failure is None else type(failure).__name__,
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
except Exception as audit_error:
|
|
262
|
+
# sink resolution/context/write 都属于独立观测边界;失败不能覆盖业务结果。
|
|
263
|
+
_LOGGER.error(
|
|
264
|
+
"application service audit write failed",
|
|
265
|
+
exc_info=audit_error,
|
|
266
|
+
extra={
|
|
267
|
+
"audit_exception_type": type(audit_error).__name__,
|
|
268
|
+
"audit_contract": method.descriptor.service.__qualname__,
|
|
269
|
+
"audit_method": method.name,
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
async def _authorize(self, requirement: _AuthorizationRequirement) -> None:
|
|
274
|
+
if requirement.allows_anonymous or not requirement.requires_authenticated_user:
|
|
275
|
+
return
|
|
276
|
+
# 所有受管理入口共用此阶段;放行不改变认证器或 CurrentUser。
|
|
277
|
+
options = await self._container.get(Options[AuthorizationOptions])
|
|
278
|
+
if options.value.always_allow:
|
|
279
|
+
return
|
|
280
|
+
current_user = self._runtime.current_user()
|
|
281
|
+
if not current_user.is_authenticated:
|
|
282
|
+
raise UnauthenticatedError
|
|
283
|
+
if not requirement.permissions:
|
|
284
|
+
return
|
|
285
|
+
try:
|
|
286
|
+
checker = await self._container.get(PermissionChecker)
|
|
287
|
+
except NoFactoryError:
|
|
288
|
+
raise PermissionCheckerUnavailableError from None
|
|
289
|
+
for permission in requirement.permissions:
|
|
290
|
+
if not await checker.is_granted(current_user, permission):
|
|
291
|
+
raise PermissionDeniedError(permission=str(permission))
|
|
@@ -2,37 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from collections.abc import Awaitable, Callable
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
6
|
from typing import cast
|
|
7
7
|
|
|
8
|
-
from dishka import AsyncContainer, BaseScope
|
|
8
|
+
from dishka import AsyncContainer, BaseScope
|
|
9
9
|
|
|
10
|
-
from ..background_jobs import BackgroundJobCatalog
|
|
11
|
-
from ..background_workers import BackgroundWorkerCatalog
|
|
12
|
-
from ..events import LocalEventCatalog, LocalEventPhase
|
|
13
10
|
from .callables import _CallablePlan
|
|
14
11
|
|
|
15
12
|
|
|
16
|
-
def _validate_callable_entries(
|
|
17
|
-
container: AsyncContainer,
|
|
18
|
-
jobs: BackgroundJobCatalog,
|
|
19
|
-
workers: BackgroundWorkerCatalog,
|
|
20
|
-
events: LocalEventCatalog,
|
|
21
|
-
*,
|
|
22
|
-
event_implementations: Mapping[type[object], type[object]],
|
|
23
|
-
) -> None:
|
|
24
|
-
for job in jobs.definitions:
|
|
25
|
-
_validate_entry(container, job.handler_type, "execute", 2, Scope.ACTION)
|
|
26
|
-
for worker in workers.definitions:
|
|
27
|
-
_validate_entry(container, worker.worker_type, "run_iteration", 1, Scope.ACTION)
|
|
28
|
-
for subscription in events.subscriptions:
|
|
29
|
-
scope = Scope.ACTION if subscription.phase is LocalEventPhase.DOMAIN else Scope.REQUEST
|
|
30
|
-
target = subscription.handler_type
|
|
31
|
-
if isinstance(target, type):
|
|
32
|
-
target = event_implementations[target]
|
|
33
|
-
_validate_entry(container, target, "handle", 1, scope)
|
|
34
|
-
|
|
35
|
-
|
|
36
13
|
def _validate_entry(
|
|
37
14
|
container: AsyncContainer,
|
|
38
15
|
target: type[object] | Callable[..., Awaitable[None]],
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Host 的正式调用入口;作用域和服务解析始终复用 Application 的原生容器。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
from typing import Concatenate, ParamSpec, TypeVar, cast
|
|
8
|
+
from uuid import uuid4
|
|
9
|
+
|
|
10
|
+
from ..application import Application
|
|
11
|
+
from ..application_services import (
|
|
12
|
+
ApplicationInvocationRejectedError,
|
|
13
|
+
ApplicationServiceContract,
|
|
14
|
+
)
|
|
15
|
+
from ..application_services.invocation import _InvocationRuntime, _ScopedApplicationServiceInvoker
|
|
16
|
+
from ..authorization import CurrentUser
|
|
17
|
+
from ..lifecycle import ApplicationState
|
|
18
|
+
from .function_runtime import _invoke_callable
|
|
19
|
+
|
|
20
|
+
_P = ParamSpec("_P")
|
|
21
|
+
_R = TypeVar("_R")
|
|
22
|
+
_TContract = TypeVar("_TContract", bound=ApplicationServiceContract)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def call(
|
|
26
|
+
application: Application,
|
|
27
|
+
function: Callable[..., Awaitable[_R]],
|
|
28
|
+
/,
|
|
29
|
+
*args: object,
|
|
30
|
+
**kwargs: object,
|
|
31
|
+
) -> _R:
|
|
32
|
+
if application.state not in (ApplicationState.STARTING, ApplicationState.RUNNING):
|
|
33
|
+
raise ApplicationInvocationRejectedError(
|
|
34
|
+
reason=f"Application must be running, got {application.state.value}",
|
|
35
|
+
)
|
|
36
|
+
runtime = await application._container_for_integration().get(_InvocationRuntime)
|
|
37
|
+
return await call_as(application, runtime.current_user(), function, *args, **kwargs)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def call_as(
|
|
41
|
+
application: Application,
|
|
42
|
+
current_user: CurrentUser,
|
|
43
|
+
function: Callable[..., Awaitable[_R]],
|
|
44
|
+
/,
|
|
45
|
+
*args: object,
|
|
46
|
+
**kwargs: object,
|
|
47
|
+
) -> _R:
|
|
48
|
+
if application.state not in (ApplicationState.STARTING, ApplicationState.RUNNING):
|
|
49
|
+
raise ApplicationInvocationRejectedError(
|
|
50
|
+
reason=f"Application must be running, got {application.state.value}",
|
|
51
|
+
)
|
|
52
|
+
container = application._container_for_integration()
|
|
53
|
+
runtime = await container.get(_InvocationRuntime)
|
|
54
|
+
return await _invoke_callable(
|
|
55
|
+
container,
|
|
56
|
+
runtime,
|
|
57
|
+
current_user,
|
|
58
|
+
str(uuid4()),
|
|
59
|
+
function,
|
|
60
|
+
args,
|
|
61
|
+
kwargs,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def invoke(
|
|
66
|
+
application: Application,
|
|
67
|
+
method: Callable[Concatenate[_TContract, _P], Awaitable[_R]],
|
|
68
|
+
/,
|
|
69
|
+
*args: _P.args,
|
|
70
|
+
**kwargs: _P.kwargs,
|
|
71
|
+
) -> _R:
|
|
72
|
+
return await invoke_as(application, CurrentUser.anonymous(), method, *args, **kwargs)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def invoke_as(
|
|
76
|
+
application: Application,
|
|
77
|
+
current_user: CurrentUser,
|
|
78
|
+
method: Callable[Concatenate[_TContract, _P], Awaitable[_R]],
|
|
79
|
+
/,
|
|
80
|
+
*args: _P.args,
|
|
81
|
+
**kwargs: _P.kwargs,
|
|
82
|
+
) -> _R:
|
|
83
|
+
if application.state not in (ApplicationState.STARTING, ApplicationState.RUNNING):
|
|
84
|
+
raise ApplicationInvocationRejectedError(
|
|
85
|
+
reason=f"Application must be running, got {application.state.value}",
|
|
86
|
+
)
|
|
87
|
+
container = application._container_for_integration()
|
|
88
|
+
invoker = await container.get(_ScopedApplicationServiceInvoker)
|
|
89
|
+
result = await invoker.invoke(
|
|
90
|
+
container,
|
|
91
|
+
current_user,
|
|
92
|
+
str(uuid4()),
|
|
93
|
+
cast(Callable[..., Awaitable[object]], method),
|
|
94
|
+
cast(tuple[object, ...], args),
|
|
95
|
+
cast(dict[str, object], kwargs),
|
|
96
|
+
)
|
|
97
|
+
return cast(_R, result)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@asynccontextmanager
|
|
101
|
+
async def invocation_lease(application: Application) -> AsyncIterator[None]:
|
|
102
|
+
runtime = await application._container_for_integration().get(_InvocationRuntime)
|
|
103
|
+
async with runtime.outer_lease():
|
|
104
|
+
yield
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@asynccontextmanager
|
|
108
|
+
async def http_request_lease(application: Application) -> AsyncIterator[None]:
|
|
109
|
+
runtime = await application._container_for_integration().get(_InvocationRuntime)
|
|
110
|
+
# composed/pre-start 健康探测仍参与 drain,业务调用的 RUNNING 限制由 dispatcher 保证。
|
|
111
|
+
async with runtime.outer_lease(allow_composed_request=True):
|
|
112
|
+
yield
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@asynccontextmanager
|
|
116
|
+
async def caller_context(
|
|
117
|
+
application: Application,
|
|
118
|
+
current_user: CurrentUser,
|
|
119
|
+
correlation_id: str,
|
|
120
|
+
) -> AsyncIterator[None]:
|
|
121
|
+
runtime = await application._container_for_integration().get(_InvocationRuntime)
|
|
122
|
+
token = runtime.bind_caller(current_user, correlation_id)
|
|
123
|
+
try:
|
|
124
|
+
yield
|
|
125
|
+
finally:
|
|
126
|
+
runtime.reset_caller(token)
|
|
@@ -12,8 +12,11 @@ from uuid import UUID
|
|
|
12
12
|
from dishka import AsyncContainer
|
|
13
13
|
from pydantic import BaseModel
|
|
14
14
|
|
|
15
|
-
from .
|
|
16
|
-
|
|
15
|
+
from ..application_services.errors import (
|
|
16
|
+
ApplicationInvocationRejectedError,
|
|
17
|
+
CrossTaskInvocationError,
|
|
18
|
+
)
|
|
19
|
+
from ..invocation.methods import ServiceMethod
|
|
17
20
|
from .interceptors import InterceptionStage, InterceptorRegistration
|
|
18
21
|
|
|
19
22
|
_ARGUMENT_SNAPSHOT_MAX_DEPTH = 64
|
|
@@ -22,7 +25,7 @@ _ARGUMENT_SNAPSHOT_MAX_DEPTH = 64
|
|
|
22
25
|
class _InterceptorPipeline:
|
|
23
26
|
def __init__(
|
|
24
27
|
self,
|
|
25
|
-
method:
|
|
28
|
+
method: ServiceMethod,
|
|
26
29
|
steps: Mapping[InterceptionStage, tuple[InterceptorRegistration, ...]],
|
|
27
30
|
args: tuple[object, ...],
|
|
28
31
|
kwargs: dict[str, object],
|
|
@@ -155,7 +158,7 @@ class MethodInvocation:
|
|
|
155
158
|
|
|
156
159
|
def __init__(
|
|
157
160
|
self,
|
|
158
|
-
method:
|
|
161
|
+
method: ServiceMethod,
|
|
159
162
|
arguments: _InvocationArguments,
|
|
160
163
|
continuation: Callable[[], Awaitable[object]],
|
|
161
164
|
) -> None:
|
|
@@ -167,7 +170,7 @@ class MethodInvocation:
|
|
|
167
170
|
self._active = True
|
|
168
171
|
|
|
169
172
|
@property
|
|
170
|
-
def method(self) ->
|
|
173
|
+
def method(self) -> ServiceMethod:
|
|
171
174
|
return self._method
|
|
172
175
|
|
|
173
176
|
@property
|
|
@@ -26,6 +26,8 @@ async def _invoke_callable(
|
|
|
26
26
|
args: tuple[object, ...],
|
|
27
27
|
kwargs: dict[str, object],
|
|
28
28
|
) -> _R:
|
|
29
|
+
from ..application_services.invocation import _managed_request
|
|
30
|
+
|
|
29
31
|
async with runtime.outer_lease():
|
|
30
32
|
ambient = runtime.current()
|
|
31
33
|
existing = ambient.action_container if ambient is not None else runtime.current_call_scope()
|
|
@@ -41,7 +43,7 @@ async def _invoke_callable(
|
|
|
41
43
|
token = runtime.bind_caller(current_user, correlation_id)
|
|
42
44
|
try:
|
|
43
45
|
plan = _CallablePlan.compile(function, container, Scope.REQUEST)
|
|
44
|
-
async with container
|
|
46
|
+
async with _managed_request(container) as request:
|
|
45
47
|
with runtime.call_scope(request):
|
|
46
48
|
return await plan.invoke(function, request, args, kwargs)
|
|
47
49
|
finally:
|