python-ddd-framework 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_ddd_framework/__init__.py +449 -0
- python_ddd_framework/application/__init__.py +6 -0
- python_ddd_framework/application/build_spec.py +37 -0
- python_ddd_framework/application/builder.py +260 -0
- python_ddd_framework/application/composition.py +219 -0
- python_ddd_framework/application/runtime.py +549 -0
- python_ddd_framework/application/runtime_hooks.py +327 -0
- python_ddd_framework/application/state.py +216 -0
- python_ddd_framework/application_services/__init__.py +54 -0
- python_ddd_framework/application_services/catalog.py +500 -0
- python_ddd_framework/application_services/contracts.py +190 -0
- python_ddd_framework/application_services/dispatcher.py +423 -0
- python_ddd_framework/application_services/errors.py +89 -0
- python_ddd_framework/application_services/execution.py +199 -0
- python_ddd_framework/application_services/interceptors.py +44 -0
- python_ddd_framework/application_services/invocation.py +431 -0
- python_ddd_framework/application_services/policies.py +46 -0
- python_ddd_framework/application_services/seeding.py +72 -0
- python_ddd_framework/application_services/signature.py +49 -0
- python_ddd_framework/application_services/validation.py +84 -0
- python_ddd_framework/auditing/__init__.py +12 -0
- python_ddd_framework/auditing/contracts.py +39 -0
- python_ddd_framework/auditing/control.py +48 -0
- python_ddd_framework/auditing/sqlalchemy/__init__.py +6 -0
- python_ddd_framework/auditing/sqlalchemy/migrations/0001_auditing.py +45 -0
- python_ddd_framework/auditing/sqlalchemy/migrations/__init__.py +1 -0
- python_ddd_framework/auditing/sqlalchemy/models.py +31 -0
- python_ddd_framework/auditing/sqlalchemy/module.py +32 -0
- python_ddd_framework/auditing/sqlalchemy/store.py +63 -0
- python_ddd_framework/authorization/__init__.py +38 -0
- python_ddd_framework/authorization/catalog.py +63 -0
- python_ddd_framework/authorization/contracts.py +241 -0
- python_ddd_framework/authorization/definition_discovery.py +81 -0
- python_ddd_framework/authorization/definitions.py +43 -0
- python_ddd_framework/authorization/errors.py +40 -0
- python_ddd_framework/background_execution/__init__.py +17 -0
- python_ddd_framework/background_execution/application.py +41 -0
- python_ddd_framework/background_execution/child.py +123 -0
- python_ddd_framework/background_execution/contracts.py +48 -0
- python_ddd_framework/background_execution/lifecycle.py +22 -0
- python_ddd_framework/background_execution/local.py +127 -0
- python_ddd_framework/background_execution/locks.py +33 -0
- python_ddd_framework/background_execution/management.py +17 -0
- python_ddd_framework/background_execution/module.py +10 -0
- python_ddd_framework/background_execution/permissions.py +14 -0
- python_ddd_framework/background_execution/processes.py +331 -0
- python_ddd_framework/background_jobs/__init__.py +31 -0
- python_ddd_framework/background_jobs/catalog.py +340 -0
- python_ddd_framework/background_jobs/contracts.py +208 -0
- python_ddd_framework/background_jobs/declaration.py +67 -0
- python_ddd_framework/background_jobs/errors.py +28 -0
- python_ddd_framework/background_jobs/execution.py +203 -0
- python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +21 -0
- python_ddd_framework/background_jobs/pgqueuer/__init__.py +6 -0
- python_ddd_framework/background_jobs/pgqueuer/enqueue.py +86 -0
- python_ddd_framework/background_jobs/pgqueuer/migrations/0001_pgqueuer_1_3_2.py +95 -0
- python_ddd_framework/background_jobs/pgqueuer/migrations/__init__.py +1 -0
- python_ddd_framework/background_jobs/pgqueuer/module.py +106 -0
- python_ddd_framework/background_jobs/pgqueuer/options.py +82 -0
- python_ddd_framework/background_jobs/pgqueuer/runtime.py +479 -0
- python_ddd_framework/background_jobs/pgqueuer/sql/pgqueuer_1_3_2_install.sql +118 -0
- python_ddd_framework/background_jobs/pgqueuer/supervision.py +201 -0
- python_ddd_framework/background_workers/__init__.py +22 -0
- python_ddd_framework/background_workers/catalog.py +118 -0
- python_ddd_framework/background_workers/contracts.py +152 -0
- python_ddd_framework/background_workers/errors.py +20 -0
- python_ddd_framework/background_workers/execution.py +133 -0
- python_ddd_framework/background_workers/runtime.py +219 -0
- python_ddd_framework/caching/__init__.py +11 -0
- python_ddd_framework/caching/catalog.py +73 -0
- python_ddd_framework/caching/contracts.py +73 -0
- python_ddd_framework/caching/errors.py +20 -0
- python_ddd_framework/cli/__init__.py +105 -0
- python_ddd_framework/cli/development.py +85 -0
- python_ddd_framework/cli/errors.py +5 -0
- python_ddd_framework/cli/inspection.py +96 -0
- python_ddd_framework/cli/project.py +69 -0
- python_ddd_framework/cli/runtime.py +56 -0
- python_ddd_framework/configuration/__init__.py +21 -0
- python_ddd_framework/configuration/composition.py +65 -0
- python_ddd_framework/configuration/contracts.py +68 -0
- python_ddd_framework/configuration/dotenv_source.py +51 -0
- python_ddd_framework/configuration/environment_source.py +38 -0
- python_ddd_framework/configuration/immutability.py +124 -0
- python_ddd_framework/configuration/input_shape.py +58 -0
- python_ddd_framework/configuration/merge.py +103 -0
- python_ddd_framework/configuration/root.py +215 -0
- python_ddd_framework/configuration/sources.py +519 -0
- python_ddd_framework/configuration/values.py +427 -0
- python_ddd_framework/configuration/yaml_source.py +51 -0
- python_ddd_framework/developer_kit/__init__.py +1 -0
- python_ddd_framework/developer_kit/generation.py +182 -0
- python_ddd_framework/developer_kit/project_metadata.py +45 -0
- python_ddd_framework/developer_kit/source.py +53 -0
- python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +70 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +24 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration.py.jinja +41 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +20 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +83 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +84 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +35 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/orders.py.jinja +56 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +14 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +19 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings.py.jinja +16 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +24 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions.py.jinja +15 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/files.py.jinja +73 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +25 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +36 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/orders.py.jinja +25 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +26 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +49 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +18 -0
- python_ddd_framework/developer_kit/templates/project/cookiecutter.json +6 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/.dockerignore +9 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/.gitignore +6 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/.python-version +1 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +38 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/Dockerfile +22 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +130 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/app.development.yaml.jinja +30 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/compose.dev.yaml.jinja +24 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/compose.production.yaml.jinja +20 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +84 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +185 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/pyproject.toml.jinja +28 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/main.py.jinja +47 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/module.py.jinja +37 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/tests/conftest.py.jinja +8 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/tests/host/test_http.py.jinja +57 -0
- python_ddd_framework/developer_kit/wiring.py +162 -0
- python_ddd_framework/diagnostics/__init__.py +23 -0
- python_ddd_framework/diagnostics/journal.py +174 -0
- python_ddd_framework/diagnostics/model.py +83 -0
- python_ddd_framework/diagnostics/source.py +25 -0
- python_ddd_framework/distributed_lock/__init__.py +6 -0
- python_ddd_framework/distributed_lock/contracts.py +20 -0
- python_ddd_framework/distributed_lock/options.py +19 -0
- python_ddd_framework/domain/__init__.py +13 -0
- python_ddd_framework/domain/aggregates.py +64 -0
- python_ddd_framework/domain/entities.py +33 -0
- python_ddd_framework/domain/errors.py +12 -0
- python_ddd_framework/domain/value_objects.py +31 -0
- python_ddd_framework/errors/__init__.py +75 -0
- python_ddd_framework/errors/base.py +13 -0
- python_ddd_framework/errors/business.py +53 -0
- python_ddd_framework/errors/configuration.py +119 -0
- python_ddd_framework/errors/diagnostics.py +12 -0
- python_ddd_framework/errors/lifecycle.py +164 -0
- python_ddd_framework/errors/modularity.py +100 -0
- python_ddd_framework/errors/services.py +100 -0
- python_ddd_framework/events/__init__.py +25 -0
- python_ddd_framework/events/aggregate.py +39 -0
- python_ddd_framework/events/catalog.py +136 -0
- python_ddd_framework/events/contracts.py +39 -0
- python_ddd_framework/events/contribution.py +128 -0
- python_ddd_framework/events/discovery.py +105 -0
- python_ddd_framework/events/errors.py +48 -0
- python_ddd_framework/events/runtime.py +192 -0
- python_ddd_framework/fastapi/__init__.py +49 -0
- python_ddd_framework/fastapi/action.py +175 -0
- python_ddd_framework/fastapi/adapter.py +391 -0
- python_ddd_framework/fastapi/application_services.py +654 -0
- python_ddd_framework/fastapi/background.py +42 -0
- python_ddd_framework/fastapi/contracts.py +275 -0
- python_ddd_framework/fastapi/errors.py +32 -0
- python_ddd_framework/fastapi/filters.py +111 -0
- python_ddd_framework/fastapi/health.py +44 -0
- python_ddd_framework/fastapi/http.py +72 -0
- python_ddd_framework/fastapi/http_router.py +216 -0
- python_ddd_framework/fastapi/manual_action.py +105 -0
- python_ddd_framework/fastapi/middleware.py +93 -0
- python_ddd_framework/fastapi/parameters.py +97 -0
- python_ddd_framework/fastapi/realtime/__init__.py +13 -0
- python_ddd_framework/fastapi/realtime/authentication.py +181 -0
- python_ddd_framework/fastapi/realtime/connection.py +166 -0
- python_ddd_framework/fastapi/realtime/module.py +41 -0
- python_ddd_framework/fastapi/realtime/options.py +50 -0
- python_ddd_framework/fastapi/realtime/runtime.py +423 -0
- python_ddd_framework/fastapi/request_context.py +247 -0
- python_ddd_framework/fastapi/route_integrity.py +88 -0
- python_ddd_framework/fastapi/routing.py +424 -0
- python_ddd_framework/fastapi/server.py +182 -0
- python_ddd_framework/fastapi/settings.py +40 -0
- python_ddd_framework/fastapi/tracing.py +62 -0
- python_ddd_framework/fastapi/transfer.py +128 -0
- python_ddd_framework/fastapi/upload_limits.py +63 -0
- python_ddd_framework/fastapi/uploads.py +94 -0
- python_ddd_framework/hosted_services/__init__.py +29 -0
- python_ddd_framework/hosted_services/bridge.py +420 -0
- python_ddd_framework/hosted_services/catalog.py +265 -0
- python_ddd_framework/hosted_services/contracts.py +55 -0
- python_ddd_framework/hosted_services/errors.py +53 -0
- python_ddd_framework/hosted_services/options.py +17 -0
- python_ddd_framework/hosted_services/runtime.py +267 -0
- python_ddd_framework/hosted_services/state.py +40 -0
- python_ddd_framework/hosting/__init__.py +4 -0
- python_ddd_framework/hosting/instance.py +40 -0
- python_ddd_framework/identity/__init__.py +73 -0
- python_ddd_framework/identity/application.py +287 -0
- python_ddd_framework/identity/contracts.py +283 -0
- python_ddd_framework/identity/errors.py +28 -0
- python_ddd_framework/identity/http_api.py +90 -0
- python_ddd_framework/identity/module.py +49 -0
- python_ddd_framework/identity/passwords.py +34 -0
- python_ddd_framework/identity/permissions.py +14 -0
- python_ddd_framework/identity/services.py +108 -0
- python_ddd_framework/identity/sqlalchemy/__init__.py +5 -0
- python_ddd_framework/identity/sqlalchemy/migrations/0001_identity.py +143 -0
- python_ddd_framework/identity/sqlalchemy/migrations/0002_physical_delete.py +42 -0
- python_ddd_framework/identity/sqlalchemy/migrations/__init__.py +1 -0
- python_ddd_framework/identity/sqlalchemy/models.py +85 -0
- python_ddd_framework/identity/sqlalchemy/module.py +62 -0
- python_ddd_framework/identity/sqlalchemy/stores.py +526 -0
- python_ddd_framework/identity/tokens.py +106 -0
- python_ddd_framework/invocation/__init__.py +3 -0
- python_ddd_framework/invocation/callables.py +171 -0
- python_ddd_framework/invocation/contracts.py +33 -0
- python_ddd_framework/invocation/entries.py +54 -0
- python_ddd_framework/invocation/function_runtime.py +48 -0
- python_ddd_framework/invocation/interception.py +193 -0
- python_ddd_framework/lifecycle/__init__.py +25 -0
- python_ddd_framework/lifecycle/composition.py +35 -0
- python_ddd_framework/lifecycle/context.py +31 -0
- python_ddd_framework/lifecycle/runtime.py +43 -0
- python_ddd_framework/lifecycle/state.py +20 -0
- python_ddd_framework/modularity/__init__.py +15 -0
- python_ddd_framework/modularity/contracts.py +91 -0
- python_ddd_framework/modularity/discovery.py +157 -0
- python_ddd_framework/modularity/graph.py +334 -0
- python_ddd_framework/modularity/registry.py +100 -0
- python_ddd_framework/modularity/selection.py +16 -0
- python_ddd_framework/notifications/__init__.py +19 -0
- python_ddd_framework/notifications/catalog.py +46 -0
- python_ddd_framework/notifications/contracts.py +107 -0
- python_ddd_framework/observability/__init__.py +5 -0
- python_ddd_framework/observability/context.py +43 -0
- python_ddd_framework/observability/export.py +73 -0
- python_ddd_framework/observability/formatting.py +89 -0
- python_ddd_framework/observability/logging.py +118 -0
- python_ddd_framework/observability/options.py +45 -0
- python_ddd_framework/observability/tracing.py +80 -0
- python_ddd_framework/options/__init__.py +19 -0
- python_ddd_framework/options/aliases.py +331 -0
- python_ddd_framework/options/contribution.py +62 -0
- python_ddd_framework/options/immutability.py +48 -0
- python_ddd_framework/options/input_keys.py +214 -0
- python_ddd_framework/options/issues.py +335 -0
- python_ddd_framework/options/models.py +114 -0
- python_ddd_framework/options/registry.py +280 -0
- python_ddd_framework/options/schema.py +488 -0
- python_ddd_framework/options/validation.py +120 -0
- python_ddd_framework/py.typed +0 -0
- python_ddd_framework/realtime/__init__.py +12 -0
- python_ddd_framework/realtime/contracts.py +28 -0
- python_ddd_framework/realtime/diagnostics.py +37 -0
- python_ddd_framework/realtime/messages.py +113 -0
- python_ddd_framework/redis/__init__.py +6 -0
- python_ddd_framework/redis/distributed_lock.py +212 -0
- python_ddd_framework/redis/lease_lock.py +36 -0
- python_ddd_framework/redis/module.py +50 -0
- python_ddd_framework/redis/notification_runtime.py +182 -0
- python_ddd_framework/redis/notifications.py +25 -0
- python_ddd_framework/redis/options.py +35 -0
- python_ddd_framework/redis/runtime.py +116 -0
- python_ddd_framework/services/__init__.py +41 -0
- python_ddd_framework/services/application_bindings.py +221 -0
- python_ddd_framework/services/arbitration.py +381 -0
- python_ddd_framework/services/binding.py +102 -0
- python_ddd_framework/services/contribution.py +438 -0
- python_ddd_framework/services/convention.py +301 -0
- python_ddd_framework/services/convention_contracts.py +101 -0
- python_ddd_framework/services/exposure.py +23 -0
- python_ddd_framework/services/fixed_lifetime.py +52 -0
- python_ddd_framework/services/framework_provider.py +178 -0
- python_ddd_framework/services/native_graph.py +201 -0
- python_ddd_framework/services/provider.py +204 -0
- python_ddd_framework/services/registration.py +101 -0
- python_ddd_framework/services/repository.py +38 -0
- python_ddd_framework/services/runtime.py +302 -0
- python_ddd_framework/settings/__init__.py +30 -0
- python_ddd_framework/settings/application.py +95 -0
- python_ddd_framework/settings/binding.py +33 -0
- python_ddd_framework/settings/catalog.py +121 -0
- python_ddd_framework/settings/changes.py +14 -0
- python_ddd_framework/settings/contracts.py +115 -0
- python_ddd_framework/settings/definition_discovery.py +71 -0
- python_ddd_framework/settings/definitions.py +41 -0
- python_ddd_framework/settings/errors.py +22 -0
- python_ddd_framework/settings/handlers.py +43 -0
- python_ddd_framework/settings/management.py +57 -0
- python_ddd_framework/settings/manager.py +89 -0
- python_ddd_framework/settings/module.py +10 -0
- python_ddd_framework/settings/notifications.py +39 -0
- python_ddd_framework/settings/permissions.py +14 -0
- python_ddd_framework/settings/provider.py +74 -0
- python_ddd_framework/settings/refresh.py +140 -0
- python_ddd_framework/settings/refresh_module.py +45 -0
- python_ddd_framework/settings/sqlalchemy/__init__.py +5 -0
- python_ddd_framework/settings/sqlalchemy/migrations/0001_settings.py +31 -0
- python_ddd_framework/settings/sqlalchemy/migrations/0002_physical_delete.py +32 -0
- python_ddd_framework/settings/sqlalchemy/migrations/0003_version_tokens.py +39 -0
- python_ddd_framework/settings/sqlalchemy/migrations/__init__.py +1 -0
- python_ddd_framework/settings/sqlalchemy/models.py +36 -0
- python_ddd_framework/settings/sqlalchemy/module.py +36 -0
- python_ddd_framework/settings/sqlalchemy/store.py +86 -0
- python_ddd_framework/settings/store.py +16 -0
- python_ddd_framework/settings/values.py +39 -0
- python_ddd_framework/sqlalchemy/__init__.py +54 -0
- python_ddd_framework/sqlalchemy/alembic_runtime/__init__.py +1 -0
- python_ddd_framework/sqlalchemy/alembic_runtime/env.py +33 -0
- python_ddd_framework/sqlalchemy/alembic_runtime/script.py.mako +14 -0
- python_ddd_framework/sqlalchemy/auditing.py +48 -0
- python_ddd_framework/sqlalchemy/errors.py +85 -0
- python_ddd_framework/sqlalchemy/metadata.py +576 -0
- python_ddd_framework/sqlalchemy/migration.py +378 -0
- python_ddd_framework/sqlalchemy/migration_options.py +56 -0
- python_ddd_framework/sqlalchemy/module.py +51 -0
- python_ddd_framework/sqlalchemy/module_migration.py +151 -0
- python_ddd_framework/sqlalchemy/options.py +66 -0
- python_ddd_framework/sqlalchemy/repository.py +33 -0
- python_ddd_framework/sqlalchemy/runtime.py +85 -0
- python_ddd_framework/sqlalchemy/session_provider.py +45 -0
- python_ddd_framework/sqlalchemy/unit_of_work.py +97 -0
- python_ddd_framework/testing/__init__.py +5 -0
- python_ddd_framework/testing/runtime.py +112 -0
- python_ddd_framework/unit_of_work/__init__.py +14 -0
- python_ddd_framework/unit_of_work/contracts.py +223 -0
- python_ddd_framework/unit_of_work/errors.py +27 -0
- python_ddd_framework/unit_of_work/manager.py +210 -0
- python_ddd_framework/unit_of_work/options.py +92 -0
- python_ddd_framework-0.3.1.dist-info/METADATA +379 -0
- python_ddd_framework-0.3.1.dist-info/RECORD +355 -0
- python_ddd_framework-0.3.1.dist-info/WHEEL +4 -0
- python_ddd_framework-0.3.1.dist-info/entry_points.txt +9 -0
- python_ddd_framework-0.3.1.dist-info/licenses/LICENSE +7 -0
- python_ddd_framework-0.3.1.dist-info/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +21 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""只读投影原生 Dishka 图,核对平台 catalog 与实际 binding 的一致性。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import get_origin
|
|
8
|
+
|
|
9
|
+
from dishka import (
|
|
10
|
+
DEFAULT_COMPONENT,
|
|
11
|
+
AsyncContainer,
|
|
12
|
+
BaseScope,
|
|
13
|
+
DependencyKey,
|
|
14
|
+
Scope,
|
|
15
|
+
)
|
|
16
|
+
from dishka.dependency_source import Factory
|
|
17
|
+
from dishka.entities.factory_type import FactoryType
|
|
18
|
+
|
|
19
|
+
from ..application_services import ApplicationService, ApplicationServiceCatalog
|
|
20
|
+
from ..application_services.errors import ApplicationServiceDefinitionError
|
|
21
|
+
from ..application_services.interceptors import InterceptionStage
|
|
22
|
+
from ..errors import CatalogNativeGraphMismatchError, InvalidConventionDefinitionError
|
|
23
|
+
from ..invocation.interception import _EntryInterceptionCatalog
|
|
24
|
+
from .arbitration import (
|
|
25
|
+
_RegistrationDraft,
|
|
26
|
+
)
|
|
27
|
+
from .fixed_lifetime import _fixed_scope, _validate_fixed_lifetime
|
|
28
|
+
from .registration import (
|
|
29
|
+
RegistrationDecision,
|
|
30
|
+
RegistrationKind,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True, slots=True)
|
|
35
|
+
class _NativeGraphRegistration:
|
|
36
|
+
key: DependencyKey
|
|
37
|
+
scope: BaseScope
|
|
38
|
+
factory: Factory
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class _NativeServiceGraph:
|
|
42
|
+
"""只读投影 Dishka 已构建图,用于证明 catalog 与真实容器一致。"""
|
|
43
|
+
|
|
44
|
+
__slots__ = ("_rows",)
|
|
45
|
+
|
|
46
|
+
def __init__(self, container: AsyncContainer) -> None:
|
|
47
|
+
rows: list[_NativeGraphRegistration] = []
|
|
48
|
+
native_registry = container.registry
|
|
49
|
+
while True:
|
|
50
|
+
rows.extend(
|
|
51
|
+
_NativeGraphRegistration(
|
|
52
|
+
key=key,
|
|
53
|
+
scope=native_registry.scope,
|
|
54
|
+
factory=factory,
|
|
55
|
+
)
|
|
56
|
+
for key, factory in native_registry.factories.items()
|
|
57
|
+
)
|
|
58
|
+
child_registry = native_registry.child_registry
|
|
59
|
+
if child_registry is None:
|
|
60
|
+
break
|
|
61
|
+
native_registry = child_registry
|
|
62
|
+
self._rows = tuple(rows)
|
|
63
|
+
|
|
64
|
+
def differences(self, drafts: tuple[_RegistrationDraft, ...]) -> tuple[str, ...]:
|
|
65
|
+
return tuple(
|
|
66
|
+
f"missing accepted {draft.contribution.kind.value} registration for {draft.key}"
|
|
67
|
+
for draft in drafts
|
|
68
|
+
if draft.decision is RegistrationDecision.ACCEPTED and not self._matches(draft)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def validate_fixed_lifetimes(self) -> None:
|
|
72
|
+
for row in self._rows:
|
|
73
|
+
for candidate in (row.key.type_hint, row.factory.source):
|
|
74
|
+
# 闭合泛型仍受原始角色契约约束,函数 factory 不提供可替代的类信息。
|
|
75
|
+
candidate = get_origin(candidate) or candidate
|
|
76
|
+
scope = _fixed_scope(candidate)
|
|
77
|
+
if scope is None or not isinstance(candidate, type):
|
|
78
|
+
continue
|
|
79
|
+
_validate_fixed_lifetime(candidate)
|
|
80
|
+
if (
|
|
81
|
+
issubclass(candidate, ApplicationService)
|
|
82
|
+
and row.key.component == DEFAULT_COMPONENT
|
|
83
|
+
):
|
|
84
|
+
# 公开 REQUEST proxy 由框架提供;其实际实现已移入私有 ACTION component。
|
|
85
|
+
continue
|
|
86
|
+
factory = row.factory
|
|
87
|
+
# Alias 自身不缓存;沿 Dishka 已验证的链核对真正创建实例的 factory。
|
|
88
|
+
while factory.type is FactoryType.ALIAS:
|
|
89
|
+
factory = next(
|
|
90
|
+
item.factory for item in self._rows if item.key == factory.dependencies[0]
|
|
91
|
+
)
|
|
92
|
+
if row.scope is not scope or not factory.cache:
|
|
93
|
+
raise InvalidConventionDefinitionError(
|
|
94
|
+
symbol=str(row.key.type_hint),
|
|
95
|
+
reason=f"final fixed role binding requires cached {scope.name} lifetime",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def scope_for(self, draft: _RegistrationDraft) -> BaseScope:
|
|
99
|
+
if draft.scope is not None:
|
|
100
|
+
return draft.scope
|
|
101
|
+
for row in self._rows:
|
|
102
|
+
if row.key == draft.key:
|
|
103
|
+
return row.scope
|
|
104
|
+
raise CatalogNativeGraphMismatchError(
|
|
105
|
+
differences=(f"native scope unavailable for {draft.key}",)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def interceptor_scope(self, interceptor: type[object]) -> BaseScope | None:
|
|
109
|
+
return next(
|
|
110
|
+
(
|
|
111
|
+
row.scope
|
|
112
|
+
for row in self._rows
|
|
113
|
+
if row.key == DependencyKey(interceptor, DEFAULT_COMPONENT)
|
|
114
|
+
),
|
|
115
|
+
None,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def implementation_for(self, target: type[object]) -> type[object]:
|
|
119
|
+
key = DependencyKey(target, DEFAULT_COMPONENT)
|
|
120
|
+
while True:
|
|
121
|
+
factory = next(row.factory for row in self._rows if row.key == key)
|
|
122
|
+
if factory.type is FactoryType.ALIAS:
|
|
123
|
+
# 原生图已验证 alias 链及循环;沿真实依赖取得最终 class factory。
|
|
124
|
+
key = factory.dependencies[0]
|
|
125
|
+
continue
|
|
126
|
+
# 动态工厂只承诺注册契约,不能在 build 执行它来猜测运行实例。
|
|
127
|
+
return factory.source if isinstance(factory.source, type) else target
|
|
128
|
+
|
|
129
|
+
def _matches(self, draft: _RegistrationDraft) -> bool:
|
|
130
|
+
if draft.is_collection:
|
|
131
|
+
return any(
|
|
132
|
+
row.key == draft.key and row.factory.type is FactoryType.COLLECTION
|
|
133
|
+
for row in self._rows
|
|
134
|
+
)
|
|
135
|
+
assert draft.factory is not None
|
|
136
|
+
if draft.alias is not None:
|
|
137
|
+
return any(
|
|
138
|
+
row.key == draft.key
|
|
139
|
+
and row.factory.type is FactoryType.ALIAS
|
|
140
|
+
and row.factory.dependencies == draft.factory.dependencies
|
|
141
|
+
for row in self._rows
|
|
142
|
+
)
|
|
143
|
+
if draft.contribution.kind is RegistrationKind.MULTI:
|
|
144
|
+
return any(
|
|
145
|
+
row.key.type_hint == draft.key.type_hint
|
|
146
|
+
and row.key.component == draft.key.component
|
|
147
|
+
and row.factory.source is draft.factory.source
|
|
148
|
+
and row.scope is draft.scope
|
|
149
|
+
for row in self._rows
|
|
150
|
+
)
|
|
151
|
+
return any(
|
|
152
|
+
row.key == draft.key
|
|
153
|
+
and row.factory.source is draft.factory.source
|
|
154
|
+
and row.scope is draft.scope
|
|
155
|
+
for row in self._rows
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _validate_interceptors(
|
|
160
|
+
graph: _NativeServiceGraph,
|
|
161
|
+
catalog: ApplicationServiceCatalog,
|
|
162
|
+
entries: _EntryInterceptionCatalog,
|
|
163
|
+
) -> None:
|
|
164
|
+
# 匹配结果和实际入口 scope 来自同一编译 catalog,不另建后台类型或规则清单。
|
|
165
|
+
requirements = (
|
|
166
|
+
(
|
|
167
|
+
registration,
|
|
168
|
+
Scope.ACTION if registration.before is InterceptionStage.METHOD else Scope.REQUEST,
|
|
169
|
+
)
|
|
170
|
+
for service in catalog.registrations
|
|
171
|
+
for method in service.methods
|
|
172
|
+
for registration in method.interceptors
|
|
173
|
+
)
|
|
174
|
+
for registration, available in (*requirements, *entries.interceptor_scopes):
|
|
175
|
+
scope = graph.interceptor_scope(registration.interceptor)
|
|
176
|
+
if scope not in (Scope.APP, Scope.REQUEST, Scope.ACTION) or scope > available:
|
|
177
|
+
raise ApplicationServiceDefinitionError(
|
|
178
|
+
symbol=registration.interceptor.__qualname__,
|
|
179
|
+
reason=(
|
|
180
|
+
f"interceptor scope is unavailable before {registration.before.value}; "
|
|
181
|
+
f"owner={registration.owner}"
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _accepted_single_service_scopes(
|
|
187
|
+
drafts: tuple[_RegistrationDraft, ...],
|
|
188
|
+
native_graph: _NativeServiceGraph,
|
|
189
|
+
) -> Mapping[type[object], BaseScope]:
|
|
190
|
+
accepted: dict[type[object], BaseScope] = {}
|
|
191
|
+
for draft in drafts:
|
|
192
|
+
key_type = draft.key.type_hint
|
|
193
|
+
if (
|
|
194
|
+
draft.decision is RegistrationDecision.ACCEPTED
|
|
195
|
+
and draft.contribution.kind is not RegistrationKind.MULTI
|
|
196
|
+
and draft.factory is not None
|
|
197
|
+
and isinstance(key_type, type)
|
|
198
|
+
and draft.key.component == DEFAULT_COMPONENT
|
|
199
|
+
):
|
|
200
|
+
accepted[key_type] = native_graph.scope_for(draft)
|
|
201
|
+
return accepted
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Dishka native Provider 的受支持边界、结构验证与 mutation fingerprint。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from types import MethodType
|
|
7
|
+
|
|
8
|
+
from dishka import BaseScope, DependencyKey, Provider
|
|
9
|
+
from dishka.dependency_source import Alias, Factory, FactoryUnionMode
|
|
10
|
+
from dishka.entities.factory_type import FactoryType
|
|
11
|
+
from dishka.entities.marker import BoolMarker
|
|
12
|
+
|
|
13
|
+
from ..diagnostics import SourceLocation
|
|
14
|
+
from ..errors import RegistrationConflictError
|
|
15
|
+
from .registration import SUPPORTED_SCOPE_LABEL, SUPPORTED_SCOPES, RegistrationKind
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class _FactorySignature:
|
|
20
|
+
key: DependencyKey
|
|
21
|
+
scope: BaseScope | None
|
|
22
|
+
source_identity: tuple[int, int | None]
|
|
23
|
+
factory_type: FactoryType
|
|
24
|
+
dependencies: tuple[DependencyKey, ...]
|
|
25
|
+
keyword_dependencies: tuple[tuple[str, DependencyKey], ...]
|
|
26
|
+
cache: bool
|
|
27
|
+
allow_static_evaluation: bool
|
|
28
|
+
is_to_bind: bool
|
|
29
|
+
native_override: bool
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class _CollectionSignature:
|
|
34
|
+
source_key: DependencyKey
|
|
35
|
+
key: DependencyKey
|
|
36
|
+
scope: BaseScope | None
|
|
37
|
+
cache: bool
|
|
38
|
+
collect: bool
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True, slots=True)
|
|
42
|
+
class _ProviderSignature:
|
|
43
|
+
factories: tuple[_FactorySignature, ...]
|
|
44
|
+
collections: tuple[_CollectionSignature, ...]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True, slots=True)
|
|
48
|
+
class _FactoryDefinition:
|
|
49
|
+
factory: Factory
|
|
50
|
+
key: DependencyKey
|
|
51
|
+
scope: BaseScope | None
|
|
52
|
+
native_override: bool
|
|
53
|
+
alias: Alias | None = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True, slots=True)
|
|
57
|
+
class _CollectionDefinition:
|
|
58
|
+
collection: FactoryUnionMode
|
|
59
|
+
source_key: DependencyKey
|
|
60
|
+
key: DependencyKey
|
|
61
|
+
scope: BaseScope | None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True, slots=True)
|
|
65
|
+
class _ValidatedProvider:
|
|
66
|
+
factories: tuple[_FactoryDefinition, ...]
|
|
67
|
+
collections: tuple[_CollectionDefinition, ...]
|
|
68
|
+
signature: _ProviderSignature
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class _NativeProviderInspector:
|
|
72
|
+
"""隔离 Dishka private dependency-source shape,返回框架拥有的稳定事实。"""
|
|
73
|
+
|
|
74
|
+
__slots__ = ("_kind", "_source")
|
|
75
|
+
|
|
76
|
+
def __init__(self, *, source: SourceLocation, kind: RegistrationKind) -> None:
|
|
77
|
+
self._source = source
|
|
78
|
+
self._kind = kind
|
|
79
|
+
|
|
80
|
+
def inspect(self, provider: Provider) -> _ValidatedProvider:
|
|
81
|
+
if provider.decorators or provider.activators:
|
|
82
|
+
raise RegistrationConflictError(
|
|
83
|
+
key=None,
|
|
84
|
+
sources=(self._source,),
|
|
85
|
+
reason=(
|
|
86
|
+
"service composition supports native provide/provide_all/alias/collect/"
|
|
87
|
+
"from_context; "
|
|
88
|
+
"decorator and activator sources are not catalog-safe"
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
if not (
|
|
92
|
+
provider.factories
|
|
93
|
+
or provider.factory_union_mode
|
|
94
|
+
or provider.aliases
|
|
95
|
+
or provider.context_vars
|
|
96
|
+
):
|
|
97
|
+
raise RegistrationConflictError(
|
|
98
|
+
key=None,
|
|
99
|
+
sources=(self._source,),
|
|
100
|
+
reason="native Provider contribution contains no dependency source",
|
|
101
|
+
)
|
|
102
|
+
# Alias 的执行与缓存仍使用 Dishka 原生模型;这里只归一化用于仲裁和指纹。
|
|
103
|
+
factories = (
|
|
104
|
+
*(
|
|
105
|
+
self._inspect_factory(provider, item.as_factory(provider.component))
|
|
106
|
+
for item in provider.context_vars
|
|
107
|
+
),
|
|
108
|
+
*(self._inspect_factory(provider, item) for item in provider.factories),
|
|
109
|
+
*(
|
|
110
|
+
self._inspect_factory(provider, alias.as_factory(None, provider.component), alias)
|
|
111
|
+
for alias in provider.aliases
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
collections = tuple(
|
|
115
|
+
self._inspect_collection(provider, item) for item in provider.factory_union_mode
|
|
116
|
+
)
|
|
117
|
+
signature = _ProviderSignature(
|
|
118
|
+
factories=tuple(
|
|
119
|
+
_FactorySignature(
|
|
120
|
+
key=item.key,
|
|
121
|
+
scope=item.scope,
|
|
122
|
+
source_identity=self.source_identity(item.factory.source),
|
|
123
|
+
factory_type=item.factory.type,
|
|
124
|
+
dependencies=tuple(item.factory.dependencies),
|
|
125
|
+
keyword_dependencies=tuple(sorted(item.factory.kw_dependencies.items())),
|
|
126
|
+
cache=item.factory.cache,
|
|
127
|
+
allow_static_evaluation=item.factory.allow_static_evaluation,
|
|
128
|
+
is_to_bind=item.factory.is_to_bind,
|
|
129
|
+
native_override=item.native_override,
|
|
130
|
+
)
|
|
131
|
+
for item in factories
|
|
132
|
+
),
|
|
133
|
+
collections=tuple(
|
|
134
|
+
_CollectionSignature(
|
|
135
|
+
source_key=item.source_key,
|
|
136
|
+
key=item.key,
|
|
137
|
+
scope=item.scope,
|
|
138
|
+
cache=item.collection.cache,
|
|
139
|
+
collect=item.collection.collect,
|
|
140
|
+
)
|
|
141
|
+
for item in collections
|
|
142
|
+
),
|
|
143
|
+
)
|
|
144
|
+
return _ValidatedProvider(factories, collections, signature)
|
|
145
|
+
|
|
146
|
+
def _inspect_factory(
|
|
147
|
+
self,
|
|
148
|
+
provider: Provider,
|
|
149
|
+
factory: Factory,
|
|
150
|
+
alias: Alias | None = None,
|
|
151
|
+
) -> _FactoryDefinition:
|
|
152
|
+
key = factory.provides.with_component(provider.component)
|
|
153
|
+
scope = factory.scope
|
|
154
|
+
if scope not in SUPPORTED_SCOPES and not (alias is not None and scope is None):
|
|
155
|
+
self._conflict(key, f"service scope must be {SUPPORTED_SCOPE_LABEL}")
|
|
156
|
+
if factory.allow_static_evaluation:
|
|
157
|
+
self._conflict(
|
|
158
|
+
key,
|
|
159
|
+
"native static evaluation would execute a service factory during "
|
|
160
|
+
"Application.build()",
|
|
161
|
+
)
|
|
162
|
+
if (
|
|
163
|
+
factory.when_active is not None and factory.type is not FactoryType.CONTEXT
|
|
164
|
+
) or factory.when_dependencies:
|
|
165
|
+
self._conflict(
|
|
166
|
+
key, "conditional native factories are outside the service arbitration contract"
|
|
167
|
+
)
|
|
168
|
+
native_override = factory.when_override == BoolMarker(True)
|
|
169
|
+
if factory.when_override is not None and not native_override:
|
|
170
|
+
self._conflict(
|
|
171
|
+
key, "conditional native override is outside the service arbitration contract"
|
|
172
|
+
)
|
|
173
|
+
if (self._kind is RegistrationKind.OVERRIDE) != native_override:
|
|
174
|
+
expected = "must" if self._kind is RegistrationKind.OVERRIDE else "must not"
|
|
175
|
+
self._conflict(
|
|
176
|
+
key, f"{self._kind.value} contribution {expected} use native override=True"
|
|
177
|
+
)
|
|
178
|
+
return _FactoryDefinition(factory, key, scope, native_override, alias)
|
|
179
|
+
|
|
180
|
+
def _inspect_collection(
|
|
181
|
+
self, provider: Provider, collection: FactoryUnionMode
|
|
182
|
+
) -> _CollectionDefinition:
|
|
183
|
+
source_key = collection.source.with_component(provider.component)
|
|
184
|
+
key = collection.provides.with_component(provider.component)
|
|
185
|
+
if self._kind is not RegistrationKind.MULTI:
|
|
186
|
+
self._conflict(
|
|
187
|
+
source_key, "native collect() must be contributed with RegistrationKind.MULTI"
|
|
188
|
+
)
|
|
189
|
+
if not collection.collect:
|
|
190
|
+
self._conflict(source_key, "multi contribution must retain native collect() semantics")
|
|
191
|
+
if collection.scope is not None and collection.scope not in SUPPORTED_SCOPES:
|
|
192
|
+
self._conflict(key, f"collection scope must be {SUPPORTED_SCOPE_LABEL}")
|
|
193
|
+
return _CollectionDefinition(collection, source_key, key, collection.scope)
|
|
194
|
+
|
|
195
|
+
def _conflict(self, key: DependencyKey, reason: str) -> None:
|
|
196
|
+
raise RegistrationConflictError(key=key, sources=(self._source,), reason=reason)
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def source_identity(source: object) -> tuple[int, int | None]:
|
|
200
|
+
if isinstance(source, MethodType):
|
|
201
|
+
# bound method 的稳定 identity 同时由 function 与 receiver 决定;
|
|
202
|
+
# 不同 Provider instance 复用同一 method 定义不是重复 factory。
|
|
203
|
+
return id(source.__func__), id(source.__self__)
|
|
204
|
+
return id(source), None
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Service registration intent、provenance 与 immutable catalog contracts。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from types import MappingProxyType
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from dishka import BaseScope, DependencyKey, Provider, Scope
|
|
12
|
+
|
|
13
|
+
from ..diagnostics import SourceLocation
|
|
14
|
+
from ..modularity import ModuleKey
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from .convention_contracts import ConventionCandidate
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
SUPPORTED_SCOPES = (Scope.APP, Scope.SESSION, Scope.REQUEST, Scope.ACTION)
|
|
21
|
+
SUPPORTED_SCOPE_LABEL = " or ".join(
|
|
22
|
+
f"{type(scope).__name__}.{scope.name}" for scope in SUPPORTED_SCOPES
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RegistrationKind(str, Enum):
|
|
27
|
+
DEFAULT = "default"
|
|
28
|
+
CONVENTION = "convention"
|
|
29
|
+
EXPLICIT = "explicit"
|
|
30
|
+
OVERRIDE = "override"
|
|
31
|
+
MULTI = "multi"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RegistrationDecision(str, Enum):
|
|
35
|
+
ACCEPTED = "accepted"
|
|
36
|
+
BACKED_OFF = "backed_off"
|
|
37
|
+
OVERRIDDEN = "overridden"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, slots=True)
|
|
41
|
+
class ProviderContribution:
|
|
42
|
+
provider: Provider
|
|
43
|
+
owner: ModuleKey
|
|
44
|
+
kind: RegistrationKind
|
|
45
|
+
source: SourceLocation
|
|
46
|
+
reason: str
|
|
47
|
+
declaration_index: int
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True, slots=True)
|
|
51
|
+
class ProviderContributionView:
|
|
52
|
+
owner: ModuleKey
|
|
53
|
+
kind: RegistrationKind
|
|
54
|
+
source: SourceLocation
|
|
55
|
+
reason: str
|
|
56
|
+
declaration_index: int
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True, slots=True)
|
|
60
|
+
class ServiceRegistration:
|
|
61
|
+
key: DependencyKey
|
|
62
|
+
scope: BaseScope
|
|
63
|
+
owner: ModuleKey
|
|
64
|
+
source: SourceLocation
|
|
65
|
+
kind: RegistrationKind
|
|
66
|
+
declaration_index: int
|
|
67
|
+
decision: RegistrationDecision
|
|
68
|
+
decision_reason: str
|
|
69
|
+
replaced_sources: tuple[SourceLocation, ...]
|
|
70
|
+
multi_ordinal: int | None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ServiceCatalog:
|
|
74
|
+
"""Application-local 诊断目录;不保存 factory,也不参与 service resolution。"""
|
|
75
|
+
|
|
76
|
+
__slots__ = ("_by_key", "_convention_candidates", "_registrations")
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
registrations: tuple[ServiceRegistration, ...],
|
|
81
|
+
convention_candidates: tuple[ConventionCandidate, ...] = (),
|
|
82
|
+
) -> None:
|
|
83
|
+
by_key: dict[DependencyKey, list[ServiceRegistration]] = {}
|
|
84
|
+
for registration in registrations:
|
|
85
|
+
by_key.setdefault(registration.key, []).append(registration)
|
|
86
|
+
self._registrations = registrations
|
|
87
|
+
self._convention_candidates = convention_candidates
|
|
88
|
+
self._by_key: Mapping[DependencyKey, tuple[ServiceRegistration, ...]] = MappingProxyType(
|
|
89
|
+
{key: tuple(values) for key, values in by_key.items()}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def registrations(self) -> tuple[ServiceRegistration, ...]:
|
|
94
|
+
return self._registrations
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def convention_candidates(self) -> tuple[ConventionCandidate, ...]:
|
|
98
|
+
return self._convention_candidates
|
|
99
|
+
|
|
100
|
+
def for_key(self, key: DependencyKey) -> tuple[ServiceRegistration, ...]:
|
|
101
|
+
return self._by_key.get(key, ())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""provider-neutral 的业务仓储契约与名义发现边界。"""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from ..errors import InvalidConventionDefinitionError
|
|
6
|
+
from .convention_contracts import _SERVICE_DEFINITION_ATTRIBUTE, _ServiceDefinition
|
|
7
|
+
from .exposure import _business_interfaces
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RepositoryContract(Protocol):
|
|
11
|
+
"""仓储接口的正式角色;实例由选中 Module 的 REQUEST scope 缓存。"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _repository_contract(candidate: type[object]) -> type[object] | None:
|
|
15
|
+
if RepositoryContract not in candidate.__mro__:
|
|
16
|
+
return None
|
|
17
|
+
contracts = tuple(
|
|
18
|
+
base
|
|
19
|
+
for base in _business_interfaces(candidate)
|
|
20
|
+
if base is not RepositoryContract and RepositoryContract in base.__mro__
|
|
21
|
+
)
|
|
22
|
+
if len(contracts) != 1:
|
|
23
|
+
raise InvalidConventionDefinitionError(
|
|
24
|
+
symbol=f"{candidate.__module__}.{candidate.__qualname__}",
|
|
25
|
+
reason="repository implementation requires one unambiguous RepositoryContract",
|
|
26
|
+
)
|
|
27
|
+
contract = contracts[0]
|
|
28
|
+
metadata = getattr(candidate, _SERVICE_DEFINITION_ATTRIBUTE, None)
|
|
29
|
+
if (
|
|
30
|
+
isinstance(metadata, _ServiceDefinition)
|
|
31
|
+
and metadata.provides is not None
|
|
32
|
+
and metadata.provides is not contract
|
|
33
|
+
):
|
|
34
|
+
raise InvalidConventionDefinitionError(
|
|
35
|
+
symbol=f"{candidate.__module__}.{candidate.__qualname__}",
|
|
36
|
+
reason="repository service metadata cannot replace its inherited contract",
|
|
37
|
+
)
|
|
38
|
+
return contract
|