python-ddd-framework 0.7.3__py3-none-any.whl → 0.9.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 (31) hide show
  1. python_ddd_framework/__init__.py +2 -2
  2. python_ddd_framework/configuration/root.py +2 -0
  3. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +6 -0
  4. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +1 -0
  5. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +6 -0
  6. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/src/host/module.py.jinja +0 -4
  7. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +6 -0
  8. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +62 -2
  9. python_ddd_framework/settings/__init__.py +3 -7
  10. python_ddd_framework/settings/application.py +53 -21
  11. python_ddd_framework/settings/binding.py +14 -0
  12. python_ddd_framework/settings/catalog.py +4 -59
  13. python_ddd_framework/settings/contracts.py +73 -2
  14. python_ddd_framework/settings/management.py +3 -13
  15. python_ddd_framework/settings/manager.py +17 -34
  16. python_ddd_framework/settings/module.py +21 -9
  17. python_ddd_framework/settings/provider.py +10 -3
  18. python_ddd_framework/settings/values.py +0 -8
  19. {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/METADATA +27 -3
  20. {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/RECORD +24 -31
  21. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +0 -1
  22. python_ddd_framework/settings/changes.py +0 -14
  23. python_ddd_framework/settings/declarations.py +0 -18
  24. python_ddd_framework/settings/handlers.py +0 -43
  25. python_ddd_framework/settings/notifications.py +0 -41
  26. python_ddd_framework/settings/refresh.py +0 -140
  27. python_ddd_framework/settings/refresh_module.py +0 -52
  28. {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/WHEEL +0 -0
  29. {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/entry_points.txt +0 -0
  30. {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/licenses/LICENSE +0 -0
  31. {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
@@ -1,32 +1,25 @@
1
- """条件写入与提交后通知的 owner;读取解析由 SettingProvider 负责。"""
1
+ """条件写入的 owner;读取解析由 SettingProvider 负责,生效时机归业务。"""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import logging
6
- from typing import TYPE_CHECKING, Any, TypeVar, cast
5
+ from typing import Any, TypeVar, cast
7
6
  from uuid import UUID
8
7
 
9
8
  from pydantic import ValidationError
10
9
  from pydantic_core import PydanticSerializationError
11
10
 
12
- from ..notifications import NotificationPayloadError, NotificationPublishStatus, Notifications
13
11
  from .catalog import SettingCatalog
14
- from .changes import SETTINGS_CHANGED, SettingChanged
15
12
  from .contracts import SettingDefinition
16
13
  from .errors import RuntimeSettingError
17
14
  from .provider import SettingProvider
18
15
  from .store import RuntimeSettingStore
19
- from .values import SettingSnapshot, SettingValueSource, SettingWriteOutcome, StoredSetting
20
-
21
- if TYPE_CHECKING:
22
- from .refresh import SettingsRefreshRuntime
16
+ from .values import SettingSnapshot, SettingValueSource, StoredSetting
23
17
 
24
18
  _T = TypeVar("_T")
25
- _LOGGER = logging.getLogger(__name__)
26
19
 
27
20
 
28
21
  class SettingManager:
29
- __slots__ = ("_catalog", "_notifications", "_provider", "_refresh", "_store")
22
+ __slots__ = ("_catalog", "_provider", "_store")
30
23
 
31
24
  def __init__(
32
25
  self, catalog: SettingCatalog, provider: SettingProvider, store: RuntimeSettingStore
@@ -34,13 +27,11 @@ class SettingManager:
34
27
  self._catalog = catalog
35
28
  self._provider = provider
36
29
  self._store = store
37
- self._refresh: SettingsRefreshRuntime | None = None
38
- self._notifications: Notifications | None = None
39
30
 
40
31
  async def set_global(self, definition: SettingDefinition[_T], value: _T) -> _T:
41
32
  current = await self._provider.snapshot(definition)
42
33
  saved = await self.update(definition, value, expected_version=current.version)
43
- return cast(_T, saved.snapshot.value)
34
+ return cast(_T, saved.value)
44
35
 
45
36
  async def reset_global(self, definition: SettingDefinition[Any]) -> None:
46
37
  current = await self._provider.snapshot(definition)
@@ -48,8 +39,8 @@ class SettingManager:
48
39
 
49
40
  async def update(
50
41
  self, definition: SettingDefinition[_T], value: _T, *, expected_version: UUID | None
51
- ) -> SettingWriteOutcome[_T]:
52
- self._catalog.require_owned(definition)
42
+ ) -> SettingSnapshot[_T]:
43
+ self._require_write(definition, operation="set")
53
44
  try:
54
45
  validated, serialized = definition._dump_json(value)
55
46
  except (TypeError, ValidationError, PydanticSerializationError):
@@ -60,30 +51,22 @@ class SettingManager:
60
51
  definition.name, serialized, expected_version=expected_version
61
52
  )
62
53
  # 返回本次已经提交的值,不在提交后重读,避免把另一次并发写入误报为本次结果。
63
- snapshot = SettingSnapshot(
54
+ return SettingSnapshot(
64
55
  definition.name, validated, SettingValueSource.RUNTIME, saved.version
65
56
  )
66
- return SettingWriteOutcome(snapshot, await self._committed(definition.name))
67
57
 
68
58
  async def reset(
69
59
  self, definition: SettingDefinition[_T], *, expected_version: UUID | None
70
- ) -> SettingWriteOutcome[_T]:
71
- self._catalog.require_owned(definition)
60
+ ) -> SettingSnapshot[_T]:
61
+ self._require_write(definition, operation="reset")
72
62
  # 默认值先校验,不能在物理删除已经提交之后再抛出配置校验失败。
73
63
  fallback = self._provider._resolve(definition, StoredSetting(None, expected_version))
74
64
  saved = await self._store.write(definition.name, None, expected_version=expected_version)
75
- snapshot = SettingSnapshot(definition.name, fallback.value, fallback.source, saved.version)
76
- return SettingWriteOutcome(snapshot, await self._committed(definition.name))
65
+ return SettingSnapshot(definition.name, fallback.value, fallback.source, saved.version)
77
66
 
78
- async def _committed(self, name: str) -> NotificationPublishStatus:
79
- # 本地刷新先入队;Redis 降级不会撤销短事务已经提交的结果。
80
- if self._refresh is not None:
81
- self._refresh.request_refresh(name)
82
- if self._notifications is None:
83
- return NotificationPublishStatus.NOT_CONFIGURED
84
- try:
85
- return await self._notifications.publish(SETTINGS_CHANGED, SettingChanged(name=name))
86
- except NotificationPayloadError:
87
- # 发布大小/编码限制是独立结果;不能把已提交的写入重新表现为保存失败。
88
- _LOGGER.warning("Committed setting notification rejected for %s", name)
89
- return NotificationPublishStatus.REJECTED
67
+ def _require_write(self, definition: SettingDefinition[Any], *, operation: str) -> None:
68
+ self._catalog.require_owned(definition)
69
+ if SettingValueSource.RUNTIME not in definition.allowed_sources:
70
+ raise RuntimeSettingError(
71
+ setting=definition.name, operation=operation, error_type="SourceNotAllowed"
72
+ )
@@ -2,16 +2,17 @@
2
2
 
3
3
  from dishka import DEFAULT_COMPONENT, DependencyKey, Provider, Scope
4
4
 
5
+ from ..authorization.module import AuthorizationModule
5
6
  from ..lifecycle import ConfigureContext
6
7
  from ..modularity import AppModule, ModuleRef
7
8
  from ..services.composition import (
8
9
  ServiceBindingContext,
9
10
  ServiceComposition,
10
- ServiceDiscoveryContext,
11
11
  )
12
12
  from ..services.registration import RegistrationDecision, RegistrationKind
13
13
  from .binding import _provide_settings
14
- from .catalog import SettingCatalog, _build_setting_catalog, _contribute_setting_handler_defaults
14
+ from .catalog import SettingCatalog, _build_setting_catalog
15
+ from .errors import SettingDefinitionError
15
16
  from .store import RuntimeSettingStore
16
17
 
17
18
 
@@ -19,15 +20,10 @@ class SettingsModule(AppModule):
19
20
  catalog: SettingCatalog
20
21
 
21
22
  def configure(self, context: ConfigureContext) -> None:
22
- self.catalog = _build_setting_catalog(context.modules, context.discoveries)
23
+ self.catalog = _build_setting_catalog(context.discoveries)
23
24
  if self.catalog.definitions:
24
25
  context.configure.section("settings")
25
- context.services.add_composition(
26
- ServiceComposition(discover=self._discover, bind=self._bind)
27
- )
28
-
29
- def _discover(self, context: ServiceDiscoveryContext) -> None:
30
- _contribute_setting_handler_defaults(context.modules, context.services)
26
+ context.services.add_composition(ServiceComposition(bind=self._bind))
31
27
 
32
28
  def _bind(self, context: ServiceBindingContext) -> None:
33
29
  provider = Provider()
@@ -57,3 +53,19 @@ class SettingsManagementModule(AppModule):
57
53
  "python_ddd_framework.settings.application",
58
54
  "python_ddd_framework.settings.permissions",
59
55
  )
56
+
57
+ def configure(self, context: ConfigureContext) -> None:
58
+ context.services.add_composition(ServiceComposition(bind=self._bind))
59
+
60
+ def _bind(self, context: ServiceBindingContext) -> None:
61
+ # 全局 discovery 屏障后核对实际权限目录;只读 Settings 无需装配授权服务。
62
+ catalog = context.modules.instance(SettingsModule).catalog
63
+ permissions = context.modules.instance(AuthorizationModule).catalog
64
+ for definition in catalog.definitions:
65
+ for permission in (definition.read_permission, definition.write_permission):
66
+ if permission is not None and permission not in permissions:
67
+ raise SettingDefinitionError(
68
+ setting=definition.name,
69
+ reason=f"permission {permission} is not defined",
70
+ source=catalog.sources.get(definition.name),
71
+ )
@@ -43,7 +43,7 @@ class SettingProvider:
43
43
  self._catalog.require_owned(definition)
44
44
  stored = (
45
45
  await self._store.read(definition.name)
46
- if self._store is not None
46
+ if self._store is not None and SettingValueSource.RUNTIME in definition.allowed_sources
47
47
  else StoredSetting(None, None)
48
48
  )
49
49
  return self._resolve(definition, stored)
@@ -56,11 +56,18 @@ class SettingProvider:
56
56
  value = definition._validate_json(stored.value_json)
57
57
  source = SettingValueSource.RUNTIME
58
58
  else:
59
- configured = self._configuration.get_path(f"settings.{definition.name}", _MISSING)
59
+ configured = (
60
+ self._configuration.get_path(f"settings.{definition.name}", _MISSING)
61
+ if SettingValueSource.CONFIGURATION in definition.allowed_sources
62
+ else _MISSING
63
+ )
60
64
  if configured is not _MISSING:
61
65
  value = definition._validate_python(configured)
62
66
  source = SettingValueSource.CONFIGURATION
63
- elif definition.has_default:
67
+ elif (
68
+ definition.has_default
69
+ and SettingValueSource.DEFINITION in definition.allowed_sources
70
+ ):
64
71
  value = definition._default_value()
65
72
  source = SettingValueSource.DEFINITION
66
73
  else:
@@ -7,8 +7,6 @@ from enum import Enum
7
7
  from typing import Generic, TypeVar
8
8
  from uuid import UUID
9
9
 
10
- from ..notifications import NotificationPublishStatus
11
-
12
10
  _T = TypeVar("_T")
13
11
 
14
12
 
@@ -31,9 +29,3 @@ class SettingSnapshot(Generic[_T]):
31
29
  value: _T | None
32
30
  source: SettingValueSource
33
31
  version: UUID | None
34
-
35
-
36
- @dataclass(frozen=True, slots=True)
37
- class SettingWriteOutcome(Generic[_T]):
38
- snapshot: SettingSnapshot[_T]
39
- notification: NotificationPublishStatus
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-ddd-framework
3
- Version: 0.7.3
3
+ Version: 0.9.0
4
4
  Summary: Modular DDD application framework with project CLI and bundled templates
5
5
  License-Expression: LicenseRef-Proprietary
6
6
  License-File: LICENSE
@@ -64,7 +64,7 @@ Python DDD Framework combines a module system with FastAPI, Dishka, Pydantic, SQ
64
64
  | HTTP APIs | Expose selected application services through FastAPI, or write explicit routers. Use OpenAPI, filters, file uploads, downloads, and streaming responses. |
65
65
  | Persistence and events | Use async SQLAlchemy repositories, explicit transaction boundaries, module-owned Alembic migrations, seed contributors, and local events before or after commit. |
66
66
  | Identity and permissions | Add JWT authentication, refresh sessions, users and roles, permission definitions, and authorization on service methods. |
67
- | Cache, settings, and locks | Use typed Redis caching, runtime settings with conditional updates and reset, settings refresh notifications, and explicit Redis-backed business locks. |
67
+ | Cache, settings, and locks | Use typed Redis caching, global runtime settings with conditional updates and reset, and explicit Redis-backed business locks. |
68
68
  | Background execution | Enqueue durable PostgreSQL jobs with typed payloads, schedule recurring work, run periodic workers, and select in-process or managed subprocess execution. |
69
69
  | Real-time communication | Add authenticated WebSocket connections, typed messages, and targeted notifications to connected clients. |
70
70
  | Hosting and observability | Manage long-lived integrations with `HostedService`, coordinate shutdown, and use structured logging, correlation, OpenTelemetry tracing, and lifecycle health endpoints. |
@@ -273,6 +273,12 @@ Edit `backend/app.development.yaml` for local connections, identity settings, lo
273
273
 
274
274
  Generated credentials are for local development. Production configuration is supplied separately in `app.production.yaml`. See [configuration](https://github.com/componet-architecture/python-ddd-framework/blob/main/docs/development.md#定义-module-与配置) and [background and logging configuration](https://github.com/componet-architecture/python-ddd-framework/blob/main/docs/development.md#settings后台与日志).
275
275
 
276
+ ### Use global settings
277
+
278
+ Declare a typed `SettingDefinition` and its scanned provider under the owning module's `domain/settings/`, then add `SettingsModule` to that module's dependencies. Inject `SettingProvider` to read values and `SettingManager` for conditional writes/reset. The generated Host already selects Settings persistence and management HTTP; apply its Settings migration before using the store for reads or writes.
279
+
280
+ Values resolve from runtime override, Host configuration, then definition default. Reset clears an override and resumes fallback. Management provides list/read/update/reset with visibility, permissions, and version checks; business code decides when to use the value. Follow the [complete project recipe](docs/development.md#settings-策略与使用) or the generated project's **Global settings** guide for declarations, configuration, permissions, and request bodies. The minimal contract starts with 0.9.0; see [migration notes](#minimal-settings-migration-090).
281
+
276
282
  ### Run application tests
277
283
 
278
284
  After completing local database setup, run from `backend/`:
@@ -314,6 +320,24 @@ New `ddd` modules contain the full responsibility directories and necessary comp
314
320
 
315
321
  See the [migration guide](docs/development.md#数据库聚合与迁移) and [verification record](docs/status.md#http-回收与迁移来源判定未发布).
316
322
 
323
+ ## Minimal Settings migration (0.9.0)
324
+
325
+ Settings now owns only global definitions, reads, writes, and clearing overrides. Modules own definitions, the Host selects storage, and business code decides when and how to use the values. Source order, source restrictions, management permissions, typed JSON, independent transactions, UUID concurrency tokens, and reset version records remain unchanged. No dependency, database schema, or migration change is required.
326
+
327
+ - Remove `SettingsRefreshModule`, `SettingsNotificationsModule`, `SettingHandlers`, `SettingHandlerDefinition`, and Settings refresh options from Host/module wiring. Remove settings observers and the dedicated `application/setting_handlers` directory. Read through `SettingProvider` where the business needs the value; the framework no longer coordinates application, polling, reconnect compensation, retries, or application status. Generic Notifications and HostedService remain available independently.
328
+ - `SettingManager.update/reset` returns `SettingSnapshot[T]` directly: replace `result.snapshot.value/version` with `result.value/version`. `SettingWriteOutcome`, notification feedback, and handler/application status exports are removed.
329
+ - HTTP update/reset returns the same `SettingView` shape as a read: `name`, `value`, `source`, and `version`. Replace `response.setting` with `response`; remove `saved`, `notification`, and `effect` handling. `SettingWriteResult` is removed. Success confirms persistence only, and stale versions still produce a conflict.
330
+
331
+ Current rules and examples are in the [Settings contract](docs/architecture.md#typed-cache-与-settings) and [usage guide](docs/development.md#settings-策略与使用). Validation and remaining limits are recorded in [status](docs/status.md#settings-最小化未发布).
332
+
333
+ ## Settings policies (0.8.0)
334
+
335
+ `SettingDefinition` now accepts `allowed_sources`, `management_access`, `read_permission`, and `write_permission`. Existing definitions retain their source order and management visibility. Generic update/reset now also require `settings.read`, in addition to `settings.write` and any declared per-setting permissions, because their responses contain the saved setting snapshot. Update roles that previously held only write permission.
336
+
337
+ Before excluding `CONFIGURATION` for a setting, remove its entries from YAML, environment variables, dotenv, CLI, explicit configuration and module configuration defaults: composition rejects forbidden inputs even when another value masks them. `HIDDEN` and `READ_ONLY` restrict generic management; internal readers and dedicated application services can still use the existing Provider/Manager subject to source, type and version rules. Defining a per-setting permission does not grant it to existing users or roles; update grants through the application's authorization system. No database migration or dependency change is required for this extension.
338
+
339
+ Version 0.8.0 includes these policies. Follow [Settings setup and usage](docs/development.md#settings-策略与使用) for definition discovery, permission declarations, internal Provider/Manager calls, and HTTP update/reset with version tokens. The [contract](docs/architecture.md#typed-cache-与-settings) owns the rules; [validation evidence](docs/status.md#settings-最小策略扩展) separates implementation results from the later read-only review.
340
+
317
341
  ## Invocation performance (0.7.2)
318
342
 
319
343
  Function calls avoid regenerating DI wrappers, empty extension chains skip unnecessary work, and completed filter/interceptor chains release execution references sooner. Managed declaration checks continue to detect dynamic method and policy changes.
@@ -343,7 +367,7 @@ Runnable examples: [HTTP generators](docs/development.md#http-生成器与持续
343
367
  Capability composition now uses the same `AppModule` model as customer modules. Existing applications must migrate their declarations together; there is no compatibility path.
344
368
 
345
369
  - Declare capability dependencies on the Module that uses them. Selecting a sibling provider in Host does not supply a missing business-module dependency. `ApplicationServicesModule` supplies the usual application-service capabilities; additional capabilities are explicit dependencies.
346
- - Wrap capability declarations with `NotificationDefinitions`, `HostedServices`, `BackgroundWorkers`, `BackgroundSchedules`, or `SettingHandlers` from their owning packages. Use native service registration for providers and `ServiceComposition` for capability discovery and final binding. Keep provider selection in Host.
370
+ - Wrap capability declarations with `NotificationDefinitions`, `HostedServices`, `BackgroundWorkers`, or `BackgroundSchedules` from their owning packages. Use native service registration for providers and `ServiceComposition` for capability discovery and final binding. Keep provider selection in Host.
347
371
  - Replace Application invocation methods with `invoke(application, Service.method, ...)`, `invoke_as(application, user, ...)`, `call(application, function, ...)`, and `call_as(application, user, ...)`. Register interception with `add_interceptor(context, ...)` and events with `subscribe_local_event(context, ...)`. Import these functions from `python_ddd_framework`.
348
372
  - Read capability catalogs from their Module instance through `application.modules.instance(ModuleType)` or the catalog's DI binding. Generic service diagnostics remain in `application.service_catalog`; readiness is `application.is_ready`.
349
373
  - `DataSeedContributor`, `DataSeeder`, and `DataSeedError` belong to `python_ddd_framework.seeding` and remain available from the root package. Domain Modules with contributors depend on `DataSeedingModule`; remove `@application_service` from contributors. Seed is no longer an ApplicationService or part of its catalog. Explicit seed commands retain contributor ordering, per-item UoW, module dependency selection, failure reporting, and cleanup.
@@ -1,4 +1,4 @@
1
- python_ddd_framework/__init__.py,sha256=UUsi5Ye8kGFX3zp7nC7QW87VEhfSCWy3_u_dcZ5qUgI,15416
1
+ python_ddd_framework/__init__.py,sha256=TuNv1DOY2PLAvwzWzj_j0zSoJmofjjkdOQdqY18SDwI,15402
2
2
  python_ddd_framework/application/__init__.py,sha256=RPe_lkegIRim7GcBnXyevOg5KM7bfUIO9YGsqcA0lXk,184
3
3
  python_ddd_framework/application/build_spec.py,sha256=Udj1JIgP2oBMCUezmzWzOdjFYBCEE8OxwHSEaDqrMMo,1030
4
4
  python_ddd_framework/application/builder.py,sha256=d0vc1D5HvG_aJI5lPQydr-ha0OTNviWlplxlyP99qDs,8080
@@ -89,7 +89,7 @@ python_ddd_framework/configuration/environment_source.py,sha256=5DFYpLyOXpCkYT4W
89
89
  python_ddd_framework/configuration/immutability.py,sha256=TRB2caTQtoDUA1bcQt9bPWBl-7u45f5vO30sRJQnKkc,5672
90
90
  python_ddd_framework/configuration/input_shape.py,sha256=T97z95F2PQkvNN0PV5Iz1s19UvX1CZGvyV7Ojo7cRLY,2438
91
91
  python_ddd_framework/configuration/merge.py,sha256=VE6vtvcyQu8rQaqGAQs23JQmhJFneq6cWd1SZCow1Bk,3702
92
- python_ddd_framework/configuration/root.py,sha256=aF2ideF_Hz6N3cY_W1kvZDQRe1JGv8SeIC3-bxpYMrQ,7919
92
+ python_ddd_framework/configuration/root.py,sha256=-xmDLRHJWOpLUVKa7i-QABsrp5kCrTtefunvdhGkgvc,8068
93
93
  python_ddd_framework/configuration/sources.py,sha256=Xd7Sxk0umHS7h5SRq08NSWGhLmJ3lHVP6tyzmh1F7AA,19233
94
94
  python_ddd_framework/configuration/values.py,sha256=4S8Ae2CRRekA9ge0ZxoMJcdJn3XqnF06E6DOaAZZuHc,14591
95
95
  python_ddd_framework/configuration/yaml_source.py,sha256=hZhKftmip6rRQUwqQBfTi_d-UYthUqWPbL5dQUvb89Q,2016
@@ -107,7 +107,7 @@ python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/
107
107
  python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/__init__.py.jinja,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
108
108
  python_ddd_framework/developer_kit/templates/basic/{{cookiecutter.module_name}}/services/default_conversion_service.py.jinja,sha256=YIYXqDycBVoUlLCwwUZ2tcyUk2fKSu-20mI00u5QUhc,342
109
109
  python_ddd_framework/developer_kit/templates/module/cookiecutter.json,sha256=edD_zSqmFuOKvMZyxmFFabAavBQJk0tbGO4YrnsgZN0,60
110
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md,sha256=bojfHi_8PIPSeJheOdS6hSIaG2LrXDwmqymnwm0ZPDs,5674
110
+ python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md,sha256=nbPgBhWynTZaK6vojFyxf2ARSYAzw6xCsNzAqUtulNo,6537
111
111
  python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/__init__.py.jinja,sha256=byPOI4PKtQDi5IQa9tzZZEVYy5zGx9PJdBzPrgcxm64,89
112
112
  python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/__init__.py.jinja,sha256=pm9_TqjN20deRELpopxK412O3mra0OZ6QdesPdcsaRU,90
113
113
  python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/__init__.py.jinja,sha256=Kbig4VsdpmVUDHx5SLPf7ZZM6S5ftVmzBxBAJIjwvng,40
@@ -120,7 +120,6 @@ python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}
120
120
  python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja,sha256=qGmq1ggBmxGY2B8H8gB8tEY-uxMuWpyr60l0ZAouho8,1950
121
121
  python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/__init__.py.jinja,sha256=Kbig4VsdpmVUDHx5SLPf7ZZM6S5ftVmzBxBAJIjwvng,40
122
122
  python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/__init__.py.jinja,sha256=Kbig4VsdpmVUDHx5SLPf7ZZM6S5ftVmzBxBAJIjwvng,40
123
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja,sha256=Kbig4VsdpmVUDHx5SLPf7ZZM6S5ftVmzBxBAJIjwvng,40
124
123
  python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/__init__.py.jinja,sha256=grSDmU4IKdRYZsqi56aEVvsgx92AYF2k3jlDzANqra4,51
125
124
  python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/__init__.py.jinja,sha256=Kbig4VsdpmVUDHx5SLPf7ZZM6S5ftVmzBxBAJIjwvng,40
126
125
  python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/__init__.py.jinja,sha256=Kbig4VsdpmVUDHx5SLPf7ZZM6S5ftVmzBxBAJIjwvng,40
@@ -158,8 +157,8 @@ python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}
158
157
  python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/__init__.py.jinja,sha256=VdqphDwvoB9nhl5Q9vHpSnmMWwDUlDp_YckRkionwWw,46
159
158
  python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/__init__.py.jinja,sha256=Kbig4VsdpmVUDHx5SLPf7ZZM6S5ftVmzBxBAJIjwvng,40
160
159
  python_ddd_framework/developer_kit/templates/project/cookiecutter.json,sha256=vmN799k9eRHAEGeYt8uUjPsusbblvgpNIsXVOsXUno4,314
161
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md,sha256=u4fnjZJgqmIGf2CI9yYNRsfDLFktvGvuOVm2M-DQmKs,13059
162
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md,sha256=i65NyuY-MXu1eZ3M_Ob5hFpcqCSd8hzc3iYr3Cn27g4,11146
160
+ python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md,sha256=MVG6FbJckCZYwq-V33AZpH7RCvcv2tKaaN5VcaIzJ6M,13432
161
+ python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md,sha256=y_Xg7rKtj6I8U2ChoLj57C5jz75gk2BdqD8F-ZH8IfI,11874
163
162
  python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/.dockerignore,sha256=TG053vIzikxG8h53vCcU_s5wcr7p4GEKwsedbrRrSOo,71
164
163
  python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/.gitignore,sha256=OF6nwgb-RiP8EAXc5osbk3bsOEYkk0JaV99v3FcglTU,66
165
164
  python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/.python-version,sha256=lN0hZ-3c-Wk_uFRrQasUfy9YJrXrdvSzYKhuHeXIXbY,34
@@ -170,11 +169,11 @@ python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name
170
169
  python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/pyproject.toml.jinja,sha256=zZ-kDjgfOYo4g35VLoctEEoyvlZm9fEB2ddHW30JzUs,932
171
170
  python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/src/host/__init__.py.jinja,sha256=RfLT7dqPnSotCDorSp6Taz9jBYOmepu6De_wZNelWZ8,74
172
171
  python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/src/host/main.py.jinja,sha256=SPxJ86SW1dx-BuoWITWifq-6NseoxnVMLTufcFFsxF4,1996
173
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/src/host/module.py.jinja,sha256=6j63muRR7tQmYIFso-egk8L5_Zw57I61ijWR8SDpw5U,3235
172
+ python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/src/host/module.py.jinja,sha256=qpM0L69ek8-SXQpJoZ-NkPyfGGIGTRrB1xtrmzpxZF8,3004
174
173
  python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/tests/conftest.py.jinja,sha256=I_SlSxioBsCFNJLM52iM-neYBJR71qOUn7C4dI9t-7I,149
175
174
  python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/tests/host/test_http.py.jinja,sha256=pmiz6k0oWWrPeW_rVG2Ynibg4j3yuKvgvYFc0K7J_no,2766
176
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md,sha256=qU6lE4iwhTUDCY3OvtSgNYw99k8JGaOsyrmdaFt_Py4,26920
177
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md,sha256=hEn_ucr8NmZnEp80TyAYB1IoLMWUoh9Hk8wCBzCizDM,53182
175
+ python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md,sha256=VINiboJya5Ae6wn0UL7pHK-IXX7OIKfI0jbWaOL0F9U,27842
176
+ python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md,sha256=V44TFV5BU5dhCautVc8D41h2-QhKgyCk69X3d-dWZgk,57556
178
177
  python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/scripts/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
179
178
  python_ddd_framework/developer_kit/wiring.py,sha256=80ZhxpGXK6NP3b_BTTc1kt3oIU21MU70cT9kgm8ZgAE,7835
180
179
  python_ddd_framework/diagnostics/__init__.py,sha256=HdgzjQqMJ_SUGKJc5EcQ5PL1rM4GcTeem-L0q1r7vjQ,455
@@ -390,25 +389,19 @@ python_ddd_framework/services/registration.py,sha256=AsvhVv_szq8TcaXdUMvOY6fNNp8
390
389
  python_ddd_framework/services/relocation.py,sha256=oMel_8RPFtPHDE9KrUepzx1nJpiug0EaNK7RWEKZFC0,3763
391
390
  python_ddd_framework/services/repository.py,sha256=jjjCmpcz4deAZmDNSM44WlDmh9msFO53qN116CHdBSM,1686
392
391
  python_ddd_framework/services/runtime.py,sha256=57G5jNjOiexpSqTCogdI18u5xeEK2YglztNqDtFcsZI,9300
393
- python_ddd_framework/settings/__init__.py,sha256=5Ikxnl_nT9l_WDuqW916hsvVlO9t_vCjR9OIvvJR8lI,1034
394
- python_ddd_framework/settings/application.py,sha256=9J7Q74Vr6QXJ2ddWU7-MFpwdjUXW-ZGI1Py_R93mAsM,3775
395
- python_ddd_framework/settings/binding.py,sha256=1a-AzTLwCTU6zQ85rYBJrQE63_b7BwFf9UWbMhOfJrQ,962
396
- python_ddd_framework/settings/catalog.py,sha256=fqVeFr3DuJWrNUfIuIwfivVhuCobQpWe32EmRieuUVE,4880
397
- python_ddd_framework/settings/changes.py,sha256=zpHhCJY83s-KIs7NkjQAmIxzY7cJfT8FD4A08gF9NfE,367
398
- python_ddd_framework/settings/contracts.py,sha256=E70lLiFC5G7XoEpmf3rsoW5BnPhzajm-eri0vzn0xuY,4428
399
- python_ddd_framework/settings/declarations.py,sha256=FeFrgw6fAdjX6bGaajjnO2tsrfxEvKW6sZrRfTAxCEs,526
392
+ python_ddd_framework/settings/__init__.py,sha256=EYm9Ek57X2o1edniVeKn150uKFgSrHF7onKUncGRQnc,867
393
+ python_ddd_framework/settings/application.py,sha256=QMouHpNJUxkmkQ3kRHSxtZ4ssDAsRZitYWiWADuGCH4,5444
394
+ python_ddd_framework/settings/binding.py,sha256=_wDIsKe-p6WuKaQB9NvhbxCm9aYbiN95m7erJS35T8Y,1621
395
+ python_ddd_framework/settings/catalog.py,sha256=IkfZTd2PGUOMTyW7koQEdIXqPdiiPYlNmoTyfxPF4P0,2452
396
+ python_ddd_framework/settings/contracts.py,sha256=v_Wn7kr3_JQYH-rXeusR_YWvtwR3RT_KxQqnvVD7uZI,6899
400
397
  python_ddd_framework/settings/definition_discovery.py,sha256=zHN3-coqzdEb--mecv00v7ifWzzCfJRZ-BrFcdVx4u4,3002
401
398
  python_ddd_framework/settings/definitions.py,sha256=hkWO0dqOoBmd7pKv-cUsCZaEU0AeUEULVLW1Rl8uwdQ,1687
402
399
  python_ddd_framework/settings/errors.py,sha256=ZcU1nMB44_Rq9Wl5bw83s3Y066LxSLrFqOW_gG6ViS4,836
403
- python_ddd_framework/settings/handlers.py,sha256=A0GWDKRXASlCoWm_Wduul0thAv4xW4SG305KXd6wY-4,1369
404
- python_ddd_framework/settings/management.py,sha256=dJhyZYfNA5Qsup9yt2dKOrIwGq3OnNkDxskJniBV3BI,1656
405
- python_ddd_framework/settings/manager.py,sha256=wjqhRCmtF_B-ETRZOTl3izRTzVJb7ebk3qLOMqFDdRE,4181
406
- python_ddd_framework/settings/module.py,sha256=8c9c4pIpzn3XB8xkJqW4AhKkuUS-HYxwfS4px7VeP1Q,2314
407
- python_ddd_framework/settings/notifications.py,sha256=tVSXjhhJWhkMcOakSmeOfXT1X02Hcx2xRU8Whr8rSig,1700
400
+ python_ddd_framework/settings/management.py,sha256=sfibyZzhrsYg8FwPZCz_mDw6UEBtnP9HJaVu2O8PMmc,1298
401
+ python_ddd_framework/settings/manager.py,sha256=0jc7dQxGgKY-vc3rzVrPevHPZ6BY7y4APaxGd74kKlk,3164
402
+ python_ddd_framework/settings/module.py,sha256=jnCyeiFAnQB2q3Zeyx-HN9xmHbpGouUrfB3K3gJ8ekE,3061
408
403
  python_ddd_framework/settings/permissions.py,sha256=2nPyZOZ9G-4mSCIqwNI0C9WUBhP0ZwqNjljP3XZD3PE,553
409
- python_ddd_framework/settings/provider.py,sha256=plT4x6UjBGn9B6-OmZYKswdto3NrE6Ib1lsyc1VUKoA,2871
410
- python_ddd_framework/settings/refresh.py,sha256=jYYqEdTU4cOXyHmdMEdw_94qybp4bzVPRdrM6WF8q6k,5413
411
- python_ddd_framework/settings/refresh_module.py,sha256=wDxWau4oZCtBiMO6TDXZFbZRcKTiYaTFGWPjZnaFNug,2225
404
+ python_ddd_framework/settings/provider.py,sha256=AvMrdWML_wbbJEqNb9IH8_OcUUX8QbdEpxw4GDibdA4,3216
412
405
  python_ddd_framework/settings/sqlalchemy/__init__.py,sha256=G-S-sSsxwfjZxr0mVAyXUBG0SIxC3ZCMtrdpMsZ14YM,158
413
406
  python_ddd_framework/settings/sqlalchemy/migrations/0001_settings.py,sha256=wx_GcxijY-43V9DoNNzdvfwg8d5oYWkmeWSe23xtXLA,1045
414
407
  python_ddd_framework/settings/sqlalchemy/migrations/0002_physical_delete.py,sha256=JtPmESosI_SIe4ijFuBFX7zvcgsnk-7dtjULipdC6NU,982
@@ -418,7 +411,7 @@ python_ddd_framework/settings/sqlalchemy/models.py,sha256=qKbRHhdm0uDPUUtLgeVeFv
418
411
  python_ddd_framework/settings/sqlalchemy/module.py,sha256=NbvxE40aSW1aWf7CbU3GRPN_WrvsQgUXp05hAAoDtcE,1212
419
412
  python_ddd_framework/settings/sqlalchemy/store.py,sha256=I9yRtpZBzNEYG0NANFAgVvVTY0_Qnl5JinXf00ebQwE,3597
420
413
  python_ddd_framework/settings/store.py,sha256=3tdF-0l0owUdWuNEd0K8OfbXncI38hx0pD6anU5GdmA,442
421
- python_ddd_framework/settings/values.py,sha256=KMRZDQu2FRNXDIdJwGVgHn656Hc5qRY2WQGjbBxfhO4,898
414
+ python_ddd_framework/settings/values.py,sha256=smw2jLySVP6aKefqmkOqHp8z9pjc2nGfEofE6uGzMZY,687
422
415
  python_ddd_framework/sqlalchemy/__init__.py,sha256=DP6PAJHevoc1i-n9ma8_2CAiPmAvWoN7opqRYzHR4Sg,2054
423
416
  python_ddd_framework/sqlalchemy/alembic_runtime/__init__.py,sha256=r-ZHMiEhca5M2xv3plW9U3fHfzt5lVQeDohEFqrTuv0,82
424
417
  python_ddd_framework/sqlalchemy/alembic_runtime/env.py,sha256=ArKiDG9n5JOPQG44oSrJUd3o96zyY-b2PPkGysFy674,1389
@@ -452,9 +445,9 @@ python_ddd_framework/unit_of_work/module.py,sha256=EjsUtCG7w56NmLU_xqBRiwJ65Mw5h
452
445
  python_ddd_framework/unit_of_work/options.py,sha256=AVcEmwwn39qvj8PLQetBeGzdU_eblABGuNLDIQU5tyI,3365
453
446
  python_ddd_framework/validation/__init__.py,sha256=nTN_BgIXMLHeUtMgSj2hpisWc1_sk6UWTapk30oh1us,100
454
447
  python_ddd_framework/validation/models.py,sha256=Ydz5ZYgqYMPZEXi9A3DA6beN5lD9AcMaImnxDf1aRXs,1737
455
- python_ddd_framework-0.7.3.dist-info/licenses/LICENSE,sha256=2sSWLZdtk4EhG2GlwpksXzmyux-G6Qiov0D27lE_bqw,805
456
- python_ddd_framework-0.7.3.dist-info/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt,sha256=0lCGUd5Pke2Up1Kqn1kRnHcwLlBz60E8xvR7WL4k_kE,1066
457
- python_ddd_framework-0.7.3.dist-info/WHEEL,sha256=si2p8vW03RnhyBnhWl4tj-ObJa78SpE-c9HsEtblGR0,80
458
- python_ddd_framework-0.7.3.dist-info/entry_points.txt,sha256=CMy4XBD8IKGUfHwe0YBmHmSJtjfnA3yyDHqsMZCLsc8,447
459
- python_ddd_framework-0.7.3.dist-info/METADATA,sha256=5FcVFcHFz-KN3-ify5NyNpNODv-o-U38hLv5GrpCRX4,67271
460
- python_ddd_framework-0.7.3.dist-info/RECORD,,
448
+ python_ddd_framework-0.9.0.dist-info/licenses/LICENSE,sha256=2sSWLZdtk4EhG2GlwpksXzmyux-G6Qiov0D27lE_bqw,805
449
+ python_ddd_framework-0.9.0.dist-info/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt,sha256=0lCGUd5Pke2Up1Kqn1kRnHcwLlBz60E8xvR7WL4k_kE,1066
450
+ python_ddd_framework-0.9.0.dist-info/WHEEL,sha256=si2p8vW03RnhyBnhWl4tj-ObJa78SpE-c9HsEtblGR0,80
451
+ python_ddd_framework-0.9.0.dist-info/entry_points.txt,sha256=CMy4XBD8IKGUfHwe0YBmHmSJtjfnA3yyDHqsMZCLsc8,447
452
+ python_ddd_framework-0.9.0.dist-info/METADATA,sha256=hk9U10YzJOgUMz5XK2ZTFvqjAwrhoBquHOzoMXqJQgI,71605
453
+ python_ddd_framework-0.9.0.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- """Settings 变更只通知标识,数据库仍是有效值的唯一权威。"""
2
-
3
- from pydantic import BaseModel, ConfigDict
4
-
5
- from ..notifications import NotificationDefinition
6
-
7
-
8
- class SettingChanged(BaseModel):
9
- model_config = ConfigDict(frozen=True, extra="forbid")
10
-
11
- name: str
12
-
13
-
14
- SETTINGS_CHANGED = NotificationDefinition("settings.changed", SettingChanged)
@@ -1,18 +0,0 @@
1
- """Settings handler 的关系声明;实例和生命周期仍由原生 DI 管理。"""
2
-
3
- from dataclasses import dataclass
4
- from typing import Any
5
-
6
- from ..modularity import AppModule, ModuleDeclaration
7
- from .handlers import SettingHandlerDefinition
8
-
9
-
10
- @dataclass(frozen=True, slots=True)
11
- class SettingHandlers(ModuleDeclaration):
12
- definitions: tuple[SettingHandlerDefinition[Any], ...]
13
-
14
- @property
15
- def required_module(self) -> type[AppModule]:
16
- from .module import SettingsModule
17
-
18
- return SettingsModule
@@ -1,43 +0,0 @@
1
- """长期组件的 typed 应用契约;执行上下文由组件自身负责。"""
2
-
3
- from __future__ import annotations
4
-
5
- import inspect
6
- from dataclasses import dataclass
7
- from typing import Generic, Protocol, TypeVar
8
-
9
- from .contracts import SettingDefinition
10
- from .errors import SettingDefinitionError
11
- from .values import SettingSnapshot
12
-
13
- _T = TypeVar("_T")
14
-
15
-
16
- class SettingHandler(Protocol[_T]):
17
- async def apply(self, snapshot: SettingSnapshot[_T]) -> None: ...
18
-
19
-
20
- @dataclass(frozen=True, slots=True)
21
- class SettingHandlerDefinition(Generic[_T]):
22
- setting: SettingDefinition[_T]
23
- handler_type: type[SettingHandler[_T]]
24
-
25
- def __post_init__(self) -> None:
26
- if not isinstance(self.setting, SettingDefinition):
27
- raise SettingDefinitionError(setting="handler", reason="setting must be a definition")
28
- if (
29
- not isinstance(self.handler_type, type)
30
- or inspect.isabstract(self.handler_type)
31
- or not inspect.iscoroutinefunction(getattr(self.handler_type, "apply", None))
32
- ):
33
- raise SettingDefinitionError(
34
- setting=self.setting.name, reason="handler must have an async apply method"
35
- )
36
-
37
-
38
- @dataclass(frozen=True, slots=True)
39
- class SettingApplicationStatus:
40
- setting: str
41
- handler: str
42
- last_applied: SettingSnapshot[object] | None
43
- error_type: str | None
@@ -1,41 +0,0 @@
1
- """Settings 是通用通知的使用方,重连和漏消息的补偿由 Settings 自身拥有。"""
2
-
3
- from __future__ import annotations
4
-
5
- from contextlib import AsyncExitStack
6
-
7
- from ..lifecycle import PreInitializeContext, ShutdownContext
8
- from ..modularity import AppModule
9
- from ..notifications import Notifications
10
- from ..notifications.declarations import NotificationDefinitions
11
- from ..notifications.module import NotificationsModule
12
- from .changes import SETTINGS_CHANGED, SettingChanged
13
- from .manager import SettingManager
14
- from .refresh import SettingsRefreshRuntime
15
- from .refresh_module import SettingsRefreshModule
16
-
17
-
18
- class SettingsNotificationsModule(AppModule):
19
- dependencies = (SettingsRefreshModule, NotificationsModule)
20
- notification_definitions = NotificationDefinitions((SETTINGS_CHANGED,))
21
-
22
- def __init__(self) -> None:
23
- self._subscriptions = AsyncExitStack()
24
-
25
- async def pre_initialize(self, context: PreInitializeContext) -> None:
26
- refresh = await context.container.get(SettingsRefreshRuntime)
27
- notifications = await context.container.get(Notifications)
28
- settings = await context.container.get(SettingManager)
29
-
30
- async def changed(message: SettingChanged) -> None:
31
- refresh.request_refresh(message.name)
32
-
33
- await self._subscriptions.enter_async_context(
34
- notifications.subscribe(SETTINGS_CHANGED, changed, on_reconnect=refresh.request_refresh)
35
- )
36
- settings._notifications = notifications
37
-
38
- async def shutdown(self, context: ShutdownContext) -> None:
39
- settings = await context.container.get(SettingManager)
40
- settings._notifications = None
41
- await self._subscriptions.aclose()