omlish 0.0.0.dev133__py3-none-any.whl → 0.0.0.dev177__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 (210) hide show
  1. omlish/.manifests.json +265 -7
  2. omlish/__about__.py +5 -3
  3. omlish/antlr/_runtime/__init__.py +0 -22
  4. omlish/antlr/_runtime/_all.py +24 -0
  5. omlish/antlr/_runtime/atn/ParserATNSimulator.py +1 -1
  6. omlish/antlr/_runtime/dfa/DFASerializer.py +1 -1
  7. omlish/antlr/_runtime/error/DiagnosticErrorListener.py +2 -1
  8. omlish/antlr/_runtime/xpath/XPath.py +7 -1
  9. omlish/antlr/_runtime/xpath/XPathLexer.py +1 -1
  10. omlish/antlr/delimit.py +106 -0
  11. omlish/antlr/dot.py +31 -0
  12. omlish/antlr/errors.py +11 -0
  13. omlish/antlr/input.py +96 -0
  14. omlish/antlr/parsing.py +19 -0
  15. omlish/antlr/runtime.py +102 -0
  16. omlish/antlr/utils.py +38 -0
  17. omlish/argparse/all.py +45 -0
  18. omlish/{argparse.py → argparse/cli.py} +112 -107
  19. omlish/asyncs/__init__.py +0 -35
  20. omlish/asyncs/all.py +35 -0
  21. omlish/asyncs/asyncio/all.py +7 -0
  22. omlish/asyncs/asyncio/channels.py +40 -0
  23. omlish/asyncs/asyncio/streams.py +45 -0
  24. omlish/asyncs/asyncio/subprocesses.py +238 -0
  25. omlish/asyncs/asyncio/timeouts.py +16 -0
  26. omlish/asyncs/bluelet/LICENSE +6 -0
  27. omlish/asyncs/bluelet/all.py +67 -0
  28. omlish/asyncs/bluelet/api.py +23 -0
  29. omlish/asyncs/bluelet/core.py +178 -0
  30. omlish/asyncs/bluelet/events.py +78 -0
  31. omlish/asyncs/bluelet/files.py +80 -0
  32. omlish/asyncs/bluelet/runner.py +416 -0
  33. omlish/asyncs/bluelet/sockets.py +214 -0
  34. omlish/bootstrap/sys.py +3 -3
  35. omlish/cached.py +2 -2
  36. omlish/check.py +49 -460
  37. omlish/codecs/__init__.py +72 -0
  38. omlish/codecs/base.py +106 -0
  39. omlish/codecs/bytes.py +119 -0
  40. omlish/codecs/chain.py +23 -0
  41. omlish/codecs/funcs.py +39 -0
  42. omlish/codecs/registry.py +139 -0
  43. omlish/codecs/standard.py +4 -0
  44. omlish/codecs/text.py +217 -0
  45. omlish/collections/cache/impl.py +50 -57
  46. omlish/collections/coerce.py +1 -0
  47. omlish/collections/mappings.py +1 -1
  48. omlish/configs/flattening.py +1 -1
  49. omlish/defs.py +1 -1
  50. omlish/diag/_pycharm/runhack.py +8 -2
  51. omlish/diag/procfs.py +8 -8
  52. omlish/docker/__init__.py +0 -36
  53. omlish/docker/all.py +31 -0
  54. omlish/docker/consts.py +4 -0
  55. omlish/{lite/docker.py → docker/detect.py} +18 -0
  56. omlish/docker/{helpers.py → timebomb.py} +0 -21
  57. omlish/formats/cbor.py +31 -0
  58. omlish/formats/cloudpickle.py +31 -0
  59. omlish/formats/codecs.py +93 -0
  60. omlish/formats/json/codecs.py +29 -0
  61. omlish/formats/json/delimted.py +4 -0
  62. omlish/formats/json/stream/errors.py +2 -0
  63. omlish/formats/json/stream/lex.py +12 -6
  64. omlish/formats/json/stream/parse.py +38 -22
  65. omlish/formats/json5.py +31 -0
  66. omlish/formats/pickle.py +31 -0
  67. omlish/formats/repr.py +25 -0
  68. omlish/formats/toml.py +17 -0
  69. omlish/formats/yaml.py +25 -0
  70. omlish/funcs/__init__.py +0 -0
  71. omlish/{genmachine.py → funcs/genmachine.py} +5 -4
  72. omlish/{matchfns.py → funcs/match.py} +1 -1
  73. omlish/funcs/pairs.py +215 -0
  74. omlish/http/__init__.py +0 -48
  75. omlish/http/all.py +48 -0
  76. omlish/http/coro/__init__.py +0 -0
  77. omlish/{lite/fdio/corohttp.py → http/coro/fdio.py} +21 -19
  78. omlish/{lite/http/coroserver.py → http/coro/server.py} +20 -21
  79. omlish/{lite/http → http}/handlers.py +3 -2
  80. omlish/{lite/http → http}/parsing.py +1 -0
  81. omlish/http/sessions.py +1 -1
  82. omlish/{lite/http → http}/versions.py +1 -0
  83. omlish/inject/managed.py +2 -2
  84. omlish/io/__init__.py +0 -3
  85. omlish/{lite/io.py → io/buffers.py} +8 -9
  86. omlish/io/compress/__init__.py +9 -0
  87. omlish/io/compress/abc.py +104 -0
  88. omlish/io/compress/adapters.py +148 -0
  89. omlish/io/compress/base.py +24 -0
  90. omlish/io/compress/brotli.py +47 -0
  91. omlish/io/compress/bz2.py +61 -0
  92. omlish/io/compress/codecs.py +78 -0
  93. omlish/io/compress/gzip.py +350 -0
  94. omlish/io/compress/lz4.py +91 -0
  95. omlish/io/compress/lzma.py +81 -0
  96. omlish/io/compress/snappy.py +34 -0
  97. omlish/io/compress/zlib.py +74 -0
  98. omlish/io/compress/zstd.py +44 -0
  99. omlish/io/fdio/__init__.py +1 -0
  100. omlish/{lite → io}/fdio/handlers.py +5 -5
  101. omlish/{lite → io}/fdio/kqueue.py +8 -8
  102. omlish/{lite → io}/fdio/manager.py +7 -7
  103. omlish/{lite → io}/fdio/pollers.py +13 -13
  104. omlish/io/generators/__init__.py +56 -0
  105. omlish/io/generators/consts.py +1 -0
  106. omlish/io/generators/direct.py +13 -0
  107. omlish/io/generators/readers.py +189 -0
  108. omlish/io/generators/stepped.py +191 -0
  109. omlish/io/pyio.py +5 -2
  110. omlish/iterators/__init__.py +24 -0
  111. omlish/iterators/iterators.py +132 -0
  112. omlish/iterators/recipes.py +18 -0
  113. omlish/iterators/tools.py +96 -0
  114. omlish/iterators/unique.py +67 -0
  115. omlish/lang/__init__.py +13 -1
  116. omlish/lang/functions.py +11 -2
  117. omlish/lang/generators.py +243 -0
  118. omlish/lang/iterables.py +46 -49
  119. omlish/lang/maybes.py +4 -4
  120. omlish/lite/cached.py +39 -6
  121. omlish/lite/check.py +438 -75
  122. omlish/lite/contextmanagers.py +17 -4
  123. omlish/lite/dataclasses.py +42 -0
  124. omlish/lite/inject.py +28 -45
  125. omlish/lite/logs.py +0 -270
  126. omlish/lite/marshal.py +309 -144
  127. omlish/lite/pycharm.py +47 -0
  128. omlish/lite/reflect.py +33 -0
  129. omlish/lite/resources.py +8 -0
  130. omlish/lite/runtime.py +4 -4
  131. omlish/lite/shlex.py +12 -0
  132. omlish/lite/socketserver.py +2 -2
  133. omlish/lite/strings.py +31 -0
  134. omlish/logs/__init__.py +0 -32
  135. omlish/logs/{_abc.py → abc.py} +0 -1
  136. omlish/logs/all.py +37 -0
  137. omlish/logs/{formatters.py → color.py} +1 -2
  138. omlish/logs/configs.py +7 -38
  139. omlish/logs/filters.py +10 -0
  140. omlish/logs/handlers.py +4 -1
  141. omlish/logs/json.py +56 -0
  142. omlish/logs/proxy.py +99 -0
  143. omlish/logs/standard.py +128 -0
  144. omlish/logs/utils.py +2 -2
  145. omlish/manifests/__init__.py +2 -0
  146. omlish/manifests/load.py +209 -0
  147. omlish/manifests/types.py +17 -0
  148. omlish/marshal/base.py +1 -1
  149. omlish/marshal/factories.py +1 -1
  150. omlish/marshal/forbidden.py +1 -1
  151. omlish/marshal/iterables.py +1 -1
  152. omlish/marshal/literals.py +50 -0
  153. omlish/marshal/mappings.py +1 -1
  154. omlish/marshal/maybes.py +1 -1
  155. omlish/marshal/standard.py +5 -1
  156. omlish/marshal/unions.py +1 -1
  157. omlish/os/__init__.py +0 -0
  158. omlish/os/atomics.py +205 -0
  159. omlish/os/deathsig.py +23 -0
  160. omlish/{os.py → os/files.py} +0 -9
  161. omlish/{lite → os}/journald.py +2 -1
  162. omlish/os/linux.py +484 -0
  163. omlish/os/paths.py +36 -0
  164. omlish/{lite → os}/pidfile.py +1 -0
  165. omlish/os/sizes.py +9 -0
  166. omlish/reflect/__init__.py +3 -0
  167. omlish/reflect/subst.py +2 -1
  168. omlish/reflect/types.py +126 -44
  169. omlish/secrets/pwhash.py +1 -1
  170. omlish/secrets/subprocesses.py +3 -1
  171. omlish/specs/jsonrpc/marshal.py +1 -1
  172. omlish/specs/openapi/marshal.py +1 -1
  173. omlish/sql/alchemy/asyncs.py +1 -1
  174. omlish/sql/queries/__init__.py +9 -1
  175. omlish/sql/queries/building.py +3 -0
  176. omlish/sql/queries/exprs.py +10 -27
  177. omlish/sql/queries/idents.py +48 -10
  178. omlish/sql/queries/names.py +80 -13
  179. omlish/sql/queries/params.py +64 -0
  180. omlish/sql/queries/rendering.py +1 -1
  181. omlish/subprocesses.py +340 -0
  182. omlish/term.py +29 -14
  183. omlish/testing/pytest/marks.py +2 -2
  184. omlish/testing/pytest/plugins/asyncs.py +6 -1
  185. omlish/testing/pytest/plugins/logging.py +1 -1
  186. omlish/testing/pytest/plugins/switches.py +1 -1
  187. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/METADATA +7 -5
  188. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/RECORD +200 -117
  189. omlish/fnpairs.py +0 -496
  190. omlish/formats/json/cli/__main__.py +0 -11
  191. omlish/formats/json/cli/cli.py +0 -298
  192. omlish/formats/json/cli/formats.py +0 -71
  193. omlish/formats/json/cli/io.py +0 -74
  194. omlish/formats/json/cli/parsing.py +0 -82
  195. omlish/formats/json/cli/processing.py +0 -48
  196. omlish/formats/json/cli/rendering.py +0 -92
  197. omlish/iterators.py +0 -300
  198. omlish/lite/subprocesses.py +0 -130
  199. /omlish/{formats/json/cli → argparse}/__init__.py +0 -0
  200. /omlish/{lite/fdio → asyncs/asyncio}/__init__.py +0 -0
  201. /omlish/asyncs/{asyncio.py → asyncio/asyncio.py} +0 -0
  202. /omlish/{lite/http → asyncs/bluelet}/__init__.py +0 -0
  203. /omlish/collections/{_abc.py → abc.py} +0 -0
  204. /omlish/{fnpipes.py → funcs/pipes.py} +0 -0
  205. /omlish/io/{_abc.py → abc.py} +0 -0
  206. /omlish/sql/{_abc.py → abc.py} +0 -0
  207. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/LICENSE +0 -0
  208. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/WHEEL +0 -0
  209. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/entry_points.txt +0 -0
  210. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,209 @@
1
+ # ruff: noqa: UP006 UP007
2
+ """
3
+ Should be kept somewhat lightweight - used in cli entrypoints.
4
+
5
+ TODO:
6
+ - persisted caching support - {pkg_name: manifests}
7
+ """
8
+ import dataclasses as dc
9
+ import importlib.machinery
10
+ import importlib.resources
11
+ import json
12
+ import threading
13
+ import typing as ta
14
+
15
+ from .types import Manifest
16
+
17
+
18
+ ##
19
+
20
+
21
+ class ManifestLoader:
22
+ def __init__(
23
+ self,
24
+ *,
25
+ module_remap: ta.Optional[ta.Mapping[str, str]] = None,
26
+ ) -> None:
27
+ super().__init__()
28
+
29
+ self._lock = threading.RLock()
30
+
31
+ self._module_remap = module_remap or {}
32
+ self._module_reverse_remap = {v: k for k, v in self._module_remap.items()}
33
+
34
+ self._cls_cache: ta.Dict[str, type] = {}
35
+ self._raw_cache: ta.Dict[str, ta.Optional[ta.Sequence[Manifest]]] = {}
36
+
37
+ #
38
+
39
+ @classmethod
40
+ def from_entry_point(
41
+ cls,
42
+ globals: ta.Mapping[str, ta.Any], # noqa
43
+ *,
44
+ module_remap: ta.Optional[ta.Mapping[str, str]] = None,
45
+ **kwargs: ta.Any,
46
+ ) -> 'ManifestLoader':
47
+ rm: ta.Dict[str, str] = {}
48
+
49
+ if module_remap:
50
+ rm.update(module_remap)
51
+
52
+ if '__name__' in globals and '__spec__' in globals:
53
+ name: str = globals['__name__']
54
+ spec: importlib.machinery.ModuleSpec = globals['__spec__']
55
+ if '__main__' not in rm and name == '__main__':
56
+ rm[spec.name] = '__main__'
57
+
58
+ return cls(module_remap=rm, **kwargs)
59
+
60
+ #
61
+
62
+ def _load_cls(self, key: str) -> type:
63
+ try:
64
+ return self._cls_cache[key]
65
+ except KeyError:
66
+ pass
67
+
68
+ if not key.startswith('$'):
69
+ raise Exception(f'Bad key: {key}')
70
+
71
+ parts = key[1:].split('.')
72
+ pos = next(i for i, p in enumerate(parts) if p[0].isupper())
73
+
74
+ mod_name = '.'.join(parts[:pos])
75
+ mod_name = self._module_remap.get(mod_name, mod_name)
76
+ mod = importlib.import_module(mod_name)
77
+
78
+ obj: ta.Any = mod
79
+ for ca in parts[pos:]:
80
+ obj = getattr(obj, ca)
81
+
82
+ cls = obj
83
+ if not isinstance(cls, type):
84
+ raise TypeError(cls)
85
+
86
+ self._cls_cache[key] = cls
87
+ return cls
88
+
89
+ def load_cls(self, key: str) -> type:
90
+ with self._lock:
91
+ return self._load_cls(key)
92
+
93
+ #
94
+
95
+ def _load_contents(self, obj: ta.Any, pkg_name: str) -> ta.Sequence[Manifest]:
96
+ if not isinstance(obj, (list, tuple)):
97
+ raise TypeError(obj)
98
+
99
+ lst: ta.List[Manifest] = []
100
+ for e in obj:
101
+ m = Manifest(**e)
102
+
103
+ m = dc.replace(m, module=pkg_name + m.module)
104
+
105
+ [(key, value_dct)] = m.value.items()
106
+ if not key.startswith('$'):
107
+ raise Exception(f'Bad key: {key}')
108
+ if key.startswith('$.'):
109
+ key = f'${pkg_name}{key[1:]}'
110
+ m = dc.replace(m, value={key: value_dct})
111
+
112
+ lst.append(m)
113
+
114
+ return lst
115
+
116
+ def load_contents(self, obj: ta.Any, pkg_name: str) -> ta.Sequence[Manifest]:
117
+ with self._lock:
118
+ return self.load_contents(obj, pkg_name)
119
+
120
+ #
121
+
122
+ def _load_raw(self, pkg_name: str) -> ta.Optional[ta.Sequence[Manifest]]:
123
+ try:
124
+ return self._raw_cache[pkg_name]
125
+ except KeyError:
126
+ pass
127
+
128
+ t = importlib.resources.files(pkg_name).joinpath('.manifests.json')
129
+ if not t.is_file():
130
+ self._raw_cache[pkg_name] = None
131
+ return None
132
+
133
+ src = t.read_text('utf-8')
134
+ obj = json.loads(src)
135
+ if not isinstance(obj, (list, tuple)):
136
+ raise TypeError(obj)
137
+
138
+ lst = self._load_contents(obj, pkg_name)
139
+
140
+ self._raw_cache[pkg_name] = lst
141
+ return lst
142
+
143
+ def load_raw(self, pkg_name: str) -> ta.Optional[ta.Sequence[Manifest]]:
144
+ with self._lock:
145
+ return self._load_raw(pkg_name)
146
+
147
+ #
148
+
149
+ def _load(
150
+ self,
151
+ *pkg_names: str,
152
+ only: ta.Optional[ta.Iterable[type]] = None,
153
+ ) -> ta.Sequence[Manifest]:
154
+ only_keys: ta.Optional[ta.Set]
155
+ if only is not None:
156
+ only_keys = set()
157
+ for cls in only:
158
+ if not (isinstance(cls, type) and dc.is_dataclass(cls)):
159
+ raise TypeError(cls)
160
+ mod_name = cls.__module__
161
+ mod_name = self._module_reverse_remap.get(mod_name, mod_name)
162
+ only_keys.add(f'${mod_name}.{cls.__qualname__}')
163
+ else:
164
+ only_keys = None
165
+
166
+ lst: ta.List[Manifest] = []
167
+ for pn in pkg_names:
168
+ for manifest in (self.load_raw(pn) or []):
169
+ [(key, value_dct)] = manifest.value.items()
170
+ if only_keys is not None and key not in only_keys:
171
+ continue
172
+
173
+ cls = self._load_cls(key)
174
+ value = cls(**value_dct)
175
+
176
+ manifest = dc.replace(manifest, value=value)
177
+ lst.append(manifest)
178
+
179
+ return lst
180
+
181
+ def load(
182
+ self,
183
+ *pkg_names: str,
184
+ only: ta.Optional[ta.Iterable[type]] = None,
185
+ ) -> ta.Sequence[Manifest]:
186
+ with self._lock:
187
+ return self._load(
188
+ *pkg_names,
189
+ only=only,
190
+ )
191
+
192
+ #
193
+
194
+ ENTRY_POINT_GROUP = 'omlish.manifests'
195
+
196
+ def discover(self) -> ta.Sequence[str]:
197
+ # This is a fat dep so do it late.
198
+ import importlib.metadata
199
+
200
+ return [
201
+ ep.value
202
+ for ep in importlib.metadata.entry_points(group=self.ENTRY_POINT_GROUP)
203
+ ]
204
+
205
+
206
+ ##
207
+
208
+
209
+ MANIFEST_LOADER = ManifestLoader()
@@ -0,0 +1,17 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+
6
+ @dc.dataclass(frozen=True)
7
+ class ManifestOrigin:
8
+ module: str
9
+ attr: str
10
+
11
+ file: str
12
+ line: int
13
+
14
+
15
+ @dc.dataclass(frozen=True)
16
+ class Manifest(ManifestOrigin):
17
+ value: ta.Any
omlish/marshal/base.py CHANGED
@@ -89,8 +89,8 @@ from .. import check
89
89
  from .. import collections as col
90
90
  from .. import dataclasses as dc
91
91
  from .. import lang
92
- from .. import matchfns as mfs
93
92
  from .. import reflect as rfl
93
+ from ..funcs import match as mfs
94
94
  from .exceptions import UnhandledTypeError
95
95
  from .factories import RecursiveTypeFactory
96
96
  from .factories import TypeCacheFactory
@@ -3,8 +3,8 @@ import threading
3
3
  import typing as ta
4
4
 
5
5
  from .. import check
6
- from .. import matchfns as mfs
7
6
  from .. import reflect as rfl
7
+ from ..funcs import match as mfs
8
8
 
9
9
 
10
10
  R = ta.TypeVar('R')
@@ -1,8 +1,8 @@
1
1
  import dataclasses as dc
2
2
  import typing as ta
3
3
 
4
- from .. import matchfns as mfs
5
4
  from .. import reflect as rfl
5
+ from ..funcs import match as mfs
6
6
  from .base import MarshalContext
7
7
  from .base import Marshaler
8
8
  from .base import UnmarshalContext
@@ -8,8 +8,8 @@ import functools
8
8
  import typing as ta
9
9
 
10
10
  from .. import check
11
- from .. import matchfns as mfs
12
11
  from .. import reflect as rfl
12
+ from ..funcs import match as mfs
13
13
  from .base import MarshalContext
14
14
  from .base import Marshaler
15
15
  from .base import MarshalerFactoryMatchClass
@@ -0,0 +1,50 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from .. import check
5
+ from .. import reflect as rfl
6
+ from .base import MarshalContext
7
+ from .base import Marshaler
8
+ from .base import MarshalerFactory
9
+ from .base import UnmarshalContext
10
+ from .base import Unmarshaler
11
+ from .base import UnmarshalerFactory
12
+ from .values import Value
13
+
14
+
15
+ @dc.dataclass(frozen=True)
16
+ class LiteralMarshaler(Marshaler):
17
+ e: Marshaler
18
+ vs: frozenset
19
+
20
+ def marshal(self, ctx: MarshalContext, o: ta.Any | None) -> Value:
21
+ return self.e.marshal(ctx, check.in_(o, self.vs))
22
+
23
+
24
+ class LiteralMarshalerFactory(MarshalerFactory):
25
+ def guard(self, ctx: MarshalContext, rty: rfl.Type) -> bool:
26
+ return isinstance(rty, rfl.Literal)
27
+
28
+ def fn(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler:
29
+ lty = check.isinstance(rty, rfl.Literal)
30
+ ety = check.single(set(map(type, lty.args)))
31
+ return LiteralMarshaler(ctx.make(ety), frozenset(lty.args))
32
+
33
+
34
+ @dc.dataclass(frozen=True)
35
+ class LiteralUnmarshaler(Unmarshaler):
36
+ e: Unmarshaler
37
+ vs: frozenset
38
+
39
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any | None:
40
+ return check.in_(self.e.unmarshal(ctx, v), self.vs)
41
+
42
+
43
+ class LiteralUnmarshalerFactory(UnmarshalerFactory):
44
+ def guard(self, ctx: UnmarshalContext, rty: rfl.Type) -> bool:
45
+ return isinstance(rty, rfl.Literal)
46
+
47
+ def fn(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
48
+ lty = check.isinstance(rty, rfl.Literal)
49
+ ety = check.single(set(map(type, lty.args)))
50
+ return LiteralUnmarshaler(ctx.make(ety), frozenset(lty.args))
@@ -3,8 +3,8 @@ import dataclasses as dc
3
3
  import typing as ta
4
4
 
5
5
  from .. import check
6
- from .. import matchfns as mfs
7
6
  from .. import reflect as rfl
7
+ from ..funcs import match as mfs
8
8
  from .base import MarshalContext
9
9
  from .base import Marshaler
10
10
  from .base import MarshalerFactoryMatchClass
omlish/marshal/maybes.py CHANGED
@@ -7,8 +7,8 @@ import typing as ta
7
7
 
8
8
  from .. import check
9
9
  from .. import lang
10
- from .. import matchfns as mfs
11
10
  from .. import reflect as rfl
11
+ from ..funcs import match as mfs
12
12
  from .base import MarshalContext
13
13
  from .base import Marshaler
14
14
  from .base import MarshalerFactoryMatchClass
@@ -1,4 +1,4 @@
1
- from .. import matchfns as mfs
1
+ from ..funcs import match as mfs
2
2
  from .any import ANY_MARSHALER_FACTORY
3
3
  from .any import ANY_UNMARSHALER_FACTORY
4
4
  from .base import MarshalerFactory
@@ -17,6 +17,8 @@ from .enums import EnumMarshalerFactory
17
17
  from .enums import EnumUnmarshalerFactory
18
18
  from .iterables import IterableMarshalerFactory
19
19
  from .iterables import IterableUnmarshalerFactory
20
+ from .literals import LiteralMarshalerFactory
21
+ from .literals import LiteralUnmarshalerFactory
20
22
  from .mappings import MappingMarshalerFactory
21
23
  from .mappings import MappingUnmarshalerFactory
22
24
  from .maybes import MaybeMarshalerFactory
@@ -48,6 +50,7 @@ STANDARD_MARSHALER_FACTORIES: list[MarshalerFactory] = [
48
50
  DataclassMarshalerFactory(),
49
51
  NamedtupleMarshalerFactory(),
50
52
  EnumMarshalerFactory(),
53
+ LiteralMarshalerFactory(),
51
54
  NUMBERS_MARSHALER_FACTORY,
52
55
  UUID_MARSHALER_FACTORY,
53
56
  BASE64_MARSHALER_FACTORY,
@@ -80,6 +83,7 @@ STANDARD_UNMARSHALER_FACTORIES: list[UnmarshalerFactory] = [
80
83
  DataclassUnmarshalerFactory(),
81
84
  NamedtupleUnmarshalerFactory(),
82
85
  EnumUnmarshalerFactory(),
86
+ LiteralUnmarshalerFactory(),
83
87
  NUMBERS_UNMARSHALER_FACTORY,
84
88
  UUID_UNMARSHALER_FACTORY,
85
89
  BASE64_UNMARSHALER_FACTORY,
omlish/marshal/unions.py CHANGED
@@ -4,8 +4,8 @@ from .. import cached
4
4
  from .. import check
5
5
  from .. import dataclasses as dc
6
6
  from .. import lang
7
- from .. import matchfns as mfs
8
7
  from .. import reflect as rfl
8
+ from ..funcs import match as mfs
9
9
  from .base import MarshalContext
10
10
  from .base import Marshaler
11
11
  from .base import MarshalerFactory
omlish/os/__init__.py ADDED
File without changes
omlish/os/atomics.py ADDED
@@ -0,0 +1,205 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import abc
4
+ import os
5
+ import shutil
6
+ import tempfile
7
+ import typing as ta
8
+
9
+ from omlish.lite.check import check
10
+ from omlish.lite.strings import attr_repr
11
+
12
+
13
+ AtomicPathSwapKind = ta.Literal['dir', 'file']
14
+ AtomicPathSwapState = ta.Literal['open', 'committed', 'aborted'] # ta.TypeAlias
15
+
16
+
17
+ ##
18
+
19
+
20
+ class AtomicPathSwap(abc.ABC):
21
+ def __init__(
22
+ self,
23
+ kind: AtomicPathSwapKind,
24
+ dst_path: str,
25
+ *,
26
+ auto_commit: bool = False,
27
+ ) -> None:
28
+ super().__init__()
29
+
30
+ self._kind = kind
31
+ self._dst_path = dst_path
32
+ self._auto_commit = auto_commit
33
+
34
+ self._state: AtomicPathSwapState = 'open'
35
+
36
+ def __repr__(self) -> str:
37
+ return attr_repr(self, 'kind', 'dst_path', 'tmp_path')
38
+
39
+ @property
40
+ def kind(self) -> AtomicPathSwapKind:
41
+ return self._kind
42
+
43
+ @property
44
+ def dst_path(self) -> str:
45
+ return self._dst_path
46
+
47
+ @property
48
+ @abc.abstractmethod
49
+ def tmp_path(self) -> str:
50
+ raise NotImplementedError
51
+
52
+ #
53
+
54
+ @property
55
+ def state(self) -> AtomicPathSwapState:
56
+ return self._state
57
+
58
+ def _check_state(self, *states: AtomicPathSwapState) -> None:
59
+ if self._state not in states:
60
+ raise RuntimeError(f'Atomic path swap not in correct state: {self._state}, {states}')
61
+
62
+ #
63
+
64
+ @abc.abstractmethod
65
+ def _commit(self) -> None:
66
+ raise NotImplementedError
67
+
68
+ def commit(self) -> None:
69
+ if self._state == 'committed':
70
+ return
71
+ self._check_state('open')
72
+ try:
73
+ self._commit()
74
+ except Exception: # noqa
75
+ self._abort()
76
+ raise
77
+ else:
78
+ self._state = 'committed'
79
+
80
+ #
81
+
82
+ @abc.abstractmethod
83
+ def _abort(self) -> None:
84
+ raise NotImplementedError
85
+
86
+ def abort(self) -> None:
87
+ if self._state == 'aborted':
88
+ return
89
+ self._abort()
90
+ self._state = 'aborted'
91
+
92
+ #
93
+
94
+ def __enter__(self) -> 'AtomicPathSwap':
95
+ return self
96
+
97
+ def __exit__(self, exc_type, exc_val, exc_tb):
98
+ if (
99
+ exc_type is None and
100
+ self._auto_commit and
101
+ self._state == 'open'
102
+ ):
103
+ self.commit()
104
+ else:
105
+ self.abort()
106
+
107
+
108
+ class AtomicPathSwapping(abc.ABC):
109
+ @abc.abstractmethod
110
+ def begin_atomic_path_swap(
111
+ self,
112
+ kind: AtomicPathSwapKind,
113
+ dst_path: str,
114
+ *,
115
+ name_hint: ta.Optional[str] = None,
116
+ make_dirs: bool = False,
117
+ **kwargs: ta.Any,
118
+ ) -> AtomicPathSwap:
119
+ raise NotImplementedError
120
+
121
+
122
+ ##
123
+
124
+
125
+ class OsReplaceAtomicPathSwap(AtomicPathSwap):
126
+ def __init__(
127
+ self,
128
+ kind: AtomicPathSwapKind,
129
+ dst_path: str,
130
+ tmp_path: str,
131
+ **kwargs: ta.Any,
132
+ ) -> None:
133
+ if kind == 'dir':
134
+ check.state(os.path.isdir(tmp_path))
135
+ elif kind == 'file':
136
+ check.state(os.path.isfile(tmp_path))
137
+ else:
138
+ raise TypeError(kind)
139
+
140
+ super().__init__(
141
+ kind,
142
+ dst_path,
143
+ **kwargs,
144
+ )
145
+
146
+ self._tmp_path = tmp_path
147
+
148
+ @property
149
+ def tmp_path(self) -> str:
150
+ return self._tmp_path
151
+
152
+ def _commit(self) -> None:
153
+ os.replace(self._tmp_path, self._dst_path)
154
+
155
+ def _abort(self) -> None:
156
+ shutil.rmtree(self._tmp_path, ignore_errors=True)
157
+
158
+
159
+ class TempDirAtomicPathSwapping(AtomicPathSwapping):
160
+ def __init__(
161
+ self,
162
+ *,
163
+ temp_dir: ta.Optional[str] = None,
164
+ root_dir: ta.Optional[str] = None,
165
+ ) -> None:
166
+ super().__init__()
167
+
168
+ if root_dir is not None:
169
+ root_dir = os.path.abspath(root_dir)
170
+ self._root_dir = root_dir
171
+ self._temp_dir = temp_dir
172
+
173
+ def begin_atomic_path_swap(
174
+ self,
175
+ kind: AtomicPathSwapKind,
176
+ dst_path: str,
177
+ *,
178
+ name_hint: ta.Optional[str] = None,
179
+ make_dirs: bool = False,
180
+ **kwargs: ta.Any,
181
+ ) -> AtomicPathSwap:
182
+ dst_path = os.path.abspath(dst_path)
183
+ if self._root_dir is not None and not dst_path.startswith(check.non_empty_str(self._root_dir)):
184
+ raise RuntimeError(f'Atomic path swap dst must be in root dir: {dst_path}, {self._root_dir}')
185
+
186
+ dst_dir = os.path.dirname(dst_path)
187
+ if make_dirs:
188
+ os.makedirs(dst_dir, exist_ok=True)
189
+ if not os.path.isdir(dst_dir):
190
+ raise RuntimeError(f'Atomic path swap dst dir does not exist: {dst_dir}')
191
+
192
+ if kind == 'dir':
193
+ tmp_path = tempfile.mkdtemp(prefix=name_hint, dir=self._temp_dir)
194
+ elif kind == 'file':
195
+ fd, tmp_path = tempfile.mkstemp(prefix=name_hint, dir=self._temp_dir)
196
+ os.close(fd)
197
+ else:
198
+ raise TypeError(kind)
199
+
200
+ return OsReplaceAtomicPathSwap(
201
+ kind,
202
+ dst_path,
203
+ tmp_path,
204
+ **kwargs,
205
+ )
omlish/os/deathsig.py ADDED
@@ -0,0 +1,23 @@
1
+ # @omlish-lite
2
+ import ctypes as ct
3
+ import sys
4
+
5
+
6
+ LINUX_PR_SET_PDEATHSIG = 1 # Second arg is a signal
7
+ LINUX_PR_GET_PDEATHSIG = 2 # Second arg is a ptr to return the signal
8
+
9
+
10
+ def set_process_deathsig(sig: int) -> bool:
11
+ if sys.platform == 'linux':
12
+ libc = ct.CDLL('libc.so.6')
13
+
14
+ # int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
15
+ libc.prctl.restype = ct.c_int
16
+ libc.prctl.argtypes = [ct.c_int, ct.c_ulong, ct.c_ulong, ct.c_ulong, ct.c_ulong]
17
+
18
+ libc.prctl(LINUX_PR_SET_PDEATHSIG, sig, 0, 0, 0, 0)
19
+
20
+ return True
21
+
22
+ else:
23
+ return False
@@ -1,19 +1,10 @@
1
1
  import contextlib
2
2
  import os
3
- import resource
4
3
  import shutil
5
4
  import tempfile
6
5
  import typing as ta
7
6
 
8
7
 
9
- PAGE_SIZE = resource.getpagesize()
10
-
11
-
12
- def round_to_page_size(sz: int) -> int:
13
- sz += PAGE_SIZE - 1
14
- return sz - (sz % PAGE_SIZE)
15
-
16
-
17
8
  @contextlib.contextmanager
18
9
  def tmp_dir(
19
10
  root_dir: str | None = None,
@@ -1,4 +1,5 @@
1
1
  # ruff: noqa: UP007 UP012
2
+ # @omlish-lite
2
3
  import ctypes as ct
3
4
  import logging
4
5
  import sys
@@ -6,7 +7,7 @@ import syslog
6
7
  import threading
7
8
  import typing as ta
8
9
 
9
- from .cached import cached_nullary
10
+ from ..lite.cached import cached_nullary
10
11
 
11
12
 
12
13
  ##