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,148 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
2
|
+
import types
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from ... import check as check_
|
|
6
|
+
from ... import lang
|
|
7
|
+
from .internals import FieldType
|
|
8
|
+
from .internals import is_classvar
|
|
9
|
+
from .internals import is_initvar
|
|
10
|
+
from .params import get_field_extras
|
|
11
|
+
|
|
12
|
+
if ta.TYPE_CHECKING:
|
|
13
|
+
from . import api
|
|
14
|
+
else:
|
|
15
|
+
api = lang.proxy_import('.api', __package__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
MISSING = dc.MISSING
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def field_type(f: dc.Field) -> FieldType:
|
|
22
|
+
if (ft := getattr(f, '_field_type')) is not None:
|
|
23
|
+
return FieldType(ft)
|
|
24
|
+
else:
|
|
25
|
+
return FieldType.INSTANCE
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def has_default(f: dc.Field) -> bool:
|
|
29
|
+
return not (f.default is MISSING and f.default_factory is MISSING)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def preprocess_field(
|
|
33
|
+
cls: type,
|
|
34
|
+
a_name: str,
|
|
35
|
+
a_type: ta.Any,
|
|
36
|
+
default_kw_only: bool,
|
|
37
|
+
) -> dc.Field:
|
|
38
|
+
default = getattr(cls, a_name, MISSING)
|
|
39
|
+
if isinstance(default, dc.Field):
|
|
40
|
+
f = default
|
|
41
|
+
else:
|
|
42
|
+
if isinstance(default, types.MemberDescriptorType):
|
|
43
|
+
# This is a field in __slots__, so it has no default value.
|
|
44
|
+
default = MISSING
|
|
45
|
+
f = api.field(default=default)
|
|
46
|
+
|
|
47
|
+
f.name = a_name
|
|
48
|
+
f.type = a_type
|
|
49
|
+
|
|
50
|
+
ft = FieldType.INSTANCE
|
|
51
|
+
if is_classvar(cls, f.type):
|
|
52
|
+
ft = FieldType.CLASS
|
|
53
|
+
if is_initvar(cls, f.type):
|
|
54
|
+
ft = FieldType.INIT
|
|
55
|
+
if ft in (FieldType.CLASS, FieldType.INIT):
|
|
56
|
+
if f.default_factory is not MISSING:
|
|
57
|
+
raise TypeError(f'field {f.name} cannot have a default factory')
|
|
58
|
+
f._field_type = ft.value # type: ignore
|
|
59
|
+
|
|
60
|
+
if ft in (FieldType.INSTANCE, FieldType.INIT):
|
|
61
|
+
if f.kw_only is MISSING:
|
|
62
|
+
f.kw_only = default_kw_only
|
|
63
|
+
else:
|
|
64
|
+
check_.arg(ft is FieldType.CLASS)
|
|
65
|
+
if f.kw_only is not MISSING:
|
|
66
|
+
raise TypeError(f'field {f.name} is a ClassVar but specifies kw_only')
|
|
67
|
+
|
|
68
|
+
if ft is FieldType.INSTANCE and f.default is not MISSING and f.default.__class__.__hash__ is None:
|
|
69
|
+
raise ValueError(f'mutable default {type(f.default)} for field {f.name} is not allowed: use default_factory')
|
|
70
|
+
|
|
71
|
+
return f
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def field_assign(
|
|
75
|
+
frozen: bool,
|
|
76
|
+
name: str,
|
|
77
|
+
value: ta.Any,
|
|
78
|
+
self_name: str,
|
|
79
|
+
override: bool,
|
|
80
|
+
) -> str:
|
|
81
|
+
if override:
|
|
82
|
+
return f'{self_name}.__dict__[{name!r}] = {value}'
|
|
83
|
+
if frozen:
|
|
84
|
+
return f'__dataclass_builtins_object__.__setattr__({self_name}, {name!r}, {value})'
|
|
85
|
+
return f'{self_name}.{name} = {value}'
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def field_init(
|
|
89
|
+
f: dc.Field,
|
|
90
|
+
frozen: bool,
|
|
91
|
+
locals: dict[str, ta.Any],
|
|
92
|
+
self_name: str,
|
|
93
|
+
slots: bool,
|
|
94
|
+
) -> ta.Sequence[str]:
|
|
95
|
+
default_name = f'__dataclass_dflt_{f.name}__'
|
|
96
|
+
fx = get_field_extras(f)
|
|
97
|
+
|
|
98
|
+
lines = []
|
|
99
|
+
|
|
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
|
+
value: str | None = None
|
|
119
|
+
if f.default_factory is not MISSING:
|
|
120
|
+
if f.init:
|
|
121
|
+
locals[default_name] = f.default_factory
|
|
122
|
+
value = (
|
|
123
|
+
f'{default_name}() '
|
|
124
|
+
f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__ '
|
|
125
|
+
f'else {f.name}'
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
locals[default_name] = f.default_factory
|
|
129
|
+
value = f'{default_name}()'
|
|
130
|
+
|
|
131
|
+
elif f.init:
|
|
132
|
+
if f.default is MISSING:
|
|
133
|
+
value = f.name
|
|
134
|
+
elif f.default is not MISSING:
|
|
135
|
+
locals[default_name] = f.default
|
|
136
|
+
value = f.name
|
|
137
|
+
|
|
138
|
+
else:
|
|
139
|
+
if slots and f.default is not MISSING:
|
|
140
|
+
locals[default_name] = f.default
|
|
141
|
+
value = default_name
|
|
142
|
+
else:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
if value is not None and field_type(f) is not FieldType.INIT:
|
|
146
|
+
lines.append(field_assign(frozen, f.name, value, self_name, fx.override)) # noqa
|
|
147
|
+
|
|
148
|
+
return lines
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
2
|
+
import typing as ta
|
|
3
|
+
|
|
4
|
+
from .processing import Processor
|
|
5
|
+
from .utils import Namespace
|
|
6
|
+
from .utils import create_fn
|
|
7
|
+
from .utils import set_new_attribute
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def frozen_get_del_attr(
|
|
11
|
+
cls: type,
|
|
12
|
+
fields: ta.Sequence[dc.Field],
|
|
13
|
+
globals: Namespace,
|
|
14
|
+
) -> tuple[ta.Callable, ta.Callable]:
|
|
15
|
+
locals = {
|
|
16
|
+
'cls': cls,
|
|
17
|
+
'FrozenInstanceError': dc.FrozenInstanceError,
|
|
18
|
+
}
|
|
19
|
+
condition = 'type(self) is cls'
|
|
20
|
+
if fields:
|
|
21
|
+
condition += ' or name in {' + ', '.join(repr(f.name) for f in fields) + '}'
|
|
22
|
+
return (
|
|
23
|
+
create_fn(
|
|
24
|
+
'__setattr__',
|
|
25
|
+
('self', 'name', 'value'),
|
|
26
|
+
[
|
|
27
|
+
f'if {condition}:',
|
|
28
|
+
' raise FrozenInstanceError(f"cannot assign to field {name!r}")',
|
|
29
|
+
f'super(cls, self).__setattr__(name, value)',
|
|
30
|
+
],
|
|
31
|
+
locals=locals,
|
|
32
|
+
globals=globals,
|
|
33
|
+
),
|
|
34
|
+
create_fn(
|
|
35
|
+
'__delattr__',
|
|
36
|
+
('self', 'name'),
|
|
37
|
+
[
|
|
38
|
+
f'if {condition}:',
|
|
39
|
+
' raise FrozenInstanceError(f"cannot delete field {name!r}")',
|
|
40
|
+
f'super(cls, self).__delattr__(name)',
|
|
41
|
+
],
|
|
42
|
+
locals=locals,
|
|
43
|
+
globals=globals,
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class FrozenProcessor(Processor):
|
|
49
|
+
def _process(self) -> None:
|
|
50
|
+
if not self._info.params.frozen:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
for fn in frozen_get_del_attr(self._cls, self._info.instance_fields, self._info.globals):
|
|
54
|
+
if set_new_attribute(self._cls, fn.__name__, fn):
|
|
55
|
+
raise TypeError(f'Cannot overwrite attribute {fn.__name__} in class {self._cls.__name__}')
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
2
|
+
import enum
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from .processing import Processor
|
|
6
|
+
from .utils import create_fn
|
|
7
|
+
from .utils import set_qualname
|
|
8
|
+
from .utils import tuple_str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HashAction(enum.Enum):
|
|
12
|
+
SET_NONE = enum.auto()
|
|
13
|
+
ADD = enum.auto()
|
|
14
|
+
EXCEPTION = enum.auto()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# See https://bugs.python.org/issue32929#msg312829 for an if-statement version of this table.
|
|
18
|
+
HASH_ACTIONS: ta.Mapping[tuple[bool, bool, bool, bool], HashAction | None] = {
|
|
19
|
+
#
|
|
20
|
+
# +-------------------------------------- unsafe_hash?
|
|
21
|
+
# | +------------------------------- eq?
|
|
22
|
+
# | | +------------------------ frozen?
|
|
23
|
+
# | | | +---------------- has-explicit-hash?
|
|
24
|
+
# v v v v
|
|
25
|
+
(False, False, False, False): None,
|
|
26
|
+
(False, False, False, True): None,
|
|
27
|
+
(False, False, True, False): None,
|
|
28
|
+
(False, False, True, True): None,
|
|
29
|
+
(False, True, False, False): HashAction.SET_NONE,
|
|
30
|
+
(False, True, False, True): None,
|
|
31
|
+
(False, True, True, False): HashAction.ADD,
|
|
32
|
+
(False, True, True, True): None,
|
|
33
|
+
(True, False, False, False): HashAction.ADD,
|
|
34
|
+
(True, False, False, True): HashAction.EXCEPTION,
|
|
35
|
+
(True, False, True, False): HashAction.ADD,
|
|
36
|
+
(True, False, True, True): HashAction.EXCEPTION,
|
|
37
|
+
(True, True, False, False): HashAction.ADD,
|
|
38
|
+
(True, True, False, True): HashAction.EXCEPTION,
|
|
39
|
+
(True, True, True, False): HashAction.ADD,
|
|
40
|
+
(True, True, True, True): HashAction.EXCEPTION,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class HashProcessor(Processor):
|
|
45
|
+
CACHED_HASH_ATTR = '__dataclass_hash__'
|
|
46
|
+
|
|
47
|
+
def _build_hash_fn(self) -> ta.Callable:
|
|
48
|
+
flds = [f for f in self._info.instance_fields if (f.compare if f.hash is None else f.hash)]
|
|
49
|
+
self_tuple = tuple_str('self', flds)
|
|
50
|
+
if self._info.params_extras.cache_hash:
|
|
51
|
+
body = [
|
|
52
|
+
f'try: return self.{self.CACHED_HASH_ATTR}',
|
|
53
|
+
f'except AttributeError: object.__setattr__(self, {self.CACHED_HASH_ATTR!r}, h := hash({self_tuple}))',
|
|
54
|
+
f'return h',
|
|
55
|
+
]
|
|
56
|
+
else:
|
|
57
|
+
body = [f'return hash({self_tuple})']
|
|
58
|
+
hash_fn = create_fn(
|
|
59
|
+
'__hash__',
|
|
60
|
+
('self',),
|
|
61
|
+
body,
|
|
62
|
+
globals=self._info.globals,
|
|
63
|
+
)
|
|
64
|
+
return set_qualname(self._cls, hash_fn) # noqa
|
|
65
|
+
|
|
66
|
+
def _process(self) -> None:
|
|
67
|
+
class_hash = self._cls.__dict__.get('__hash__', dc.MISSING)
|
|
68
|
+
has_explicit_hash = not (class_hash is dc.MISSING or (class_hash is None and '__eq__' in self._cls.__dict__))
|
|
69
|
+
|
|
70
|
+
match (hash_action := HASH_ACTIONS[(
|
|
71
|
+
bool(self._info.params.unsafe_hash),
|
|
72
|
+
bool(self._info.params.eq),
|
|
73
|
+
bool(self._info.params.frozen),
|
|
74
|
+
has_explicit_hash,
|
|
75
|
+
)]):
|
|
76
|
+
case HashAction.SET_NONE:
|
|
77
|
+
self._cls.__hash__ = None # type: ignore
|
|
78
|
+
case HashAction.ADD:
|
|
79
|
+
self._cls.__hash__ = self._build_hash_fn() # type: ignore
|
|
80
|
+
case HashAction.EXCEPTION:
|
|
81
|
+
raise TypeError(f'Cannot overwrite attribute __hash__ in class {self._cls.__name__}')
|
|
82
|
+
case None:
|
|
83
|
+
pass
|
|
84
|
+
case _:
|
|
85
|
+
raise ValueError(hash_action)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
2
|
+
import inspect
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from ... import lang
|
|
6
|
+
from .exceptions import CheckException
|
|
7
|
+
from .fields import field_init
|
|
8
|
+
from .fields import field_type
|
|
9
|
+
from .fields import has_default
|
|
10
|
+
from .internals import FieldType
|
|
11
|
+
from .internals import HAS_DEFAULT_FACTORY
|
|
12
|
+
from .internals import POST_INIT_NAME
|
|
13
|
+
from .metadata import Check
|
|
14
|
+
from .metadata import Init
|
|
15
|
+
from .processing import Processor
|
|
16
|
+
from .reflect import ClassInfo
|
|
17
|
+
from .utils import Namespace
|
|
18
|
+
from .utils import create_fn
|
|
19
|
+
from .utils import set_new_attribute
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
MISSING = dc.MISSING
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class InitFields(ta.NamedTuple):
|
|
26
|
+
all: ta.Sequence[dc.Field]
|
|
27
|
+
ordered: ta.Sequence[dc.Field]
|
|
28
|
+
std: ta.Sequence[dc.Field]
|
|
29
|
+
kw_only: ta.Sequence[dc.Field]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_init_fields(fields: ta.Iterable[dc.Field], *, reorder: bool = False) -> InitFields:
|
|
33
|
+
all_init_fields = [f for f in fields if field_type(f) in (FieldType.INSTANCE, FieldType.INIT)]
|
|
34
|
+
ordered_init_fields = list(all_init_fields)
|
|
35
|
+
if reorder:
|
|
36
|
+
ordered_init_fields.sort(key=lambda f: (has_default(f), not f.kw_only))
|
|
37
|
+
std_init_fields, kw_only_init_fields = (
|
|
38
|
+
tuple(f1 for f1 in ordered_init_fields if f1.init and not f1.kw_only),
|
|
39
|
+
tuple(f1 for f1 in ordered_init_fields if f1.init and f1.kw_only),
|
|
40
|
+
)
|
|
41
|
+
return InitFields(
|
|
42
|
+
all=all_init_fields,
|
|
43
|
+
ordered=ordered_init_fields,
|
|
44
|
+
std=std_init_fields,
|
|
45
|
+
kw_only=kw_only_init_fields,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def init_param(f: dc.Field) -> str:
|
|
50
|
+
if not has_default(f):
|
|
51
|
+
default = ''
|
|
52
|
+
elif f.default is not MISSING:
|
|
53
|
+
default = f' = __dataclass_dflt_{f.name}__'
|
|
54
|
+
elif f.default_factory is not MISSING:
|
|
55
|
+
default = ' = __dataclass_HAS_DEFAULT_FACTORY__'
|
|
56
|
+
return f'{f.name}: __dataclass_type_{f.name}__{default}' # noqa
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class InitBuilder:
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
info: ClassInfo,
|
|
64
|
+
fields: ta.Mapping[str, dc.Field],
|
|
65
|
+
has_post_init: bool,
|
|
66
|
+
self_name: str,
|
|
67
|
+
globals: Namespace,
|
|
68
|
+
) -> None:
|
|
69
|
+
super().__init__()
|
|
70
|
+
|
|
71
|
+
self._info = info
|
|
72
|
+
self._fields = fields
|
|
73
|
+
self._has_post_init = has_post_init
|
|
74
|
+
self._self_name = self_name
|
|
75
|
+
self._globals = globals
|
|
76
|
+
|
|
77
|
+
@lang.cached_function
|
|
78
|
+
def build(self) -> ta.Callable:
|
|
79
|
+
ifs = get_init_fields(self._fields.values(), reorder=self._info.params_extras.reorder)
|
|
80
|
+
|
|
81
|
+
seen_default = None
|
|
82
|
+
for f in ifs.std:
|
|
83
|
+
if f.init:
|
|
84
|
+
if has_default(f):
|
|
85
|
+
seen_default = f
|
|
86
|
+
elif seen_default:
|
|
87
|
+
raise TypeError(f'non-default argument {f.name!r} follows default argument {seen_default.name!r}')
|
|
88
|
+
|
|
89
|
+
locals: dict[str, ta.Any] = {}
|
|
90
|
+
|
|
91
|
+
if self._info.params_extras.generic_init:
|
|
92
|
+
get_fty = lambda f: self._info.generic_replaced_field_annotations[f.name]
|
|
93
|
+
else:
|
|
94
|
+
get_fty = lambda f: f.type
|
|
95
|
+
locals.update({f'__dataclass_type_{f.name}__': get_fty(f) for f in ifs.all})
|
|
96
|
+
|
|
97
|
+
locals.update({
|
|
98
|
+
'__dataclass_HAS_DEFAULT_FACTORY__': HAS_DEFAULT_FACTORY,
|
|
99
|
+
'__dataclass_builtins_object__': object,
|
|
100
|
+
'__dataclass_builtins_isinstance__': isinstance,
|
|
101
|
+
'__dataclass_builtins_TypeError__': TypeError,
|
|
102
|
+
'__dataclass_CheckException__': CheckException,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
body_lines: list[str] = []
|
|
106
|
+
for f in ifs.all:
|
|
107
|
+
f_lines = field_init(
|
|
108
|
+
f,
|
|
109
|
+
self._info.params.frozen,
|
|
110
|
+
locals,
|
|
111
|
+
self._self_name,
|
|
112
|
+
self._info.params12.slots,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if f_lines:
|
|
116
|
+
body_lines.extend(f_lines)
|
|
117
|
+
|
|
118
|
+
if self._has_post_init:
|
|
119
|
+
params_str = ','.join(f.name for f in ifs.all if field_type(f) is FieldType.INIT)
|
|
120
|
+
body_lines.append(f'{self._self_name}.{POST_INIT_NAME}({params_str})')
|
|
121
|
+
|
|
122
|
+
for i, fn in enumerate(self._info.merged_metadata.get(Check, [])):
|
|
123
|
+
if isinstance(fn, staticmethod):
|
|
124
|
+
fn = fn.__func__
|
|
125
|
+
cn = f'__dataclass_check_{i}__'
|
|
126
|
+
locals[cn] = fn
|
|
127
|
+
csig = inspect.signature(fn)
|
|
128
|
+
cas = ', '.join(p.name for p in csig.parameters.values())
|
|
129
|
+
body_lines.append(f'if not {cn}({cas}): raise __dataclass_CheckException__')
|
|
130
|
+
|
|
131
|
+
for i, fn in enumerate(self._info.merged_metadata.get(Init, [])):
|
|
132
|
+
cn = f'__dataclass_init_{i}__'
|
|
133
|
+
locals[cn] = fn
|
|
134
|
+
body_lines.append(f'{cn}({self._self_name})')
|
|
135
|
+
|
|
136
|
+
if not body_lines:
|
|
137
|
+
body_lines = ['pass']
|
|
138
|
+
|
|
139
|
+
_init_params = [init_param(f) for f in ifs.std]
|
|
140
|
+
if ifs.kw_only:
|
|
141
|
+
_init_params += ['*']
|
|
142
|
+
_init_params += [init_param(f) for f in ifs.kw_only]
|
|
143
|
+
|
|
144
|
+
return create_fn(
|
|
145
|
+
'__init__',
|
|
146
|
+
[self._self_name] + _init_params,
|
|
147
|
+
body_lines,
|
|
148
|
+
locals=locals,
|
|
149
|
+
globals=self._globals,
|
|
150
|
+
return_type=lang.just(None),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class InitProcessor(Processor):
|
|
155
|
+
def _process(self) -> None:
|
|
156
|
+
if not self._info.params.init:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
has_post_init = hasattr(self._cls, POST_INIT_NAME)
|
|
160
|
+
self_name = '__dataclass_self__' if 'self' in self._info.fields else 'self'
|
|
161
|
+
|
|
162
|
+
init = InitBuilder(
|
|
163
|
+
ClassInfo(self._cls),
|
|
164
|
+
self._info.fields,
|
|
165
|
+
has_post_init,
|
|
166
|
+
self_name,
|
|
167
|
+
self._info.globals,
|
|
168
|
+
).build()
|
|
169
|
+
set_new_attribute(
|
|
170
|
+
self._cls,
|
|
171
|
+
'__init__',
|
|
172
|
+
init,
|
|
173
|
+
)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
2
|
+
import enum
|
|
3
|
+
import sys
|
|
4
|
+
import types
|
|
5
|
+
import typing as ta
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
HAS_DEFAULT_FACTORY = dc._HAS_DEFAULT_FACTORY # type: ignore # noqa
|
|
12
|
+
|
|
13
|
+
FIELDS_ATTR = dc._FIELDS # type: ignore # noqa
|
|
14
|
+
PARAMS_ATTR = dc._PARAMS # type: ignore # noqa
|
|
15
|
+
|
|
16
|
+
POST_INIT_NAME = dc._POST_INIT_NAME # type: ignore # noqa
|
|
17
|
+
|
|
18
|
+
Params = dc._DataclassParams # type: ignore # noqa
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
is_dataclass_instance = dc._is_dataclass_instance # type: ignore # noqa
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
ATOMIC_TYPES: ta.FrozenSet[type]
|
|
31
|
+
|
|
32
|
+
if hasattr(dc, '_ATOMIC_TYPES'):
|
|
33
|
+
ATOMIC_TYPES = getattr(dc, '_ATOMIC_TYPES')
|
|
34
|
+
|
|
35
|
+
else:
|
|
36
|
+
ATOMIC_TYPES = frozenset({
|
|
37
|
+
types.NoneType,
|
|
38
|
+
bool,
|
|
39
|
+
int,
|
|
40
|
+
float,
|
|
41
|
+
str,
|
|
42
|
+
|
|
43
|
+
complex,
|
|
44
|
+
bytes,
|
|
45
|
+
|
|
46
|
+
types.EllipsisType,
|
|
47
|
+
types.NotImplementedType,
|
|
48
|
+
types.CodeType,
|
|
49
|
+
types.BuiltinFunctionType,
|
|
50
|
+
types.FunctionType,
|
|
51
|
+
type,
|
|
52
|
+
range,
|
|
53
|
+
property,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _patch_missing_ctor() -> None:
|
|
61
|
+
# dc.asdict uses copy.deepcopy which instantiates new _MISSING_TYPE objects which do not pass the 'foo is MISSING'
|
|
62
|
+
# checks used throughout dataclasses code. Code should not depend on this behavior but it is a debugging landmine.
|
|
63
|
+
if dc._MISSING_TYPE.__new__ is object.__new__: # noqa
|
|
64
|
+
def _MISSING_TYPE_new(cls): # noqa
|
|
65
|
+
return dc.MISSING
|
|
66
|
+
dc._MISSING_TYPE.__new__ = _MISSING_TYPE_new # type: ignore # noqa
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class FieldType(enum.Enum):
|
|
73
|
+
INSTANCE = dc._FIELD # type: ignore # noqa
|
|
74
|
+
CLASS = dc._FIELD_CLASSVAR # type: ignore # noqa
|
|
75
|
+
INIT = dc._FIELD_INITVAR # type: ignore # noqa
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
_SELF_MODULE = None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _self_module():
|
|
82
|
+
global _SELF_MODULE
|
|
83
|
+
if _SELF_MODULE is None:
|
|
84
|
+
_SELF_MODULE = sys.modules[__package__.rpartition('.')[0]]
|
|
85
|
+
return _SELF_MODULE
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def is_classvar(cls: type, ty: ta.Any) -> bool:
|
|
89
|
+
return (
|
|
90
|
+
dc._is_classvar(ty, ta) # type: ignore # noqa
|
|
91
|
+
or (isinstance(ty, str) and dc._is_type(ty, cls, ta, ta.ClassVar, dc._is_classvar)) # type: ignore # noqa
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def is_initvar(cls: type, ty: ta.Any) -> bool:
|
|
96
|
+
return (
|
|
97
|
+
dc._is_initvar(ty, dc) # type: ignore # noqa
|
|
98
|
+
or (
|
|
99
|
+
isinstance(ty, str)
|
|
100
|
+
and any(
|
|
101
|
+
dc._is_type(ty, cls, mod, dc.InitVar, dc._is_initvar) # type: ignore # noqa
|
|
102
|
+
for mod in (dc, _self_module())
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def is_kw_only(cls: type, ty: ta.Any) -> bool:
|
|
109
|
+
return (
|
|
110
|
+
dc._is_kw_only(ty, dc) # type: ignore # noqa
|
|
111
|
+
or (
|
|
112
|
+
isinstance(ty, str)
|
|
113
|
+
and any(
|
|
114
|
+
dc._is_type(ty, cls, mod, dc.KW_ONLY, dc._is_kw_only) # type: ignore # noqa
|
|
115
|
+
for mod in (dc, _self_module())
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
)
|