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,150 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import dataclasses as dc
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from ... import check
|
|
6
|
+
from ... import lang
|
|
7
|
+
from .fields import preprocess_field
|
|
8
|
+
from .frozen import FrozenProcessor
|
|
9
|
+
from .hashing import HashProcessor
|
|
10
|
+
from .init import InitProcessor
|
|
11
|
+
from .internals import FIELDS_ATTR
|
|
12
|
+
from .internals import PARAMS_ATTR
|
|
13
|
+
from .internals import Params
|
|
14
|
+
from .internals import is_kw_only
|
|
15
|
+
from .order import OrderProcessor
|
|
16
|
+
from .params import ParamsExtras
|
|
17
|
+
from .processing import Processor
|
|
18
|
+
from .reflect import ClassInfo
|
|
19
|
+
from .replace import ReplaceProcessor
|
|
20
|
+
from .repr import ReprProcessor
|
|
21
|
+
from .simple import DocProcessor
|
|
22
|
+
from .simple import EqProcessor
|
|
23
|
+
from .simple import MatchArgsProcessor
|
|
24
|
+
from .simple import OverridesProcessor
|
|
25
|
+
from .slots import add_slots
|
|
26
|
+
|
|
27
|
+
if ta.TYPE_CHECKING:
|
|
28
|
+
from . import metaclass
|
|
29
|
+
else:
|
|
30
|
+
metaclass = lang.proxy_import('.metaclass', __package__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
MISSING = dc.MISSING
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MainProcessor:
|
|
37
|
+
def __init__(self, cls: type) -> None:
|
|
38
|
+
super().__init__()
|
|
39
|
+
|
|
40
|
+
self._cls = check.isinstance(cls, type)
|
|
41
|
+
self._info = info = ClassInfo(cls, _constructing=True)
|
|
42
|
+
|
|
43
|
+
check.not_in(FIELDS_ATTR, cls.__dict__)
|
|
44
|
+
check.is_(check.isinstance(cls.__dict__[PARAMS_ATTR], Params), info.params)
|
|
45
|
+
check.is_(check.isinstance(check.not_none(info.cls_metadata)[ParamsExtras], ParamsExtras), info.params_extras) # noqa
|
|
46
|
+
|
|
47
|
+
def _check_params(self) -> None:
|
|
48
|
+
if self._info.params.order and not self._info.params.eq:
|
|
49
|
+
raise ValueError('eq must be true if order is true')
|
|
50
|
+
|
|
51
|
+
def _check_frozen_bases(self) -> None:
|
|
52
|
+
mc_base = getattr(metaclass, 'Data', None)
|
|
53
|
+
all_frozen_bases = None
|
|
54
|
+
any_frozen_base = False
|
|
55
|
+
has_dataclass_bases = False
|
|
56
|
+
for b in self._cls.__mro__[-1:0:-1]:
|
|
57
|
+
if b is mc_base:
|
|
58
|
+
continue
|
|
59
|
+
base_fields = getattr(b, FIELDS_ATTR, None)
|
|
60
|
+
if base_fields is not None:
|
|
61
|
+
has_dataclass_bases = True
|
|
62
|
+
if all_frozen_bases is None:
|
|
63
|
+
all_frozen_bases = True
|
|
64
|
+
current_frozen = getattr(b, PARAMS_ATTR).frozen
|
|
65
|
+
all_frozen_bases = all_frozen_bases and current_frozen
|
|
66
|
+
any_frozen_base = any_frozen_base or current_frozen
|
|
67
|
+
|
|
68
|
+
if has_dataclass_bases:
|
|
69
|
+
if any_frozen_base and not self._info.params.frozen:
|
|
70
|
+
raise TypeError('cannot inherit non-frozen dataclass from a frozen one')
|
|
71
|
+
|
|
72
|
+
if all_frozen_bases is False and self._info.params.frozen:
|
|
73
|
+
raise TypeError('cannot inherit frozen dataclass from a non-frozen one')
|
|
74
|
+
|
|
75
|
+
@lang.cached_function
|
|
76
|
+
def _process_fields(self) -> None:
|
|
77
|
+
fields: dict[str, dc.Field] = {}
|
|
78
|
+
|
|
79
|
+
for b in self._cls.__mro__[-1:0:-1]:
|
|
80
|
+
base_fields = getattr(b, FIELDS_ATTR, None)
|
|
81
|
+
if base_fields is not None:
|
|
82
|
+
for f in base_fields.values():
|
|
83
|
+
fields[f.name] = f
|
|
84
|
+
|
|
85
|
+
cls_fields: list[dc.Field] = []
|
|
86
|
+
|
|
87
|
+
kw_only = self._info.params12.kw_only
|
|
88
|
+
kw_only_seen = False
|
|
89
|
+
for name, ann in self._info.cls_annotations.items():
|
|
90
|
+
if is_kw_only(self._cls, ann):
|
|
91
|
+
if kw_only_seen:
|
|
92
|
+
raise TypeError(f'{name!r} is KW_ONLY, but KW_ONLY has already been specified')
|
|
93
|
+
kw_only_seen = True
|
|
94
|
+
kw_only = True
|
|
95
|
+
else:
|
|
96
|
+
cls_fields.append(preprocess_field(self._cls, name, ann, kw_only))
|
|
97
|
+
|
|
98
|
+
for f in cls_fields:
|
|
99
|
+
fields[f.name] = f
|
|
100
|
+
if isinstance(getattr(self._cls, f.name, None), dc.Field):
|
|
101
|
+
if f.default is MISSING:
|
|
102
|
+
delattr(self._cls, f.name)
|
|
103
|
+
else:
|
|
104
|
+
setattr(self._cls, f.name, f.default)
|
|
105
|
+
|
|
106
|
+
for name, value in self._cls.__dict__.items():
|
|
107
|
+
if isinstance(value, dc.Field) and name not in self._info.cls_annotations:
|
|
108
|
+
raise TypeError(f'{name!r} is a field but has no type annotation')
|
|
109
|
+
|
|
110
|
+
setattr(self._cls, FIELDS_ATTR, fields)
|
|
111
|
+
|
|
112
|
+
@lang.cached_function
|
|
113
|
+
def _transform_slots(self) -> None:
|
|
114
|
+
if self._info.params12.weakref_slot and not self._info.params12.slots:
|
|
115
|
+
raise TypeError('weakref_slot is True but slots is False')
|
|
116
|
+
if not self._info.params12.slots:
|
|
117
|
+
return
|
|
118
|
+
self._cls = add_slots(self._cls, self._info.params.frozen, self._info.params12.weakref_slot)
|
|
119
|
+
|
|
120
|
+
@lang.cached_function
|
|
121
|
+
def process(self) -> type:
|
|
122
|
+
self._check_params()
|
|
123
|
+
self._check_frozen_bases()
|
|
124
|
+
|
|
125
|
+
self._process_fields()
|
|
126
|
+
|
|
127
|
+
pcls: type[Processor]
|
|
128
|
+
for pcls in [
|
|
129
|
+
InitProcessor,
|
|
130
|
+
OverridesProcessor,
|
|
131
|
+
ReprProcessor,
|
|
132
|
+
EqProcessor,
|
|
133
|
+
OrderProcessor,
|
|
134
|
+
FrozenProcessor,
|
|
135
|
+
HashProcessor,
|
|
136
|
+
DocProcessor,
|
|
137
|
+
MatchArgsProcessor,
|
|
138
|
+
ReplaceProcessor,
|
|
139
|
+
]:
|
|
140
|
+
pcls(self._info).process()
|
|
141
|
+
|
|
142
|
+
self._transform_slots()
|
|
143
|
+
|
|
144
|
+
abc.update_abstractmethods(self._cls) # noqa
|
|
145
|
+
|
|
146
|
+
return self._cls
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def process_class(cls: type) -> type:
|
|
150
|
+
return MainProcessor(cls).process()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- Enum
|
|
4
|
+
"""
|
|
5
|
+
import abc
|
|
6
|
+
import collections
|
|
7
|
+
import dataclasses as dc
|
|
8
|
+
import typing as ta
|
|
9
|
+
|
|
10
|
+
from ... import lang
|
|
11
|
+
from .api import dataclass
|
|
12
|
+
from .api import field # noqa
|
|
13
|
+
from .params import MetaclassParams
|
|
14
|
+
from .params import get_metaclass_params
|
|
15
|
+
from .params import get_params
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
T = ta.TypeVar('T')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def confer_kwarg(out: dict[str, ta.Any], k: str, v: ta.Any) -> None:
|
|
22
|
+
if k in out:
|
|
23
|
+
if out[k] != v:
|
|
24
|
+
raise ValueError
|
|
25
|
+
else:
|
|
26
|
+
out[k] = v
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def confer_kwargs(
|
|
30
|
+
bases: ta.Sequence[type],
|
|
31
|
+
kwargs: ta.Mapping[str, ta.Any],
|
|
32
|
+
) -> dict[str, ta.Any]:
|
|
33
|
+
out: dict[str, ta.Any] = {}
|
|
34
|
+
for base in bases:
|
|
35
|
+
if not dc.is_dataclass(base):
|
|
36
|
+
continue
|
|
37
|
+
if not (bmp := get_metaclass_params(base)).confer:
|
|
38
|
+
continue
|
|
39
|
+
for ck in bmp.confer:
|
|
40
|
+
if ck in kwargs:
|
|
41
|
+
continue
|
|
42
|
+
if ck in ('frozen', 'generic_init', 'kw_only'):
|
|
43
|
+
confer_kwarg(out, ck, get_params(base).frozen)
|
|
44
|
+
elif ck == 'confer':
|
|
45
|
+
confer_kwarg(out, 'confer', bmp.confer)
|
|
46
|
+
else:
|
|
47
|
+
raise KeyError(ck)
|
|
48
|
+
return out
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DataMeta(abc.ABCMeta):
|
|
52
|
+
def __new__(
|
|
53
|
+
mcls,
|
|
54
|
+
name,
|
|
55
|
+
bases,
|
|
56
|
+
namespace,
|
|
57
|
+
*,
|
|
58
|
+
|
|
59
|
+
# confer=frozenset(),
|
|
60
|
+
|
|
61
|
+
metadata=None,
|
|
62
|
+
**kwargs
|
|
63
|
+
):
|
|
64
|
+
cls = lang.super_meta(
|
|
65
|
+
super(),
|
|
66
|
+
mcls,
|
|
67
|
+
name,
|
|
68
|
+
bases,
|
|
69
|
+
namespace,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
ckw = confer_kwargs(bases, kwargs)
|
|
73
|
+
nkw = {**kwargs, **ckw}
|
|
74
|
+
|
|
75
|
+
mcp = MetaclassParams(
|
|
76
|
+
confer=nkw.pop('confer', frozenset()),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
mmd = {
|
|
80
|
+
MetaclassParams: mcp,
|
|
81
|
+
}
|
|
82
|
+
if metadata is not None:
|
|
83
|
+
metadata = collections.ChainMap(mmd, metadata)
|
|
84
|
+
else:
|
|
85
|
+
metadata = mmd
|
|
86
|
+
|
|
87
|
+
return dataclass(cls, metadata=metadata, **nkw)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# @ta.dataclass_transform(field_specifiers=(field,)) # FIXME: ctor
|
|
91
|
+
class Data(metaclass=DataMeta):
|
|
92
|
+
def __init__(self, *args, **kwargs):
|
|
93
|
+
super().__init__(*args, **kwargs)
|
|
94
|
+
|
|
95
|
+
def __post_init__(self, *args, **kwargs) -> None:
|
|
96
|
+
try:
|
|
97
|
+
spi = super().__post_init__ # type: ignore # noqa
|
|
98
|
+
except AttributeError:
|
|
99
|
+
if args or kwargs:
|
|
100
|
+
raise TypeError(args, kwargs)
|
|
101
|
+
else:
|
|
102
|
+
spi(*args, **kwargs)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Frozen(
|
|
106
|
+
Data,
|
|
107
|
+
frozen=True,
|
|
108
|
+
confer=frozenset([
|
|
109
|
+
'frozen',
|
|
110
|
+
'confer',
|
|
111
|
+
]),
|
|
112
|
+
):
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Box(
|
|
117
|
+
Frozen,
|
|
118
|
+
ta.Generic[T],
|
|
119
|
+
generic_init=True,
|
|
120
|
+
confer=frozenset([
|
|
121
|
+
'frozen',
|
|
122
|
+
'generic_init',
|
|
123
|
+
'confer',
|
|
124
|
+
]),
|
|
125
|
+
):
|
|
126
|
+
v: T
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import types
|
|
2
|
+
import typing as ta
|
|
3
|
+
|
|
4
|
+
from ... import lang
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
METADATA_ATTR = '__dataclass_metadata__'
|
|
8
|
+
|
|
9
|
+
Metadata: ta.TypeAlias = ta.Mapping[ta.Any, ta.Any]
|
|
10
|
+
|
|
11
|
+
EMPTY_METADATA: Metadata = types.MappingProxyType({})
|
|
12
|
+
|
|
13
|
+
_CLASS_MERGED_KEYS: set[str] = set()
|
|
14
|
+
CLASS_MERGED_KEYS: ta.AbstractSet = _CLASS_MERGED_KEYS
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _class_merged(o):
|
|
18
|
+
_CLASS_MERGED_KEYS.add(o)
|
|
19
|
+
return o
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_merged_metadata(obj: ta.Any) -> Metadata:
|
|
23
|
+
cls = obj if isinstance(obj, type) else type(obj)
|
|
24
|
+
dct: dict[ta.Any, ta.Any] = {}
|
|
25
|
+
for cur in cls.__mro__[::-1]:
|
|
26
|
+
if not (smd := cur.__dict__.get(METADATA_ATTR)):
|
|
27
|
+
continue
|
|
28
|
+
for k, v in smd.items():
|
|
29
|
+
if k in CLASS_MERGED_KEYS:
|
|
30
|
+
dct.setdefault(k, []).extend(v)
|
|
31
|
+
else:
|
|
32
|
+
dct[k] = v
|
|
33
|
+
return dct
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _append_cls_md(k, v):
|
|
37
|
+
lang.get_caller_cls_dct(1).setdefault(METADATA_ATTR, {}).setdefault(k, []).append(v)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@_class_merged
|
|
44
|
+
class UserMetadata(lang.Marker):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@lang.cls_dct_fn()
|
|
49
|
+
def metadata(cls_dct, *args) -> None:
|
|
50
|
+
cls_dct.setdefault(METADATA_ATTR, {}).setdefault(UserMetadata, []).extend(args)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@_class_merged
|
|
57
|
+
class Check(lang.Marker):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def check(fn: ta.Union[ta.Callable[..., bool], staticmethod]) -> None:
|
|
62
|
+
_append_cls_md(Check, fn)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@_class_merged
|
|
69
|
+
class Init(lang.Marker):
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def init(fn: ta.Callable[..., None]) -> None:
|
|
74
|
+
_append_cls_md(Init, fn)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from .utils import Namespace
|
|
4
|
+
from .processing import Processor
|
|
5
|
+
from .utils import create_fn
|
|
6
|
+
from .utils import set_new_attribute
|
|
7
|
+
from .utils import tuple_str
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def cmp_fn(
|
|
11
|
+
name: str,
|
|
12
|
+
op: str,
|
|
13
|
+
self_tuple: str,
|
|
14
|
+
other_tuple: str,
|
|
15
|
+
globals: Namespace,
|
|
16
|
+
) -> ta.Callable:
|
|
17
|
+
return create_fn(
|
|
18
|
+
name,
|
|
19
|
+
('self', 'other'),
|
|
20
|
+
[
|
|
21
|
+
'if other.__class__ is self.__class__:',
|
|
22
|
+
f' return {self_tuple}{op}{other_tuple}',
|
|
23
|
+
'return NotImplemented',
|
|
24
|
+
],
|
|
25
|
+
globals=globals,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class OrderProcessor(Processor):
|
|
30
|
+
def _process(self) -> None:
|
|
31
|
+
if not self._info.params.order:
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
flds = [f for f in self._info.instance_fields if f.compare]
|
|
35
|
+
self_tuple = tuple_str('self', flds)
|
|
36
|
+
other_tuple = tuple_str('other', flds)
|
|
37
|
+
for name, op in [
|
|
38
|
+
('__lt__', '<'),
|
|
39
|
+
('__le__', '<='),
|
|
40
|
+
('__gt__', '>'),
|
|
41
|
+
('__ge__', '>='),
|
|
42
|
+
]:
|
|
43
|
+
if set_new_attribute(self._cls, name, cmp_fn(name, op, self_tuple, other_tuple, globals=self._info.globals)): # noqa
|
|
44
|
+
raise TypeError(
|
|
45
|
+
f'Cannot overwrite attribute {name} in class {self._cls.__name__}. '
|
|
46
|
+
f'Consider using functools.total_ordering'
|
|
47
|
+
)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Field:
|
|
3
|
+
name: str | None = None
|
|
4
|
+
type: Any = None
|
|
5
|
+
default: Any | MISSING = MISSING
|
|
6
|
+
default_factory: Any | MISSING = MISSING
|
|
7
|
+
repr: bool = True
|
|
8
|
+
hash: bool | None = None
|
|
9
|
+
init: bool = True
|
|
10
|
+
compare: bool = True
|
|
11
|
+
metadata: Metadata | None = None
|
|
12
|
+
kw_only: bool | MISSING = MISSING
|
|
13
|
+
|
|
14
|
+
_field_type: Any = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Params:
|
|
18
|
+
init: bool = True
|
|
19
|
+
repr: bool = True
|
|
20
|
+
eq: bool = True
|
|
21
|
+
order: bool = False
|
|
22
|
+
unsafe_hash: bool = False
|
|
23
|
+
frozen: bool = False
|
|
24
|
+
|
|
25
|
+
match_args: bool = True
|
|
26
|
+
kw_only: bool = False
|
|
27
|
+
slots: bool = False
|
|
28
|
+
weakref_slot: bool = False
|
|
29
|
+
"""
|
|
30
|
+
import dataclasses as dc
|
|
31
|
+
import sys
|
|
32
|
+
import typing as ta
|
|
33
|
+
|
|
34
|
+
from ... import lang
|
|
35
|
+
from .internals import PARAMS_ATTR
|
|
36
|
+
from .internals import Params
|
|
37
|
+
from .metadata import EMPTY_METADATA
|
|
38
|
+
from .metadata import METADATA_ATTR
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
IS_12 = sys.version_info[1] >= 12
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dc.dataclass(frozen=True)
|
|
48
|
+
class FieldExtras(lang.Final):
|
|
49
|
+
coerce: ta.Optional[ta.Union[bool, ta.Callable[[ta.Any], ta.Any]]] = None
|
|
50
|
+
check: ta.Optional[ta.Callable[[ta.Any], bool]] = None
|
|
51
|
+
check_type: ta.Optional[bool] = None
|
|
52
|
+
override: bool = False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
DEFAULT_FIELD_EXTRAS = FieldExtras()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_field_extras(f: dc.Field) -> FieldExtras:
|
|
59
|
+
if not isinstance(f, dc.Field):
|
|
60
|
+
raise TypeError(f)
|
|
61
|
+
return f.metadata.get(FieldExtras, DEFAULT_FIELD_EXTRAS)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_params_cls(obj: ta.Any) -> type | None:
|
|
68
|
+
if not isinstance(obj, type):
|
|
69
|
+
obj = type(obj)
|
|
70
|
+
for cur in obj.__mro__:
|
|
71
|
+
if PARAMS_ATTR in cur.__dict__:
|
|
72
|
+
return cur
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_params(obj: ta.Any) -> Params:
|
|
77
|
+
if not hasattr(obj, PARAMS_ATTR):
|
|
78
|
+
raise TypeError(obj)
|
|
79
|
+
return getattr(obj, PARAMS_ATTR)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
##
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dc.dataclass(frozen=True)
|
|
86
|
+
class Params12(lang.Final):
|
|
87
|
+
match_args: bool = True
|
|
88
|
+
kw_only: bool = False
|
|
89
|
+
slots: bool = False
|
|
90
|
+
weakref_slot: bool = False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
DEFAULT_PARAMS12 = Params12()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_params12(obj: ta.Any) -> Params12:
|
|
97
|
+
if IS_12:
|
|
98
|
+
p = get_params(obj)
|
|
99
|
+
return Params12(
|
|
100
|
+
match_args=p.match_args,
|
|
101
|
+
kw_only=p.kw_only,
|
|
102
|
+
slots=p.slots,
|
|
103
|
+
weakref_slot=p.weakref_slot,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if (pcls := get_params_cls(obj)) is None:
|
|
107
|
+
raise TypeError(pcls)
|
|
108
|
+
|
|
109
|
+
md = pcls.__dict__.get(METADATA_ATTR, EMPTY_METADATA)
|
|
110
|
+
return md.get(Params12, DEFAULT_PARAMS12)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
##
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dc.dataclass(frozen=True)
|
|
117
|
+
class ParamsExtras(lang.Final):
|
|
118
|
+
reorder: bool = False
|
|
119
|
+
cache_hash: bool = False
|
|
120
|
+
generic_init: bool = False
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
DEFAULT_PARAMS_EXTRAS = ParamsExtras()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_params_extras(obj: ta.Any) -> ParamsExtras:
|
|
127
|
+
if (pcls := get_params_cls(obj)) is None:
|
|
128
|
+
raise TypeError(pcls)
|
|
129
|
+
|
|
130
|
+
md = pcls.__dict__.get(METADATA_ATTR, EMPTY_METADATA)
|
|
131
|
+
return md.get(ParamsExtras, DEFAULT_PARAMS_EXTRAS)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
##
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dc.dataclass(frozen=True)
|
|
138
|
+
class MetaclassParams:
|
|
139
|
+
confer: frozenset[str] = frozenset()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
DEFAULT_METACLASS_PARAMS = MetaclassParams()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def get_metaclass_params(obj: ta.Any) -> MetaclassParams:
|
|
146
|
+
if (pcls := get_params_cls(obj)) is None:
|
|
147
|
+
raise TypeError(pcls)
|
|
148
|
+
|
|
149
|
+
md = pcls.__dict__.get(METADATA_ATTR, EMPTY_METADATA)
|
|
150
|
+
return md.get(MetaclassParams, DEFAULT_METACLASS_PARAMS)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from ... import lang
|
|
2
|
+
from .reflect import ClassInfo
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Processor(lang.Abstract):
|
|
6
|
+
def __init__(self, info: ClassInfo) -> None:
|
|
7
|
+
super().__init__()
|
|
8
|
+
self._cls = info.cls
|
|
9
|
+
self._info = info
|
|
10
|
+
|
|
11
|
+
@lang.cached_function
|
|
12
|
+
def process(self) -> None:
|
|
13
|
+
self._process()
|
|
14
|
+
|
|
15
|
+
def _process(self) -> None:
|
|
16
|
+
raise NotImplementedError
|