omlish 0.0.0.dev192__py3-none-any.whl → 0.0.0.dev194__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. omlish/.manifests.json +17 -3
  2. omlish/__about__.py +2 -2
  3. omlish/codecs/base.py +2 -0
  4. omlish/configs/__init__.py +0 -5
  5. omlish/configs/formats.py +247 -0
  6. omlish/configs/nginx.py +72 -0
  7. omlish/configs/processing/__init__.py +0 -0
  8. omlish/configs/{flattening.py → processing/flattening.py} +31 -33
  9. omlish/configs/processing/inheritance.py +58 -0
  10. omlish/configs/processing/matching.py +53 -0
  11. omlish/configs/processing/names.py +50 -0
  12. omlish/configs/processing/rewriting.py +141 -0
  13. omlish/configs/processing/strings.py +45 -0
  14. omlish/configs/types.py +9 -0
  15. omlish/formats/__init__.py +4 -0
  16. omlish/formats/ini/__init__.py +0 -0
  17. omlish/formats/ini/codec.py +26 -0
  18. omlish/formats/ini/sections.py +45 -0
  19. omlish/formats/toml/__init__.py +0 -0
  20. omlish/formats/{toml.py → toml/codec.py} +2 -2
  21. omlish/formats/toml/parser.py +827 -0
  22. omlish/formats/toml/writer.py +124 -0
  23. omlish/lang/__init__.py +4 -1
  24. omlish/lang/iterables.py +0 -7
  25. omlish/lite/configs.py +38 -0
  26. omlish/lite/types.py +9 -0
  27. omlish/text/glyphsplit.py +25 -10
  28. {omlish-0.0.0.dev192.dist-info → omlish-0.0.0.dev194.dist-info}/METADATA +1 -1
  29. {omlish-0.0.0.dev192.dist-info → omlish-0.0.0.dev194.dist-info}/RECORD +33 -17
  30. omlish/configs/strings.py +0 -96
  31. {omlish-0.0.0.dev192.dist-info → omlish-0.0.0.dev194.dist-info}/LICENSE +0 -0
  32. {omlish-0.0.0.dev192.dist-info → omlish-0.0.0.dev194.dist-info}/WHEEL +0 -0
  33. {omlish-0.0.0.dev192.dist-info → omlish-0.0.0.dev194.dist-info}/entry_points.txt +0 -0
  34. {omlish-0.0.0.dev192.dist-info → omlish-0.0.0.dev194.dist-info}/top_level.txt +0 -0
omlish/.manifests.json CHANGED
@@ -51,6 +51,20 @@
51
51
  }
52
52
  }
53
53
  },
54
+ {
55
+ "module": ".formats.ini.codec",
56
+ "attr": "_INI_LAZY_CODEC",
57
+ "file": "omlish/formats/ini/codec.py",
58
+ "line": 25,
59
+ "value": {
60
+ "$.codecs.base.LazyLoadedCodec": {
61
+ "mod_name": "omlish.formats.ini.codec",
62
+ "attr_name": "INI_CODEC",
63
+ "name": "ini",
64
+ "aliases": null
65
+ }
66
+ }
67
+ },
54
68
  {
55
69
  "module": ".formats.json.codecs",
56
70
  "attr": "_JSON_LAZY_CODEC",
@@ -136,13 +150,13 @@
136
150
  }
137
151
  },
138
152
  {
139
- "module": ".formats.toml",
153
+ "module": ".formats.toml.codec",
140
154
  "attr": "_TOML_LAZY_CODEC",
141
- "file": "omlish/formats/toml.py",
155
+ "file": "omlish/formats/toml/codec.py",
142
156
  "line": 16,
143
157
  "value": {
144
158
  "$.codecs.base.LazyLoadedCodec": {
145
- "mod_name": "omlish.formats.toml",
159
+ "mod_name": "omlish.formats.toml.codec",
146
160
  "attr_name": "TOML_CODEC",
147
161
  "name": "toml",
148
162
  "aliases": null
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev192'
2
- __revision__ = '75df1ba4f1cbe21d9930a1f467e7e99c5c23c621'
1
+ __version__ = '0.0.0.dev194'
2
+ __revision__ = '39cfe872573628af34684eb1ec41a0ccb127ad0f'
3
3
 
4
4
 
5
5
  #
omlish/codecs/base.py CHANGED
@@ -1,6 +1,8 @@
1
1
  """
2
2
  TODO:
3
3
  - bytes-like - bytearray, memoryview
4
+ - FileCodec
5
+ - implement options
4
6
  """
5
7
  import abc
6
8
  import typing as ta
@@ -1,5 +0,0 @@
1
- from .classes import ( # noqa
2
- Config,
3
- Configurable,
4
- get_impl,
5
- )
@@ -0,0 +1,247 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ Notes:
5
+ - necessarily string-oriented
6
+ - single file, as this is intended to be amalg'd and thus all included anyway
7
+
8
+ TODO:
9
+ - ConfigDataMapper? to_map -> ConfigMap?
10
+ - nginx ?
11
+ - raw ?
12
+ """
13
+ import abc
14
+ import collections.abc
15
+ import configparser
16
+ import dataclasses as dc
17
+ import json
18
+ import os.path
19
+ import typing as ta
20
+
21
+ from ..formats.ini.sections import IniSectionSettingsMap
22
+ from ..formats.ini.sections import extract_ini_sections
23
+ from ..formats.ini.sections import render_ini_sections
24
+ from ..formats.toml.parser import toml_loads
25
+ from ..formats.toml.writer import TomlWriter
26
+ from ..lite.check import check
27
+ from ..lite.json import json_dumps_pretty
28
+ from .types import ConfigMap
29
+
30
+
31
+ T = ta.TypeVar('T')
32
+ ConfigDataT = ta.TypeVar('ConfigDataT', bound='ConfigData')
33
+
34
+
35
+ ##
36
+
37
+
38
+ @dc.dataclass(frozen=True)
39
+ class ConfigData(abc.ABC): # noqa
40
+ @abc.abstractmethod
41
+ def as_map(self) -> ConfigMap:
42
+ raise NotImplementedError
43
+
44
+
45
+ #
46
+
47
+
48
+ class ConfigLoader(abc.ABC, ta.Generic[ConfigDataT]):
49
+ @property
50
+ def file_exts(self) -> ta.Sequence[str]:
51
+ return ()
52
+
53
+ def match_file(self, n: str) -> bool:
54
+ return '.' in n and n.split('.')[-1] in check.not_isinstance(self.file_exts, str)
55
+
56
+ #
57
+
58
+ def load_file(self, p: str) -> ConfigDataT:
59
+ with open(p) as f:
60
+ return self.load_str(f.read())
61
+
62
+ @abc.abstractmethod
63
+ def load_str(self, s: str) -> ConfigDataT:
64
+ raise NotImplementedError
65
+
66
+
67
+ #
68
+
69
+
70
+ class ConfigRenderer(abc.ABC, ta.Generic[ConfigDataT]):
71
+ @property
72
+ @abc.abstractmethod
73
+ def data_cls(self) -> ta.Type[ConfigDataT]:
74
+ raise NotImplementedError
75
+
76
+ def match_data(self, d: ConfigDataT) -> bool:
77
+ return isinstance(d, self.data_cls)
78
+
79
+ #
80
+
81
+ @abc.abstractmethod
82
+ def render(self, d: ConfigDataT) -> str:
83
+ raise NotImplementedError
84
+
85
+
86
+ ##
87
+
88
+
89
+ @dc.dataclass(frozen=True)
90
+ class ObjConfigData(ConfigData, abc.ABC):
91
+ obj: ta.Any
92
+
93
+ def as_map(self) -> ConfigMap:
94
+ return check.isinstance(self.obj, collections.abc.Mapping)
95
+
96
+
97
+ ##
98
+
99
+
100
+ @dc.dataclass(frozen=True)
101
+ class JsonConfigData(ObjConfigData):
102
+ pass
103
+
104
+
105
+ class JsonConfigLoader(ConfigLoader[JsonConfigData]):
106
+ file_exts = ('json',)
107
+
108
+ def load_str(self, s: str) -> JsonConfigData:
109
+ return JsonConfigData(json.loads(s))
110
+
111
+
112
+ class JsonConfigRenderer(ConfigRenderer[JsonConfigData]):
113
+ data_cls = JsonConfigData
114
+
115
+ def render(self, d: JsonConfigData) -> str:
116
+ return json_dumps_pretty(d.obj)
117
+
118
+
119
+ ##
120
+
121
+
122
+ @dc.dataclass(frozen=True)
123
+ class TomlConfigData(ObjConfigData):
124
+ pass
125
+
126
+
127
+ class TomlConfigLoader(ConfigLoader[TomlConfigData]):
128
+ file_exts = ('toml',)
129
+
130
+ def load_str(self, s: str) -> TomlConfigData:
131
+ return TomlConfigData(toml_loads(s))
132
+
133
+
134
+ class TomlConfigRenderer(ConfigRenderer[TomlConfigData]):
135
+ data_cls = TomlConfigData
136
+
137
+ def render(self, d: TomlConfigData) -> str:
138
+ return TomlWriter.write_str(d.obj)
139
+
140
+
141
+ ##
142
+
143
+
144
+ @dc.dataclass(frozen=True)
145
+ class YamlConfigData(ObjConfigData):
146
+ pass
147
+
148
+
149
+ class YamlConfigLoader(ConfigLoader[YamlConfigData]):
150
+ file_exts = ('yaml', 'yml')
151
+
152
+ def load_str(self, s: str) -> YamlConfigData:
153
+ return YamlConfigData(__import__('yaml').safe_load(s))
154
+
155
+
156
+ class YamlConfigRenderer(ConfigRenderer[YamlConfigData]):
157
+ data_cls = YamlConfigData
158
+
159
+ def render(self, d: YamlConfigData) -> str:
160
+ return __import__('yaml').safe_dump(d.obj)
161
+
162
+
163
+ ##
164
+
165
+
166
+ @dc.dataclass(frozen=True)
167
+ class IniConfigData(ConfigData):
168
+ sections: IniSectionSettingsMap
169
+
170
+ def as_map(self) -> ConfigMap:
171
+ return self.sections
172
+
173
+
174
+ class IniConfigLoader(ConfigLoader[IniConfigData]):
175
+ file_exts = ('ini',)
176
+
177
+ def load_str(self, s: str) -> IniConfigData:
178
+ cp = configparser.ConfigParser()
179
+ cp.read_string(s)
180
+ return IniConfigData(extract_ini_sections(cp))
181
+
182
+
183
+ class IniConfigRenderer(ConfigRenderer[IniConfigData]):
184
+ data_cls = IniConfigData
185
+
186
+ def render(self, d: IniConfigData) -> str:
187
+ return render_ini_sections(d.sections)
188
+
189
+
190
+ ##
191
+
192
+
193
+ @dc.dataclass(frozen=True)
194
+ class SwitchedConfigFileLoader:
195
+ loaders: ta.Sequence[ConfigLoader]
196
+ default: ta.Optional[ConfigLoader] = None
197
+
198
+ def load_file(self, p: str) -> ConfigData:
199
+ n = os.path.basename(p)
200
+
201
+ for l in self.loaders:
202
+ if l.match_file(n):
203
+ return l.load_file(p)
204
+
205
+ if (d := self.default) is not None:
206
+ return d.load_file(p)
207
+
208
+ raise NameError(n)
209
+
210
+
211
+ DEFAULT_CONFIG_LOADERS: ta.Sequence[ConfigLoader] = [
212
+ JsonConfigLoader(),
213
+ TomlConfigLoader(),
214
+ YamlConfigLoader(),
215
+ IniConfigLoader(),
216
+ ]
217
+
218
+ DEFAULT_CONFIG_LOADER: ConfigLoader = JsonConfigLoader()
219
+
220
+ DEFAULT_CONFIG_FILE_LOADER = SwitchedConfigFileLoader(
221
+ loaders=DEFAULT_CONFIG_LOADERS,
222
+ default=DEFAULT_CONFIG_LOADER,
223
+ )
224
+
225
+
226
+ ##
227
+
228
+
229
+ @dc.dataclass(frozen=True)
230
+ class SwitchedConfigRenderer:
231
+ renderers: ta.Sequence[ConfigRenderer]
232
+
233
+ def render(self, d: ConfigData) -> str:
234
+ for r in self.renderers:
235
+ if r.match_data(d):
236
+ return r.render(d)
237
+ raise TypeError(d)
238
+
239
+
240
+ DEFAULT_CONFIG_RENDERERS: ta.Sequence[ConfigRenderer] = [
241
+ JsonConfigRenderer(),
242
+ TomlConfigRenderer(),
243
+ YamlConfigRenderer(),
244
+ IniConfigRenderer(),
245
+ ]
246
+
247
+ DEFAULT_CONFIG_RENDERER = SwitchedConfigRenderer(DEFAULT_CONFIG_RENDERERS)
@@ -0,0 +1,72 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ See:
5
+ - https://nginx.org/en/docs/dev/development_guide.html
6
+ - https://nginx.org/en/docs/dev/development_guide.html#config_directives
7
+ - https://nginx.org/en/docs/example.html
8
+ """
9
+ import collections.abc
10
+ import dataclasses as dc
11
+ import typing as ta
12
+
13
+ from ..lite.check import check
14
+ from ..text.indent import IndentWriter
15
+
16
+
17
+ @dc.dataclass()
18
+ class NginxConfigItems:
19
+ lst: ta.List['NginxConfigItem']
20
+
21
+ @classmethod
22
+ def of(cls, obj: ta.Any) -> 'NginxConfigItems':
23
+ if isinstance(obj, NginxConfigItems):
24
+ return obj
25
+ return cls([NginxConfigItem.of(e) for e in check.isinstance(obj, list)])
26
+
27
+
28
+ @dc.dataclass()
29
+ class NginxConfigItem:
30
+ name: str
31
+ args: ta.Optional[ta.List[str]] = None
32
+ block: ta.Optional[NginxConfigItems] = None
33
+
34
+ @classmethod
35
+ def of(cls, obj: ta.Any) -> 'NginxConfigItem':
36
+ if isinstance(obj, NginxConfigItem):
37
+ return obj
38
+ args = check.isinstance(check.not_isinstance(obj, str), collections.abc.Sequence)
39
+ name, args = check.isinstance(args[0], str), args[1:]
40
+ if args and not isinstance(args[-1], str):
41
+ block, args = NginxConfigItems.of(args[-1]), args[:-1]
42
+ else:
43
+ block = None
44
+ return NginxConfigItem(name, [check.isinstance(e, str) for e in args], block=block)
45
+
46
+
47
+ def render_nginx_config(wr: IndentWriter, obj: ta.Any) -> None:
48
+ if isinstance(obj, NginxConfigItem):
49
+ wr.write(obj.name)
50
+ for e in obj.args or ():
51
+ wr.write(' ')
52
+ wr.write(e)
53
+ if obj.block:
54
+ wr.write(' {\n')
55
+ with wr.indent():
56
+ render_nginx_config(wr, obj.block)
57
+ wr.write('}\n')
58
+ else:
59
+ wr.write(';\n')
60
+
61
+ elif isinstance(obj, NginxConfigItems):
62
+ for e2 in obj.lst:
63
+ render_nginx_config(wr, e2)
64
+
65
+ else:
66
+ raise TypeError(obj)
67
+
68
+
69
+ def render_nginx_config_str(obj: ta.Any) -> str:
70
+ iw = IndentWriter()
71
+ render_nginx_config(iw, obj)
72
+ return iw.getvalue()
File without changes
@@ -1,32 +1,33 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
1
3
  import abc
2
- import itertools
3
4
  import typing as ta
4
5
 
5
- from .. import check
6
- from .. import lang
6
+ from ...lite.check import check
7
7
 
8
8
 
9
9
  K = ta.TypeVar('K')
10
10
  V = ta.TypeVar('V')
11
- StrMap = ta.Mapping[str, ta.Any]
12
11
 
13
12
 
14
- class _MISSING(lang.Marker):
15
- pass
13
+ ##
16
14
 
17
15
 
18
- class Flattening:
19
-
16
+ class ConfigFlattening:
20
17
  DEFAULT_DELIMITER = '.'
21
18
  DEFAULT_INDEX_OPEN = '('
22
19
  DEFAULT_INDEX_CLOSE = ')'
23
20
 
21
+ class _MISSING:
22
+ def __new__(cls, *args, **kwargs): # noqa
23
+ raise TypeError
24
+
24
25
  def __init__(
25
26
  self,
26
27
  *,
27
- delimiter=DEFAULT_DELIMITER,
28
- index_open=DEFAULT_INDEX_OPEN,
29
- index_close=DEFAULT_INDEX_CLOSE,
28
+ delimiter: str = DEFAULT_DELIMITER,
29
+ index_open: str = DEFAULT_INDEX_OPEN,
30
+ index_close: str = DEFAULT_INDEX_CLOSE,
30
31
  ) -> None:
31
32
  super().__init__()
32
33
 
@@ -34,8 +35,8 @@ class Flattening:
34
35
  self._index_open = check.not_empty(index_open)
35
36
  self._index_close = check.not_empty(index_close)
36
37
 
37
- def flatten(self, unflattened: StrMap) -> StrMap:
38
- def rec(prefix: list[str], value: ta.Any) -> None:
38
+ def flatten(self, unflattened: ta.Mapping[str, ta.Any]) -> ta.Mapping[str, ta.Any]:
39
+ def rec(prefix: ta.List[str], value: ta.Any) -> None:
39
40
  if isinstance(value, dict):
40
41
  for k, v in value.items():
41
42
  rec([*prefix, k], v)
@@ -53,8 +54,7 @@ class Flattening:
53
54
  rec([], unflattened)
54
55
  return ret
55
56
 
56
- class UnflattenNode(lang.Abstract, ta.Generic[K]):
57
-
57
+ class UnflattenNode(abc.ABC, ta.Generic[K]):
58
58
  @abc.abstractmethod
59
59
  def get(self, key: K) -> ta.Any:
60
60
  raise NotImplementedError
@@ -65,7 +65,7 @@ class Flattening:
65
65
 
66
66
  def setdefault(self, key: K, supplier: ta.Callable[[], V]) -> V:
67
67
  ret = self.get(key)
68
- if ret is _MISSING:
68
+ if ret is ConfigFlattening._MISSING:
69
69
  ret = supplier()
70
70
  self.put(key, ret)
71
71
  return ret
@@ -77,50 +77,48 @@ class Flattening:
77
77
  @staticmethod
78
78
  def maybe_build(value: ta.Any) -> ta.Any:
79
79
  check.not_none(value)
80
- return value.build() if isinstance(value, Flattening.UnflattenNode) else value
80
+ return value.build() if isinstance(value, ConfigFlattening.UnflattenNode) else value
81
81
 
82
82
  class UnflattenDict(UnflattenNode[str]):
83
-
84
83
  def __init__(self) -> None:
85
84
  super().__init__()
86
85
 
87
86
  self._dict: dict[str, ta.Any] = {}
88
87
 
89
88
  def get(self, key: str) -> ta.Any:
90
- return self._dict.get(key, _MISSING)
89
+ return self._dict.get(key, ConfigFlattening._MISSING)
91
90
 
92
91
  def put(self, key: str, value: ta.Any) -> None:
93
92
  check.arg(key not in self._dict)
94
93
  self._dict[key] = value
95
94
 
96
95
  def build(self) -> ta.Any:
97
- return {k: Flattening.UnflattenNode.maybe_build(v) for k, v in self._dict.items()}
96
+ return {k: ConfigFlattening.UnflattenNode.maybe_build(v) for k, v in self._dict.items()}
98
97
 
99
98
  class UnflattenList(UnflattenNode[int]):
100
-
101
99
  def __init__(self) -> None:
102
100
  super().__init__()
103
101
 
104
- self._list: list[ta.Any] = []
102
+ self._list: ta.List[ta.Any] = []
105
103
 
106
104
  def get(self, key: int) -> ta.Any:
107
105
  check.arg(key >= 0)
108
- return self._list[key] if key < len(self._list) else _MISSING
106
+ return self._list[key] if key < len(self._list) else ConfigFlattening._MISSING
109
107
 
110
108
  def put(self, key: int, value: ta.Any) -> None:
111
109
  check.arg(key >= 0)
112
110
  if key >= len(self._list):
113
- self._list.extend([_MISSING] * (key - len(self._list) + 1))
114
- check.arg(self._list[key] is _MISSING)
111
+ self._list.extend([ConfigFlattening._MISSING] * (key - len(self._list) + 1))
112
+ check.arg(self._list[key] is ConfigFlattening._MISSING)
115
113
  self._list[key] = value
116
114
 
117
115
  def build(self) -> ta.Any:
118
- return [Flattening.UnflattenNode.maybe_build(e) for e in self._list]
116
+ return [ConfigFlattening.UnflattenNode.maybe_build(e) for e in self._list]
119
117
 
120
- def unflatten(self, flattened: StrMap) -> StrMap:
121
- root = Flattening.UnflattenDict()
118
+ def unflatten(self, flattened: ta.Mapping[str, ta.Any]) -> ta.Mapping[str, ta.Any]:
119
+ root = ConfigFlattening.UnflattenDict()
122
120
 
123
- def split_keys(fkey: str) -> ta.Iterable[str | int]:
121
+ def split_keys(fkey: str) -> ta.Iterable[ta.Union[str, int]]:
124
122
  for part in fkey.split(self._delimiter):
125
123
  if self._index_open in part:
126
124
  check.state(part.endswith(self._index_close))
@@ -134,13 +132,13 @@ class Flattening:
134
132
  yield part
135
133
 
136
134
  for fk, v in flattened.items():
137
- node: Flattening.UnflattenNode = root
135
+ node: ConfigFlattening.UnflattenNode = root
138
136
  fks = list(split_keys(fk))
139
- for key, nkey in itertools.pairwise(fks):
137
+ for key, nkey in zip(fks, fks[1:]): # noqa
140
138
  if isinstance(nkey, str):
141
- node = node.setdefault(key, Flattening.UnflattenDict)
139
+ node = node.setdefault(key, ConfigFlattening.UnflattenDict)
142
140
  elif isinstance(nkey, int):
143
- node = node.setdefault(key, Flattening.UnflattenList)
141
+ node = node.setdefault(key, ConfigFlattening.UnflattenList)
144
142
  else:
145
143
  raise TypeError(key)
146
144
  node.put(fks[-1], v)
@@ -0,0 +1,58 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ TODO:
5
+ - custom merging by key - replace, append, setdefault, equality enforcement, ...
6
+ - better handling of 'None' as signal to replace or not
7
+ - remove inherited
8
+ """
9
+ import typing as ta
10
+
11
+ from ..types import ConfigMap
12
+
13
+
14
+ ##
15
+
16
+
17
+ def build_config_inherited_values(
18
+ m: ta.Mapping[str, ConfigMap],
19
+ *,
20
+ inherits_key: str = 'inherits',
21
+ ) -> ta.Dict[str, ConfigMap]:
22
+ done: ta.Dict[str, ConfigMap] = {}
23
+
24
+ def rec(mk: str) -> ta.Mapping[str, ConfigMap]:
25
+ try:
26
+ return done[mk]
27
+ except KeyError:
28
+ pass
29
+
30
+ cur = m[mk]
31
+ try:
32
+ inh_seq = cur[inherits_key]
33
+ except KeyError:
34
+ done[mk] = cur
35
+ return cur
36
+
37
+ new = dict(cur)
38
+ for inh in inh_seq:
39
+ inh_dct = rec(inh)
40
+ new.update({
41
+ k: v
42
+ for k, v in inh_dct.items()
43
+ if v is not None
44
+ and cur.get(k) is None
45
+ })
46
+
47
+ out = {
48
+ k: v
49
+ for k, v in new.items()
50
+ if k != inherits_key
51
+ }
52
+
53
+ done[mk] = out
54
+ return out
55
+
56
+ for mk in m:
57
+ rec(mk)
58
+ return done
@@ -0,0 +1,53 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import dataclasses as dc
4
+ import functools
5
+ import typing as ta
6
+
7
+ from ...lite.check import check
8
+ from .rewriting import ConfigRewriter
9
+ from .rewriting import ConfigRewriterPath
10
+
11
+
12
+ T = ta.TypeVar('T')
13
+
14
+
15
+ ##
16
+
17
+
18
+ class MatchingConfigRewriter(ConfigRewriter):
19
+ def __init__(
20
+ self,
21
+ fn: ta.Callable[[ta.Any], ta.Any],
22
+ paths: ta.Iterable[ConfigRewriterPath],
23
+ *,
24
+ recurse: bool = False,
25
+ ) -> None:
26
+ super().__init__()
27
+
28
+ self._fn = fn
29
+ self._paths = frozenset(check.isinstance(p, tuple) for p in paths)
30
+ self._recurse = recurse
31
+
32
+ def rewrite(self, ctx: ConfigRewriter.Context[T]) -> T:
33
+ if ctx.path in self._paths:
34
+ no = self._fn(ctx.obj)
35
+ if not self._recurse:
36
+ return no
37
+ ctx = dc.replace(ctx, obj=no)
38
+
39
+ return super().rewrite(ctx)
40
+
41
+
42
+ def matched_config_rewrite(
43
+ fn: ta.Callable[[ta.Any], ta.Any],
44
+ obj: T,
45
+ *paths: ConfigRewriterPath,
46
+ recurse: bool = False,
47
+ **kwargs: ta.Any,
48
+ ) -> T:
49
+ return MatchingConfigRewriter(
50
+ functools.partial(fn, **kwargs),
51
+ paths,
52
+ recurse=recurse,
53
+ )(obj)
@@ -0,0 +1,50 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ usecase: supervisor process groups
5
+ """
6
+ import typing as ta
7
+
8
+ from ...lite.check import check
9
+ from ..types import ConfigMap
10
+
11
+
12
+ ##
13
+
14
+
15
+ def build_config_named_children(
16
+ o: ta.Union[
17
+ ta.Sequence[ConfigMap],
18
+ ta.Mapping[str, ConfigMap],
19
+ None,
20
+ ],
21
+ *,
22
+ name_key: str = 'name',
23
+ ) -> ta.Optional[ta.Sequence[ConfigMap]]:
24
+ if o is None:
25
+ return None
26
+
27
+ lst: ta.List[ConfigMap] = []
28
+ if isinstance(o, ta.Mapping):
29
+ for k, v in o.items():
30
+ check.isinstance(v, ta.Mapping)
31
+ if name_key in v:
32
+ n = v[name_key]
33
+ if k != n:
34
+ raise KeyError(f'Given names do not match: {n} != {k}')
35
+ lst.append(v)
36
+ else:
37
+ lst.append({name_key: k, **v})
38
+
39
+ else:
40
+ check.not_isinstance(o, str)
41
+ lst.extend(o)
42
+
43
+ seen = set()
44
+ for d in lst:
45
+ n = d['name']
46
+ if n in d:
47
+ raise KeyError(f'Duplicate name: {n}')
48
+ seen.add(n)
49
+
50
+ return lst