python-ddd-framework 0.3.2__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_ddd_framework/__init__.py +139 -21
- python_ddd_framework/application/builder.py +23 -78
- python_ddd_framework/application/composition.py +4 -22
- python_ddd_framework/application/runtime.py +96 -291
- python_ddd_framework/application/state.py +7 -75
- python_ddd_framework/application_services/__init__.py +5 -21
- python_ddd_framework/{services/application_bindings.py → application_services/bindings.py} +52 -9
- python_ddd_framework/application_services/catalog.py +36 -288
- python_ddd_framework/application_services/contracts.py +14 -13
- python_ddd_framework/application_services/dispatcher.py +19 -269
- python_ddd_framework/application_services/invocation.py +126 -7
- python_ddd_framework/application_services/module.py +168 -0
- python_ddd_framework/application_services/pagination.py +21 -0
- python_ddd_framework/auditing/control.py +1 -1
- python_ddd_framework/auditing/module.py +15 -0
- python_ddd_framework/authorization/__init__.py +2 -0
- python_ddd_framework/authorization/contracts.py +7 -2
- python_ddd_framework/authorization/definitions.py +5 -0
- python_ddd_framework/authorization/module.py +40 -0
- python_ddd_framework/authorization/options.py +7 -0
- python_ddd_framework/background_execution/child.py +13 -3
- python_ddd_framework/background_execution/declarations.py +45 -0
- python_ddd_framework/background_execution/lifecycle.py +12 -0
- python_ddd_framework/background_execution/module.py +107 -1
- python_ddd_framework/background_jobs/catalog.py +59 -42
- python_ddd_framework/background_jobs/contracts.py +16 -19
- python_ddd_framework/background_jobs/pgqueuer/enqueue.py +4 -0
- python_ddd_framework/background_jobs/pgqueuer/module.py +2 -1
- python_ddd_framework/background_jobs/pgqueuer/runtime.py +7 -7
- python_ddd_framework/background_workers/catalog.py +23 -36
- python_ddd_framework/background_workers/contracts.py +3 -0
- python_ddd_framework/background_workers/execution.py +1 -1
- python_ddd_framework/background_workers/runtime.py +38 -1
- python_ddd_framework/caching/__init__.py +7 -6
- python_ddd_framework/caching/contracts.py +65 -67
- python_ddd_framework/caching/errors.py +0 -7
- python_ddd_framework/caching/module.py +18 -0
- python_ddd_framework/caching/naming.py +29 -0
- python_ddd_framework/caching/options.py +38 -0
- python_ddd_framework/caching/unit_of_work.py +46 -0
- python_ddd_framework/cli/__init__.py +31 -6
- python_ddd_framework/cli/project.py +68 -6
- python_ddd_framework/cli/runtime.py +1 -1
- python_ddd_framework/developer_kit/generation.py +75 -22
- python_ddd_framework/developer_kit/project_metadata.py +2 -4
- python_ddd_framework/developer_kit/templates/basic/cookiecutter.json +4 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/README.md +45 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/contracts/conversion_service.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/module.py.jinja +38 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/__init__.py.jinja +0 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/default_conversion_service.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +41 -49
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +45 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +8 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +35 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +5 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +15 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +28 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +26 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +23 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{integration.py.jinja → hosted_services/order_integration_service.py.jinja} +8 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +16 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +16 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +105 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/{options.py.jinja → options/order_options.py.jinja} +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +65 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +35 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +41 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +6 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +39 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +12 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +9 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +12 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{orders.py.jinja → entities/order.py.jinja} +10 -18
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +42 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +17 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +22 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +23 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/{settings.py.jinja → settings/approval_settings.py.jinja} +3 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +10 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +3 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +6 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +11 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +26 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +13 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +40 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/{permissions.py.jinja → permissions/order_permission_provider.py.jinja} +6 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +18 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +15 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +5 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +85 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/{files.py.jinja → routers/order_files.py.jinja} +23 -20
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +44 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/__init__.py.jinja +1 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/base.py.jinja +7 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/{orders.py.jinja → order_model.py.jinja} +7 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +42 -5
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +79 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/__init__.py.jinja +1 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +4 -3
- python_ddd_framework/developer_kit/templates/project/cookiecutter.json +1 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +16 -3
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +29 -22
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{app.development.yaml.jinja → backend/app.development.yaml.jinja} +2 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{pyproject.toml.jinja → backend/pyproject.toml.jinja} +1 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/main.py.jinja +2 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/module.py.jinja +40 -3
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +40 -10
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +134 -23
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/scripts/.gitkeep +0 -0
- python_ddd_framework/distributed_lock/contracts.py +4 -1
- python_ddd_framework/domain/__init__.py +2 -0
- python_ddd_framework/domain/services.py +11 -0
- python_ddd_framework/errors/business.py +10 -1
- python_ddd_framework/errors/lifecycle.py +3 -31
- python_ddd_framework/events/__init__.py +5 -2
- python_ddd_framework/events/aggregate.py +3 -3
- python_ddd_framework/events/catalog.py +12 -11
- python_ddd_framework/events/contracts.py +6 -1
- python_ddd_framework/events/contribution.py +1 -52
- python_ddd_framework/events/entities.py +23 -0
- python_ddd_framework/events/module.py +97 -0
- python_ddd_framework/events/runtime.py +80 -29
- python_ddd_framework/events/subscription.py +36 -0
- python_ddd_framework/events/unit_of_work.py +25 -0
- python_ddd_framework/extensions/__init__.py +22 -0
- python_ddd_framework/extensions/catalog.py +198 -0
- python_ddd_framework/extensions/contracts.py +85 -0
- python_ddd_framework/extensions/models.py +142 -0
- python_ddd_framework/extensions/module.py +21 -0
- python_ddd_framework/fastapi/__init__.py +2 -0
- python_ddd_framework/fastapi/action.py +1 -2
- python_ddd_framework/fastapi/adapter.py +16 -0
- python_ddd_framework/fastapi/application_services.py +43 -25
- python_ddd_framework/fastapi/background.py +2 -1
- python_ddd_framework/fastapi/contracts.py +26 -8
- python_ddd_framework/fastapi/health.py +1 -1
- python_ddd_framework/fastapi/module.py +14 -0
- python_ddd_framework/fastapi/parameters.py +3 -18
- python_ddd_framework/fastapi/realtime/connection.py +10 -7
- python_ddd_framework/fastapi/realtime/module.py +3 -0
- python_ddd_framework/fastapi/realtime/runtime.py +10 -24
- python_ddd_framework/fastapi/request_context.py +2 -1
- python_ddd_framework/fastapi/routing.py +2 -1
- python_ddd_framework/fastapi/server.py +8 -0
- python_ddd_framework/fastapi/settings.py +2 -1
- python_ddd_framework/hosted_services/bridge.py +20 -1
- python_ddd_framework/hosted_services/catalog.py +6 -16
- python_ddd_framework/hosted_services/contracts.py +4 -0
- python_ddd_framework/hosted_services/declarations.py +15 -0
- python_ddd_framework/hosted_services/module.py +69 -0
- python_ddd_framework/hosted_services/runtime.py +30 -1
- python_ddd_framework/identity/__init__.py +24 -0
- python_ddd_framework/identity/application.py +38 -90
- python_ddd_framework/identity/contracts.py +181 -16
- python_ddd_framework/identity/http_api.py +79 -2
- python_ddd_framework/identity/management.py +166 -0
- python_ddd_framework/identity/module.py +12 -4
- python_ddd_framework/identity/services.py +29 -16
- python_ddd_framework/identity/sqlalchemy/migrations/0003_extra_properties.py +26 -0
- python_ddd_framework/identity/sqlalchemy/migrations/0004_concurrency_version.py +29 -0
- python_ddd_framework/identity/sqlalchemy/models.py +21 -2
- python_ddd_framework/identity/sqlalchemy/module.py +11 -2
- python_ddd_framework/identity/sqlalchemy/repository.py +458 -0
- python_ddd_framework/identity/sqlalchemy/session_security.py +88 -0
- python_ddd_framework/identity/sqlalchemy/stores.py +69 -290
- python_ddd_framework/identity/tokens.py +2 -5
- python_ddd_framework/{application_services/validation.py → invocation/arguments.py} +8 -6
- python_ddd_framework/invocation/contribution.py +61 -0
- python_ddd_framework/invocation/dispatcher.py +291 -0
- python_ddd_framework/invocation/entries.py +2 -25
- python_ddd_framework/invocation/entrypoints.py +126 -0
- python_ddd_framework/{application_services → invocation}/execution.py +8 -5
- python_ddd_framework/invocation/function_runtime.py +3 -1
- python_ddd_framework/invocation/interception.py +113 -42
- python_ddd_framework/{application_services → invocation}/interceptors.py +2 -2
- python_ddd_framework/invocation/managed_proxy.py +116 -0
- python_ddd_framework/invocation/managed_services.py +250 -0
- python_ddd_framework/invocation/methods.py +345 -0
- python_ddd_framework/invocation/module.py +113 -0
- python_ddd_framework/{application_services → invocation}/policies.py +8 -1
- python_ddd_framework/invocation/validation.py +25 -0
- python_ddd_framework/lifecycle/__init__.py +2 -0
- python_ddd_framework/lifecycle/composition.py +5 -2
- python_ddd_framework/lifecycle/participants.py +33 -0
- python_ddd_framework/messaging/__init__.py +25 -0
- python_ddd_framework/messaging/channel.py +205 -0
- python_ddd_framework/messaging/contracts.py +69 -0
- python_ddd_framework/messaging/module.py +101 -0
- python_ddd_framework/messaging/options.py +13 -0
- python_ddd_framework/messaging/receipt.py +27 -0
- python_ddd_framework/messaging/runtime.py +124 -0
- python_ddd_framework/modularity/__init__.py +11 -1
- python_ddd_framework/modularity/contracts.py +47 -16
- python_ddd_framework/modularity/discovery.py +21 -2
- python_ddd_framework/modularity/graph.py +45 -1
- python_ddd_framework/modularity/registry.py +83 -3
- python_ddd_framework/notifications/__init__.py +2 -0
- python_ddd_framework/notifications/catalog.py +3 -2
- python_ddd_framework/notifications/declarations.py +18 -0
- python_ddd_framework/notifications/module.py +21 -0
- python_ddd_framework/observability/logging.py +3 -0
- python_ddd_framework/observability/tracing.py +3 -0
- python_ddd_framework/options/contribution.py +12 -0
- python_ddd_framework/options/registry.py +44 -11
- python_ddd_framework/realtime/__init__.py +2 -1
- python_ddd_framework/realtime/contracts.py +3 -7
- python_ddd_framework/realtime/messages.py +26 -1
- python_ddd_framework/redis/cache.py +243 -0
- python_ddd_framework/redis/cache_scripts.py +40 -0
- python_ddd_framework/redis/distributed_lock.py +10 -2
- python_ddd_framework/redis/module.py +13 -4
- python_ddd_framework/redis/notifications.py +5 -2
- python_ddd_framework/redis/runtime.py +1 -79
- python_ddd_framework/seeding/__init__.py +13 -0
- python_ddd_framework/seeding/contracts.py +45 -0
- python_ddd_framework/seeding/module.py +73 -0
- python_ddd_framework/seeding/runtime.py +63 -0
- python_ddd_framework/services/arbitration.py +23 -0
- python_ddd_framework/services/binding.py +2 -3
- python_ddd_framework/services/composition.py +108 -0
- python_ddd_framework/services/contribution.py +44 -54
- python_ddd_framework/services/convention.py +53 -181
- python_ddd_framework/services/fixed_lifetime.py +12 -15
- python_ddd_framework/services/framework_provider.py +3 -158
- python_ddd_framework/services/native_graph.py +35 -60
- python_ddd_framework/services/provider.py +59 -4
- python_ddd_framework/services/repository.py +6 -0
- python_ddd_framework/services/runtime.py +129 -215
- python_ddd_framework/settings/catalog.py +5 -4
- python_ddd_framework/settings/declarations.py +18 -0
- python_ddd_framework/settings/definitions.py +5 -0
- python_ddd_framework/settings/manager.py +1 -1
- python_ddd_framework/settings/module.py +50 -1
- python_ddd_framework/settings/notifications.py +4 -2
- python_ddd_framework/settings/refresh_module.py +9 -2
- python_ddd_framework/settings/sqlalchemy/module.py +2 -1
- python_ddd_framework/sqlalchemy/__init__.py +12 -0
- python_ddd_framework/sqlalchemy/alembic_runtime/env.py +1 -0
- python_ddd_framework/sqlalchemy/auditing.py +7 -1
- python_ddd_framework/sqlalchemy/extensions.py +186 -0
- python_ddd_framework/sqlalchemy/metadata.py +110 -6
- python_ddd_framework/sqlalchemy/migration.py +61 -8
- python_ddd_framework/sqlalchemy/migration_operations.py +86 -0
- python_ddd_framework/sqlalchemy/module.py +14 -11
- python_ddd_framework/sqlalchemy/module_migration.py +71 -3
- python_ddd_framework/sqlalchemy/repository.py +217 -13
- python_ddd_framework/sqlalchemy/session_provider.py +14 -1
- python_ddd_framework/testing/runtime.py +5 -4
- python_ddd_framework/unit_of_work/__init__.py +2 -1
- python_ddd_framework/unit_of_work/contracts.py +6 -7
- python_ddd_framework/unit_of_work/errors.py +11 -0
- python_ddd_framework/unit_of_work/lifecycle.py +19 -0
- python_ddd_framework/unit_of_work/manager.py +86 -32
- python_ddd_framework/unit_of_work/module.py +61 -0
- python_ddd_framework/unit_of_work/options.py +7 -1
- python_ddd_framework/validation/__init__.py +1 -0
- python_ddd_framework/validation/models.py +37 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/METADATA +139 -44
- python_ddd_framework-0.4.0.dist-info/RECORD +488 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/WHEEL +1 -1
- python_ddd_framework/application_services/seeding.py +0 -72
- python_ddd_framework/caching/catalog.py +0 -73
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +0 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +0 -24
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +0 -83
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +0 -84
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +0 -35
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +0 -14
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +0 -19
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +0 -24
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +0 -36
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/orders.py.jinja +0 -49
- python_ddd_framework-0.3.2.dist-info/RECORD +0 -355
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.dockerignore → backend/.dockerignore} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.gitignore → backend/.gitignore} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{.python-version → backend/.python-version} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{Dockerfile → backend/Dockerfile} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.dev.yaml.jinja → backend/compose.dev.yaml.jinja} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{compose.production.yaml.jinja → backend/compose.production.yaml.jinja} +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{src → backend/src}/host/__init__.py.jinja +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/conftest.py.jinja +0 -0
- /python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/{tests → backend/tests}/host/test_http.py.jinja +0 -0
- /python_ddd_framework/{application_services → invocation}/signature.py +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {python_ddd_framework-0.3.2.dist-info → python_ddd_framework-0.4.0.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""仓储参与当前 UoW,成功暂存后由 operation 收集聚合事件。"""
|
|
2
|
+
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from modules.{{ cookiecutter.module_name }}.domain.entities.order import Order
|
|
6
|
+
from modules.{{ cookiecutter.module_name }}.domain.repositories.order_repository import OrderRepository
|
|
7
|
+
from modules.{{ cookiecutter.module_name }}.domain.value_objects.order_title import OrderTitle
|
|
8
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.enums.order_status import OrderStatus
|
|
9
|
+
from modules.{{ cookiecutter.module_name }}.sqlalchemy.models.order_model import OrderRow
|
|
10
|
+
|
|
11
|
+
from python_ddd_framework import (
|
|
12
|
+
OptimisticConcurrencyError, ResourceNotFoundError, EntityCreatedEvent, EntityUpdatedEvent,
|
|
13
|
+
)
|
|
14
|
+
from python_ddd_framework.sqlalchemy import SqlAlchemyRepository, SqlAlchemySessionProvider
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _OrderRows(SqlAlchemyRepository[OrderRow, UUID]):
|
|
18
|
+
def _default_sorting(self) -> str:
|
|
19
|
+
return "created_at"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SqlAlchemyOrderRepository(OrderRepository):
|
|
23
|
+
def __init__(self, provider: SqlAlchemySessionProvider) -> None:
|
|
24
|
+
self._provider = provider
|
|
25
|
+
self._rows = _OrderRows(provider, OrderRow)
|
|
26
|
+
|
|
27
|
+
async def find(self, id: UUID, include_details: bool = True) -> Order | None:
|
|
28
|
+
# 持久化扩展:这里查询本模块的表,再通过显式映射重建聚合;业务判断留给聚合/应用服务。
|
|
29
|
+
async with self._provider.operation("get"):
|
|
30
|
+
row = await self._rows.find(id, include_details)
|
|
31
|
+
return None if row is None else _order(row)
|
|
32
|
+
|
|
33
|
+
async def get(self, id: UUID, include_details: bool = True) -> Order:
|
|
34
|
+
order = await self.find(id, include_details)
|
|
35
|
+
if order is None:
|
|
36
|
+
raise ResourceNotFoundError("Order not found")
|
|
37
|
+
return order
|
|
38
|
+
|
|
39
|
+
async def get_list(self, include_details: bool = False) -> list[Order]:
|
|
40
|
+
async with self._provider.operation("get_list"):
|
|
41
|
+
return [_order(row) for row in await self._rows.get_list(include_details)]
|
|
42
|
+
|
|
43
|
+
async def get_count(self) -> int:
|
|
44
|
+
return await self._rows.get_count()
|
|
45
|
+
|
|
46
|
+
async def get_paged_list(self, skip_count: int, max_result_count: int,
|
|
47
|
+
sorting: str | None = None, include_details: bool = False) -> list[Order]:
|
|
48
|
+
async with self._provider.operation("get_paged_list"):
|
|
49
|
+
rows = await self._rows.get_paged_list(
|
|
50
|
+
skip_count, max_result_count, sorting, include_details
|
|
51
|
+
)
|
|
52
|
+
return [_order(row) for row in rows]
|
|
53
|
+
|
|
54
|
+
async def save(self, order: Order) -> None:
|
|
55
|
+
# 持久化扩展:把自己的聚合字段/子实体映射到 ORM;下面的版本核对和事件收集边界要保留。
|
|
56
|
+
# 这里只暂存改动,不自行 commit,避免绕开调用者的工作单元。
|
|
57
|
+
async with self._provider.operation(
|
|
58
|
+
"save", aggregate=order,
|
|
59
|
+
entity_event=EntityCreatedEvent(order) if order.version == 0 else EntityUpdatedEvent(order),
|
|
60
|
+
):
|
|
61
|
+
session = await self._provider.get_session()
|
|
62
|
+
row = await session.get(OrderRow, order.id)
|
|
63
|
+
if row is None:
|
|
64
|
+
if order.version:
|
|
65
|
+
raise OptimisticConcurrencyError
|
|
66
|
+
row = OrderRow(id=order.id)
|
|
67
|
+
session.add(row)
|
|
68
|
+
elif row.version != order.version:
|
|
69
|
+
raise OptimisticConcurrencyError
|
|
70
|
+
order.stage_next_version(order.version)
|
|
71
|
+
# 新增持久字段的赋值写在这里,同时修改模型、下方读取映射并生成迁移。
|
|
72
|
+
row.title, row.status, row.version = order.title, order.status.value, order.version
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _order(row: OrderRow) -> Order:
|
|
76
|
+
# 映射扩展:在这里把数据库字段恢复成领域实体/值对象,不调用会再次产生创建事件的工厂。
|
|
77
|
+
return Order(
|
|
78
|
+
row.id, OrderTitle(value=row.title), status=OrderStatus(row.status), version=row.version
|
|
79
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""所属能力的实现或契约。"""
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
from uuid import uuid4
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
|
-
from
|
|
6
|
+
from modules.{{ cookiecutter.module_name }}.domain.entities.order import Order
|
|
7
|
+
from modules.{{ cookiecutter.module_name }}.domain.value_objects.order_title import OrderTitle
|
|
8
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.enums.order_status import OrderStatus
|
|
7
9
|
|
|
8
|
-
from
|
|
9
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.definitions import OrderStatus
|
|
10
|
+
from python_ddd_framework.errors import BusinessError
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def test_order_approval_rejects_repeat():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"project_name": "application", "package_name": "application", "class_prefix": "Application",
|
|
3
|
-
"framework_version": "", "framework_source": "", "python_version": "", "build_version": "",
|
|
3
|
+
"framework_version": "", "framework_source": "", "python_version": "", "requires_python": "", "build_version": "",
|
|
4
4
|
"database_password": "", "admin_password": "", "jwt_key": "",
|
|
5
5
|
"host_environment_variable": ""
|
|
6
6
|
}
|
python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md
CHANGED
|
@@ -6,8 +6,10 @@ These instructions apply to this application and its business modules. Follow th
|
|
|
6
6
|
|
|
7
7
|
1. Check the branch, worktree, requested outcome, and existing changes before editing. Analysis and review requests are read-only unless implementation is requested.
|
|
8
8
|
2. Use [README](README.md) for setup and commands, [architecture](docs/architecture.md) for ownership and dependencies, and the relevant section of [development](docs/development.md) for framework usage.
|
|
9
|
-
3. Before changing a business module, read its `src/modules/<name>/README.md`, then inspect the affected source and direct callers. Expand the investigation only when those facts reveal another affected boundary.
|
|
10
|
-
4. Read versions and dependencies from `pyproject.toml`, exact resolutions from `uv.lock`, and commands from `
|
|
9
|
+
3. First identify the layer and responsibility directory in the [module layout rules](docs/development.md#module-layout-and-coding-rules). Before changing a business module, read its `backend/src/modules/<name>/README.md`, then inspect the affected source and direct callers. Expand the investigation only when those facts reveal another affected boundary.
|
|
10
|
+
4. Read versions and dependencies from `backend/pyproject.toml`, exact resolutions from `backend/uv.lock`, and commands from `pddd --help`. Verify behavior against the installed framework version when the application documentation is insufficient.
|
|
11
|
+
|
|
12
|
+
Python code, metadata, configuration, environment, and deployment files belong under `backend/`. Root `scripts/` contains consumer-owned scripts. Run installed `pddd` commands from any directory in the application; run native uv/build/test and Docker commands from `backend/`.
|
|
11
13
|
|
|
12
14
|
## Preserve ownership and contracts
|
|
13
15
|
|
|
@@ -18,15 +20,26 @@ These instructions apply to this application and its business modules. Follow th
|
|
|
18
20
|
- Resolve conflicting requirements, documented contracts, and implementation before changing the affected boundary. Describe the conflict to the user; do not rewrite documentation to justify an unapproved design.
|
|
19
21
|
- Confirm changes to dependencies, public contracts, persistent data, external protocols, or deployment infrastructure when they extend beyond the requested task. Internal simplification does not authorize breaking consumers or stored data.
|
|
20
22
|
|
|
23
|
+
Use the documented responsibility directories for every new capability. Propose a standards change before adding a responsibility category or changing dependency direction. The `ddd` template intentionally generates all supported example capabilities, even before use; this exception applies to DDD templates and generated projects, not speculative framework runtime layers. The `basic` template generates only package markers, `module.py`, `contracts/conversion_service.py`, `services/default_conversion_service.py`, and its README. It uses ordinary transient DI, without Domain, persistence, HTTP, interception, or empty layers. Keep `module.py` limited to dependencies, registration, and lifecycle hooks, and keep package `__init__.py` free of business code and compatibility exports.
|
|
24
|
+
|
|
21
25
|
## Use the framework safely
|
|
22
26
|
|
|
23
|
-
-
|
|
27
|
+
- Extend only explicitly opened targets. Keep typed properties in application contracts, optional SQL mappings and local extension revisions in persistence, and reuse the same declaration for validation, OpenAPI, and storage. Never mutate shared DTO/ORM classes or duplicate the base table/Schema owner; extension migrations declare Alembic prerequisites explicitly.
|
|
28
|
+
|
|
29
|
+
- Invoke application services through their injected public contracts or the framework functions `invoke(application, ...)` / `call(application, ...)`. Manual construction and self-calls do not create a new managed invocation.
|
|
30
|
+
- Ordinary DI services opt into interception with `@unit_of_work`, `@authorize`, or `ValidationEnabled`; declare the corresponding capability dependency on their owning Module. Keep marked methods async instance methods and preserve native scope/cache. These declarations do not add HTTP exposure or require ApplicationService inheritance.
|
|
24
31
|
- Keep transactions short. Never carry a Session, unit of work, or request scope into an ordinary child task, external thread, network wait, or streamed response.
|
|
25
32
|
- Keep aggregate rules and optimistic concurrency checks intact. Use repository operations to stage changes and collect events; do not publish the same aggregate events twice.
|
|
26
33
|
- Review generated migrations. Do not edit applied revisions, bypass migration guards, or treat Host startup as migration or seeding.
|
|
27
34
|
- Keep background side effects idempotent. Local events and WebSocket messages do not provide durable distributed delivery; distributed locks do not replace database concurrency or idempotency.
|
|
28
35
|
- Validate external inputs, propagate meaningful failures, and preserve cancellation and cleanup. Add defaults, retries, or fallbacks only for an identified requirement.
|
|
29
36
|
- Use typed public APIs and native framework extension points. Keep dynamic data narrowing at external boundaries; do not use `Any`, unchecked casts, or broad exception handlers to hide contract mistakes.
|
|
37
|
+
- Keep Job identity in its definition and realtime names/schemas in typed message classes under `domain_shared/messages`; callers provide typed values. Explicit public error data belongs to a Pydantic DTO selected by the business. Preserve existing wire names and fields when changing these schemas; queue acceptance is not delivery acknowledgment.
|
|
38
|
+
|
|
39
|
+
For in-process messages, read the [message example](docs/development.md#in-process-messages).
|
|
40
|
+
Business modules own message values and coalescing identities. Only replaceable snapshots may
|
|
41
|
+
coalesce. Acceptance, processing, commit and external acknowledgement are separate facts.
|
|
42
|
+
Use the framework channel and hosted bridge; preserve capacity and actual cleanup guarantees.
|
|
30
43
|
|
|
31
44
|
## Verify and maintain
|
|
32
45
|
|
python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md
CHANGED
|
@@ -8,20 +8,20 @@ A modular Python application generated with Python DDD Framework {{ cookiecutter
|
|
|
8
8
|
| [Architecture](docs/architecture.md) | Ownership, dependency direction, transaction boundaries, and lifecycle guarantees. |
|
|
9
9
|
| [Development guide](docs/development.md) | Recipes for services, permissions, persistence, configuration, events, and background work. |
|
|
10
10
|
|
|
11
|
-
Each generated business module also has its own README under `src/modules/<name>/`. This README owns application setup and operations.
|
|
11
|
+
Each generated business module also has its own README under `backend/src/modules/<name>/`. This README owns application setup and operations.
|
|
12
12
|
|
|
13
13
|
## Prerequisites
|
|
14
14
|
|
|
15
|
-
Use the Python version declared
|
|
15
|
+
Python metadata, `.python-version`, `uv.lock`, `.venv`, source, tests, YAML, Compose files, and the Dockerfile belong in `backend/`. Root `scripts/` belongs to application-owned development scripts. Use uv and the Python version declared under `backend/`; Docker with Compose is required for the generated Host's providers. Normal creation synchronizes dependencies. With `pddd new <name> --python <major.minor[.patch]> --no-sync`, creation writes files only, even if that interpreter is absent. Run `uv sync --project backend` afterwards; uv selects or downloads the requested Python. On later checkouts, use `uv sync --project backend --locked`.
|
|
16
16
|
|
|
17
|
-
Run project commands from
|
|
17
|
+
Run `pddd` project commands from the application root, `backend/`, or any child directory. Host entry points identify the owning backend; unrelated nested Python projects do not redirect the command. The CLI uses that backend's existing environment and framework version. Synchronize explicitly before the first project command. Inside `backend/`, `uv run pddd` is also available; from other locations use the installed `pddd` launcher. Use `pddd` or a subcommand's `--help` to see available commands and options.
|
|
18
18
|
|
|
19
19
|
## Prepare local development
|
|
20
20
|
|
|
21
|
-
Review `app.development.yaml`, then start the local infrastructure:
|
|
21
|
+
Review `backend/app.development.yaml`, then start the local infrastructure:
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
|
-
|
|
24
|
+
pddd dev-init
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
This starts or reuses PostgreSQL and Redis from the Host's development configuration and preserves data. It does not start the application, migrate, seed, or create an `.env` file. The bundled Compose setup requires local URLs with explicit ports; its Redis service does not configure authentication or TLS.
|
|
@@ -29,46 +29,51 @@ This starts or reuses PostgreSQL and Redis from the Host's development configura
|
|
|
29
29
|
To add the example business module:
|
|
30
30
|
|
|
31
31
|
```sh
|
|
32
|
-
|
|
32
|
+
pddd add module orders --template ddd
|
|
33
|
+
pddd add module conversions --template basic
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
Read `src/modules/orders/README.md` before changing the sample. The project is also usable as a pure Host without this step.
|
|
36
|
+
Read `backend/src/modules/orders/README.md` before changing the sample. The project is also usable as a pure Host without this step.
|
|
36
37
|
|
|
37
38
|
## Initialize the database
|
|
38
39
|
|
|
39
40
|
Apply the migrations for the providers selected by the generated Host:
|
|
40
41
|
|
|
41
42
|
```sh
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
pddd db upgrade --module identity
|
|
44
|
+
pddd db upgrade --module settings
|
|
45
|
+
pddd db upgrade --module auditing
|
|
46
|
+
pddd db upgrade --module background_jobs
|
|
47
|
+
pddd db seed --module identity
|
|
47
48
|
```
|
|
48
49
|
|
|
49
50
|
If you added `orders`, generate its first revision:
|
|
50
51
|
|
|
51
52
|
```sh
|
|
52
|
-
|
|
53
|
+
pddd db revision --module orders
|
|
53
54
|
```
|
|
54
55
|
|
|
55
|
-
Review the generated file in `src/modules/orders/sqlalchemy/migrations/`, then apply and seed it:
|
|
56
|
+
Review the generated file in `backend/src/modules/orders/sqlalchemy/migrations/`, then apply and seed it:
|
|
56
57
|
|
|
57
58
|
```sh
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
pddd db upgrade --module orders
|
|
60
|
+
pddd db status --module orders
|
|
61
|
+
pddd db seed --module orders
|
|
61
62
|
```
|
|
62
63
|
|
|
63
64
|
All database operations select an explicit module. Upgrade prerequisite owners explicitly; commands do not migrate unrelated modules. Host startup does not migrate or seed. Database commands do not start workers or hosted services. See [persistence and migrations](docs/development.md#persistence-and-migrations) before changing models or existing data.
|
|
64
65
|
|
|
65
66
|
## Run and inspect
|
|
66
67
|
|
|
68
|
+
To add fields to an explicitly extensible module such as Identity, follow [Extend reusable modules](docs/development.md#extend-reusable-modules). Upgrade the base owner before generating your customer module's extension revision. Keep all new revisions in this project's extension module; framework installation files are not an authoring destination.
|
|
69
|
+
|
|
67
70
|
```sh
|
|
68
|
-
|
|
71
|
+
pddd dev
|
|
69
72
|
```
|
|
70
73
|
|
|
71
|
-
Open **http://127.0.0.1:8000/docs**. Call `/api/auth/login` using the generated `identity.seed_admin_username` and `identity.seed_admin_password` from `app.development.yaml`, then enter the access token in **Authorize**. Local credentials must not be reused for production.
|
|
74
|
+
Open **http://127.0.0.1:8000/docs**. Call `/api/auth/login` using the generated `identity.seed_admin_username` and `identity.seed_admin_password` from `backend/app.development.yaml`, then enter the access token in **Authorize**. Local credentials must not be reused for production.
|
|
75
|
+
|
|
76
|
+
Identity includes paged user/role management, password operations, and version-protected assignments. Before using it, apply the framework's Identity migrations; preserve the returned `concurrency_version` in updates. See [Identity management](docs/development.md#identity-management) for API usage, session invalidation, and Host authorization options.
|
|
72
77
|
|
|
73
78
|
With the `orders` example installed, `/api/orders` supports create/query, `/api/orders/{id}/approve` approves an order, and `/api/orders/{id}/queue-approval` enqueues approval. Read the module README for its rules and OpenAPI for request schemas. A stale version produces a conflict response.
|
|
74
79
|
|
|
@@ -77,7 +82,7 @@ With the `orders` example installed, `/api/orders` supports create/query, `/api/
|
|
|
77
82
|
To inspect module composition, service registrations, and redacted configuration inputs:
|
|
78
83
|
|
|
79
84
|
```sh
|
|
80
|
-
|
|
85
|
+
pddd inspect --environment development
|
|
81
86
|
```
|
|
82
87
|
|
|
83
88
|
Inspection composes and closes a new Application without starting it. It does not query an existing process, and its input snapshot is not the final Options view. Configuration, workers, jobs, real-time communication, and logging are covered in the [development guide](docs/development.md).
|
|
@@ -85,6 +90,7 @@ Inspection composes and closes a new Application without starting it. It does no
|
|
|
85
90
|
## Test and package
|
|
86
91
|
|
|
87
92
|
```sh
|
|
93
|
+
cd backend
|
|
88
94
|
uv run pytest
|
|
89
95
|
uv build --no-sources
|
|
90
96
|
```
|
|
@@ -93,7 +99,7 @@ The application tests include real Host infrastructure and require Docker. Modul
|
|
|
93
99
|
|
|
94
100
|
## Deploy
|
|
95
101
|
|
|
96
|
-
The deployment supplies `app.production.yaml`, following the development file's key structure with independent database, Redis, JWT, and administrator credentials. Set the production logging environment and format. Keep the file out of Git and the image; production Compose mounts it read-only at `/app/app.production.yaml` for both web and migration services.
|
|
102
|
+
Run Docker and Compose commands from `backend/`. The deployment supplies `backend/app.production.yaml`, following the development file's key structure with independent database, Redis, JWT, and administrator credentials. Set the production logging environment and format. Keep the file out of Git and the image; production Compose mounts it read-only at `/app/app.production.yaml` for both web and migration services.
|
|
97
103
|
|
|
98
104
|
Build an image, validate it, and set `APP_IMAGE` to its immutable digest. In PowerShell, run the following for each required module, replacing the module alias and digest:
|
|
99
105
|
|
|
@@ -118,9 +124,10 @@ The base framework dependency includes runtime capabilities. Production does not
|
|
|
118
124
|
|
|
119
125
|
Read the target framework release's migration instructions. Pin `python-ddd-framework` in runtime dependencies and `python-ddd-framework[developer-kit]` in the development group to the same target version. Preserve the application's own dependencies and source overrides.
|
|
120
126
|
|
|
121
|
-
For a first move from a local wheel or old Git source to PyPI, remove only the framework entry in `[tool.uv.sources]`; retain other dependencies and vendor files.
|
|
127
|
+
For a first move from a local wheel or old Git source to PyPI, remove only the framework entry in `[tool.uv.sources]`; retain other dependencies and vendor files. From the application root, run:
|
|
122
128
|
|
|
123
129
|
```sh
|
|
130
|
+
cd backend
|
|
124
131
|
uv lock --upgrade-package python-ddd-framework
|
|
125
132
|
uv sync --locked
|
|
126
133
|
uv run pytest
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "{{ cookiecutter.project_name }}"
|
|
3
3
|
version = "0.1.0"
|
|
4
|
-
requires-python = "
|
|
4
|
+
requires-python = "{{ cookiecutter.requires_python }}"
|
|
5
5
|
dependencies = ["python-ddd-framework=={{ cookiecutter.framework_version }}"]
|
|
6
6
|
|
|
7
7
|
[project.entry-points."python_ddd_framework.hosts"]
|
|
@@ -17,6 +17,7 @@ from .module import {{ cookiecutter.class_prefix }}HostModule
|
|
|
17
17
|
def create_application(
|
|
18
18
|
*, environment: str | None = None, cli_args: tuple[str, ...] = ()
|
|
19
19
|
) -> Application:
|
|
20
|
+
# Host 扩展:在这里选择环境、配置来源和根模块;业务规则与用例实现放在各业务模块中。
|
|
20
21
|
environment = environment or os.environ.get(ENVIRONMENT_VARIABLE, "development")
|
|
21
22
|
return ApplicationBuilder(
|
|
22
23
|
{{ cookiecutter.class_prefix }}HostModule,
|
|
@@ -28,6 +29,7 @@ def create_application(
|
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
def create_web() -> FastAPI:
|
|
32
|
+
# HTTP 组装扩展:在这里设置站点信息和 Host middleware,并在 install 前完成需要的配置。
|
|
31
33
|
application = create_application()
|
|
32
34
|
# 全局 middleware 使用原生 Starlette 实现,在 adapter.install 前完成配置。
|
|
33
35
|
adapter = FastApiAdapter(
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
"""Host 只选择 provider 与组合 Module,不拥有业务规则。"""
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from python_ddd_framework import
|
|
3
|
+
|
|
4
|
+
from python_ddd_framework import (
|
|
5
|
+
AppModule,
|
|
6
|
+
ConfigureContext,
|
|
7
|
+
InitializeContext,
|
|
8
|
+
PostConfigureContext,
|
|
9
|
+
PostInitializeContext,
|
|
10
|
+
PreConfigureContext,
|
|
11
|
+
PreInitializeContext,
|
|
12
|
+
ShutdownContext,
|
|
13
|
+
)
|
|
5
14
|
from python_ddd_framework.auditing.sqlalchemy import SqlAlchemyAuditingModule
|
|
6
15
|
from python_ddd_framework.background_jobs.pgqueuer import PgQueuerBackgroundJobsModule
|
|
7
16
|
from python_ddd_framework.fastapi.background import BackgroundManagementHttpApiModule
|
|
@@ -13,6 +22,7 @@ from python_ddd_framework.observability.tracing import TracingModule
|
|
|
13
22
|
from python_ddd_framework.redis import RedisDistributedLockModule, RedisModule
|
|
14
23
|
from python_ddd_framework.redis.notifications import RedisNotificationsModule
|
|
15
24
|
from python_ddd_framework.settings.notifications import SettingsNotificationsModule
|
|
25
|
+
from python_ddd_framework.settings.refresh_module import SettingsRefreshModule
|
|
16
26
|
from python_ddd_framework.settings.sqlalchemy import SqlAlchemyRuntimeSettingsModule
|
|
17
27
|
|
|
18
28
|
|
|
@@ -27,11 +37,38 @@ class {{ cookiecutter.class_prefix }}HostModule(AppModule):
|
|
|
27
37
|
RedisDistributedLockModule,
|
|
28
38
|
RedisNotificationsModule,
|
|
29
39
|
SettingsNotificationsModule,
|
|
40
|
+
SettingsRefreshModule,
|
|
30
41
|
PgQueuerBackgroundJobsModule,
|
|
31
42
|
BackgroundManagementHttpApiModule,
|
|
32
43
|
StructuredLoggingModule,
|
|
33
44
|
TracingModule,
|
|
34
45
|
)
|
|
35
46
|
|
|
47
|
+
def pre_configure(self, context: PreConfigureContext) -> None:
|
|
48
|
+
# 贡献影响后续注册的 PreOptions;此阶段不启动资源。
|
|
49
|
+
pass
|
|
50
|
+
|
|
36
51
|
def configure(self, context: ConfigureContext) -> None:
|
|
37
|
-
|
|
52
|
+
# Host 组装扩展:在这里接入运行环境所需的 provider/服务配置;模块依赖声明在上方 dependencies。
|
|
53
|
+
# 新业务写进模块的应用服务和领域对象,不在 configure 中执行数据库操作或启动长期任务。
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def post_configure(self, context: PostConfigureContext) -> None:
|
|
57
|
+
# 读取已冻结 PreOptions,补充 Options 或覆盖已有服务注册。
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
async def pre_initialize(self, context: PreInitializeContext) -> None:
|
|
61
|
+
# 异步初始化前置条件;依赖模块在同一阶段先完成。
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
async def initialize(self, context: InitializeContext) -> None:
|
|
65
|
+
# 初始化本模块资源;不手工启动框架管理的 Worker 或 HostedService。
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
async def post_initialize(self, context: PostInitializeContext) -> None:
|
|
69
|
+
# 完成初始化收尾;应用此时尚未对外就绪。
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
async def shutdown(self, context: ShutdownContext) -> None:
|
|
73
|
+
# 逆依赖关闭本模块拥有的资源;返回前等待清理结束。
|
|
74
|
+
pass
|
|
@@ -10,18 +10,20 @@ The initial project is a Host without business modules. `pddd add module <name>`
|
|
|
10
10
|
|
|
11
11
|
| Owner | Responsibility |
|
|
12
12
|
| --- | --- |
|
|
13
|
-
| `src/host/main.py` | Application and FastAPI factories, configuration inputs, middleware assembly, and the server entry point. |
|
|
14
|
-
| `src/host/module.py` | Explicit module dependencies and provider selection for this deployment. |
|
|
15
|
-
| `src/modules/<name>/` | A business capability, its public contracts, implementation, persistence, transport, and tests. |
|
|
16
|
-
| `app.development.yaml` | Local environment inputs and generated development credentials. |
|
|
13
|
+
| `backend/src/host/main.py` | Application and FastAPI factories, configuration inputs, middleware assembly, and the server entry point. |
|
|
14
|
+
| `backend/src/host/module.py` | Explicit module dependencies and provider selection for this deployment. |
|
|
15
|
+
| `backend/src/modules/<name>/` | A business capability, its public contracts, implementation, persistence, transport, and tests. |
|
|
16
|
+
| `backend/app.development.yaml` | Local environment inputs and generated development credentials. |
|
|
17
17
|
| Deployment configuration | Production connections, secrets, environment configuration, and image selection. |
|
|
18
|
-
| `pyproject.toml` and `uv.lock` | Package dependencies, entry points, Python requirements, and resolved versions. |
|
|
18
|
+
| `backend/pyproject.toml` and `backend/uv.lock` | Package dependencies, entry points, Python requirements, and resolved versions. |
|
|
19
19
|
|
|
20
|
-
The Host initially selects PostgreSQL persistence, identity, auditing, Redis capabilities, settings, background jobs, and observability integrations. Read its dependency declarations or run `
|
|
20
|
+
The Host initially selects PostgreSQL persistence, identity, auditing, Redis capabilities, settings, background jobs, and observability integrations. Read its dependency declarations or run `pddd inspect` for the actual composition. Installing a framework dependency does not enable a module or start its resources.
|
|
21
21
|
|
|
22
22
|
## Business-module boundaries
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
A `basic` module has `contracts/` and `services/`: implementation depends on the public Protocol; a consuming Module declares its dependency and injects the service. Ordinary transient DI retains native REQUEST scope and has no implicit application-service interception or HTTP exposure. It requires no database or Redis provider.
|
|
25
|
+
|
|
26
|
+
A `ddd` module uses the following dependency direction:
|
|
25
27
|
|
|
26
28
|
```text
|
|
27
29
|
application -> domain -> domain_shared
|
|
@@ -34,7 +36,7 @@ Host composes application, sqlalchemy, and http_api modules.
|
|
|
34
36
|
|
|
35
37
|
| Layer | Owns | Keep outside this layer |
|
|
36
38
|
| --- | --- | --- |
|
|
37
|
-
| `domain_shared` | Shared values, error definitions, permission names, and business constants. | Host configuration, persistence, and transport behavior. |
|
|
39
|
+
| `domain_shared` | Shared values, typed message schemas, error definitions, permission names, and business constants. | Host configuration, persistence, and transport behavior. |
|
|
38
40
|
| `domain` | Aggregates, state transitions, events, repository contracts, setting definitions, and seed contributors. | ORM models, HTTP requests, and concrete infrastructure providers. |
|
|
39
41
|
| `application_contracts` | Input/output DTOs and public service Protocols. | Service policies, implementation details, and ORM types. |
|
|
40
42
|
| `application` | Use cases, authorization policies, event handlers, typed job handlers, workers, and integration behavior. | Database mappings and Host-specific provider selection. |
|
|
@@ -43,19 +45,39 @@ Host composes application, sqlalchemy, and http_api modules.
|
|
|
43
45
|
|
|
44
46
|
The application layer depends on domain and application contracts. The HTTP layer consumes contracts; it does not import the application implementation. The persistence layer implements domain repository contracts. Framework types and selected native Python libraries remain available where appropriate to these responsibilities.
|
|
45
47
|
|
|
48
|
+
HTTP exposure contributes the ContractsModule dependency to the normal graph. Typed realtime messages own their versioned names and payload fields in the shared layer; application handlers and HTTP sockets map business events/results explicitly. Message schemas do not import application DTOs, and the framework owns only the envelope and process-local delivery queue. Job schedules reference their registered Job type/definition and typed payload instead of duplicating persistent task identity.
|
|
49
|
+
|
|
50
|
+
Immutable values shared by domain objects and public DTOs belong in `domain_shared/value_objects`; values used only inside Domain belong in `domain/value_objects`. Contracts must not depend on Domain to reuse a value. Keep one definition owner.
|
|
51
|
+
|
|
46
52
|
Cross-module access uses public service contracts and explicit Module dependencies. A module owns its own data and migrations. Shared database deployment does not authorize another module to query its tables. Keep internal collaboration contracts separate from end-user service contracts where the caller and authorization semantics differ; `IntegrationService` requires its own contract and is not exposed over HTTP.
|
|
47
53
|
|
|
54
|
+
The [development guide](development.md#module-layout-and-coding-rules) owns the complete directory and naming rules. Query, management, approval, and optional item operations have separate user-facing contracts. Internal reporting belongs to a separate integration contract and implementation. HTTP keeps its existing paths and operation identities through public route overrides when Python services move.
|
|
55
|
+
|
|
56
|
+
Business Modules explicitly depend on the framework capabilities they use. Domain seed contributors depend on `DataSeedingModule`, permission definitions on `AuthorizationModule`, application services on `ApplicationServicesModule`, and HTTP declarations on `FastApiModule`. Host provider selection does not repair a missing business-module dependency. Capability definitions use typed module declarations; their catalogs and runtime state belong to each Application. Every Host and six-layer Module explicitly declares the seven framework hooks. Synchronous configuration phases only compose the application; asynchronous initialization phases follow dependency-first barriers, and shutdown closes resources in reverse dependency order. Empty hooks use `pass`. Generating a complete example does not enable its workers, hosted services, or schedules.
|
|
57
|
+
|
|
48
58
|
## Calls, scopes, and transactions
|
|
49
59
|
|
|
50
60
|
A managed service call passes through authorization, validation, ACTION scope, unit-of-work handling, and the final implementation. HTTP and direct service invocation share this pipeline. Inject the public service contract; do not construct implementations to bypass it. A self-call is an ordinary Python call and does not re-enter the pipeline.
|
|
51
61
|
|
|
52
62
|
Dishka owns service lifetimes. Repository implementations use cached REQUEST scope, application-service implementations use cached ACTION scope, and hosted services use cached APP scope. A unit of work independently owns its transaction and Session; REQUEST and ACTION scopes are not themselves transactions.
|
|
53
63
|
|
|
54
|
-
Default read/write transaction behavior follows framework method/HTTP conventions and explicit policies.
|
|
64
|
+
Default read/write transaction behavior follows framework method/HTTP conventions and explicit policies. Manual `begin()` creates and owns missing execution scopes, or reuses a valid existing context. Call `complete()` before exiting; a new transaction never rewrites the ACTION context cache. A flush is not a commit. Keep sessions local to the owning invocation and avoid transactions across external I/O or long waits.
|
|
55
65
|
|
|
56
66
|
Repositories map aggregates and ORM models explicitly. Their `SqlAlchemySessionProvider.operation(..., aggregate=...)` boundary stages version changes and collects aggregate events once. Database optimistic concurrency still protects competing writes; do not replace it with a cache or a distributed lock.
|
|
57
67
|
|
|
58
|
-
Local transactional events run in this order: DOMAIN handlers, flush/version checks, database commit, then AFTER_COMMIT handlers. A post-commit failure does not roll back committed data. Local events provide neither distributed delivery nor crash recovery.
|
|
68
|
+
Local transactional events run in this order: DOMAIN handlers, flush/version checks, database commit, then AFTER_COMMIT handlers. Outside a UoW, `await events.publish(message)` dispatches immediately and does not claim a transaction was committed. Nontransactional UoWs reject explicit and automatic events before repository writes. A post-commit failure does not roll back committed data. Local events provide neither distributed delivery nor crash recovery.
|
|
69
|
+
|
|
70
|
+
Typed caches inject `DistributedCache[Item]` and take keys/values directly. The value type owns its cache name; the Host owns the Redis prefix and global defaults. Entry options replace the whole default object. `consider_uow=True` stages changes in the concrete UoW and applies them after successful completion; rollback discards them. Redis and PostgreSQL do not share an atomic commit.
|
|
71
|
+
|
|
72
|
+
## Extension ownership
|
|
73
|
+
|
|
74
|
+
Identity owns core user/role fields, edit versions, and their base migrations. `concurrency_version` protects edits and relationship replacements; `permission_version` only tracks permission state. Password changes, deactivation, and deletion revoke sessions in the same transaction. Login and refresh coordinate through the user's transaction lock; refresh families retain their own lock-before-row mutation order. Consumer modules own only their declared extension columns/indexes and project-local revisions.
|
|
75
|
+
|
|
76
|
+
Authorization options belong to this Application. The Host may enable `always_allow` for shared authorization checks, but authentication and real-user business requirements remain separate. The setting does not change token validation, input validation, transaction ownership, or business rules. User creation, editing, and paged results share the same application-local extension models and JSON/column mappings.
|
|
77
|
+
|
|
78
|
+
Reusable modules explicitly open `ExtensionTarget` values through `ExtensionPoints`; ordinary application models need no extension registry. A customer Module depends on the target owner and contributes typed `ExtensionProperties` through `ModelExtensions`. Effective Pydantic models belong to this Application and supply validation, serialization, and OpenAPI before service methods compile, including ordinary nested DTOs, recursive fields, and native generic containers. HTTP values stay inside `extra_properties`; explicit Query/Header/Body metadata remains on the final implementation's effective signature.
|
|
79
|
+
|
|
80
|
+
The persistence Module may reference those same Pydantic fields in `SqlAlchemyModelExtension` using native Column factories. Effective mapper/registry/metadata are Application-local and shared by runtime and migrations. The original module owns its table, Schema, JSON container, and core concurrency columns; the customer extension owns only its declared columns/indexes and local revisions with explicit Alembic `depends_on`. Ownership constrains both object filters and generated operations, including inline create-table columns and table comments. Current extension heads satisfy current dependencies; historical revisions keep their original dependencies. Module dependencies do not imply database prerequisites. Never patch shared framework models or author revisions in installed packages.
|
|
59
81
|
|
|
60
82
|
## Configuration and lifecycle
|
|
61
83
|
|
|
@@ -63,6 +85,14 @@ The Host selects the environment through `{{ cookiecutter.host_environment_varia
|
|
|
63
85
|
|
|
64
86
|
Modules bind typed `BaseOptions` models with `context.configure(...)`; services inject `Options[T]`. Startup options and mutable runtime settings have separate purposes. `SettingProvider` reads settings; conditional management updates use the current version token.
|
|
65
87
|
|
|
88
|
+
`MessagingModule` owns bounded in-process channels and their lifecycle. Each message type has
|
|
89
|
+
one declared channel and a cached ACTION Handler binding. Consumption creates a fresh
|
|
90
|
+
REQUEST/ACTION through the hosted bridge; idle receive holds no transaction. Closing admission
|
|
91
|
+
still permits previously accepted work through one-use reservations. Failure/deadline handling
|
|
92
|
+
waits for handler/UoW/finalizer exit. Only pending replaceable snapshots may coalesce. Messages
|
|
93
|
+
are independent of local events, durable Jobs and WebSocket delivery; see
|
|
94
|
+
[usage](development.md#in-process-messages).
|
|
95
|
+
|
|
66
96
|
An Application owns its runtime state, DI container, connections, and background execution. Module definitions can be shared; mutable runtime state cannot. No global application registry or service locator is required.
|
|
67
97
|
|
|
68
98
|
Module initialization precedes selected hosted services and background execution. Shutdown waits for real cleanup. The Host does not migrate or seed the database. `dev-init` prepares local infrastructure, while explicit `db` commands own migration and seed operations.
|