python-ddd-framework 0.4.0__py3-none-any.whl → 0.6.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/application/runtime.py +46 -19
- python_ddd_framework/application_services/bindings.py +0 -64
- python_ddd_framework/application_services/dispatcher.py +1 -1
- python_ddd_framework/application_services/invocation.py +7 -500
- python_ddd_framework/application_services/module.py +16 -5
- python_ddd_framework/authorization/module.py +1 -1
- python_ddd_framework/background_execution/local.py +20 -1
- python_ddd_framework/background_execution/processes.py +11 -1
- python_ddd_framework/background_jobs/execution.py +1 -1
- python_ddd_framework/background_jobs/pgqueuer/enqueue.py +1 -1
- python_ddd_framework/background_workers/execution.py +1 -1
- python_ddd_framework/background_workers/runtime.py +11 -2
- python_ddd_framework/caching/unit_of_work.py +2 -4
- python_ddd_framework/cli/project.py +11 -10
- python_ddd_framework/developer_kit/generation.py +46 -36
- python_ddd_framework/developer_kit/publication.py +442 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +42 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_integration_service.py.jinja +31 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +2 -2
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +3 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +21 -3
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +18 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +74 -3
- python_ddd_framework/diagnostics/source.py +14 -1
- python_ddd_framework/domain/aggregates.py +5 -12
- python_ddd_framework/domain/event_values.py +124 -0
- python_ddd_framework/events/runtime.py +2 -7
- python_ddd_framework/events/unit_of_work.py +1 -4
- python_ddd_framework/fastapi/action.py +2 -1
- python_ddd_framework/fastapi/adapter.py +11 -117
- python_ddd_framework/fastapi/application_services.py +17 -402
- python_ddd_framework/fastapi/error_handlers.py +128 -0
- python_ddd_framework/fastapi/realtime/runtime.py +1 -1
- python_ddd_framework/fastapi/request_context.py +2 -5
- python_ddd_framework/fastapi/routing.py +4 -10
- python_ddd_framework/fastapi/service_endpoints.py +182 -0
- python_ddd_framework/fastapi/service_routes.py +237 -0
- python_ddd_framework/fastapi/transfer.py +1 -1
- python_ddd_framework/hosted_services/bridge.py +76 -21
- python_ddd_framework/hosted_services/contracts.py +5 -1
- python_ddd_framework/hosted_services/runtime.py +71 -17
- python_ddd_framework/identity/__init__.py +21 -13
- python_ddd_framework/identity/application.py +12 -66
- python_ddd_framework/identity/contracts.py +38 -411
- python_ddd_framework/identity/domain_rules.py +17 -0
- python_ddd_framework/identity/dtos.py +152 -0
- python_ddd_framework/identity/extensions.py +9 -0
- python_ddd_framework/identity/management.py +28 -25
- python_ddd_framework/identity/models.py +86 -0
- python_ddd_framework/identity/module.py +3 -4
- python_ddd_framework/identity/options.py +54 -0
- python_ddd_framework/identity/seeding.py +59 -0
- python_ddd_framework/identity/services.py +1 -1
- python_ddd_framework/identity/sqlalchemy/module.py +9 -6
- python_ddd_framework/identity/sqlalchemy/repository.py +4 -5
- python_ddd_framework/identity/sqlalchemy/session_security.py +1 -3
- python_ddd_framework/identity/sqlalchemy/stores.py +35 -47
- python_ddd_framework/identity/stores.py +120 -0
- python_ddd_framework/identity/tokens.py +1 -1
- python_ddd_framework/invocation/callables.py +1 -1
- python_ddd_framework/invocation/dispatcher.py +1 -1
- python_ddd_framework/invocation/entrypoints.py +2 -1
- python_ddd_framework/invocation/function_runtime.py +2 -2
- python_ddd_framework/invocation/interception.py +1 -1
- python_ddd_framework/invocation/managed_proxy.py +2 -1
- python_ddd_framework/invocation/managed_services.py +8 -14
- python_ddd_framework/invocation/methods.py +2 -7
- python_ddd_framework/invocation/module.py +1 -1
- python_ddd_framework/invocation/runtime.py +483 -0
- python_ddd_framework/invocation/scopes.py +91 -0
- python_ddd_framework/lifecycle/participants.py +4 -0
- python_ddd_framework/messaging/channel.py +35 -8
- python_ddd_framework/messaging/contracts.py +9 -0
- python_ddd_framework/messaging/module.py +13 -0
- python_ddd_framework/messaging/runtime.py +17 -2
- python_ddd_framework/modularity/discovery.py +2 -7
- python_ddd_framework/modularity/graph.py +2 -8
- python_ddd_framework/observability/logging.py +1 -1
- python_ddd_framework/observability/tracing.py +1 -1
- python_ddd_framework/redis/cache.py +2 -0
- python_ddd_framework/redis/notification_runtime.py +1 -1
- python_ddd_framework/services/arbitration.py +1 -20
- python_ddd_framework/services/composition.py +71 -2
- python_ddd_framework/services/provider.py +23 -1
- python_ddd_framework/services/relocation.py +96 -0
- python_ddd_framework/settings/refresh.py +1 -1
- python_ddd_framework/settings/sqlalchemy/store.py +1 -1
- python_ddd_framework/sqlalchemy/metadata.py +11 -413
- python_ddd_framework/sqlalchemy/metadata_builder.py +234 -0
- python_ddd_framework/sqlalchemy/metadata_fingerprint.py +108 -0
- python_ddd_framework/sqlalchemy/metadata_ownership.py +73 -0
- python_ddd_framework/sqlalchemy/migration.py +2 -1
- python_ddd_framework/sqlalchemy/migration_sources.py +38 -0
- python_ddd_framework/sqlalchemy/module.py +2 -1
- python_ddd_framework/sqlalchemy/unit_of_work.py +1 -1
- python_ddd_framework/unit_of_work/contracts.py +68 -7
- python_ddd_framework/unit_of_work/manager.py +4 -7
- python_ddd_framework/unit_of_work/module.py +1 -1
- {python_ddd_framework-0.4.0.dist-info → python_ddd_framework-0.6.0.dist-info}/METADATA +82 -4
- {python_ddd_framework-0.4.0.dist-info → python_ddd_framework-0.6.0.dist-info}/RECORD +104 -85
- {python_ddd_framework-0.4.0.dist-info → python_ddd_framework-0.6.0.dist-info}/WHEEL +1 -1
- {python_ddd_framework-0.4.0.dist-info → python_ddd_framework-0.6.0.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.4.0.dist-info → python_ddd_framework-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {python_ddd_framework-0.4.0.dist-info → python_ddd_framework-0.6.0.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
from collections.abc import Awaitable, Callable, Iterable
|
|
7
|
+
from functools import partial
|
|
6
8
|
from typing import Never
|
|
7
9
|
from uuid import UUID
|
|
8
10
|
|
|
@@ -155,27 +157,25 @@ class Application:
|
|
|
155
157
|
|
|
156
158
|
async def _stop_runtimes(self, reason: ShutdownReason) -> tuple[BaseException, ...]:
|
|
157
159
|
failures: list[BaseException] = []
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
+
# 每个阶段都是全局屏障;收尾与排空完成前,不释放任何参与者的依赖资源。
|
|
161
|
+
# 用户钩子只交给触达过 start 的参与者;预启动请求仍由独立 drain 收口。
|
|
160
162
|
participants = tuple(reversed(self._root.touched_runtimes))
|
|
161
|
-
for
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
for
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
failures.extend(_call_shutdown_callbacks(item.quiesce for item in participants))
|
|
164
|
+
failures.extend(
|
|
165
|
+
await _await_shutdown_callbacks(
|
|
166
|
+
partial(item.stopping, reason) for item in participants if item.stopping is not None
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
failures.extend(_call_shutdown_callbacks(item.close_admission for item in participants))
|
|
170
|
+
failures.extend(
|
|
171
|
+
await _await_shutdown_callbacks(
|
|
172
|
+
item.drain for item in reversed(self._root.runtimes) if item.drain is not None
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
failures.extend(
|
|
176
|
+
await _await_shutdown_callbacks(partial(item.stop, reason) for item in participants)
|
|
177
|
+
)
|
|
172
178
|
self._root.touched_runtimes.clear()
|
|
173
|
-
for participant in reversed(self._root.runtimes):
|
|
174
|
-
if participant.drain is not None:
|
|
175
|
-
try:
|
|
176
|
-
await participant.drain()
|
|
177
|
-
except BaseException as error:
|
|
178
|
-
failures.append(error)
|
|
179
179
|
return tuple(failures)
|
|
180
180
|
|
|
181
181
|
async def stop(self) -> None:
|
|
@@ -352,3 +352,30 @@ class Application:
|
|
|
352
352
|
if not watcher.done():
|
|
353
353
|
watcher.cancel()
|
|
354
354
|
await asyncio.gather(*watchers, return_exceptions=True)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _call_shutdown_callbacks(
|
|
358
|
+
callbacks: Iterable[Callable[[], None] | None],
|
|
359
|
+
) -> tuple[BaseException, ...]:
|
|
360
|
+
failures: list[BaseException] = []
|
|
361
|
+
for callback in callbacks:
|
|
362
|
+
if callback is not None:
|
|
363
|
+
try:
|
|
364
|
+
callback()
|
|
365
|
+
except BaseException as error:
|
|
366
|
+
failures.append(error)
|
|
367
|
+
return tuple(failures)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
async def _await_shutdown_callbacks(
|
|
371
|
+
callbacks: Iterable[Callable[[], Awaitable[tuple[BaseException, ...] | None]]],
|
|
372
|
+
) -> tuple[BaseException, ...]:
|
|
373
|
+
failures: list[BaseException] = []
|
|
374
|
+
for callback in callbacks:
|
|
375
|
+
try:
|
|
376
|
+
result = await callback()
|
|
377
|
+
if result is not None:
|
|
378
|
+
failures.extend(result)
|
|
379
|
+
except BaseException as error:
|
|
380
|
+
failures.append(error)
|
|
381
|
+
return tuple(failures)
|
|
@@ -9,15 +9,12 @@ from typing import get_origin
|
|
|
9
9
|
from dishka import (
|
|
10
10
|
DEFAULT_COMPONENT,
|
|
11
11
|
DependencyKey,
|
|
12
|
-
Provider,
|
|
13
12
|
Scope,
|
|
14
13
|
)
|
|
15
14
|
|
|
16
15
|
from ..diagnostics import SourceLocation
|
|
17
16
|
from ..modularity import ModuleRegistry
|
|
18
17
|
from ..services.arbitration import (
|
|
19
|
-
_ArbitrationResult,
|
|
20
|
-
_provider_with_active_sources,
|
|
21
18
|
_RegistrationDraft,
|
|
22
19
|
)
|
|
23
20
|
from ..services.native_graph import _NativeServiceGraph
|
|
@@ -201,64 +198,3 @@ def _validate_application_service_bindings(
|
|
|
201
198
|
"or private implementation binding"
|
|
202
199
|
),
|
|
203
200
|
)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def _runtime_providers(
|
|
207
|
-
arbitration: _ArbitrationResult,
|
|
208
|
-
services: tuple[_BoundApplicationService, ...],
|
|
209
|
-
) -> tuple[Provider, ...]:
|
|
210
|
-
"""仅重定位 Application Service factories;其他 Dishka provider 保持原生形状。"""
|
|
211
|
-
|
|
212
|
-
related_draft_ids = frozenset(
|
|
213
|
-
draft_id for service in services for draft_id in service.related_draft_ids
|
|
214
|
-
)
|
|
215
|
-
drafts_by_entry: dict[int, list[_RegistrationDraft]] = {}
|
|
216
|
-
for draft in arbitration.drafts:
|
|
217
|
-
drafts_by_entry.setdefault(draft.entry.entry_id, []).append(draft)
|
|
218
|
-
|
|
219
|
-
providers: list[Provider] = []
|
|
220
|
-
for entry in arbitration.ordered_entries:
|
|
221
|
-
entry_drafts = drafts_by_entry[entry.entry_id]
|
|
222
|
-
active = tuple(
|
|
223
|
-
draft
|
|
224
|
-
for draft in entry_drafts
|
|
225
|
-
if draft.decision is not RegistrationDecision.BACKED_OFF
|
|
226
|
-
and id(draft) not in related_draft_ids
|
|
227
|
-
)
|
|
228
|
-
if not active:
|
|
229
|
-
continue
|
|
230
|
-
if len(active) == len(entry_drafts):
|
|
231
|
-
providers.append(entry.contribution.provider)
|
|
232
|
-
else:
|
|
233
|
-
providers.append(_provider_with_active_sources(entry.contribution.provider, active))
|
|
234
|
-
|
|
235
|
-
# default component 只发布 REQUEST proxy;ACTION implementation 统一藏入框架私有 component。
|
|
236
|
-
implementation_provider = Provider(component=_APPLICATION_SERVICE_COMPONENT)
|
|
237
|
-
for service in services:
|
|
238
|
-
winner = service.winner
|
|
239
|
-
assert winner.factory is not None
|
|
240
|
-
# internal contributor 被 override 时仍以原声明 key 调度;
|
|
241
|
-
# winner class 只是实现事实,不能改写契约 key。
|
|
242
|
-
contract = (
|
|
243
|
-
service.binding.discovery.definition.contract or service.binding.discovery.service_type
|
|
244
|
-
)
|
|
245
|
-
implementation_provider.factories.append(
|
|
246
|
-
winner.factory.replace(
|
|
247
|
-
dependencies=tuple(
|
|
248
|
-
dependency.with_component(DEFAULT_COMPONENT)
|
|
249
|
-
for dependency in winner.factory.dependencies
|
|
250
|
-
),
|
|
251
|
-
kw_dependencies={
|
|
252
|
-
name: dependency.with_component(DEFAULT_COMPONENT)
|
|
253
|
-
for name, dependency in winner.factory.kw_dependencies.items()
|
|
254
|
-
},
|
|
255
|
-
provides=DependencyKey(contract, None),
|
|
256
|
-
when_override=None,
|
|
257
|
-
when_component=DEFAULT_COMPONENT,
|
|
258
|
-
)
|
|
259
|
-
)
|
|
260
|
-
# ServiceCatalog 的 accepted row 必须描述 relocated native binding,而非旧 default key。
|
|
261
|
-
winner.key = DependencyKey(contract, _APPLICATION_SERVICE_COMPONENT)
|
|
262
|
-
if services:
|
|
263
|
-
providers.append(implementation_provider)
|
|
264
|
-
return tuple(providers)
|
|
@@ -13,6 +13,7 @@ from dishka import AsyncContainer
|
|
|
13
13
|
from ..invocation.dispatcher import _MethodDispatcher
|
|
14
14
|
from ..invocation.execution import _InterceptorPipeline
|
|
15
15
|
from ..invocation.methods import _CompiledMethod
|
|
16
|
+
from ..invocation.runtime import _current_task, _InvocationRuntime
|
|
16
17
|
from ..settings.provider import SettingProvider
|
|
17
18
|
from .catalog import (
|
|
18
19
|
_APPLICATION_SERVICE_COMPONENT,
|
|
@@ -22,7 +23,6 @@ from .contracts import ApplicationService
|
|
|
22
23
|
from .errors import (
|
|
23
24
|
CrossTaskInvocationError,
|
|
24
25
|
)
|
|
25
|
-
from .invocation import _current_task, _InvocationRuntime
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
@dataclass(slots=True)
|