python-ddd-framework 0.8.0__py3-none-any.whl → 0.10.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_ddd_framework/__init__.py +17 -3
- python_ddd_framework/application/builder.py +38 -32
- python_ddd_framework/application/runtime_hooks.py +5 -3
- python_ddd_framework/application/state.py +2 -0
- python_ddd_framework/auditing/sqlalchemy/mappings.py +29 -0
- python_ddd_framework/auditing/sqlalchemy/module.py +1 -2
- python_ddd_framework/auditing/sqlalchemy/store.py +23 -15
- python_ddd_framework/caching/unit_of_work.py +4 -0
- python_ddd_framework/consumer_docs/README.md +91 -0
- python_ddd_framework/consumer_docs/architecture.md +264 -0
- python_ddd_framework/consumer_docs/development.md +934 -0
- python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/README.md +13 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +18 -23
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +2 -4
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +33 -53
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +37 -18
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/pyproject.toml.jinja +15 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/src/host/module.py.jinja +0 -4
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +22 -145
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +39 -482
- python_ddd_framework/domain/__init__.py +5 -0
- python_ddd_framework/domain/aggregates.py +5 -35
- python_ddd_framework/domain/auditing.py +11 -0
- python_ddd_framework/domain/capabilities.py +39 -0
- python_ddd_framework/domain/entities.py +0 -2
- python_ddd_framework/entity_caching/__init__.py +5 -0
- python_ddd_framework/entity_caching/cache.py +95 -0
- python_ddd_framework/entity_caching/contracts.py +22 -0
- python_ddd_framework/entity_caching/module.py +9 -0
- python_ddd_framework/entity_caching/registration.py +131 -0
- python_ddd_framework/events/aggregate.py +6 -13
- python_ddd_framework/events/runtime.py +93 -27
- python_ddd_framework/extensions/__init__.py +2 -0
- python_ddd_framework/extensions/catalog.py +4 -0
- python_ddd_framework/extensions/contracts.py +12 -2
- python_ddd_framework/identity/application.py +2 -4
- python_ddd_framework/identity/domain_rules.py +4 -2
- python_ddd_framework/identity/dtos.py +19 -20
- python_ddd_framework/identity/management.py +14 -11
- python_ddd_framework/identity/models.py +28 -36
- python_ddd_framework/identity/seeding.py +2 -2
- python_ddd_framework/identity/sqlalchemy/mappings.py +132 -0
- python_ddd_framework/identity/sqlalchemy/module.py +1 -11
- python_ddd_framework/identity/sqlalchemy/repository.py +147 -178
- python_ddd_framework/identity/sqlalchemy/session_security.py +8 -9
- python_ddd_framework/identity/sqlalchemy/stores.py +110 -86
- python_ddd_framework/identity/stores.py +5 -5
- python_ddd_framework/invocation/runtime.py +6 -0
- python_ddd_framework/services/__init__.py +2 -1
- python_ddd_framework/services/composition.py +8 -0
- python_ddd_framework/services/convention.py +28 -1
- python_ddd_framework/services/repository.py +31 -29
- python_ddd_framework/services/runtime.py +4 -0
- python_ddd_framework/settings/__init__.py +1 -6
- python_ddd_framework/settings/application.py +4 -9
- python_ddd_framework/settings/catalog.py +4 -59
- python_ddd_framework/settings/management.py +3 -13
- python_ddd_framework/settings/manager.py +9 -33
- python_ddd_framework/settings/module.py +3 -9
- python_ddd_framework/settings/sqlalchemy/mappings.py +31 -0
- python_ddd_framework/settings/sqlalchemy/module.py +1 -2
- python_ddd_framework/settings/sqlalchemy/store.py +66 -45
- python_ddd_framework/settings/values.py +0 -8
- python_ddd_framework/sqlalchemy/__init__.py +8 -6
- python_ddd_framework/sqlalchemy/alembic_runtime/env.py +3 -0
- python_ddd_framework/sqlalchemy/auditing.py +112 -30
- python_ddd_framework/sqlalchemy/conventions.py +54 -0
- python_ddd_framework/sqlalchemy/event_origins.py +163 -0
- python_ddd_framework/sqlalchemy/extensions.py +81 -49
- python_ddd_framework/sqlalchemy/locking.py +22 -0
- python_ddd_framework/sqlalchemy/mapping.py +53 -0
- python_ddd_framework/sqlalchemy/metadata.py +27 -47
- python_ddd_framework/sqlalchemy/metadata_builder.py +26 -20
- python_ddd_framework/sqlalchemy/metadata_fingerprint.py +4 -1
- python_ddd_framework/sqlalchemy/metadata_ownership.py +1 -1
- python_ddd_framework/sqlalchemy/migration.py +3 -5
- python_ddd_framework/sqlalchemy/migration_rendering.py +25 -0
- python_ddd_framework/sqlalchemy/module.py +56 -3
- python_ddd_framework/sqlalchemy/module_migration.py +4 -4
- python_ddd_framework/sqlalchemy/repository.py +139 -64
- python_ddd_framework/sqlalchemy/repository_binding.py +152 -0
- python_ddd_framework/sqlalchemy/session_provider.py +16 -21
- python_ddd_framework/sqlalchemy/tracking.py +231 -0
- python_ddd_framework/sqlalchemy/types.py +25 -0
- python_ddd_framework/sqlalchemy/unit_of_work.py +110 -6
- python_ddd_framework/unit_of_work/contracts.py +13 -6
- python_ddd_framework/unit_of_work/lifecycle.py +3 -0
- python_ddd_framework/unit_of_work/manager.py +17 -4
- python_ddd_framework-0.10.1.dist-info/METADATA +363 -0
- {python_ddd_framework-0.8.0.dist-info → python_ddd_framework-0.10.1.dist-info}/RECORD +95 -85
- {python_ddd_framework-0.8.0.dist-info → python_ddd_framework-0.10.1.dist-info}/WHEEL +1 -1
- python_ddd_framework-0.10.1.dist-info/licenses/LICENSE +21 -0
- python_ddd_framework/auditing/sqlalchemy/models.py +0 -31
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +0 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/base.py.jinja +0 -7
- python_ddd_framework/identity/sqlalchemy/models.py +0 -104
- python_ddd_framework/settings/changes.py +0 -14
- python_ddd_framework/settings/declarations.py +0 -18
- python_ddd_framework/settings/handlers.py +0 -43
- python_ddd_framework/settings/notifications.py +0 -41
- python_ddd_framework/settings/refresh.py +0 -140
- python_ddd_framework/settings/refresh_module.py +0 -52
- python_ddd_framework/settings/sqlalchemy/models.py +0 -36
- python_ddd_framework-0.8.0.dist-info/METADATA +0 -603
- python_ddd_framework-0.8.0.dist-info/licenses/LICENSE +0 -7
- /python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/{models → mappings}/__init__.py.jinja +0 -0
- {python_ddd_framework-0.8.0.dist-info → python_ddd_framework-0.10.1.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.8.0.dist-info → python_ddd_framework-0.10.1.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
python_ddd_framework/__init__.py
CHANGED
|
@@ -106,7 +106,16 @@ from .diagnostics import (
|
|
|
106
106
|
SourceLocation,
|
|
107
107
|
)
|
|
108
108
|
from .distributed_lock import DistributedLock, DistributedLockError, DistributedLockOptions
|
|
109
|
-
from .domain import
|
|
109
|
+
from .domain import (
|
|
110
|
+
AggregateRoot,
|
|
111
|
+
DomainService,
|
|
112
|
+
Entity,
|
|
113
|
+
GeneratesLocalEvents,
|
|
114
|
+
HasVersion,
|
|
115
|
+
OptimisticConcurrencyError,
|
|
116
|
+
ValueObject,
|
|
117
|
+
)
|
|
118
|
+
from .entity_caching import EntityCache, EntityCachingModule, add_entity_cache
|
|
110
119
|
from .errors import (
|
|
111
120
|
ApplicationRuntimeError,
|
|
112
121
|
ApplicationStartError,
|
|
@@ -263,6 +272,7 @@ from .services import (
|
|
|
263
272
|
ProviderContributionView,
|
|
264
273
|
RegistrationDecision,
|
|
265
274
|
RegistrationKind,
|
|
275
|
+
Repository,
|
|
266
276
|
RepositoryContract,
|
|
267
277
|
ScopedDependency,
|
|
268
278
|
ServiceCatalog,
|
|
@@ -284,7 +294,6 @@ from .settings import (
|
|
|
284
294
|
SettingManager,
|
|
285
295
|
SettingProvider,
|
|
286
296
|
)
|
|
287
|
-
from .settings.declarations import SettingHandlers
|
|
288
297
|
from .settings.module import SettingsModule
|
|
289
298
|
from .unit_of_work import (
|
|
290
299
|
UnitOfWork,
|
|
@@ -397,6 +406,8 @@ __all__ = (
|
|
|
397
406
|
"DuplicateModuleIdentityError",
|
|
398
407
|
"DuplicateOptionDefinitionError",
|
|
399
408
|
"Entity",
|
|
409
|
+
"EntityCache",
|
|
410
|
+
"EntityCachingModule",
|
|
400
411
|
"EntityChangedEvent",
|
|
401
412
|
"EntityCreatedEvent",
|
|
402
413
|
"EntityDeletedEvent",
|
|
@@ -413,7 +424,9 @@ __all__ = (
|
|
|
413
424
|
"ExtensionsModule",
|
|
414
425
|
"FailureRecord",
|
|
415
426
|
"FrameworkError",
|
|
427
|
+
"GeneratesLocalEvents",
|
|
416
428
|
"GraphInvariantError",
|
|
429
|
+
"HasVersion",
|
|
417
430
|
"HostedService",
|
|
418
431
|
"HostedServiceCatalog",
|
|
419
432
|
"HostedServiceContext",
|
|
@@ -511,6 +524,7 @@ __all__ = (
|
|
|
511
524
|
"RegistrationConflictError",
|
|
512
525
|
"RegistrationDecision",
|
|
513
526
|
"RegistrationKind",
|
|
527
|
+
"Repository",
|
|
514
528
|
"RepositoryContract",
|
|
515
529
|
"ResourceConflictError",
|
|
516
530
|
"ResourceNotFoundError",
|
|
@@ -528,7 +542,6 @@ __all__ = (
|
|
|
528
542
|
"SettingDefinitionContext",
|
|
529
543
|
"SettingDefinitionError",
|
|
530
544
|
"SettingDefinitionProvider",
|
|
531
|
-
"SettingHandlers",
|
|
532
545
|
"SettingManagementAccess",
|
|
533
546
|
"SettingManager",
|
|
534
547
|
"SettingProvider",
|
|
@@ -551,6 +564,7 @@ __all__ = (
|
|
|
551
564
|
"UnknownOptionsError",
|
|
552
565
|
"ValidationEnabled",
|
|
553
566
|
"ValueObject",
|
|
567
|
+
"add_entity_cache",
|
|
554
568
|
"add_interceptor",
|
|
555
569
|
"allow_anonymous",
|
|
556
570
|
"application_service",
|
|
@@ -97,38 +97,44 @@ class ApplicationBuilder:
|
|
|
97
97
|
journal=_DiagnosticJournal(),
|
|
98
98
|
completed_stages={descriptor.key: [] for descriptor in registry.dependency_order},
|
|
99
99
|
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
100
|
+
# 组装中的原生资源可能早于容器创建;失败回滚和成功后的运行时清理共用一个 owner。
|
|
101
|
+
with root.composition_resources:
|
|
102
|
+
_CompositionLifecycle(root).run()
|
|
103
|
+
application = Application(root)
|
|
104
|
+
service_runtime = _ServiceRuntimeBuilder(
|
|
105
|
+
registry,
|
|
106
|
+
root.service_registry,
|
|
107
|
+
scan_packages,
|
|
108
|
+
discoveries,
|
|
109
|
+
root.configuration,
|
|
110
|
+
root.require_options(),
|
|
111
|
+
application,
|
|
112
|
+
_ApplicationBuildSpec(
|
|
113
|
+
self._startup_module,
|
|
114
|
+
self._environment,
|
|
115
|
+
self._base_path,
|
|
116
|
+
self._env_prefix,
|
|
117
|
+
self._cli_args,
|
|
118
|
+
self._dotenv_path,
|
|
119
|
+
dict(self._explicit_overrides),
|
|
120
|
+
Path.cwd().resolve(),
|
|
121
|
+
),
|
|
122
|
+
root.composition_resources,
|
|
123
|
+
).build()
|
|
124
|
+
root.service_catalog = service_runtime.catalog
|
|
125
|
+
root.container = service_runtime.container
|
|
126
|
+
participants: list[RuntimeParticipant] = []
|
|
127
|
+
for descriptor in registry.dependency_order:
|
|
128
|
+
participant = descriptor.instance.runtime
|
|
129
|
+
if participant is not None:
|
|
130
|
+
if not isinstance(participant, RuntimeParticipant):
|
|
131
|
+
raise TypeError(
|
|
132
|
+
f"{descriptor.key}.runtime must be RuntimeParticipant or None"
|
|
133
|
+
)
|
|
134
|
+
participants.append(participant)
|
|
135
|
+
root.runtimes = tuple(participants)
|
|
136
|
+
root.composition_resources = root.composition_resources.pop_all()
|
|
137
|
+
return application
|
|
132
138
|
|
|
133
139
|
|
|
134
140
|
def _validate_environment(environment: str) -> str:
|
|
@@ -83,11 +83,9 @@ class _RuntimeLifecycle:
|
|
|
83
83
|
exception: BaseException | None,
|
|
84
84
|
) -> tuple[ContainerCleanupError, ...]:
|
|
85
85
|
container = self._root.container
|
|
86
|
-
if container is None:
|
|
87
|
-
return ()
|
|
88
86
|
failures: list[ContainerCleanupError] = []
|
|
89
87
|
try:
|
|
90
|
-
while
|
|
88
|
+
while container is not None:
|
|
91
89
|
try:
|
|
92
90
|
await container.close(exception)
|
|
93
91
|
except BaseException as original_error:
|
|
@@ -99,6 +97,10 @@ class _RuntimeLifecycle:
|
|
|
99
97
|
finally:
|
|
100
98
|
# 只在 native close 已穷尽后撤销引用;stop 后不再暴露可解析容器。
|
|
101
99
|
self._root.container = None
|
|
100
|
+
try:
|
|
101
|
+
self._root.composition_resources.close()
|
|
102
|
+
except BaseException as original_error:
|
|
103
|
+
failures.append(self._container_cleanup_error(original_error))
|
|
102
104
|
return tuple(failures)
|
|
103
105
|
|
|
104
106
|
def _container_cleanup_error(
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
from collections.abc import Callable
|
|
7
|
+
from contextlib import ExitStack
|
|
7
8
|
from dataclasses import dataclass, field
|
|
8
9
|
from typing import assert_never
|
|
9
10
|
from uuid import UUID
|
|
@@ -41,6 +42,7 @@ class _ApplicationStateRoot:
|
|
|
41
42
|
options: OptionsSnapshot | None = None
|
|
42
43
|
service_catalog: ServiceCatalog | None = None
|
|
43
44
|
container: AsyncContainer | None = None
|
|
45
|
+
composition_resources: ExitStack = field(default_factory=ExitStack)
|
|
44
46
|
state: ApplicationState = ApplicationState.COMPOSED
|
|
45
47
|
transition_token: object | None = None
|
|
46
48
|
runtime_touched: set[ModuleKey] = field(default_factory=set)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""审计记录使用原生表;事件记录到日志字段的业务投影归 AuditSink。"""
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import BigInteger, Boolean, Column, DateTime, Integer, MetaData, String, Table
|
|
4
|
+
from sqlalchemy.orm import registry
|
|
5
|
+
|
|
6
|
+
from ...sqlalchemy import NativeUuid, sqlalchemy_mapping
|
|
7
|
+
|
|
8
|
+
AUDIT_TABLE = "audit_logs"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@sqlalchemy_mapping
|
|
12
|
+
def configure_auditing(mapper_registry: registry, metadata: MetaData) -> None:
|
|
13
|
+
Table(
|
|
14
|
+
AUDIT_TABLE,
|
|
15
|
+
metadata,
|
|
16
|
+
Column("audit_id", NativeUuid(), primary_key=True),
|
|
17
|
+
Column("occurred_at", DateTime(timezone=True), nullable=False),
|
|
18
|
+
Column("kind", String(32), nullable=False, index=True),
|
|
19
|
+
Column("user_id", NativeUuid(), nullable=True, index=True),
|
|
20
|
+
Column("session_id", NativeUuid(), nullable=True),
|
|
21
|
+
Column("correlation_id", String(128), nullable=False, index=True),
|
|
22
|
+
Column("route_template", String(512), nullable=True),
|
|
23
|
+
Column("contract", String(512), nullable=True),
|
|
24
|
+
Column("method", String(128), nullable=True),
|
|
25
|
+
Column("status_code", Integer, nullable=True),
|
|
26
|
+
Column("succeeded", Boolean, nullable=True),
|
|
27
|
+
Column("duration_ns", BigInteger, nullable=False),
|
|
28
|
+
Column("exception_type", String(256), nullable=True),
|
|
29
|
+
)
|
|
@@ -12,13 +12,12 @@ from ...sqlalchemy import (
|
|
|
12
12
|
contributes_sqlalchemy_models,
|
|
13
13
|
)
|
|
14
14
|
from ..contracts import AuditSink
|
|
15
|
-
from .models import AuditingBase
|
|
16
15
|
from .store import SqlAlchemyAuditSink
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
@contributes_sqlalchemy_models(
|
|
20
19
|
SqlAlchemyModelRegistration(
|
|
21
|
-
|
|
20
|
+
mappings_package=__package__ + ".mappings",
|
|
22
21
|
migrations_package="python_ddd_framework.auditing.sqlalchemy.migrations",
|
|
23
22
|
branch_label="auditing",
|
|
24
23
|
)
|
|
@@ -4,21 +4,29 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from uuid import uuid4
|
|
6
6
|
|
|
7
|
-
from
|
|
8
|
-
|
|
7
|
+
from sqlalchemy import insert
|
|
8
|
+
|
|
9
|
+
from ...sqlalchemy import SqlAlchemyModelCatalog, SqlAlchemySessionProvider
|
|
10
|
+
from ...unit_of_work import UnitOfWorkManager
|
|
9
11
|
from ..contracts import ApplicationServiceAuditRecord, RequestAuditRecord
|
|
10
|
-
from .
|
|
12
|
+
from . import mappings
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class SqlAlchemyAuditSink:
|
|
14
|
-
__slots__ = ("
|
|
16
|
+
__slots__ = ("_manager", "_provider", "_table")
|
|
15
17
|
|
|
16
|
-
def __init__(
|
|
17
|
-
self
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
manager: UnitOfWorkManager,
|
|
21
|
+
provider: SqlAlchemySessionProvider,
|
|
22
|
+
catalog: SqlAlchemyModelCatalog,
|
|
23
|
+
) -> None:
|
|
24
|
+
self._manager, self._provider = manager, provider
|
|
25
|
+
self._table = catalog.metadata_for(mappings.__name__).tables[mappings.AUDIT_TABLE]
|
|
18
26
|
|
|
19
27
|
async def write_request(self, record: RequestAuditRecord) -> None:
|
|
20
28
|
await self._write(
|
|
21
|
-
|
|
29
|
+
dict(
|
|
22
30
|
audit_id=uuid4(),
|
|
23
31
|
occurred_at=record.occurred_at,
|
|
24
32
|
kind="request",
|
|
@@ -37,7 +45,7 @@ class SqlAlchemyAuditSink:
|
|
|
37
45
|
|
|
38
46
|
async def write_application_service(self, record: ApplicationServiceAuditRecord) -> None:
|
|
39
47
|
await self._write(
|
|
40
|
-
|
|
48
|
+
dict(
|
|
41
49
|
audit_id=uuid4(),
|
|
42
50
|
occurred_at=record.occurred_at,
|
|
43
51
|
kind="application_service",
|
|
@@ -54,10 +62,10 @@ class SqlAlchemyAuditSink:
|
|
|
54
62
|
)
|
|
55
63
|
)
|
|
56
64
|
|
|
57
|
-
async def _write(self,
|
|
58
|
-
async with
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
async def _write(self, values: dict[str, object]) -> None:
|
|
66
|
+
async with self._manager.begin(
|
|
67
|
+
requires_new=True, connection_name="default", is_transactional=True
|
|
68
|
+
) as work:
|
|
69
|
+
session = await self._provider.get_session()
|
|
70
|
+
await session.execute(insert(self._table).values(**values))
|
|
71
|
+
await work.complete()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Framework consumer guide
|
|
2
|
+
|
|
3
|
+
These resources belong to the installed `python-ddd-framework` distribution. Use the Python package `python_ddd_framework` in the application's own backend environment; the repository checkout, global CLI, and generated-template version are not substitutes for that artifact. The application's `docs/development.md#framework-documentation` owns environment lookup and setup failures.
|
|
4
|
+
|
|
5
|
+
Read the capability index, then the relevant **contract and usage** together. Installing dependencies, composing Modules, starting resources, and readiness are separate facts. Examples describe framework use; they do not authorize application business rules, dependencies, tests, or infrastructure changes.
|
|
6
|
+
|
|
7
|
+
<a id="capabilities"></a>
|
|
8
|
+
|
|
9
|
+
## Capabilities
|
|
10
|
+
|
|
11
|
+
Each row identifies supported behavior, its public owner, both reading destinations, and an executable reference in the [same-version Demo](#demo). Demo paths are relative to its backend; bare test names and business/ paths are under tests/integration. The links to installed source are evidence for the entry points, not instructions to modify the installation. This is a navigation index, not a runtime module registry; declarations and metadata still own activation.
|
|
12
|
+
|
|
13
|
+
| Task / capability | Public entry or owner | Contract | Usage and activation | Demo source / activation / verification |
|
|
14
|
+
| --- | --- | --- | --- | --- |
|
|
15
|
+
| Compose an application; start and stop resources | [ApplicationBuilder](../application/__init__.py), [AppModule and declarations](../modularity/__init__.py) | [Ownership and lifecycle](architecture.md#configuration-and-lifecycle) | [Modules and lifecycle](development.md#modules-and-lifecycle) | `src/host/module.py`; start Web or Worker; `tests/integration/test_reference_app.py` |
|
|
16
|
+
| Bind configuration; configure composition before startup | [Configuration](../configuration/__init__.py), [Options and PreOptions](../options/__init__.py) | [Configuration](architecture.md#configuration-and-lifecycle) | [Options](development.md#options-and-settings) | `src/modules/orders/application/options`; local YAML and CLI overrides; `business/test_http_capabilities.py` |
|
|
17
|
+
| Register, inject, replace, or multi-bind ordinary services | [Services](../services/__init__.py), native Dishka | [Scopes and calls](architecture.md#calls-scopes-and-transactions) | [DI and replacement](development.md#dependency-injection) | `src/modules/orders/application/services`; Module scan and native DI; `business/test_background_business_di.py` |
|
|
18
|
+
| Implement a use case, page results, or call an internal service | [Application services](../application_services/__init__.py), [invocation](../invocation/entrypoints.py) | [Contracts and call boundaries](architecture.md#business-module-boundaries) | [Services and permissions](development.md#services-and-permissions) | `src/modules/orders/application/services`; authenticated order requests; `business/test_business_ddd.py` |
|
|
19
|
+
| Validate inputs; expose safe business errors | [Validation](../validation/__init__.py), [errors](../errors/__init__.py) | [Validation and errors](architecture.md#validation-and-errors) | [Validation and errors](development.md#validation-and-errors) | `src/modules/orders/domain/value_objects`; invalid order inputs; `business/test_boundary_business.py` |
|
|
20
|
+
| Model aggregates and values; persist and query them | [Domain](../domain/__init__.py), [SQLAlchemy](../sqlalchemy/__init__.py) | [Domain and persistence](architecture.md#calls-scopes-and-transactions) | [Repositories, mappings and migrations](development.md#persistence-and-migrations) | `src/modules/orders/sqlalchemy`; explicit migrate and Seed; `business/test_business_ddd.py` |
|
|
21
|
+
| Define transaction boundaries and post-commit work | [UnitOfWorkManager](../unit_of_work/__init__.py) | [Unit of work](architecture.md#unit-of-work) | [Unit of work](development.md#unit-of-work) | `src/modules/orders/application/services`; managed service calls; `business/test_domain_services_events.py` |
|
|
22
|
+
| Handle local domain changes; contribute seed data | [Events](../events/__init__.py), [seeding](../seeding/__init__.py) | [Event values and transactions](architecture.md#calls-scopes-and-transactions) | [Events](development.md#events-and-background-work), [seed procedure](development.md#persistence-and-migrations) | `src/modules/orders/application/event_handlers`; committed changes; `business/test_domain_services_events.py` |
|
|
23
|
+
| Authenticate; manage users, roles, permissions and sessions | [Identity](../identity/__init__.py), [authorization](../authorization/__init__.py) | [Identity and authorization](architecture.md#identity-and-authorization) | [Identity](development.md#identity-management), [service authorization](development.md#services-and-permissions) | `src/modules/customer`; migrate Identity and extension, then log in; `test_enterprise_e2e.py` |
|
|
24
|
+
| Expose HTTP, filters, uploads and downloads | [FastAPI integration](../fastapi/__init__.py) | [HTTP boundaries](architecture.md#http-and-files) | [HTTP and files](development.md#http-files-and-real-time-communication) | `src/modules/orders/http_api`; authenticated HTTP; `business/test_http_pipeline.py` |
|
|
25
|
+
| Stream HTTP output with continuing access checks | [HttpRouter and native FastAPI](../fastapi/__init__.py) | [Streaming](architecture.md#http-streaming-and-continuous-access) | [Complete generator example](development.md#http-generators-and-continuous-access) | `src/modules/orders/http_api/routers`; stream routes; `test_http_streaming.py` |
|
|
26
|
+
| Deliver typed online WebSocket messages | [Realtime](../realtime/__init__.py), [FastAPI realtime](../fastapi/realtime/__init__.py) | [Online delivery](architecture.md#background-work-and-external-boundaries) | [Realtime](development.md#realtime-and-notifications) | `src/modules/orders/http_api/websockets`; connect authenticated socket; `business/test_http_capabilities.py` |
|
|
27
|
+
| Publish typed notifications | [Notifications](../notifications/__init__.py) | [Notification delivery](architecture.md#notifications) | [Notifications](development.md#realtime-and-notifications) | `src/host/module.py` selects RedisNotificationsModule; `test_notifications.py` |
|
|
28
|
+
| Extend DTOs and supported entities; own extension migrations | [Extensions](../extensions/__init__.py), [SQLAlchemy extensions](../sqlalchemy/__init__.py) | [Extension ownership](architecture.md#extension-ownership) | [Extension recipe](development.md#extend-reusable-modules) | `src/modules/customer`; extension migration after Identity; `test_model_extensions.py` |
|
|
29
|
+
| Cache typed values and defer writes until commit | [DistributedCache](../caching/__init__.py) | [Cache and transaction limits](architecture.md#calls-scopes-and-transactions) | [Caching](development.md#events-and-background-work) | `src/modules/orders/application/caching/statistics_cache.py` and `background_jobs/order_statistics/handler.py`; `tests/integration/test_slice3_data_e2e.py` |
|
|
30
|
+
| Cache read-only entities with repository fallback and CRUD invalidation | [EntityCache and registration](../entity_caching/__init__.py) | [Entity caching](architecture.md#entity-caching) | [Registration and queries](development.md#entity-caching) | `src/modules/orders/application/module.py` and `services/order_query_service.py`; `tests/integration/test_entity_caching.py` |
|
|
31
|
+
| Read and conditionally change global runtime settings | [Settings](../settings/__init__.py) | [Settings ownership](architecture.md#global-settings-ownership) | [Definitions, reads and writes](development.md#global-settings) | `src/modules/orders/domain/settings`; change approval setting with its version; `business/test_boundary_business.py` |
|
|
32
|
+
| Run durable jobs, schedules and periodic workers; manage execution | [Background execution](../background_execution/__init__.py), [jobs](../background_jobs/__init__.py), [workers](../background_workers/__init__.py) | [Execution boundaries](architecture.md#background-work-and-external-boundaries) | [Background execution](development.md#events-and-background-work) | `src/modules/orders/application/background_jobs` and `background_workers`; execution profile/modes; `business/test_recovery_capabilities.py` |
|
|
33
|
+
| Own an SDK, thread or long-lived resource | [Hosted services](../hosted_services/__init__.py) | [Shutdown and ownership](architecture.md#background-work-and-external-boundaries) | [Hosted services](development.md#hosted-services) | `src/modules/orders/application/hosted_services`; declared HostedServices; `business/test_background_business_di.py` |
|
|
34
|
+
| Transfer bounded in-process messages and preserve accepted work | [Messaging](../messaging/__init__.py) | [Message lifecycle](architecture.md#configuration-and-lifecycle) | [Channels and handlers](development.md#in-process-messages) | `src/modules/orders/application/hosted_services/observation_module.py`; explicitly add ObservationModule to Host dependencies; `test_reference_app.py` |
|
|
35
|
+
| Acquire a business lease across processes | [DistributedLock](../distributed_lock/__init__.py), [Redis provider](../redis/__init__.py) | [Lease limits](architecture.md#distributed-locks) | [Acquisition and cleanup](development.md#distributed-locks) | `src/modules/orders/application/background_workers/order_maintenance_worker.py`; selected Redis provider; `test_distributed_lock.py` |
|
|
36
|
+
| Audit operations and entities | [Auditing](../auditing/__init__.py) | [Auditing](architecture.md#auditing-and-observability) | [Auditing](development.md#auditing-and-observability) | `src/host/module.py` selects auditing; managed mutations; `test_enterprise_e2e.py` |
|
|
37
|
+
| Inspect composition; configure structured logs and tracing | [Diagnostics](../diagnostics/__init__.py), [observability](../observability/__init__.py) | [Observation boundaries](architecture.md#auditing-and-observability) | [Inspection](development.md#inspect-configuration), [logging and tracing](development.md#auditing-and-observability) | `src/host/module.py` selects logging/tracing; enable export in local configuration when needed; `test_observability.py` |
|
|
38
|
+
| Test an application using its real builder and typed contracts | [TestApplication](../testing/__init__.py) | [Testing boundary](architecture.md#testing) | [Test lifecycle](development.md#testing), [verification](development.md#verification) | `tests/integration/business/test_domain_services_events.py`; real builder plus FrameworkTestApplication |
|
|
39
|
+
| Generate an application/module; operate and deploy a Host | [CLI](../cli/__init__.py), [server](../fastapi/server.py) | [Delivery ownership](architecture.md#documentation-ownership) | [CLI](development.md#cli-operations), [database setup](development.md#initialize-the-database), [deployment](development.md#deployment) | `src/host/main.py`, `worker.py`, `migrate.py`; Demo setup; repository `tests/cli_delivery` |
|
|
40
|
+
|
|
41
|
+
Choose a capability from the confirmed business need, not from a directory name. Read [implementation workflow](development.md#implementation-workflow) before writing code, and [ownership](architecture.md#ownership-and-dependencies) before changing boundaries. If a capability appears missing, check the relevant installed public API and formal extension point, identify the exact missing behavior, and report it before proposing new infrastructure. Reading every implementation file is not required.
|
|
42
|
+
|
|
43
|
+
<a id="migration-notes"></a>
|
|
44
|
+
|
|
45
|
+
## Upgrade reference
|
|
46
|
+
|
|
47
|
+
The framework's root README owns the current installation and upgrade procedure. That exact README is carried in this distribution's metadata; there is no separately maintained copy here. In the application's backend environment, use the same project interpreter as document lookup with `-I -B -c` and the following Python code:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from email.parser import Parser
|
|
51
|
+
from importlib.metadata import distribution
|
|
52
|
+
|
|
53
|
+
installed = distribution("python-ddd-framework")
|
|
54
|
+
metadata_text = installed.read_text("METADATA")
|
|
55
|
+
if metadata_text is None:
|
|
56
|
+
raise RuntimeError("The installed framework has no METADATA resource")
|
|
57
|
+
print(Parser().parsestr(metadata_text).get_payload())
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Before an upgrade, read the target release's README and capability guides from its actual release artifact or versioned release reference. The installed metadata describes the installed version, not a future target. Check required capabilities, enablement conditions, public contracts and application-specific data migration needs. Upgrades change dependencies and package resources, not application Markdown or business code. Follow the application's upgrade procedure and deliberately adapt affected project documentation. A guidance or source lookup is never permission to upgrade, regenerate, migrate data, or edit installed files.
|
|
61
|
+
|
|
62
|
+
<a id="demo"></a>
|
|
63
|
+
|
|
64
|
+
## Obtain the same-version Demo
|
|
65
|
+
|
|
66
|
+
The framework sdist includes `examples/framework_demo`, a complete MIT application with Web and Worker entry points, sample configuration, module-owned migrations and Seed, and the integration suite. It is not included in the runtime wheel and does not require the private repository. Its README contains independent setup and startup instructions; pytest is not needed to run the application. Internal algorithm/failure branches remain covered by framework unit tests rather than invented business endpoints.
|
|
67
|
+
|
|
68
|
+
In your application's environment, run this standard-library code with the same interpreter used for installed documentation lookup. It resolves the **installed version's** public PyPI sdist, verifies the index SHA-256, and saves it without overwriting an existing file:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import hashlib
|
|
72
|
+
import json
|
|
73
|
+
from importlib.metadata import version
|
|
74
|
+
from pathlib import Path
|
|
75
|
+
from urllib.request import urlopen
|
|
76
|
+
|
|
77
|
+
installed = version("python-ddd-framework")
|
|
78
|
+
with urlopen(f"https://pypi.org/pypi/python-ddd-framework/{installed}/json", timeout=30) as response:
|
|
79
|
+
release = json.load(response)
|
|
80
|
+
source, = (file for file in release["urls"] if file["packagetype"] == "sdist")
|
|
81
|
+
with urlopen(source["url"], timeout=60) as response:
|
|
82
|
+
content = response.read()
|
|
83
|
+
if hashlib.sha256(content).hexdigest() != source["digests"]["sha256"]:
|
|
84
|
+
raise RuntimeError("The sdist hash does not match PyPI")
|
|
85
|
+
destination = Path(source["filename"]).name
|
|
86
|
+
with open(destination, "xb") as output:
|
|
87
|
+
output.write(content)
|
|
88
|
+
print(destination)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Extract the archive into a new directory and open its `examples/framework_demo/README.md`. If that exact version is not published or has no sdist, stop and obtain the matching validated distribution from its owner; do not silently substitute main, a private checkout or another version. Downloading does not authorize infrastructure changes. The Demo pins the same framework version and ships configuration examples only; provide local connections and secrets before migration, Seed or startup. Framework self-authored code is MIT; third-party notices remain attached to their owners. Templates do not select a license for your application's own business code.
|