python-ddd-framework 0.6.0__py3-none-any.whl → 0.7.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/authorization/checking.py +35 -0
- python_ddd_framework/developer_kit/generation.py +0 -9
- python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +29 -77
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +5 -72
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +1 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +2 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +8 -62
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +1 -2
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +12 -8
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +22 -10
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/src/host/main.py.jinja +4 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +40 -6
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +177 -30
- python_ddd_framework/fastapi/__init__.py +2 -0
- python_ddd_framework/fastapi/access.py +127 -0
- python_ddd_framework/fastapi/access_options.py +13 -0
- python_ddd_framework/fastapi/action.py +6 -0
- python_ddd_framework/fastapi/manual_action.py +33 -2
- python_ddd_framework/fastapi/module.py +2 -0
- python_ddd_framework/fastapi/realtime/authentication.py +21 -10
- python_ddd_framework/fastapi/realtime/options.py +0 -1
- python_ddd_framework/fastapi/realtime/runtime.py +41 -26
- python_ddd_framework/fastapi/request_context.py +10 -2
- python_ddd_framework/fastapi/routing.py +12 -5
- python_ddd_framework/fastapi/server.py +9 -0
- python_ddd_framework/fastapi/service_endpoints.py +5 -0
- python_ddd_framework/fastapi/streaming.py +95 -0
- python_ddd_framework/fastapi/transfer.py +138 -3
- python_ddd_framework/invocation/dispatcher.py +2 -24
- python_ddd_framework/invocation/scopes.py +5 -3
- python_ddd_framework/observability/formatting.py +1 -0
- python_ddd_framework/unit_of_work/manager.py +11 -2
- {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/METADATA +29 -8
- {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/RECORD +39 -86
- {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/WHEEL +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +0 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +0 -45
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +0 -8
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +0 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +0 -35
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +0 -5
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +0 -15
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +0 -28
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +0 -26
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +0 -9
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +0 -9
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +0 -23
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_integration_service.py.jinja +0 -71
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +0 -16
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +0 -16
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +0 -17
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/order_options.py.jinja +0 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +0 -65
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +0 -35
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +0 -41
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +0 -17
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +0 -6
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +0 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +0 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +0 -13
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +0 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +0 -12
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +0 -9
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +0 -12
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/order.py.jinja +0 -48
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +0 -13
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +0 -17
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +0 -22
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +0 -23
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/approval_settings.py.jinja +0 -17
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +0 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +0 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +0 -6
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +0 -11
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +0 -26
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +0 -13
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permission_provider.py.jinja +0 -19
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +0 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +0 -18
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +0 -15
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +0 -5
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/order_files.py.jinja +0 -76
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +0 -44
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/order_model.py.jinja +0 -29
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +0 -81
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +0 -19
- {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import hashlib
|
|
3
|
-
import logging
|
|
4
|
-
from collections.abc import AsyncIterator
|
|
5
|
-
from typing import Annotated
|
|
6
|
-
from uuid import UUID
|
|
7
|
-
|
|
8
|
-
from fastapi import File, Form, UploadFile
|
|
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
|
|
22
|
-
from starlette.responses import Response, StreamingResponse
|
|
23
|
-
|
|
24
|
-
from python_ddd_framework.authorization import authorize
|
|
25
|
-
from python_ddd_framework.fastapi import HttpRouter, service_filter
|
|
26
|
-
|
|
27
|
-
router = HttpRouter(use_api_prefix=True, prefix=f"/{MODULE_NAME}/files", permissions=(ORDERS_READ,))
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@router.post("/upload", status_code=201)
|
|
31
|
-
@authorize(ORDERS_WRITE)
|
|
32
|
-
async def upload(
|
|
33
|
-
service: {{ cookiecutter.class_prefix }}ManagementApplicationService,
|
|
34
|
-
title: Annotated[str, Form()],
|
|
35
|
-
file: Annotated[UploadFile, File()],
|
|
36
|
-
) -> dict[str, object]:
|
|
37
|
-
# 接口扩展:在这里解析上传内容并组装业务命令,交给应用服务处理;当前只演示创建订单及计算摘要。
|
|
38
|
-
# File/Form 使用框架读取期限额及原生 parser;UploadFile 资源由 REQUEST 关闭。
|
|
39
|
-
content = await file.read()
|
|
40
|
-
order = await service.create(CreateOrder(title=title))
|
|
41
|
-
return {
|
|
42
|
-
"order": order.model_dump(mode="json"),
|
|
43
|
-
"bytes": len(content),
|
|
44
|
-
"sha256": hashlib.sha256(content).hexdigest(),
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@router.get("/{id}/download")
|
|
49
|
-
@service_filter(ExportFilter)
|
|
50
|
-
async def download(id: UUID, service: {{ cookiecutter.class_prefix }}QueryApplicationService) -> Response:
|
|
51
|
-
# 接口扩展:通过服务查询已授权的数据后,在这里转换为实际下载格式、文件名和媒体类型。
|
|
52
|
-
order = await service.get(id)
|
|
53
|
-
return Response(
|
|
54
|
-
order.model_dump_json(),
|
|
55
|
-
media_type="application/json",
|
|
56
|
-
headers={"Content-Disposition": f'attachment; filename="{id}.json"'},
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@router.get("/export/stream")
|
|
61
|
-
@service_filter(ExportFilter)
|
|
62
|
-
async def export(service: {{ cookiecutter.class_prefix }}QueryApplicationService) -> StreamingResponse:
|
|
63
|
-
# 接口扩展:这里准备导出数据,下面的 producer 负责编码输出;不要让业务事务跨越网络发送。
|
|
64
|
-
orders = await service.get_list()
|
|
65
|
-
|
|
66
|
-
async def produce() -> AsyncIterator[bytes]:
|
|
67
|
-
# 输出扩展:将 JSON 行替换为实际流格式;大数据分批读取须各自完成短 UoW 后再 yield。
|
|
68
|
-
try:
|
|
69
|
-
for order in orders:
|
|
70
|
-
yield (order.model_dump_json() + "\n").encode()
|
|
71
|
-
await asyncio.sleep(0)
|
|
72
|
-
finally:
|
|
73
|
-
# 此示例只持有不可变 DTO,无悬挂 Session/文件;扩展外部资源时在此 shield 异步关闭。
|
|
74
|
-
logging.getLogger(__name__).info("Order export producer closed")
|
|
75
|
-
|
|
76
|
-
return StreamingResponse(produce(), media_type="application/x-ndjson")
|
|
@@ -1,44 +0,0 @@
|
|
|
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,29 +0,0 @@
|
|
|
1
|
-
"""修改模型后显式生成并审查迁移,不在 Host 启动时建表。"""
|
|
2
|
-
|
|
3
|
-
from uuid import UUID
|
|
4
|
-
|
|
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
|
|
11
|
-
from sqlalchemy import Integer, String, Uuid
|
|
12
|
-
from sqlalchemy.orm import Mapped, mapped_column
|
|
13
|
-
|
|
14
|
-
from python_ddd_framework.sqlalchemy import AuditedEntityMixin
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class OrderRow(AuditedEntityMixin, {{ cookiecutter.class_prefix }}Base):
|
|
18
|
-
__tablename__ = MODULE_NAME
|
|
19
|
-
id: Mapped[UUID] = mapped_column(Uuid, primary_key=True)
|
|
20
|
-
title: Mapped[str] = mapped_column(String(TITLE_MAX_LENGTH), nullable=False)
|
|
21
|
-
status: Mapped[str] = mapped_column(
|
|
22
|
-
String(max(len(item.value) for item in OrderStatus)), nullable=False
|
|
23
|
-
)
|
|
24
|
-
version: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
25
|
-
# SQLAlchemy 以行版本检查并发;聚合暂存版本与数据库提交版本保持一致。
|
|
26
|
-
__mapper_args__ = { # noqa: RUF012 -- SQLAlchemy 原生声明属性,基类不允许 ClassVar。
|
|
27
|
-
"version_id_col": version,
|
|
28
|
-
"version_id_generator": False,
|
|
29
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
"""仓储参与当前 UoW,成功暂存后由 operation 收集聚合事件。"""
|
|
2
|
-
|
|
3
|
-
from uuid import UUID
|
|
4
|
-
|
|
5
|
-
from modules.{{ cookiecutter.module_name }}.domain.entities.order import Order
|
|
6
|
-
from modules.{{ cookiecutter.module_name }}.domain.repositories.order_repository import OrderRepository
|
|
7
|
-
from modules.{{ cookiecutter.module_name }}.domain.value_objects.order_title import OrderTitle
|
|
8
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.enums.order_status import OrderStatus
|
|
9
|
-
from modules.{{ cookiecutter.module_name }}.sqlalchemy.models.order_model import OrderRow
|
|
10
|
-
|
|
11
|
-
from python_ddd_framework import (
|
|
12
|
-
OptimisticConcurrencyError, ResourceNotFoundError, EntityCreatedEvent, EntityUpdatedEvent,
|
|
13
|
-
)
|
|
14
|
-
from python_ddd_framework.sqlalchemy import SqlAlchemyRepository, SqlAlchemySessionProvider
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class _OrderRows(SqlAlchemyRepository[OrderRow, UUID]):
|
|
18
|
-
def _default_sorting(self) -> str:
|
|
19
|
-
return "created_at"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class SqlAlchemyOrderRepository(OrderRepository):
|
|
23
|
-
def __init__(self, provider: SqlAlchemySessionProvider) -> None:
|
|
24
|
-
self._provider = provider
|
|
25
|
-
self._rows = _OrderRows(provider, OrderRow)
|
|
26
|
-
|
|
27
|
-
async def find(self, id: UUID, include_details: bool = True) -> Order | None:
|
|
28
|
-
# 持久化扩展:这里查询本模块的表,再通过显式映射重建聚合;业务判断留给聚合/应用服务。
|
|
29
|
-
async with self._provider.operation("get"):
|
|
30
|
-
row = await self._rows.find(id, include_details)
|
|
31
|
-
return None if row is None else _order(row)
|
|
32
|
-
|
|
33
|
-
async def get(self, id: UUID, include_details: bool = True) -> Order:
|
|
34
|
-
order = await self.find(id, include_details)
|
|
35
|
-
if order is None:
|
|
36
|
-
raise ResourceNotFoundError("Order not found")
|
|
37
|
-
return order
|
|
38
|
-
|
|
39
|
-
async def get_list(self, include_details: bool = False) -> list[Order]:
|
|
40
|
-
async with self._provider.operation("get_list"):
|
|
41
|
-
return [_order(row) for row in await self._rows.get_list(include_details)]
|
|
42
|
-
|
|
43
|
-
async def get_count(self) -> int:
|
|
44
|
-
return await self._rows.get_count()
|
|
45
|
-
|
|
46
|
-
async def get_paged_list(self, skip_count: int, max_result_count: int,
|
|
47
|
-
sorting: str | None = None, include_details: bool = False) -> list[Order]:
|
|
48
|
-
async with self._provider.operation("get_paged_list"):
|
|
49
|
-
rows = await self._rows.get_paged_list(
|
|
50
|
-
skip_count, max_result_count, sorting, include_details
|
|
51
|
-
)
|
|
52
|
-
return [_order(row) for row in rows]
|
|
53
|
-
|
|
54
|
-
async def save(self, order: Order) -> None:
|
|
55
|
-
# 持久化扩展:把自己的聚合字段/子实体映射到 ORM;下面的版本核对和事件收集边界要保留。
|
|
56
|
-
# 这里只暂存改动,不自行 commit,避免绕开调用者的工作单元。
|
|
57
|
-
async with self._provider.operation(
|
|
58
|
-
"save", aggregate=order,
|
|
59
|
-
entity_event=EntityCreatedEvent(order) if order.version == 0 else EntityUpdatedEvent(order),
|
|
60
|
-
):
|
|
61
|
-
session = await self._provider.get_session()
|
|
62
|
-
row = await session.get(OrderRow, order.id)
|
|
63
|
-
if row is None:
|
|
64
|
-
if order.version:
|
|
65
|
-
raise OptimisticConcurrencyError
|
|
66
|
-
row = OrderRow(id=order.id)
|
|
67
|
-
session.add(row)
|
|
68
|
-
elif row.version != order.version:
|
|
69
|
-
raise OptimisticConcurrencyError
|
|
70
|
-
order.stage_next_version(order.version)
|
|
71
|
-
# 新增持久字段的赋值写在这里,同时修改模型、下方读取映射并生成迁移。
|
|
72
|
-
row.title, row.status, row.version = order.title, order.status.value, order.version
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def _order(row: OrderRow) -> Order:
|
|
76
|
-
# 映射扩展:在这里把数据库字段恢复成领域实体/值对象,不调用会再次产生创建事件的工厂。
|
|
77
|
-
# 驱动的 UUID 子类在边界恢复为原生 UUID,聚合事件只持有稳定领域值。
|
|
78
|
-
return Order(
|
|
79
|
-
UUID(bytes=row.id.bytes), OrderTitle(value=row.title),
|
|
80
|
-
status=OrderStatus(row.status), version=row.version
|
|
81
|
-
)
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"""模块内领域验收,不随生产 wheel 打包或参与服务扫描。"""
|
|
2
|
-
|
|
3
|
-
from uuid import uuid4
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
from modules.{{ cookiecutter.module_name }}.domain.entities.order import Order
|
|
7
|
-
from modules.{{ cookiecutter.module_name }}.domain.value_objects.order_title import OrderTitle
|
|
8
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.enums.order_status import OrderStatus
|
|
9
|
-
|
|
10
|
-
from python_ddd_framework.errors import BusinessError
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_order_approval_rejects_repeat():
|
|
14
|
-
order = Order.create(uuid4(), OrderTitle(value="Example order"))
|
|
15
|
-
order.approve()
|
|
16
|
-
assert order.status is OrderStatus.APPROVED
|
|
17
|
-
assert len(order.release_local_events()) == 2
|
|
18
|
-
with pytest.raises(BusinessError):
|
|
19
|
-
order.approve()
|
{python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|