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
|
@@ -47,6 +47,29 @@ class _ArbitrationResult:
|
|
|
47
47
|
drafts: tuple[_RegistrationDraft, ...]
|
|
48
48
|
ordered_entries: tuple[_ContributionEntry, ...]
|
|
49
49
|
|
|
50
|
+
def single_factory(self, key: DependencyKey) -> Factory | None:
|
|
51
|
+
"""沿最终获选的原生 alias 查询构造事实,不实例化服务、不重复仲裁。"""
|
|
52
|
+
visited: set[DependencyKey] = set()
|
|
53
|
+
while key not in visited:
|
|
54
|
+
visited.add(key)
|
|
55
|
+
draft = next(
|
|
56
|
+
(
|
|
57
|
+
item
|
|
58
|
+
for item in self.drafts
|
|
59
|
+
if item.key == key
|
|
60
|
+
and item.decision is RegistrationDecision.ACCEPTED
|
|
61
|
+
and item.contribution.kind is not RegistrationKind.MULTI
|
|
62
|
+
and not item.is_collection
|
|
63
|
+
),
|
|
64
|
+
None,
|
|
65
|
+
)
|
|
66
|
+
if draft is None or draft.factory is None:
|
|
67
|
+
return None
|
|
68
|
+
if draft.alias is None:
|
|
69
|
+
return draft.factory
|
|
70
|
+
key = draft.factory.dependencies[0]
|
|
71
|
+
return None
|
|
72
|
+
|
|
50
73
|
|
|
51
74
|
class _ServiceArbitrator:
|
|
52
75
|
"""拥有一次 service policy decision;只修改尚未发布的 registration drafts。"""
|
|
@@ -6,7 +6,6 @@ from typing import Annotated
|
|
|
6
6
|
|
|
7
7
|
from dishka import BaseScope, FromComponent, Provider
|
|
8
8
|
|
|
9
|
-
from ..application_services.contracts import ApplicationService
|
|
10
9
|
from ..diagnostics import SourceLocation
|
|
11
10
|
from ..errors import RegistrationConflictError
|
|
12
11
|
from .registration import RegistrationKind
|
|
@@ -49,12 +48,12 @@ def _registration_providers(
|
|
|
49
48
|
kind = RegistrationKind.OVERRIDE if override else RegistrationKind.EXPLICIT
|
|
50
49
|
provider = Provider(scope=scope, component=component)
|
|
51
50
|
concrete = implementation or service
|
|
52
|
-
if
|
|
51
|
+
if any(role.__dict__.get("_requires_convention", False) for role in concrete.__mro__):
|
|
53
52
|
raise RegistrationConflictError(
|
|
54
53
|
key=None,
|
|
55
54
|
sources=(source,),
|
|
56
55
|
reason=(
|
|
57
|
-
"
|
|
56
|
+
f"{concrete.__qualname__} registration requires convention discovery "
|
|
58
57
|
"and its contract proxy"
|
|
59
58
|
),
|
|
60
59
|
)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""能力模块参与最终服务图解释的扩展契约;容器仍由 Dishka 唯一创建。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from dishka import AsyncContainer, Provider
|
|
10
|
+
|
|
11
|
+
from ..configuration import ConfigurationRoot
|
|
12
|
+
from ..diagnostics.source import caller_source
|
|
13
|
+
from ..errors import ServiceContributorClosedError
|
|
14
|
+
from ..modularity import ModuleDescriptor, ModuleKey, ModuleRegistry
|
|
15
|
+
from ..modularity.discovery import _DiscoveredType
|
|
16
|
+
from ..options import OptionsSnapshot
|
|
17
|
+
from .convention_contracts import ConventionDiscoveryKind, _ServiceDefinition
|
|
18
|
+
from .provider import _NativeProviderInspector, _ProviderSignature
|
|
19
|
+
from .registration import RegistrationKind
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from ..application import Application
|
|
23
|
+
from ..application.build_spec import _ApplicationBuildSpec
|
|
24
|
+
from .arbitration import _ArbitrationResult
|
|
25
|
+
from .contribution import ServiceContributionRegistry, _ContributionEntry
|
|
26
|
+
from .native_graph import _NativeServiceGraph
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True, slots=True)
|
|
30
|
+
class ServiceConvention:
|
|
31
|
+
definition: _ServiceDefinition
|
|
32
|
+
kind: ConventionDiscoveryKind
|
|
33
|
+
reason: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True, slots=True)
|
|
37
|
+
class ServiceDiscoveryContext:
|
|
38
|
+
modules: ModuleRegistry
|
|
39
|
+
discoveries: tuple[_DiscoveredType, ...]
|
|
40
|
+
services: ServiceContributionRegistry
|
|
41
|
+
conventions: dict[tuple[ModuleKey, type[object]], ServiceConvention]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(slots=True)
|
|
45
|
+
class _ProviderRegistrationState:
|
|
46
|
+
closed: bool = False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True, slots=True)
|
|
50
|
+
class ServiceBindingContext:
|
|
51
|
+
descriptor: ModuleDescriptor
|
|
52
|
+
modules: ModuleRegistry
|
|
53
|
+
configuration: ConfigurationRoot
|
|
54
|
+
options: OptionsSnapshot
|
|
55
|
+
arbitration: _ArbitrationResult
|
|
56
|
+
_providers: list[Provider]
|
|
57
|
+
_provider_checks: list[
|
|
58
|
+
tuple[ModuleDescriptor, _NativeProviderInspector, Provider, _ProviderSignature]
|
|
59
|
+
]
|
|
60
|
+
_decorators: list[Provider]
|
|
61
|
+
application: Application
|
|
62
|
+
build_spec: _ApplicationBuildSpec
|
|
63
|
+
_provider_registration: _ProviderRegistrationState = field(
|
|
64
|
+
default_factory=_ProviderRegistrationState
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def add_provider(self, provider: Provider) -> None:
|
|
68
|
+
"""派生目录只能增加绑定;替换必须在仲裁前经过正式 owner、依赖和原因校验。"""
|
|
69
|
+
if self._provider_registration.closed:
|
|
70
|
+
raise ServiceContributorClosedError(stage="final binding")
|
|
71
|
+
inspector = _NativeProviderInspector(
|
|
72
|
+
source=caller_source(str(self.descriptor.key), depth=2),
|
|
73
|
+
kind=RegistrationKind.EXPLICIT,
|
|
74
|
+
)
|
|
75
|
+
signature = inspector.inspect(provider).signature
|
|
76
|
+
self._providers.append(provider)
|
|
77
|
+
self._provider_checks.append((self.descriptor, inspector, provider, signature))
|
|
78
|
+
|
|
79
|
+
def _add_method_decorator(self, provider: Provider) -> None:
|
|
80
|
+
"""仅供调用能力生成的同 scope 装饰;与普通贡献分别记录并核对指纹。"""
|
|
81
|
+
inspector = _NativeProviderInspector(
|
|
82
|
+
source=caller_source(str(self.descriptor.key), depth=2),
|
|
83
|
+
kind=RegistrationKind.EXPLICIT,
|
|
84
|
+
method_decorators=True,
|
|
85
|
+
)
|
|
86
|
+
signature = inspector.inspect(provider).signature
|
|
87
|
+
self._providers.append(provider)
|
|
88
|
+
self._decorators.append(provider)
|
|
89
|
+
self._provider_checks.append((self.descriptor, inspector, provider, signature))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True, slots=True)
|
|
93
|
+
class ServiceValidationContext:
|
|
94
|
+
binding: ServiceBindingContext
|
|
95
|
+
container: AsyncContainer
|
|
96
|
+
native_graph: _NativeServiceGraph
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass(frozen=True, slots=True)
|
|
100
|
+
class ServiceComposition:
|
|
101
|
+
"""只登记模块实际需要的阶段;回调及其捕获状态属于本次 build。"""
|
|
102
|
+
|
|
103
|
+
discover: Callable[[ServiceDiscoveryContext], None] | None = None
|
|
104
|
+
before_binding: Callable[[tuple[_ContributionEntry, ...]], None] | None = None
|
|
105
|
+
bind: Callable[[ServiceBindingContext], None] | None = None
|
|
106
|
+
finalize: Callable[[ServiceBindingContext], None] | None = None
|
|
107
|
+
decorate: Callable[[ServiceBindingContext], None] | None = None
|
|
108
|
+
validate: Callable[[ServiceValidationContext], None] | None = None
|
|
@@ -2,25 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import inspect
|
|
6
|
-
from collections.abc import Callable
|
|
7
5
|
from dataclasses import dataclass
|
|
8
6
|
|
|
9
7
|
from dishka import BaseScope, Provider, Scope
|
|
10
8
|
|
|
11
|
-
from ..application_services.catalog import ApplicationServiceMethod
|
|
12
|
-
from ..application_services.errors import ApplicationServiceDefinitionError
|
|
13
|
-
from ..application_services.interceptors import (
|
|
14
|
-
InterceptionStage,
|
|
15
|
-
Interceptor,
|
|
16
|
-
InterceptorRegistration,
|
|
17
|
-
_InterceptorRule,
|
|
18
|
-
)
|
|
19
9
|
from ..diagnostics import LifecycleStage, SourceLocation
|
|
20
10
|
from ..diagnostics.source import caller_source
|
|
21
11
|
from ..errors import RegistrationConflictError, ServiceContributorClosedError
|
|
22
12
|
from ..modularity import ModuleDescriptor, ModuleKey
|
|
23
13
|
from .binding import _collection_provider, _registration_providers
|
|
14
|
+
from .composition import ServiceComposition
|
|
24
15
|
from .provider import _NativeProviderInspector, _ProviderSignature
|
|
25
16
|
from .registration import ProviderContribution, ProviderContributionView, RegistrationKind
|
|
26
17
|
|
|
@@ -67,6 +58,18 @@ class ServiceContributor:
|
|
|
67
58
|
source=caller_source(str(self._descriptor.key), depth=2),
|
|
68
59
|
)
|
|
69
60
|
|
|
61
|
+
def add_composition(self, composition: ServiceComposition) -> None:
|
|
62
|
+
"""在 configure 登记最终绑定解释;不能从 post_configure 新增组装参与者。"""
|
|
63
|
+
if not self._active:
|
|
64
|
+
raise ServiceContributorClosedError(stage=self._stage.value)
|
|
65
|
+
if self._stage is not LifecycleStage.CONFIGURE:
|
|
66
|
+
raise RegistrationConflictError(
|
|
67
|
+
key=None,
|
|
68
|
+
sources=(caller_source(str(self._descriptor.key), depth=2),),
|
|
69
|
+
reason="service composition must be contributed during configure",
|
|
70
|
+
)
|
|
71
|
+
self._registry.add_composition(self._descriptor, composition)
|
|
72
|
+
|
|
70
73
|
def add_singleton(
|
|
71
74
|
self,
|
|
72
75
|
service: type[object],
|
|
@@ -183,67 +186,54 @@ class ServiceContributor:
|
|
|
183
186
|
def _close(self) -> None:
|
|
184
187
|
self._active = False
|
|
185
188
|
|
|
186
|
-
def
|
|
187
|
-
|
|
188
|
-
interceptor: type[Interceptor],
|
|
189
|
-
*,
|
|
190
|
-
match: Callable[[ApplicationServiceMethod], bool],
|
|
191
|
-
before: InterceptionStage = InterceptionStage.METHOD,
|
|
192
|
-
) -> None:
|
|
193
|
-
"""只贡献匹配事实;类型及依赖仍通过原生 services 注册。"""
|
|
189
|
+
def require_active(self) -> None:
|
|
190
|
+
"""能力扩展复用当前 hook 的有效期,不能持有 context 在屏障后继续贡献。"""
|
|
194
191
|
if not self._active:
|
|
195
192
|
raise ServiceContributorClosedError(stage=self._stage.value)
|
|
196
|
-
if (
|
|
197
|
-
not isinstance(interceptor, type)
|
|
198
|
-
or not issubclass(interceptor, Interceptor)
|
|
199
|
-
or inspect.isabstract(interceptor)
|
|
200
|
-
or not inspect.iscoroutinefunction(interceptor.intercept)
|
|
201
|
-
):
|
|
202
|
-
raise ApplicationServiceDefinitionError(
|
|
203
|
-
symbol=str(interceptor), reason="interceptor must implement async intercept"
|
|
204
|
-
)
|
|
205
|
-
if (
|
|
206
|
-
not callable(match)
|
|
207
|
-
or inspect.iscoroutinefunction(match)
|
|
208
|
-
or not isinstance(before, InterceptionStage)
|
|
209
|
-
):
|
|
210
|
-
raise ApplicationServiceDefinitionError(
|
|
211
|
-
symbol=interceptor.__qualname__,
|
|
212
|
-
reason="interceptor requires a synchronous match and valid position",
|
|
213
|
-
)
|
|
214
|
-
self._registry.add_interceptor_rule(
|
|
215
|
-
_InterceptorRule(
|
|
216
|
-
InterceptorRegistration(
|
|
217
|
-
interceptor,
|
|
218
|
-
before,
|
|
219
|
-
self._descriptor.key,
|
|
220
|
-
caller_source(str(self._descriptor.key), depth=2),
|
|
221
|
-
),
|
|
222
|
-
match,
|
|
223
|
-
)
|
|
224
|
-
)
|
|
225
193
|
|
|
226
194
|
|
|
227
195
|
class ServiceContributionRegistry:
|
|
228
196
|
"""一次 build 的 append-only collector;freeze 后只由仲裁流程读取。"""
|
|
229
197
|
|
|
230
|
-
__slots__ = (
|
|
198
|
+
__slots__ = (
|
|
199
|
+
"_collections",
|
|
200
|
+
"_compositions",
|
|
201
|
+
"_declaration_counts",
|
|
202
|
+
"_entries",
|
|
203
|
+
"_frozen",
|
|
204
|
+
)
|
|
231
205
|
|
|
232
206
|
def __init__(self) -> None:
|
|
233
207
|
self._entries: list[_ContributionEntry] = []
|
|
234
208
|
self._declaration_counts: dict[ModuleKey, int] = {}
|
|
235
209
|
self._frozen = False
|
|
236
210
|
self._collections: set[type[object]] = set()
|
|
237
|
-
self.
|
|
211
|
+
self._compositions: list[tuple[ModuleDescriptor, ServiceComposition]] = []
|
|
238
212
|
|
|
239
|
-
def
|
|
213
|
+
def add_composition(
|
|
214
|
+
self, descriptor: ModuleDescriptor, composition: ServiceComposition
|
|
215
|
+
) -> None:
|
|
240
216
|
if self._frozen:
|
|
241
|
-
raise ServiceContributorClosedError(stage="
|
|
242
|
-
|
|
217
|
+
raise ServiceContributorClosedError(stage="composition")
|
|
218
|
+
if not isinstance(composition, ServiceComposition):
|
|
219
|
+
raise TypeError("composition must be ServiceComposition")
|
|
220
|
+
callbacks = (
|
|
221
|
+
composition.discover,
|
|
222
|
+
composition.before_binding,
|
|
223
|
+
composition.bind,
|
|
224
|
+
composition.finalize,
|
|
225
|
+
composition.decorate,
|
|
226
|
+
composition.validate,
|
|
227
|
+
)
|
|
228
|
+
if not any(callback is not None for callback in callbacks):
|
|
229
|
+
raise ValueError("service composition must contribute at least one callback")
|
|
230
|
+
if any(callback is not None and not callable(callback) for callback in callbacks):
|
|
231
|
+
raise TypeError("service composition callbacks must be callable")
|
|
232
|
+
self._compositions.append((descriptor, composition))
|
|
243
233
|
|
|
244
234
|
@property
|
|
245
|
-
def
|
|
246
|
-
return tuple(self.
|
|
235
|
+
def compositions(self) -> tuple[tuple[ModuleDescriptor, ServiceComposition], ...]:
|
|
236
|
+
return tuple(self._compositions)
|
|
247
237
|
|
|
248
238
|
def add_registration(
|
|
249
239
|
self,
|
|
@@ -1,28 +1,17 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""有界类型扫描及通用 DI 约定;能力规则由已启用模块贡献。"""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import inspect
|
|
6
6
|
from collections.abc import Mapping
|
|
7
|
-
from typing import cast
|
|
8
7
|
|
|
9
8
|
from dishka import Provider, Scope
|
|
10
9
|
|
|
11
|
-
from ..application_services.catalog import _DiscoveredApplicationService
|
|
12
|
-
from ..application_services.contracts import (
|
|
13
|
-
_APPLICATION_SERVICE_DEFINITION_ATTRIBUTE,
|
|
14
|
-
ApplicationService,
|
|
15
|
-
_ApplicationServiceDefinition,
|
|
16
|
-
)
|
|
17
|
-
from ..application_services.contracts import (
|
|
18
|
-
_definition_for as _application_service_definition,
|
|
19
|
-
)
|
|
20
|
-
from ..application_services.errors import ApplicationServiceDefinitionError
|
|
21
|
-
from ..diagnostics import SourceLocation
|
|
22
10
|
from ..errors import ConventionScanError, InvalidConventionDefinitionError
|
|
23
11
|
from ..modularity import ModuleKey, ModuleRegistry
|
|
24
12
|
from ..modularity.discovery import _DiscoveredType, _is_concrete_implementation
|
|
25
13
|
from .arbitration import _RegistrationDraft
|
|
14
|
+
from .composition import ServiceConvention
|
|
26
15
|
from .contribution import ServiceContributionRegistry
|
|
27
16
|
from .convention_contracts import (
|
|
28
17
|
_LIFETIME_MARKERS,
|
|
@@ -40,34 +29,20 @@ from .repository import _repository_contract
|
|
|
40
29
|
|
|
41
30
|
|
|
42
31
|
class _ConventionScanner:
|
|
43
|
-
"""拥有一次 Application scan 的顺序、去重状态和 candidate diagnostics。"""
|
|
44
|
-
|
|
45
|
-
__slots__ = (
|
|
46
|
-
"_application_services",
|
|
47
|
-
"_contributions",
|
|
48
|
-
"_discoveries",
|
|
49
|
-
"_event_handler_scopes",
|
|
50
|
-
"_lifetime_conflicts",
|
|
51
|
-
"_records",
|
|
52
|
-
"_registry",
|
|
53
|
-
"_scan_packages",
|
|
54
|
-
)
|
|
55
|
-
|
|
56
32
|
def __init__(
|
|
57
33
|
self,
|
|
58
34
|
registry: ModuleRegistry,
|
|
59
35
|
contributions: ServiceContributionRegistry,
|
|
60
36
|
scan_packages: Mapping[ModuleKey, tuple[str, ...]],
|
|
61
37
|
discoveries: tuple[_DiscoveredType, ...],
|
|
62
|
-
|
|
38
|
+
conventions: Mapping[tuple[ModuleKey, type[object]], ServiceConvention],
|
|
63
39
|
) -> None:
|
|
64
|
-
self._discoveries = discoveries
|
|
65
|
-
self._event_handler_scopes = event_handler_scopes
|
|
66
40
|
self._registry = registry
|
|
67
41
|
self._contributions = contributions
|
|
68
42
|
self._scan_packages = scan_packages
|
|
43
|
+
self._discoveries = discoveries
|
|
44
|
+
self._conventions = conventions
|
|
69
45
|
self._records: list[ConventionCandidate] = []
|
|
70
|
-
self._application_services: list[_DiscoveredApplicationService] = []
|
|
71
46
|
self._lifetime_conflicts: set[type[object]] = set()
|
|
72
47
|
|
|
73
48
|
def validate_lifetimes(self, drafts: tuple[_RegistrationDraft, ...]) -> None:
|
|
@@ -86,6 +61,7 @@ class _ConventionScanner:
|
|
|
86
61
|
)
|
|
87
62
|
|
|
88
63
|
def discover(self) -> tuple[ConventionCandidate, ...]:
|
|
64
|
+
self._registry.assert_declarations_unchanged()
|
|
89
65
|
for descriptor in self._registry.dependency_order:
|
|
90
66
|
if descriptor.module_type.scan_packages != self._scan_packages[descriptor.key]:
|
|
91
67
|
raise ConventionScanError(
|
|
@@ -95,160 +71,65 @@ class _ConventionScanner:
|
|
|
95
71
|
reason="scan_packages metadata was mutated after the Application snapshot",
|
|
96
72
|
original_error=None,
|
|
97
73
|
)
|
|
98
|
-
seen:
|
|
74
|
+
seen: set[tuple[ModuleKey, type[object]]] = set()
|
|
99
75
|
for discovery in self._discoveries:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return tuple(self._application_services)
|
|
106
|
-
|
|
107
|
-
def _record_candidate(self, discovery: _DiscoveredType, seen_types: set[type[object]]) -> None:
|
|
108
|
-
candidate = discovery.candidate
|
|
109
|
-
source = discovery.source
|
|
110
|
-
owner = discovery.owner
|
|
111
|
-
package_name = discovery.package
|
|
112
|
-
definition, application_definition, discovered_by, reason = (
|
|
113
|
-
self._definition(candidate)
|
|
114
|
-
if _is_concrete_implementation(candidate)
|
|
115
|
-
else (None, None, ConventionDiscoveryKind.NONE, "not a concrete closed implementation")
|
|
116
|
-
)
|
|
117
|
-
if candidate in seen_types:
|
|
118
|
-
self._append_record(
|
|
119
|
-
owner,
|
|
120
|
-
package_name,
|
|
121
|
-
source,
|
|
122
|
-
discovered_by,
|
|
123
|
-
ConventionCandidateDecision.DUPLICATE,
|
|
124
|
-
"candidate already interpreted from an earlier scan package",
|
|
76
|
+
candidate = discovery.candidate
|
|
77
|
+
definition, kind, reason = (
|
|
78
|
+
self._definition(discovery)
|
|
79
|
+
if _is_concrete_implementation(candidate)
|
|
80
|
+
else (None, ConventionDiscoveryKind.NONE, "not a concrete closed implementation")
|
|
125
81
|
)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
provider.provide(
|
|
140
|
-
candidate,
|
|
141
|
-
provides=definition.provides if definition.provides is not None else candidate,
|
|
142
|
-
scope=definition.scope,
|
|
143
|
-
cache=definition.cache,
|
|
144
|
-
)
|
|
145
|
-
if definition.provides is None:
|
|
146
|
-
for interface in _business_interfaces(candidate):
|
|
147
|
-
provider.alias(candidate, provides=interface, cache=False)
|
|
148
|
-
self._contributions.add_convention(
|
|
149
|
-
provider=provider,
|
|
150
|
-
owner=owner,
|
|
151
|
-
source=source,
|
|
152
|
-
reason=reason,
|
|
153
|
-
)
|
|
154
|
-
if application_definition is not None:
|
|
155
|
-
# 分层模块的公开契约由实际扫描它的 Module 拥有;实现 DI 的 owner 仍由上面的
|
|
156
|
-
# contribution 保留。HTTP 因此可只依赖 Contracts,不反向引用应用实现。
|
|
157
|
-
declarations = {
|
|
158
|
-
item.owner: item
|
|
159
|
-
for item in self._discoveries
|
|
160
|
-
if item.candidate is application_definition.contract
|
|
161
|
-
}
|
|
162
|
-
if len(declarations) > 1:
|
|
163
|
-
raise ApplicationServiceDefinitionError(
|
|
164
|
-
symbol=str(application_definition.contract),
|
|
165
|
-
reason="application service contract is scanned by multiple Modules",
|
|
82
|
+
key = (discovery.owner, candidate)
|
|
83
|
+
if key in seen:
|
|
84
|
+
decision = ConventionCandidateDecision.DUPLICATE
|
|
85
|
+
reason = "candidate already interpreted from an earlier scan package"
|
|
86
|
+
elif definition is None:
|
|
87
|
+
decision = ConventionCandidateDecision.REJECTED
|
|
88
|
+
else:
|
|
89
|
+
provider = Provider(scope=definition.scope)
|
|
90
|
+
provider.provide(
|
|
91
|
+
candidate,
|
|
92
|
+
provides=definition.provides if definition.provides is not None else candidate,
|
|
93
|
+
scope=definition.scope,
|
|
94
|
+
cache=definition.cache,
|
|
166
95
|
)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
owner=
|
|
173
|
-
source=
|
|
174
|
-
|
|
96
|
+
if definition.provides is None:
|
|
97
|
+
for interface in _business_interfaces(candidate):
|
|
98
|
+
provider.alias(candidate, provides=interface, cache=False)
|
|
99
|
+
self._contributions.add_convention(
|
|
100
|
+
provider=provider,
|
|
101
|
+
owner=discovery.owner,
|
|
102
|
+
source=discovery.source,
|
|
103
|
+
reason=reason,
|
|
104
|
+
)
|
|
105
|
+
decision = ConventionCandidateDecision.ACCEPTED
|
|
106
|
+
seen.add(key)
|
|
107
|
+
self._records.append(
|
|
108
|
+
ConventionCandidate(
|
|
109
|
+
owner=discovery.owner,
|
|
110
|
+
package=discovery.package,
|
|
111
|
+
source=discovery.source,
|
|
112
|
+
discovered_by=kind,
|
|
113
|
+
decision=decision,
|
|
114
|
+
reason=reason,
|
|
115
|
+
ordinal=len(self._records),
|
|
175
116
|
)
|
|
176
117
|
)
|
|
177
|
-
self.
|
|
178
|
-
owner,
|
|
179
|
-
package_name,
|
|
180
|
-
source,
|
|
181
|
-
discovered_by,
|
|
182
|
-
ConventionCandidateDecision.ACCEPTED,
|
|
183
|
-
reason,
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
def _append_record(
|
|
187
|
-
self,
|
|
188
|
-
owner: ModuleKey,
|
|
189
|
-
package: str,
|
|
190
|
-
source: SourceLocation,
|
|
191
|
-
discovered_by: ConventionDiscoveryKind,
|
|
192
|
-
decision: ConventionCandidateDecision,
|
|
193
|
-
reason: str,
|
|
194
|
-
) -> None:
|
|
195
|
-
self._records.append(
|
|
196
|
-
ConventionCandidate(
|
|
197
|
-
owner=owner,
|
|
198
|
-
package=package,
|
|
199
|
-
source=source,
|
|
200
|
-
discovered_by=discovered_by,
|
|
201
|
-
decision=decision,
|
|
202
|
-
reason=reason,
|
|
203
|
-
ordinal=len(self._records),
|
|
204
|
-
)
|
|
205
|
-
)
|
|
118
|
+
return tuple(self._records)
|
|
206
119
|
|
|
207
120
|
def _definition(
|
|
208
121
|
self,
|
|
209
|
-
|
|
210
|
-
) -> tuple[
|
|
211
|
-
|
|
212
|
-
_ApplicationServiceDefinition | None,
|
|
213
|
-
ConventionDiscoveryKind,
|
|
214
|
-
str,
|
|
215
|
-
]:
|
|
122
|
+
discovery: _DiscoveredType,
|
|
123
|
+
) -> tuple[_ServiceDefinition | None, ConventionDiscoveryKind, str]:
|
|
124
|
+
candidate = discovery.candidate
|
|
216
125
|
_validate_fixed_lifetime(candidate)
|
|
217
126
|
repository_contract = _repository_contract(candidate)
|
|
218
127
|
if repository_contract is not None:
|
|
219
128
|
return (
|
|
220
129
|
_ServiceDefinition(provides=repository_contract, scope=Scope.REQUEST),
|
|
221
|
-
None,
|
|
222
130
|
ConventionDiscoveryKind.FRAMEWORK_MARKER,
|
|
223
131
|
"accepted from unique inherited RepositoryContract",
|
|
224
132
|
)
|
|
225
|
-
application_definition = _application_service_definition(candidate)
|
|
226
|
-
if application_definition is not None:
|
|
227
|
-
if _SERVICE_DEFINITION_ATTRIBUTE in candidate.__dict__:
|
|
228
|
-
raise ApplicationServiceDefinitionError(
|
|
229
|
-
symbol=f"{candidate.__module__}.{candidate.__qualname__}",
|
|
230
|
-
reason="application_service and service metadata cannot be combined",
|
|
231
|
-
)
|
|
232
|
-
decorated = _APPLICATION_SERVICE_DEFINITION_ATTRIBUTE in candidate.__dict__
|
|
233
|
-
discovered_by = (
|
|
234
|
-
ConventionDiscoveryKind.APPLICATION_SERVICE_DECORATOR
|
|
235
|
-
if decorated
|
|
236
|
-
else ConventionDiscoveryKind.FRAMEWORK_MARKER
|
|
237
|
-
)
|
|
238
|
-
reason = (
|
|
239
|
-
"accepted from immutable application_service decorator metadata"
|
|
240
|
-
if decorated
|
|
241
|
-
else "accepted from unique inherited ApplicationService contract"
|
|
242
|
-
)
|
|
243
|
-
return (
|
|
244
|
-
_ServiceDefinition(
|
|
245
|
-
provides=application_definition.contract or candidate,
|
|
246
|
-
scope=Scope.ACTION,
|
|
247
|
-
),
|
|
248
|
-
application_definition,
|
|
249
|
-
discovered_by,
|
|
250
|
-
reason,
|
|
251
|
-
)
|
|
252
133
|
metadata = candidate.__dict__.get(_SERVICE_DEFINITION_ATTRIBUTE)
|
|
253
134
|
if metadata is not None:
|
|
254
135
|
if not isinstance(metadata, _ServiceDefinition):
|
|
@@ -258,17 +139,12 @@ class _ConventionScanner:
|
|
|
258
139
|
)
|
|
259
140
|
return (
|
|
260
141
|
metadata,
|
|
261
|
-
None,
|
|
262
142
|
ConventionDiscoveryKind.DECORATOR_METADATA,
|
|
263
143
|
"accepted from immutable service decorator metadata",
|
|
264
144
|
)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
None,
|
|
269
|
-
ConventionDiscoveryKind.DECORATOR_METADATA,
|
|
270
|
-
"accepted from immutable local_event_handler metadata",
|
|
271
|
-
)
|
|
145
|
+
convention = self._conventions.get((discovery.owner, candidate))
|
|
146
|
+
if convention is not None:
|
|
147
|
+
return convention.definition, convention.kind, convention.reason
|
|
272
148
|
lifetimes = tuple(
|
|
273
149
|
(marker, scope, cache)
|
|
274
150
|
for marker, scope, cache in _LIFETIME_MARKERS
|
|
@@ -276,25 +152,21 @@ class _ConventionScanner:
|
|
|
276
152
|
)
|
|
277
153
|
if lifetimes and not inspect.isabstract(candidate):
|
|
278
154
|
if len(lifetimes) != 1:
|
|
279
|
-
#
|
|
280
|
-
# 显式覆盖链可以让它退让,扫描阶段不自行挑选有效注册。
|
|
155
|
+
# 冲突等最终仲裁确认:显式注册可替代约定,约定本身胜出则必须拒绝。
|
|
281
156
|
self._lifetime_conflicts.add(candidate)
|
|
282
157
|
marker, scope, cache = lifetimes[0]
|
|
283
158
|
return (
|
|
284
159
|
_ServiceDefinition(provides=None, scope=scope, cache=cache),
|
|
285
|
-
None,
|
|
286
160
|
ConventionDiscoveryKind.FRAMEWORK_MARKER,
|
|
287
161
|
f"accepted from {marker.__name__} marker",
|
|
288
162
|
)
|
|
289
163
|
if issubclass(candidate, ConventionalService) and candidate is not ConventionalService:
|
|
290
164
|
return (
|
|
291
165
|
_ServiceDefinition(provides=candidate, scope=Scope.APP),
|
|
292
|
-
None,
|
|
293
166
|
ConventionDiscoveryKind.FRAMEWORK_MARKER,
|
|
294
167
|
"accepted from ConventionalService marker",
|
|
295
168
|
)
|
|
296
169
|
return (
|
|
297
|
-
None,
|
|
298
170
|
None,
|
|
299
171
|
ConventionDiscoveryKind.NONE,
|
|
300
172
|
"rejected because no service decorator metadata or framework marker exists",
|
|
@@ -2,32 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
from dishka import Scope
|
|
4
4
|
|
|
5
|
-
from ..application_services.contracts import ApplicationService
|
|
6
|
-
from ..background_jobs.contracts import BackgroundJobHandler
|
|
7
|
-
from ..background_workers.contracts import BackgroundWorker
|
|
8
5
|
from ..errors import InvalidConventionDefinitionError
|
|
9
|
-
from ..hosted_services.contracts import HostedService
|
|
10
6
|
from .convention_contracts import (
|
|
11
7
|
_LIFETIME_MARKERS,
|
|
12
8
|
_SERVICE_DEFINITION_ATTRIBUTE,
|
|
13
9
|
_ServiceDefinition,
|
|
14
10
|
)
|
|
15
|
-
from .repository import RepositoryContract
|
|
16
11
|
|
|
17
12
|
|
|
18
13
|
def _fixed_scope(candidate: object) -> Scope | None:
|
|
19
14
|
if not isinstance(candidate, type):
|
|
20
15
|
return None
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
(
|
|
26
|
-
|
|
27
|
-
):
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
# 生命周期由角色自身声明,内核不维护能力类型的第二份名单。
|
|
17
|
+
scopes = {
|
|
18
|
+
scope
|
|
19
|
+
for role in candidate.__mro__
|
|
20
|
+
if (scope := role.__dict__.get("_fixed_service_scope")) is not None
|
|
21
|
+
}
|
|
22
|
+
if len(scopes) > 1 or any(not isinstance(scope, Scope) for scope in scopes):
|
|
23
|
+
raise InvalidConventionDefinitionError(
|
|
24
|
+
symbol=candidate.__qualname__,
|
|
25
|
+
reason="conflicting fixed role lifetimes",
|
|
26
|
+
)
|
|
27
|
+
return next(iter(scopes), None)
|
|
31
28
|
|
|
32
29
|
|
|
33
30
|
def _validate_fixed_lifetime(candidate: type[object]) -> None:
|