omlish 0.0.0.dev192__py3-none-any.whl → 0.0.0.dev194__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 (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