python-ddd-framework 0.7.3__py3-none-any.whl → 0.8.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 -0
- python_ddd_framework/configuration/root.py +2 -0
- python_ddd_framework/settings/__init__.py +2 -1
- python_ddd_framework/settings/application.py +49 -12
- python_ddd_framework/settings/binding.py +14 -0
- python_ddd_framework/settings/contracts.py +73 -2
- python_ddd_framework/settings/manager.py +9 -2
- python_ddd_framework/settings/module.py +18 -0
- python_ddd_framework/settings/provider.py +10 -3
- {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.8.0.dist-info}/METADATA +9 -1
- {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.8.0.dist-info}/RECORD +15 -15
- {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.8.0.dist-info}/WHEEL +0 -0
- {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.8.0.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.8.0.dist-info}/licenses/LICENSE +0 -0
- {python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.8.0.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
python_ddd_framework/__init__.py
CHANGED
|
@@ -280,6 +280,7 @@ from .settings import (
|
|
|
280
280
|
SettingDefinitionContext,
|
|
281
281
|
SettingDefinitionError,
|
|
282
282
|
SettingDefinitionProvider,
|
|
283
|
+
SettingManagementAccess,
|
|
283
284
|
SettingManager,
|
|
284
285
|
SettingProvider,
|
|
285
286
|
)
|
|
@@ -528,6 +529,7 @@ __all__ = (
|
|
|
528
529
|
"SettingDefinitionError",
|
|
529
530
|
"SettingDefinitionProvider",
|
|
530
531
|
"SettingHandlers",
|
|
532
|
+
"SettingManagementAccess",
|
|
531
533
|
"SettingManager",
|
|
532
534
|
"SettingProvider",
|
|
533
535
|
"SettingsModule",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Global Runtime Settings 的 typed contract 与 Application-local catalog。"""
|
|
2
2
|
|
|
3
3
|
from .catalog import SettingCatalog
|
|
4
|
-
from .contracts import SettingDefinition
|
|
4
|
+
from .contracts import SettingDefinition, SettingManagementAccess
|
|
5
5
|
from .definitions import SettingDefinitionContext, SettingDefinitionProvider
|
|
6
6
|
from .errors import RuntimeSettingError, SettingDefinitionError
|
|
7
7
|
from .handlers import SettingApplicationStatus, SettingHandler, SettingHandlerDefinition
|
|
@@ -21,6 +21,7 @@ __all__ = (
|
|
|
21
21
|
"SettingDefinitionProvider",
|
|
22
22
|
"SettingHandler",
|
|
23
23
|
"SettingHandlerDefinition",
|
|
24
|
+
"SettingManagementAccess",
|
|
24
25
|
"SettingManager",
|
|
25
26
|
"SettingProvider",
|
|
26
27
|
"SettingSnapshot",
|
|
@@ -5,15 +5,19 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
from typing import Any, cast
|
|
7
7
|
|
|
8
|
+
from dishka import AsyncContainer
|
|
8
9
|
from pydantic import JsonValue
|
|
9
10
|
|
|
10
11
|
from ..application_services import ApplicationService, application_service
|
|
11
|
-
from ..authorization import authorize
|
|
12
|
+
from ..authorization import PermissionDeniedError, PermissionName, authorize
|
|
13
|
+
from ..authorization.checking import _check_authorization
|
|
14
|
+
from ..authorization.contracts import _AuthorizationRequirement
|
|
12
15
|
from ..configuration.immutability import _serialization_value
|
|
13
16
|
from ..errors import BusinessError, BusinessErrorDefinition, BusinessErrorField
|
|
14
17
|
from ..errors.base import ResourceNotFoundError
|
|
18
|
+
from ..invocation.runtime import _InvocationRuntime
|
|
15
19
|
from .catalog import SettingCatalog
|
|
16
|
-
from .contracts import SettingDefinition
|
|
20
|
+
from .contracts import SettingDefinition, SettingManagementAccess
|
|
17
21
|
from .management import (
|
|
18
22
|
SETTINGS_READ,
|
|
19
23
|
SETTINGS_WRITE,
|
|
@@ -32,27 +36,41 @@ _VALUE_FIELD = BusinessErrorField(UpdateSettingCommand, "value")
|
|
|
32
36
|
|
|
33
37
|
@application_service(provides=SettingsManagementApplicationService)
|
|
34
38
|
class _SettingsManagementApplicationService(ApplicationService):
|
|
35
|
-
def __init__(
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
catalog: SettingCatalog,
|
|
42
|
+
settings: SettingManager,
|
|
43
|
+
container: AsyncContainer,
|
|
44
|
+
runtime: _InvocationRuntime,
|
|
45
|
+
) -> None:
|
|
36
46
|
self._catalog = catalog
|
|
37
47
|
self._settings = settings
|
|
48
|
+
self._container = container
|
|
49
|
+
self._runtime = runtime
|
|
38
50
|
|
|
39
51
|
@authorize(SETTINGS_READ)
|
|
40
52
|
async def get_list(self) -> tuple[SettingView, ...]:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
result: list[SettingView] = []
|
|
54
|
+
for definition in self._catalog.definitions:
|
|
55
|
+
if definition.management_access is SettingManagementAccess.HIDDEN:
|
|
56
|
+
continue
|
|
57
|
+
try:
|
|
58
|
+
await self._check_permissions(definition.read_permission)
|
|
59
|
+
except PermissionDeniedError:
|
|
60
|
+
continue
|
|
61
|
+
result.append(_view(definition, await self.setting_provider.snapshot(definition)))
|
|
62
|
+
return tuple(result)
|
|
47
63
|
|
|
48
64
|
@authorize(SETTINGS_READ)
|
|
49
65
|
async def get(self, name: str) -> SettingView:
|
|
50
66
|
definition = self._definition(name)
|
|
67
|
+
await self._check_permissions(definition.read_permission)
|
|
51
68
|
return _view(definition, await self.setting_provider.snapshot(definition))
|
|
52
69
|
|
|
53
|
-
@authorize(SETTINGS_WRITE)
|
|
70
|
+
@authorize(SETTINGS_READ, SETTINGS_WRITE)
|
|
54
71
|
async def update(self, name: str, command: UpdateSettingCommand) -> SettingWriteResult:
|
|
55
72
|
definition = self._definition(name)
|
|
73
|
+
await self._check_write(definition)
|
|
56
74
|
try:
|
|
57
75
|
# JSON 数组/日期须由 Pydantic 的 JSON 模式解释,不能误用 strict Python 输入语义。
|
|
58
76
|
value = definition._validate_json(json.dumps(command.value, allow_nan=False))
|
|
@@ -65,9 +83,10 @@ class _SettingsManagementApplicationService(ApplicationService):
|
|
|
65
83
|
setting=_view(definition, saved.snapshot), notification=saved.notification
|
|
66
84
|
)
|
|
67
85
|
|
|
68
|
-
@authorize(SETTINGS_WRITE)
|
|
86
|
+
@authorize(SETTINGS_READ, SETTINGS_WRITE)
|
|
69
87
|
async def reset(self, name: str, command: ResetSettingCommand) -> SettingWriteResult:
|
|
70
88
|
definition = self._definition(name)
|
|
89
|
+
await self._check_write(definition)
|
|
71
90
|
saved = await self._settings.reset(definition, expected_version=command.expected_version)
|
|
72
91
|
return SettingWriteResult(
|
|
73
92
|
setting=_view(definition, saved.snapshot), notification=saved.notification
|
|
@@ -75,10 +94,28 @@ class _SettingsManagementApplicationService(ApplicationService):
|
|
|
75
94
|
|
|
76
95
|
def _definition(self, name: str) -> SettingDefinition[Any]:
|
|
77
96
|
definition = self._catalog.get(name)
|
|
78
|
-
if definition is None:
|
|
97
|
+
if definition is None or definition.management_access is SettingManagementAccess.HIDDEN:
|
|
79
98
|
raise ResourceNotFoundError("Setting not found")
|
|
80
99
|
return definition
|
|
81
100
|
|
|
101
|
+
async def _check_write(self, definition: SettingDefinition[Any]) -> None:
|
|
102
|
+
if (
|
|
103
|
+
definition.management_access is not SettingManagementAccess.READ_WRITE
|
|
104
|
+
or SettingValueSource.RUNTIME not in definition.allowed_sources
|
|
105
|
+
):
|
|
106
|
+
raise PermissionDeniedError(permission=str(SETTINGS_WRITE))
|
|
107
|
+
# 写入响应包含完整快照;读权限须在校验设置值及执行保存前成立。
|
|
108
|
+
await self._check_permissions(definition.read_permission, definition.write_permission)
|
|
109
|
+
|
|
110
|
+
async def _check_permissions(self, *permissions: PermissionName | None) -> None:
|
|
111
|
+
required = tuple(dict.fromkeys(item for item in permissions if item is not None))
|
|
112
|
+
if required:
|
|
113
|
+
await _check_authorization(
|
|
114
|
+
self._container,
|
|
115
|
+
self._runtime.current_user,
|
|
116
|
+
_AuthorizationRequirement(True, required, False),
|
|
117
|
+
)
|
|
118
|
+
|
|
82
119
|
|
|
83
120
|
def _view(definition: SettingDefinition[Any], snapshot: SettingSnapshot[Any]) -> SettingView:
|
|
84
121
|
# HTTP DTO 是独立 JSON 副本;不会把内部不可变快照的容器直接交给调用方。
|
|
@@ -6,9 +6,11 @@ from dishka import Provider, Scope
|
|
|
6
6
|
|
|
7
7
|
from ..configuration import ConfigurationRoot
|
|
8
8
|
from .catalog import SettingCatalog
|
|
9
|
+
from .errors import SettingDefinitionError
|
|
9
10
|
from .manager import SettingManager
|
|
10
11
|
from .provider import SettingProvider
|
|
11
12
|
from .store import RuntimeSettingStore
|
|
13
|
+
from .values import SettingValueSource
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
def _provide_settings(
|
|
@@ -18,6 +20,18 @@ def _provide_settings(
|
|
|
18
20
|
*,
|
|
19
21
|
has_store: bool,
|
|
20
22
|
) -> None:
|
|
23
|
+
# 检查冻结的输入层,不能让存储覆盖或后续配置遮住被禁止的配置贡献。
|
|
24
|
+
for definition in catalog.definitions:
|
|
25
|
+
path = f"settings.{definition.name}"
|
|
26
|
+
if (
|
|
27
|
+
SettingValueSource.CONFIGURATION not in definition.allowed_sources
|
|
28
|
+
and configuration._section_inputs(path)
|
|
29
|
+
):
|
|
30
|
+
raise SettingDefinitionError(
|
|
31
|
+
setting=definition.name,
|
|
32
|
+
reason=f"configuration source is not allowed at {path}",
|
|
33
|
+
source=catalog.sources.get(definition.name),
|
|
34
|
+
)
|
|
21
35
|
if has_store:
|
|
22
36
|
|
|
23
37
|
def reader(store: RuntimeSettingStore) -> SettingProvider:
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from enum import Enum
|
|
5
6
|
from typing import Generic, TypeVar, cast, get_args, get_origin
|
|
6
7
|
|
|
7
8
|
from pydantic import BaseModel, Secret, SecretBytes, SecretStr, TypeAdapter, ValidationError
|
|
8
9
|
|
|
10
|
+
from ..authorization import PermissionName
|
|
9
11
|
from ..configuration.immutability import (
|
|
10
12
|
_serialization_value,
|
|
11
13
|
freeze_typed_value,
|
|
@@ -13,16 +15,65 @@ from ..configuration.immutability import (
|
|
|
13
15
|
)
|
|
14
16
|
from ..configuration.values import _looks_secret_name, canonical_path
|
|
15
17
|
from .errors import RuntimeSettingError, SettingDefinitionError
|
|
18
|
+
from .values import SettingValueSource
|
|
16
19
|
|
|
17
20
|
_T = TypeVar("_T")
|
|
18
21
|
_MISSING = object()
|
|
22
|
+
_ALL_VALUE_SOURCES = frozenset(
|
|
23
|
+
source for source in SettingValueSource if source is not SettingValueSource.UNSET
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SettingManagementAccess(str, Enum):
|
|
28
|
+
"""只限制通用管理入口;内部读写仍遵守设置自身的来源与值契约。"""
|
|
29
|
+
|
|
30
|
+
HIDDEN = "hidden"
|
|
31
|
+
READ_ONLY = "read_only"
|
|
32
|
+
READ_WRITE = "read_write"
|
|
19
33
|
|
|
20
34
|
|
|
21
35
|
class SettingDefinition(Generic[_T]):
|
|
22
|
-
__slots__ = (
|
|
36
|
+
__slots__ = (
|
|
37
|
+
"_adapter",
|
|
38
|
+
"_allowed_sources",
|
|
39
|
+
"_default",
|
|
40
|
+
"_management_access",
|
|
41
|
+
"_name",
|
|
42
|
+
"_read_permission",
|
|
43
|
+
"_value_type",
|
|
44
|
+
"_write_permission",
|
|
45
|
+
)
|
|
23
46
|
|
|
24
|
-
def __init__(
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
name: str,
|
|
50
|
+
value_type: type[_T],
|
|
51
|
+
*,
|
|
52
|
+
default: object = _MISSING,
|
|
53
|
+
allowed_sources: frozenset[SettingValueSource] = _ALL_VALUE_SOURCES,
|
|
54
|
+
management_access: SettingManagementAccess = SettingManagementAccess.READ_WRITE,
|
|
55
|
+
read_permission: PermissionName | None = None,
|
|
56
|
+
write_permission: PermissionName | None = None,
|
|
57
|
+
) -> None:
|
|
25
58
|
_validate_setting_name(name)
|
|
59
|
+
if (
|
|
60
|
+
not isinstance(allowed_sources, frozenset)
|
|
61
|
+
or not allowed_sources
|
|
62
|
+
or any(
|
|
63
|
+
not isinstance(source, SettingValueSource) or source is SettingValueSource.UNSET
|
|
64
|
+
for source in allowed_sources
|
|
65
|
+
)
|
|
66
|
+
):
|
|
67
|
+
raise SettingDefinitionError(
|
|
68
|
+
setting=name, reason="allowed_sources must contain value sources, not UNSET"
|
|
69
|
+
)
|
|
70
|
+
if not isinstance(management_access, SettingManagementAccess):
|
|
71
|
+
raise SettingDefinitionError(setting=name, reason="invalid management_access")
|
|
72
|
+
for permission in (read_permission, write_permission):
|
|
73
|
+
if permission is not None and not isinstance(permission, PermissionName):
|
|
74
|
+
raise SettingDefinitionError(
|
|
75
|
+
setting=name, reason="permissions must be PermissionName values"
|
|
76
|
+
)
|
|
26
77
|
if not isinstance(value_type, type):
|
|
27
78
|
raise SettingDefinitionError(setting=name, reason="value_type must be a runtime type")
|
|
28
79
|
if _contains_secret_type(value_type):
|
|
@@ -46,6 +97,10 @@ class SettingDefinition(Generic[_T]):
|
|
|
46
97
|
self._value_type = value_type
|
|
47
98
|
self._adapter = adapter
|
|
48
99
|
self._default = validated_default
|
|
100
|
+
self._allowed_sources = allowed_sources
|
|
101
|
+
self._management_access = management_access
|
|
102
|
+
self._read_permission = read_permission
|
|
103
|
+
self._write_permission = write_permission
|
|
49
104
|
|
|
50
105
|
@property
|
|
51
106
|
def name(self) -> str:
|
|
@@ -59,6 +114,22 @@ class SettingDefinition(Generic[_T]):
|
|
|
59
114
|
def has_default(self) -> bool:
|
|
60
115
|
return self._default is not _MISSING
|
|
61
116
|
|
|
117
|
+
@property
|
|
118
|
+
def allowed_sources(self) -> frozenset[SettingValueSource]:
|
|
119
|
+
return self._allowed_sources
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def management_access(self) -> SettingManagementAccess:
|
|
123
|
+
return self._management_access
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def read_permission(self) -> PermissionName | None:
|
|
127
|
+
return self._read_permission
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def write_permission(self) -> PermissionName | None:
|
|
131
|
+
return self._write_permission
|
|
132
|
+
|
|
62
133
|
def _validate_python(self, value: object) -> _T:
|
|
63
134
|
return cast(
|
|
64
135
|
_T, freeze_typed_value(self._adapter.validate_python(value), allow_secrets=False)
|
|
@@ -49,7 +49,7 @@ class SettingManager:
|
|
|
49
49
|
async def update(
|
|
50
50
|
self, definition: SettingDefinition[_T], value: _T, *, expected_version: UUID | None
|
|
51
51
|
) -> SettingWriteOutcome[_T]:
|
|
52
|
-
self.
|
|
52
|
+
self._require_write(definition, operation="set")
|
|
53
53
|
try:
|
|
54
54
|
validated, serialized = definition._dump_json(value)
|
|
55
55
|
except (TypeError, ValidationError, PydanticSerializationError):
|
|
@@ -68,13 +68,20 @@ class SettingManager:
|
|
|
68
68
|
async def reset(
|
|
69
69
|
self, definition: SettingDefinition[_T], *, expected_version: UUID | None
|
|
70
70
|
) -> SettingWriteOutcome[_T]:
|
|
71
|
-
self.
|
|
71
|
+
self._require_write(definition, operation="reset")
|
|
72
72
|
# 默认值先校验,不能在物理删除已经提交之后再抛出配置校验失败。
|
|
73
73
|
fallback = self._provider._resolve(definition, StoredSetting(None, expected_version))
|
|
74
74
|
saved = await self._store.write(definition.name, None, expected_version=expected_version)
|
|
75
75
|
snapshot = SettingSnapshot(definition.name, fallback.value, fallback.source, saved.version)
|
|
76
76
|
return SettingWriteOutcome(snapshot, await self._committed(definition.name))
|
|
77
77
|
|
|
78
|
+
def _require_write(self, definition: SettingDefinition[Any], *, operation: str) -> None:
|
|
79
|
+
self._catalog.require_owned(definition)
|
|
80
|
+
if SettingValueSource.RUNTIME not in definition.allowed_sources:
|
|
81
|
+
raise RuntimeSettingError(
|
|
82
|
+
setting=definition.name, operation=operation, error_type="SourceNotAllowed"
|
|
83
|
+
)
|
|
84
|
+
|
|
78
85
|
async def _committed(self, name: str) -> NotificationPublishStatus:
|
|
79
86
|
# 本地刷新先入队;Redis 降级不会撤销短事务已经提交的结果。
|
|
80
87
|
if self._refresh is not None:
|
|
@@ -2,6 +2,7 @@
|
|
|
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 (
|
|
@@ -12,6 +13,7 @@ from ..services.composition import (
|
|
|
12
13
|
from ..services.registration import RegistrationDecision, RegistrationKind
|
|
13
14
|
from .binding import _provide_settings
|
|
14
15
|
from .catalog import SettingCatalog, _build_setting_catalog, _contribute_setting_handler_defaults
|
|
16
|
+
from .errors import SettingDefinitionError
|
|
15
17
|
from .store import RuntimeSettingStore
|
|
16
18
|
|
|
17
19
|
|
|
@@ -57,3 +59,19 @@ class SettingsManagementModule(AppModule):
|
|
|
57
59
|
"python_ddd_framework.settings.application",
|
|
58
60
|
"python_ddd_framework.settings.permissions",
|
|
59
61
|
)
|
|
62
|
+
|
|
63
|
+
def configure(self, context: ConfigureContext) -> None:
|
|
64
|
+
context.services.add_composition(ServiceComposition(bind=self._bind))
|
|
65
|
+
|
|
66
|
+
def _bind(self, context: ServiceBindingContext) -> None:
|
|
67
|
+
# 全局 discovery 屏障后核对实际权限目录;只读 Settings 无需装配授权服务。
|
|
68
|
+
catalog = context.modules.instance(SettingsModule).catalog
|
|
69
|
+
permissions = context.modules.instance(AuthorizationModule).catalog
|
|
70
|
+
for definition in catalog.definitions:
|
|
71
|
+
for permission in (definition.read_permission, definition.write_permission):
|
|
72
|
+
if permission is not None and permission not in permissions:
|
|
73
|
+
raise SettingDefinitionError(
|
|
74
|
+
setting=definition.name,
|
|
75
|
+
reason=f"permission {permission} is not defined",
|
|
76
|
+
source=catalog.sources.get(definition.name),
|
|
77
|
+
)
|
|
@@ -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 =
|
|
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
|
|
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:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-ddd-framework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
|
@@ -314,6 +314,14 @@ New `ddd` modules contain the full responsibility directories and necessary comp
|
|
|
314
314
|
|
|
315
315
|
See the [migration guide](docs/development.md#数据库聚合与迁移) and [verification record](docs/status.md#http-回收与迁移来源判定未发布).
|
|
316
316
|
|
|
317
|
+
## Settings policies (0.8.0)
|
|
318
|
+
|
|
319
|
+
`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.
|
|
320
|
+
|
|
321
|
+
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.
|
|
322
|
+
|
|
323
|
+
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.
|
|
324
|
+
|
|
317
325
|
## Invocation performance (0.7.2)
|
|
318
326
|
|
|
319
327
|
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.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
python_ddd_framework/__init__.py,sha256=
|
|
1
|
+
python_ddd_framework/__init__.py,sha256=P4vRQIXbhDQk8zp_5_ToXK2lRJdPLUvAZ8iC-t6bg7U,15476
|
|
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
|
|
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
|
|
@@ -390,23 +390,23 @@ python_ddd_framework/services/registration.py,sha256=AsvhVv_szq8TcaXdUMvOY6fNNp8
|
|
|
390
390
|
python_ddd_framework/services/relocation.py,sha256=oMel_8RPFtPHDE9KrUepzx1nJpiug0EaNK7RWEKZFC0,3763
|
|
391
391
|
python_ddd_framework/services/repository.py,sha256=jjjCmpcz4deAZmDNSM44WlDmh9msFO53qN116CHdBSM,1686
|
|
392
392
|
python_ddd_framework/services/runtime.py,sha256=57G5jNjOiexpSqTCogdI18u5xeEK2YglztNqDtFcsZI,9300
|
|
393
|
-
python_ddd_framework/settings/__init__.py,sha256=
|
|
394
|
-
python_ddd_framework/settings/application.py,sha256=
|
|
395
|
-
python_ddd_framework/settings/binding.py,sha256=
|
|
393
|
+
python_ddd_framework/settings/__init__.py,sha256=j9byBRTyegmIEK1KD6Q3UkHurPCnMUYJdnlhXTWL_wo,1090
|
|
394
|
+
python_ddd_framework/settings/application.py,sha256=3hAlheeHcwSIS_1bFQmv7qjsAMIk7JYq9LnQLITfOWk,5666
|
|
395
|
+
python_ddd_framework/settings/binding.py,sha256=_wDIsKe-p6WuKaQB9NvhbxCm9aYbiN95m7erJS35T8Y,1621
|
|
396
396
|
python_ddd_framework/settings/catalog.py,sha256=fqVeFr3DuJWrNUfIuIwfivVhuCobQpWe32EmRieuUVE,4880
|
|
397
397
|
python_ddd_framework/settings/changes.py,sha256=zpHhCJY83s-KIs7NkjQAmIxzY7cJfT8FD4A08gF9NfE,367
|
|
398
|
-
python_ddd_framework/settings/contracts.py,sha256=
|
|
398
|
+
python_ddd_framework/settings/contracts.py,sha256=v_Wn7kr3_JQYH-rXeusR_YWvtwR3RT_KxQqnvVD7uZI,6899
|
|
399
399
|
python_ddd_framework/settings/declarations.py,sha256=FeFrgw6fAdjX6bGaajjnO2tsrfxEvKW6sZrRfTAxCEs,526
|
|
400
400
|
python_ddd_framework/settings/definition_discovery.py,sha256=zHN3-coqzdEb--mecv00v7ifWzzCfJRZ-BrFcdVx4u4,3002
|
|
401
401
|
python_ddd_framework/settings/definitions.py,sha256=hkWO0dqOoBmd7pKv-cUsCZaEU0AeUEULVLW1Rl8uwdQ,1687
|
|
402
402
|
python_ddd_framework/settings/errors.py,sha256=ZcU1nMB44_Rq9Wl5bw83s3Y066LxSLrFqOW_gG6ViS4,836
|
|
403
403
|
python_ddd_framework/settings/handlers.py,sha256=A0GWDKRXASlCoWm_Wduul0thAv4xW4SG305KXd6wY-4,1369
|
|
404
404
|
python_ddd_framework/settings/management.py,sha256=dJhyZYfNA5Qsup9yt2dKOrIwGq3OnNkDxskJniBV3BI,1656
|
|
405
|
-
python_ddd_framework/settings/manager.py,sha256=
|
|
406
|
-
python_ddd_framework/settings/module.py,sha256=
|
|
405
|
+
python_ddd_framework/settings/manager.py,sha256=5pWLFB7EHMUsqnz-Gy615A2Sxv0HsKIEQtgmm0zl70A,4561
|
|
406
|
+
python_ddd_framework/settings/module.py,sha256=KQydGp507MUlcykRWVUa36r5-_--BRJn88QLirxotAI,3340
|
|
407
407
|
python_ddd_framework/settings/notifications.py,sha256=tVSXjhhJWhkMcOakSmeOfXT1X02Hcx2xRU8Whr8rSig,1700
|
|
408
408
|
python_ddd_framework/settings/permissions.py,sha256=2nPyZOZ9G-4mSCIqwNI0C9WUBhP0ZwqNjljP3XZD3PE,553
|
|
409
|
-
python_ddd_framework/settings/provider.py,sha256=
|
|
409
|
+
python_ddd_framework/settings/provider.py,sha256=AvMrdWML_wbbJEqNb9IH8_OcUUX8QbdEpxw4GDibdA4,3216
|
|
410
410
|
python_ddd_framework/settings/refresh.py,sha256=jYYqEdTU4cOXyHmdMEdw_94qybp4bzVPRdrM6WF8q6k,5413
|
|
411
411
|
python_ddd_framework/settings/refresh_module.py,sha256=wDxWau4oZCtBiMO6TDXZFbZRcKTiYaTFGWPjZnaFNug,2225
|
|
412
412
|
python_ddd_framework/settings/sqlalchemy/__init__.py,sha256=G-S-sSsxwfjZxr0mVAyXUBG0SIxC3ZCMtrdpMsZ14YM,158
|
|
@@ -452,9 +452,9 @@ python_ddd_framework/unit_of_work/module.py,sha256=EjsUtCG7w56NmLU_xqBRiwJ65Mw5h
|
|
|
452
452
|
python_ddd_framework/unit_of_work/options.py,sha256=AVcEmwwn39qvj8PLQetBeGzdU_eblABGuNLDIQU5tyI,3365
|
|
453
453
|
python_ddd_framework/validation/__init__.py,sha256=nTN_BgIXMLHeUtMgSj2hpisWc1_sk6UWTapk30oh1us,100
|
|
454
454
|
python_ddd_framework/validation/models.py,sha256=Ydz5ZYgqYMPZEXi9A3DA6beN5lD9AcMaImnxDf1aRXs,1737
|
|
455
|
-
python_ddd_framework-0.
|
|
456
|
-
python_ddd_framework-0.
|
|
457
|
-
python_ddd_framework-0.
|
|
458
|
-
python_ddd_framework-0.
|
|
459
|
-
python_ddd_framework-0.
|
|
460
|
-
python_ddd_framework-0.
|
|
455
|
+
python_ddd_framework-0.8.0.dist-info/licenses/LICENSE,sha256=2sSWLZdtk4EhG2GlwpksXzmyux-G6Qiov0D27lE_bqw,805
|
|
456
|
+
python_ddd_framework-0.8.0.dist-info/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt,sha256=0lCGUd5Pke2Up1Kqn1kRnHcwLlBz60E8xvR7WL4k_kE,1066
|
|
457
|
+
python_ddd_framework-0.8.0.dist-info/WHEEL,sha256=si2p8vW03RnhyBnhWl4tj-ObJa78SpE-c9HsEtblGR0,80
|
|
458
|
+
python_ddd_framework-0.8.0.dist-info/entry_points.txt,sha256=CMy4XBD8IKGUfHwe0YBmHmSJtjfnA3yyDHqsMZCLsc8,447
|
|
459
|
+
python_ddd_framework-0.8.0.dist-info/METADATA,sha256=IbP9K_ypVhMhck9GNfdMSwDRTeyqV_Muvph0XxBUDDY,68850
|
|
460
|
+
python_ddd_framework-0.8.0.dist-info/RECORD,,
|
|
File without changes
|
{python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.8.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{python_ddd_framework-0.7.3.dist-info → python_ddd_framework-0.8.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|