omlish 0.0.0.dev159__py3-none-any.whl → 0.0.0.dev161__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.
- omlish/__about__.py +2 -2
- omlish/lite/marshal.py +20 -9
- omlish/lite/reflect.py +4 -0
- omlish/os/atomics.py +205 -0
- {omlish-0.0.0.dev159.dist-info → omlish-0.0.0.dev161.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev159.dist-info → omlish-0.0.0.dev161.dist-info}/RECORD +10 -9
- {omlish-0.0.0.dev159.dist-info → omlish-0.0.0.dev161.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev159.dist-info → omlish-0.0.0.dev161.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev159.dist-info → omlish-0.0.0.dev161.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev159.dist-info → omlish-0.0.0.dev161.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/lite/marshal.py
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
TODO:
|
3
3
|
- pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
|
4
|
-
- namedtuple
|
5
4
|
- literals
|
6
|
-
- newtypes?
|
7
5
|
"""
|
8
6
|
# ruff: noqa: UP006 UP007
|
9
7
|
import abc
|
@@ -15,6 +13,7 @@ import decimal
|
|
15
13
|
import enum
|
16
14
|
import fractions
|
17
15
|
import functools
|
16
|
+
import inspect
|
18
17
|
import threading
|
19
18
|
import typing as ta
|
20
19
|
import uuid
|
@@ -22,8 +21,10 @@ import weakref # noqa
|
|
22
21
|
|
23
22
|
from .check import check
|
24
23
|
from .reflect import deep_subclasses
|
24
|
+
from .reflect import get_new_type_supertype
|
25
25
|
from .reflect import get_optional_alias_arg
|
26
26
|
from .reflect import is_generic_alias
|
27
|
+
from .reflect import is_new_type
|
27
28
|
from .reflect import is_union_alias
|
28
29
|
from .strings import snake_case
|
29
30
|
|
@@ -37,7 +38,7 @@ T = ta.TypeVar('T')
|
|
37
38
|
@dc.dataclass(frozen=True)
|
38
39
|
class ObjMarshalOptions:
|
39
40
|
raw_bytes: bool = False
|
40
|
-
|
41
|
+
non_strict_fields: bool = False
|
41
42
|
|
42
43
|
|
43
44
|
class ObjMarshaler(abc.ABC):
|
@@ -166,10 +167,10 @@ class IterableObjMarshaler(ObjMarshaler):
|
|
166
167
|
|
167
168
|
|
168
169
|
@dc.dataclass(frozen=True)
|
169
|
-
class
|
170
|
+
class FieldsObjMarshaler(ObjMarshaler):
|
170
171
|
ty: type
|
171
172
|
fs: ta.Mapping[str, ObjMarshaler]
|
172
|
-
|
173
|
+
non_strict: bool = False
|
173
174
|
|
174
175
|
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
175
176
|
return {
|
@@ -181,7 +182,7 @@ class DataclassObjMarshaler(ObjMarshaler):
|
|
181
182
|
return self.ty(**{
|
182
183
|
k: self.fs[k].unmarshal(v, ctx)
|
183
184
|
for k, v in o.items()
|
184
|
-
if not (self.
|
185
|
+
if not (self.non_strict or ctx.options.non_strict_fields) or k in self.fs
|
185
186
|
})
|
186
187
|
|
187
188
|
|
@@ -313,7 +314,7 @@ class ObjMarshalerManager:
|
|
313
314
|
ty: ta.Any,
|
314
315
|
rec: ta.Callable[[ta.Any], ObjMarshaler],
|
315
316
|
*,
|
316
|
-
|
317
|
+
non_strict_fields: bool = False,
|
317
318
|
) -> ObjMarshaler:
|
318
319
|
if isinstance(ty, type):
|
319
320
|
if abc.ABC in ty.__bases__:
|
@@ -335,12 +336,22 @@ class ObjMarshalerManager:
|
|
335
336
|
return EnumObjMarshaler(ty)
|
336
337
|
|
337
338
|
if dc.is_dataclass(ty):
|
338
|
-
return
|
339
|
+
return FieldsObjMarshaler(
|
339
340
|
ty,
|
340
341
|
{f.name: rec(f.type) for f in dc.fields(ty)},
|
341
|
-
|
342
|
+
non_strict=non_strict_fields,
|
342
343
|
)
|
343
344
|
|
345
|
+
if issubclass(ty, tuple) and hasattr(ty, '_fields'):
|
346
|
+
return FieldsObjMarshaler(
|
347
|
+
ty,
|
348
|
+
{p.name: rec(p.annotation) for p in inspect.signature(ty).parameters.values()},
|
349
|
+
non_strict=non_strict_fields,
|
350
|
+
)
|
351
|
+
|
352
|
+
if is_new_type(ty):
|
353
|
+
return rec(get_new_type_supertype(ty))
|
354
|
+
|
344
355
|
if is_generic_alias(ty):
|
345
356
|
try:
|
346
357
|
mt = self._generic_mapping_types[ta.get_origin(ty)]
|
omlish/lite/reflect.py
CHANGED
@@ -46,6 +46,10 @@ def is_new_type(spec: ta.Any) -> bool:
|
|
46
46
|
return isinstance(spec, types.FunctionType) and spec.__code__ is ta.NewType.__code__.co_consts[1] # type: ignore # noqa
|
47
47
|
|
48
48
|
|
49
|
+
def get_new_type_supertype(spec: ta.Any) -> ta.Any:
|
50
|
+
return spec.__supertype__
|
51
|
+
|
52
|
+
|
49
53
|
def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
|
50
54
|
seen = set()
|
51
55
|
todo = list(reversed(cls.__subclasses__()))
|
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 OsRenameAtomicPathSwap(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.rename(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 OsRenameAtomicPathSwap(
|
201
|
+
kind,
|
202
|
+
dst_path,
|
203
|
+
tmp_path,
|
204
|
+
**kwargs,
|
205
|
+
)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=RX24SRc6DCEg77PUVnaXOKCWa5TF_c9RQJdGIf7gl9c,1135
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=mO53HhyG-ZTHxwvhTxgq__7mREyycsxsT-5hHzJF95Q,3409
|
3
3
|
omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
|
4
4
|
omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
|
5
5
|
omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
|
@@ -342,10 +342,10 @@ omlish/lite/contextmanagers.py,sha256=m9JO--p7L7mSl4cycXysH-1AO27weDKjP3DZG61cww
|
|
342
342
|
omlish/lite/inject.py,sha256=729Qi0TLbQgBtkvx97q1EUMe73VFYA1hu4woXkOTcwM,23572
|
343
343
|
omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
|
344
344
|
omlish/lite/logs.py,sha256=CWFG0NKGhqNeEgryF5atN2gkPYbUdTINEw_s1phbINM,51
|
345
|
-
omlish/lite/marshal.py,sha256=
|
345
|
+
omlish/lite/marshal.py,sha256=ldoZs_yiQIUpOjBviV9f4mwm7hSZy0hRLXrvQA-6POU,14257
|
346
346
|
omlish/lite/maybes.py,sha256=7OlHJ8Q2r4wQ-aRbZSlJY7x0e8gDvufFdlohGEIJ3P4,833
|
347
347
|
omlish/lite/pycharm.py,sha256=pUOJevrPClSqTCEOkQBO11LKX2003tfDcp18a03QFrc,1163
|
348
|
-
omlish/lite/reflect.py,sha256=
|
348
|
+
omlish/lite/reflect.py,sha256=L5_9gNp_BmAZ3l9PVezDmiXFg_6BOHbfQNB98tUULW0,1765
|
349
349
|
omlish/lite/resources.py,sha256=YNSmX1Ohck1aoWRs55a-o5ChVbFJIQhtbqE-XwF55Oc,326
|
350
350
|
omlish/lite/runtime.py,sha256=XQo408zxTdJdppUZqOWHyeUR50VlCpNIExNGHz4U6O4,459
|
351
351
|
omlish/lite/secrets.py,sha256=3Mz3V2jf__XU9qNHcH56sBSw95L3U2UPL24bjvobG0c,816
|
@@ -403,6 +403,7 @@ omlish/math/bits.py,sha256=yip1l8agOYzT7bFyMGc0RR3XlnGCfHMpjw_SECLLh1I,3477
|
|
403
403
|
omlish/math/floats.py,sha256=UimhOT7KRl8LXTzOI5cQWoX_9h6WNWe_3vcOuO7-h_8,327
|
404
404
|
omlish/math/stats.py,sha256=MegzKVsmv2kra4jDWLOUgV0X7Ee2Tbl5u6ql1v4-dEY,10053
|
405
405
|
omlish/os/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
406
|
+
omlish/os/atomics.py,sha256=gArGtyH5l3UjLKwKIqFJ5XkRnNouXntPEtkRbI0ch_0,5071
|
406
407
|
omlish/os/deathsig.py,sha256=hk9Yq2kyDdI-cI7OQH7mOfpRbOKzY_TfPKEqgrjVYbA,641
|
407
408
|
omlish/os/files.py,sha256=1tNy1z5I_CgYKA5c6lOfsXc-hknP4tQDbSShdz8HArw,1308
|
408
409
|
omlish/os/journald.py,sha256=2nI8Res1poXkbLc31--MPUlzYMESnCcPUkIxDOCjZW0,3903
|
@@ -525,9 +526,9 @@ omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,329
|
|
525
526
|
omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
|
526
527
|
omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
|
527
528
|
omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
|
528
|
-
omlish-0.0.0.
|
529
|
-
omlish-0.0.0.
|
530
|
-
omlish-0.0.0.
|
531
|
-
omlish-0.0.0.
|
532
|
-
omlish-0.0.0.
|
533
|
-
omlish-0.0.0.
|
529
|
+
omlish-0.0.0.dev161.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
530
|
+
omlish-0.0.0.dev161.dist-info/METADATA,sha256=FeXlao_p21yJqF4nL1hTicjLO3Ir0XMm9jii9k8-2ws,4264
|
531
|
+
omlish-0.0.0.dev161.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
532
|
+
omlish-0.0.0.dev161.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
533
|
+
omlish-0.0.0.dev161.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
534
|
+
omlish-0.0.0.dev161.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|