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,27 @@
|
|
|
1
|
+
"""Unit of Work 公开失败契约与状态诊断。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from ..errors.base import FrameworkError
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .contracts import UnitOfWorkState
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UnitOfWorkStateError(FrameworkError):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
*,
|
|
17
|
+
operation: str,
|
|
18
|
+
current: UnitOfWorkState,
|
|
19
|
+
allowed: tuple[UnitOfWorkState, ...],
|
|
20
|
+
) -> None:
|
|
21
|
+
self.operation = operation
|
|
22
|
+
self.current = current
|
|
23
|
+
self.allowed = allowed
|
|
24
|
+
allowed_values = ", ".join(item.value for item in allowed)
|
|
25
|
+
super().__init__(
|
|
26
|
+
f"UnitOfWork.{operation} requires state in ({allowed_values}), got {current.value}"
|
|
27
|
+
)
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Application-owned ambient UoW 与取消监督;不捕获 Session。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections.abc import AsyncIterator
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
8
|
+
from contextvars import ContextVar
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
from uuid import uuid4
|
|
11
|
+
|
|
12
|
+
from dishka import Scope
|
|
13
|
+
|
|
14
|
+
from ..invocation import ApplicationInvocationContext
|
|
15
|
+
from .contracts import UnitOfWork, _UnitOfWorkFactory, _UnitOfWorkParticipant
|
|
16
|
+
from .options import (
|
|
17
|
+
UnitOfWorkDeclaration,
|
|
18
|
+
UnitOfWorkOptions,
|
|
19
|
+
UnitOfWorkTransactionBehavior,
|
|
20
|
+
_ResolvedUnitOfWorkOptions,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from ..application_services.invocation import _InvocationRuntime
|
|
25
|
+
from ..events import LocalEventCatalog
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class UnitOfWorkManager:
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
runtime: _InvocationRuntime,
|
|
32
|
+
factory: _UnitOfWorkFactory,
|
|
33
|
+
options: UnitOfWorkOptions,
|
|
34
|
+
catalog: LocalEventCatalog,
|
|
35
|
+
) -> None:
|
|
36
|
+
self._runtime = runtime
|
|
37
|
+
self._factory = factory
|
|
38
|
+
self._options = options
|
|
39
|
+
self._catalog = catalog
|
|
40
|
+
self._ambient: ContextVar[UnitOfWork | None] = ContextVar(
|
|
41
|
+
f"python_ddd_framework_uow_{runtime.application_id}", default=None
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def current(self) -> UnitOfWork | None:
|
|
46
|
+
work = self._ambient.get()
|
|
47
|
+
if work is not None:
|
|
48
|
+
work._require_active("current", allow_completing=True)
|
|
49
|
+
return work
|
|
50
|
+
|
|
51
|
+
def require_current(self, *, transactional: bool = False) -> UnitOfWork:
|
|
52
|
+
work = self.current
|
|
53
|
+
if work is None:
|
|
54
|
+
raise RuntimeError("operation requires an active UnitOfWork")
|
|
55
|
+
if transactional and not work.is_transactional:
|
|
56
|
+
raise RuntimeError("operation requires a transactional UnitOfWork")
|
|
57
|
+
return work
|
|
58
|
+
|
|
59
|
+
def _resolve_options(
|
|
60
|
+
self, connection_name: str | None, declaration: UnitOfWorkDeclaration, method_name: str
|
|
61
|
+
) -> _ResolvedUnitOfWorkOptions:
|
|
62
|
+
from ..application_services.contracts import (
|
|
63
|
+
_DEFAULT_CONNECTION_NAME,
|
|
64
|
+
_validate_connection_name,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
ambient = self.current
|
|
68
|
+
execution = self._runtime.current()
|
|
69
|
+
connection = (
|
|
70
|
+
connection_name
|
|
71
|
+
if connection_name is not None
|
|
72
|
+
else (
|
|
73
|
+
ambient.connection_name
|
|
74
|
+
if ambient is not None
|
|
75
|
+
else (
|
|
76
|
+
execution.context.connection_name
|
|
77
|
+
if execution is not None
|
|
78
|
+
else _DEFAULT_CONNECTION_NAME
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
transactional = declaration.is_transactional
|
|
83
|
+
if transactional is None:
|
|
84
|
+
behavior = self._options.transaction_behavior
|
|
85
|
+
if behavior is UnitOfWorkTransactionBehavior.AUTO:
|
|
86
|
+
verb = self._runtime.http_method()
|
|
87
|
+
transactional = (
|
|
88
|
+
verb != "GET"
|
|
89
|
+
if verb is not None
|
|
90
|
+
else not (method_name == "get" or method_name.startswith("get_"))
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
transactional = behavior is UnitOfWorkTransactionBehavior.ENABLED
|
|
94
|
+
return _ResolvedUnitOfWorkOptions(
|
|
95
|
+
_validate_connection_name(connection),
|
|
96
|
+
transactional,
|
|
97
|
+
declaration.isolation_level or self._options.isolation_level,
|
|
98
|
+
declaration.command_timeout_ms
|
|
99
|
+
if declaration.command_timeout_ms is not None
|
|
100
|
+
else self._options.command_timeout_ms,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@asynccontextmanager
|
|
104
|
+
async def begin(
|
|
105
|
+
self,
|
|
106
|
+
*,
|
|
107
|
+
requires_new: bool = False,
|
|
108
|
+
connection_name: str | None = None,
|
|
109
|
+
is_transactional: bool | None = None,
|
|
110
|
+
isolation_level: str | None = None,
|
|
111
|
+
command_timeout_ms: int | None = None,
|
|
112
|
+
) -> AsyncIterator[UnitOfWork]:
|
|
113
|
+
declaration = UnitOfWorkDeclaration(
|
|
114
|
+
is_transactional=is_transactional,
|
|
115
|
+
isolation_level=isolation_level,
|
|
116
|
+
command_timeout_ms=command_timeout_ms,
|
|
117
|
+
)
|
|
118
|
+
options = self._resolve_options(connection_name, declaration, "begin")
|
|
119
|
+
async with self._begin(options, requires_new=requires_new) as work:
|
|
120
|
+
yield work
|
|
121
|
+
|
|
122
|
+
def _check_current(self, connection_name: str) -> UnitOfWork | None:
|
|
123
|
+
from ..application_services.errors import NestedConnectionError
|
|
124
|
+
|
|
125
|
+
outer = self.current
|
|
126
|
+
# disabled 只控制自动创建;参与外层仍须满足同 task、有效状态与同连接。
|
|
127
|
+
if outer is not None and outer.connection_name != connection_name:
|
|
128
|
+
raise NestedConnectionError(outer=outer.connection_name, requested=connection_name)
|
|
129
|
+
return outer
|
|
130
|
+
|
|
131
|
+
@asynccontextmanager
|
|
132
|
+
async def _begin(
|
|
133
|
+
self, options: _ResolvedUnitOfWorkOptions, *, requires_new: bool = False
|
|
134
|
+
) -> AsyncIterator[UnitOfWork]:
|
|
135
|
+
outer = self.current if requires_new else self._check_current(options.connection_name)
|
|
136
|
+
if outer is not None and not requires_new:
|
|
137
|
+
participant = _UnitOfWorkParticipant(outer)
|
|
138
|
+
try:
|
|
139
|
+
yield participant
|
|
140
|
+
finally:
|
|
141
|
+
participant._released = True
|
|
142
|
+
return
|
|
143
|
+
async with self._runtime.outer_lease():
|
|
144
|
+
async with self._own_work(options) as work:
|
|
145
|
+
yield work
|
|
146
|
+
|
|
147
|
+
@asynccontextmanager
|
|
148
|
+
async def _own_work(self, options: _ResolvedUnitOfWorkOptions) -> AsyncIterator[UnitOfWork]:
|
|
149
|
+
from ..application_services.errors import ApplicationInvocationCleanupError
|
|
150
|
+
from ..events.runtime import _LocalEventQueue
|
|
151
|
+
|
|
152
|
+
execution = self._runtime.current()
|
|
153
|
+
container = (
|
|
154
|
+
execution.action_container
|
|
155
|
+
if execution is not None
|
|
156
|
+
else self._runtime.current_call_scope()
|
|
157
|
+
)
|
|
158
|
+
if container is None:
|
|
159
|
+
raise RuntimeError("UnitOfWork.begin requires a managed execution scope")
|
|
160
|
+
context = ApplicationInvocationContext(
|
|
161
|
+
application_id=self._runtime.application_id,
|
|
162
|
+
invocation_id=uuid4(),
|
|
163
|
+
parent_invocation_id=execution.context.invocation_id if execution else None,
|
|
164
|
+
connection_name=options.connection_name,
|
|
165
|
+
)
|
|
166
|
+
work = self._factory.create()
|
|
167
|
+
work._options = options
|
|
168
|
+
work._event_queue = _LocalEventQueue(container, context, self._catalog)
|
|
169
|
+
|
|
170
|
+
async def after_commit() -> None:
|
|
171
|
+
# 新服务调用既不能复用旧事务,也不能落入旧 ACTION 的对象缓存。
|
|
172
|
+
token = self._ambient.set(None)
|
|
173
|
+
parent = container.parent_container if container.scope is Scope.ACTION else container
|
|
174
|
+
assert parent is not None
|
|
175
|
+
try:
|
|
176
|
+
with self._runtime.detached_execution(parent):
|
|
177
|
+
assert work._event_queue is not None
|
|
178
|
+
await work._event_queue._dispatch_after_commit()
|
|
179
|
+
finally:
|
|
180
|
+
self._ambient.reset(token)
|
|
181
|
+
|
|
182
|
+
work._after_commit = after_commit
|
|
183
|
+
token = self._ambient.set(work)
|
|
184
|
+
primary: BaseException | None = None
|
|
185
|
+
try:
|
|
186
|
+
yield work
|
|
187
|
+
except BaseException as error:
|
|
188
|
+
primary = error
|
|
189
|
+
finally:
|
|
190
|
+
# 仅原生资源 I/O 移入受监督 task;ContextVar token 始终由原 task 恢复。
|
|
191
|
+
cleanup = asyncio.create_task(work._cleanup())
|
|
192
|
+
try:
|
|
193
|
+
while not cleanup.done():
|
|
194
|
+
try:
|
|
195
|
+
await asyncio.shield(cleanup)
|
|
196
|
+
except asyncio.CancelledError as cancellation:
|
|
197
|
+
if primary is None:
|
|
198
|
+
primary = cancellation
|
|
199
|
+
cleanup.result()
|
|
200
|
+
finally:
|
|
201
|
+
self._ambient.reset(token)
|
|
202
|
+
if work.cleanup_failures:
|
|
203
|
+
failure = ApplicationInvocationCleanupError(
|
|
204
|
+
primary=primary, cleanup_failures=work.cleanup_failures, committed=work.committed
|
|
205
|
+
)
|
|
206
|
+
if isinstance(primary, asyncio.CancelledError):
|
|
207
|
+
raise primary from failure
|
|
208
|
+
raise failure from (primary or work.cleanup_failures[0])
|
|
209
|
+
if primary is not None:
|
|
210
|
+
raise primary
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""UoW 声明整项覆盖;默认值只来自不可变全局 Options。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import TypeVar
|
|
9
|
+
|
|
10
|
+
from pydantic import ConfigDict, Field, field_validator
|
|
11
|
+
|
|
12
|
+
from ..options import BaseOptions
|
|
13
|
+
|
|
14
|
+
_UNIT_OF_WORK_ATTRIBUTE = "__python_ddd_framework_unit_of_work__"
|
|
15
|
+
_T = TypeVar("_T")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class UnitOfWorkTransactionBehavior(str, Enum):
|
|
19
|
+
AUTO = "auto"
|
|
20
|
+
ENABLED = "enabled"
|
|
21
|
+
DISABLED = "disabled"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class UnitOfWorkOptions(BaseOptions):
|
|
25
|
+
model_config = ConfigDict(extra="forbid")
|
|
26
|
+
|
|
27
|
+
transaction_behavior: UnitOfWorkTransactionBehavior = UnitOfWorkTransactionBehavior.AUTO
|
|
28
|
+
isolation_level: str | None = None
|
|
29
|
+
command_timeout_ms: int | None = Field(default=None, strict=True, ge=0)
|
|
30
|
+
|
|
31
|
+
@field_validator("isolation_level")
|
|
32
|
+
@classmethod
|
|
33
|
+
def _validate_isolation_level(cls, value: str | None) -> str | None:
|
|
34
|
+
if value is not None and (
|
|
35
|
+
not value or value != value.strip() or value.upper() == "AUTOCOMMIT"
|
|
36
|
+
):
|
|
37
|
+
# 事务隔离沿用原生 provider;DBAPI AUTOCOMMIT 会破坏本框架的批次/回滚语义。
|
|
38
|
+
raise ValueError("isolation_level must name a database transaction isolation level")
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True, slots=True)
|
|
43
|
+
class UnitOfWorkDeclaration:
|
|
44
|
+
disabled: bool = False
|
|
45
|
+
is_transactional: bool | None = None
|
|
46
|
+
isolation_level: str | None = None
|
|
47
|
+
command_timeout_ms: int | None = None
|
|
48
|
+
|
|
49
|
+
def __post_init__(self) -> None:
|
|
50
|
+
if not isinstance(self.disabled, bool):
|
|
51
|
+
raise TypeError("disabled must be bool")
|
|
52
|
+
if self.is_transactional is not None and not isinstance(self.is_transactional, bool):
|
|
53
|
+
raise TypeError("is_transactional must be bool or None")
|
|
54
|
+
UnitOfWorkOptions(
|
|
55
|
+
isolation_level=self.isolation_level, command_timeout_ms=self.command_timeout_ms
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True, slots=True)
|
|
60
|
+
class _ResolvedUnitOfWorkOptions:
|
|
61
|
+
connection_name: str
|
|
62
|
+
is_transactional: bool
|
|
63
|
+
isolation_level: str | None
|
|
64
|
+
command_timeout_ms: int | None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def unit_of_work(
|
|
68
|
+
*,
|
|
69
|
+
disabled: bool = False,
|
|
70
|
+
is_transactional: bool | None = None,
|
|
71
|
+
isolation_level: str | None = None,
|
|
72
|
+
command_timeout_ms: int | None = None,
|
|
73
|
+
) -> Callable[[_T], _T]:
|
|
74
|
+
declaration = UnitOfWorkDeclaration(
|
|
75
|
+
disabled, is_transactional, isolation_level, command_timeout_ms
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def decorate(target: _T) -> _T:
|
|
79
|
+
setattr(target, _UNIT_OF_WORK_ATTRIBUTE, declaration)
|
|
80
|
+
return target
|
|
81
|
+
|
|
82
|
+
return decorate
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _declaration(implementation: type[object], method: object) -> UnitOfWorkDeclaration:
|
|
86
|
+
# 方法替换整项声明,未填写的字段只补全全局默认,不能泄漏类配置。
|
|
87
|
+
result = getattr(method, _UNIT_OF_WORK_ATTRIBUTE, None)
|
|
88
|
+
if result is None:
|
|
89
|
+
result = getattr(implementation, _UNIT_OF_WORK_ATTRIBUTE, UnitOfWorkDeclaration())
|
|
90
|
+
if not isinstance(result, UnitOfWorkDeclaration):
|
|
91
|
+
raise TypeError("invalid unit_of_work metadata")
|
|
92
|
+
return result
|