omlish 0.0.0.dev4__py3-none-any.whl → 0.0.0.dev6__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 +1 -1
- omlish/__init__.py +1 -1
- omlish/asyncs/__init__.py +10 -4
- omlish/asyncs/anyio.py +142 -12
- omlish/asyncs/asyncio.py +23 -0
- omlish/asyncs/asyncs.py +9 -6
- omlish/asyncs/bridge.py +316 -0
- omlish/asyncs/flavors.py +27 -1
- omlish/asyncs/trio_asyncio.py +28 -18
- omlish/c3.py +1 -1
- omlish/cached.py +1 -2
- omlish/collections/__init__.py +5 -1
- omlish/collections/cache/impl.py +1 -1
- omlish/collections/identity.py +7 -0
- omlish/collections/indexed.py +1 -1
- omlish/collections/utils.py +38 -6
- omlish/configs/__init__.py +5 -0
- omlish/configs/classes.py +53 -0
- omlish/configs/strings.py +94 -0
- omlish/dataclasses/__init__.py +9 -0
- omlish/dataclasses/impl/api.py +1 -1
- omlish/dataclasses/impl/as_.py +1 -1
- omlish/dataclasses/impl/copy.py +30 -0
- omlish/dataclasses/impl/exceptions.py +6 -0
- omlish/dataclasses/impl/fields.py +25 -25
- omlish/dataclasses/impl/init.py +5 -3
- omlish/dataclasses/impl/main.py +3 -0
- omlish/dataclasses/impl/metaclass.py +6 -1
- omlish/dataclasses/impl/order.py +1 -1
- omlish/dataclasses/impl/reflect.py +15 -2
- omlish/dataclasses/utils.py +44 -0
- omlish/defs.py +1 -1
- omlish/diag/__init__.py +4 -0
- omlish/diag/procfs.py +31 -3
- omlish/diag/procstats.py +32 -0
- omlish/{testing → diag}/pydevd.py +35 -0
- omlish/diag/replserver/console.py +3 -3
- omlish/diag/replserver/server.py +6 -5
- omlish/diag/threads.py +86 -0
- omlish/dispatch/_dispatch2.py +65 -0
- omlish/dispatch/_dispatch3.py +104 -0
- omlish/docker.py +20 -1
- omlish/fnpairs.py +37 -18
- omlish/graphs/dags.py +113 -0
- omlish/graphs/domination.py +268 -0
- omlish/graphs/trees.py +2 -2
- omlish/http/__init__.py +25 -0
- omlish/http/asgi.py +132 -0
- omlish/http/collections.py +15 -0
- omlish/http/consts.py +47 -5
- omlish/http/cookies.py +194 -0
- omlish/http/dates.py +70 -0
- omlish/http/encodings.py +6 -0
- omlish/http/json.py +273 -0
- omlish/http/sessions.py +204 -0
- omlish/inject/__init__.py +51 -17
- omlish/inject/binder.py +185 -5
- omlish/inject/bindings.py +3 -36
- omlish/inject/eagers.py +2 -8
- omlish/inject/elements.py +30 -9
- omlish/inject/exceptions.py +3 -3
- omlish/inject/impl/elements.py +65 -31
- omlish/inject/impl/injector.py +20 -2
- omlish/inject/impl/inspect.py +33 -5
- omlish/inject/impl/multis.py +74 -0
- omlish/inject/impl/origins.py +75 -0
- omlish/inject/impl/{private.py → privates.py} +2 -2
- omlish/inject/impl/providers.py +19 -39
- omlish/inject/{proxy.py → impl/proxy.py} +2 -2
- omlish/inject/impl/scopes.py +7 -2
- omlish/inject/injector.py +9 -4
- omlish/inject/inspect.py +18 -0
- omlish/inject/keys.py +11 -23
- omlish/inject/listeners.py +26 -0
- omlish/inject/managed.py +76 -10
- omlish/inject/multis.py +120 -0
- omlish/inject/origins.py +27 -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 +20 -9
- omlish/inject/types.py +2 -8
- omlish/iterators.py +13 -0
- omlish/lang/__init__.py +12 -2
- omlish/lang/cached.py +2 -2
- omlish/lang/classes/restrict.py +3 -2
- omlish/lang/classes/simple.py +18 -8
- omlish/lang/classes/virtual.py +2 -2
- omlish/lang/contextmanagers.py +75 -2
- omlish/lang/datetimes.py +6 -5
- omlish/lang/descriptors.py +131 -0
- omlish/lang/functions.py +18 -28
- omlish/lang/imports.py +11 -2
- omlish/lang/iterables.py +20 -1
- omlish/lang/typing.py +6 -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/logs/formatters.py +1 -1
- omlish/logs/utils.py +1 -1
- omlish/marshal/__init__.py +4 -0
- omlish/marshal/datetimes.py +1 -1
- omlish/marshal/naming.py +4 -0
- omlish/marshal/objects.py +1 -0
- omlish/marshal/polymorphism.py +4 -4
- omlish/reflect.py +139 -18
- omlish/secrets/__init__.py +7 -0
- omlish/secrets/marshal.py +41 -0
- omlish/secrets/passwords.py +120 -0
- omlish/secrets/secrets.py +47 -0
- omlish/serde/__init__.py +0 -0
- omlish/serde/dotenv.py +574 -0
- omlish/{json.py → serde/json.py} +4 -2
- omlish/serde/props.py +604 -0
- omlish/serde/yaml.py +223 -0
- omlish/sql/dbs.py +1 -1
- omlish/sql/duckdb.py +136 -0
- omlish/sql/sqlean.py +17 -0
- omlish/sync.py +70 -0
- omlish/term.py +7 -2
- omlish/testing/pytest/__init__.py +8 -2
- omlish/testing/pytest/helpers.py +0 -24
- omlish/testing/pytest/inject/harness.py +4 -4
- omlish/testing/pytest/marks.py +45 -0
- omlish/testing/pytest/plugins/__init__.py +3 -0
- omlish/testing/pytest/plugins/asyncs.py +136 -0
- omlish/testing/pytest/plugins/managermarks.py +60 -0
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/testing/testing.py +10 -0
- omlish/text/delimit.py +4 -0
- omlish/text/glyphsplit.py +92 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/METADATA +1 -1
- omlish-0.0.0.dev6.dist-info/RECORD +240 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/WHEEL +1 -1
- omlish/configs/props.py +0 -64
- omlish-0.0.0.dev4.dist-info/RECORD +0 -195
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/top_level.txt +0 -0
omlish/asyncs/trio_asyncio.py
CHANGED
|
@@ -3,12 +3,16 @@ import typing as ta
|
|
|
3
3
|
|
|
4
4
|
from .. import lang
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
if ta.TYPE_CHECKING:
|
|
7
8
|
import asyncio
|
|
9
|
+
|
|
8
10
|
import sniffio
|
|
9
11
|
import trio_asyncio
|
|
12
|
+
|
|
10
13
|
else:
|
|
11
14
|
asyncio = lang.proxy_import('asyncio')
|
|
15
|
+
|
|
12
16
|
sniffio = lang.proxy_import('sniffio')
|
|
13
17
|
trio_asyncio = lang.proxy_import('trio_asyncio')
|
|
14
18
|
|
|
@@ -18,24 +22,30 @@ def check_trio_asyncio() -> None:
|
|
|
18
22
|
raise RuntimeError('trio_asyncio loop not running')
|
|
19
23
|
|
|
20
24
|
|
|
21
|
-
def with_trio_asyncio_loop(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if sniffio.current_async_library() != 'trio':
|
|
29
|
-
raise RuntimeError('trio loop not running')
|
|
25
|
+
def with_trio_asyncio_loop(*, wait=False, strict=False):
|
|
26
|
+
def outer(fn):
|
|
27
|
+
@functools.wraps(fn)
|
|
28
|
+
async def inner(*args, **kwargs):
|
|
29
|
+
if trio_asyncio.current_loop.get() is not None:
|
|
30
|
+
await fn(*args, **kwargs)
|
|
31
|
+
return
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
if sniffio.current_async_library() != 'trio':
|
|
34
|
+
if strict:
|
|
35
|
+
raise RuntimeError('trio loop not running')
|
|
34
36
|
await fn(*args, **kwargs)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
loop: asyncio.BaseEventLoop
|
|
40
|
+
async with trio_asyncio.open_loop() as loop:
|
|
41
|
+
try:
|
|
42
|
+
await fn(*args, **kwargs)
|
|
43
|
+
finally:
|
|
44
|
+
if wait:
|
|
45
|
+
# FIXME: lol
|
|
46
|
+
while asyncio.all_tasks(loop):
|
|
47
|
+
await asyncio.sleep(.1)
|
|
48
|
+
|
|
49
|
+
return inner
|
|
40
50
|
|
|
41
|
-
return
|
|
51
|
+
return outer
|
omlish/c3.py
CHANGED
|
@@ -144,7 +144,7 @@ def compose_mro(
|
|
|
144
144
|
|
|
145
145
|
# Remove entries which are strict bases of other entries (they will end up in the MRO anyway.
|
|
146
146
|
def is_strict_base(typ):
|
|
147
|
-
for other in types:
|
|
147
|
+
for other in types: # noqa
|
|
148
148
|
if typ != other and typ in (get_mro(other) or []):
|
|
149
149
|
return True
|
|
150
150
|
return False
|
omlish/cached.py
CHANGED
omlish/collections/__init__.py
CHANGED
|
@@ -37,6 +37,7 @@ from .identity import ( # noqa
|
|
|
37
37
|
IdentityKeyDict,
|
|
38
38
|
IdentitySet,
|
|
39
39
|
IdentityWrapper,
|
|
40
|
+
IdentityWeakSet,
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
from .indexed import ( # noqa
|
|
@@ -90,9 +91,12 @@ from .utils import ( # noqa
|
|
|
90
91
|
all_not_equal,
|
|
91
92
|
indexes,
|
|
92
93
|
key_cmp,
|
|
94
|
+
multi_map,
|
|
95
|
+
multi_map_by,
|
|
93
96
|
mut_toposort,
|
|
94
97
|
partition,
|
|
95
98
|
toposort,
|
|
96
99
|
unique,
|
|
97
|
-
|
|
100
|
+
unique_map,
|
|
101
|
+
unique_map_by,
|
|
98
102
|
)
|
omlish/collections/cache/impl.py
CHANGED
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/indexed.py
CHANGED
omlish/collections/utils.py
CHANGED
|
@@ -12,6 +12,9 @@ K = ta.TypeVar('K')
|
|
|
12
12
|
V = ta.TypeVar('V')
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
##
|
|
16
|
+
|
|
17
|
+
|
|
15
18
|
def mut_toposort(data: dict[T, set[T]]) -> ta.Iterator[set[T]]:
|
|
16
19
|
for k, v in data.items():
|
|
17
20
|
v.discard(k)
|
|
@@ -31,6 +34,9 @@ def toposort(data: ta.Mapping[T, ta.AbstractSet[T]]) -> ta.Iterator[set[T]]:
|
|
|
31
34
|
return mut_toposort({k: set(v) for k, v in data.items()})
|
|
32
35
|
|
|
33
36
|
|
|
37
|
+
##
|
|
38
|
+
|
|
39
|
+
|
|
34
40
|
def partition(items: ta.Iterable[T], pred: ta.Callable[[T], bool]) -> tuple[list[T], list[T]]:
|
|
35
41
|
t: list[T] = []
|
|
36
42
|
f: list[T] = []
|
|
@@ -54,13 +60,36 @@ def unique(it: ta.Iterable[T], *, identity: bool = False) -> list[T]:
|
|
|
54
60
|
return ret
|
|
55
61
|
|
|
56
62
|
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
for k, v in
|
|
60
|
-
if k in
|
|
63
|
+
def unique_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, V]:
|
|
64
|
+
d: ta.MutableMapping[K, V] = IdentityKeyDict() if identity else {}
|
|
65
|
+
for k, v in kvs:
|
|
66
|
+
if k in d:
|
|
61
67
|
raise KeyError(k)
|
|
62
|
-
|
|
63
|
-
return
|
|
68
|
+
d[k] = v
|
|
69
|
+
return d
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def unique_map_by(fn: ta.Callable[[V], K], vs: ta.Iterable[V], *, identity: bool = False) -> ta.MutableMapping[K, V]:
|
|
73
|
+
return unique_map(((fn(v), v) for v in vs), identity=identity)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def multi_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]:
|
|
77
|
+
d: ta.MutableMapping[K, list[V]] = IdentityKeyDict() if identity else {}
|
|
78
|
+
l: list[V]
|
|
79
|
+
for k, v in kvs:
|
|
80
|
+
try:
|
|
81
|
+
l = d[k]
|
|
82
|
+
except KeyError:
|
|
83
|
+
l = d[k] = []
|
|
84
|
+
l.append(v)
|
|
85
|
+
return d
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def multi_map_by(fn: ta.Callable[[V], K], vs: ta.Iterable[V], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]: # noqa
|
|
89
|
+
return multi_map(((fn(v), v) for v in vs), identity=identity)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
##
|
|
64
93
|
|
|
65
94
|
|
|
66
95
|
def all_equal(it: ta.Iterable[T]) -> bool:
|
|
@@ -81,6 +110,9 @@ def all_not_equal(it: ta.Iterable[T]) -> bool:
|
|
|
81
110
|
return True
|
|
82
111
|
|
|
83
112
|
|
|
113
|
+
##
|
|
114
|
+
|
|
115
|
+
|
|
84
116
|
def key_cmp(fn: ta.Callable[[K, K], int]) -> ta.Callable[[tuple[K, V], tuple[K, V]], int]:
|
|
85
117
|
return lambda t0, t1: fn(t0[0], t1[0])
|
|
86
118
|
|
omlish/configs/__init__.py
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
import weakref
|
|
3
|
+
|
|
4
|
+
from .. import check
|
|
5
|
+
from .. import dataclasses as dc
|
|
6
|
+
from .. import lang
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Config(
|
|
10
|
+
dc.Data,
|
|
11
|
+
lang.Abstract,
|
|
12
|
+
frozen=True,
|
|
13
|
+
reorder=True,
|
|
14
|
+
confer=frozenset([
|
|
15
|
+
'frozen',
|
|
16
|
+
'reorder',
|
|
17
|
+
'confer',
|
|
18
|
+
]),
|
|
19
|
+
):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ConfigT = ta.TypeVar('ConfigT', bound='Config')
|
|
24
|
+
|
|
25
|
+
_CONFIG_CLS_MAP: ta.MutableMapping[type[Config], type['Configurable']] = weakref.WeakValueDictionary()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Configurable(ta.Generic[ConfigT], lang.Abstract):
|
|
29
|
+
|
|
30
|
+
# FIXME: https://github.com/python/mypy/issues/5144
|
|
31
|
+
Config: ta.ClassVar[type[ConfigT]] # type: ignore # noqa
|
|
32
|
+
|
|
33
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
|
34
|
+
super().__init_subclass__(**kwargs)
|
|
35
|
+
|
|
36
|
+
cfg_cls = check.issubclass(cls.__dict__['Config'], Config)
|
|
37
|
+
check.not_in(cfg_cls, _CONFIG_CLS_MAP)
|
|
38
|
+
_CONFIG_CLS_MAP[cfg_cls] = cls
|
|
39
|
+
|
|
40
|
+
def __init__(self, config: ConfigT) -> None:
|
|
41
|
+
super().__init__()
|
|
42
|
+
|
|
43
|
+
self._config: ConfigT = check.isinstance(config, self.Config)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_impl(cfg: type[Config] | Config) -> type[Configurable]:
|
|
47
|
+
if isinstance(cfg, type):
|
|
48
|
+
cfg_cls = check.issubclass(cfg, Config) # noqa
|
|
49
|
+
elif isinstance(cfg, Config):
|
|
50
|
+
cfg_cls = type(cfg)
|
|
51
|
+
else:
|
|
52
|
+
raise TypeError(cfg)
|
|
53
|
+
return _CONFIG_CLS_MAP[cfg_cls]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- reflecty generalized rewriter, obviously..
|
|
4
|
+
"""
|
|
5
|
+
import collections.abc
|
|
6
|
+
import typing as ta
|
|
7
|
+
|
|
8
|
+
from .. import dataclasses as dc
|
|
9
|
+
from .. import lang
|
|
10
|
+
from ..text import glyphsplit
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
T = ta.TypeVar('T')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class InterpolateStringsMetadata(lang.Marker):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dc.field_modifier
|
|
21
|
+
def secret_or_key_field(f: dc.Field) -> dc.Field:
|
|
22
|
+
return dc.update_field_metadata(f, {
|
|
23
|
+
InterpolateStringsMetadata: True,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dc.field_modifier
|
|
28
|
+
def interpolate_field(f: dc.Field) -> dc.Field:
|
|
29
|
+
return dc.update_field_metadata(f, {InterpolateStringsMetadata: True})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class StringRewriter:
|
|
33
|
+
def __init__(self, fn: ta.Callable[[str], str]) -> None:
|
|
34
|
+
super().__init__()
|
|
35
|
+
self._fn = fn
|
|
36
|
+
|
|
37
|
+
def __call__(self, v: T, *, _soft: bool = False) -> T:
|
|
38
|
+
if v is None:
|
|
39
|
+
return None # type: ignore
|
|
40
|
+
|
|
41
|
+
if dc.is_dataclass(v):
|
|
42
|
+
kw = {}
|
|
43
|
+
for f in dc.fields(v):
|
|
44
|
+
fv = getattr(v, f.name)
|
|
45
|
+
nfv = self(fv, _soft=not f.metadata.get(InterpolateStringsMetadata))
|
|
46
|
+
if fv is not nfv:
|
|
47
|
+
kw[f.name] = nfv
|
|
48
|
+
if not kw:
|
|
49
|
+
return v # type: ignore
|
|
50
|
+
return dc.replace(v, **kw)
|
|
51
|
+
|
|
52
|
+
if isinstance(v, str):
|
|
53
|
+
if not _soft:
|
|
54
|
+
v = self._fn(v) # type: ignore
|
|
55
|
+
return v # type: ignore
|
|
56
|
+
|
|
57
|
+
if isinstance(v, lang.BUILTIN_SCALAR_ITERABLE_TYPES):
|
|
58
|
+
return v # type: ignore
|
|
59
|
+
|
|
60
|
+
if isinstance(v, collections.abc.Mapping):
|
|
61
|
+
nm = []
|
|
62
|
+
b = False
|
|
63
|
+
for mk, mv in v.items():
|
|
64
|
+
nk, nv = self(mk, _soft=_soft), self(mv, _soft=_soft)
|
|
65
|
+
nm.append((nk, nv))
|
|
66
|
+
b |= nk is not mk or nv is not mv
|
|
67
|
+
if not b:
|
|
68
|
+
return v # type: ignore
|
|
69
|
+
return v.__class__(nm) # type: ignore
|
|
70
|
+
|
|
71
|
+
if isinstance(v, (collections.abc.Sequence, collections.abc.Set)):
|
|
72
|
+
nl = []
|
|
73
|
+
b = False
|
|
74
|
+
for le in v:
|
|
75
|
+
ne = self(le, _soft=_soft)
|
|
76
|
+
nl.append(ne)
|
|
77
|
+
b |= ne is not le
|
|
78
|
+
if not b:
|
|
79
|
+
return v # type: ignore
|
|
80
|
+
return v.__class__(nl) # type: ignore
|
|
81
|
+
|
|
82
|
+
return v
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def interpolate_strings(v: T, rpl: ta.Mapping[str, str]) -> T:
|
|
86
|
+
def fn(v):
|
|
87
|
+
if not v:
|
|
88
|
+
return v
|
|
89
|
+
sps = glyphsplit.split_braces(v)
|
|
90
|
+
if len(sps) == 1 and isinstance(sps[0], str):
|
|
91
|
+
return sps[0]
|
|
92
|
+
return ''.join(rpl[p.s] if isinstance(p, glyphsplit.GlyphMatch) else p for p in sps)
|
|
93
|
+
|
|
94
|
+
return StringRewriter(fn)(v)
|
omlish/dataclasses/__init__.py
CHANGED
|
@@ -55,6 +55,7 @@ globals()['make_dataclass'] = xmake_dataclass
|
|
|
55
55
|
|
|
56
56
|
from .impl.exceptions import ( # noqa
|
|
57
57
|
CheckError,
|
|
58
|
+
FieldCheckError,
|
|
58
59
|
)
|
|
59
60
|
|
|
60
61
|
from .impl.metaclass import ( # noqa
|
|
@@ -81,3 +82,11 @@ from .impl.reflect import ( # noqa
|
|
|
81
82
|
ClassInfo,
|
|
82
83
|
reflect,
|
|
83
84
|
)
|
|
85
|
+
|
|
86
|
+
from .utils import ( # noqa
|
|
87
|
+
chain_metadata,
|
|
88
|
+
field_modifier,
|
|
89
|
+
maybe_post_init,
|
|
90
|
+
opt_repr,
|
|
91
|
+
update_field_metadata,
|
|
92
|
+
)
|
omlish/dataclasses/impl/api.py
CHANGED
|
@@ -34,7 +34,7 @@ def field( # noqa
|
|
|
34
34
|
check: ta.Callable[[ta.Any], bool] | None = None,
|
|
35
35
|
check_type: bool | None = None,
|
|
36
36
|
override: bool = False,
|
|
37
|
-
repr_fn: ta.Callable[[ta.Any], str | None] | None = None
|
|
37
|
+
repr_fn: ta.Callable[[ta.Any], str | None] | None = None,
|
|
38
38
|
): # -> dc.Field
|
|
39
39
|
if default is not MISSING and default_factory is not MISSING:
|
|
40
40
|
raise ValueError('cannot specify both default and default_factory')
|
omlish/dataclasses/impl/as_.py
CHANGED
|
@@ -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)
|
|
@@ -9,6 +9,7 @@ from .internals import is_classvar
|
|
|
9
9
|
from .internals import is_initvar
|
|
10
10
|
from .params import get_field_extras
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
if ta.TYPE_CHECKING:
|
|
13
14
|
from . import api
|
|
14
15
|
else:
|
|
@@ -97,51 +98,50 @@ def field_init(
|
|
|
97
98
|
|
|
98
99
|
lines = []
|
|
99
100
|
|
|
100
|
-
if fx.coerce is not None:
|
|
101
|
-
cn = f'__dataclass_coerce__{f.name}__'
|
|
102
|
-
locals[cn] = fx.coerce
|
|
103
|
-
lines.append(f'{f.name} = {cn}({f.name})')
|
|
104
|
-
|
|
105
|
-
if fx.check is not None:
|
|
106
|
-
cn = f'__dataclass_check__{f.name}__'
|
|
107
|
-
locals[cn] = fx.check
|
|
108
|
-
lines.append(f'if not {cn}({f.name}): raise __dataclass_CheckException__')
|
|
109
|
-
|
|
110
|
-
if fx.check_type:
|
|
111
|
-
cn = f'__dataclass_check_type__{f.name}__'
|
|
112
|
-
locals[cn] = f.type
|
|
113
|
-
lines.append(
|
|
114
|
-
f'if not __dataclass_builtins_isinstance__({f.name}, {cn}): '
|
|
115
|
-
f'raise __dataclass_builtins_TypeError__({f.name}, {cn})',
|
|
116
|
-
)
|
|
117
|
-
|
|
118
101
|
value: str | None = None
|
|
119
102
|
if f.default_factory is not MISSING:
|
|
120
103
|
if f.init:
|
|
121
104
|
locals[default_name] = f.default_factory
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__ '
|
|
125
|
-
f'else {f.name}'
|
|
126
|
-
)
|
|
105
|
+
lines.append(f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__: {f.name} = {default_name}()')
|
|
106
|
+
value = f.name
|
|
127
107
|
else:
|
|
128
108
|
locals[default_name] = f.default_factory
|
|
129
|
-
|
|
109
|
+
lines.append(f'{f.name} = {default_name}()')
|
|
110
|
+
value = f.name
|
|
130
111
|
|
|
131
112
|
elif f.init:
|
|
132
113
|
if f.default is MISSING:
|
|
133
114
|
value = f.name
|
|
134
115
|
elif f.default is not MISSING:
|
|
135
|
-
locals[default_name] = f.default
|
|
116
|
+
locals[default_name] = f.default # Not referenced her, just useful / consistent to have in function scope
|
|
136
117
|
value = f.name
|
|
137
118
|
|
|
138
119
|
elif slots and f.default is not MISSING:
|
|
139
120
|
locals[default_name] = f.default
|
|
121
|
+
lines.append(f'{f.name} = {default_name}')
|
|
140
122
|
value = default_name
|
|
141
123
|
|
|
142
124
|
else:
|
|
143
125
|
pass
|
|
144
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
|
+
|
|
145
145
|
if value is not None and field_type(f) is not FieldType.INIT:
|
|
146
146
|
lines.append(field_assign(frozen, f.name, value, self_name, fx.override)) # noqa
|
|
147
147
|
|
omlish/dataclasses/impl/init.py
CHANGED
|
@@ -4,12 +4,13 @@ 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
|
|
10
|
-
from .internals import FieldType
|
|
11
11
|
from .internals import HAS_DEFAULT_FACTORY
|
|
12
12
|
from .internals import POST_INIT_NAME
|
|
13
|
+
from .internals import FieldType
|
|
13
14
|
from .metadata import Check
|
|
14
15
|
from .metadata import Init
|
|
15
16
|
from .processing import Processor
|
|
@@ -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
|
|
@@ -24,6 +25,7 @@ from .simple import MatchArgsProcessor
|
|
|
24
25
|
from .simple import OverridesProcessor
|
|
25
26
|
from .slots import add_slots
|
|
26
27
|
|
|
28
|
+
|
|
27
29
|
if ta.TYPE_CHECKING:
|
|
28
30
|
from . import metaclass
|
|
29
31
|
else:
|
|
@@ -136,6 +138,7 @@ class MainProcessor:
|
|
|
136
138
|
DocProcessor,
|
|
137
139
|
MatchArgsProcessor,
|
|
138
140
|
ReplaceProcessor,
|
|
141
|
+
CopyProcessor,
|
|
139
142
|
]:
|
|
140
143
|
pcls(self._info).process()
|
|
141
144
|
|
|
@@ -39,7 +39,12 @@ def confer_kwargs(
|
|
|
39
39
|
for ck in bmp.confer:
|
|
40
40
|
if ck in kwargs:
|
|
41
41
|
continue
|
|
42
|
-
if ck in (
|
|
42
|
+
if ck in (
|
|
43
|
+
'frozen',
|
|
44
|
+
'generic_init',
|
|
45
|
+
'kw_only',
|
|
46
|
+
'reorder',
|
|
47
|
+
):
|
|
43
48
|
confer_kwarg(out, ck, get_params(base).frozen)
|
|
44
49
|
elif ck == 'confer':
|
|
45
50
|
confer_kwarg(out, 'confer', bmp.confer)
|
omlish/dataclasses/impl/order.py
CHANGED
|
@@ -28,9 +28,22 @@ from .params import get_params_extras
|
|
|
28
28
|
from .utils import Namespace
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
try:
|
|
32
|
+
import annotationlib # noqa
|
|
33
|
+
except ImportError:
|
|
34
|
+
annotationlib = None
|
|
35
|
+
|
|
36
|
+
|
|
31
37
|
MISSING = dc.MISSING
|
|
32
38
|
|
|
33
39
|
|
|
40
|
+
def _get_annotations(obj):
|
|
41
|
+
if annotationlib is not None:
|
|
42
|
+
return annotationlib.get_annotations(obj, format=annotationlib.Format.FORWARDREF) # noqa
|
|
43
|
+
else:
|
|
44
|
+
return inspect.get_annotations(obj)
|
|
45
|
+
|
|
46
|
+
|
|
34
47
|
class ClassInfo:
|
|
35
48
|
|
|
36
49
|
def __init__(self, cls: type, *, _constructing: bool = False) -> None:
|
|
@@ -54,7 +67,7 @@ class ClassInfo:
|
|
|
54
67
|
|
|
55
68
|
@cached.property
|
|
56
69
|
def cls_annotations(self) -> ta.Mapping[str, ta.Any]:
|
|
57
|
-
return
|
|
70
|
+
return _get_annotations(self._cls)
|
|
58
71
|
|
|
59
72
|
##
|
|
60
73
|
|
|
@@ -136,7 +149,7 @@ class ClassInfo:
|
|
|
136
149
|
|
|
137
150
|
@cached.property
|
|
138
151
|
def generic_mro_lookup(self) -> ta.Mapping[type, rfl.Type]:
|
|
139
|
-
return col.
|
|
152
|
+
return col.unique_map((check.not_none(rfl.get_concrete_type(g)), g) for g in self.generic_mro)
|
|
140
153
|
|
|
141
154
|
@cached.property
|
|
142
155
|
def generic_replaced_field_types(self) -> ta.Mapping[str, rfl.Type]:
|