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,83 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- Configurable? class Config crtc?
|
4
|
+
- all config on specs? prob not desirable purity, prob want ~some~ global config if only defaults
|
5
|
+
- ProcessorConfigSpecProviderFactoryThingy?
|
6
|
+
- FIXME: for now these are processor ctor kwargs that are never overridden
|
7
|
+
"""
|
8
|
+
import typing as ta
|
9
|
+
|
10
|
+
from ... import check
|
11
|
+
from ... import lang
|
12
|
+
from ..specs import ClassSpec
|
13
|
+
|
14
|
+
|
15
|
+
T = ta.TypeVar('T')
|
16
|
+
|
17
|
+
|
18
|
+
##
|
19
|
+
|
20
|
+
|
21
|
+
ProcessingContextItemFactory: ta.TypeAlias = ta.Callable[['ProcessingContext'], ta.Any]
|
22
|
+
|
23
|
+
|
24
|
+
class ProcessingContext:
|
25
|
+
def __init__(
|
26
|
+
self,
|
27
|
+
cls: type,
|
28
|
+
cs: ClassSpec,
|
29
|
+
item_factories: ta.Mapping[type, ProcessingContextItemFactory],
|
30
|
+
*,
|
31
|
+
options: ta.Sequence[ta.Any] | None = None,
|
32
|
+
) -> None:
|
33
|
+
super().__init__()
|
34
|
+
|
35
|
+
self._cls = cls
|
36
|
+
self._cs = cs
|
37
|
+
self._item_factories = item_factories
|
38
|
+
|
39
|
+
options_dct: dict = {}
|
40
|
+
for o in options or ():
|
41
|
+
check.not_in(type(o), options_dct)
|
42
|
+
options_dct[type(o)] = o
|
43
|
+
self._options_dct = options_dct
|
44
|
+
|
45
|
+
self._items: dict = {}
|
46
|
+
|
47
|
+
@property
|
48
|
+
def cls(self) -> type:
|
49
|
+
return self._cls
|
50
|
+
|
51
|
+
@property
|
52
|
+
def cs(self) -> ClassSpec:
|
53
|
+
return self._cs
|
54
|
+
|
55
|
+
def __getitem__(self, ty: type[T]) -> T:
|
56
|
+
try:
|
57
|
+
return self._items[ty]
|
58
|
+
except KeyError:
|
59
|
+
pass
|
60
|
+
|
61
|
+
fac = self._item_factories[ty]
|
62
|
+
ret = fac(self)
|
63
|
+
self._items[ty] = ret
|
64
|
+
return ret
|
65
|
+
|
66
|
+
def option(self, ty: type[T]) -> T | None:
|
67
|
+
return self._options_dct.get(ty)
|
68
|
+
|
69
|
+
|
70
|
+
##
|
71
|
+
|
72
|
+
|
73
|
+
class Processor(lang.Abstract):
|
74
|
+
def __init__(self, ctx: ProcessingContext) -> None:
|
75
|
+
super().__init__()
|
76
|
+
|
77
|
+
self._ctx = ctx
|
78
|
+
|
79
|
+
def check(self) -> None:
|
80
|
+
pass
|
81
|
+
|
82
|
+
def process(self, cls: type) -> type:
|
83
|
+
return cls
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from .. import concerns as _concerns # noqa # imported for registration
|
4
|
+
from ..generation import processor as gp
|
5
|
+
from ..specs import ClassSpec
|
6
|
+
from .base import ProcessingContext
|
7
|
+
from .base import Processor
|
8
|
+
from .registry import all_processing_context_item_factories
|
9
|
+
from .registry import ordered_processor_types
|
10
|
+
|
11
|
+
|
12
|
+
##
|
13
|
+
|
14
|
+
|
15
|
+
def drive_cls_processing(
|
16
|
+
cls: type,
|
17
|
+
cs: ClassSpec,
|
18
|
+
*,
|
19
|
+
plan_only: bool = False,
|
20
|
+
verbose: bool = False,
|
21
|
+
) -> type:
|
22
|
+
options: list[ta.Any] = []
|
23
|
+
if plan_only:
|
24
|
+
options.append(gp.PlanOnly(True))
|
25
|
+
if verbose:
|
26
|
+
options.append(gp.Verbose(True))
|
27
|
+
|
28
|
+
#
|
29
|
+
|
30
|
+
ctx = ProcessingContext(
|
31
|
+
cls,
|
32
|
+
cs,
|
33
|
+
all_processing_context_item_factories(),
|
34
|
+
options=options,
|
35
|
+
)
|
36
|
+
|
37
|
+
processors: list[Processor] = [
|
38
|
+
proc_cls(ctx)
|
39
|
+
for proc_cls in ordered_processor_types()
|
40
|
+
]
|
41
|
+
|
42
|
+
for p in processors:
|
43
|
+
p.check()
|
44
|
+
|
45
|
+
ret = cls
|
46
|
+
for p in processors:
|
47
|
+
ret = p.process(ret)
|
48
|
+
|
49
|
+
return ret
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import enum
|
2
|
+
|
3
|
+
|
4
|
+
class ProcessorPriority(enum.IntEnum):
|
5
|
+
BOOTSTRAP = enum.auto()
|
6
|
+
|
7
|
+
PRE_GENERATION = enum.auto()
|
8
|
+
GENERATION = enum.auto()
|
9
|
+
POST_GENERATION = enum.auto()
|
10
|
+
|
11
|
+
PRE_SLOTS = enum.auto()
|
12
|
+
SLOTS = enum.auto()
|
13
|
+
POST_SLOTS = enum.auto()
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from ... import check
|
5
|
+
from ... import lang
|
6
|
+
from ..utils import SealableRegistry
|
7
|
+
from .base import ProcessingContextItemFactory
|
8
|
+
from .base import Processor
|
9
|
+
from .priority import ProcessorPriority
|
10
|
+
|
11
|
+
|
12
|
+
ProcessorT = ta.TypeVar('ProcessorT', bound=Processor)
|
13
|
+
|
14
|
+
|
15
|
+
##
|
16
|
+
|
17
|
+
|
18
|
+
_PROCESSING_CONTEXT_ITEM_FACTORIES: SealableRegistry[type, ProcessingContextItemFactory] = SealableRegistry()
|
19
|
+
|
20
|
+
|
21
|
+
def register_processing_context_item_factory(i_ty):
|
22
|
+
def inner(fn):
|
23
|
+
_PROCESSING_CONTEXT_ITEM_FACTORIES[i_ty] = fn
|
24
|
+
return fn
|
25
|
+
|
26
|
+
return inner
|
27
|
+
|
28
|
+
|
29
|
+
@lang.cached_function
|
30
|
+
def processing_context_item_factory_for(i_ty: type) -> ProcessingContextItemFactory:
|
31
|
+
return _PROCESSING_CONTEXT_ITEM_FACTORIES[i_ty]
|
32
|
+
|
33
|
+
|
34
|
+
@lang.cached_function
|
35
|
+
def all_processing_context_item_factories() -> ta.Mapping[type, ProcessingContextItemFactory]:
|
36
|
+
return dict(_PROCESSING_CONTEXT_ITEM_FACTORIES.items())
|
37
|
+
|
38
|
+
|
39
|
+
##
|
40
|
+
|
41
|
+
|
42
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
43
|
+
class ProcessorTypeRegistration:
|
44
|
+
priority: ProcessorPriority
|
45
|
+
|
46
|
+
|
47
|
+
_PROCESSOR_TYPES: SealableRegistry[type[Processor], ProcessorTypeRegistration] = SealableRegistry()
|
48
|
+
|
49
|
+
|
50
|
+
def register_processor_type(
|
51
|
+
*,
|
52
|
+
priority: ProcessorPriority,
|
53
|
+
**kwargs: ta.Any,
|
54
|
+
) -> ta.Callable[[type[ProcessorT]], type[ProcessorT]]:
|
55
|
+
reg = ProcessorTypeRegistration(
|
56
|
+
priority=priority,
|
57
|
+
**kwargs,
|
58
|
+
)
|
59
|
+
|
60
|
+
def inner(ty):
|
61
|
+
check.issubclass(ty, Processor)
|
62
|
+
_PROCESSOR_TYPES[ty] = reg
|
63
|
+
return ty
|
64
|
+
|
65
|
+
return inner
|
66
|
+
|
67
|
+
|
68
|
+
@lang.cached_function
|
69
|
+
def all_processor_types() -> ta.Mapping[type, ProcessorTypeRegistration]:
|
70
|
+
return dict(_PROCESSOR_TYPES.items())
|
71
|
+
|
72
|
+
|
73
|
+
@lang.cached_function
|
74
|
+
def ordered_processor_types() -> ta.Sequence[type]:
|
75
|
+
return [
|
76
|
+
t
|
77
|
+
for t, r in sorted(
|
78
|
+
all_processor_types().items(),
|
79
|
+
key=lambda t_r: t_r[1].priority,
|
80
|
+
)
|
81
|
+
]
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from .. import lang
|
5
|
+
from .api.classes.conversion import std_params_to_class_spec
|
6
|
+
from .api.classes.params import get_class_spec
|
7
|
+
from .api.fields.conversion import std_field_to_field_spec
|
8
|
+
from .concerns.fields import InitFields
|
9
|
+
from .concerns.fields import calc_init_fields
|
10
|
+
from .inspect import FieldsInspection
|
11
|
+
from .inspect import inspect_fields
|
12
|
+
from .internals import STD_PARAMS_ATTR
|
13
|
+
from .internals import StdFieldType
|
14
|
+
from .internals import std_field_type
|
15
|
+
from .specs import ClassSpec
|
16
|
+
|
17
|
+
|
18
|
+
##
|
19
|
+
|
20
|
+
|
21
|
+
class ClassReflection:
|
22
|
+
def __init__(self, cls: type) -> None:
|
23
|
+
super().__init__()
|
24
|
+
|
25
|
+
self._cls = cls
|
26
|
+
|
27
|
+
@property
|
28
|
+
def cls(self) -> type:
|
29
|
+
return self._cls
|
30
|
+
|
31
|
+
@lang.cached_property
|
32
|
+
def spec(self) -> ClassSpec:
|
33
|
+
if (cs := get_class_spec(self._cls)) is not None:
|
34
|
+
return cs
|
35
|
+
|
36
|
+
try:
|
37
|
+
p = getattr(self._cls, STD_PARAMS_ATTR)
|
38
|
+
except AttributeError:
|
39
|
+
raise TypeError(self._cls) from None
|
40
|
+
|
41
|
+
# This class was not constructed as one of our dataclasses, so surface no additional functionality even if
|
42
|
+
# present as extra_params or such.
|
43
|
+
fsl = [
|
44
|
+
std_field_to_field_spec(
|
45
|
+
f,
|
46
|
+
ignore_metadata=True,
|
47
|
+
ignore_extra_params=True,
|
48
|
+
)
|
49
|
+
for f in dc.fields(self._cls) # noqa
|
50
|
+
]
|
51
|
+
|
52
|
+
cs = std_params_to_class_spec(
|
53
|
+
p,
|
54
|
+
fsl,
|
55
|
+
)
|
56
|
+
|
57
|
+
return cs
|
58
|
+
|
59
|
+
@lang.cached_property
|
60
|
+
def fields_inspection(self) -> FieldsInspection:
|
61
|
+
return inspect_fields(self._cls)
|
62
|
+
|
63
|
+
@lang.cached_property
|
64
|
+
def init_fields(self) -> InitFields:
|
65
|
+
return calc_init_fields(
|
66
|
+
self.spec.fields,
|
67
|
+
reorder=self.spec.reorder,
|
68
|
+
class_kw_only=self.spec.kw_only,
|
69
|
+
)
|
70
|
+
|
71
|
+
@lang.cached_property
|
72
|
+
def fields(self) -> ta.Mapping[str, dc.Field]:
|
73
|
+
return {f.name: f for f in dc.fields(self._cls)} # noqa
|
74
|
+
|
75
|
+
@lang.cached_property
|
76
|
+
def instance_fields(self) -> ta.Sequence[dc.Field]:
|
77
|
+
return [f for f in self.fields.values() if std_field_type(f) is StdFieldType.INSTANCE]
|
78
|
+
|
79
|
+
|
80
|
+
def reflect(cls: type) -> ClassReflection:
|
81
|
+
return ClassReflection(cls)
|
@@ -0,0 +1,224 @@
|
|
1
|
+
"""Should be kept pure. No references to dc std, no references to impl detail."""
|
2
|
+
import dataclasses as dc
|
3
|
+
import enum
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from .. import check
|
7
|
+
from .. import lang
|
8
|
+
|
9
|
+
|
10
|
+
T = ta.TypeVar('T')
|
11
|
+
|
12
|
+
|
13
|
+
##
|
14
|
+
|
15
|
+
|
16
|
+
CoerceFn: ta.TypeAlias = ta.Callable[[ta.Any], ta.Any]
|
17
|
+
ValidateFn: ta.TypeAlias = ta.Callable[[ta.Any], bool]
|
18
|
+
ReprFn: ta.TypeAlias = ta.Callable[[ta.Any], str | None]
|
19
|
+
|
20
|
+
InitFn: ta.TypeAlias = ta.Callable[[ta.Any], None]
|
21
|
+
ClassValidateFn: ta.TypeAlias = ta.Callable[..., bool]
|
22
|
+
|
23
|
+
|
24
|
+
class DefaultFactory(ta.NamedTuple):
|
25
|
+
fn: ta.Callable[..., ta.Any]
|
26
|
+
|
27
|
+
|
28
|
+
##
|
29
|
+
|
30
|
+
|
31
|
+
class FieldType(enum.StrEnum):
|
32
|
+
INSTANCE = enum.auto()
|
33
|
+
CLASS_VAR = enum.auto()
|
34
|
+
INIT_VAR = enum.auto()
|
35
|
+
|
36
|
+
__repr__ = lang.enum_name_repr
|
37
|
+
|
38
|
+
|
39
|
+
@dc.dataclass(frozen=True, kw_only=True, eq=False)
|
40
|
+
class FieldSpec(lang.Final):
|
41
|
+
name: str
|
42
|
+
annotation: ta.Any
|
43
|
+
|
44
|
+
default: lang.Maybe[DefaultFactory | ta.Any] = lang.empty()
|
45
|
+
|
46
|
+
##
|
47
|
+
# std
|
48
|
+
|
49
|
+
init: bool = True
|
50
|
+
repr: bool = True
|
51
|
+
|
52
|
+
# This can be a bool or None. If true, this field is included in the generated __hash__() method. If false, this
|
53
|
+
# field is excluded from the generated __hash__(). If None (the default), use the value of compare: this would
|
54
|
+
# normally be the expected behavior, since a field should be included in the hash if it's used for comparisons.
|
55
|
+
# Setting this value to anything other than None is discouraged.
|
56
|
+
hash: bool | None = None # FIXME: type?
|
57
|
+
|
58
|
+
compare: bool = True
|
59
|
+
|
60
|
+
# This can be a mapping or None. None is treated as an empty dict. This value is wrapped in MappingProxyType() to
|
61
|
+
# make it read-only, and exposed on the Field object. It is not used at all by Data Classes, and is provided as a
|
62
|
+
# third-party extension mechanism. Multiple third-parties can each have their own key, to use as a namespace in the
|
63
|
+
# metadata.
|
64
|
+
metadata: ta.Mapping[ta.Any, ta.Any] | None = None
|
65
|
+
|
66
|
+
kw_only: bool | None = None
|
67
|
+
|
68
|
+
# doc: ta.Any = None
|
69
|
+
|
70
|
+
##
|
71
|
+
# ext
|
72
|
+
|
73
|
+
# derive: ta.Callable[..., ta.Any] | None = None # NYI in core
|
74
|
+
|
75
|
+
coerce: bool | CoerceFn | None = None
|
76
|
+
validate: ValidateFn | None = None
|
77
|
+
check_type: bool | type | tuple[type | None, ...] | None = None
|
78
|
+
|
79
|
+
override: bool = False
|
80
|
+
|
81
|
+
repr_fn: ReprFn | None = None
|
82
|
+
repr_priority: int | None = None
|
83
|
+
|
84
|
+
# frozen: bool | None = None # NYI in core
|
85
|
+
|
86
|
+
##
|
87
|
+
# derived
|
88
|
+
|
89
|
+
field_type: FieldType = FieldType.INSTANCE
|
90
|
+
|
91
|
+
##
|
92
|
+
# validate
|
93
|
+
|
94
|
+
def __post_init__(self) -> None:
|
95
|
+
check.non_empty_str(self.name)
|
96
|
+
check.arg(self.name.isidentifier())
|
97
|
+
|
98
|
+
if self.field_type in (FieldType.CLASS_VAR, FieldType.INIT_VAR):
|
99
|
+
if isinstance(self.default.or_else(None), DefaultFactory):
|
100
|
+
raise TypeError(f'field {self.name} cannot have a default factory')
|
101
|
+
|
102
|
+
if self.field_type is FieldType.CLASS_VAR:
|
103
|
+
if self.kw_only is not None:
|
104
|
+
raise TypeError(f'field {self.name} is a ClassVar but specifies kw_only')
|
105
|
+
check.none(self.coerce)
|
106
|
+
check.none(self.validate)
|
107
|
+
check.in_(self.check_type, (None, False))
|
108
|
+
|
109
|
+
if (
|
110
|
+
self.field_type is FieldType.INSTANCE and
|
111
|
+
self.default.present and
|
112
|
+
not isinstance(dfv := self.default.must(), DefaultFactory) and
|
113
|
+
dfv.__class__.__hash__ is None # noqa
|
114
|
+
):
|
115
|
+
raise ValueError(
|
116
|
+
f'mutable default {type(dfv)} for field {self.name} '
|
117
|
+
f'is not allowed: use default_factory',
|
118
|
+
)
|
119
|
+
|
120
|
+
|
121
|
+
##
|
122
|
+
|
123
|
+
|
124
|
+
@dc.dataclass(frozen=True, kw_only=True, eq=False)
|
125
|
+
class ClassSpec(lang.Final):
|
126
|
+
##
|
127
|
+
# fields
|
128
|
+
|
129
|
+
fields: ta.Sequence[FieldSpec]
|
130
|
+
|
131
|
+
@property
|
132
|
+
def fields_by_name(self) -> ta.Mapping[str, FieldSpec]:
|
133
|
+
return self._fields_by_name
|
134
|
+
|
135
|
+
_fields_by_name: ta.ClassVar[ta.Mapping[str, FieldSpec]]
|
136
|
+
|
137
|
+
##
|
138
|
+
# std
|
139
|
+
|
140
|
+
init: bool = True
|
141
|
+
repr: bool = True
|
142
|
+
eq: bool = True
|
143
|
+
order: bool = False
|
144
|
+
unsafe_hash: bool = False
|
145
|
+
frozen: bool = False
|
146
|
+
|
147
|
+
match_args: bool = True
|
148
|
+
kw_only: bool = False
|
149
|
+
slots: bool = False
|
150
|
+
weakref_slot: bool = False
|
151
|
+
|
152
|
+
##
|
153
|
+
# ext
|
154
|
+
|
155
|
+
metadata: ta.Sequence[ta.Any] | None = None
|
156
|
+
|
157
|
+
@property
|
158
|
+
def metadata_by_type(self) -> ta.Mapping[type, ta.Sequence[ta.Any]]:
|
159
|
+
return self._metadata_by_type
|
160
|
+
|
161
|
+
_metadata_by_type: ta.ClassVar[ta.Mapping[type, ta.Sequence[ta.Any]]]
|
162
|
+
|
163
|
+
@ta.overload
|
164
|
+
def get_last_metadata(self, ty: type[T], default: T) -> T:
|
165
|
+
...
|
166
|
+
|
167
|
+
@ta.overload
|
168
|
+
def get_last_metadata(self, ty: type[T], default: None = None) -> T | None:
|
169
|
+
...
|
170
|
+
|
171
|
+
def get_last_metadata(self, ty, default=None):
|
172
|
+
try:
|
173
|
+
mdl = self._metadata_by_type[ty]
|
174
|
+
except KeyError:
|
175
|
+
return default
|
176
|
+
if not mdl:
|
177
|
+
return default
|
178
|
+
return mdl[-1]
|
179
|
+
|
180
|
+
#
|
181
|
+
|
182
|
+
reorder: bool = False
|
183
|
+
cache_hash: bool = False
|
184
|
+
generic_init: bool = False
|
185
|
+
override: bool = False
|
186
|
+
repr_id: bool = False
|
187
|
+
|
188
|
+
##
|
189
|
+
# callbacks
|
190
|
+
|
191
|
+
init_fns: ta.Sequence[InitFn | property] | None = None
|
192
|
+
|
193
|
+
@dc.dataclass(frozen=True)
|
194
|
+
class ValidateFnWithParams:
|
195
|
+
fn: ClassValidateFn
|
196
|
+
params: ta.Sequence[str]
|
197
|
+
|
198
|
+
def __post_init__(self) -> None:
|
199
|
+
check.not_isinstance(self.params, str)
|
200
|
+
|
201
|
+
validate_fns: ta.Sequence[ValidateFnWithParams] | None = None
|
202
|
+
|
203
|
+
##
|
204
|
+
# validate
|
205
|
+
|
206
|
+
def __post_init__(self) -> None:
|
207
|
+
fields_by_name: dict[str, FieldSpec] = {}
|
208
|
+
for f in self.fields:
|
209
|
+
check.not_in(f.name, fields_by_name)
|
210
|
+
fields_by_name[f.name] = f
|
211
|
+
object.__setattr__(self, '_fields_by_name', fields_by_name)
|
212
|
+
|
213
|
+
metadata_by_type: dict[type, list[ta.Any]] = {}
|
214
|
+
for md in self.metadata or ():
|
215
|
+
mdt = type(md)
|
216
|
+
try:
|
217
|
+
mdl = metadata_by_type[mdt]
|
218
|
+
except KeyError:
|
219
|
+
mdl = metadata_by_type[mdt] = []
|
220
|
+
mdl.append(md)
|
221
|
+
object.__setattr__(self, '_metadata_by_type', metadata_by_type)
|
222
|
+
|
223
|
+
if self.order and not self.eq:
|
224
|
+
raise ValueError('eq must be true if order is true')
|
File without changes
|
@@ -1,21 +1,25 @@
|
|
1
1
|
import copy
|
2
2
|
import dataclasses as dc
|
3
|
+
import typing as ta
|
3
4
|
|
4
|
-
from
|
5
|
-
from
|
5
|
+
from ..internals import STD_ATOMIC_TYPES
|
6
|
+
from ..internals import std_is_dataclass_instance
|
7
|
+
|
8
|
+
|
9
|
+
##
|
6
10
|
|
7
11
|
|
8
12
|
def asdict(obj, *, dict_factory=dict): # noqa
|
9
|
-
if not
|
13
|
+
if not std_is_dataclass_instance(obj): # noqa
|
10
14
|
raise TypeError('asdict() should be called on dataclass instances')
|
11
15
|
return _asdict_inner(obj, dict_factory)
|
12
16
|
|
13
17
|
|
14
18
|
def _asdict_inner(obj, dict_factory):
|
15
|
-
if type(obj) in
|
19
|
+
if type(obj) in STD_ATOMIC_TYPES:
|
16
20
|
return obj
|
17
21
|
|
18
|
-
elif
|
22
|
+
elif std_is_dataclass_instance(obj):
|
19
23
|
l = []
|
20
24
|
for f in dc.fields(obj):
|
21
25
|
value = _asdict_inner(getattr(obj, f.name), dict_factory)
|
@@ -41,16 +45,16 @@ def _asdict_inner(obj, dict_factory):
|
|
41
45
|
|
42
46
|
|
43
47
|
def astuple(obj, *, tuple_factory=tuple): # noqa
|
44
|
-
if not
|
48
|
+
if not std_is_dataclass_instance(obj):
|
45
49
|
raise TypeError('astuple() should be called on dataclass instances')
|
46
50
|
return _astuple_inner(obj, tuple_factory)
|
47
51
|
|
48
52
|
|
49
53
|
def _astuple_inner(obj, tuple_factory):
|
50
|
-
if type(obj) in
|
54
|
+
if type(obj) in STD_ATOMIC_TYPES:
|
51
55
|
return obj
|
52
56
|
|
53
|
-
elif
|
57
|
+
elif std_is_dataclass_instance(obj):
|
54
58
|
l = []
|
55
59
|
for f in dc.fields(obj):
|
56
60
|
value = _astuple_inner(getattr(obj, f.name), tuple_factory)
|
@@ -74,3 +78,14 @@ def _astuple_inner(obj, tuple_factory):
|
|
74
78
|
|
75
79
|
else:
|
76
80
|
return copy.deepcopy(obj)
|
81
|
+
|
82
|
+
|
83
|
+
##
|
84
|
+
|
85
|
+
|
86
|
+
def shallow_astuple(o: ta.Any) -> tuple[ta.Any, ...]:
|
87
|
+
return tuple(getattr(o, f.name) for f in dc.fields(o))
|
88
|
+
|
89
|
+
|
90
|
+
def shallow_asdict(o: ta.Any) -> dict[str, ta.Any]:
|
91
|
+
return {f.name: getattr(o, f.name) for f in dc.fields(o)}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
|
5
|
+
##
|
6
|
+
|
7
|
+
|
8
|
+
def fields_dict(cls_or_instance: ta.Any) -> dict[str, dc.Field]:
|
9
|
+
return {f.name: f for f in dc.fields(cls_or_instance)}
|
10
|
+
|
11
|
+
|
12
|
+
##
|
13
|
+
|
14
|
+
|
15
|
+
def iter_items(obj: ta.Any) -> ta.Iterator[tuple[str, ta.Any]]:
|
16
|
+
for f in dc.fields(obj):
|
17
|
+
yield (f.name, getattr(obj, f.name))
|
18
|
+
|
19
|
+
|
20
|
+
def iter_keys(obj: ta.Any) -> ta.Iterator[str]:
|
21
|
+
for f in dc.fields(obj):
|
22
|
+
yield f.name
|
23
|
+
|
24
|
+
|
25
|
+
def iter_values(obj: ta.Any) -> ta.Iterator[ta.Any]:
|
26
|
+
for f in dc.fields(obj):
|
27
|
+
yield getattr(obj, f.name)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from ... import check
|
5
|
+
|
6
|
+
|
7
|
+
T = ta.TypeVar('T')
|
8
|
+
|
9
|
+
|
10
|
+
##
|
11
|
+
|
12
|
+
|
13
|
+
class field_modifier: # noqa
|
14
|
+
def __init__(self, fn: ta.Callable[[dc.Field], dc.Field]) -> None:
|
15
|
+
super().__init__()
|
16
|
+
self.fn = fn
|
17
|
+
|
18
|
+
def __ror__(self, other: T) -> T:
|
19
|
+
return self(other)
|
20
|
+
|
21
|
+
def __call__(self, f: T) -> T:
|
22
|
+
return check.isinstance(self.fn(check.isinstance(f, dc.Field)), dc.Field) # type: ignore
|
23
|
+
|
24
|
+
|
25
|
+
##
|
26
|
+
|
27
|
+
|
28
|
+
def update_fields(
|
29
|
+
fn: ta.Callable[[str, dc.Field], dc.Field],
|
30
|
+
fields: ta.Iterable[str] | None = None,
|
31
|
+
) -> ta.Callable[[type[T]], type[T]]:
|
32
|
+
def inner(cls):
|
33
|
+
if fields is None:
|
34
|
+
for a, v in list(cls.__dict__.items()):
|
35
|
+
if isinstance(v, dc.Field):
|
36
|
+
setattr(cls, a, fn(a, v))
|
37
|
+
|
38
|
+
else:
|
39
|
+
for a in fields:
|
40
|
+
try:
|
41
|
+
v = cls.__dict__[a]
|
42
|
+
except KeyError:
|
43
|
+
v = dc.field()
|
44
|
+
else:
|
45
|
+
if not isinstance(v, dc.Field):
|
46
|
+
v = dc.field(default=v)
|
47
|
+
setattr(cls, a, fn(a, v))
|
48
|
+
|
49
|
+
return cls
|
50
|
+
|
51
|
+
check.not_isinstance(fields, str)
|
52
|
+
return inner
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
|
5
|
+
T = ta.TypeVar('T')
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
|
10
|
+
|
11
|
+
def deep_replace(o: T, *args: str | ta.Callable[[ta.Any], ta.Mapping[str, ta.Any]]) -> T:
|
12
|
+
if not args:
|
13
|
+
return o
|
14
|
+
elif len(args) == 1:
|
15
|
+
return dc.replace(o, **args[0](o)) # type: ignore
|
16
|
+
else:
|
17
|
+
return dc.replace(o, **{args[0]: deep_replace(getattr(o, args[0]), *args[1:])}) # type: ignore
|