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.
- python_ddd_framework/__init__.py +2 -2
- python_ddd_framework/configuration/root.py +2 -0
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +6 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +1 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +6 -0
- 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 +6 -0
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +62 -2
- python_ddd_framework/settings/__init__.py +3 -7
- python_ddd_framework/settings/application.py +53 -21
- python_ddd_framework/settings/binding.py +14 -0
- python_ddd_framework/settings/catalog.py +4 -59
- python_ddd_framework/settings/contracts.py +73 -2
- python_ddd_framework/settings/management.py +3 -13
- python_ddd_framework/settings/manager.py +17 -34
- python_ddd_framework/settings/module.py +21 -9
- python_ddd_framework/settings/provider.py +10 -3
- python_ddd_framework/settings/values.py +0 -8
- {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/METADATA +27 -3
- {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/RECORD +24 -31
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/__init__.py.jinja +0 -1
- 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-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/WHEEL +0 -0
- {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {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,140 +0,0 @@
|
|
|
1
|
-
"""单进程 Settings 刷新 owner:合并读取、串行应用、失败后按周期重试。"""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
import logging
|
|
7
|
-
from contextlib import suppress
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
from typing import Any, ClassVar
|
|
10
|
-
|
|
11
|
-
from pydantic import ConfigDict, Field
|
|
12
|
-
|
|
13
|
-
from ..hosted_services import HostedService, HostedServiceContext
|
|
14
|
-
from ..invocation.runtime import _InvocationRuntime
|
|
15
|
-
from ..lifecycle import ShutdownReason
|
|
16
|
-
from ..observability.context import capture_trace_context, invocation_span
|
|
17
|
-
from ..options import BaseOptions, Options
|
|
18
|
-
from .contracts import SettingDefinition
|
|
19
|
-
from .handlers import SettingApplicationStatus, SettingHandler
|
|
20
|
-
from .provider import SettingProvider
|
|
21
|
-
from .values import SettingSnapshot, SettingValueSource
|
|
22
|
-
|
|
23
|
-
_LOGGER = logging.getLogger(__name__)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class SettingsRefreshOptions(BaseOptions):
|
|
27
|
-
model_config = ConfigDict(extra="forbid")
|
|
28
|
-
|
|
29
|
-
reconciliation_interval_seconds: float = Field(default=30.0, gt=0)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@dataclass(slots=True)
|
|
33
|
-
class _ApplicationRecord:
|
|
34
|
-
handler: SettingHandler[Any]
|
|
35
|
-
last_applied: SettingSnapshot[Any] | None = None
|
|
36
|
-
error_type: str | None = None
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class SettingsRefreshRuntime(HostedService):
|
|
40
|
-
run_in_background_processes: ClassVar[bool] = True
|
|
41
|
-
|
|
42
|
-
def __init__(
|
|
43
|
-
self,
|
|
44
|
-
settings: SettingProvider,
|
|
45
|
-
options: Options[SettingsRefreshOptions],
|
|
46
|
-
runtime: _InvocationRuntime,
|
|
47
|
-
) -> None:
|
|
48
|
-
self._settings = settings
|
|
49
|
-
self._options = options.value
|
|
50
|
-
self._runtime = runtime
|
|
51
|
-
self._definitions: dict[str, SettingDefinition[Any]] = {}
|
|
52
|
-
self._records: dict[str, list[_ApplicationRecord]] = {}
|
|
53
|
-
self._dirty: dict[str, dict[str, str]] = {}
|
|
54
|
-
self._wakeup = asyncio.Event()
|
|
55
|
-
self._task: asyncio.Task[None] | None = None
|
|
56
|
-
|
|
57
|
-
def _register(self, definition: SettingDefinition[Any], handler: SettingHandler[Any]) -> None:
|
|
58
|
-
self._definitions[definition.name] = definition
|
|
59
|
-
self._records.setdefault(definition.name, []).append(_ApplicationRecord(handler))
|
|
60
|
-
|
|
61
|
-
def request_refresh(self, name: str | None = None) -> None:
|
|
62
|
-
if name is None:
|
|
63
|
-
for key in self._definitions:
|
|
64
|
-
self._dirty.setdefault(key, {})
|
|
65
|
-
elif name in self._definitions:
|
|
66
|
-
self._dirty[name] = capture_trace_context(self._runtime.tracer)
|
|
67
|
-
if self._dirty:
|
|
68
|
-
self._wakeup.set()
|
|
69
|
-
|
|
70
|
-
@property
|
|
71
|
-
def statuses(self) -> tuple[SettingApplicationStatus, ...]:
|
|
72
|
-
return tuple(
|
|
73
|
-
SettingApplicationStatus(
|
|
74
|
-
name,
|
|
75
|
-
f"{type(record.handler).__module__}.{type(record.handler).__qualname__}",
|
|
76
|
-
record.last_applied,
|
|
77
|
-
record.error_type,
|
|
78
|
-
)
|
|
79
|
-
for name, records in self._records.items()
|
|
80
|
-
for record in records
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
async def start(self, context: HostedServiceContext) -> None:
|
|
84
|
-
self.request_refresh()
|
|
85
|
-
self._task = asyncio.create_task(self._run(), name=context.name)
|
|
86
|
-
|
|
87
|
-
async def stop(self, reason: ShutdownReason) -> None:
|
|
88
|
-
if self._task is not None:
|
|
89
|
-
self._task.cancel()
|
|
90
|
-
with suppress(asyncio.CancelledError):
|
|
91
|
-
await self._task
|
|
92
|
-
|
|
93
|
-
async def _run(self) -> None:
|
|
94
|
-
if not self._definitions:
|
|
95
|
-
await asyncio.Future[None]()
|
|
96
|
-
while True:
|
|
97
|
-
try:
|
|
98
|
-
await asyncio.wait_for(
|
|
99
|
-
self._wakeup.wait(), timeout=self._options.reconciliation_interval_seconds
|
|
100
|
-
)
|
|
101
|
-
except TimeoutError:
|
|
102
|
-
self.request_refresh()
|
|
103
|
-
# 取走本轮名字后立即清理;await 期间到达的新请求进入下一轮,不能被误清除。
|
|
104
|
-
requests = tuple(self._dirty.items())
|
|
105
|
-
self._dirty.clear()
|
|
106
|
-
self._wakeup.clear()
|
|
107
|
-
for name, carrier in requests:
|
|
108
|
-
with invocation_span(
|
|
109
|
-
self._runtime.tracer, f"refresh setting {name}", carrier=carrier
|
|
110
|
-
):
|
|
111
|
-
await self._refresh(name)
|
|
112
|
-
|
|
113
|
-
async def _refresh(self, name: str) -> None:
|
|
114
|
-
try:
|
|
115
|
-
latest = await self._settings.snapshot(self._definitions[name])
|
|
116
|
-
except Exception as error:
|
|
117
|
-
for record in self._records[name]:
|
|
118
|
-
record.error_type = type(error).__name__
|
|
119
|
-
_LOGGER.error("Settings refresh failed for %s: %s", name, type(error).__name__)
|
|
120
|
-
return
|
|
121
|
-
for record in self._records[name]:
|
|
122
|
-
previous = record.last_applied
|
|
123
|
-
if (
|
|
124
|
-
previous is not None
|
|
125
|
-
and record.error_type is None
|
|
126
|
-
and previous.value == latest.value
|
|
127
|
-
and (previous.source is SettingValueSource.UNSET)
|
|
128
|
-
== (latest.source is SettingValueSource.UNSET)
|
|
129
|
-
):
|
|
130
|
-
continue
|
|
131
|
-
try:
|
|
132
|
-
with invocation_span(self._runtime.tracer, f"apply setting {name}"):
|
|
133
|
-
await record.handler.apply(latest)
|
|
134
|
-
except Exception as error:
|
|
135
|
-
# 失败不能覆盖成功记录,下一次校对仍会按最后成功应用的值决定重试。
|
|
136
|
-
record.error_type = type(error).__name__
|
|
137
|
-
_LOGGER.error("Settings handler failed for %s: %s", name, record.error_type)
|
|
138
|
-
else:
|
|
139
|
-
record.last_applied = latest
|
|
140
|
-
record.error_type = None
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"""只组装正式 handler 与刷新运行时;组件应用规则由 handler 自己拥有。"""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from dishka import DEFAULT_COMPONENT, DependencyKey
|
|
6
|
-
|
|
7
|
-
from python_ddd_framework import ConfigureContext
|
|
8
|
-
|
|
9
|
-
from ..hosted_services.declarations import HostedServices
|
|
10
|
-
from ..lifecycle import PreInitializeContext, ShutdownContext
|
|
11
|
-
from ..modularity import AppModule, ModuleRef
|
|
12
|
-
from .catalog import SettingCatalog
|
|
13
|
-
from .manager import SettingManager
|
|
14
|
-
from .module import SettingsModule
|
|
15
|
-
from .refresh import SettingsRefreshOptions, SettingsRefreshRuntime
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class SettingsRefreshModule(AppModule):
|
|
19
|
-
dependencies = (
|
|
20
|
-
SettingsModule,
|
|
21
|
-
ModuleRef("python_ddd_framework.hosted_services.module:HostedServicesModule"),
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
def __init__(self) -> None:
|
|
25
|
-
self._manager: SettingManager | None = None
|
|
26
|
-
|
|
27
|
-
def configure(self, context: ConfigureContext) -> None:
|
|
28
|
-
context.configure(SettingsRefreshOptions, section="settings_refresh")
|
|
29
|
-
|
|
30
|
-
hosted_services = HostedServices((SettingsRefreshRuntime,))
|
|
31
|
-
|
|
32
|
-
async def pre_initialize(self, context: PreInitializeContext) -> None:
|
|
33
|
-
catalog = await context.container.get(SettingCatalog)
|
|
34
|
-
refresh = await context.container.get(SettingsRefreshRuntime)
|
|
35
|
-
for definition in catalog.handlers:
|
|
36
|
-
handler = await context.container.get(definition.handler_type)
|
|
37
|
-
refresh._register(definition.setting, handler)
|
|
38
|
-
# Manager 固定归 APP;查询最终原生图,含 alias,不构造服务或维护第二清单。
|
|
39
|
-
# 无存储仍应用只读值;已注册服务由原生 get 解析,构造失败不能降级。
|
|
40
|
-
if (
|
|
41
|
-
context.container.registry.get_factory(DependencyKey(SettingManager, DEFAULT_COMPONENT))
|
|
42
|
-
is not None
|
|
43
|
-
):
|
|
44
|
-
settings = await context.container.get(SettingManager)
|
|
45
|
-
settings._refresh = refresh
|
|
46
|
-
self._manager = settings
|
|
47
|
-
|
|
48
|
-
async def shutdown(self, context: ShutdownContext) -> None:
|
|
49
|
-
# 只解除本实例完成的接线;启动部分失败后的关闭不得重新构造依赖。
|
|
50
|
-
if self._manager is not None:
|
|
51
|
-
self._manager._refresh = None
|
|
52
|
-
self._manager = None
|
|
File without changes
|
{python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.9.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|