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.
Files changed (107) hide show
  1. omlish/__about__.py +6 -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 +179 -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 +49 -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/lang/maybes.py +17 -0
  73. omlish/lite/maybes.py +17 -0
  74. omlish/marshal/objects/dataclasses.py +3 -7
  75. omlish/marshal/objects/helpers.py +3 -3
  76. omlish/secrets/marshal.py +1 -1
  77. omlish/secrets/secrets.py +1 -1
  78. omlish/sql/queries/base.py +1 -1
  79. omlish/text/minja.py +81 -25
  80. omlish/text/templating.py +116 -0
  81. omlish/typedvalues/marshal.py +2 -2
  82. {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/METADATA +4 -1
  83. {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/RECORD +87 -46
  84. omlish/dataclasses/impl/LICENSE +0 -279
  85. omlish/dataclasses/impl/__init__.py +0 -33
  86. omlish/dataclasses/impl/api.py +0 -278
  87. omlish/dataclasses/impl/copy.py +0 -30
  88. omlish/dataclasses/impl/errors.py +0 -53
  89. omlish/dataclasses/impl/fields.py +0 -245
  90. omlish/dataclasses/impl/frozen.py +0 -93
  91. omlish/dataclasses/impl/hashing.py +0 -86
  92. omlish/dataclasses/impl/init.py +0 -199
  93. omlish/dataclasses/impl/main.py +0 -93
  94. omlish/dataclasses/impl/metaclass.py +0 -235
  95. omlish/dataclasses/impl/metadata.py +0 -75
  96. omlish/dataclasses/impl/order.py +0 -57
  97. omlish/dataclasses/impl/overrides.py +0 -53
  98. omlish/dataclasses/impl/params.py +0 -128
  99. omlish/dataclasses/impl/processing.py +0 -24
  100. omlish/dataclasses/impl/replace.py +0 -40
  101. omlish/dataclasses/impl/repr.py +0 -66
  102. omlish/dataclasses/impl/simple.py +0 -50
  103. omlish/dataclasses/impl/utils.py +0 -167
  104. {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/WHEEL +0 -0
  105. {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/entry_points.txt +0 -0
  106. {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/licenses/LICENSE +0 -0
  107. {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/top_level.txt +0 -0
@@ -1,93 +0,0 @@
1
- import dataclasses as dc
2
- import typing as ta
3
-
4
- from ... import lang
5
- from .internals import FIELDS_ATTR
6
- from .internals import PARAMS_ATTR
7
- from .processing import Processor
8
- from .reflect import ClassInfo
9
- from .utils import Namespace
10
- from .utils import create_fn
11
- from .utils import set_new_attribute
12
-
13
-
14
- if ta.TYPE_CHECKING:
15
- from . import metaclass
16
- else:
17
- metaclass = lang.proxy_import('.metaclass', __package__)
18
-
19
-
20
- def check_frozen_bases(info: ClassInfo) -> None:
21
- mc_base = getattr(metaclass, 'Data', None)
22
- all_frozen_bases = None
23
- any_frozen_base = False
24
- has_dataclass_bases = False
25
- for b in info.cls.__mro__[-1:0:-1]:
26
- if b is mc_base:
27
- continue
28
- base_fields = getattr(b, FIELDS_ATTR, None)
29
- if base_fields is not None:
30
- has_dataclass_bases = True
31
- if all_frozen_bases is None:
32
- all_frozen_bases = True
33
- current_frozen = getattr(b, PARAMS_ATTR).frozen
34
- all_frozen_bases = all_frozen_bases and current_frozen
35
- any_frozen_base = any_frozen_base or current_frozen
36
-
37
- if has_dataclass_bases:
38
- if any_frozen_base and not info.params.frozen:
39
- raise TypeError('cannot inherit non-frozen dataclass from a frozen one')
40
-
41
- if all_frozen_bases is False and info.params.frozen:
42
- raise TypeError('cannot inherit frozen dataclass from a non-frozen one')
43
-
44
-
45
- def frozen_get_del_attr(
46
- cls: type,
47
- fields: ta.Sequence[dc.Field],
48
- globals: Namespace, # noqa
49
- ) -> tuple[ta.Callable, ta.Callable]:
50
- locals = { # noqa
51
- 'cls': cls,
52
- 'FrozenInstanceError': dc.FrozenInstanceError,
53
- }
54
- condition = 'type(self) is cls'
55
- if fields:
56
- condition += ' or name in {' + ', '.join(repr(f.name) for f in fields) + '}'
57
- return (
58
- create_fn(
59
- '__setattr__',
60
- ('self', 'name', 'value'),
61
- [
62
- f'if {condition}:',
63
- ' raise FrozenInstanceError(f"cannot assign to field {name!r}")',
64
- f'super(cls, self).__setattr__(name, value)',
65
- ],
66
- locals=locals,
67
- globals=globals,
68
- ),
69
- create_fn(
70
- '__delattr__',
71
- ('self', 'name'),
72
- [
73
- f'if {condition}:',
74
- ' raise FrozenInstanceError(f"cannot delete field {name!r}")',
75
- f'super(cls, self).__delattr__(name)',
76
- ],
77
- locals=locals,
78
- globals=globals,
79
- ),
80
- )
81
-
82
-
83
- class FrozenProcessor(Processor):
84
- def check(self) -> None:
85
- check_frozen_bases(self._info)
86
-
87
- def _process(self) -> None:
88
- if not self._info.params.frozen:
89
- return
90
-
91
- for fn in frozen_get_del_attr(self._cls, self._info.instance_fields, self._info.globals):
92
- if set_new_attribute(self._cls, fn.__name__, fn):
93
- raise TypeError(f'Cannot overwrite attribute {fn.__name__} in class {self._cls.__name__}')
@@ -1,86 +0,0 @@
1
- import dataclasses as dc
2
- import enum
3
- import typing as ta
4
-
5
- from .processing import Processor
6
- from .utils import create_fn
7
- from .utils import set_qualname
8
- from .utils import tuple_str
9
-
10
-
11
- class HashAction(enum.Enum):
12
- SET_NONE = enum.auto()
13
- ADD = enum.auto()
14
- EXCEPTION = enum.auto()
15
-
16
-
17
- # See https://bugs.python.org/issue32929#msg312829 for an if-statement version of this table.
18
- HASH_ACTIONS: ta.Mapping[tuple[bool, bool, bool, bool], HashAction | None] = {
19
- #
20
- # +-------------------------------------- unsafe_hash?
21
- # | +------------------------------- eq?
22
- # | | +------------------------ frozen?
23
- # | | | +---------------- has-explicit-hash?
24
- # v v v v
25
- (False, False, False, False): None,
26
- (False, False, False, True): None,
27
- (False, False, True, False): None,
28
- (False, False, True, True): None,
29
- (False, True, False, False): HashAction.SET_NONE,
30
- (False, True, False, True): None,
31
- (False, True, True, False): HashAction.ADD,
32
- (False, True, True, True): None,
33
- (True, False, False, False): HashAction.ADD,
34
- (True, False, False, True): HashAction.EXCEPTION,
35
- (True, False, True, False): HashAction.ADD,
36
- (True, False, True, True): HashAction.EXCEPTION,
37
- (True, True, False, False): HashAction.ADD,
38
- (True, True, False, True): HashAction.EXCEPTION,
39
- (True, True, True, False): HashAction.ADD,
40
- (True, True, True, True): HashAction.EXCEPTION,
41
- }
42
-
43
-
44
- class HashProcessor(Processor):
45
- CACHED_HASH_ATTR = '__dataclass_hash__'
46
-
47
- def _build_hash_fn(self) -> ta.Callable:
48
- flds = [f for f in self._info.instance_fields if (f.compare if f.hash is None else f.hash)]
49
- self_tuple = tuple_str('self', flds)
50
- if self._info.params_extras.cache_hash:
51
- body = [
52
- f'try: return self.{self.CACHED_HASH_ATTR}',
53
- f'except AttributeError: pass',
54
- f'object.__setattr__(self, {self.CACHED_HASH_ATTR!r}, h := hash({self_tuple}))',
55
- f'return h',
56
- ]
57
- else:
58
- body = [f'return hash({self_tuple})']
59
- hash_fn = create_fn(
60
- '__hash__',
61
- ('self',),
62
- body,
63
- globals=self._info.globals,
64
- )
65
- return set_qualname(self._cls, hash_fn) # noqa
66
-
67
- def _process(self) -> None:
68
- class_hash = self._cls.__dict__.get('__hash__', dc.MISSING)
69
- has_explicit_hash = not (class_hash is dc.MISSING or (class_hash is None and '__eq__' in self._cls.__dict__))
70
-
71
- match (hash_action := HASH_ACTIONS[(
72
- bool(self._info.params.unsafe_hash),
73
- bool(self._info.params.eq),
74
- bool(self._info.params.frozen),
75
- has_explicit_hash,
76
- )]):
77
- case HashAction.SET_NONE:
78
- self._cls.__hash__ = None # type: ignore
79
- case HashAction.ADD:
80
- self._cls.__hash__ = self._build_hash_fn() # type: ignore
81
- case HashAction.EXCEPTION:
82
- raise TypeError(f'Cannot overwrite attribute __hash__ in class {self._cls.__name__}')
83
- case None:
84
- pass
85
- case _:
86
- raise ValueError(hash_action)
@@ -1,199 +0,0 @@
1
- import dataclasses as dc
2
- import inspect
3
- import typing as ta
4
-
5
- from ... import lang
6
- from .errors import ValidationError
7
- from .fields import field_init
8
- from .fields import field_type
9
- from .fields import has_default
10
- from .fields import raise_field_validation_error
11
- from .internals import HAS_DEFAULT_FACTORY
12
- from .internals import POST_INIT_NAME
13
- from .internals import FieldType
14
- from .metadata import Init
15
- from .metadata import Validate
16
- from .processing import Processor
17
- from .reflect import ClassInfo
18
- from .utils import Namespace
19
- from .utils import create_fn
20
- from .utils import set_new_attribute
21
-
22
-
23
- MISSING = dc.MISSING
24
-
25
-
26
- ##
27
-
28
-
29
- def raise_validation_error(
30
- obj: ta.Any,
31
- fn: ta.Callable,
32
- ) -> ta.NoReturn:
33
- raise ValidationError(obj, fn)
34
-
35
-
36
- ##
37
-
38
-
39
- class InitFields(ta.NamedTuple):
40
- all: ta.Sequence[dc.Field]
41
- ordered: ta.Sequence[dc.Field]
42
- std: ta.Sequence[dc.Field]
43
- kw_only: ta.Sequence[dc.Field]
44
-
45
-
46
- def get_init_fields(fields: ta.Iterable[dc.Field], *, reorder: bool = False) -> InitFields:
47
- all_init_fields = [f for f in fields if field_type(f) in (FieldType.INSTANCE, FieldType.INIT)]
48
- ordered_init_fields = list(all_init_fields)
49
- if reorder:
50
- ordered_init_fields.sort(key=lambda f: (has_default(f), not f.kw_only))
51
- std_init_fields, kw_only_init_fields = (
52
- tuple(f1 for f1 in ordered_init_fields if f1.init and not f1.kw_only),
53
- tuple(f1 for f1 in ordered_init_fields if f1.init and f1.kw_only),
54
- )
55
- return InitFields(
56
- all=all_init_fields,
57
- ordered=ordered_init_fields,
58
- std=std_init_fields,
59
- kw_only=kw_only_init_fields,
60
- )
61
-
62
-
63
- def init_param(f: dc.Field) -> str:
64
- if not has_default(f):
65
- default = ''
66
- elif f.default is not MISSING:
67
- default = f' = __dataclass_dflt_{f.name}__'
68
- elif f.default_factory is not MISSING:
69
- default = ' = __dataclass_HAS_DEFAULT_FACTORY__'
70
- return f'{f.name}: __dataclass_type_{f.name}__{default}' # noqa
71
-
72
-
73
- ##
74
-
75
-
76
- class InitBuilder:
77
- def __init__(
78
- self,
79
- info: ClassInfo,
80
- fields: ta.Mapping[str, dc.Field],
81
- has_post_init: bool,
82
- self_name: str,
83
- globals: Namespace, # noqa
84
- ) -> None:
85
- super().__init__()
86
-
87
- self._info = info
88
- self._fields = fields
89
- self._has_post_init = has_post_init
90
- self._self_name = self_name
91
- self._globals = globals
92
-
93
- @lang.cached_function
94
- def build(self) -> ta.Callable:
95
- ifs = get_init_fields(self._fields.values(), reorder=self._info.params_extras.reorder)
96
-
97
- seen_default = None
98
- for f in ifs.std:
99
- if f.init:
100
- if has_default(f):
101
- seen_default = f
102
- elif seen_default:
103
- raise TypeError(f'non-default argument {f.name!r} follows default argument {seen_default.name!r}')
104
-
105
- locals: dict[str, ta.Any] = {} # noqa
106
-
107
- if self._info.params_extras.generic_init:
108
- get_fty = lambda f: self._info.generic_replaced_field_annotations[f.name]
109
- else:
110
- get_fty = lambda f: f.type
111
- locals.update({f'__dataclass_type_{f.name}__': get_fty(f) for f in ifs.all})
112
-
113
- locals.update({
114
- '__dataclass_HAS_DEFAULT_FACTORY__': HAS_DEFAULT_FACTORY,
115
- '__dataclass_builtins_object__': object,
116
- '__dataclass_builtins_isinstance__': isinstance,
117
- '__dataclass_builtins_TypeError__': TypeError,
118
- '__dataclass_raise_validation_error__': raise_validation_error,
119
- '__dataclass_raise_field_validation_error__': raise_field_validation_error,
120
- })
121
-
122
- body_lines: list[str] = []
123
- for f in ifs.all:
124
- f_lines = field_init(
125
- f,
126
- self._info.params.frozen,
127
- locals,
128
- self._self_name,
129
- self._info.params.slots,
130
- self._info.params_extras.override,
131
- )
132
-
133
- if f_lines:
134
- body_lines.extend(f_lines)
135
-
136
- if self._has_post_init:
137
- params_str = ','.join(f.name for f in ifs.all if field_type(f) is FieldType.INIT)
138
- body_lines.append(f'{self._self_name}.{POST_INIT_NAME}({params_str})')
139
-
140
- for i, fn in enumerate(self._info.merged_metadata.get(Validate, [])):
141
- if isinstance(fn, staticmethod):
142
- fn = fn.__func__
143
- cn = f'__dataclass_validate_{i}__'
144
- locals[cn] = fn
145
- csig = inspect.signature(fn)
146
- cas = ', '.join(p.name for p in csig.parameters.values())
147
- body_lines.append(f'if not {cn}({cas}): __dataclass_raise_validation_error__({self._self_name}, {cn})')
148
-
149
- inits = self._info.merged_metadata.get(Init, [])
150
- mro_dct = lang.mro_dict(self._info.cls)
151
- mro_v_ids = set(map(id, mro_dct.values()))
152
- props_by_fget_id = {id(v.fget): v for v in mro_dct.values() if isinstance(v, property) and v.fget is not None}
153
- for i, obj in enumerate(inits):
154
- if (obj_id := id(obj)) not in mro_v_ids and obj_id in props_by_fget_id:
155
- obj = props_by_fget_id[obj_id].__get__
156
- elif isinstance(obj, property):
157
- obj = obj.__get__
158
- cn = f'__dataclass_init_{i}__'
159
- locals[cn] = obj
160
- body_lines.append(f'{cn}({self._self_name})')
161
-
162
- if not body_lines:
163
- body_lines = ['pass']
164
-
165
- _init_params = [init_param(f) for f in ifs.std]
166
- if ifs.kw_only:
167
- _init_params += ['*']
168
- _init_params += [init_param(f) for f in ifs.kw_only]
169
-
170
- return create_fn(
171
- '__init__',
172
- [self._self_name, *_init_params],
173
- body_lines,
174
- locals=locals,
175
- globals=self._globals,
176
- return_type=lang.just(None),
177
- )
178
-
179
-
180
- class InitProcessor(Processor):
181
- def _process(self) -> None:
182
- if not self._info.params.init:
183
- return
184
-
185
- has_post_init = hasattr(self._cls, POST_INIT_NAME)
186
- self_name = '__dataclass_self__' if 'self' in self._info.fields else 'self'
187
-
188
- init = InitBuilder(
189
- ClassInfo(self._cls),
190
- self._info.fields,
191
- has_post_init,
192
- self_name,
193
- self._info.globals,
194
- ).build()
195
- set_new_attribute(
196
- self._cls,
197
- '__init__',
198
- init,
199
- )
@@ -1,93 +0,0 @@
1
- import abc
2
- import dataclasses as dc
3
- import typing as ta
4
-
5
- from ... import check
6
- from ... import lang
7
- from .copy import CopyProcessor
8
- from .fields import FieldsProcessor
9
- from .frozen import FrozenProcessor
10
- from .hashing import HashProcessor
11
- from .init import InitProcessor
12
- from .internals import FIELDS_ATTR
13
- from .internals import PARAMS_ATTR
14
- from .internals import Params
15
- from .order import OrderProcessor
16
- from .overrides import OverridesProcessor
17
- from .params import ParamsExtras
18
- from .processing import Processor
19
- from .reflect import ClassInfo
20
- from .replace import ReplaceProcessor
21
- from .repr import ReprProcessor
22
- from .simple import DocProcessor
23
- from .simple import EqProcessor
24
- from .simple import MatchArgsProcessor
25
- from .slots import add_slots
26
-
27
-
28
- MISSING = dc.MISSING
29
-
30
-
31
- class MainProcessor:
32
- def __init__(self, cls: type) -> None:
33
- super().__init__()
34
-
35
- self._cls = check.isinstance(cls, type)
36
- self._info = info = ClassInfo(cls, _constructing=True)
37
-
38
- check.not_in(FIELDS_ATTR, cls.__dict__)
39
- check.is_(check.isinstance(cls.__dict__[PARAMS_ATTR], Params), info.params)
40
- check.is_(check.isinstance(check.not_none(info.cls_metadata)[ParamsExtras], ParamsExtras), info.params_extras) # noqa
41
-
42
- def _check_params(self) -> None:
43
- if self._info.params.order and not self._info.params.eq:
44
- raise ValueError('eq must be true if order is true')
45
-
46
- @lang.cached_function
47
- def _transform_slots(self) -> None:
48
- if self._info.params.weakref_slot and not self._info.params.slots:
49
- raise TypeError('weakref_slot is True but slots is False')
50
- if not self._info.params.slots:
51
- return
52
- self._cls = add_slots(
53
- self._cls,
54
- is_frozen=self._info.params.frozen,
55
- weakref_slot=self._info.params.weakref_slot,
56
- )
57
-
58
- PROCESSOR_TYPES: ta.ClassVar[ta.Sequence[type[Processor]]] = [
59
- FieldsProcessor,
60
- InitProcessor,
61
- OverridesProcessor,
62
- ReprProcessor,
63
- EqProcessor,
64
- OrderProcessor,
65
- FrozenProcessor,
66
- HashProcessor,
67
- DocProcessor,
68
- MatchArgsProcessor,
69
- ReplaceProcessor,
70
- CopyProcessor,
71
- ]
72
-
73
- @lang.cached_function
74
- def process(self) -> type:
75
- self._check_params()
76
-
77
- ps = [pcls(self._info) for pcls in self.PROCESSOR_TYPES]
78
-
79
- for p in ps:
80
- p.check()
81
-
82
- for p in ps:
83
- p.process()
84
-
85
- self._transform_slots()
86
-
87
- abc.update_abstractmethods(self._cls) # noqa
88
-
89
- return self._cls
90
-
91
-
92
- def process_class(cls: type) -> type:
93
- return MainProcessor(cls).process()
@@ -1,235 +0,0 @@
1
- """
2
- TODO:
3
- - Rewrite lol
4
- - Enum - enforce Abstract or Final
5
- """
6
- import abc
7
- import collections
8
- import dataclasses as dc
9
- import typing as ta
10
-
11
- from ... import lang
12
- from .api import MISSING
13
- from .api import dataclass
14
- from .api import field # noqa
15
- from .params import MetaclassParams
16
- from .params import get_metaclass_params
17
- from .params import get_params
18
- from .params import get_params_extras
19
-
20
-
21
- T = ta.TypeVar('T')
22
-
23
-
24
- ##
25
-
26
-
27
- _CONFER_PARAMS: tuple[str, ...] = (
28
- 'eq',
29
- 'frozen',
30
- 'kw_only',
31
- )
32
-
33
- _CONFER_PARAMS_EXTRAS: tuple[str, ...] = (
34
- 'reorder',
35
- 'cache_hash',
36
- 'generic_init',
37
- 'override',
38
- )
39
-
40
- _CONFER_METACLASS_PARAMS: tuple[str, ...] = (
41
- 'confer',
42
- 'final_subclasses',
43
- 'abstract_immediate_subclasses',
44
- )
45
-
46
-
47
- def confer_kwarg(out: dict[str, ta.Any], k: str, v: ta.Any) -> None:
48
- if k in out:
49
- if out[k] != v:
50
- raise ValueError
51
- else:
52
- out[k] = v
53
-
54
-
55
- def confer_kwargs(
56
- bases: ta.Sequence[type],
57
- kwargs: ta.Mapping[str, ta.Any],
58
- ) -> dict[str, ta.Any]:
59
- out: dict[str, ta.Any] = {}
60
- for base in bases:
61
- if not dc.is_dataclass(base):
62
- continue
63
-
64
- if not (bmp := get_metaclass_params(base)).confer:
65
- continue
66
-
67
- for ck in bmp.confer:
68
- if ck in kwargs:
69
- continue
70
-
71
- if ck in _CONFER_PARAMS:
72
- confer_kwarg(out, ck, getattr(get_params(base), ck))
73
-
74
- elif ck in _CONFER_PARAMS_EXTRAS:
75
- confer_kwarg(out, ck, getattr(get_params_extras(base), ck))
76
-
77
- elif ck in _CONFER_METACLASS_PARAMS:
78
- confer_kwarg(out, ck, getattr(bmp, ck))
79
-
80
- else:
81
- raise KeyError(ck)
82
-
83
- return out
84
-
85
-
86
- ##
87
-
88
-
89
- class DataMeta(abc.ABCMeta):
90
- def __new__(
91
- mcls,
92
- name,
93
- bases,
94
- namespace,
95
- *,
96
-
97
- abstract=False,
98
- sealed=False,
99
- final=False,
100
-
101
- metadata=None,
102
- **kwargs,
103
- ):
104
- ckw = confer_kwargs(bases, kwargs)
105
- nkw = {**kwargs, **ckw}
106
-
107
- mcp = MetaclassParams(**{
108
- mpa: nkw.pop(mpa)
109
- for mpa in _CONFER_METACLASS_PARAMS
110
- if mpa in nkw
111
- })
112
-
113
- mmd = {
114
- MetaclassParams: mcp,
115
- }
116
- if metadata is not None:
117
- metadata = collections.ChainMap(mmd, metadata)
118
- else:
119
- metadata = mmd
120
-
121
- #
122
-
123
- xbs: list[type] = []
124
-
125
- if any(get_metaclass_params(b).abstract_immediate_subclasses for b in bases if dc.is_dataclass(b)):
126
- abstract = True
127
-
128
- final |= (mcp.final_subclasses and not abstract)
129
-
130
- if final and abstract:
131
- raise TypeError(f'Class cannot be abstract and final: {name!r}')
132
-
133
- if abstract:
134
- xbs.append(lang.Abstract)
135
- if sealed:
136
- xbs.append(lang.Sealed)
137
- if final:
138
- xbs.append(lang.Final)
139
-
140
- if xbs:
141
- if bases and bases[-1] is ta.Generic:
142
- bases = (*bases[:-1], *xbs, bases[-1])
143
- else:
144
- bases = (*bases, *xbs)
145
- if ob := namespace.get('__orig_bases__'):
146
- if getattr(ob[-1], '__origin__', None) is ta.Generic:
147
- namespace['__orig_bases__'] = (*ob[:-1], *xbs, ob[-1])
148
- else:
149
- namespace['__orig_bases__'] = (*ob, *xbs)
150
-
151
- #
152
-
153
- ofs: set[str] = set()
154
- if any(issubclass(b, lang.Abstract) for b in bases) and nkw.get('override'):
155
- ofs.update(a for a in namespace.get('__annotations__', []) if a not in namespace)
156
- namespace.update((a, MISSING) for a in ofs)
157
-
158
- #
159
-
160
- cls = lang.super_meta(
161
- super(),
162
- mcls,
163
- name,
164
- bases,
165
- namespace,
166
- )
167
-
168
- #
169
-
170
- for a in ofs:
171
- delattr(cls, a)
172
-
173
- #
174
-
175
- return dataclass(cls, metadata=metadata, **nkw)
176
-
177
-
178
- ##
179
-
180
-
181
- # @ta.dataclass_transform(field_specifiers=(field,)) # FIXME: ctor
182
- class Data(
183
- eq=False,
184
- order=False,
185
- confer=frozenset([
186
- 'confer',
187
- 'final_subclasses',
188
- ]),
189
- metaclass=DataMeta,
190
- ):
191
- def __init__(self, *args, **kwargs):
192
- # Typechecking barrier
193
- super().__init__(*args, **kwargs)
194
-
195
- def __init_subclass__(cls, **kwargs):
196
- # Typechecking barrier
197
- super().__init_subclass__(**kwargs)
198
-
199
-
200
- class Frozen(
201
- Data,
202
- frozen=True,
203
- eq=False,
204
- order=False,
205
- confer=frozenset([
206
- *get_metaclass_params(Data).confer,
207
- 'frozen',
208
- 'reorder',
209
- 'cache_hash',
210
- 'override',
211
- ]),
212
- ):
213
- pass
214
-
215
-
216
- class Case(
217
- Frozen,
218
- abstract=True,
219
- override=True,
220
- final_subclasses=True,
221
- abstract_immediate_subclasses=True,
222
- ):
223
- pass
224
-
225
-
226
- class Box(
227
- Frozen,
228
- ta.Generic[T],
229
- generic_init=True,
230
- confer=frozenset([
231
- *get_metaclass_params(Frozen).confer,
232
- 'generic_init',
233
- ]),
234
- ):
235
- v: T