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
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev284'
2
- __revision__ = 'e10caaddb37a1fedcbe1bf3e711062e82a8cc7cb'
1
+ __version__ = '0.0.0.dev286'
2
+ __revision__ = '07fa507e06f16ab8f3ccba3d580382b07079b73e'
3
3
 
4
4
 
5
5
  #
@@ -106,6 +106,10 @@ class Project(ProjectBase):
106
106
  'duckdb ~= 1.2',
107
107
  ],
108
108
 
109
+ 'templates': [
110
+ 'jinja2 ~= 3.1',
111
+ ],
112
+
109
113
  'testing': [
110
114
  'pytest ~= 8.3',
111
115
  ],
@@ -1,3 +1,6 @@
1
+ ##
2
+ # stdlib interface
3
+
1
4
  from dataclasses import ( # noqa
2
5
  FrozenInstanceError,
3
6
 
@@ -15,46 +18,28 @@ from dataclasses import ( # noqa
15
18
  fields,
16
19
 
17
20
  is_dataclass,
18
-
19
- # asdict,
20
- # astuple,
21
-
22
- # replace,
23
21
  )
24
22
 
25
- from .impl.api import ( # noqa
26
- field as xfield,
27
-
23
+ from .api import ( # noqa
28
24
  dataclass as xdataclass,
25
+
29
26
  make_dataclass as xmake_dataclass,
30
27
 
31
- extra_class_params,
28
+ field as xfield,
32
29
  )
33
30
 
34
- from .impl.as_ import ( # noqa
31
+ from .tools.as_ import ( # noqa
35
32
  asdict,
36
33
  astuple,
37
34
  )
38
35
 
39
- from .impl.params import ( # noqa
40
- FieldExtras,
41
- get_field_extras,
42
-
43
- get_params,
44
-
45
- get_params_extras,
46
-
47
- MetaclassParams,
48
- get_metaclass_params,
49
- )
50
-
51
- from .impl.replace import ( # noqa
36
+ from .concerns.replace import ( # noqa
52
37
  replace,
53
38
  )
54
39
 
55
40
 
56
41
  ##
57
-
42
+ # globals hack
58
43
 
59
44
  globals()['field'] = xfield
60
45
 
@@ -63,69 +48,82 @@ globals()['make_dataclass'] = xmake_dataclass
63
48
 
64
49
 
65
50
  ##
51
+ # additional interface
66
52
 
53
+ from .api import ( # noqa
54
+ append_class_metadata,
55
+ extra_class_params,
56
+ init,
57
+ metadata,
58
+ validate,
67
59
 
68
- from .impl.errors import ( # noqa
60
+ extra_field_params,
61
+ set_field_metadata,
62
+ update_extra_field_params,
63
+ )
64
+
65
+ from .errors import ( # noqa
66
+ FieldFnValidationError,
67
+ FieldTypeValidationError,
69
68
  FieldValidationError,
69
+ FnValidationError,
70
+ TypeValidationError,
70
71
  ValidationError,
71
72
  )
72
73
 
73
- from .impl.metaclass import ( # noqa
74
- DataMeta,
74
+ from .metaclass.bases import ( # noqa
75
+ Box,
76
+ Case,
75
77
  Data,
76
78
  Frozen,
77
- Case,
78
- Box,
79
79
  )
80
80
 
81
- from .impl.metadata import ( # noqa
82
- Metadata,
83
-
84
- get_merged_metadata,
85
-
86
- UserMetadata,
87
- metadata,
88
-
89
- Validate,
90
- validate,
81
+ from .metaclass.meta import ( # noqa
82
+ DataMeta,
83
+ )
91
84
 
92
- Init,
93
- init,
85
+ from .metaclass.specs import ( # noqa
86
+ get_metaclass_spec,
94
87
  )
95
88
 
96
- from .impl.reflect import ( # noqa
97
- ClassInfo,
89
+ from .reflection import ( # noqa
98
90
  reflect,
99
91
  )
100
92
 
101
- from .static import ( # noqa
102
- Static,
93
+ from .tools.as_ import ( # noqa
94
+ shallow_asdict,
95
+ shallow_astuple,
103
96
  )
104
97
 
105
- from .utils import ( # noqa
106
- opt_repr,
107
- truthy_repr,
108
-
98
+ from .tools.iter import ( # noqa
109
99
  fields_dict,
100
+
101
+ iter_items,
102
+ iter_keys,
103
+ iter_values,
104
+ )
105
+
106
+ from .tools.modifiers import ( # noqa
110
107
  field_modifier,
111
- chain_metadata,
112
- update_class_metadata,
113
- update_field_metadata,
114
- update_field_extras,
115
108
  update_fields,
116
- update_fields_metadata,
117
-
118
- shallow_astuple,
119
- shallow_asdict,
109
+ )
120
110
 
111
+ from .tools.replace import ( # noqa
121
112
  deep_replace,
113
+ )
122
114
 
123
- iter_items,
124
- iter_keys,
125
- iter_values,
115
+ from .tools.repr import ( # noqa
116
+ opt_repr,
117
+ truthy_repr,
126
118
  )
127
119
 
120
+ from .tools.static import ( # noqa
121
+ Static,
122
+ )
123
+
124
+
128
125
  ##
126
+ # lite imports
129
127
 
130
128
  from ..lite.dataclasses import ( # noqa
131
129
  is_immediate_dataclass,
@@ -0,0 +1,25 @@
1
+ from .classes.decorator import ( # noqa
2
+ dataclass,
3
+ )
4
+
5
+ from .classes.make import ( # noqa
6
+ make_dataclass,
7
+ )
8
+
9
+ from .classes.metadata import ( # noqa
10
+ append_class_metadata,
11
+ extra_class_params,
12
+ init,
13
+ metadata,
14
+ validate,
15
+ )
16
+
17
+ from .fields.metadata import ( # noqa
18
+ extra_field_params,
19
+ set_field_metadata,
20
+ update_extra_field_params,
21
+ )
22
+
23
+ from .fields.constructor import ( # noqa
24
+ field,
25
+ )
File without changes
@@ -0,0 +1,30 @@
1
+ import typing as ta
2
+
3
+ from .... import check
4
+ from ...internals import StdParams
5
+ from ...specs import ClassSpec
6
+ from ...specs import FieldSpec
7
+
8
+
9
+ #
10
+
11
+
12
+ def std_params_to_class_spec(
13
+ p: StdParams,
14
+ fields: ta.Sequence[FieldSpec],
15
+ ) -> ClassSpec:
16
+ return ClassSpec(
17
+ fields=fields,
18
+
19
+ init=check.isinstance(p.init, bool),
20
+ repr=check.isinstance(p.repr, bool),
21
+ eq=check.isinstance(p.eq, bool),
22
+ order=check.isinstance(p.order, bool),
23
+ unsafe_hash=check.isinstance(p.unsafe_hash, bool),
24
+ frozen=check.isinstance(p.frozen, bool),
25
+
26
+ match_args=check.isinstance(p.match_args, bool),
27
+ kw_only=check.isinstance(p.kw_only, bool),
28
+ slots=check.isinstance(p.slots, bool),
29
+ weakref_slot=check.isinstance(p.weakref_slot, bool),
30
+ )
@@ -0,0 +1,145 @@
1
+ """
2
+ TODO:
3
+ - collect init_fn's / validate_fns from superclass ClassSpecs
4
+ """
5
+ import inspect
6
+ import typing as ta
7
+
8
+ from .... import check
9
+ from .... import lang
10
+ from ...internals import STD_FIELDS_ATTR
11
+ from ...internals import STD_PARAMS_ATTR
12
+ from ...processing.driving import drive_cls_processing
13
+ from ...specs import ClassSpec
14
+ from ...utils import class_decorator
15
+ from ..fields.building import build_cls_std_fields
16
+ from ..fields.conversion import std_field_to_field_spec
17
+ from .metadata import extract_cls_metadata
18
+ from .params import build_spec_std_params
19
+
20
+
21
+ ##
22
+
23
+
24
+ @class_decorator
25
+ def dataclass(
26
+ cls=None,
27
+ /,
28
+ *,
29
+
30
+ init=True,
31
+ repr=True, # noqa
32
+ eq=True,
33
+ order=False,
34
+ unsafe_hash=False,
35
+ frozen=False,
36
+
37
+ match_args=True,
38
+ kw_only=False,
39
+ slots=False,
40
+ weakref_slot=False,
41
+
42
+ #
43
+
44
+ metadata: ta.Sequence[ta.Any] | None = None,
45
+
46
+ reorder: bool | None = None,
47
+ cache_hash: bool | None = None,
48
+ generic_init: bool | None = None,
49
+ override: bool | None = None,
50
+ repr_id: bool | None = None,
51
+
52
+ #
53
+
54
+ _plan_only: bool = False,
55
+ ):
56
+ if isinstance(metadata, ta.Mapping):
57
+ raise TypeError(metadata)
58
+
59
+ #
60
+
61
+ cls = check.not_none(cls)
62
+
63
+ #
64
+
65
+ fields = build_cls_std_fields(
66
+ cls,
67
+ kw_only=kw_only,
68
+ )
69
+ setattr(cls, STD_FIELDS_ATTR, fields)
70
+
71
+ #
72
+
73
+ fsl = [
74
+ std_field_to_field_spec(
75
+ f,
76
+ set_metadata=True,
77
+ )
78
+ for f in fields.values()
79
+ ]
80
+
81
+ #
82
+
83
+ cmd = extract_cls_metadata(cls, deep=True)
84
+
85
+ vfp_lst: list[ClassSpec.ValidateFnWithParams] = []
86
+ for md_vf in cmd.validate_fns or []:
87
+ if isinstance(md_vf, staticmethod):
88
+ md_vf = md_vf.__func__
89
+ vfp_lst.append(ClassSpec.ValidateFnWithParams(
90
+ md_vf,
91
+ [p.name for p in inspect.signature(md_vf).parameters.values()],
92
+ ))
93
+
94
+ #
95
+
96
+ cs = ClassSpec(
97
+ fields=fsl,
98
+
99
+ init=init,
100
+ repr=repr,
101
+ eq=eq,
102
+ order=order,
103
+ unsafe_hash=unsafe_hash,
104
+ frozen=frozen,
105
+
106
+ match_args=match_args,
107
+ kw_only=kw_only,
108
+ slots=slots,
109
+ weakref_slot=weakref_slot,
110
+
111
+ #
112
+
113
+ metadata=(
114
+ *(cmd.user_metadata or []),
115
+ *(metadata or []),
116
+ ) or None,
117
+
118
+ **{
119
+ **(cmd.extra_params or {}),
120
+ **lang.opt_kw(
121
+ reorder=reorder,
122
+ cache_hash=cache_hash,
123
+ generic_init=generic_init,
124
+ override=override,
125
+ repr_id=repr_id,
126
+ ),
127
+ },
128
+
129
+ init_fns=cmd.init_fns or None,
130
+ validate_fns=vfp_lst or None,
131
+ )
132
+
133
+ #
134
+
135
+ std_params = build_spec_std_params(cs)
136
+ check.not_in(STD_PARAMS_ATTR, cls.__dict__)
137
+ setattr(cls, STD_PARAMS_ATTR, std_params)
138
+
139
+ #
140
+
141
+ return drive_cls_processing(
142
+ cls,
143
+ cs,
144
+ plan_only=_plan_only,
145
+ )
@@ -0,0 +1,109 @@
1
+ import contextlib
2
+ import keyword
3
+ import sys
4
+ import types
5
+ import typing as ta
6
+
7
+ from .decorator import dataclass
8
+
9
+
10
+ ##
11
+
12
+
13
+ def make_dataclass( # noqa
14
+ cls_name,
15
+ fields,
16
+ *,
17
+ bases=(),
18
+ namespace=None,
19
+
20
+ init=True,
21
+ repr=True, # noqa
22
+ eq=True,
23
+ order=False,
24
+ unsafe_hash=False,
25
+ frozen=False,
26
+
27
+ match_args=True,
28
+ kw_only=False,
29
+ slots=False,
30
+ weakref_slot=False,
31
+
32
+ module=None,
33
+
34
+ #
35
+
36
+ metadata: ta.Sequence[ta.Any] | None = None,
37
+
38
+ reorder: bool | None = None,
39
+ cache_hash: bool | None = None,
40
+ generic_init: bool | None = None,
41
+ override: bool | None = None,
42
+ repr_id: bool | None = None,
43
+ ):
44
+ if namespace is None:
45
+ namespace = {}
46
+
47
+ seen = set()
48
+ annotations = {}
49
+ defaults = {}
50
+ for item in fields:
51
+ if isinstance(item, str):
52
+ name = item
53
+ tp = 'typing.Any'
54
+ elif len(item) == 2:
55
+ name, tp, = item
56
+ elif len(item) == 3:
57
+ name, tp, spec = item
58
+ defaults[name] = spec
59
+ else:
60
+ raise TypeError(f'Invalid field: {item!r}')
61
+ if not isinstance(name, str) or not name.isidentifier():
62
+ raise TypeError(f'Field names must be valid identifiers: {name!r}')
63
+ if keyword.iskeyword(name):
64
+ raise TypeError(f'Field names must not be keywords: {name!r}')
65
+ if name in seen:
66
+ raise TypeError(f'Field name duplicated: {name!r}')
67
+
68
+ seen.add(name)
69
+ annotations[name] = tp
70
+
71
+ def exec_body_callback(ns):
72
+ ns.update(namespace)
73
+ ns.update(defaults)
74
+ ns['__annotations__'] = annotations
75
+
76
+ cls = types.new_class(cls_name, bases, {}, exec_body_callback)
77
+
78
+ if module is None:
79
+ try:
80
+ module = sys._getframemodulename(1) or '__main__' # type: ignore # noqa
81
+ except AttributeError:
82
+ with contextlib.suppress(AttributeError, ValueError):
83
+ module = sys._getframe(1).f_globals.get('__name__', '__main__') # noqa
84
+ if module is not None:
85
+ cls.__module__ = module
86
+
87
+ return dataclass(
88
+ cls,
89
+ init=init,
90
+ repr=repr,
91
+ eq=eq,
92
+ order=order,
93
+ unsafe_hash=unsafe_hash,
94
+ frozen=frozen,
95
+ match_args=match_args,
96
+ kw_only=kw_only,
97
+ slots=slots,
98
+ weakref_slot=weakref_slot,
99
+
100
+ #
101
+
102
+ metadata=metadata,
103
+
104
+ reorder=reorder,
105
+ cache_hash=cache_hash,
106
+ generic_init=generic_init,
107
+ override=override,
108
+ repr_id=repr_id,
109
+ )
@@ -0,0 +1,133 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from .... import check
5
+ from .... import lang
6
+ from ....lite.dataclasses import is_immediate_dataclass
7
+ from ...specs import InitFn
8
+
9
+
10
+ T = ta.TypeVar('T')
11
+
12
+
13
+ ##
14
+
15
+
16
+ METADATA_ATTR = '__dataclass_metadata__'
17
+
18
+
19
+ def _get_cls_metadata_dct(cls: type) -> dict:
20
+ check.isinstance(cls, type)
21
+ check.arg(not is_immediate_dataclass(cls))
22
+ try:
23
+ return cls.__dict__[METADATA_ATTR]
24
+ except KeyError:
25
+ pass
26
+ dct: dict = {}
27
+ setattr(cls, METADATA_ATTR, dct)
28
+ return dct
29
+
30
+
31
+ def _append_cls_metadata(cls, k, v):
32
+ _get_cls_metadata_dct(cls).setdefault(k, []).append(v)
33
+
34
+
35
+ def _append_cls_dct_metadata(k, *vs):
36
+ lang.get_caller_cls_dct(1).setdefault(METADATA_ATTR, {}).setdefault(k, []).extend(vs)
37
+
38
+
39
+ ##
40
+
41
+
42
+ class _ExtraClassParamsMetadata(lang.Marker):
43
+ pass
44
+
45
+
46
+ def extra_class_params(**kwargs):
47
+ def inner(cls):
48
+ _append_cls_metadata(cls, _ExtraClassParamsMetadata, kwargs)
49
+ return cls
50
+ return inner
51
+
52
+
53
+ ##
54
+
55
+
56
+ class _UserMetadata(lang.Marker):
57
+ pass
58
+
59
+
60
+ def metadata(*objs) -> None:
61
+ _append_cls_dct_metadata(_InitMetadata, *objs)
62
+
63
+
64
+ def append_class_metadata(cls: type[T], *args: ta.Any) -> type[T]:
65
+ _append_cls_metadata(cls, _UserMetadata, *args)
66
+ return cls
67
+
68
+
69
+ #
70
+
71
+
72
+ class _InitMetadata(lang.Marker):
73
+ pass
74
+
75
+
76
+ def init(obj):
77
+ _append_cls_dct_metadata(_InitMetadata, obj)
78
+ return obj
79
+
80
+
81
+ #
82
+
83
+
84
+ class _ValidateMetadata(lang.Marker):
85
+ pass
86
+
87
+
88
+ def validate(obj):
89
+ _append_cls_dct_metadata(_ValidateMetadata, obj)
90
+ return obj
91
+
92
+
93
+ ##
94
+
95
+
96
+ @dc.dataclass(frozen=True, kw_only=True, eq=False)
97
+ class ClassMetadata:
98
+ extra_params: ta.Mapping[str, ta.Any] | None = None
99
+
100
+ user_metadata: ta.Sequence[ta.Any] | None = None
101
+ init_fns: ta.Sequence[InitFn | property] | None = None
102
+ validate_fns: ta.Sequence[ta.Any] | None = None
103
+
104
+
105
+ def extract_cls_metadata(
106
+ cls: type,
107
+ *,
108
+ deep: bool,
109
+ ) -> ClassMetadata:
110
+ extra_params = {}
111
+
112
+ user_metadata: list[ta.Any] = []
113
+ init_fns: list[InitFn | property] = []
114
+ validate_fns: list[ta.Any] = []
115
+
116
+ for b_cls in (cls.__mro__[-2::-1] if deep else (cls,)):
117
+ cls_md_dct = b_cls.__dict__.get(METADATA_ATTR, {})
118
+
119
+ if b_cls is cls:
120
+ for kw in cls_md_dct.get(_ExtraClassParamsMetadata, []):
121
+ extra_params.update(kw)
122
+
123
+ user_metadata.extend(cls_md_dct.get(_UserMetadata) or [])
124
+ init_fns.extend(cls_md_dct.get(_InitMetadata) or [])
125
+ validate_fns.extend(cls_md_dct.get(_ValidateMetadata) or [])
126
+
127
+ return ClassMetadata(
128
+ extra_params=extra_params or None,
129
+
130
+ user_metadata=user_metadata,
131
+ init_fns=init_fns,
132
+ validate_fns=validate_fns,
133
+ )
@@ -0,0 +1,78 @@
1
+ import typing as ta
2
+
3
+ from .... import check
4
+ from ...internals import STD_PARAMS_ATTR
5
+ from ...internals import StdParams
6
+ from ...specs import ClassSpec
7
+
8
+
9
+ ##
10
+
11
+
12
+ class SpecDataclassParams(StdParams):
13
+ __slots__ = (*StdParams.__slots__, '_spec')
14
+
15
+ def __init__(
16
+ self,
17
+ init,
18
+ repr, # noqa
19
+ eq,
20
+ order,
21
+ unsafe_hash,
22
+ frozen,
23
+ match_args,
24
+ kw_only,
25
+ slots,
26
+ weakref_slot,
27
+ *args: ta.Any,
28
+ spec: ClassSpec,
29
+ **kwargs: ta.Any,
30
+ ) -> None:
31
+ super().__init__(
32
+ init,
33
+ repr,
34
+ eq,
35
+ order,
36
+ unsafe_hash,
37
+ frozen,
38
+ match_args,
39
+ kw_only,
40
+ slots,
41
+ weakref_slot,
42
+ *args,
43
+ **kwargs,
44
+ )
45
+
46
+ self._spec = spec
47
+
48
+ def __repr__(self) -> str:
49
+ r = super().__repr__()
50
+ return f'{self.__class__.__name__}{r[r.index("("):]}'
51
+
52
+
53
+ def build_spec_std_params(cs: ClassSpec) -> 'SpecDataclassParams':
54
+ return SpecDataclassParams(
55
+ init=cs.init,
56
+ repr=cs.repr,
57
+ eq=cs.eq,
58
+ order=cs.order,
59
+ unsafe_hash=cs.unsafe_hash,
60
+ frozen=cs.frozen,
61
+
62
+ match_args=cs.match_args,
63
+ kw_only=cs.kw_only,
64
+ slots=cs.slots,
65
+ weakref_slot=cs.weakref_slot,
66
+
67
+ spec=cs,
68
+ )
69
+
70
+
71
+ def get_class_spec(cls: type) -> ClassSpec | None:
72
+ check.isinstance(cls, type)
73
+
74
+ sp = getattr(cls, STD_PARAMS_ATTR)
75
+ if not isinstance(sp, SpecDataclassParams):
76
+ return None
77
+
78
+ return check.isinstance(sp._spec, ClassSpec) # noqa
File without changes