omlish 0.0.0.dev283__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 (121) hide show
  1. omlish/__about__.py +4 -4
  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/bindings.py +1 -1
  71. omlish/inject/eagers.py +1 -1
  72. omlish/inject/impl/bindings.py +1 -1
  73. omlish/inject/impl/origins.py +1 -1
  74. omlish/inject/keys.py +1 -1
  75. omlish/inject/listeners.py +1 -1
  76. omlish/inject/multis.py +4 -4
  77. omlish/inject/origins.py +2 -2
  78. omlish/inject/overrides.py +1 -1
  79. omlish/inject/privates.py +2 -2
  80. omlish/inject/providers.py +4 -4
  81. omlish/inject/scopes.py +3 -3
  82. omlish/lang/__init__.py +6 -2
  83. omlish/lang/cached/function.py +13 -2
  84. omlish/lang/cached/property.py +3 -1
  85. omlish/lang/imports.py +68 -10
  86. omlish/lang/objects.py +0 -46
  87. omlish/lite/reprs.py +84 -0
  88. omlish/marshal/objects/dataclasses.py +5 -9
  89. omlish/marshal/objects/helpers.py +3 -3
  90. omlish/secrets/marshal.py +1 -1
  91. omlish/secrets/secrets.py +1 -1
  92. omlish/sql/queries/base.py +4 -4
  93. omlish/text/mangle.py +66 -7
  94. omlish/typedvalues/marshal.py +2 -2
  95. omlish/typedvalues/values.py +1 -1
  96. {omlish-0.0.0.dev283.dist-info → omlish-0.0.0.dev285.dist-info}/METADATA +3 -3
  97. {omlish-0.0.0.dev283.dist-info → omlish-0.0.0.dev285.dist-info}/RECORD +101 -60
  98. omlish/dataclasses/impl/LICENSE +0 -279
  99. omlish/dataclasses/impl/__init__.py +0 -33
  100. omlish/dataclasses/impl/api.py +0 -278
  101. omlish/dataclasses/impl/copy.py +0 -30
  102. omlish/dataclasses/impl/errors.py +0 -53
  103. omlish/dataclasses/impl/fields.py +0 -245
  104. omlish/dataclasses/impl/frozen.py +0 -93
  105. omlish/dataclasses/impl/hashing.py +0 -86
  106. omlish/dataclasses/impl/init.py +0 -199
  107. omlish/dataclasses/impl/main.py +0 -93
  108. omlish/dataclasses/impl/metaclass.py +0 -235
  109. omlish/dataclasses/impl/metadata.py +0 -75
  110. omlish/dataclasses/impl/order.py +0 -47
  111. omlish/dataclasses/impl/overrides.py +0 -53
  112. omlish/dataclasses/impl/params.py +0 -128
  113. omlish/dataclasses/impl/processing.py +0 -24
  114. omlish/dataclasses/impl/replace.py +0 -40
  115. omlish/dataclasses/impl/repr.py +0 -66
  116. omlish/dataclasses/impl/simple.py +0 -50
  117. omlish/dataclasses/impl/utils.py +0 -167
  118. {omlish-0.0.0.dev283.dist-info → omlish-0.0.0.dev285.dist-info}/WHEEL +0 -0
  119. {omlish-0.0.0.dev283.dist-info → omlish-0.0.0.dev285.dist-info}/entry_points.txt +0 -0
  120. {omlish-0.0.0.dev283.dist-info → omlish-0.0.0.dev285.dist-info}/licenses/LICENSE +0 -0
  121. {omlish-0.0.0.dev283.dist-info → omlish-0.0.0.dev285.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,83 @@
1
+ import typing as ta
2
+
3
+ from .globals import OBJECT_SETATTR_GLOBAL
4
+ from .globals import FnGlobal
5
+ from .idents import SELF_DICT_IDENT
6
+ from .idents import SELF_IDENT
7
+
8
+
9
+ ##
10
+
11
+
12
+ def build_attr_tuple_body_src_lines(
13
+ obj_name: str,
14
+ *attrs: str,
15
+ prefix: str = '',
16
+ ) -> list[str]:
17
+ return [
18
+ f'{prefix}{obj_name}.{a},'
19
+ for a in attrs
20
+ ]
21
+
22
+
23
+ def build_attr_kwargs_body_src_lines(
24
+ obj_name: str,
25
+ *attrs: str,
26
+ prefix: str = '',
27
+ ) -> list[str]:
28
+ return [
29
+ f'{prefix}{a}={obj_name}.{a},'
30
+ for a in attrs
31
+ ]
32
+
33
+
34
+ class SetattrSrcBuilder:
35
+ def __init__(
36
+ self,
37
+ *,
38
+ object_ident: str = SELF_IDENT,
39
+ object_dict_ident: str | None = None,
40
+ ) -> None:
41
+ super().__init__()
42
+
43
+ self._object_ident = object_ident
44
+ self._object_dict_ident = object_dict_ident
45
+
46
+ self._has_set_object_dict = False
47
+ self._global_refs: set[FnGlobal] = set()
48
+
49
+ @property
50
+ def refs(self) -> ta.AbstractSet[FnGlobal]:
51
+ return self._global_refs
52
+
53
+ def __call__(
54
+ self,
55
+ name: str,
56
+ value_src: str,
57
+ *,
58
+ frozen: bool,
59
+ override: bool,
60
+ ) -> list[str]:
61
+ if override:
62
+ if self._object_dict_ident is not None:
63
+ return [f'{self._object_dict_ident}[{name!r}] = {value_src}']
64
+
65
+ else:
66
+ if not self._has_set_object_dict:
67
+ x = [
68
+ f'{SELF_DICT_IDENT} = {self._object_ident}.__dict__',
69
+ ]
70
+ self._has_set_object_dict = True
71
+ else:
72
+ x = []
73
+ return [
74
+ *x,
75
+ f'{SELF_DICT_IDENT}[{name!r}] = {value_src}',
76
+ ]
77
+
78
+ elif frozen:
79
+ self._global_refs.add(OBJECT_SETATTR_GLOBAL)
80
+ return [f'{OBJECT_SETATTR_GLOBAL.ident}({self._object_ident}, {name!r}, {value_src})']
81
+
82
+ else:
83
+ return [f'{self._object_ident}.{name} = {value_src}']
@@ -1,116 +1,90 @@
1
- """
2
- TODO:
3
- - more cache-recursive reuse - fields, mro, etc
4
- """
5
1
  import dataclasses as dc
6
- import sys
7
2
  import typing as ta
8
3
  import weakref
9
4
 
10
- from ... import cached
11
- from ... import check
12
- from ... import collections as col
13
- from ... import lang
14
- from ... import reflect as rfl
15
- from .fields import field_type
16
- from .internals import FIELDS_ATTR
17
- from .internals import FieldType
18
- from .internals import Params
19
- from .internals import is_kw_only
20
- from .metadata import METADATA_ATTR
21
- from .metadata import Metadata
22
- from .metadata import get_merged_metadata
23
- from .params import PARAMS_ATTR
24
- from .params import ParamsExtras
25
- from .params import get_params
26
- from .params import get_params_extras
27
- from .utils import Namespace
28
-
29
-
30
- MISSING = dc.MISSING
5
+ from .. import cached
6
+ from .. import check
7
+ from .. import collections as col
8
+ from .. import lang
9
+ from .. import reflect as rfl
10
+ from .internals import STD_FIELDS_ATTR
11
+ from .internals import StdFieldType
12
+ from .internals import std_field_type
13
+ from .internals import std_is_kw_only
14
+
15
+
16
+ ClassAnnotations: ta.TypeAlias = ta.Mapping[str, ta.Any]
31
17
 
32
18
 
33
19
  ##
34
20
 
35
21
 
36
- def get_cls_annotations(cls: type) -> ta.Mapping[str, ta.Any]:
22
+ def _get_cls_annotations(cls: type) -> ClassAnnotations:
37
23
  # Does not use ta.get_type_hints because that's what std dataclasses do [1]. Might be worth revisiting? A part
38
24
  # of why they don't is to not import typing for efficiency but we don't care about that degree of startup speed.
39
25
  # [1]: https://github.com/python/cpython/blob/54c63a32d06cb5f07a66245c375eac7d7efb964a/Lib/dataclasses.py#L985-L986 # noqa
40
26
  return rfl.get_annotations(cls)
41
27
 
42
28
 
29
+ _CLS_ANNOTATIONS_CACHE: ta.MutableMapping[type, ClassAnnotations] = weakref.WeakKeyDictionary()
30
+
31
+
32
+ def get_cls_annotations(cls: type) -> ClassAnnotations:
33
+ try:
34
+ return _CLS_ANNOTATIONS_CACHE[cls]
35
+ except KeyError:
36
+ pass
37
+ ret = _get_cls_annotations(cls)
38
+ _CLS_ANNOTATIONS_CACHE[cls] = ret
39
+ return ret
40
+
41
+
43
42
  ##
44
43
 
45
44
 
46
- class ClassInfo:
47
- def __init__(self, cls: type, *, _constructing: bool = False) -> None:
48
- check.isinstance(cls, type)
49
- self._constructing = _constructing
50
- if not _constructing:
51
- check.arg(dc.is_dataclass(cls))
45
+ class FieldsInspection:
46
+ def __init__(
47
+ self,
48
+ cls: type,
49
+ *,
50
+ cls_fields: ta.Mapping[str, dc.Field] | None = None,
51
+ ) -> None:
52
52
  super().__init__()
53
- self._cls: type = cls
53
+
54
+ self._cls = cls
55
+
56
+ self._given_cls_fields = cls_fields
57
+ if cls_fields is None:
58
+ cls_fields = getattr(self._cls, STD_FIELDS_ATTR)
59
+ self._cls_fields = cls_fields
54
60
 
55
61
  @property
56
62
  def cls(self) -> type:
57
63
  return self._cls # noqa
58
64
 
59
- @cached.property
60
- def globals(self) -> Namespace:
61
- if self._cls.__module__ in sys.modules:
62
- return sys.modules[self._cls.__module__].__dict__
63
- else:
64
- return {}
65
+ @property
66
+ def cls_fields(self) -> ta.Mapping[str, dc.Field]:
67
+ return self._cls_fields
65
68
 
66
69
  @cached.property
67
70
  def cls_annotations(self) -> ta.Mapping[str, ta.Any]:
68
71
  return get_cls_annotations(self._cls)
69
72
 
70
- ##
71
-
72
- @cached.property
73
- def params(self) -> Params:
74
- return get_params(self._cls)
75
-
76
- @cached.property
77
- def cls_params(self) -> Params | None:
78
- return self._cls.__dict__.get(PARAMS_ATTR)
79
-
80
- @cached.property
81
- def params_extras(self) -> ParamsExtras:
82
- return get_params_extras(self._cls)
83
-
84
- @cached.property
85
- def cls_params_extras(self) -> ParamsExtras | None:
86
- return (self.cls_metadata or {}).get(ParamsExtras)
87
-
88
- @cached.property
89
- def cls_metadata(self) -> Metadata | None:
90
- return self._cls.__dict__.get(METADATA_ATTR)
91
-
92
- @cached.property
93
- def merged_metadata(self) -> Metadata:
94
- return get_merged_metadata(self._cls)
95
-
96
- ##
73
+ #
97
74
 
98
75
  class _FoundFields(ta.NamedTuple):
99
76
  fields: dict[str, dc.Field]
100
77
  field_owners: dict[str, type]
101
78
 
102
- @lang.cached_function
79
+ @lang.cached_function(no_wrapper_update=True)
103
80
  def _find_fields(self) -> _FoundFields:
104
- if self._constructing:
105
- check.in_(FIELDS_ATTR, self._cls.__dict__)
106
-
107
81
  fields: dict[str, dc.Field] = {}
108
82
  field_owners: dict[str, type] = {}
109
83
 
110
84
  for b in self._cls.__mro__[-1:0:-1]:
111
- base_fields = getattr(b, FIELDS_ATTR, None)
85
+ base_fields = getattr(b, STD_FIELDS_ATTR, None)
112
86
  if base_fields is not None:
113
- for name in reflect(b).cls_annotations:
87
+ for name in get_cls_annotations(b):
114
88
  try:
115
89
  f = base_fields[name]
116
90
  except KeyError:
@@ -118,14 +92,14 @@ class ClassInfo:
118
92
  fields[f.name] = f
119
93
  field_owners[f.name] = b
120
94
 
121
- cls_fields = getattr(self._cls, FIELDS_ATTR)
95
+ cls_fields = self._cls_fields
122
96
  for name, ann in self.cls_annotations.items():
123
- if is_kw_only(self._cls, ann):
97
+ if std_is_kw_only(self._cls, ann):
124
98
  continue
125
99
  fields[name] = cls_fields[name]
126
100
  field_owners[name] = self._cls
127
101
 
128
- return ClassInfo._FoundFields(fields, field_owners)
102
+ return FieldsInspection._FoundFields(fields, field_owners)
129
103
 
130
104
  @cached.property
131
105
  def fields(self) -> ta.Mapping[str, dc.Field]:
@@ -133,7 +107,7 @@ class ClassInfo:
133
107
 
134
108
  @cached.property
135
109
  def instance_fields(self) -> ta.Sequence[dc.Field]:
136
- return [f for f in self.fields.values() if field_type(f) is FieldType.INSTANCE]
110
+ return [f for f in self.fields.values() if std_field_type(f) is StdFieldType.INSTANCE]
137
111
 
138
112
  @cached.property
139
113
  def field_owners(self) -> ta.Mapping[str, type]:
@@ -170,16 +144,5 @@ class ClassInfo:
170
144
  return {k: rfl.to_annotation(v) for k, v in self.generic_replaced_field_types.items()}
171
145
 
172
146
 
173
- ##
174
-
175
-
176
- _CLASS_INFO_CACHE: ta.MutableMapping[type, ClassInfo] = weakref.WeakKeyDictionary()
177
-
178
-
179
- def reflect(obj: ta.Any) -> ClassInfo:
180
- cls = obj if isinstance(obj, type) else type(obj)
181
- try:
182
- return _CLASS_INFO_CACHE[cls]
183
- except KeyError:
184
- _CLASS_INFO_CACHE[cls] = info = ClassInfo(cls)
185
- return info
147
+ def inspect_fields(cls: type) -> FieldsInspection:
148
+ return FieldsInspection(cls)
@@ -8,48 +8,32 @@ import typing as ta
8
8
  ##
9
9
 
10
10
 
11
- HAS_DEFAULT_FACTORY = dc._HAS_DEFAULT_FACTORY # type: ignore # noqa
11
+ STD_HAS_DEFAULT_FACTORY = dc._HAS_DEFAULT_FACTORY # type: ignore # noqa
12
12
 
13
- FIELDS_ATTR = dc._FIELDS # type: ignore # noqa
14
- PARAMS_ATTR = dc._PARAMS # type: ignore # noqa
13
+ STD_FIELDS_ATTR = dc._FIELDS # type: ignore # noqa
14
+ STD_PARAMS_ATTR = dc._PARAMS # type: ignore # noqa
15
15
 
16
- POST_INIT_NAME = dc._POST_INIT_NAME # type: ignore # noqa
16
+ STD_POST_INIT_NAME = dc._POST_INIT_NAME # type: ignore # noqa
17
17
 
18
- Params = dc._DataclassParams # type: ignore # noqa
19
-
20
-
21
- """
22
- @dc.dataclass(frozen=True)
23
- class Params:
24
- init = True
25
- repr = True
26
- eq = True
27
- order = False
28
- unsafe_hash = False
29
- frozen = False
30
- match_args = True
31
- kw_only = False
32
- slots = False
33
- weakref_slot = False
34
- """
18
+ StdParams = dc._DataclassParams # type: ignore # noqa
35
19
 
36
20
 
37
21
  ##
38
22
 
39
23
 
40
- is_dataclass_instance = dc._is_dataclass_instance # type: ignore # noqa
24
+ std_is_dataclass_instance = dc._is_dataclass_instance # type: ignore # noqa
41
25
 
42
26
 
43
27
  ##
44
28
 
45
29
 
46
- ATOMIC_TYPES: frozenset[type]
30
+ STD_ATOMIC_TYPES: frozenset[type]
47
31
 
48
32
  if hasattr(dc, '_ATOMIC_TYPES'):
49
- ATOMIC_TYPES = getattr(dc, '_ATOMIC_TYPES')
33
+ STD_ATOMIC_TYPES = getattr(dc, '_ATOMIC_TYPES')
50
34
 
51
35
  else:
52
- ATOMIC_TYPES = frozenset({
36
+ STD_ATOMIC_TYPES = frozenset({
53
37
  types.NoneType,
54
38
  bool,
55
39
  int,
@@ -85,10 +69,20 @@ def _patch_missing_ctor() -> None:
85
69
  ##
86
70
 
87
71
 
88
- class FieldType(enum.Enum):
72
+ class StdFieldType(enum.Enum):
89
73
  INSTANCE = dc._FIELD # type: ignore # noqa
90
- CLASS = dc._FIELD_CLASSVAR # type: ignore # noqa
91
- INIT = dc._FIELD_INITVAR # type: ignore # noqa
74
+ CLASS_VAR = dc._FIELD_CLASSVAR # type: ignore # noqa
75
+ INIT_VAR = dc._FIELD_INITVAR # type: ignore # noqa
76
+
77
+
78
+ def std_field_type(f: dc.Field) -> StdFieldType:
79
+ if (ft := getattr(f, '_field_type')) is not None:
80
+ return StdFieldType(ft)
81
+ else:
82
+ return StdFieldType.INSTANCE
83
+
84
+
85
+ ##
92
86
 
93
87
 
94
88
  _SELF_MODULE = None
@@ -97,18 +91,18 @@ _SELF_MODULE = None
97
91
  def _self_module():
98
92
  global _SELF_MODULE
99
93
  if _SELF_MODULE is None:
100
- _SELF_MODULE = sys.modules[__package__.rpartition('.')[0]]
94
+ _SELF_MODULE = sys.modules[__package__]
101
95
  return _SELF_MODULE
102
96
 
103
97
 
104
- def is_classvar(cls: type, ty: ta.Any) -> bool:
98
+ def std_is_classvar(cls: type, ty: ta.Any) -> bool:
105
99
  return (
106
100
  dc._is_classvar(ty, ta) # type: ignore # noqa
107
101
  or (isinstance(ty, str) and dc._is_type(ty, cls, ta, ta.ClassVar, dc._is_classvar)) # type: ignore # noqa
108
102
  )
109
103
 
110
104
 
111
- def is_initvar(cls: type, ty: ta.Any) -> bool:
105
+ def std_is_initvar(cls: type, ty: ta.Any) -> bool:
112
106
  return (
113
107
  dc._is_initvar(ty, dc) # type: ignore # noqa
114
108
  or (
@@ -121,7 +115,7 @@ def is_initvar(cls: type, ty: ta.Any) -> bool:
121
115
  )
122
116
 
123
117
 
124
- def is_kw_only(cls: type, ty: ta.Any) -> bool:
118
+ def std_is_kw_only(cls: type, ty: ta.Any) -> bool:
125
119
  return (
126
120
  dc._is_kw_only(ty, dc) # type: ignore # noqa
127
121
  or (
File without changes
@@ -0,0 +1,69 @@
1
+ import typing as ta
2
+
3
+ from ..concerns.frozen import unchecked_frozen_base
4
+ from .meta import DataMeta
5
+ from .specs import get_metaclass_spec
6
+
7
+
8
+ T = ta.TypeVar('T')
9
+
10
+
11
+ ##
12
+
13
+
14
+ # @ta.dataclass_transform(field_specifiers=(field,)) # FIXME: ctor
15
+ @unchecked_frozen_base
16
+ class Data(
17
+ eq=False,
18
+ order=False,
19
+ confer=frozenset([
20
+ 'confer',
21
+ 'final_subclasses',
22
+ ]),
23
+ metaclass=DataMeta,
24
+ ):
25
+ def __init__(self, *args, **kwargs):
26
+ # Typechecking barrier
27
+ super().__init__(*args, **kwargs)
28
+
29
+ def __init_subclass__(cls, **kwargs):
30
+ # Typechecking barrier
31
+ super().__init_subclass__(**kwargs)
32
+
33
+
34
+ class Frozen(
35
+ Data,
36
+ frozen=True,
37
+ eq=False,
38
+ order=False,
39
+ confer=frozenset([
40
+ *get_metaclass_spec(Data).confer,
41
+ 'frozen',
42
+ 'reorder',
43
+ 'cache_hash',
44
+ 'override',
45
+ ]),
46
+ ):
47
+ pass
48
+
49
+
50
+ class Case(
51
+ Frozen,
52
+ abstract=True,
53
+ override=True,
54
+ final_subclasses=True,
55
+ abstract_immediate_subclasses=True,
56
+ ):
57
+ pass
58
+
59
+
60
+ class Box(
61
+ Frozen,
62
+ ta.Generic[T],
63
+ generic_init=True,
64
+ confer=frozenset([
65
+ *get_metaclass_spec(Frozen).confer,
66
+ 'generic_init',
67
+ ]),
68
+ ):
69
+ v: T
@@ -0,0 +1,65 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ..api.classes.params import get_class_spec
5
+ from .specs import get_metaclass_spec
6
+
7
+
8
+ ##
9
+
10
+
11
+ CONFER_CLASS_PARAMS: tuple[str, ...] = (
12
+ 'eq',
13
+ 'frozen',
14
+ 'kw_only',
15
+
16
+ 'reorder',
17
+ 'cache_hash',
18
+ 'generic_init',
19
+ 'override',
20
+ )
21
+
22
+ CONFER_METACLASS_PARAMS: tuple[str, ...] = (
23
+ 'confer',
24
+ 'final_subclasses',
25
+ 'abstract_immediate_subclasses',
26
+ )
27
+
28
+
29
+ def confer_kwarg(out: dict[str, ta.Any], k: str, v: ta.Any) -> None:
30
+ if k in out:
31
+ if out[k] != v:
32
+ raise ValueError
33
+ else:
34
+ out[k] = v
35
+
36
+
37
+ def confer_kwargs(
38
+ bases: ta.Sequence[type],
39
+ kwargs: ta.Mapping[str, ta.Any],
40
+ ) -> dict[str, ta.Any]:
41
+ out: dict[str, ta.Any] = {}
42
+ for base in bases:
43
+ if not dc.is_dataclass(base):
44
+ continue
45
+
46
+ if (bcs := get_class_spec(base)) is None:
47
+ continue
48
+
49
+ if not (bmp := get_metaclass_spec(bcs)).confer:
50
+ continue
51
+
52
+ for ck in bmp.confer:
53
+ if ck in kwargs:
54
+ continue
55
+
56
+ if ck in CONFER_CLASS_PARAMS:
57
+ confer_kwarg(out, ck, getattr(bcs, ck))
58
+
59
+ elif ck in CONFER_METACLASS_PARAMS:
60
+ confer_kwarg(out, ck, getattr(bmp, ck))
61
+
62
+ else:
63
+ raise KeyError(ck)
64
+
65
+ return out
@@ -0,0 +1,115 @@
1
+ """
2
+ TODO:
3
+ - Rewrite lol
4
+ - Enum - enforce Abstract or Final
5
+ """
6
+ import abc
7
+ import dataclasses as dc
8
+ import typing as ta
9
+
10
+ from ... import lang
11
+ from ..api.classes.decorator import dataclass
12
+ from .confer import CONFER_METACLASS_PARAMS
13
+ from .confer import confer_kwargs
14
+ from .specs import MetaclassSpec
15
+ from .specs import get_metaclass_spec
16
+
17
+
18
+ T = ta.TypeVar('T')
19
+
20
+
21
+ ##
22
+
23
+
24
+ class DataMeta(abc.ABCMeta):
25
+ def __new__(
26
+ mcls,
27
+ name,
28
+ bases,
29
+ namespace,
30
+ *,
31
+
32
+ abstract=False,
33
+ sealed=False,
34
+ final=False,
35
+
36
+ metadata=None,
37
+ **kwargs,
38
+ ):
39
+ ckw = confer_kwargs(bases, kwargs)
40
+ nkw = {**kwargs, **ckw}
41
+
42
+ mcp = MetaclassSpec(**{
43
+ mpa: nkw.pop(mpa)
44
+ for mpa in CONFER_METACLASS_PARAMS
45
+ if mpa in nkw
46
+ })
47
+
48
+ metadata = [
49
+ *([metadata] if metadata is not None else []),
50
+ mcp,
51
+ ]
52
+
53
+ #
54
+
55
+ xbs: list[type] = []
56
+
57
+ if any(
58
+ get_metaclass_spec(b).abstract_immediate_subclasses # type: ignore[arg-type]
59
+ for b in bases
60
+ if dc.is_dataclass(b)
61
+ ):
62
+ abstract = True
63
+
64
+ final |= (mcp.final_subclasses and not abstract)
65
+
66
+ if final and abstract:
67
+ raise TypeError(f'Class cannot be abstract and final: {name!r}')
68
+
69
+ if abstract:
70
+ xbs.append(lang.Abstract)
71
+ if sealed:
72
+ xbs.append(lang.Sealed)
73
+ if final:
74
+ xbs.append(lang.Final)
75
+
76
+ if xbs:
77
+ if bases and bases[-1] is ta.Generic:
78
+ bases = (*bases[:-1], *xbs, bases[-1])
79
+ else:
80
+ bases = (*bases, *xbs)
81
+ if ob := namespace.get('__orig_bases__'):
82
+ if getattr(ob[-1], '__origin__', None) is ta.Generic:
83
+ namespace['__orig_bases__'] = (*ob[:-1], *xbs, ob[-1])
84
+ else:
85
+ namespace['__orig_bases__'] = (*ob, *xbs)
86
+
87
+ #
88
+
89
+ ofs: set[str] = set()
90
+ if any(issubclass(b, lang.Abstract) for b in bases) and nkw.get('override'):
91
+ ofs.update(a for a in namespace.get('__annotations__', []) if a not in namespace)
92
+ namespace.update((a, dc.MISSING) for a in ofs)
93
+
94
+ #
95
+
96
+ cls = lang.super_meta(
97
+ super(),
98
+ mcls,
99
+ name,
100
+ bases,
101
+ namespace,
102
+ )
103
+
104
+ #
105
+
106
+ for a in ofs:
107
+ delattr(cls, a)
108
+
109
+ #
110
+
111
+ return dataclass(
112
+ cls,
113
+ metadata=metadata,
114
+ **nkw,
115
+ )
@@ -0,0 +1,38 @@
1
+ import dataclasses as dc
2
+
3
+ from ... import lang
4
+ from ..api.classes.params import get_class_spec
5
+ from ..specs import ClassSpec
6
+
7
+
8
+ ##
9
+
10
+
11
+ @dc.dataclass(frozen=True, kw_only=True, eq=False)
12
+ class MetaclassSpec(lang.Final):
13
+ confer: frozenset[str] = frozenset()
14
+
15
+ final_subclasses: bool = False
16
+
17
+ abstract_immediate_subclasses: bool = False
18
+
19
+
20
+ DEFAULT_METACLASS_SPEC = MetaclassSpec()
21
+
22
+
23
+ ##
24
+
25
+
26
+ def get_metaclass_spec(obj: type | ClassSpec) -> MetaclassSpec:
27
+ cs: ClassSpec
28
+ if isinstance(obj, type):
29
+ if (cs := get_class_spec(obj)) is None: # type: ignore[assignment]
30
+ return DEFAULT_METACLASS_SPEC
31
+
32
+ elif isinstance(obj, ClassSpec):
33
+ cs = obj
34
+
35
+ else:
36
+ raise TypeError(obj)
37
+
38
+ return cs.get_last_metadata(MetaclassSpec, DEFAULT_METACLASS_SPEC)
File without changes