Coverage for src / c41811 / config / basic / environment.py: 100%
105 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-09 01:06 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-09 01:06 +0000
1# cython: language_level = 3 # noqa: ERA001
4"""
5环境变量配置数据实现
7.. versionadded:: 0.2.0
8"""
10from collections.abc import Callable
11from collections.abc import Iterable
12from collections.abc import MutableMapping
13from dataclasses import dataclass
14from dataclasses import field
15from functools import update_wrapper
16from typing import Any
17from typing import Self
18from typing import cast
19from typing import override
21import wrapt
23from .mapping import MappingConfigData
24from ..abc import PathLike
25from ..utils import Unset
28@dataclass
29class Difference:
30 """与初始化数据的差异"""
32 updated: set[str] = field(default_factory=set)
33 """
34 修改/新增的键
36 .. note::
37 实现的不是很完美,如果一个键更改为了另一个值再改回来仍然会被认为是被修改过的键
38 """ # noqa: RUF001
39 removed: set[str] = field(default_factory=set)
40 """
41 删除的键
42 """
44 def clear(self) -> None:
45 """清空差异"""
46 self.updated.clear()
47 self.removed.clear()
49 def __iadd__(self, other: Any) -> Self:
50 if not isinstance(other, Iterable):
51 return NotImplemented
52 other = set(other)
53 self.updated |= other
54 self.removed -= other
55 return self
57 def __isub__(self, other: Any) -> Self:
58 if not isinstance(other, Iterable):
59 return NotImplemented
60 other = set(other)
61 self.updated -= other
62 self.removed |= other
63 return self
65 def __bool__(self) -> bool:
66 return bool(self.updated and self.removed)
69# noinspection PyNewStyleGenericSyntax
70def diff_keys[F: Callable[..., Any]](func: F) -> F:
71 """
72 计算执行方法前后配置数据的键差异
74 :param func: 方法
75 :type func: F
77 :return: 方法
78 :rtype: F
79 """
81 @wrapt.decorator # type: ignore[arg-type]
82 def wrapper(wrapped: F, instance: Any, args: tuple[Any, ...], kwargs: dict[str, Any]) -> Any:
83 if not isinstance(instance, EnvironmentConfigData): # pragma: no cover
84 msg = f"instance must be {EnvironmentConfigData.__name__} but got {type(instance).__name__}"
85 raise TypeError(msg)
87 before = set(instance.keys())
88 before_never_changed = before - instance.difference.updated - instance.difference.removed
89 may_change = {k: instance[k] for k in before_never_changed}
91 result = wrapped(*args, **kwargs)
92 after = set(instance.keys())
94 added = after - before
95 deleted = before - after
97 instance.difference += added
98 instance.difference -= deleted
100 current_never_changed = before_never_changed - added - deleted
101 for may_changed in current_never_changed:
102 if may_change[may_changed] != instance[may_changed]:
103 instance.difference += {may_changed}
105 return result
107 return cast(F, update_wrapper(wrapper(func), func))
110class EnvironmentConfigData(MappingConfigData[MutableMapping[str, str]]):
111 """
112 环境变量配置数据
114 内部维护了与初始化参数的键差异
116 .. note::
117 :py:class:`~config.processor.OSEnv.OSEnvSL` 在保存时会重置差异数据
119 .. warning::
120 当前实现 `不会验证值的类型` ,当值不是字符串类型时,在实际写入 :py:data:`os.environ` 时会抛出错误,
121 请确保传入的值是字符串类型
122 """ # noqa: RUF002
124 def __init__(self, data: MutableMapping[str, str] | None = None):
125 """
126 :param data: 环境变量数据
127 :type data: MutableMapping[str, str] | None
128 """ # noqa: D205
129 super().__init__(data)
130 self.difference = Difference()
132 @diff_keys
133 @override
134 def modify(self, path: PathLike, value: str, *, allow_create: bool = True) -> Self:
135 return super().modify(path, value, allow_create=allow_create)
137 @diff_keys
138 @override
139 def delete(self, path: PathLike) -> Self:
140 return super().delete(path)
142 @diff_keys
143 @override
144 def unset(self, path: PathLike) -> Self:
145 return super().unset(path)
147 @diff_keys
148 @override
149 def setdefault[V](self, path: PathLike, default: V | None = None, *, return_raw_value: bool = False) -> V | Any:
150 return super().setdefault(path, default, return_raw_value=return_raw_value)
152 @diff_keys
153 @override
154 def clear(self) -> None:
155 super().clear()
157 @diff_keys
158 @override
159 def pop(self, path: PathLike, /, default: Any = Unset) -> Any:
160 return super().pop(path, default)
162 @diff_keys
163 @override
164 def popitem(self) -> Any:
165 return super().popitem()
167 @diff_keys
168 @override
169 def update(self, m: Any | None = None, /, **kwargs: str) -> None:
170 super().update(m, **kwargs)
172 @diff_keys
173 @override
174 def __setitem__(self, index: str, value: str) -> None:
175 super().__setitem__(index, value)
177 @diff_keys
178 @override
179 def __delitem__(self, index: str) -> None:
180 super().__delitem__(index)
182 @diff_keys
183 def __ior__(self, other: MutableMapping[str, str]) -> Self:
184 return super().__ior__(other) # type: ignore[misc, no-any-return]
187__all__ = (
188 "Difference",
189 "EnvironmentConfigData",
190)