omlish 0.0.0.dev1__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 +7 -0
- omlish/__init__.py +0 -0
- omlish/argparse.py +223 -0
- omlish/asyncs/__init__.py +17 -0
- omlish/asyncs/anyio.py +23 -0
- omlish/asyncs/asyncio.py +19 -0
- omlish/asyncs/asyncs.py +76 -0
- omlish/asyncs/futures.py +179 -0
- omlish/asyncs/trio.py +11 -0
- omlish/c3.py +173 -0
- omlish/cached.py +9 -0
- omlish/check.py +231 -0
- omlish/collections/__init__.py +63 -0
- omlish/collections/_abc.py +156 -0
- omlish/collections/_io_abc.py +78 -0
- omlish/collections/cache/__init__.py +11 -0
- omlish/collections/cache/descriptor.py +188 -0
- omlish/collections/cache/impl.py +485 -0
- omlish/collections/cache/types.py +37 -0
- omlish/collections/coerce.py +337 -0
- omlish/collections/frozen.py +148 -0
- omlish/collections/identity.py +106 -0
- omlish/collections/indexed.py +75 -0
- omlish/collections/mappings.py +127 -0
- omlish/collections/ordered.py +81 -0
- omlish/collections/persistent.py +36 -0
- omlish/collections/skiplist.py +193 -0
- omlish/collections/sorted.py +126 -0
- omlish/collections/treap.py +228 -0
- omlish/collections/treapmap.py +144 -0
- omlish/collections/unmodifiable.py +174 -0
- omlish/collections/utils.py +110 -0
- omlish/configs/__init__.py +0 -0
- omlish/configs/flattening.py +147 -0
- omlish/configs/props.py +64 -0
- omlish/dataclasses/__init__.py +83 -0
- omlish/dataclasses/impl/__init__.py +6 -0
- omlish/dataclasses/impl/api.py +260 -0
- omlish/dataclasses/impl/as_.py +76 -0
- omlish/dataclasses/impl/exceptions.py +2 -0
- omlish/dataclasses/impl/fields.py +148 -0
- omlish/dataclasses/impl/frozen.py +55 -0
- omlish/dataclasses/impl/hashing.py +85 -0
- omlish/dataclasses/impl/init.py +173 -0
- omlish/dataclasses/impl/internals.py +118 -0
- omlish/dataclasses/impl/main.py +150 -0
- omlish/dataclasses/impl/metaclass.py +126 -0
- omlish/dataclasses/impl/metadata.py +74 -0
- omlish/dataclasses/impl/order.py +47 -0
- omlish/dataclasses/impl/params.py +150 -0
- omlish/dataclasses/impl/processing.py +16 -0
- omlish/dataclasses/impl/reflect.py +173 -0
- omlish/dataclasses/impl/replace.py +40 -0
- omlish/dataclasses/impl/repr.py +34 -0
- omlish/dataclasses/impl/simple.py +92 -0
- omlish/dataclasses/impl/slots.py +80 -0
- omlish/dataclasses/impl/utils.py +167 -0
- omlish/defs.py +193 -0
- omlish/dispatch/__init__.py +3 -0
- omlish/dispatch/dispatch.py +137 -0
- omlish/dispatch/functions.py +52 -0
- omlish/dispatch/methods.py +162 -0
- omlish/docker.py +149 -0
- omlish/dynamic.py +220 -0
- omlish/graphs/__init__.py +0 -0
- omlish/graphs/dot/__init__.py +19 -0
- omlish/graphs/dot/items.py +162 -0
- omlish/graphs/dot/rendering.py +147 -0
- omlish/graphs/dot/utils.py +30 -0
- omlish/graphs/trees.py +249 -0
- omlish/http/__init__.py +0 -0
- omlish/http/consts.py +20 -0
- omlish/http/wsgi.py +34 -0
- omlish/inject/__init__.py +85 -0
- omlish/inject/binder.py +12 -0
- omlish/inject/bindings.py +49 -0
- omlish/inject/eagers.py +21 -0
- omlish/inject/elements.py +43 -0
- omlish/inject/exceptions.py +49 -0
- omlish/inject/impl/__init__.py +0 -0
- omlish/inject/impl/bindings.py +19 -0
- omlish/inject/impl/elements.py +154 -0
- omlish/inject/impl/injector.py +182 -0
- omlish/inject/impl/inspect.py +98 -0
- omlish/inject/impl/private.py +109 -0
- omlish/inject/impl/providers.py +132 -0
- omlish/inject/impl/scopes.py +198 -0
- omlish/inject/injector.py +40 -0
- omlish/inject/inspect.py +14 -0
- omlish/inject/keys.py +43 -0
- omlish/inject/managed.py +24 -0
- omlish/inject/overrides.py +18 -0
- omlish/inject/private.py +29 -0
- omlish/inject/providers.py +111 -0
- omlish/inject/proxy.py +48 -0
- omlish/inject/scopes.py +84 -0
- omlish/inject/types.py +21 -0
- omlish/iterators.py +184 -0
- omlish/json.py +194 -0
- omlish/lang/__init__.py +112 -0
- omlish/lang/cached.py +267 -0
- omlish/lang/classes/__init__.py +24 -0
- omlish/lang/classes/abstract.py +74 -0
- omlish/lang/classes/restrict.py +137 -0
- omlish/lang/classes/simple.py +120 -0
- omlish/lang/classes/test/__init__.py +0 -0
- omlish/lang/classes/test/test_abstract.py +89 -0
- omlish/lang/classes/test/test_restrict.py +71 -0
- omlish/lang/classes/test/test_simple.py +58 -0
- omlish/lang/classes/test/test_virtual.py +72 -0
- omlish/lang/classes/virtual.py +130 -0
- omlish/lang/clsdct.py +67 -0
- omlish/lang/cmp.py +63 -0
- omlish/lang/contextmanagers.py +249 -0
- omlish/lang/datetimes.py +67 -0
- omlish/lang/descriptors.py +52 -0
- omlish/lang/functions.py +126 -0
- omlish/lang/imports.py +153 -0
- omlish/lang/iterables.py +54 -0
- omlish/lang/maybes.py +136 -0
- omlish/lang/objects.py +103 -0
- omlish/lang/resolving.py +50 -0
- omlish/lang/strings.py +128 -0
- omlish/lang/typing.py +92 -0
- omlish/libc.py +532 -0
- omlish/logs/__init__.py +9 -0
- omlish/logs/_abc.py +247 -0
- omlish/logs/configs.py +62 -0
- omlish/logs/filters.py +9 -0
- omlish/logs/formatters.py +67 -0
- omlish/logs/utils.py +20 -0
- omlish/marshal/__init__.py +52 -0
- omlish/marshal/any.py +25 -0
- omlish/marshal/base.py +201 -0
- omlish/marshal/base64.py +25 -0
- omlish/marshal/dataclasses.py +115 -0
- omlish/marshal/datetimes.py +90 -0
- omlish/marshal/enums.py +43 -0
- omlish/marshal/exceptions.py +7 -0
- omlish/marshal/factories.py +129 -0
- omlish/marshal/global_.py +33 -0
- omlish/marshal/iterables.py +57 -0
- omlish/marshal/mappings.py +66 -0
- omlish/marshal/naming.py +17 -0
- omlish/marshal/objects.py +106 -0
- omlish/marshal/optionals.py +49 -0
- omlish/marshal/polymorphism.py +147 -0
- omlish/marshal/primitives.py +43 -0
- omlish/marshal/registries.py +57 -0
- omlish/marshal/standard.py +80 -0
- omlish/marshal/utils.py +23 -0
- omlish/marshal/uuids.py +29 -0
- omlish/marshal/values.py +30 -0
- omlish/math.py +184 -0
- omlish/os.py +32 -0
- omlish/reflect.py +359 -0
- omlish/replserver/__init__.py +5 -0
- omlish/replserver/__main__.py +4 -0
- omlish/replserver/console.py +247 -0
- omlish/replserver/server.py +146 -0
- omlish/runmodule.py +28 -0
- omlish/stats.py +342 -0
- omlish/term.py +222 -0
- omlish/testing/__init__.py +7 -0
- omlish/testing/pydevd.py +225 -0
- omlish/testing/pytest/__init__.py +8 -0
- omlish/testing/pytest/helpers.py +35 -0
- omlish/testing/pytest/inject/__init__.py +1 -0
- omlish/testing/pytest/inject/harness.py +159 -0
- omlish/testing/pytest/plugins/__init__.py +20 -0
- omlish/testing/pytest/plugins/_registry.py +6 -0
- omlish/testing/pytest/plugins/logging.py +13 -0
- omlish/testing/pytest/plugins/pycharm.py +54 -0
- omlish/testing/pytest/plugins/repeat.py +19 -0
- omlish/testing/pytest/plugins/skips.py +32 -0
- omlish/testing/pytest/plugins/spacing.py +19 -0
- omlish/testing/pytest/plugins/switches.py +70 -0
- omlish/testing/testing.py +102 -0
- omlish/text/__init__.py +0 -0
- omlish/text/delimit.py +171 -0
- omlish/text/indent.py +50 -0
- omlish/text/parts.py +265 -0
- omlish-0.0.0.dev1.dist-info/LICENSE +21 -0
- omlish-0.0.0.dev1.dist-info/METADATA +17 -0
- omlish-0.0.0.dev1.dist-info/RECORD +187 -0
- omlish-0.0.0.dev1.dist-info/WHEEL +5 -0
- omlish-0.0.0.dev1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing as ta
|
|
3
|
+
|
|
4
|
+
from .. import check
|
|
5
|
+
from .. import lang
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
K = ta.TypeVar('K')
|
|
9
|
+
V = ta.TypeVar('V')
|
|
10
|
+
StrMap = ta.Mapping[str, ta.Any]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _MISSING(lang.Marker):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Flattening:
|
|
18
|
+
|
|
19
|
+
DEFAULT_DELIMITER = '.'
|
|
20
|
+
DEFAULT_INDEX_OPEN = '('
|
|
21
|
+
DEFAULT_INDEX_CLOSE = ')'
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
*,
|
|
26
|
+
delimiter=DEFAULT_DELIMITER,
|
|
27
|
+
index_open=DEFAULT_INDEX_OPEN,
|
|
28
|
+
index_close=DEFAULT_INDEX_CLOSE,
|
|
29
|
+
) -> None:
|
|
30
|
+
super().__init__()
|
|
31
|
+
|
|
32
|
+
self._delimiter = check.not_empty(delimiter)
|
|
33
|
+
self._index_open = check.not_empty(index_open)
|
|
34
|
+
self._index_close = check.not_empty(index_close)
|
|
35
|
+
|
|
36
|
+
def flatten(self, unflattened: StrMap) -> StrMap:
|
|
37
|
+
def rec(prefix: ta.List[str], value: ta.Any) -> None:
|
|
38
|
+
if isinstance(value, dict):
|
|
39
|
+
for k, v in value.items():
|
|
40
|
+
rec(prefix + [k], v)
|
|
41
|
+
elif isinstance(value, list):
|
|
42
|
+
check.not_empty(prefix)
|
|
43
|
+
for i, v in enumerate(value):
|
|
44
|
+
rec(prefix[:-1] + [f'{prefix[-1]}{self._index_open}{i}{self._index_close}'], v)
|
|
45
|
+
else:
|
|
46
|
+
k = self._delimiter.join(prefix)
|
|
47
|
+
if k in ret:
|
|
48
|
+
raise KeyError(k)
|
|
49
|
+
ret[k] = value
|
|
50
|
+
|
|
51
|
+
ret: ta.Dict[str, ta.Any] = {}
|
|
52
|
+
rec([], unflattened)
|
|
53
|
+
return ret
|
|
54
|
+
|
|
55
|
+
class UnflattenNode(lang.Abstract, ta.Generic[K]):
|
|
56
|
+
|
|
57
|
+
@abc.abstractmethod
|
|
58
|
+
def get(self, key: K) -> ta.Any:
|
|
59
|
+
raise NotImplementedError
|
|
60
|
+
|
|
61
|
+
@abc.abstractmethod
|
|
62
|
+
def put(self, key: K, value: ta.Any) -> None:
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
|
|
65
|
+
def setdefault(self, key: K, supplier: ta.Callable[[], V]) -> V:
|
|
66
|
+
ret = self.get(key)
|
|
67
|
+
if ret is _MISSING:
|
|
68
|
+
ret = supplier()
|
|
69
|
+
self.put(key, ret)
|
|
70
|
+
return ret
|
|
71
|
+
|
|
72
|
+
@abc.abstractmethod
|
|
73
|
+
def build(self) -> ta.Any:
|
|
74
|
+
raise NotImplementedError
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def maybe_build(value: ta.Any) -> ta.Any:
|
|
78
|
+
check.not_none(value)
|
|
79
|
+
return value.build() if isinstance(value, Flattening.UnflattenNode) else value
|
|
80
|
+
|
|
81
|
+
class UnflattenDict(UnflattenNode[str]):
|
|
82
|
+
|
|
83
|
+
def __init__(self) -> None:
|
|
84
|
+
super().__init__()
|
|
85
|
+
|
|
86
|
+
self._dict: ta.Dict[str, ta.Any] = {}
|
|
87
|
+
|
|
88
|
+
def get(self, key: str) -> ta.Any:
|
|
89
|
+
return self._dict.get(key, _MISSING)
|
|
90
|
+
|
|
91
|
+
def put(self, key: str, value: ta.Any) -> None:
|
|
92
|
+
check.arg(key not in self._dict)
|
|
93
|
+
self._dict[key] = value
|
|
94
|
+
|
|
95
|
+
def build(self) -> ta.Any:
|
|
96
|
+
return {k: Flattening.UnflattenNode.maybe_build(v) for k, v in self._dict.items()}
|
|
97
|
+
|
|
98
|
+
class UnflattenList(UnflattenNode[int]):
|
|
99
|
+
|
|
100
|
+
def __init__(self) -> None:
|
|
101
|
+
super().__init__()
|
|
102
|
+
|
|
103
|
+
self._list: ta.List[ta.Any] = []
|
|
104
|
+
|
|
105
|
+
def get(self, key: int) -> ta.Any:
|
|
106
|
+
check.arg(key >= 0)
|
|
107
|
+
return self._list[key] if key < len(self._list) else _MISSING
|
|
108
|
+
|
|
109
|
+
def put(self, key: int, value: ta.Any) -> None:
|
|
110
|
+
check.arg(key >= 0)
|
|
111
|
+
if key >= len(self._list):
|
|
112
|
+
self._list.extend([_MISSING] * (key - len(self._list) + 1))
|
|
113
|
+
check.arg(self._list[key] is _MISSING)
|
|
114
|
+
self._list[key] = value
|
|
115
|
+
|
|
116
|
+
def build(self) -> ta.Any:
|
|
117
|
+
return [Flattening.UnflattenNode.maybe_build(e) for e in self._list]
|
|
118
|
+
|
|
119
|
+
def unflatten(self, flattened: StrMap) -> StrMap:
|
|
120
|
+
root = Flattening.UnflattenDict()
|
|
121
|
+
|
|
122
|
+
def split_keys(fkey: str) -> ta.Iterable[ta.Union[str, int]]:
|
|
123
|
+
for part in fkey.split(self._delimiter):
|
|
124
|
+
if self._index_open in part:
|
|
125
|
+
check.state(part.endswith(self._index_close))
|
|
126
|
+
pos = part.index(self._index_open)
|
|
127
|
+
yield part[:pos]
|
|
128
|
+
for p in part[pos + len(self._index_open):-len(self._index_close)] \
|
|
129
|
+
.split(self._index_close + self._index_open):
|
|
130
|
+
yield int(p)
|
|
131
|
+
else:
|
|
132
|
+
check.state(')' not in part)
|
|
133
|
+
yield part
|
|
134
|
+
|
|
135
|
+
for fk, v in flattened.items():
|
|
136
|
+
node: Flattening.UnflattenNode = root
|
|
137
|
+
fks = list(split_keys(fk))
|
|
138
|
+
for key, nkey in zip(fks, fks[1:]):
|
|
139
|
+
if isinstance(nkey, str):
|
|
140
|
+
node = node.setdefault(key, Flattening.UnflattenDict)
|
|
141
|
+
elif isinstance(nkey, int):
|
|
142
|
+
node = node.setdefault(key, Flattening.UnflattenList)
|
|
143
|
+
else:
|
|
144
|
+
raise TypeError(key)
|
|
145
|
+
node.put(fks[-1], v)
|
|
146
|
+
|
|
147
|
+
return root.build()
|
omlish/configs/props.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- \\uXXXX
|
|
4
|
+
"""
|
|
5
|
+
import re
|
|
6
|
+
import typing as ta
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_NORMALIZE_PATTERN = re.compile(r'\\([:=\s])')
|
|
10
|
+
_ESCAPE_PATTERN = re.compile(r'([=:\s])')
|
|
11
|
+
_SEPARATOR_PATTERN = re.compile(r'(?<!\\)[=:]')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def normalize(atom: str) -> str:
|
|
15
|
+
return _NORMALIZE_PATTERN.sub(r'\1', atom.strip())
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def escape(token: str) -> str:
|
|
19
|
+
return _ESCAPE_PATTERN.sub(r'\\\1', token)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_line(line: str) -> ta.Optional[ta.Tuple[str, str]]:
|
|
23
|
+
if line and not (line.startswith('#') or line.startswith('!')):
|
|
24
|
+
match = _SEPARATOR_PATTERN.search(line)
|
|
25
|
+
if match:
|
|
26
|
+
return normalize(line[:match.start()]), normalize(line[match.end():])
|
|
27
|
+
else:
|
|
28
|
+
space_sep = line.find(' ')
|
|
29
|
+
if space_sep == -1:
|
|
30
|
+
return normalize(line), ''
|
|
31
|
+
else:
|
|
32
|
+
return normalize(line[:space_sep]), normalize(line[space_sep:])
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def coalesce_lines(lines: ta.Iterable[str]) -> ta.Generator[str, None, None]:
|
|
37
|
+
line_iter = iter(lines)
|
|
38
|
+
try:
|
|
39
|
+
buffer = ''
|
|
40
|
+
while True:
|
|
41
|
+
line = next(line_iter)
|
|
42
|
+
if line.strip().endswith('\\'):
|
|
43
|
+
buffer += line.strip()[:-1]
|
|
44
|
+
else:
|
|
45
|
+
if buffer:
|
|
46
|
+
buffer += line.rstrip()
|
|
47
|
+
else:
|
|
48
|
+
buffer = line.strip()
|
|
49
|
+
try:
|
|
50
|
+
yield buffer
|
|
51
|
+
finally:
|
|
52
|
+
buffer = ''
|
|
53
|
+
except StopIteration:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def parse_lines(lines: ta.Iterable[str]) -> ta.Dict[str, str]:
|
|
58
|
+
props = {}
|
|
59
|
+
for line in coalesce_lines(lines):
|
|
60
|
+
kv_pair = parse_line(line)
|
|
61
|
+
if kv_pair:
|
|
62
|
+
key, value = kv_pair
|
|
63
|
+
props[key] = value
|
|
64
|
+
return props
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from dataclasses import ( # noqa
|
|
2
|
+
FrozenInstanceError,
|
|
3
|
+
|
|
4
|
+
MISSING,
|
|
5
|
+
KW_ONLY,
|
|
6
|
+
|
|
7
|
+
InitVar,
|
|
8
|
+
Field,
|
|
9
|
+
|
|
10
|
+
field,
|
|
11
|
+
|
|
12
|
+
dataclass,
|
|
13
|
+
make_dataclass,
|
|
14
|
+
|
|
15
|
+
fields,
|
|
16
|
+
|
|
17
|
+
is_dataclass,
|
|
18
|
+
|
|
19
|
+
# asdict,
|
|
20
|
+
# astuple,
|
|
21
|
+
|
|
22
|
+
# replace,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from .impl.api import ( # noqa
|
|
26
|
+
field as xfield,
|
|
27
|
+
|
|
28
|
+
dataclass as xdataclass,
|
|
29
|
+
make_dataclass as xmake_dataclass,
|
|
30
|
+
|
|
31
|
+
extra_params,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from .impl.as_ import ( # noqa
|
|
35
|
+
asdict,
|
|
36
|
+
astuple,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
from .impl.replace import ( # noqa
|
|
40
|
+
replace,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
globals()['field'] = xfield
|
|
48
|
+
|
|
49
|
+
globals()['dataclass'] = xdataclass
|
|
50
|
+
globals()['make_dataclass'] = xmake_dataclass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
from .impl.exceptions import ( # noqa
|
|
57
|
+
CheckException,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
from .impl.metaclass import ( # noqa
|
|
61
|
+
DataMeta,
|
|
62
|
+
Data,
|
|
63
|
+
Frozen,
|
|
64
|
+
Box,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
from .impl.metadata import ( # noqa
|
|
68
|
+
get_merged_metadata,
|
|
69
|
+
|
|
70
|
+
UserMetadata,
|
|
71
|
+
metadata,
|
|
72
|
+
|
|
73
|
+
Check,
|
|
74
|
+
check,
|
|
75
|
+
|
|
76
|
+
Init,
|
|
77
|
+
init,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
from .impl.reflect import ( # noqa
|
|
81
|
+
ClassInfo,
|
|
82
|
+
reflect,
|
|
83
|
+
)
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import collections.abc
|
|
2
|
+
import dataclasses as dc
|
|
3
|
+
import keyword
|
|
4
|
+
import sys
|
|
5
|
+
import types
|
|
6
|
+
import typing as ta
|
|
7
|
+
|
|
8
|
+
from ... import check as check_
|
|
9
|
+
from .internals import PARAMS_ATTR
|
|
10
|
+
from .internals import Params
|
|
11
|
+
from .main import process_class
|
|
12
|
+
from .metadata import METADATA_ATTR
|
|
13
|
+
from .metadata import Metadata
|
|
14
|
+
from .params import FieldExtras
|
|
15
|
+
from .params import Params12
|
|
16
|
+
from .params import ParamsExtras
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
MISSING = dc.MISSING
|
|
20
|
+
|
|
21
|
+
IS_12 = sys.version_info[1] >= 12
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def field(
|
|
25
|
+
default=MISSING,
|
|
26
|
+
*,
|
|
27
|
+
default_factory=MISSING,
|
|
28
|
+
init=True,
|
|
29
|
+
repr=True,
|
|
30
|
+
hash=None,
|
|
31
|
+
compare=True,
|
|
32
|
+
metadata=None,
|
|
33
|
+
kw_only=MISSING,
|
|
34
|
+
|
|
35
|
+
coerce: ta.Optional[ta.Callable[[ta.Any], ta.Any]] = None,
|
|
36
|
+
check: ta.Optional[ta.Callable[[ta.Any], bool]] = None,
|
|
37
|
+
check_type: ta.Optional[bool] = None,
|
|
38
|
+
override: bool = False,
|
|
39
|
+
): # -> dc.Field
|
|
40
|
+
if default is not MISSING and default_factory is not MISSING:
|
|
41
|
+
raise ValueError('cannot specify both default and default_factory')
|
|
42
|
+
|
|
43
|
+
fx = FieldExtras(
|
|
44
|
+
coerce=coerce,
|
|
45
|
+
check=check,
|
|
46
|
+
check_type=check_type,
|
|
47
|
+
override=override,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
md: ta.Mapping = {FieldExtras: fx}
|
|
51
|
+
if metadata is not None:
|
|
52
|
+
md = collections.ChainMap(md, check_.isinstance(metadata, collections.abc.Mapping)) # type: ignore
|
|
53
|
+
|
|
54
|
+
return dc.Field(
|
|
55
|
+
default,
|
|
56
|
+
default_factory, # noqa
|
|
57
|
+
init,
|
|
58
|
+
repr,
|
|
59
|
+
hash,
|
|
60
|
+
compare,
|
|
61
|
+
types.MappingProxyType(md),
|
|
62
|
+
kw_only, # noqa
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _strip_missing_values(d):
|
|
67
|
+
return {k: v for k, v in d.items() if v is not MISSING}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def dataclass(
|
|
71
|
+
cls=None,
|
|
72
|
+
/,
|
|
73
|
+
*,
|
|
74
|
+
init=True,
|
|
75
|
+
repr=True,
|
|
76
|
+
eq=True,
|
|
77
|
+
order=False,
|
|
78
|
+
unsafe_hash=False,
|
|
79
|
+
frozen=False,
|
|
80
|
+
match_args=True,
|
|
81
|
+
kw_only=False,
|
|
82
|
+
slots=False,
|
|
83
|
+
weakref_slot=False,
|
|
84
|
+
|
|
85
|
+
metadata=None,
|
|
86
|
+
|
|
87
|
+
reorder=MISSING,
|
|
88
|
+
cache_hash=MISSING,
|
|
89
|
+
generic_init=MISSING,
|
|
90
|
+
):
|
|
91
|
+
def wrap(cls):
|
|
92
|
+
pkw = dict(
|
|
93
|
+
init=init,
|
|
94
|
+
repr=repr,
|
|
95
|
+
eq=eq,
|
|
96
|
+
order=order,
|
|
97
|
+
unsafe_hash=unsafe_hash,
|
|
98
|
+
frozen=frozen,
|
|
99
|
+
)
|
|
100
|
+
p12kw = dict(
|
|
101
|
+
match_args=match_args,
|
|
102
|
+
kw_only=kw_only,
|
|
103
|
+
slots=slots,
|
|
104
|
+
weakref_slot=weakref_slot,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
dmd = cls.__dict__.get(METADATA_ATTR)
|
|
108
|
+
|
|
109
|
+
epk = dict(dmd.get(_ExtraParamsKwargs, ()) if dmd is not None else ())
|
|
110
|
+
epk.update(_strip_missing_values(dict(
|
|
111
|
+
reorder=reorder,
|
|
112
|
+
cache_hash=cache_hash,
|
|
113
|
+
generic_init=generic_init,
|
|
114
|
+
)))
|
|
115
|
+
pex = ParamsExtras(**epk)
|
|
116
|
+
|
|
117
|
+
mmd: dict = {
|
|
118
|
+
ParamsExtras: pex,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if IS_12:
|
|
122
|
+
pkw.update(p12kw)
|
|
123
|
+
else:
|
|
124
|
+
mmd[Params12] = Params12(**p12kw)
|
|
125
|
+
|
|
126
|
+
md: Metadata = mmd
|
|
127
|
+
cmds = []
|
|
128
|
+
if metadata is not None:
|
|
129
|
+
cmds.append(check_.isinstance(metadata, collections.abc.Mapping))
|
|
130
|
+
if dmd is not None:
|
|
131
|
+
cmds.append(dmd)
|
|
132
|
+
if cmds:
|
|
133
|
+
md = collections.ChainMap(md, *cmds) # type: ignore
|
|
134
|
+
|
|
135
|
+
setattr(cls, PARAMS_ATTR, Params(**pkw))
|
|
136
|
+
setattr(cls, METADATA_ATTR, types.MappingProxyType(md))
|
|
137
|
+
|
|
138
|
+
return process_class(cls)
|
|
139
|
+
|
|
140
|
+
if cls is None:
|
|
141
|
+
return wrap
|
|
142
|
+
|
|
143
|
+
return wrap(cls)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def make_dataclass(
|
|
147
|
+
cls_name,
|
|
148
|
+
fields,
|
|
149
|
+
*,
|
|
150
|
+
bases=(),
|
|
151
|
+
namespace=None,
|
|
152
|
+
init=True,
|
|
153
|
+
repr=True,
|
|
154
|
+
eq=True,
|
|
155
|
+
order=False,
|
|
156
|
+
unsafe_hash=False,
|
|
157
|
+
frozen=False,
|
|
158
|
+
match_args=True,
|
|
159
|
+
kw_only=False,
|
|
160
|
+
slots=False,
|
|
161
|
+
weakref_slot=False,
|
|
162
|
+
module=None,
|
|
163
|
+
|
|
164
|
+
reorder=MISSING,
|
|
165
|
+
cache_hash=MISSING,
|
|
166
|
+
generic_init=MISSING,
|
|
167
|
+
):
|
|
168
|
+
if namespace is None:
|
|
169
|
+
namespace = {}
|
|
170
|
+
|
|
171
|
+
seen = set()
|
|
172
|
+
annotations = {}
|
|
173
|
+
defaults = {}
|
|
174
|
+
for item in fields:
|
|
175
|
+
if isinstance(item, str):
|
|
176
|
+
name = item
|
|
177
|
+
tp = 'typing.Any'
|
|
178
|
+
elif len(item) == 2:
|
|
179
|
+
name, tp, = item
|
|
180
|
+
elif len(item) == 3:
|
|
181
|
+
name, tp, spec = item
|
|
182
|
+
defaults[name] = spec
|
|
183
|
+
else:
|
|
184
|
+
raise TypeError(f'Invalid field: {item!r}')
|
|
185
|
+
if not isinstance(name, str) or not name.isidentifier():
|
|
186
|
+
raise TypeError(f'Field names must be valid identifiers: {name!r}')
|
|
187
|
+
if keyword.iskeyword(name):
|
|
188
|
+
raise TypeError(f'Field names must not be keywords: {name!r}')
|
|
189
|
+
if name in seen:
|
|
190
|
+
raise TypeError(f'Field name duplicated: {name!r}')
|
|
191
|
+
|
|
192
|
+
seen.add(name)
|
|
193
|
+
annotations[name] = tp
|
|
194
|
+
|
|
195
|
+
def exec_body_callback(ns):
|
|
196
|
+
ns.update(namespace)
|
|
197
|
+
ns.update(defaults)
|
|
198
|
+
ns['__annotations__'] = annotations
|
|
199
|
+
|
|
200
|
+
cls = types.new_class(cls_name, bases, {}, exec_body_callback)
|
|
201
|
+
|
|
202
|
+
if module is None:
|
|
203
|
+
try:
|
|
204
|
+
module = sys._getframemodulename(1) or '__main__' # type: ignore # noqa
|
|
205
|
+
except AttributeError:
|
|
206
|
+
try:
|
|
207
|
+
module = sys._getframe(1).f_globals.get('__name__', '__main__') # noqa
|
|
208
|
+
except (AttributeError, ValueError):
|
|
209
|
+
pass
|
|
210
|
+
if module is not None:
|
|
211
|
+
cls.__module__ = module
|
|
212
|
+
|
|
213
|
+
return dataclass(
|
|
214
|
+
cls,
|
|
215
|
+
init=init,
|
|
216
|
+
repr=repr,
|
|
217
|
+
eq=eq,
|
|
218
|
+
order=order,
|
|
219
|
+
unsafe_hash=unsafe_hash,
|
|
220
|
+
frozen=frozen,
|
|
221
|
+
match_args=match_args,
|
|
222
|
+
kw_only=kw_only,
|
|
223
|
+
slots=slots,
|
|
224
|
+
weakref_slot=weakref_slot,
|
|
225
|
+
|
|
226
|
+
reorder=reorder,
|
|
227
|
+
cache_hash=cache_hash,
|
|
228
|
+
generic_init=generic_init,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class _ExtraParamsKwargs:
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def extra_params(
|
|
237
|
+
*,
|
|
238
|
+
reorder=MISSING,
|
|
239
|
+
cache_hash=MISSING,
|
|
240
|
+
generic_init=MISSING,
|
|
241
|
+
):
|
|
242
|
+
def inner(cls):
|
|
243
|
+
if PARAMS_ATTR in cls.__dict__:
|
|
244
|
+
raise TypeError(cls)
|
|
245
|
+
try:
|
|
246
|
+
md = cls.__dict__[METADATA_ATTR]
|
|
247
|
+
except KeyError:
|
|
248
|
+
md = {}
|
|
249
|
+
setattr(cls, METADATA_ATTR, md)
|
|
250
|
+
if _ExtraParamsKwargs in md:
|
|
251
|
+
raise TypeError(cls)
|
|
252
|
+
|
|
253
|
+
md[_ExtraParamsKwargs] = _strip_missing_values(dict(
|
|
254
|
+
reorder=reorder,
|
|
255
|
+
cache_hash=cache_hash,
|
|
256
|
+
generic_init=generic_init,
|
|
257
|
+
))
|
|
258
|
+
|
|
259
|
+
return cls
|
|
260
|
+
return inner
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import dataclasses as dc
|
|
3
|
+
|
|
4
|
+
from .internals import is_dataclass_instance
|
|
5
|
+
from .internals import ATOMIC_TYPES
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def asdict(obj, *, dict_factory=dict):
|
|
9
|
+
if not is_dataclass_instance(obj): # noqa
|
|
10
|
+
raise TypeError("asdict() should be called on dataclass instances")
|
|
11
|
+
return _asdict_inner(obj, dict_factory)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _asdict_inner(obj, dict_factory):
|
|
15
|
+
if type(obj) in ATOMIC_TYPES:
|
|
16
|
+
return obj
|
|
17
|
+
|
|
18
|
+
elif is_dataclass_instance(obj):
|
|
19
|
+
l = []
|
|
20
|
+
for f in dc.fields(obj):
|
|
21
|
+
value = _asdict_inner(getattr(obj, f.name), dict_factory)
|
|
22
|
+
l.append((f.name, value))
|
|
23
|
+
return dict_factory(l)
|
|
24
|
+
|
|
25
|
+
elif isinstance(obj, tuple) and hasattr(obj, '_fields'):
|
|
26
|
+
return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj])
|
|
27
|
+
|
|
28
|
+
elif isinstance(obj, (list, tuple)):
|
|
29
|
+
return type(obj)(_asdict_inner(v, dict_factory) for v in obj)
|
|
30
|
+
|
|
31
|
+
elif isinstance(obj, dict):
|
|
32
|
+
if hasattr(type(obj), 'default_factory'):
|
|
33
|
+
d = type(obj)(getattr(obj, 'default_factory'))
|
|
34
|
+
for k, v in obj.items():
|
|
35
|
+
d[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory)
|
|
36
|
+
return d
|
|
37
|
+
return type(obj)((_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory)) for k, v in obj.items())
|
|
38
|
+
|
|
39
|
+
else:
|
|
40
|
+
return copy.deepcopy(obj)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def astuple(obj, *, tuple_factory=tuple):
|
|
44
|
+
if not is_dataclass_instance(obj):
|
|
45
|
+
raise TypeError("astuple() should be called on dataclass instances")
|
|
46
|
+
return _astuple_inner(obj, tuple_factory)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _astuple_inner(obj, tuple_factory):
|
|
50
|
+
if type(obj) in ATOMIC_TYPES:
|
|
51
|
+
return obj
|
|
52
|
+
|
|
53
|
+
elif is_dataclass_instance(obj):
|
|
54
|
+
l = []
|
|
55
|
+
for f in dc.fields(obj):
|
|
56
|
+
value = _astuple_inner(getattr(obj, f.name), tuple_factory)
|
|
57
|
+
l.append(value)
|
|
58
|
+
return tuple_factory(l)
|
|
59
|
+
|
|
60
|
+
elif isinstance(obj, tuple) and hasattr(obj, '_fields'):
|
|
61
|
+
return type(obj)(*[_astuple_inner(v, tuple_factory) for v in obj])
|
|
62
|
+
|
|
63
|
+
elif isinstance(obj, (list, tuple)):
|
|
64
|
+
return type(obj)(_astuple_inner(v, tuple_factory) for v in obj)
|
|
65
|
+
|
|
66
|
+
elif isinstance(obj, dict):
|
|
67
|
+
obj_type = type(obj)
|
|
68
|
+
if hasattr(obj_type, 'default_factory'):
|
|
69
|
+
d = obj_type(getattr(obj, 'default_factory'))
|
|
70
|
+
for k, v in obj.items():
|
|
71
|
+
d[_astuple_inner(k, tuple_factory)] = _astuple_inner(v, tuple_factory)
|
|
72
|
+
return d
|
|
73
|
+
return obj_type((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory)) for k, v in obj.items())
|
|
74
|
+
|
|
75
|
+
else:
|
|
76
|
+
return copy.deepcopy(obj)
|