omlish 0.0.0.dev5__py3-none-any.whl → 0.0.0.dev7__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.
Potentially problematic release.
This version of omlish might be problematic. Click here for more details.
- omlish/__about__.py +109 -5
- omlish/__init__.py +0 -8
- omlish/asyncs/__init__.py +9 -9
- omlish/asyncs/anyio.py +123 -19
- omlish/asyncs/asyncio.py +23 -0
- omlish/asyncs/asyncs.py +9 -6
- omlish/asyncs/bridge.py +316 -0
- omlish/asyncs/trio_asyncio.py +7 -3
- omlish/bootstrap.py +737 -0
- omlish/check.py +1 -1
- omlish/collections/__init__.py +5 -0
- omlish/collections/exceptions.py +2 -0
- omlish/collections/identity.py +7 -0
- omlish/collections/utils.py +38 -9
- omlish/configs/strings.py +96 -0
- omlish/dataclasses/__init__.py +16 -0
- omlish/dataclasses/impl/copy.py +30 -0
- omlish/dataclasses/impl/descriptors.py +95 -0
- omlish/dataclasses/impl/exceptions.py +6 -0
- omlish/dataclasses/impl/fields.py +24 -25
- omlish/dataclasses/impl/init.py +4 -2
- omlish/dataclasses/impl/main.py +2 -0
- omlish/dataclasses/impl/reflect.py +1 -1
- omlish/dataclasses/utils.py +67 -0
- omlish/{lang/datetimes.py → datetimes.py} +8 -4
- omlish/diag/__init__.py +4 -0
- omlish/diag/procfs.py +2 -2
- omlish/{testing → diag}/pydevd.py +35 -0
- omlish/diag/threads.py +131 -48
- omlish/dispatch/_dispatch2.py +65 -0
- omlish/dispatch/_dispatch3.py +104 -0
- omlish/docker.py +16 -1
- omlish/fnpairs.py +11 -4
- omlish/formats/__init__.py +0 -0
- omlish/{configs → formats}/dotenv.py +15 -24
- omlish/{json.py → formats/json.py} +2 -1
- omlish/formats/yaml.py +223 -0
- omlish/graphs/trees.py +1 -1
- omlish/http/asgi.py +2 -1
- omlish/http/collections.py +15 -0
- omlish/http/consts.py +22 -1
- omlish/http/sessions.py +10 -3
- omlish/inject/__init__.py +49 -17
- omlish/inject/binder.py +185 -5
- omlish/inject/bindings.py +3 -36
- omlish/inject/eagers.py +2 -8
- omlish/inject/elements.py +31 -10
- omlish/inject/exceptions.py +1 -1
- omlish/inject/impl/elements.py +37 -12
- omlish/inject/impl/injector.py +72 -25
- omlish/inject/impl/inspect.py +33 -5
- omlish/inject/impl/origins.py +77 -0
- omlish/inject/impl/{private.py → privates.py} +2 -2
- omlish/inject/impl/scopes.py +6 -2
- omlish/inject/injector.py +8 -4
- omlish/inject/inspect.py +18 -0
- omlish/inject/keys.py +8 -14
- omlish/inject/listeners.py +26 -0
- omlish/inject/managed.py +76 -10
- omlish/inject/multis.py +68 -18
- omlish/inject/origins.py +30 -0
- omlish/inject/overrides.py +5 -4
- omlish/inject/{private.py → privates.py} +6 -10
- omlish/inject/providers.py +12 -85
- omlish/inject/scopes.py +13 -6
- omlish/inject/types.py +3 -1
- omlish/inject/utils.py +18 -0
- omlish/iterators.py +69 -2
- omlish/lang/__init__.py +24 -9
- omlish/lang/cached.py +2 -2
- omlish/lang/classes/restrict.py +12 -1
- omlish/lang/classes/simple.py +18 -8
- omlish/lang/contextmanagers.py +13 -4
- omlish/lang/descriptors.py +132 -1
- omlish/lang/functions.py +8 -28
- omlish/lang/imports.py +67 -0
- omlish/lang/iterables.py +60 -1
- omlish/lang/maybes.py +3 -0
- omlish/lang/objects.py +38 -0
- omlish/lang/strings.py +25 -0
- omlish/lang/sys.py +9 -0
- omlish/lang/typing.py +42 -0
- omlish/lifecycles/__init__.py +34 -0
- omlish/lifecycles/abstract.py +43 -0
- omlish/lifecycles/base.py +51 -0
- omlish/lifecycles/contextmanagers.py +74 -0
- omlish/lifecycles/controller.py +116 -0
- omlish/lifecycles/manager.py +161 -0
- omlish/lifecycles/states.py +43 -0
- omlish/lifecycles/transitions.py +64 -0
- omlish/lite/__init__.py +1 -0
- omlish/lite/cached.py +18 -0
- omlish/lite/check.py +29 -0
- omlish/lite/contextmanagers.py +18 -0
- omlish/lite/json.py +30 -0
- omlish/lite/logs.py +52 -0
- omlish/lite/marshal.py +316 -0
- omlish/lite/reflect.py +49 -0
- omlish/lite/runtime.py +18 -0
- omlish/lite/secrets.py +19 -0
- omlish/lite/strings.py +25 -0
- omlish/lite/subprocesses.py +112 -0
- omlish/logs/configs.py +15 -2
- omlish/logs/formatters.py +7 -2
- omlish/marshal/__init__.py +32 -0
- omlish/marshal/any.py +5 -5
- omlish/marshal/base.py +27 -11
- omlish/marshal/base64.py +24 -9
- omlish/marshal/dataclasses.py +34 -28
- omlish/marshal/datetimes.py +74 -18
- omlish/marshal/enums.py +14 -8
- omlish/marshal/exceptions.py +11 -1
- omlish/marshal/factories.py +59 -74
- omlish/marshal/forbidden.py +35 -0
- omlish/marshal/global_.py +11 -4
- omlish/marshal/iterables.py +21 -24
- omlish/marshal/mappings.py +23 -26
- omlish/marshal/naming.py +4 -0
- omlish/marshal/numbers.py +51 -0
- omlish/marshal/objects.py +1 -0
- omlish/marshal/optionals.py +11 -12
- omlish/marshal/polymorphism.py +86 -21
- omlish/marshal/primitives.py +4 -5
- omlish/marshal/standard.py +13 -8
- omlish/marshal/uuids.py +4 -5
- omlish/matchfns.py +218 -0
- omlish/os.py +64 -0
- omlish/reflect/__init__.py +39 -0
- omlish/reflect/isinstance.py +38 -0
- omlish/reflect/ops.py +84 -0
- omlish/reflect/subst.py +110 -0
- omlish/reflect/types.py +275 -0
- omlish/secrets/__init__.py +23 -0
- omlish/secrets/crypto.py +132 -0
- omlish/secrets/marshal.py +70 -0
- omlish/secrets/openssl.py +207 -0
- omlish/secrets/passwords.py +120 -0
- omlish/secrets/secrets.py +299 -0
- omlish/secrets/subprocesses.py +42 -0
- omlish/sql/dbs.py +7 -6
- omlish/sql/duckdb.py +136 -0
- omlish/sql/exprs.py +12 -0
- omlish/sql/secrets.py +10 -0
- omlish/sql/sqlean.py +17 -0
- omlish/term.py +2 -2
- omlish/testing/pytest/__init__.py +3 -2
- omlish/testing/pytest/inject/harness.py +3 -3
- omlish/testing/pytest/marks.py +4 -7
- omlish/testing/pytest/plugins/__init__.py +1 -0
- omlish/testing/pytest/plugins/asyncs.py +136 -0
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/testing/pytest/plugins/switches.py +54 -19
- omlish/text/glyphsplit.py +97 -0
- omlish-0.0.0.dev7.dist-info/METADATA +50 -0
- omlish-0.0.0.dev7.dist-info/RECORD +268 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/WHEEL +1 -1
- omlish/reflect.py +0 -355
- omlish-0.0.0.dev5.dist-info/METADATA +0 -34
- omlish-0.0.0.dev5.dist-info/RECORD +0 -212
- /omlish/{asyncs/futures.py → concurrent.py} +0 -0
- /omlish/{configs → formats}/props.py +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/top_level.txt +0 -0
omlish/check.py
CHANGED
|
@@ -58,7 +58,7 @@ def _unpack_isinstance_spec(spec: ta.Any) -> tuple:
|
|
|
58
58
|
return spec
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
def isinstance(v: ta.Any, spec:
|
|
61
|
+
def isinstance(v: ta.Any, spec: type[T] | tuple, msg: Message = None) -> T: # noqa
|
|
62
62
|
if not _isinstance(v, _unpack_isinstance_spec(spec)):
|
|
63
63
|
_raise(TypeError, 'Must be instance', msg, v, spec)
|
|
64
64
|
return v
|
omlish/collections/__init__.py
CHANGED
|
@@ -25,6 +25,10 @@ from .coerce import ( # noqa
|
|
|
25
25
|
seq_or_none,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
+
from .exceptions import ( # noqa
|
|
29
|
+
DuplicateKeyError,
|
|
30
|
+
)
|
|
31
|
+
|
|
28
32
|
from .frozen import ( # noqa
|
|
29
33
|
Frozen,
|
|
30
34
|
FrozenDict,
|
|
@@ -37,6 +41,7 @@ from .identity import ( # noqa
|
|
|
37
41
|
IdentityKeyDict,
|
|
38
42
|
IdentitySet,
|
|
39
43
|
IdentityWrapper,
|
|
44
|
+
IdentityWeakSet,
|
|
40
45
|
)
|
|
41
46
|
|
|
42
47
|
from .indexed import ( # noqa
|
omlish/collections/identity.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import operator as op
|
|
3
3
|
import typing as ta
|
|
4
|
+
import weakref
|
|
4
5
|
|
|
5
6
|
from .. import lang
|
|
6
7
|
from .mappings import yield_dict_init
|
|
@@ -103,3 +104,9 @@ class IdentitySet(ta.MutableSet[T]):
|
|
|
103
104
|
|
|
104
105
|
def __iter__(self) -> ta.Iterator[T]:
|
|
105
106
|
return iter(self._dict.values())
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class IdentityWeakSet(weakref.WeakSet):
|
|
110
|
+
def __init__(self, init=None):
|
|
111
|
+
super().__init__()
|
|
112
|
+
self.data = IdentitySet(init) # type: ignore
|
omlish/collections/utils.py
CHANGED
|
@@ -3,6 +3,8 @@ import itertools
|
|
|
3
3
|
import typing as ta
|
|
4
4
|
|
|
5
5
|
from .. import check
|
|
6
|
+
from .. import lang
|
|
7
|
+
from .exceptions import DuplicateKeyError
|
|
6
8
|
from .identity import IdentityKeyDict
|
|
7
9
|
from .identity import IdentitySet
|
|
8
10
|
|
|
@@ -48,29 +50,56 @@ def partition(items: ta.Iterable[T], pred: ta.Callable[[T], bool]) -> tuple[list
|
|
|
48
50
|
return t, f
|
|
49
51
|
|
|
50
52
|
|
|
51
|
-
def unique(
|
|
53
|
+
def unique(
|
|
54
|
+
it: ta.Iterable[T],
|
|
55
|
+
*,
|
|
56
|
+
key: ta.Callable[[T], ta.Any] = lang.identity,
|
|
57
|
+
identity: bool = False,
|
|
58
|
+
strict: bool = False,
|
|
59
|
+
) -> list[T]:
|
|
52
60
|
if isinstance(it, str):
|
|
53
61
|
raise TypeError(it)
|
|
54
62
|
ret: list[T] = []
|
|
55
|
-
seen: ta.MutableSet
|
|
63
|
+
seen: ta.MutableSet = IdentitySet() if identity else set()
|
|
56
64
|
for e in it:
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
k = key(e)
|
|
66
|
+
if k in seen:
|
|
67
|
+
if strict:
|
|
68
|
+
raise DuplicateKeyError(k, e)
|
|
69
|
+
else:
|
|
70
|
+
seen.add(k)
|
|
59
71
|
ret.append(e)
|
|
60
72
|
return ret
|
|
61
73
|
|
|
62
74
|
|
|
63
|
-
def unique_map(
|
|
75
|
+
def unique_map(
|
|
76
|
+
kvs: ta.Iterable[tuple[K, V]],
|
|
77
|
+
*,
|
|
78
|
+
identity: bool = False,
|
|
79
|
+
strict: bool = False,
|
|
80
|
+
) -> ta.MutableMapping[K, V]:
|
|
64
81
|
d: ta.MutableMapping[K, V] = IdentityKeyDict() if identity else {}
|
|
65
82
|
for k, v in kvs:
|
|
66
83
|
if k in d:
|
|
67
|
-
|
|
68
|
-
|
|
84
|
+
if strict:
|
|
85
|
+
raise DuplicateKeyError(k)
|
|
86
|
+
else:
|
|
87
|
+
d[k] = v
|
|
69
88
|
return d
|
|
70
89
|
|
|
71
90
|
|
|
72
|
-
def unique_map_by(
|
|
73
|
-
|
|
91
|
+
def unique_map_by(
|
|
92
|
+
fn: ta.Callable[[V], K],
|
|
93
|
+
vs: ta.Iterable[V],
|
|
94
|
+
*,
|
|
95
|
+
identity: bool = False,
|
|
96
|
+
strict: bool = False,
|
|
97
|
+
) -> ta.MutableMapping[K, V]:
|
|
98
|
+
return unique_map(
|
|
99
|
+
((fn(v), v) for v in vs),
|
|
100
|
+
identity=identity,
|
|
101
|
+
strict=strict,
|
|
102
|
+
)
|
|
74
103
|
|
|
75
104
|
|
|
76
105
|
def multi_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]:
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- reflecty generalized rewriter, obviously..
|
|
4
|
+
- env vars
|
|
5
|
+
- coalescing - {$FOO|$BAR|baz}
|
|
6
|
+
"""
|
|
7
|
+
import collections.abc
|
|
8
|
+
import typing as ta
|
|
9
|
+
|
|
10
|
+
from .. import dataclasses as dc
|
|
11
|
+
from .. import lang
|
|
12
|
+
from ..text import glyphsplit
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
T = ta.TypeVar('T')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InterpolateStringsMetadata(lang.Marker):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dc.field_modifier
|
|
23
|
+
def secret_or_key_field(f: dc.Field) -> dc.Field:
|
|
24
|
+
return dc.update_field_metadata(f, {
|
|
25
|
+
InterpolateStringsMetadata: True,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dc.field_modifier
|
|
30
|
+
def interpolate_field(f: dc.Field) -> dc.Field:
|
|
31
|
+
return dc.update_field_metadata(f, {InterpolateStringsMetadata: True})
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class StringRewriter:
|
|
35
|
+
def __init__(self, fn: ta.Callable[[str], str]) -> None:
|
|
36
|
+
super().__init__()
|
|
37
|
+
self._fn = fn
|
|
38
|
+
|
|
39
|
+
def __call__(self, v: T, *, _soft: bool = False) -> T:
|
|
40
|
+
if v is None:
|
|
41
|
+
return None # type: ignore
|
|
42
|
+
|
|
43
|
+
if dc.is_dataclass(v):
|
|
44
|
+
kw = {}
|
|
45
|
+
for f in dc.fields(v):
|
|
46
|
+
fv = getattr(v, f.name)
|
|
47
|
+
nfv = self(fv, _soft=not f.metadata.get(InterpolateStringsMetadata))
|
|
48
|
+
if fv is not nfv:
|
|
49
|
+
kw[f.name] = nfv
|
|
50
|
+
if not kw:
|
|
51
|
+
return v # type: ignore
|
|
52
|
+
return dc.replace(v, **kw)
|
|
53
|
+
|
|
54
|
+
if isinstance(v, str):
|
|
55
|
+
if not _soft:
|
|
56
|
+
v = self._fn(v) # type: ignore
|
|
57
|
+
return v # type: ignore
|
|
58
|
+
|
|
59
|
+
if isinstance(v, lang.BUILTIN_SCALAR_ITERABLE_TYPES):
|
|
60
|
+
return v # type: ignore
|
|
61
|
+
|
|
62
|
+
if isinstance(v, collections.abc.Mapping):
|
|
63
|
+
nm = []
|
|
64
|
+
b = False
|
|
65
|
+
for mk, mv in v.items():
|
|
66
|
+
nk, nv = self(mk, _soft=_soft), self(mv, _soft=_soft)
|
|
67
|
+
nm.append((nk, nv))
|
|
68
|
+
b |= nk is not mk or nv is not mv
|
|
69
|
+
if not b:
|
|
70
|
+
return v # type: ignore
|
|
71
|
+
return v.__class__(nm) # type: ignore
|
|
72
|
+
|
|
73
|
+
if isinstance(v, (collections.abc.Sequence, collections.abc.Set)):
|
|
74
|
+
nl = []
|
|
75
|
+
b = False
|
|
76
|
+
for le in v:
|
|
77
|
+
ne = self(le, _soft=_soft)
|
|
78
|
+
nl.append(ne)
|
|
79
|
+
b |= ne is not le
|
|
80
|
+
if not b:
|
|
81
|
+
return v # type: ignore
|
|
82
|
+
return v.__class__(nl) # type: ignore
|
|
83
|
+
|
|
84
|
+
return v
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def interpolate_strings(v: T, rpl: ta.Mapping[str, str]) -> T:
|
|
88
|
+
def fn(v):
|
|
89
|
+
if not v:
|
|
90
|
+
return v
|
|
91
|
+
sps = glyphsplit.split_braces(v)
|
|
92
|
+
if len(sps) == 1 and isinstance(sps[0], str):
|
|
93
|
+
return sps[0]
|
|
94
|
+
return ''.join(rpl[p.s] if isinstance(p, glyphsplit.GlyphMatch) else p for p in sps)
|
|
95
|
+
|
|
96
|
+
return StringRewriter(fn)(v)
|
omlish/dataclasses/__init__.py
CHANGED
|
@@ -36,6 +36,11 @@ from .impl.as_ import ( # noqa
|
|
|
36
36
|
astuple,
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
+
from .impl.params import ( # noqa
|
|
40
|
+
FieldExtras,
|
|
41
|
+
get_field_extras,
|
|
42
|
+
)
|
|
43
|
+
|
|
39
44
|
from .impl.replace import ( # noqa
|
|
40
45
|
replace,
|
|
41
46
|
)
|
|
@@ -55,6 +60,7 @@ globals()['make_dataclass'] = xmake_dataclass
|
|
|
55
60
|
|
|
56
61
|
from .impl.exceptions import ( # noqa
|
|
57
62
|
CheckError,
|
|
63
|
+
FieldCheckError,
|
|
58
64
|
)
|
|
59
65
|
|
|
60
66
|
from .impl.metaclass import ( # noqa
|
|
@@ -81,3 +87,13 @@ from .impl.reflect import ( # noqa
|
|
|
81
87
|
ClassInfo,
|
|
82
88
|
reflect,
|
|
83
89
|
)
|
|
90
|
+
|
|
91
|
+
from .utils import ( # noqa
|
|
92
|
+
chain_metadata,
|
|
93
|
+
deep_replace,
|
|
94
|
+
field_modifier,
|
|
95
|
+
maybe_post_init,
|
|
96
|
+
opt_repr,
|
|
97
|
+
update_field_extras,
|
|
98
|
+
update_field_metadata,
|
|
99
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- __deepcopy__
|
|
4
|
+
"""
|
|
5
|
+
import dataclasses as dc
|
|
6
|
+
|
|
7
|
+
from .fields import field_type
|
|
8
|
+
from .internals import FIELDS_ATTR
|
|
9
|
+
from .internals import FieldType
|
|
10
|
+
from .processing import Processor
|
|
11
|
+
from .utils import set_new_attribute
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
MISSING = dc.MISSING
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _copy(obj):
|
|
18
|
+
kw = {}
|
|
19
|
+
|
|
20
|
+
for f in getattr(obj, FIELDS_ATTR).values():
|
|
21
|
+
if field_type(f) is FieldType.CLASS:
|
|
22
|
+
continue
|
|
23
|
+
kw[f.name] = getattr(obj, f.name)
|
|
24
|
+
|
|
25
|
+
return obj.__class__(**kw)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CopyProcessor(Processor):
|
|
29
|
+
def _process(self) -> None:
|
|
30
|
+
set_new_attribute(self._cls, '__copy__', _copy)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import dataclasses as dc
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from ... import defs
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AbstractFieldDescriptor(abc.ABC):
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
*,
|
|
13
|
+
default: ta.Any = dc.MISSING,
|
|
14
|
+
frozen: bool = False,
|
|
15
|
+
name: str | None = None,
|
|
16
|
+
pre_set: ta.Callable[[ta.Any, ta.Any], ta.Any] | None = None,
|
|
17
|
+
post_set: ta.Callable[[ta.Any, ta.Any], None] | None = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
super().__init__()
|
|
20
|
+
|
|
21
|
+
self._default = default
|
|
22
|
+
self._frozen = frozen
|
|
23
|
+
self._name = name
|
|
24
|
+
self._pre_set = pre_set
|
|
25
|
+
self._post_set = post_set
|
|
26
|
+
|
|
27
|
+
defs.repr('name')
|
|
28
|
+
defs.getter('default', 'frozen', 'name', 'pre_set', 'post_set')
|
|
29
|
+
|
|
30
|
+
def __set_name__(self, owner, name):
|
|
31
|
+
if self._name is None:
|
|
32
|
+
self._name = name
|
|
33
|
+
|
|
34
|
+
def __get__(self, instance, owner=None):
|
|
35
|
+
if instance is not None:
|
|
36
|
+
try:
|
|
37
|
+
return self._get(instance)
|
|
38
|
+
except AttributeError:
|
|
39
|
+
pass
|
|
40
|
+
if self._default is not dc.MISSING:
|
|
41
|
+
return self._default
|
|
42
|
+
raise AttributeError(self._name)
|
|
43
|
+
|
|
44
|
+
@abc.abstractmethod
|
|
45
|
+
def _get(self, instance):
|
|
46
|
+
raise NotImplementedError
|
|
47
|
+
|
|
48
|
+
def __set__(self, instance, value):
|
|
49
|
+
if self._frozen:
|
|
50
|
+
raise dc.FrozenInstanceError(f'cannot assign to field {self._name!r}')
|
|
51
|
+
if self._pre_set is not None:
|
|
52
|
+
value = self._pre_set(instance, value)
|
|
53
|
+
self._set(instance, value)
|
|
54
|
+
if self._post_set is not None:
|
|
55
|
+
self._post_set(instance, value)
|
|
56
|
+
|
|
57
|
+
@abc.abstractmethod
|
|
58
|
+
def _set(self, instance, value):
|
|
59
|
+
raise NotImplementedError
|
|
60
|
+
|
|
61
|
+
def __delete__(self, instance):
|
|
62
|
+
if self._frozen:
|
|
63
|
+
raise dc.FrozenInstanceError(f'cannot delete field {self._name!r}')
|
|
64
|
+
self._del(instance)
|
|
65
|
+
|
|
66
|
+
@abc.abstractmethod
|
|
67
|
+
def _del(self, instance):
|
|
68
|
+
raise NotImplementedError
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class PyFieldDescriptor(AbstractFieldDescriptor):
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
attr: str,
|
|
76
|
+
**kwargs: ta.Any,
|
|
77
|
+
) -> None:
|
|
78
|
+
super().__init__(**kwargs)
|
|
79
|
+
|
|
80
|
+
self._attr = attr
|
|
81
|
+
|
|
82
|
+
defs.repr('attr', 'name')
|
|
83
|
+
defs.getter('attr')
|
|
84
|
+
|
|
85
|
+
def _get(self, instance):
|
|
86
|
+
return getattr(instance, self._attr)
|
|
87
|
+
|
|
88
|
+
def _set(self, instance, value):
|
|
89
|
+
setattr(instance, self._attr, value)
|
|
90
|
+
|
|
91
|
+
def _del(self, instance):
|
|
92
|
+
delattr(instance, self._attr)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
FieldDescriptor = PyFieldDescriptor
|
|
@@ -98,51 +98,50 @@ def field_init(
|
|
|
98
98
|
|
|
99
99
|
lines = []
|
|
100
100
|
|
|
101
|
-
if fx.coerce is not None:
|
|
102
|
-
cn = f'__dataclass_coerce__{f.name}__'
|
|
103
|
-
locals[cn] = fx.coerce
|
|
104
|
-
lines.append(f'{f.name} = {cn}({f.name})')
|
|
105
|
-
|
|
106
|
-
if fx.check is not None:
|
|
107
|
-
cn = f'__dataclass_check__{f.name}__'
|
|
108
|
-
locals[cn] = fx.check
|
|
109
|
-
lines.append(f'if not {cn}({f.name}): raise __dataclass_CheckException__')
|
|
110
|
-
|
|
111
|
-
if fx.check_type:
|
|
112
|
-
cn = f'__dataclass_check_type__{f.name}__'
|
|
113
|
-
locals[cn] = f.type
|
|
114
|
-
lines.append(
|
|
115
|
-
f'if not __dataclass_builtins_isinstance__({f.name}, {cn}): '
|
|
116
|
-
f'raise __dataclass_builtins_TypeError__({f.name}, {cn})',
|
|
117
|
-
)
|
|
118
|
-
|
|
119
101
|
value: str | None = None
|
|
120
102
|
if f.default_factory is not MISSING:
|
|
121
103
|
if f.init:
|
|
122
104
|
locals[default_name] = f.default_factory
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__ '
|
|
126
|
-
f'else {f.name}'
|
|
127
|
-
)
|
|
105
|
+
lines.append(f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__: {f.name} = {default_name}()')
|
|
106
|
+
value = f.name
|
|
128
107
|
else:
|
|
129
108
|
locals[default_name] = f.default_factory
|
|
130
|
-
|
|
109
|
+
lines.append(f'{f.name} = {default_name}()')
|
|
110
|
+
value = f.name
|
|
131
111
|
|
|
132
112
|
elif f.init:
|
|
133
113
|
if f.default is MISSING:
|
|
134
114
|
value = f.name
|
|
135
115
|
elif f.default is not MISSING:
|
|
136
|
-
locals[default_name] = f.default
|
|
116
|
+
locals[default_name] = f.default # Not referenced her, just useful / consistent to have in function scope
|
|
137
117
|
value = f.name
|
|
138
118
|
|
|
139
119
|
elif slots and f.default is not MISSING:
|
|
140
120
|
locals[default_name] = f.default
|
|
121
|
+
lines.append(f'{f.name} = {default_name}')
|
|
141
122
|
value = default_name
|
|
142
123
|
|
|
143
124
|
else:
|
|
144
125
|
pass
|
|
145
126
|
|
|
127
|
+
if fx.coerce is not None:
|
|
128
|
+
cn = f'__dataclass_coerce__{f.name}__'
|
|
129
|
+
locals[cn] = fx.coerce
|
|
130
|
+
lines.append(f'{value} = {cn}({value})')
|
|
131
|
+
|
|
132
|
+
if fx.check is not None:
|
|
133
|
+
cn = f'__dataclass_check__{f.name}__'
|
|
134
|
+
locals[cn] = fx.check
|
|
135
|
+
lines.append(f'if not {cn}({value}): raise __dataclass_FieldCheckError__({f.name})')
|
|
136
|
+
|
|
137
|
+
if fx.check_type:
|
|
138
|
+
cn = f'__dataclass_check_type__{f.name}__'
|
|
139
|
+
locals[cn] = f.type
|
|
140
|
+
lines.append(
|
|
141
|
+
f'if not __dataclass_builtins_isinstance__({value}, {cn}): '
|
|
142
|
+
f'raise __dataclass_builtins_TypeError__({value}, {cn})',
|
|
143
|
+
)
|
|
144
|
+
|
|
146
145
|
if value is not None and field_type(f) is not FieldType.INIT:
|
|
147
146
|
lines.append(field_assign(frozen, f.name, value, self_name, fx.override)) # noqa
|
|
148
147
|
|
omlish/dataclasses/impl/init.py
CHANGED
|
@@ -4,6 +4,7 @@ import typing as ta
|
|
|
4
4
|
|
|
5
5
|
from ... import lang
|
|
6
6
|
from .exceptions import CheckError
|
|
7
|
+
from .exceptions import FieldCheckError
|
|
7
8
|
from .fields import field_init
|
|
8
9
|
from .fields import field_type
|
|
9
10
|
from .fields import has_default
|
|
@@ -99,7 +100,8 @@ class InitBuilder:
|
|
|
99
100
|
'__dataclass_builtins_object__': object,
|
|
100
101
|
'__dataclass_builtins_isinstance__': isinstance,
|
|
101
102
|
'__dataclass_builtins_TypeError__': TypeError,
|
|
102
|
-
'
|
|
103
|
+
'__dataclass_CheckError__': CheckError,
|
|
104
|
+
'__dataclass_FieldCheckError__': FieldCheckError,
|
|
103
105
|
})
|
|
104
106
|
|
|
105
107
|
body_lines: list[str] = []
|
|
@@ -126,7 +128,7 @@ class InitBuilder:
|
|
|
126
128
|
locals[cn] = fn
|
|
127
129
|
csig = inspect.signature(fn)
|
|
128
130
|
cas = ', '.join(p.name for p in csig.parameters.values())
|
|
129
|
-
body_lines.append(f'if not {cn}({cas}): raise
|
|
131
|
+
body_lines.append(f'if not {cn}({cas}): raise __dataclass_CheckError__')
|
|
130
132
|
|
|
131
133
|
for i, fn in enumerate(self._info.merged_metadata.get(Init, [])):
|
|
132
134
|
cn = f'__dataclass_init_{i}__'
|
omlish/dataclasses/impl/main.py
CHANGED
|
@@ -4,6 +4,7 @@ import typing as ta
|
|
|
4
4
|
|
|
5
5
|
from ... import check
|
|
6
6
|
from ... import lang
|
|
7
|
+
from .copy import CopyProcessor
|
|
7
8
|
from .fields import preprocess_field
|
|
8
9
|
from .frozen import FrozenProcessor
|
|
9
10
|
from .hashing import HashProcessor
|
|
@@ -137,6 +138,7 @@ class MainProcessor:
|
|
|
137
138
|
DocProcessor,
|
|
138
139
|
MatchArgsProcessor,
|
|
139
140
|
ReplaceProcessor,
|
|
141
|
+
CopyProcessor,
|
|
140
142
|
]:
|
|
141
143
|
pcls(self._info).process()
|
|
142
144
|
|
|
@@ -149,7 +149,7 @@ class ClassInfo:
|
|
|
149
149
|
|
|
150
150
|
@cached.property
|
|
151
151
|
def generic_mro_lookup(self) -> ta.Mapping[type, rfl.Type]:
|
|
152
|
-
return col.unique_map((check.not_none(rfl.get_concrete_type(g)), g) for g in self.generic_mro)
|
|
152
|
+
return col.unique_map(((check.not_none(rfl.get_concrete_type(g)), g) for g in self.generic_mro), strict=True)
|
|
153
153
|
|
|
154
154
|
@cached.property
|
|
155
155
|
def generic_replaced_field_types(self) -> ta.Mapping[str, rfl.Type]:
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
import dataclasses as dc
|
|
3
|
+
import types
|
|
4
|
+
import typing as ta
|
|
5
|
+
|
|
6
|
+
from .. import check
|
|
7
|
+
from .impl.params import DEFAULT_FIELD_EXTRAS
|
|
8
|
+
from .impl.params import FieldExtras
|
|
9
|
+
from .impl.params import get_field_extras
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
T = ta.TypeVar('T')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def maybe_post_init(sup: ta.Any) -> bool:
|
|
16
|
+
try:
|
|
17
|
+
fn = sup.__post_init__
|
|
18
|
+
except AttributeError:
|
|
19
|
+
return False
|
|
20
|
+
fn()
|
|
21
|
+
return True
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def opt_repr(o: ta.Any) -> str | None:
|
|
25
|
+
return repr(o) if o is not None else None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class field_modifier: # noqa
|
|
29
|
+
def __init__(self, fn: ta.Callable[[dc.Field], dc.Field]) -> None:
|
|
30
|
+
super().__init__()
|
|
31
|
+
self.fn = fn
|
|
32
|
+
|
|
33
|
+
def __ror__(self, other: T) -> T:
|
|
34
|
+
return self(other)
|
|
35
|
+
|
|
36
|
+
def __call__(self, f: T) -> T:
|
|
37
|
+
return check.isinstance(self.fn(check.isinstance(f, dc.Field)), dc.Field) # type: ignore
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def chain_metadata(*mds: ta.Mapping) -> types.MappingProxyType:
|
|
41
|
+
return types.MappingProxyType(collections.ChainMap(*mds)) # type: ignore # noqa
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def update_field_metadata(f: dc.Field, nmd: ta.Mapping) -> dc.Field:
|
|
45
|
+
check.isinstance(f, dc.Field)
|
|
46
|
+
f.metadata = chain_metadata(nmd, f.metadata)
|
|
47
|
+
return f
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def update_field_extras(f: dc.Field, *, unless_non_default: bool = False, **kwargs: ta.Any) -> dc.Field:
|
|
51
|
+
fe = get_field_extras(f)
|
|
52
|
+
return update_field_metadata(f, {
|
|
53
|
+
FieldExtras: dc.replace(fe, **{
|
|
54
|
+
k: v
|
|
55
|
+
for k, v in kwargs.items()
|
|
56
|
+
if not unless_non_default or v != getattr(DEFAULT_FIELD_EXTRAS, k)
|
|
57
|
+
}),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def deep_replace(o: T, *args: str | ta.Callable[[ta.Any], ta.Mapping[str, ta.Any]]) -> T:
|
|
62
|
+
if not args:
|
|
63
|
+
return o
|
|
64
|
+
elif len(args) == 1:
|
|
65
|
+
return dc.replace(o, **args[0](o)) # type: ignore
|
|
66
|
+
else:
|
|
67
|
+
return dc.replace(o, **{args[0]: deep_replace(getattr(o, args[0]), *args[1:])}) # type: ignore
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
# ruff: noqa: UP007
|
|
2
|
+
# @omlish-lite
|
|
1
3
|
import datetime
|
|
2
4
|
import re
|
|
5
|
+
import typing as ta
|
|
3
6
|
|
|
4
7
|
|
|
5
8
|
def to_seconds(value: datetime.timedelta) -> float:
|
|
@@ -18,8 +21,7 @@ def months_ago(date: datetime.date, num: int) -> datetime.date:
|
|
|
18
21
|
return datetime.date(ago_year, ago_month, 1)
|
|
19
22
|
|
|
20
23
|
|
|
21
|
-
def parse_date(s: str, tz: datetime.timezone
|
|
22
|
-
|
|
24
|
+
def parse_date(s: str, tz: ta.Optional[datetime.timezone] = None) -> datetime.date:
|
|
23
25
|
if s.lower() in ['today', 'now']:
|
|
24
26
|
return datetime.datetime.now(tz=tz)
|
|
25
27
|
elif s.lower() == 'yesterday':
|
|
@@ -39,7 +41,8 @@ _TIMEDELTA_STR_RE = re.compile(
|
|
|
39
41
|
r'((?P<days>-?\d+)\s*days?,\s*)?'
|
|
40
42
|
r'(?P<hours>\d?\d):(?P<minutes>\d\d)'
|
|
41
43
|
r':(?P<seconds>\d\d+(\.\d+)?)'
|
|
42
|
-
r'\s*$'
|
|
44
|
+
r'\s*$',
|
|
45
|
+
)
|
|
43
46
|
|
|
44
47
|
|
|
45
48
|
_TIMEDELTA_DHMS_RE = re.compile(
|
|
@@ -49,7 +52,8 @@ _TIMEDELTA_DHMS_RE = re.compile(
|
|
|
49
52
|
r',?\s*((?P<hours>\d+(\.\d+)?)\s*(h|hours?))?'
|
|
50
53
|
r',?\s*((?P<minutes>\d+(\.\d+)?)\s*(m|minutes?))?'
|
|
51
54
|
r',?\s*((?P<seconds>\d+(\.\d+)?)\s*(s|secs?|seconds?))?'
|
|
52
|
-
r'\s*$'
|
|
55
|
+
r'\s*$',
|
|
56
|
+
)
|
|
53
57
|
|
|
54
58
|
|
|
55
59
|
def parse_timedelta(s: str) -> datetime.timedelta:
|
omlish/diag/__init__.py
CHANGED
omlish/diag/procfs.py
CHANGED
|
@@ -12,9 +12,9 @@ import sys
|
|
|
12
12
|
import typing as ta
|
|
13
13
|
|
|
14
14
|
from .. import iterators as it
|
|
15
|
-
from .. import json
|
|
16
15
|
from .. import lang
|
|
17
16
|
from .. import os as oos
|
|
17
|
+
from ..formats import json
|
|
18
18
|
from .procstats import ProcStats
|
|
19
19
|
|
|
20
20
|
|
|
@@ -42,7 +42,7 @@ def parse_size(s: str) -> int:
|
|
|
42
42
|
return int(v) * us[u]
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
class ProcStat(lang.Namespace):
|
|
45
|
+
class ProcStat(lang.Namespace, lang.Final):
|
|
46
46
|
PID = 0
|
|
47
47
|
COMM = 1
|
|
48
48
|
STATE = 2
|