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,23 @@
|
|
|
1
|
+
"""审批设置属于跨对象的领域策略;订单自身仍拥有状态转换规则。"""
|
|
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.settings.approval_settings import APPROVAL_ENABLED
|
|
7
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.errors.order_errors import APPROVAL_DISABLED
|
|
8
|
+
|
|
9
|
+
from python_ddd_framework import DomainService, ValidationEnabled
|
|
10
|
+
from python_ddd_framework.errors import BusinessError
|
|
11
|
+
from python_ddd_framework.settings import SettingProvider
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OrderApprovalService(DomainService, ValidationEnabled):
|
|
15
|
+
def __init__(self, settings: SettingProvider) -> None:
|
|
16
|
+
self._settings = settings
|
|
17
|
+
|
|
18
|
+
async def approve(self, order: Order, *, user_id: UUID | None = None) -> None:
|
|
19
|
+
# 业务扩展:依赖设置、仓储或多个聚合的规则写在领域服务;不接收 HTTP DTO。
|
|
20
|
+
# 聚合仍保证不能重复审批;保存和提交由调用方完成,失败才能整体回滚。
|
|
21
|
+
if not await self._settings.get(APPROVAL_ENABLED):
|
|
22
|
+
raise BusinessError(APPROVAL_DISABLED)
|
|
23
|
+
order.approve(user_id=user_id)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""所属能力的实现或契约。"""
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
"""设置默认值属于领域,业务通过 SettingProvider 获取冻结读取视图。"""
|
|
2
2
|
|
|
3
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import MODULE_NAME
|
|
4
|
+
|
|
3
5
|
from python_ddd_framework.settings import (
|
|
4
6
|
SettingDefinition,
|
|
5
7
|
SettingDefinitionContext,
|
|
6
8
|
SettingDefinitionProvider,
|
|
7
9
|
)
|
|
8
10
|
|
|
9
|
-
from ..domain_shared.definitions import MODULE_NAME
|
|
10
|
-
|
|
11
11
|
APPROVAL_ENABLED = SettingDefinition(f"{MODULE_NAME}.approval_enabled", bool, default=True)
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class {{ cookiecutter.class_prefix }}SettingDefinitionProvider(SettingDefinitionProvider):
|
|
15
15
|
def define(self, context: SettingDefinitionContext) -> None:
|
|
16
|
+
# 定义扩展:在这里发布本模块的动态业务设置;类型/默认值归 SettingDefinition,读取走 SettingProvider。
|
|
16
17
|
context.add(APPROVAL_ENABLED)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""所属能力的实现或契约。"""
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import TITLE_MAX_LENGTH
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from python_ddd_framework import ValueObject
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OrderTitle(ValueObject):
|
|
10
|
+
value: str = Field(min_length=1, max_length=TITLE_MAX_LENGTH)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""所属能力的实现或契约。"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""所属能力的实现或契约。"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""所属能力的实现或契约。"""
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import MODULE_NAME
|
|
2
|
+
|
|
3
|
+
from python_ddd_framework.errors import BusinessErrorDefinition
|
|
4
|
+
|
|
5
|
+
ALREADY_APPROVED = BusinessErrorDefinition(
|
|
6
|
+
f"{MODULE_NAME}.already_approved", "Order is already approved"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
APPROVAL_DISABLED = BusinessErrorDefinition(
|
|
10
|
+
f"{MODULE_NAME}.approval_disabled", "Order approval is disabled"
|
|
11
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""所属能力的实现或契约。"""
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from uuid import UUID
|
|
2
|
+
|
|
3
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import MODULE_NAME
|
|
4
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.enums.order_status import OrderStatus
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
|
|
7
|
+
from python_ddd_framework.realtime import RealtimeMessage
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OrderChangedMessage(RealtimeMessage):
|
|
11
|
+
message_type = f"{MODULE_NAME}.changed.v1"
|
|
12
|
+
id: UUID
|
|
13
|
+
status: OrderStatus
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OrderSnapshotItem(BaseModel):
|
|
17
|
+
model_config = ConfigDict(frozen=True)
|
|
18
|
+
id: UUID
|
|
19
|
+
title: str
|
|
20
|
+
status: OrderStatus
|
|
21
|
+
version: int
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class OrderSnapshotMessage(RealtimeMessage):
|
|
25
|
+
message_type = f"{MODULE_NAME}.snapshot.v1"
|
|
26
|
+
orders: tuple[OrderSnapshotItem, ...]
|
|
@@ -1,7 +1,46 @@
|
|
|
1
1
|
"""共享层只发布业务常量与权限,不引用基础设施。"""
|
|
2
2
|
|
|
3
|
-
from python_ddd_framework import
|
|
3
|
+
from python_ddd_framework import (
|
|
4
|
+
AppModule,
|
|
5
|
+
AuthorizationModule,
|
|
6
|
+
ConfigureContext,
|
|
7
|
+
InitializeContext,
|
|
8
|
+
PostConfigureContext,
|
|
9
|
+
PostInitializeContext,
|
|
10
|
+
PreConfigureContext,
|
|
11
|
+
PreInitializeContext,
|
|
12
|
+
ShutdownContext,
|
|
13
|
+
)
|
|
4
14
|
|
|
5
15
|
|
|
6
16
|
class {{ cookiecutter.class_prefix }}DomainSharedModule(AppModule):
|
|
17
|
+
dependencies = (AuthorizationModule,)
|
|
7
18
|
scan_packages = (__package__,)
|
|
19
|
+
|
|
20
|
+
def pre_configure(self, context: PreConfigureContext) -> None:
|
|
21
|
+
# 贡献影响后续注册的 PreOptions;此阶段不启动资源。
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
def configure(self, context: ConfigureContext) -> None:
|
|
25
|
+
# 注册服务和普通 Options;业务执行留在所属能力。
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def post_configure(self, context: PostConfigureContext) -> None:
|
|
29
|
+
# 读取已冻结 PreOptions,补充 Options 或覆盖已有服务注册。
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
async def pre_initialize(self, context: PreInitializeContext) -> None:
|
|
33
|
+
# 异步初始化前置条件;依赖模块在同一阶段先完成。
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
async def initialize(self, context: InitializeContext) -> None:
|
|
37
|
+
# 初始化本模块资源;不手工启动框架管理的 Worker 或 HostedService。
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
async def post_initialize(self, context: PostInitializeContext) -> None:
|
|
41
|
+
# 完成初始化收尾;应用此时尚未对外就绪。
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
async def shutdown(self, context: ShutdownContext) -> None:
|
|
45
|
+
# 逆依赖关闭本模块拥有的资源;返回前等待清理结束。
|
|
46
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""所属能力的实现或契约。"""
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
"""权限名称由共享定义拥有;Provider 仅发布定义。"""
|
|
2
2
|
|
|
3
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.permissions.order_permissions import (
|
|
4
|
+
ORDERS_READ,
|
|
5
|
+
ORDERS_WRITE,
|
|
6
|
+
)
|
|
7
|
+
|
|
3
8
|
from python_ddd_framework import (
|
|
4
9
|
PermissionDefinition,
|
|
5
10
|
PermissionDefinitionContext,
|
|
6
11
|
PermissionDefinitionProvider,
|
|
7
12
|
)
|
|
8
13
|
|
|
9
|
-
from .definitions import ORDERS_READ, ORDERS_WRITE
|
|
10
|
-
|
|
11
14
|
|
|
12
15
|
class {{ cookiecutter.class_prefix }}PermissionDefinitionProvider(PermissionDefinitionProvider):
|
|
13
16
|
def define(self, context: PermissionDefinitionContext) -> None:
|
|
17
|
+
# 定义扩展:在这里发布自己的业务权限,名称统一放在共享定义;实际授权检查声明在服务/端点上。
|
|
14
18
|
context.add(PermissionDefinition(ORDERS_READ, "Read orders"))
|
|
15
19
|
context.add(PermissionDefinition(ORDERS_WRITE, "Create and approve orders"))
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import MODULE_NAME
|
|
2
|
+
|
|
3
|
+
from python_ddd_framework import PermissionName
|
|
4
|
+
|
|
5
|
+
ORDERS_READ = PermissionName(f"{MODULE_NAME}.read")
|
|
6
|
+
|
|
7
|
+
ORDERS_WRITE = PermissionName(f"{MODULE_NAME}.write")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""跨层共享的不可变领域值;不依赖 Domain 实现或 DTO。"""
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Shared immutable monetary value for domain objects and public DTOs."""
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from pydantic import Field, field_validator
|
|
6
|
+
|
|
7
|
+
from python_ddd_framework import ValueObject
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Money(ValueObject):
|
|
11
|
+
amount: Decimal = Field(ge=0, max_digits=12, decimal_places=2)
|
|
12
|
+
currency: str = Field(default="CNY", pattern=r"^[A-Z]{3}$")
|
|
13
|
+
|
|
14
|
+
@field_validator("amount")
|
|
15
|
+
@classmethod
|
|
16
|
+
def normalize_amount(cls, value: Decimal) -> Decimal:
|
|
17
|
+
# 与两位小数的持久价格一致,避免写入前后公开 DTO 表达不同。
|
|
18
|
+
return value.quantize(Decimal("0.01"))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""所属能力的实现或契约。"""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections.abc import Awaitable, Callable
|
|
3
|
+
|
|
4
|
+
from python_ddd_framework.fastapi import ActionFilterContext
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ExportFilter:
|
|
8
|
+
async def invoke(
|
|
9
|
+
self, context: ActionFilterContext, next_action: Callable[[], Awaitable[object]]
|
|
10
|
+
) -> object:
|
|
11
|
+
# 管线扩展:在这里添加端点执行前/后的 HTTP 处理;领域规则仍由调用的应用服务/聚合负责。
|
|
12
|
+
# Filter 的作用域只覆盖已绑定参数到响应准备;不假定流传输已经结束。
|
|
13
|
+
result = await next_action()
|
|
14
|
+
logging.getLogger(__name__).info("File response prepared")
|
|
15
|
+
return result
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""所属能力的实现或契约。"""
|
|
@@ -1,25 +1,103 @@
|
|
|
1
1
|
"""自动服务暴露与手写路由共用正式认证/REQUEST 管线。"""
|
|
2
2
|
|
|
3
3
|
from dishka import Scope
|
|
4
|
-
from
|
|
4
|
+
from modules.{{ cookiecutter.module_name }}.application_contracts.module import (
|
|
5
|
+
{{ cookiecutter.class_prefix }}ApplicationContractsModule,
|
|
6
|
+
)
|
|
7
|
+
from modules.{{ cookiecutter.module_name }}.application_contracts.services.order_approval_service import (
|
|
8
|
+
{{ cookiecutter.class_prefix }}ApprovalApplicationService,
|
|
9
|
+
)
|
|
10
|
+
from modules.{{ cookiecutter.module_name }}.application_contracts.services.order_management_service import (
|
|
11
|
+
{{ cookiecutter.class_prefix }}ManagementApplicationService,
|
|
12
|
+
)
|
|
13
|
+
from modules.{{ cookiecutter.module_name }}.application_contracts.services.order_query_service import (
|
|
14
|
+
{{ cookiecutter.class_prefix }}QueryApplicationService,
|
|
15
|
+
)
|
|
16
|
+
from modules.{{ cookiecutter.module_name }}.http_api.filters.export_filter import ExportFilter
|
|
17
|
+
from modules.{{ cookiecutter.module_name }}.http_api.routers.order_files import router as files_router
|
|
18
|
+
from modules.{{ cookiecutter.module_name }}.http_api.websockets.order_socket import router as realtime_router
|
|
19
|
+
|
|
20
|
+
from python_ddd_framework import (
|
|
21
|
+
AppModule,
|
|
22
|
+
ConfigureContext,
|
|
23
|
+
InitializeContext,
|
|
24
|
+
PostConfigureContext,
|
|
25
|
+
PostInitializeContext,
|
|
26
|
+
PreConfigureContext,
|
|
27
|
+
PreInitializeContext,
|
|
28
|
+
ShutdownContext,
|
|
29
|
+
)
|
|
5
30
|
from python_ddd_framework.fastapi import (
|
|
31
|
+
ApplicationServiceRouteOverride,
|
|
6
32
|
FastApiRealtimeModule,
|
|
7
33
|
contributes_routers,
|
|
8
34
|
exposes_application_services,
|
|
9
35
|
)
|
|
10
36
|
|
|
11
|
-
from ..application_contracts.module import {{ cookiecutter.class_prefix }}ApplicationContractsModule
|
|
12
|
-
from .files import ExportFilter
|
|
13
|
-
from .files import router as files_router
|
|
14
|
-
from .realtime import router as realtime_router
|
|
15
|
-
|
|
16
37
|
|
|
17
38
|
@contributes_routers(files_router, realtime_router)
|
|
18
39
|
@exposes_application_services(
|
|
19
40
|
{{ cookiecutter.class_prefix }}ApplicationContractsModule,
|
|
41
|
+
overrides=(
|
|
42
|
+
ApplicationServiceRouteOverride(
|
|
43
|
+
service={{ cookiecutter.class_prefix }}QueryApplicationService,
|
|
44
|
+
method={{ cookiecutter.class_prefix }}QueryApplicationService.get,
|
|
45
|
+
path="{{ cookiecutter.http_service_path }}/{id}",
|
|
46
|
+
operation_id="{{ cookiecutter.http_operation_prefix }}get",
|
|
47
|
+
),
|
|
48
|
+
ApplicationServiceRouteOverride(
|
|
49
|
+
service={{ cookiecutter.class_prefix }}QueryApplicationService,
|
|
50
|
+
method={{ cookiecutter.class_prefix }}QueryApplicationService.get_list,
|
|
51
|
+
path="{{ cookiecutter.http_service_path }}",
|
|
52
|
+
operation_id="{{ cookiecutter.http_operation_prefix }}get_list",
|
|
53
|
+
),
|
|
54
|
+
ApplicationServiceRouteOverride(
|
|
55
|
+
service={{ cookiecutter.class_prefix }}ManagementApplicationService,
|
|
56
|
+
method={{ cookiecutter.class_prefix }}ManagementApplicationService.create,
|
|
57
|
+
path="{{ cookiecutter.http_service_path }}",
|
|
58
|
+
operation_id="{{ cookiecutter.http_operation_prefix }}create",
|
|
59
|
+
),
|
|
60
|
+
ApplicationServiceRouteOverride(
|
|
61
|
+
service={{ cookiecutter.class_prefix }}ApprovalApplicationService,
|
|
62
|
+
method={{ cookiecutter.class_prefix }}ApprovalApplicationService.approve,
|
|
63
|
+
path="{{ cookiecutter.http_service_path }}/{id}/approve",
|
|
64
|
+
operation_id="{{ cookiecutter.http_operation_prefix }}approve",
|
|
65
|
+
),
|
|
66
|
+
ApplicationServiceRouteOverride(
|
|
67
|
+
service={{ cookiecutter.class_prefix }}ApprovalApplicationService,
|
|
68
|
+
method={{ cookiecutter.class_prefix }}ApprovalApplicationService.queue_approval,
|
|
69
|
+
path="{{ cookiecutter.http_service_path }}/{id}/queue-approval",
|
|
70
|
+
operation_id="{{ cookiecutter.http_operation_prefix }}queue_approval",
|
|
71
|
+
),
|
|
72
|
+
),
|
|
20
73
|
)
|
|
21
74
|
class {{ cookiecutter.class_prefix }}HttpApiModule(AppModule):
|
|
22
|
-
dependencies = (
|
|
75
|
+
dependencies = (FastApiRealtimeModule,)
|
|
76
|
+
|
|
77
|
+
def pre_configure(self, context: PreConfigureContext) -> None:
|
|
78
|
+
# 贡献影响后续注册的 PreOptions;此阶段不启动资源。
|
|
79
|
+
pass
|
|
23
80
|
|
|
24
81
|
def configure(self, context: ConfigureContext) -> None:
|
|
82
|
+
# 组装扩展:在这里注册 HTTP 层自己的 Filter 等依赖;新路由通过上方 Module 声明接入。
|
|
25
83
|
context.services.add_scoped(ExportFilter, scope=Scope.REQUEST)
|
|
84
|
+
|
|
85
|
+
def post_configure(self, context: PostConfigureContext) -> None:
|
|
86
|
+
# 读取已冻结 PreOptions,补充 Options 或覆盖已有服务注册。
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
async def pre_initialize(self, context: PreInitializeContext) -> None:
|
|
90
|
+
# 异步初始化前置条件;依赖模块在同一阶段先完成。
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
async def initialize(self, context: InitializeContext) -> None:
|
|
94
|
+
# 初始化本模块资源;不手工启动框架管理的 Worker 或 HostedService。
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
async def post_initialize(self, context: PostInitializeContext) -> None:
|
|
98
|
+
# 完成初始化收尾;应用此时尚未对外就绪。
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
async def shutdown(self, context: ShutdownContext) -> None:
|
|
102
|
+
# 逆依赖关闭本模块拥有的资源;返回前等待清理结束。
|
|
103
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""所属能力的实现或契约。"""
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
"""原生上传与流响应;业务查询在返回响应前完成,不跨网络发送持有事务。"""
|
|
2
|
-
|
|
3
1
|
import asyncio
|
|
4
2
|
import hashlib
|
|
5
3
|
import logging
|
|
6
|
-
from collections.abc import AsyncIterator
|
|
4
|
+
from collections.abc import AsyncIterator
|
|
7
5
|
from typing import Annotated
|
|
8
6
|
from uuid import UUID
|
|
9
7
|
|
|
10
8
|
from fastapi import File, Form, UploadFile
|
|
11
|
-
from
|
|
12
|
-
from
|
|
9
|
+
from modules.{{ cookiecutter.module_name }}.application_contracts.inputs.create_order import CreateOrder
|
|
10
|
+
from modules.{{ cookiecutter.module_name }}.application_contracts.services.order_management_service import (
|
|
11
|
+
{{ cookiecutter.class_prefix }}ManagementApplicationService,
|
|
12
|
+
)
|
|
13
|
+
from modules.{{ cookiecutter.module_name }}.application_contracts.services.order_query_service import (
|
|
14
|
+
{{ cookiecutter.class_prefix }}QueryApplicationService,
|
|
15
|
+
)
|
|
16
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import MODULE_NAME
|
|
17
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.permissions.order_permissions import (
|
|
18
|
+
ORDERS_READ,
|
|
19
|
+
ORDERS_WRITE,
|
|
20
|
+
)
|
|
21
|
+
from modules.{{ cookiecutter.module_name }}.http_api.filters.export_filter import ExportFilter
|
|
13
22
|
from starlette.responses import Response, StreamingResponse
|
|
14
23
|
|
|
15
|
-
from
|
|
16
|
-
from
|
|
24
|
+
from python_ddd_framework.authorization import authorize
|
|
25
|
+
from python_ddd_framework.fastapi import HttpRouter, service_filter
|
|
17
26
|
|
|
18
27
|
router = HttpRouter(use_api_prefix=True, prefix=f"/{MODULE_NAME}/files", permissions=(ORDERS_READ,))
|
|
19
28
|
|
|
20
29
|
|
|
21
|
-
class ExportFilter:
|
|
22
|
-
async def invoke(
|
|
23
|
-
self, context: ActionFilterContext, next_action: Callable[[], Awaitable[object]]
|
|
24
|
-
) -> object:
|
|
25
|
-
# Filter 的作用域只覆盖已绑定参数到响应准备;不假定流传输已经结束。
|
|
26
|
-
result = await next_action()
|
|
27
|
-
logging.getLogger(__name__).info("File response prepared")
|
|
28
|
-
return result
|
|
29
|
-
|
|
30
|
-
|
|
31
30
|
@router.post("/upload", status_code=201)
|
|
32
31
|
@authorize(ORDERS_WRITE)
|
|
33
32
|
async def upload(
|
|
34
|
-
service: {{ cookiecutter.class_prefix }}
|
|
33
|
+
service: {{ cookiecutter.class_prefix }}ManagementApplicationService,
|
|
35
34
|
title: Annotated[str, Form()],
|
|
36
35
|
file: Annotated[UploadFile, File()],
|
|
37
36
|
) -> dict[str, object]:
|
|
37
|
+
# 接口扩展:在这里解析上传内容并组装业务命令,交给应用服务处理;当前只演示创建订单及计算摘要。
|
|
38
38
|
# File/Form 使用框架读取期限额及原生 parser;UploadFile 资源由 REQUEST 关闭。
|
|
39
39
|
content = await file.read()
|
|
40
40
|
order = await service.create(CreateOrder(title=title))
|
|
@@ -47,7 +47,8 @@ async def upload(
|
|
|
47
47
|
|
|
48
48
|
@router.get("/{id}/download")
|
|
49
49
|
@service_filter(ExportFilter)
|
|
50
|
-
async def download(id: UUID, service: {{ cookiecutter.class_prefix }}
|
|
50
|
+
async def download(id: UUID, service: {{ cookiecutter.class_prefix }}QueryApplicationService) -> Response:
|
|
51
|
+
# 接口扩展:通过服务查询已授权的数据后,在这里转换为实际下载格式、文件名和媒体类型。
|
|
51
52
|
order = await service.get(id)
|
|
52
53
|
return Response(
|
|
53
54
|
order.model_dump_json(),
|
|
@@ -58,10 +59,12 @@ async def download(id: UUID, service: {{ cookiecutter.class_prefix }}Application
|
|
|
58
59
|
|
|
59
60
|
@router.get("/export/stream")
|
|
60
61
|
@service_filter(ExportFilter)
|
|
61
|
-
async def export(service: {{ cookiecutter.class_prefix }}
|
|
62
|
+
async def export(service: {{ cookiecutter.class_prefix }}QueryApplicationService) -> StreamingResponse:
|
|
63
|
+
# 接口扩展:这里准备导出数据,下面的 producer 负责编码输出;不要让业务事务跨越网络发送。
|
|
62
64
|
orders = await service.get_list()
|
|
63
65
|
|
|
64
66
|
async def produce() -> AsyncIterator[bytes]:
|
|
67
|
+
# 输出扩展:将 JSON 行替换为实际流格式;大数据分批读取须各自完成短 UoW 后再 yield。
|
|
65
68
|
try:
|
|
66
69
|
for order in orders:
|
|
67
70
|
yield (order.model_dump_json() + "\n").encode()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""所属能力的实现或契约。"""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends
|
|
4
|
+
from modules.{{ cookiecutter.module_name }}.application_contracts.services.order_query_service import (
|
|
5
|
+
{{ cookiecutter.class_prefix }}QueryApplicationService,
|
|
6
|
+
)
|
|
7
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import MODULE_NAME
|
|
8
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.messages.order_messages import (
|
|
9
|
+
OrderSnapshotItem,
|
|
10
|
+
OrderSnapshotMessage,
|
|
11
|
+
)
|
|
12
|
+
from modules.{{ cookiecutter.module_name }}.http_api.models.refresh_orders import RefreshOrders
|
|
13
|
+
from starlette.websockets import WebSocketDisconnect
|
|
14
|
+
|
|
15
|
+
from python_ddd_framework import FrameworkError
|
|
16
|
+
from python_ddd_framework.fastapi import WebSocketConnection, websocket_connection
|
|
17
|
+
|
|
18
|
+
router = APIRouter()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@router.websocket(f"/ws/{MODULE_NAME}")
|
|
22
|
+
async def orders_socket(
|
|
23
|
+
connection: Annotated[WebSocketConnection, Depends(websocket_connection)],
|
|
24
|
+
) -> None:
|
|
25
|
+
# 接口扩展:这里定义“查询快照 → 发送消息 → 接收下一条命令”的交互,替换消息 DTO 和业务调用。
|
|
26
|
+
# 每次业务操作仍经 connection.invoke 进入授权/事务管线,不在连接期间保留 Session 或聚合实例。
|
|
27
|
+
try:
|
|
28
|
+
while True:
|
|
29
|
+
orders = await connection.invoke({{ cookiecutter.class_prefix }}QueryApplicationService.get_list)
|
|
30
|
+
await connection.send(
|
|
31
|
+
OrderSnapshotMessage(
|
|
32
|
+
orders=tuple(
|
|
33
|
+
OrderSnapshotItem(
|
|
34
|
+
id=item.id, title=item.title, status=item.status, version=item.version
|
|
35
|
+
)
|
|
36
|
+
for item in orders
|
|
37
|
+
)
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
await connection.receive(RefreshOrders)
|
|
41
|
+
except WebSocketDisconnect:
|
|
42
|
+
return
|
|
43
|
+
except FrameworkError as error:
|
|
44
|
+
await connection.send_problem(error)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"""初始无业务 revision;执行 pddd db revision --module
|
|
1
|
+
"""初始无业务 revision;执行 pddd db revision --module orders 后人工审查。"""
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
from uuid import UUID
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import (
|
|
6
|
+
MODULE_NAME,
|
|
7
|
+
TITLE_MAX_LENGTH,
|
|
8
|
+
)
|
|
9
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.enums.order_status import OrderStatus
|
|
10
|
+
from modules.{{ cookiecutter.module_name }}.sqlalchemy.models.base import {{ cookiecutter.class_prefix }}Base
|
|
6
11
|
from sqlalchemy import Integer, String, Uuid
|
|
7
12
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
8
13
|
|
|
9
|
-
from
|
|
10
|
-
from . import {{ cookiecutter.class_prefix }}Base
|
|
14
|
+
from python_ddd_framework.sqlalchemy import AuditedEntityMixin
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
class OrderRow(AuditedEntityMixin, {{ cookiecutter.class_prefix }}Base):
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
"""provider 依赖领域契约;Host 显式选择此实现。"""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from modules.{{ cookiecutter.module_name }}.domain.module import {{ cookiecutter.class_prefix }}DomainModule
|
|
4
|
+
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import MODULE_NAME
|
|
5
|
+
from modules.{{ cookiecutter.module_name }}.sqlalchemy.models.base import {{ cookiecutter.class_prefix }}Base
|
|
6
|
+
|
|
7
|
+
from python_ddd_framework import (
|
|
8
|
+
AppModule,
|
|
9
|
+
ConfigureContext,
|
|
10
|
+
InitializeContext,
|
|
11
|
+
PostConfigureContext,
|
|
12
|
+
PostInitializeContext,
|
|
13
|
+
PreConfigureContext,
|
|
14
|
+
PreInitializeContext,
|
|
15
|
+
ShutdownContext,
|
|
16
|
+
)
|
|
4
17
|
from python_ddd_framework.sqlalchemy import (
|
|
5
18
|
SqlAlchemyModelRegistration,
|
|
6
19
|
SqlAlchemyPersistenceModule,
|
|
7
20
|
contributes_sqlalchemy_models,
|
|
8
21
|
)
|
|
9
22
|
|
|
10
|
-
from ..domain.module import {{ cookiecutter.class_prefix }}DomainModule
|
|
11
|
-
from ..domain_shared.definitions import MODULE_NAME
|
|
12
|
-
from .models import {{ cookiecutter.class_prefix }}Base
|
|
13
|
-
|
|
14
23
|
|
|
15
24
|
@contributes_sqlalchemy_models(
|
|
16
25
|
SqlAlchemyModelRegistration.from_package(
|
|
@@ -24,3 +33,31 @@ class {{ cookiecutter.class_prefix }}SqlAlchemyModule(AppModule):
|
|
|
24
33
|
dependencies = ({{ cookiecutter.class_prefix }}DomainModule, SqlAlchemyPersistenceModule)
|
|
25
34
|
|
|
26
35
|
scan_packages = (__package__ + ".repositories",)
|
|
36
|
+
|
|
37
|
+
def pre_configure(self, context: PreConfigureContext) -> None:
|
|
38
|
+
# 贡献影响后续注册的 PreOptions;此阶段不启动资源。
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def configure(self, context: ConfigureContext) -> None:
|
|
42
|
+
# 注册服务和普通 Options;业务执行留在所属能力。
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
def post_configure(self, context: PostConfigureContext) -> None:
|
|
46
|
+
# 读取已冻结 PreOptions,补充 Options 或覆盖已有服务注册。
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
async def pre_initialize(self, context: PreInitializeContext) -> None:
|
|
50
|
+
# 异步初始化前置条件;依赖模块在同一阶段先完成。
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
async def initialize(self, context: InitializeContext) -> None:
|
|
54
|
+
# 初始化本模块资源;不手工启动框架管理的 Worker 或 HostedService。
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
async def post_initialize(self, context: PostInitializeContext) -> None:
|
|
58
|
+
# 完成初始化收尾;应用此时尚未对外就绪。
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
async def shutdown(self, context: ShutdownContext) -> None:
|
|
62
|
+
# 逆依赖关闭本模块拥有的资源;返回前等待清理结束。
|
|
63
|
+
pass
|