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
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"""Cache metadata 的 Application-local 动态聚合与 immutable view。"""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from collections.abc import Iterator, Mapping
|
|
6
|
-
from types import MappingProxyType
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
from ..modularity import ModuleRegistry
|
|
10
|
-
from .contracts import CacheDefinition
|
|
11
|
-
from .errors import CacheDefinitionError
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class CacheCatalog:
|
|
15
|
-
__slots__ = ("_definitions",)
|
|
16
|
-
|
|
17
|
-
def __init__(self, definitions: tuple[CacheDefinition[Any], ...]) -> None:
|
|
18
|
-
by_name: dict[str, CacheDefinition[Any]] = {}
|
|
19
|
-
for definition in definitions:
|
|
20
|
-
if not isinstance(definition, CacheDefinition):
|
|
21
|
-
raise CacheDefinitionError(
|
|
22
|
-
cache=repr(definition),
|
|
23
|
-
reason="catalog entries must be CacheDefinition values",
|
|
24
|
-
)
|
|
25
|
-
if definition.name in by_name:
|
|
26
|
-
raise CacheDefinitionError(
|
|
27
|
-
cache=definition.name,
|
|
28
|
-
reason="cache is declared more than once",
|
|
29
|
-
)
|
|
30
|
-
by_name[definition.name] = definition
|
|
31
|
-
self._definitions: Mapping[str, CacheDefinition[Any]] = MappingProxyType(
|
|
32
|
-
dict(sorted(by_name.items()))
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
@property
|
|
36
|
-
def definitions(self) -> tuple[CacheDefinition[Any], ...]:
|
|
37
|
-
return tuple(self._definitions.values())
|
|
38
|
-
|
|
39
|
-
def __iter__(self) -> Iterator[str]:
|
|
40
|
-
return iter(self._definitions)
|
|
41
|
-
|
|
42
|
-
def get(self, name: str) -> CacheDefinition[Any] | None:
|
|
43
|
-
return self._definitions.get(name)
|
|
44
|
-
|
|
45
|
-
def owns(self, definition: CacheDefinition[Any]) -> bool:
|
|
46
|
-
return self._definitions.get(definition.name) is definition
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _build_cache_catalog(registry: ModuleRegistry) -> CacheCatalog:
|
|
50
|
-
definitions: list[CacheDefinition[Any]] = []
|
|
51
|
-
owners: dict[str, str] = {}
|
|
52
|
-
for descriptor in registry.dependency_order:
|
|
53
|
-
declared = descriptor.module_type.cache_definitions
|
|
54
|
-
if not isinstance(declared, tuple):
|
|
55
|
-
raise CacheDefinitionError(
|
|
56
|
-
cache=str(descriptor.key),
|
|
57
|
-
reason="Module cache_definitions must be an immutable tuple",
|
|
58
|
-
)
|
|
59
|
-
for definition in declared:
|
|
60
|
-
if not isinstance(definition, CacheDefinition):
|
|
61
|
-
raise CacheDefinitionError(
|
|
62
|
-
cache=str(descriptor.key),
|
|
63
|
-
reason="Module cache_definitions contains an invalid item",
|
|
64
|
-
)
|
|
65
|
-
previous = owners.get(definition.name)
|
|
66
|
-
if previous is not None:
|
|
67
|
-
raise CacheDefinitionError(
|
|
68
|
-
cache=definition.name,
|
|
69
|
-
reason=f"cache is already owned by {previous}",
|
|
70
|
-
)
|
|
71
|
-
owners[definition.name] = str(descriptor.key)
|
|
72
|
-
definitions.append(definition)
|
|
73
|
-
return CacheCatalog(tuple(definitions))
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"""缓存契约只声明一次;提交后处理负责失效。"""
|
|
2
|
-
|
|
3
|
-
from datetime import timedelta
|
|
4
|
-
|
|
5
|
-
from python_ddd_framework.caching import CacheDefinition
|
|
6
|
-
|
|
7
|
-
from ..application_contracts.orders import OrderView
|
|
8
|
-
from ..domain_shared.definitions import MODULE_NAME
|
|
9
|
-
|
|
10
|
-
ORDER_CACHE = CacheDefinition(f"{MODULE_NAME}.view", OrderView, ttl=timedelta(minutes=1))
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"""提交后失效缓存与通知;失败不伪装成数据库未提交,不承诺跨进程事件投递。"""
|
|
2
|
-
|
|
3
|
-
from python_ddd_framework import LocalEventPhase, local_event_handler
|
|
4
|
-
from python_ddd_framework.caching import DistributedCache
|
|
5
|
-
from python_ddd_framework.realtime import RealtimePublisher
|
|
6
|
-
|
|
7
|
-
from ..domain.orders import OrderChanged
|
|
8
|
-
from ..domain_shared.definitions import ORDER_CHANGED_MESSAGE
|
|
9
|
-
from .cache import ORDER_CACHE
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@local_event_handler(phase=LocalEventPhase.AFTER_COMMIT)
|
|
13
|
-
class OrderChangedHandler:
|
|
14
|
-
def __init__(self, cache: DistributedCache, realtime: RealtimePublisher) -> None:
|
|
15
|
-
self._cache, self._realtime = cache, realtime
|
|
16
|
-
|
|
17
|
-
async def handle(self, event: OrderChanged) -> None:
|
|
18
|
-
await self._cache.remove(ORDER_CACHE, str(event.order_id))
|
|
19
|
-
if event.user_id is not None:
|
|
20
|
-
await self._realtime.send_to_user(
|
|
21
|
-
event.user_id,
|
|
22
|
-
message_type=ORDER_CHANGED_MESSAGE,
|
|
23
|
-
payload={"id": str(event.order_id), "status": event.status.value},
|
|
24
|
-
)
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"""正式应用服务共用验证、授权、审计和 UoW;业务层不接触框架执行器。"""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from uuid import UUID, uuid4
|
|
5
|
-
|
|
6
|
-
from python_ddd_framework import ApplicationService, CurrentUser
|
|
7
|
-
from python_ddd_framework.authorization import authorize
|
|
8
|
-
from python_ddd_framework.background_jobs import BackgroundJobEnqueuer
|
|
9
|
-
from python_ddd_framework.caching import DistributedCache
|
|
10
|
-
from python_ddd_framework.errors import BusinessError
|
|
11
|
-
from python_ddd_framework.errors.base import ResourceConflictError, ResourceNotFoundError
|
|
12
|
-
from python_ddd_framework.fastapi import http
|
|
13
|
-
|
|
14
|
-
from ..application_contracts.orders import (
|
|
15
|
-
{%- for contract in ["ApproveOrder", "CreateOrder", "OrderView", cookiecutter.class_prefix ~ "ApplicationService"] | sort %}
|
|
16
|
-
{{ contract }},
|
|
17
|
-
{%- endfor %}
|
|
18
|
-
)
|
|
19
|
-
from ..domain.orders import Order, OrderTitle
|
|
20
|
-
from ..domain.repository import OrderRepository
|
|
21
|
-
from ..domain.settings import APPROVAL_ENABLED
|
|
22
|
-
from ..domain_shared.definitions import APPROVAL_DISABLED, ORDERS_READ, ORDERS_WRITE
|
|
23
|
-
from .cache import ORDER_CACHE
|
|
24
|
-
from .tasks import ApprovalJob, ApprovalPayload
|
|
25
|
-
|
|
26
|
-
logger = logging.getLogger(__name__)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class _{{ cookiecutter.class_prefix }}ApplicationService(ApplicationService, {{ cookiecutter.class_prefix }}ApplicationService):
|
|
30
|
-
def __init__(
|
|
31
|
-
self,
|
|
32
|
-
repository: OrderRepository,
|
|
33
|
-
cache: DistributedCache,
|
|
34
|
-
jobs: BackgroundJobEnqueuer,
|
|
35
|
-
user: CurrentUser,
|
|
36
|
-
) -> None:
|
|
37
|
-
self._repository, self._cache, self._jobs, self._user = repository, cache, jobs, user
|
|
38
|
-
|
|
39
|
-
@authorize(ORDERS_WRITE)
|
|
40
|
-
@http.post(status_code=201)
|
|
41
|
-
async def create(self, command: CreateOrder) -> OrderView:
|
|
42
|
-
order = Order.create(uuid4(), OrderTitle(value=command.title))
|
|
43
|
-
await self._repository.save(order)
|
|
44
|
-
logger.info("Order created")
|
|
45
|
-
return OrderView.model_validate(order)
|
|
46
|
-
|
|
47
|
-
@authorize(ORDERS_READ)
|
|
48
|
-
async def get(self, id: UUID) -> OrderView:
|
|
49
|
-
cached = await self._cache.get(ORDER_CACHE, str(id))
|
|
50
|
-
if cached is not None:
|
|
51
|
-
return cached
|
|
52
|
-
order = await self._repository.get(id)
|
|
53
|
-
if order is None:
|
|
54
|
-
raise ResourceNotFoundError("Order not found")
|
|
55
|
-
view = OrderView.model_validate(order)
|
|
56
|
-
await self._cache.set(ORDER_CACHE, str(id), view)
|
|
57
|
-
return view
|
|
58
|
-
|
|
59
|
-
@authorize(ORDERS_READ)
|
|
60
|
-
async def get_list(self) -> tuple[OrderView, ...]:
|
|
61
|
-
return tuple(OrderView.model_validate(order) for order in await self._repository.get_list())
|
|
62
|
-
|
|
63
|
-
@authorize(ORDERS_WRITE)
|
|
64
|
-
async def approve(self, id: UUID, command: ApproveOrder) -> OrderView:
|
|
65
|
-
if not await self.setting_provider.get(APPROVAL_ENABLED):
|
|
66
|
-
raise BusinessError(APPROVAL_DISABLED)
|
|
67
|
-
order = await self._repository.get(id)
|
|
68
|
-
if order is None:
|
|
69
|
-
raise ResourceNotFoundError("Order not found")
|
|
70
|
-
if order.version != command.expected_version:
|
|
71
|
-
raise ResourceConflictError("Order version changed")
|
|
72
|
-
order.approve(user_id=self._user.user_id)
|
|
73
|
-
await self._repository.save(order)
|
|
74
|
-
logger.info("Order approved")
|
|
75
|
-
return OrderView.model_validate(order)
|
|
76
|
-
|
|
77
|
-
@authorize(ORDERS_WRITE)
|
|
78
|
-
@http.post(status_code=202)
|
|
79
|
-
async def queue_approval(self, id: UUID) -> str:
|
|
80
|
-
if await self._repository.get(id) is None:
|
|
81
|
-
raise ResourceNotFoundError("Order not found")
|
|
82
|
-
# 入队参与本方法原生事务;失败时订单业务与队列写入一起回滚。
|
|
83
|
-
return await self._jobs.enqueue(ApprovalJob, ApprovalPayload(order_id=id))
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"""非持久 Worker 与持久 Job 分开;Job 重复投递由业务状态保证幂等。"""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from datetime import timedelta
|
|
5
|
-
from uuid import UUID
|
|
6
|
-
|
|
7
|
-
from pydantic import BaseModel, ConfigDict
|
|
8
|
-
from python_ddd_framework import (
|
|
9
|
-
BackgroundWorker,
|
|
10
|
-
BackgroundWorkerContext,
|
|
11
|
-
Options,
|
|
12
|
-
)
|
|
13
|
-
from python_ddd_framework.background_jobs import (
|
|
14
|
-
BackgroundJobContext,
|
|
15
|
-
BackgroundJobHandler,
|
|
16
|
-
)
|
|
17
|
-
from python_ddd_framework.distributed_lock import DistributedLock
|
|
18
|
-
from python_ddd_framework.settings import SettingProvider
|
|
19
|
-
|
|
20
|
-
from ..domain.repository import OrderRepository
|
|
21
|
-
from ..domain.settings import APPROVAL_ENABLED
|
|
22
|
-
from ..domain_shared.definitions import MODULE_NAME, OrderStatus
|
|
23
|
-
from .options import {{ cookiecutter.class_prefix }}Options
|
|
24
|
-
|
|
25
|
-
logger = logging.getLogger(__name__)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class ApprovalPayload(BaseModel):
|
|
29
|
-
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
30
|
-
order_id: UUID
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class ApprovalJob(BackgroundJobHandler[ApprovalPayload]):
|
|
34
|
-
name = f"{MODULE_NAME}.approve"
|
|
35
|
-
version = 1
|
|
36
|
-
current = True
|
|
37
|
-
timeout = timedelta(seconds=30)
|
|
38
|
-
|
|
39
|
-
def __init__(
|
|
40
|
-
self,
|
|
41
|
-
repository: OrderRepository,
|
|
42
|
-
settings: SettingProvider,
|
|
43
|
-
options: Options[{{ cookiecutter.class_prefix }}Options],
|
|
44
|
-
) -> None:
|
|
45
|
-
self._repository, self._settings, self._options = repository, settings, options
|
|
46
|
-
|
|
47
|
-
async def execute(self, payload: ApprovalPayload, context: BackgroundJobContext) -> None:
|
|
48
|
-
if self._options.value.allow_background_approval and await self._settings.get(
|
|
49
|
-
APPROVAL_ENABLED
|
|
50
|
-
):
|
|
51
|
-
order = await self._repository.get(payload.order_id)
|
|
52
|
-
if order is not None and order.status is OrderStatus.PENDING:
|
|
53
|
-
order.approve()
|
|
54
|
-
await self._repository.save(order)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class OrderMaintenanceWorker(BackgroundWorker):
|
|
58
|
-
name = f"{MODULE_NAME}.maintenance"
|
|
59
|
-
enabled = False
|
|
60
|
-
interval = timedelta(seconds=30)
|
|
61
|
-
iteration_timeout = timedelta(seconds=10)
|
|
62
|
-
|
|
63
|
-
def __init__(self, repository: OrderRepository, lock: DistributedLock) -> None:
|
|
64
|
-
self._repository, self._lock = repository, lock
|
|
65
|
-
|
|
66
|
-
async def run_iteration(self, context: BackgroundWorkerContext) -> None:
|
|
67
|
-
# 租约失效由框架请求取消并等待本轮清理;业务不吞 CancelledError。
|
|
68
|
-
async with self._lock.acquire(f"{MODULE_NAME}:maintenance") as acquired:
|
|
69
|
-
if acquired:
|
|
70
|
-
orders = await self._repository.get_list()
|
|
71
|
-
logger.info("Order maintenance completed: %s", len(orders))
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class OrderStatisticsWorker(BackgroundWorker):
|
|
75
|
-
name = f"{MODULE_NAME}.statistics"
|
|
76
|
-
enabled = False
|
|
77
|
-
interval = timedelta(minutes=5)
|
|
78
|
-
iteration_timeout = timedelta(seconds=10)
|
|
79
|
-
|
|
80
|
-
def __init__(self, repository: OrderRepository) -> None:
|
|
81
|
-
self._repository = repository
|
|
82
|
-
|
|
83
|
-
async def run_iteration(self, context: BackgroundWorkerContext) -> None:
|
|
84
|
-
logger.info("Order count: %s", len(await self._repository.get_list()))
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"""DTO 只依赖共享定义;HTTP 与进程内调用共用同一验证管线。"""
|
|
2
|
-
|
|
3
|
-
from typing import Protocol
|
|
4
|
-
from uuid import UUID
|
|
5
|
-
|
|
6
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
7
|
-
from python_ddd_framework import ApplicationServiceContract
|
|
8
|
-
|
|
9
|
-
from ..domain_shared.definitions import TITLE_MAX_LENGTH, OrderStatus
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class CreateOrder(BaseModel):
|
|
13
|
-
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
14
|
-
title: str = Field(min_length=1, max_length=TITLE_MAX_LENGTH)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class ApproveOrder(BaseModel):
|
|
18
|
-
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
19
|
-
expected_version: int = Field(ge=1)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class OrderView(BaseModel):
|
|
23
|
-
model_config = ConfigDict(frozen=True, from_attributes=True)
|
|
24
|
-
id: UUID
|
|
25
|
-
title: str
|
|
26
|
-
status: OrderStatus
|
|
27
|
-
version: int
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class {{ cookiecutter.class_prefix }}ApplicationService(ApplicationServiceContract, Protocol):
|
|
31
|
-
async def create(self, command: CreateOrder) -> OrderView: ...
|
|
32
|
-
async def get(self, id: UUID) -> OrderView: ...
|
|
33
|
-
async def get_list(self) -> tuple[OrderView, ...]: ...
|
|
34
|
-
async def approve(self, id: UUID, command: ApproveOrder) -> OrderView: ...
|
|
35
|
-
async def queue_approval(self, id: UUID) -> str: ...
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
"""领域需要的业务仓储边界;不暴露 Session 或容器。"""
|
|
2
|
-
|
|
3
|
-
from typing import Protocol
|
|
4
|
-
from uuid import UUID
|
|
5
|
-
|
|
6
|
-
from python_ddd_framework import RepositoryContract
|
|
7
|
-
|
|
8
|
-
from .orders import Order
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class OrderRepository(RepositoryContract, Protocol):
|
|
12
|
-
async def get(self, id: UUID) -> Order | None: ...
|
|
13
|
-
async def get_list(self) -> tuple[Order, ...]: ...
|
|
14
|
-
async def save(self, order: Order) -> None: ...
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"""仅 pddd db seed 显式调用;确定性业务 ID 保证重复初始化不产生新订单。"""
|
|
2
|
-
|
|
3
|
-
from uuid import NAMESPACE_URL, uuid5
|
|
4
|
-
|
|
5
|
-
from python_ddd_framework import DataSeedContributor
|
|
6
|
-
|
|
7
|
-
from ..domain_shared.definitions import MODULE_NAME
|
|
8
|
-
from .orders import Order, OrderTitle
|
|
9
|
-
from .repository import OrderRepository
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class {{ cookiecutter.class_prefix }}SeedContributor(DataSeedContributor):
|
|
13
|
-
def __init__(self, repository: OrderRepository) -> None:
|
|
14
|
-
self._repository = repository
|
|
15
|
-
|
|
16
|
-
async def seed(self) -> None:
|
|
17
|
-
id = uuid5(NAMESPACE_URL, f"{MODULE_NAME}:sample-order")
|
|
18
|
-
if await self._repository.get(id) is None:
|
|
19
|
-
await self._repository.save(Order.create(id, OrderTitle(value="Sample order")))
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"""修改业务名称、权限和约束的单一位置。"""
|
|
2
|
-
|
|
3
|
-
from enum import Enum
|
|
4
|
-
|
|
5
|
-
from python_ddd_framework import PermissionName
|
|
6
|
-
from python_ddd_framework.errors import BusinessErrorDefinition
|
|
7
|
-
|
|
8
|
-
MODULE_NAME = "{{ cookiecutter.module_name }}"
|
|
9
|
-
ORDER_CHANGED_MESSAGE = f"{MODULE_NAME}.changed.v1"
|
|
10
|
-
ORDER_SNAPSHOT_MESSAGE = f"{MODULE_NAME}.snapshot.v1"
|
|
11
|
-
TITLE_MAX_LENGTH = 120
|
|
12
|
-
ORDERS_READ = PermissionName(f"{MODULE_NAME}.read")
|
|
13
|
-
ORDERS_WRITE = PermissionName(f"{MODULE_NAME}.write")
|
|
14
|
-
ALREADY_APPROVED = BusinessErrorDefinition(
|
|
15
|
-
f"{MODULE_NAME}.already_approved", "Order is already approved"
|
|
16
|
-
)
|
|
17
|
-
APPROVAL_DISABLED = BusinessErrorDefinition(
|
|
18
|
-
f"{MODULE_NAME}.approval_disabled", "Order approval is disabled"
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class OrderStatus(str, Enum):
|
|
23
|
-
PENDING = "pending"
|
|
24
|
-
APPROVED = "approved"
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"""单进程在线通知;连接认证由框架管理,业务授权仍经过服务契约。"""
|
|
2
|
-
|
|
3
|
-
from typing import Annotated
|
|
4
|
-
|
|
5
|
-
from fastapi import APIRouter, Depends
|
|
6
|
-
from pydantic import BaseModel, ConfigDict
|
|
7
|
-
from python_ddd_framework import FrameworkError
|
|
8
|
-
from python_ddd_framework.fastapi import WebSocketConnection, websocket_connection
|
|
9
|
-
from starlette.websockets import WebSocketDisconnect
|
|
10
|
-
|
|
11
|
-
from ..application_contracts.orders import {{ cookiecutter.class_prefix }}ApplicationService
|
|
12
|
-
from ..domain_shared.definitions import MODULE_NAME, ORDER_SNAPSHOT_MESSAGE
|
|
13
|
-
|
|
14
|
-
router = APIRouter()
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class RefreshOrders(BaseModel):
|
|
18
|
-
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@router.websocket(f"/ws/{MODULE_NAME}")
|
|
22
|
-
async def orders_socket(
|
|
23
|
-
connection: Annotated[WebSocketConnection, Depends(websocket_connection)],
|
|
24
|
-
) -> None:
|
|
25
|
-
try:
|
|
26
|
-
while True:
|
|
27
|
-
orders = await connection.invoke({{ cookiecutter.class_prefix }}ApplicationService.get_list)
|
|
28
|
-
await connection.send(
|
|
29
|
-
ORDER_SNAPSHOT_MESSAGE,
|
|
30
|
-
{"orders": [item.model_dump(mode="json") for item in orders]},
|
|
31
|
-
)
|
|
32
|
-
await connection.receive(RefreshOrders)
|
|
33
|
-
except WebSocketDisconnect:
|
|
34
|
-
return
|
|
35
|
-
except FrameworkError as error:
|
|
36
|
-
await connection.send_problem(error)
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"""仓储参与当前 UoW,成功暂存后由 operation 收集聚合事件。"""
|
|
2
|
-
|
|
3
|
-
from uuid import UUID
|
|
4
|
-
|
|
5
|
-
from python_ddd_framework import OptimisticConcurrencyError
|
|
6
|
-
from python_ddd_framework.sqlalchemy import SqlAlchemySessionProvider
|
|
7
|
-
from sqlalchemy import select
|
|
8
|
-
|
|
9
|
-
from ...domain.orders import Order, OrderTitle
|
|
10
|
-
from ...domain.repository import OrderRepository
|
|
11
|
-
from ...domain_shared.definitions import OrderStatus
|
|
12
|
-
from ..models.orders import OrderRow
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class SqlAlchemyOrderRepository(OrderRepository):
|
|
16
|
-
def __init__(self, provider: SqlAlchemySessionProvider) -> None:
|
|
17
|
-
self._provider = provider
|
|
18
|
-
|
|
19
|
-
async def get(self, id: UUID) -> Order | None:
|
|
20
|
-
async with self._provider.operation("get"):
|
|
21
|
-
row = await (await self._provider.get_session()).get(OrderRow, id)
|
|
22
|
-
return None if row is None else _order(row)
|
|
23
|
-
|
|
24
|
-
async def get_list(self) -> tuple[Order, ...]:
|
|
25
|
-
async with self._provider.operation("get_list"):
|
|
26
|
-
rows = await (await self._provider.get_session()).scalars(
|
|
27
|
-
select(OrderRow).order_by(OrderRow.created_at, OrderRow.id)
|
|
28
|
-
)
|
|
29
|
-
return tuple(_order(row) for row in rows)
|
|
30
|
-
|
|
31
|
-
async def save(self, order: Order) -> None:
|
|
32
|
-
async with self._provider.operation("save", aggregate=order):
|
|
33
|
-
session = await self._provider.get_session()
|
|
34
|
-
row = await session.get(OrderRow, order.id)
|
|
35
|
-
if row is None:
|
|
36
|
-
if order.version:
|
|
37
|
-
raise OptimisticConcurrencyError
|
|
38
|
-
row = OrderRow(id=order.id)
|
|
39
|
-
session.add(row)
|
|
40
|
-
elif row.version != order.version:
|
|
41
|
-
raise OptimisticConcurrencyError
|
|
42
|
-
order.stage_next_version(order.version)
|
|
43
|
-
row.title, row.status, row.version = order.title, order.status.value, order.version
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def _order(row: OrderRow) -> Order:
|
|
47
|
-
return Order(
|
|
48
|
-
row.id, OrderTitle(value=row.title), status=OrderStatus(row.status), version=row.version
|
|
49
|
-
)
|