omlish 0.0.0.dev284__py3-none-any.whl → 0.0.0.dev285__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.
Files changed (103) hide show
  1. omlish/__about__.py +2 -2
  2. omlish/dataclasses/__init__.py +58 -60
  3. omlish/dataclasses/api/__init__.py +25 -0
  4. omlish/dataclasses/api/classes/__init__.py +0 -0
  5. omlish/dataclasses/api/classes/conversion.py +30 -0
  6. omlish/dataclasses/api/classes/decorator.py +145 -0
  7. omlish/dataclasses/api/classes/make.py +109 -0
  8. omlish/dataclasses/api/classes/metadata.py +133 -0
  9. omlish/dataclasses/api/classes/params.py +78 -0
  10. omlish/dataclasses/api/fields/__init__.py +0 -0
  11. omlish/dataclasses/api/fields/building.py +120 -0
  12. omlish/dataclasses/api/fields/constructor.py +56 -0
  13. omlish/dataclasses/api/fields/conversion.py +191 -0
  14. omlish/dataclasses/api/fields/metadata.py +94 -0
  15. omlish/dataclasses/concerns/__init__.py +17 -0
  16. omlish/dataclasses/concerns/abc.py +15 -0
  17. omlish/dataclasses/concerns/copy.py +63 -0
  18. omlish/dataclasses/concerns/doc.py +53 -0
  19. omlish/dataclasses/concerns/eq.py +60 -0
  20. omlish/dataclasses/concerns/fields.py +119 -0
  21. omlish/dataclasses/concerns/frozen.py +133 -0
  22. omlish/dataclasses/concerns/hash.py +165 -0
  23. omlish/dataclasses/concerns/init.py +453 -0
  24. omlish/dataclasses/concerns/matchargs.py +27 -0
  25. omlish/dataclasses/concerns/mro.py +16 -0
  26. omlish/dataclasses/concerns/order.py +87 -0
  27. omlish/dataclasses/concerns/override.py +98 -0
  28. omlish/dataclasses/concerns/params.py +14 -0
  29. omlish/dataclasses/concerns/replace.py +48 -0
  30. omlish/dataclasses/concerns/repr.py +95 -0
  31. omlish/dataclasses/{impl → concerns}/slots.py +25 -1
  32. omlish/dataclasses/debug.py +2 -0
  33. omlish/dataclasses/errors.py +115 -0
  34. omlish/dataclasses/generation/__init__.py +0 -0
  35. omlish/dataclasses/generation/base.py +38 -0
  36. omlish/dataclasses/generation/compilation.py +258 -0
  37. omlish/dataclasses/generation/execution.py +195 -0
  38. omlish/dataclasses/generation/globals.py +83 -0
  39. omlish/dataclasses/generation/idents.py +6 -0
  40. omlish/dataclasses/generation/mangling.py +18 -0
  41. omlish/dataclasses/generation/manifests.py +20 -0
  42. omlish/dataclasses/generation/ops.py +97 -0
  43. omlish/dataclasses/generation/plans.py +35 -0
  44. omlish/dataclasses/generation/processor.py +174 -0
  45. omlish/dataclasses/generation/registry.py +42 -0
  46. omlish/dataclasses/generation/utils.py +83 -0
  47. omlish/dataclasses/{impl/reflect.py → inspect.py} +53 -90
  48. omlish/dataclasses/{impl/internals.py → internals.py} +26 -32
  49. omlish/dataclasses/metaclass/__init__.py +0 -0
  50. omlish/dataclasses/metaclass/bases.py +69 -0
  51. omlish/dataclasses/metaclass/confer.py +65 -0
  52. omlish/dataclasses/metaclass/meta.py +115 -0
  53. omlish/dataclasses/metaclass/specs.py +38 -0
  54. omlish/dataclasses/processing/__init__.py +0 -0
  55. omlish/dataclasses/processing/base.py +83 -0
  56. omlish/dataclasses/processing/driving.py +45 -0
  57. omlish/dataclasses/processing/priority.py +13 -0
  58. omlish/dataclasses/processing/registry.py +81 -0
  59. omlish/dataclasses/reflection.py +81 -0
  60. omlish/dataclasses/specs.py +224 -0
  61. omlish/dataclasses/tools/__init__.py +0 -0
  62. omlish/dataclasses/{impl → tools}/as_.py +23 -8
  63. omlish/dataclasses/tools/iter.py +27 -0
  64. omlish/dataclasses/tools/modifiers.py +52 -0
  65. omlish/dataclasses/tools/replace.py +17 -0
  66. omlish/dataclasses/tools/repr.py +12 -0
  67. omlish/dataclasses/{static.py → tools/static.py} +25 -4
  68. omlish/dataclasses/utils.py +54 -109
  69. omlish/diag/__init__.py +4 -4
  70. omlish/inject/impl/origins.py +1 -1
  71. omlish/lang/cached/function.py +4 -2
  72. omlish/marshal/objects/dataclasses.py +3 -7
  73. omlish/marshal/objects/helpers.py +3 -3
  74. omlish/secrets/marshal.py +1 -1
  75. omlish/secrets/secrets.py +1 -1
  76. omlish/sql/queries/base.py +1 -1
  77. omlish/typedvalues/marshal.py +2 -2
  78. {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/METADATA +1 -1
  79. {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/RECORD +83 -43
  80. omlish/dataclasses/impl/LICENSE +0 -279
  81. omlish/dataclasses/impl/__init__.py +0 -33
  82. omlish/dataclasses/impl/api.py +0 -278
  83. omlish/dataclasses/impl/copy.py +0 -30
  84. omlish/dataclasses/impl/errors.py +0 -53
  85. omlish/dataclasses/impl/fields.py +0 -245
  86. omlish/dataclasses/impl/frozen.py +0 -93
  87. omlish/dataclasses/impl/hashing.py +0 -86
  88. omlish/dataclasses/impl/init.py +0 -199
  89. omlish/dataclasses/impl/main.py +0 -93
  90. omlish/dataclasses/impl/metaclass.py +0 -235
  91. omlish/dataclasses/impl/metadata.py +0 -75
  92. omlish/dataclasses/impl/order.py +0 -57
  93. omlish/dataclasses/impl/overrides.py +0 -53
  94. omlish/dataclasses/impl/params.py +0 -128
  95. omlish/dataclasses/impl/processing.py +0 -24
  96. omlish/dataclasses/impl/replace.py +0 -40
  97. omlish/dataclasses/impl/repr.py +0 -66
  98. omlish/dataclasses/impl/simple.py +0 -50
  99. omlish/dataclasses/impl/utils.py +0 -167
  100. {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/WHEEL +0 -0
  101. {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/entry_points.txt +0 -0
  102. {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/licenses/LICENSE +0 -0
  103. {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.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,45 @@
1
+ import typing as ta
2
+
3
+ from .. import concerns as _concerns # noqa # imported for registration
4
+ from ..generation import processor as _generation_processor # noqa # imported for registration
5
+ from ..generation.processor import PlanOnly
6
+ from ..specs import ClassSpec
7
+ from .base import ProcessingContext
8
+ from .base import Processor
9
+ from .registry import all_processing_context_item_factories
10
+ from .registry import ordered_processor_types
11
+
12
+
13
+ ##
14
+
15
+
16
+ def drive_cls_processing(
17
+ cls: type,
18
+ cs: ClassSpec,
19
+ *,
20
+ plan_only: bool = False,
21
+ ) -> type:
22
+ options: list[ta.Any] = []
23
+ if plan_only:
24
+ options.append(PlanOnly(True))
25
+
26
+ ctx = ProcessingContext(
27
+ cls,
28
+ cs,
29
+ all_processing_context_item_factories(),
30
+ options=options,
31
+ )
32
+
33
+ processors: list[Processor] = [
34
+ proc_cls(ctx)
35
+ for proc_cls in ordered_processor_types()
36
+ ]
37
+
38
+ for p in processors:
39
+ p.check()
40
+
41
+ ret = cls
42
+ for p in processors:
43
+ ret = p.process(ret)
44
+
45
+ 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 .internals import ATOMIC_TYPES
5
- from .internals import is_dataclass_instance
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 is_dataclass_instance(obj): # noqa
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 ATOMIC_TYPES:
19
+ if type(obj) in STD_ATOMIC_TYPES:
16
20
  return obj
17
21
 
18
- elif is_dataclass_instance(obj):
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 is_dataclass_instance(obj):
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 ATOMIC_TYPES:
54
+ if type(obj) in STD_ATOMIC_TYPES:
51
55
  return obj
52
56
 
53
- elif is_dataclass_instance(obj):
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
@@ -0,0 +1,12 @@
1
+ import typing as ta
2
+
3
+
4
+ ##
5
+
6
+
7
+ def opt_repr(o: ta.Any) -> str | None:
8
+ return repr(o) if o is not None else None
9
+
10
+
11
+ def truthy_repr(o: ta.Any) -> str | None:
12
+ return repr(o) if o else None