omlish 0.0.0.dev284__py3-none-any.whl → 0.0.0.dev286__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.
- omlish/__about__.py +6 -2
- omlish/dataclasses/__init__.py +58 -60
- omlish/dataclasses/api/__init__.py +25 -0
- omlish/dataclasses/api/classes/__init__.py +0 -0
- omlish/dataclasses/api/classes/conversion.py +30 -0
- omlish/dataclasses/api/classes/decorator.py +145 -0
- omlish/dataclasses/api/classes/make.py +109 -0
- omlish/dataclasses/api/classes/metadata.py +133 -0
- omlish/dataclasses/api/classes/params.py +78 -0
- omlish/dataclasses/api/fields/__init__.py +0 -0
- omlish/dataclasses/api/fields/building.py +120 -0
- omlish/dataclasses/api/fields/constructor.py +56 -0
- omlish/dataclasses/api/fields/conversion.py +191 -0
- omlish/dataclasses/api/fields/metadata.py +94 -0
- omlish/dataclasses/concerns/__init__.py +17 -0
- omlish/dataclasses/concerns/abc.py +15 -0
- omlish/dataclasses/concerns/copy.py +63 -0
- omlish/dataclasses/concerns/doc.py +53 -0
- omlish/dataclasses/concerns/eq.py +60 -0
- omlish/dataclasses/concerns/fields.py +119 -0
- omlish/dataclasses/concerns/frozen.py +133 -0
- omlish/dataclasses/concerns/hash.py +165 -0
- omlish/dataclasses/concerns/init.py +453 -0
- omlish/dataclasses/concerns/matchargs.py +27 -0
- omlish/dataclasses/concerns/mro.py +16 -0
- omlish/dataclasses/concerns/order.py +87 -0
- omlish/dataclasses/concerns/override.py +98 -0
- omlish/dataclasses/concerns/params.py +14 -0
- omlish/dataclasses/concerns/replace.py +48 -0
- omlish/dataclasses/concerns/repr.py +95 -0
- omlish/dataclasses/{impl → concerns}/slots.py +25 -1
- omlish/dataclasses/debug.py +2 -0
- omlish/dataclasses/errors.py +115 -0
- omlish/dataclasses/generation/__init__.py +0 -0
- omlish/dataclasses/generation/base.py +38 -0
- omlish/dataclasses/generation/compilation.py +258 -0
- omlish/dataclasses/generation/execution.py +195 -0
- omlish/dataclasses/generation/globals.py +83 -0
- omlish/dataclasses/generation/idents.py +6 -0
- omlish/dataclasses/generation/mangling.py +18 -0
- omlish/dataclasses/generation/manifests.py +20 -0
- omlish/dataclasses/generation/ops.py +97 -0
- omlish/dataclasses/generation/plans.py +35 -0
- omlish/dataclasses/generation/processor.py +179 -0
- omlish/dataclasses/generation/registry.py +42 -0
- omlish/dataclasses/generation/utils.py +83 -0
- omlish/dataclasses/{impl/reflect.py → inspect.py} +53 -90
- omlish/dataclasses/{impl/internals.py → internals.py} +26 -32
- omlish/dataclasses/metaclass/__init__.py +0 -0
- omlish/dataclasses/metaclass/bases.py +69 -0
- omlish/dataclasses/metaclass/confer.py +65 -0
- omlish/dataclasses/metaclass/meta.py +115 -0
- omlish/dataclasses/metaclass/specs.py +38 -0
- omlish/dataclasses/processing/__init__.py +0 -0
- omlish/dataclasses/processing/base.py +83 -0
- omlish/dataclasses/processing/driving.py +49 -0
- omlish/dataclasses/processing/priority.py +13 -0
- omlish/dataclasses/processing/registry.py +81 -0
- omlish/dataclasses/reflection.py +81 -0
- omlish/dataclasses/specs.py +224 -0
- omlish/dataclasses/tools/__init__.py +0 -0
- omlish/dataclasses/{impl → tools}/as_.py +23 -8
- omlish/dataclasses/tools/iter.py +27 -0
- omlish/dataclasses/tools/modifiers.py +52 -0
- omlish/dataclasses/tools/replace.py +17 -0
- omlish/dataclasses/tools/repr.py +12 -0
- omlish/dataclasses/{static.py → tools/static.py} +25 -4
- omlish/dataclasses/utils.py +54 -109
- omlish/diag/__init__.py +4 -4
- omlish/inject/impl/origins.py +1 -1
- omlish/lang/cached/function.py +4 -2
- omlish/lang/maybes.py +17 -0
- omlish/lite/maybes.py +17 -0
- omlish/marshal/objects/dataclasses.py +3 -7
- omlish/marshal/objects/helpers.py +3 -3
- omlish/secrets/marshal.py +1 -1
- omlish/secrets/secrets.py +1 -1
- omlish/sql/queries/base.py +1 -1
- omlish/text/minja.py +81 -25
- omlish/text/templating.py +116 -0
- omlish/typedvalues/marshal.py +2 -2
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/METADATA +4 -1
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/RECORD +87 -46
- omlish/dataclasses/impl/LICENSE +0 -279
- omlish/dataclasses/impl/__init__.py +0 -33
- omlish/dataclasses/impl/api.py +0 -278
- omlish/dataclasses/impl/copy.py +0 -30
- omlish/dataclasses/impl/errors.py +0 -53
- omlish/dataclasses/impl/fields.py +0 -245
- omlish/dataclasses/impl/frozen.py +0 -93
- omlish/dataclasses/impl/hashing.py +0 -86
- omlish/dataclasses/impl/init.py +0 -199
- omlish/dataclasses/impl/main.py +0 -93
- omlish/dataclasses/impl/metaclass.py +0 -235
- omlish/dataclasses/impl/metadata.py +0 -75
- omlish/dataclasses/impl/order.py +0 -57
- omlish/dataclasses/impl/overrides.py +0 -53
- omlish/dataclasses/impl/params.py +0 -128
- omlish/dataclasses/impl/processing.py +0 -24
- omlish/dataclasses/impl/replace.py +0 -40
- omlish/dataclasses/impl/repr.py +0 -66
- omlish/dataclasses/impl/simple.py +0 -50
- omlish/dataclasses/impl/utils.py +0 -167
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,120 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import types
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from .... import check
|
6
|
+
from ...inspect import get_cls_annotations
|
7
|
+
from ...internals import STD_FIELDS_ATTR
|
8
|
+
from ...internals import StdFieldType
|
9
|
+
from ...internals import std_is_classvar
|
10
|
+
from ...internals import std_is_initvar
|
11
|
+
from ...internals import std_is_kw_only
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
def build_std_field(
|
18
|
+
cls: type,
|
19
|
+
a_name: str,
|
20
|
+
a_type: ta.Any,
|
21
|
+
*,
|
22
|
+
default_kw_only: bool,
|
23
|
+
) -> dc.Field:
|
24
|
+
default: ta.Any = getattr(cls, a_name, dc.MISSING)
|
25
|
+
if isinstance(default, dc.Field):
|
26
|
+
f = default
|
27
|
+
else:
|
28
|
+
if isinstance(default, types.MemberDescriptorType):
|
29
|
+
# This is a field in __slots__, so it has no default value.
|
30
|
+
default = dc.MISSING
|
31
|
+
f = dc.field(default=default)
|
32
|
+
|
33
|
+
f.name = a_name
|
34
|
+
f.type = a_type
|
35
|
+
|
36
|
+
# field type
|
37
|
+
|
38
|
+
ft = StdFieldType.INSTANCE
|
39
|
+
if std_is_classvar(cls, f.type):
|
40
|
+
ft = StdFieldType.CLASS_VAR
|
41
|
+
if std_is_initvar(cls, f.type):
|
42
|
+
ft = StdFieldType.INIT_VAR
|
43
|
+
if ft in (StdFieldType.CLASS_VAR, StdFieldType.INIT_VAR):
|
44
|
+
if f.default_factory is not dc.MISSING:
|
45
|
+
raise TypeError(f'field {a_name} cannot have a default factory')
|
46
|
+
f._field_type = ft.value # type: ignore[attr-defined] # noqa
|
47
|
+
|
48
|
+
# kw_only
|
49
|
+
|
50
|
+
if ft in (StdFieldType.INSTANCE, StdFieldType.INIT_VAR):
|
51
|
+
if f.kw_only is dc.MISSING:
|
52
|
+
f.kw_only = default_kw_only
|
53
|
+
else:
|
54
|
+
check.arg(ft is StdFieldType.CLASS_VAR)
|
55
|
+
if f.kw_only is not dc.MISSING:
|
56
|
+
raise TypeError(f'field {a_name} is a ClassVar but specifies kw_only')
|
57
|
+
|
58
|
+
# defaults
|
59
|
+
|
60
|
+
if (
|
61
|
+
ft is StdFieldType.INSTANCE and
|
62
|
+
f.default is not dc.MISSING and
|
63
|
+
f.default.__class__.__hash__ is None
|
64
|
+
):
|
65
|
+
raise ValueError(f'mutable default {type(f.default)} for field {a_name} is not allowed: use default_factory')
|
66
|
+
|
67
|
+
#
|
68
|
+
|
69
|
+
return f
|
70
|
+
|
71
|
+
|
72
|
+
##
|
73
|
+
|
74
|
+
|
75
|
+
def build_cls_std_fields(
|
76
|
+
cls: type,
|
77
|
+
*,
|
78
|
+
kw_only: bool,
|
79
|
+
) -> ta.Mapping[str, dc.Field]:
|
80
|
+
fields: dict[str, dc.Field] = {}
|
81
|
+
|
82
|
+
for b in cls.__mro__[-1:0:-1]:
|
83
|
+
if not (base_fields := getattr(b, STD_FIELDS_ATTR, None)):
|
84
|
+
continue
|
85
|
+
for f in base_fields.values():
|
86
|
+
fields[f.name] = f
|
87
|
+
|
88
|
+
cls_annotations = get_cls_annotations(cls)
|
89
|
+
|
90
|
+
cls_fields: list[dc.Field] = []
|
91
|
+
|
92
|
+
kw_only_seen = False
|
93
|
+
for name, ann in cls_annotations.items():
|
94
|
+
if std_is_kw_only(cls, ann):
|
95
|
+
if kw_only_seen:
|
96
|
+
raise TypeError(f'{name!r} is KW_ONLY, but KW_ONLY has already been specified')
|
97
|
+
kw_only_seen = True
|
98
|
+
kw_only = True
|
99
|
+
|
100
|
+
else:
|
101
|
+
cls_fields.append(build_std_field(
|
102
|
+
cls,
|
103
|
+
name,
|
104
|
+
ann,
|
105
|
+
default_kw_only=kw_only,
|
106
|
+
))
|
107
|
+
|
108
|
+
for f in cls_fields:
|
109
|
+
fields[f.name] = f
|
110
|
+
if isinstance(getattr(cls, f.name, None), dc.Field):
|
111
|
+
if f.default is dc.MISSING:
|
112
|
+
delattr(cls, f.name)
|
113
|
+
else:
|
114
|
+
setattr(cls, f.name, f.default)
|
115
|
+
|
116
|
+
for name, value in cls.__dict__.items():
|
117
|
+
if isinstance(value, dc.Field) and name not in cls_annotations:
|
118
|
+
raise TypeError(f'{name!r} is a field but has no type annotation')
|
119
|
+
|
120
|
+
return fields
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import collections
|
2
|
+
import dataclasses as dc
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from ...specs import CoerceFn
|
6
|
+
from ...specs import ReprFn
|
7
|
+
from ...specs import ValidateFn
|
8
|
+
from .metadata import extra_field_params
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
|
13
|
+
|
14
|
+
def field(
|
15
|
+
default=dc.MISSING,
|
16
|
+
*,
|
17
|
+
default_factory=dc.MISSING,
|
18
|
+
init=True,
|
19
|
+
repr=True, # noqa
|
20
|
+
hash=None, # noqa
|
21
|
+
compare=True,
|
22
|
+
metadata=None,
|
23
|
+
kw_only=dc.MISSING,
|
24
|
+
|
25
|
+
coerce: bool | CoerceFn | None = None,
|
26
|
+
validate: ValidateFn | None = None, # noqa
|
27
|
+
check_type: bool | type | tuple[type | None, ...] | None = None,
|
28
|
+
override: bool | None = None,
|
29
|
+
repr_fn: ReprFn | None = None,
|
30
|
+
repr_priority: int | None = None,
|
31
|
+
) -> ta.Any:
|
32
|
+
efp = extra_field_params(
|
33
|
+
coerce=coerce,
|
34
|
+
validate=validate,
|
35
|
+
check_type=check_type,
|
36
|
+
override=override,
|
37
|
+
repr_fn=repr_fn,
|
38
|
+
repr_priority=repr_priority,
|
39
|
+
)
|
40
|
+
|
41
|
+
md: ta.Any = metadata
|
42
|
+
if md is None:
|
43
|
+
md = efp
|
44
|
+
else:
|
45
|
+
md = collections.ChainMap(efp, md) # type: ignore[arg-type]
|
46
|
+
|
47
|
+
return dc.field(
|
48
|
+
default=default,
|
49
|
+
default_factory=default_factory,
|
50
|
+
init=init,
|
51
|
+
repr=repr,
|
52
|
+
hash=hash,
|
53
|
+
compare=compare,
|
54
|
+
metadata=md,
|
55
|
+
kw_only=kw_only,
|
56
|
+
)
|
@@ -0,0 +1,191 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from .... import check
|
5
|
+
from .... import lang
|
6
|
+
from ...debug import DEBUG
|
7
|
+
from ...internals import StdFieldType
|
8
|
+
from ...internals import std_field_type
|
9
|
+
from ...specs import DefaultFactory
|
10
|
+
from ...specs import FieldSpec
|
11
|
+
from ...specs import FieldType
|
12
|
+
from .metadata import _ExtraFieldParamsMetadata
|
13
|
+
from .metadata import set_field_spec_metadata
|
14
|
+
|
15
|
+
|
16
|
+
##
|
17
|
+
|
18
|
+
|
19
|
+
STD_FIELD_TYPE_BY_SPEC_FIELD_TYPE: ta.Mapping[FieldType, StdFieldType] = {
|
20
|
+
FieldType.INSTANCE: StdFieldType.INSTANCE,
|
21
|
+
FieldType.CLASS_VAR: StdFieldType.CLASS_VAR,
|
22
|
+
FieldType.INIT_VAR: StdFieldType.INIT_VAR,
|
23
|
+
}
|
24
|
+
|
25
|
+
SPEC_FIELD_TYPE_BY_STD_FIELD_TYPE = {v: k for k, v in STD_FIELD_TYPE_BY_SPEC_FIELD_TYPE.items()}
|
26
|
+
|
27
|
+
|
28
|
+
#
|
29
|
+
|
30
|
+
|
31
|
+
def std_to_spec_field_default(
|
32
|
+
*,
|
33
|
+
default: ta.Any,
|
34
|
+
default_factory: ta.Any,
|
35
|
+
) -> lang.Maybe[ta.Any]:
|
36
|
+
if default is not dc.MISSING:
|
37
|
+
check.state(default_factory is dc.MISSING)
|
38
|
+
return lang.just(default)
|
39
|
+
elif default_factory is not dc.MISSING:
|
40
|
+
return lang.just(DefaultFactory(default_factory))
|
41
|
+
else:
|
42
|
+
return lang.empty()
|
43
|
+
|
44
|
+
|
45
|
+
def std_field_to_spec_field_default(f: dc.Field) -> lang.Maybe[ta.Any]:
|
46
|
+
return std_to_spec_field_default(
|
47
|
+
default=f.default,
|
48
|
+
default_factory=f.default_factory,
|
49
|
+
)
|
50
|
+
|
51
|
+
|
52
|
+
class StdDefaults(ta.NamedTuple):
|
53
|
+
default: ta.Any
|
54
|
+
default_factory: ta.Any
|
55
|
+
|
56
|
+
|
57
|
+
def spec_field_default_to_std_defaults(dfl: lang.Maybe[DefaultFactory | ta.Any]) -> StdDefaults:
|
58
|
+
if not dfl.present:
|
59
|
+
return StdDefaults(dc.MISSING, dc.MISSING)
|
60
|
+
elif isinstance(dfv := dfl.must(), DefaultFactory):
|
61
|
+
return StdDefaults(dc.MISSING, dfv.fn)
|
62
|
+
else:
|
63
|
+
return StdDefaults(dfv, dc.MISSING)
|
64
|
+
|
65
|
+
|
66
|
+
#
|
67
|
+
|
68
|
+
|
69
|
+
def std_field_to_field_spec(
|
70
|
+
f: dc.Field,
|
71
|
+
*,
|
72
|
+
ignore_metadata: bool = False,
|
73
|
+
ignore_extra_params: bool = False,
|
74
|
+
set_metadata: bool = False,
|
75
|
+
) -> FieldSpec:
|
76
|
+
if not ignore_metadata:
|
77
|
+
try:
|
78
|
+
fs = f.metadata[FieldSpec]
|
79
|
+
except KeyError:
|
80
|
+
pass
|
81
|
+
else:
|
82
|
+
check_field_spec_against_field(f, fs)
|
83
|
+
return fs
|
84
|
+
|
85
|
+
extra_params = {}
|
86
|
+
if not ignore_extra_params:
|
87
|
+
extra_params.update(f.metadata.get(_ExtraFieldParamsMetadata, {}))
|
88
|
+
|
89
|
+
fs = FieldSpec(
|
90
|
+
name=check.non_empty_str(f.name),
|
91
|
+
annotation=check.is_not(f.type, dc.MISSING),
|
92
|
+
|
93
|
+
default=std_field_to_spec_field_default(f),
|
94
|
+
|
95
|
+
init=check.isinstance(f.init, bool) if DEBUG else f.init,
|
96
|
+
repr=check.isinstance(f.repr, bool) if DEBUG else f.repr,
|
97
|
+
hash=check.isinstance(f.hash, (bool, None)) if DEBUG else f.hash,
|
98
|
+
compare=check.isinstance(f.compare, bool) if DEBUG else f.compare,
|
99
|
+
metadata=f.metadata,
|
100
|
+
kw_only=None if f.kw_only is dc.MISSING else (check.isinstance(f.kw_only, bool) if DEBUG else f.kw_only),
|
101
|
+
|
102
|
+
**lang.opt_kw(
|
103
|
+
coerce=extra_params.get('coerce'),
|
104
|
+
validate=extra_params.get('validate'),
|
105
|
+
check_type=extra_params.get('check_type'),
|
106
|
+
override=extra_params.get('override'),
|
107
|
+
repr_fn=extra_params.get('repr_fn'),
|
108
|
+
repr_priority=extra_params.get('repr_priority'),
|
109
|
+
),
|
110
|
+
|
111
|
+
field_type=SPEC_FIELD_TYPE_BY_STD_FIELD_TYPE[std_field_type(f)],
|
112
|
+
)
|
113
|
+
|
114
|
+
if set_metadata:
|
115
|
+
set_field_spec_metadata(f, fs)
|
116
|
+
|
117
|
+
return fs
|
118
|
+
|
119
|
+
|
120
|
+
##
|
121
|
+
|
122
|
+
|
123
|
+
def field_spec_to_std_field(fs: FieldSpec) -> dc.Field:
|
124
|
+
sdf = spec_field_default_to_std_defaults(fs.default)
|
125
|
+
|
126
|
+
f = dc.Field(
|
127
|
+
default=sdf.default,
|
128
|
+
default_factory=sdf.default_factory,
|
129
|
+
|
130
|
+
init=fs.init,
|
131
|
+
repr=fs.repr,
|
132
|
+
hash=fs.hash,
|
133
|
+
compare=fs.compare,
|
134
|
+
**lang.opt_kw(metadata=fs.metadata),
|
135
|
+
kw_only=dc.MISSING if fs.kw_only is None else fs.kw_only, # type: ignore[arg-type]
|
136
|
+
)
|
137
|
+
|
138
|
+
f.name = fs.name
|
139
|
+
f.type = fs.annotation
|
140
|
+
|
141
|
+
f._field_type = STD_FIELD_TYPE_BY_SPEC_FIELD_TYPE[fs.field_type].value # type: ignore[attr-defined] # noqa
|
142
|
+
|
143
|
+
set_field_spec_metadata(f, fs)
|
144
|
+
|
145
|
+
return f
|
146
|
+
|
147
|
+
|
148
|
+
##
|
149
|
+
|
150
|
+
|
151
|
+
def check_field_spec_against_field(f: dc.Field, fs: FieldSpec) -> None:
|
152
|
+
f_dct = {
|
153
|
+
'name': f.name,
|
154
|
+
'type': f.type,
|
155
|
+
|
156
|
+
'default': f.default,
|
157
|
+
'default_factory': f.default_factory,
|
158
|
+
|
159
|
+
'repr': f.repr,
|
160
|
+
'hash': f.hash,
|
161
|
+
'init': f.init,
|
162
|
+
'compare': f.compare,
|
163
|
+
# f.metadata,
|
164
|
+
'kw_only': f.kw_only if f.kw_only is not dc.MISSING else None,
|
165
|
+
|
166
|
+
'std_field_type': f._field_type, # type: ignore[attr-defined] # noqa
|
167
|
+
}
|
168
|
+
|
169
|
+
fs_dct = {
|
170
|
+
'name': fs.name,
|
171
|
+
'type': fs.annotation,
|
172
|
+
|
173
|
+
**spec_field_default_to_std_defaults(fs.default)._asdict(),
|
174
|
+
|
175
|
+
'repr': fs.repr,
|
176
|
+
'hash': fs.hash,
|
177
|
+
'init': fs.init,
|
178
|
+
'compare': fs.compare,
|
179
|
+
# fs.metadata,
|
180
|
+
'kw_only': fs.kw_only,
|
181
|
+
|
182
|
+
'std_field_type': STD_FIELD_TYPE_BY_SPEC_FIELD_TYPE[fs.field_type].value,
|
183
|
+
}
|
184
|
+
|
185
|
+
if f_dct != fs_dct:
|
186
|
+
diff_dct = {
|
187
|
+
k: (f_v, fs_v)
|
188
|
+
for k, f_v in f_dct.items()
|
189
|
+
if (fs_v := fs_dct[k]) != f_v
|
190
|
+
}
|
191
|
+
raise RuntimeError(f'Field/FieldSpec mismatch: {diff_dct!r}')
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from .... import check
|
5
|
+
from .... import lang
|
6
|
+
from ...debug import DEBUG
|
7
|
+
from ...specs import FieldSpec
|
8
|
+
from ...utils import chain_mapping_proxy
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
|
13
|
+
|
14
|
+
class _ExtraFieldParamsMetadata(lang.Marker):
|
15
|
+
pass
|
16
|
+
|
17
|
+
|
18
|
+
def extra_field_params(**kwargs: ta.Any) -> ta.Mapping[ta.Any, ta.Any]:
|
19
|
+
return {_ExtraFieldParamsMetadata: kwargs}
|
20
|
+
|
21
|
+
|
22
|
+
##
|
23
|
+
|
24
|
+
|
25
|
+
def get_field_spec(f: dc.Field) -> FieldSpec | None:
|
26
|
+
try:
|
27
|
+
fs = f.metadata[FieldSpec]
|
28
|
+
except KeyError:
|
29
|
+
return None
|
30
|
+
|
31
|
+
return check.isinstance(fs, FieldSpec)
|
32
|
+
|
33
|
+
|
34
|
+
##
|
35
|
+
|
36
|
+
|
37
|
+
def set_field_metadata(
|
38
|
+
f: dc.Field,
|
39
|
+
metadata: ta.Mapping[ta.Any, ta.Any],
|
40
|
+
) -> dc.Field:
|
41
|
+
if DEBUG:
|
42
|
+
check.isinstance(f, dc.Field)
|
43
|
+
|
44
|
+
md: ta.Any = f.metadata
|
45
|
+
|
46
|
+
mdu: dict = {}
|
47
|
+
for k, v in metadata.items():
|
48
|
+
if md is None or md.get(k) != v:
|
49
|
+
mdu[k] = v # noqa
|
50
|
+
if not mdu:
|
51
|
+
return f
|
52
|
+
|
53
|
+
if md is None:
|
54
|
+
ms = [mdu]
|
55
|
+
else:
|
56
|
+
ms = [mdu, md]
|
57
|
+
|
58
|
+
f.metadata = chain_mapping_proxy(*ms)
|
59
|
+
return f
|
60
|
+
|
61
|
+
|
62
|
+
#
|
63
|
+
|
64
|
+
|
65
|
+
def set_field_spec_metadata(
|
66
|
+
f: dc.Field,
|
67
|
+
fs: FieldSpec,
|
68
|
+
) -> None:
|
69
|
+
set_field_metadata(
|
70
|
+
f,
|
71
|
+
{
|
72
|
+
FieldSpec: fs,
|
73
|
+
_ExtraFieldParamsMetadata: {},
|
74
|
+
},
|
75
|
+
)
|
76
|
+
|
77
|
+
|
78
|
+
##
|
79
|
+
|
80
|
+
|
81
|
+
def update_extra_field_params(
|
82
|
+
f: dc.Field,
|
83
|
+
*,
|
84
|
+
unless_non_default: bool = False,
|
85
|
+
**kwargs: ta.Any,
|
86
|
+
) -> dc.Field:
|
87
|
+
fe = f.metadata.get(_ExtraFieldParamsMetadata, {})
|
88
|
+
return set_field_metadata(f, {
|
89
|
+
_ExtraFieldParamsMetadata: {
|
90
|
+
**(fe if not unless_non_default else {}),
|
91
|
+
**kwargs,
|
92
|
+
**(fe if unless_non_default else {}),
|
93
|
+
},
|
94
|
+
})
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import abc
|
2
|
+
|
3
|
+
from ..processing.base import Processor
|
4
|
+
from ..processing.priority import ProcessorPriority
|
5
|
+
from ..processing.registry import register_processor_type
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
|
10
|
+
|
11
|
+
@register_processor_type(priority=ProcessorPriority.POST_SLOTS)
|
12
|
+
class UpdateAbstractMethodsProcessor(Processor):
|
13
|
+
def process(self, cls: type) -> type:
|
14
|
+
abc.update_abstractmethods(cls) # noqa
|
15
|
+
return cls
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from ..generation.base import Generator
|
5
|
+
from ..generation.base import Plan
|
6
|
+
from ..generation.base import PlanResult
|
7
|
+
from ..generation.idents import CLS_IDENT
|
8
|
+
from ..generation.ops import AddMethodOp
|
9
|
+
from ..generation.ops import Op
|
10
|
+
from ..generation.registry import register_generator_type
|
11
|
+
from ..generation.utils import build_attr_kwargs_body_src_lines
|
12
|
+
from ..processing.base import ProcessingContext
|
13
|
+
from ..specs import FieldType
|
14
|
+
|
15
|
+
|
16
|
+
##
|
17
|
+
|
18
|
+
|
19
|
+
@dc.dataclass(frozen=True)
|
20
|
+
class CopyPlan(Plan):
|
21
|
+
fields: tuple[str, ...]
|
22
|
+
|
23
|
+
|
24
|
+
@register_generator_type(CopyPlan)
|
25
|
+
class CopyGenerator(Generator[CopyPlan]):
|
26
|
+
def plan(self, ctx: ProcessingContext) -> PlanResult[CopyPlan] | None:
|
27
|
+
if '__copy__' in ctx.cls.__dict__:
|
28
|
+
return None
|
29
|
+
|
30
|
+
return PlanResult(CopyPlan(
|
31
|
+
tuple(f.name for f in ctx.cs.fields if f.field_type is not FieldType.CLASS_VAR),
|
32
|
+
))
|
33
|
+
|
34
|
+
def generate(self, pl: CopyPlan) -> ta.Iterable[Op]:
|
35
|
+
return_lines: list[str]
|
36
|
+
if pl.fields:
|
37
|
+
return_lines = [
|
38
|
+
f' return {CLS_IDENT}( # noqa',
|
39
|
+
*build_attr_kwargs_body_src_lines(
|
40
|
+
'self',
|
41
|
+
*pl.fields,
|
42
|
+
prefix=' ',
|
43
|
+
),
|
44
|
+
f' )',
|
45
|
+
]
|
46
|
+
else:
|
47
|
+
return_lines = [
|
48
|
+
f' return {CLS_IDENT}() # noqa',
|
49
|
+
]
|
50
|
+
|
51
|
+
lines = [
|
52
|
+
f'def __copy__(self):',
|
53
|
+
f' if self.__class__ is not {CLS_IDENT}:',
|
54
|
+
f' raise TypeError(self)',
|
55
|
+
*return_lines,
|
56
|
+
]
|
57
|
+
|
58
|
+
return [
|
59
|
+
AddMethodOp(
|
60
|
+
'__copy__',
|
61
|
+
'\n'.join(lines),
|
62
|
+
),
|
63
|
+
]
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import inspect
|
2
|
+
|
3
|
+
from ..processing.base import ProcessingContext
|
4
|
+
from ..processing.base import Processor
|
5
|
+
from ..processing.priority import ProcessorPriority
|
6
|
+
from ..processing.registry import register_processor_type
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
|
11
|
+
|
12
|
+
def _build_cls_doc(cls: type) -> str:
|
13
|
+
try:
|
14
|
+
text_sig = str(inspect.signature(cls)).replace(' -> None', '')
|
15
|
+
except (TypeError, ValueError):
|
16
|
+
text_sig = ''
|
17
|
+
return cls.__name__ + text_sig
|
18
|
+
|
19
|
+
|
20
|
+
class _LazyClsDocDescriptor:
|
21
|
+
def __get__(self, instance, owner):
|
22
|
+
if instance is not None:
|
23
|
+
owner = instance.__class__
|
24
|
+
if not owner:
|
25
|
+
raise RuntimeError
|
26
|
+
doc = _build_cls_doc(owner)
|
27
|
+
owner.__doc__ = doc
|
28
|
+
return doc
|
29
|
+
|
30
|
+
|
31
|
+
@register_processor_type(priority=ProcessorPriority.POST_GENERATION)
|
32
|
+
class DocProcessor(Processor):
|
33
|
+
def __init__(
|
34
|
+
self,
|
35
|
+
ctx: ProcessingContext,
|
36
|
+
*,
|
37
|
+
lazy: bool = True,
|
38
|
+
) -> None:
|
39
|
+
super().__init__(ctx)
|
40
|
+
|
41
|
+
self._lazy = lazy
|
42
|
+
|
43
|
+
def process(self, cls: type) -> type:
|
44
|
+
# FIXME: doesn't update doc in subclasses lol, as per stdlib
|
45
|
+
if getattr(cls, '__doc__'):
|
46
|
+
return cls
|
47
|
+
|
48
|
+
if self._lazy:
|
49
|
+
cls.__doc__ = _LazyClsDocDescriptor() # type: ignore
|
50
|
+
else:
|
51
|
+
cls.__doc__ = _build_cls_doc(cls)
|
52
|
+
|
53
|
+
return cls
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from ..generation.base import Generator
|
5
|
+
from ..generation.base import Plan
|
6
|
+
from ..generation.base import PlanResult
|
7
|
+
from ..generation.ops import AddMethodOp
|
8
|
+
from ..generation.ops import Op
|
9
|
+
from ..generation.registry import register_generator_type
|
10
|
+
from ..processing.base import ProcessingContext
|
11
|
+
from .fields import InstanceFields
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
@dc.dataclass(frozen=True)
|
18
|
+
class EqPlan(Plan):
|
19
|
+
fields: tuple[str, ...]
|
20
|
+
|
21
|
+
|
22
|
+
@register_generator_type(EqPlan)
|
23
|
+
class EqGenerator(Generator[EqPlan]):
|
24
|
+
def plan(self, ctx: ProcessingContext) -> PlanResult[EqPlan] | None:
|
25
|
+
if not ctx.cs.eq or '__eq__' in ctx.cls.__dict__:
|
26
|
+
return None
|
27
|
+
|
28
|
+
return PlanResult(EqPlan(
|
29
|
+
tuple(f.name for f in ctx[InstanceFields] if f.compare),
|
30
|
+
))
|
31
|
+
|
32
|
+
def generate(self, pl: EqPlan) -> ta.Iterable[Op]:
|
33
|
+
ret_lines: list[str]
|
34
|
+
if pl.fields:
|
35
|
+
ret_lines = [
|
36
|
+
f' return (',
|
37
|
+
*[
|
38
|
+
f' self.{a} == other.{a}{" and" if i < len(pl.fields) - 1 else ""}'
|
39
|
+
for i, a in enumerate(pl.fields)
|
40
|
+
],
|
41
|
+
f' )',
|
42
|
+
]
|
43
|
+
else:
|
44
|
+
ret_lines = [
|
45
|
+
f' return True',
|
46
|
+
]
|
47
|
+
|
48
|
+
return [
|
49
|
+
AddMethodOp(
|
50
|
+
'__eq__',
|
51
|
+
'\n'.join([
|
52
|
+
f'def __eq__(self, other):',
|
53
|
+
f' if self is other:',
|
54
|
+
f' return True',
|
55
|
+
f' if self.__class__ is not other.__class__:',
|
56
|
+
f' return NotImplemented',
|
57
|
+
*ret_lines,
|
58
|
+
]),
|
59
|
+
),
|
60
|
+
]
|