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.
Files changed (104) hide show
  1. python_ddd_framework/application/runtime.py +46 -19
  2. python_ddd_framework/application_services/bindings.py +0 -64
  3. python_ddd_framework/application_services/dispatcher.py +1 -1
  4. python_ddd_framework/application_services/invocation.py +7 -500
  5. python_ddd_framework/application_services/module.py +16 -5
  6. python_ddd_framework/authorization/module.py +1 -1
  7. python_ddd_framework/background_execution/local.py +20 -1
  8. python_ddd_framework/background_execution/processes.py +11 -1
  9. python_ddd_framework/background_jobs/execution.py +1 -1
  10. python_ddd_framework/background_jobs/pgqueuer/enqueue.py +1 -1
  11. python_ddd_framework/background_workers/execution.py +1 -1
  12. python_ddd_framework/background_workers/runtime.py +11 -2
  13. python_ddd_framework/caching/unit_of_work.py +2 -4
  14. python_ddd_framework/cli/project.py +11 -10
  15. python_ddd_framework/developer_kit/generation.py +46 -36
  16. python_ddd_framework/developer_kit/publication.py +442 -0
  17. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +42 -3
  18. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_integration_service.py.jinja +31 -7
  19. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +2 -2
  20. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +3 -1
  21. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +21 -3
  22. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +18 -1
  23. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +74 -3
  24. python_ddd_framework/diagnostics/source.py +14 -1
  25. python_ddd_framework/domain/aggregates.py +5 -12
  26. python_ddd_framework/domain/event_values.py +124 -0
  27. python_ddd_framework/events/runtime.py +2 -7
  28. python_ddd_framework/events/unit_of_work.py +1 -4
  29. python_ddd_framework/fastapi/action.py +2 -1
  30. python_ddd_framework/fastapi/adapter.py +11 -117
  31. python_ddd_framework/fastapi/application_services.py +17 -402
  32. python_ddd_framework/fastapi/error_handlers.py +128 -0
  33. python_ddd_framework/fastapi/realtime/runtime.py +1 -1
  34. python_ddd_framework/fastapi/request_context.py +2 -5
  35. python_ddd_framework/fastapi/routing.py +4 -10
  36. python_ddd_framework/fastapi/service_endpoints.py +182 -0
  37. python_ddd_framework/fastapi/service_routes.py +237 -0
  38. python_ddd_framework/fastapi/transfer.py +1 -1
  39. python_ddd_framework/hosted_services/bridge.py +76 -21
  40. python_ddd_framework/hosted_services/contracts.py +5 -1
  41. python_ddd_framework/hosted_services/runtime.py +71 -17
  42. python_ddd_framework/identity/__init__.py +21 -13
  43. python_ddd_framework/identity/application.py +12 -66
  44. python_ddd_framework/identity/contracts.py +38 -411
  45. python_ddd_framework/identity/domain_rules.py +17 -0
  46. python_ddd_framework/identity/dtos.py +152 -0
  47. python_ddd_framework/identity/extensions.py +9 -0
  48. python_ddd_framework/identity/management.py +28 -25
  49. python_ddd_framework/identity/models.py +86 -0
  50. python_ddd_framework/identity/module.py +3 -4
  51. python_ddd_framework/identity/options.py +54 -0
  52. python_ddd_framework/identity/seeding.py +59 -0
  53. python_ddd_framework/identity/services.py +1 -1
  54. python_ddd_framework/identity/sqlalchemy/module.py +9 -6
  55. python_ddd_framework/identity/sqlalchemy/repository.py +4 -5
  56. python_ddd_framework/identity/sqlalchemy/session_security.py +1 -3
  57. python_ddd_framework/identity/sqlalchemy/stores.py +35 -47
  58. python_ddd_framework/identity/stores.py +120 -0
  59. python_ddd_framework/identity/tokens.py +1 -1
  60. python_ddd_framework/invocation/callables.py +1 -1
  61. python_ddd_framework/invocation/dispatcher.py +1 -1
  62. python_ddd_framework/invocation/entrypoints.py +2 -1
  63. python_ddd_framework/invocation/function_runtime.py +2 -2
  64. python_ddd_framework/invocation/interception.py +1 -1
  65. python_ddd_framework/invocation/managed_proxy.py +2 -1
  66. python_ddd_framework/invocation/managed_services.py +8 -14
  67. python_ddd_framework/invocation/methods.py +2 -7
  68. python_ddd_framework/invocation/module.py +1 -1
  69. python_ddd_framework/invocation/runtime.py +483 -0
  70. python_ddd_framework/invocation/scopes.py +91 -0
  71. python_ddd_framework/lifecycle/participants.py +4 -0
  72. python_ddd_framework/messaging/channel.py +35 -8
  73. python_ddd_framework/messaging/contracts.py +9 -0
  74. python_ddd_framework/messaging/module.py +13 -0
  75. python_ddd_framework/messaging/runtime.py +17 -2
  76. python_ddd_framework/modularity/discovery.py +2 -7
  77. python_ddd_framework/modularity/graph.py +2 -8
  78. python_ddd_framework/observability/logging.py +1 -1
  79. python_ddd_framework/observability/tracing.py +1 -1
  80. python_ddd_framework/redis/cache.py +2 -0
  81. python_ddd_framework/redis/notification_runtime.py +1 -1
  82. python_ddd_framework/services/arbitration.py +1 -20
  83. python_ddd_framework/services/composition.py +71 -2
  84. python_ddd_framework/services/provider.py +23 -1
  85. python_ddd_framework/services/relocation.py +96 -0
  86. python_ddd_framework/settings/refresh.py +1 -1
  87. python_ddd_framework/settings/sqlalchemy/store.py +1 -1
  88. python_ddd_framework/sqlalchemy/metadata.py +11 -413
  89. python_ddd_framework/sqlalchemy/metadata_builder.py +234 -0
  90. python_ddd_framework/sqlalchemy/metadata_fingerprint.py +108 -0
  91. python_ddd_framework/sqlalchemy/metadata_ownership.py +73 -0
  92. python_ddd_framework/sqlalchemy/migration.py +2 -1
  93. python_ddd_framework/sqlalchemy/migration_sources.py +38 -0
  94. python_ddd_framework/sqlalchemy/module.py +2 -1
  95. python_ddd_framework/sqlalchemy/unit_of_work.py +1 -1
  96. python_ddd_framework/unit_of_work/contracts.py +68 -7
  97. python_ddd_framework/unit_of_work/manager.py +4 -7
  98. python_ddd_framework/unit_of_work/module.py +1 -1
  99. {python_ddd_framework-0.4.0.dist-info → python_ddd_framework-0.6.0.dist-info}/METADATA +82 -4
  100. {python_ddd_framework-0.4.0.dist-info → python_ddd_framework-0.6.0.dist-info}/RECORD +104 -85
  101. {python_ddd_framework-0.4.0.dist-info → python_ddd_framework-0.6.0.dist-info}/WHEEL +1 -1
  102. {python_ddd_framework-0.4.0.dist-info → python_ddd_framework-0.6.0.dist-info}/entry_points.txt +0 -0
  103. {python_ddd_framework-0.4.0.dist-info → python_ddd_framework-0.6.0.dist-info}/licenses/LICENSE +0 -0
  104. {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
- # stop 只交给触达过 start 的参与者;预启动入口已接受的资源由独立 drain 收口。
160
+ # 每个阶段都是全局屏障;收尾与排空完成前,不释放任何参与者的依赖资源。
161
+ # 用户钩子只交给触达过 start 的参与者;预启动请求仍由独立 drain 收口。
160
162
  participants = tuple(reversed(self._root.touched_runtimes))
161
- for participant in participants:
162
- if participant.close_admission is not None:
163
- try:
164
- participant.close_admission()
165
- except BaseException as error:
166
- failures.append(error)
167
- for participant in participants:
168
- try:
169
- failures.extend(await participant.stop(reason))
170
- except BaseException as error:
171
- failures.append(error)
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)