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,120 @@
1
+ import dataclasses as dc
2
+ import types
3
+ import typing as ta
4
+
5
+ from .... import check
6
+ from ...inspect import get_cls_annotations
7
+ from ...internals import STD_FIELDS_ATTR
8
+ from ...internals import StdFieldType
9
+ from ...internals import std_is_classvar
10
+ from ...internals import std_is_initvar
11
+ from ...internals import std_is_kw_only
12
+
13
+
14
+ ##
15
+
16
+
17
+ def build_std_field(
18
+ cls: type,
19
+ a_name: str,
20
+ a_type: ta.Any,
21
+ *,
22
+ default_kw_only: bool,
23
+ ) -> dc.Field:
24
+ default: ta.Any = getattr(cls, a_name, dc.MISSING)
25
+ if isinstance(default, dc.Field):
26
+ f = default
27
+ else:
28
+ if isinstance(default, types.MemberDescriptorType):
29
+ # This is a field in __slots__, so it has no default value.
30
+ default = dc.MISSING
31
+ f = dc.field(default=default)
32
+
33
+ f.name = a_name
34
+ f.type = a_type
35
+
36
+ # field type
37
+
38
+ ft = StdFieldType.INSTANCE
39
+ if std_is_classvar(cls, f.type):
40
+ ft = StdFieldType.CLASS_VAR
41
+ if std_is_initvar(cls, f.type):
42
+ ft = StdFieldType.INIT_VAR
43
+ if ft in (StdFieldType.CLASS_VAR, StdFieldType.INIT_VAR):
44
+ if f.default_factory is not dc.MISSING:
45
+ raise TypeError(f'field {a_name} cannot have a default factory')
46
+ f._field_type = ft.value # type: ignore[attr-defined] # noqa
47
+
48
+ # kw_only
49
+
50
+ if ft in (StdFieldType.INSTANCE, StdFieldType.INIT_VAR):
51
+ if f.kw_only is dc.MISSING:
52
+ f.kw_only = default_kw_only
53
+ else:
54
+ check.arg(ft is StdFieldType.CLASS_VAR)
55
+ if f.kw_only is not dc.MISSING:
56
+ raise TypeError(f'field {a_name} is a ClassVar but specifies kw_only')
57
+
58
+ # defaults
59
+
60
+ if (
61
+ ft is StdFieldType.INSTANCE and
62
+ f.default is not dc.MISSING and
63
+ f.default.__class__.__hash__ is None
64
+ ):
65
+ raise ValueError(f'mutable default {type(f.default)} for field {a_name} is not allowed: use default_factory')
66
+
67
+ #
68
+
69
+ return f
70
+
71
+
72
+ ##
73
+
74
+
75
+ def build_cls_std_fields(
76
+ cls: type,
77
+ *,
78
+ kw_only: bool,
79
+ ) -> ta.Mapping[str, dc.Field]:
80
+ fields: dict[str, dc.Field] = {}
81
+
82
+ for b in cls.__mro__[-1:0:-1]:
83
+ if not (base_fields := getattr(b, STD_FIELDS_ATTR, None)):
84
+ continue
85
+ for f in base_fields.values():
86
+ fields[f.name] = f
87
+
88
+ cls_annotations = get_cls_annotations(cls)
89
+
90
+ cls_fields: list[dc.Field] = []
91
+
92
+ kw_only_seen = False
93
+ for name, ann in cls_annotations.items():
94
+ if std_is_kw_only(cls, ann):
95
+ if kw_only_seen:
96
+ raise TypeError(f'{name!r} is KW_ONLY, but KW_ONLY has already been specified')
97
+ kw_only_seen = True
98
+ kw_only = True
99
+
100
+ else:
101
+ cls_fields.append(build_std_field(
102
+ cls,
103
+ name,
104
+ ann,
105
+ default_kw_only=kw_only,
106
+ ))
107
+
108
+ for f in cls_fields:
109
+ fields[f.name] = f
110
+ if isinstance(getattr(cls, f.name, None), dc.Field):
111
+ if f.default is dc.MISSING:
112
+ delattr(cls, f.name)
113
+ else:
114
+ setattr(cls, f.name, f.default)
115
+
116
+ for name, value in cls.__dict__.items():
117
+ if isinstance(value, dc.Field) and name not in cls_annotations:
118
+ raise TypeError(f'{name!r} is a field but has no type annotation')
119
+
120
+ return fields
@@ -0,0 +1,56 @@
1
+ import collections
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+ from ...specs import CoerceFn
6
+ from ...specs import ReprFn
7
+ from ...specs import ValidateFn
8
+ from .metadata import extra_field_params
9
+
10
+
11
+ ##
12
+
13
+
14
+ def field(
15
+ default=dc.MISSING,
16
+ *,
17
+ default_factory=dc.MISSING,
18
+ init=True,
19
+ repr=True, # noqa
20
+ hash=None, # noqa
21
+ compare=True,
22
+ metadata=None,
23
+ kw_only=dc.MISSING,
24
+
25
+ coerce: bool | CoerceFn | None = None,
26
+ validate: ValidateFn | None = None, # noqa
27
+ check_type: bool | type | tuple[type | None, ...] | None = None,
28
+ override: bool | None = None,
29
+ repr_fn: ReprFn | None = None,
30
+ repr_priority: int | None = None,
31
+ ) -> ta.Any:
32
+ efp = extra_field_params(
33
+ coerce=coerce,
34
+ validate=validate,
35
+ check_type=check_type,
36
+ override=override,
37
+ repr_fn=repr_fn,
38
+ repr_priority=repr_priority,
39
+ )
40
+
41
+ md: ta.Any = metadata
42
+ if md is None:
43
+ md = efp
44
+ else:
45
+ md = collections.ChainMap(efp, md) # type: ignore[arg-type]
46
+
47
+ return dc.field(
48
+ default=default,
49
+ default_factory=default_factory,
50
+ init=init,
51
+ repr=repr,
52
+ hash=hash,
53
+ compare=compare,
54
+ metadata=md,
55
+ kw_only=kw_only,
56
+ )
@@ -0,0 +1,191 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from .... import check
5
+ from .... import lang
6
+ from ...debug import DEBUG
7
+ from ...internals import StdFieldType
8
+ from ...internals import std_field_type
9
+ from ...specs import DefaultFactory
10
+ from ...specs import FieldSpec
11
+ from ...specs import FieldType
12
+ from .metadata import _ExtraFieldParamsMetadata
13
+ from .metadata import set_field_spec_metadata
14
+
15
+
16
+ ##
17
+
18
+
19
+ STD_FIELD_TYPE_BY_SPEC_FIELD_TYPE: ta.Mapping[FieldType, StdFieldType] = {
20
+ FieldType.INSTANCE: StdFieldType.INSTANCE,
21
+ FieldType.CLASS_VAR: StdFieldType.CLASS_VAR,
22
+ FieldType.INIT_VAR: StdFieldType.INIT_VAR,
23
+ }
24
+
25
+ SPEC_FIELD_TYPE_BY_STD_FIELD_TYPE = {v: k for k, v in STD_FIELD_TYPE_BY_SPEC_FIELD_TYPE.items()}
26
+
27
+
28
+ #
29
+
30
+
31
+ def std_to_spec_field_default(
32
+ *,
33
+ default: ta.Any,
34
+ default_factory: ta.Any,
35
+ ) -> lang.Maybe[ta.Any]:
36
+ if default is not dc.MISSING:
37
+ check.state(default_factory is dc.MISSING)
38
+ return lang.just(default)
39
+ elif default_factory is not dc.MISSING:
40
+ return lang.just(DefaultFactory(default_factory))
41
+ else:
42
+ return lang.empty()
43
+
44
+
45
+ def std_field_to_spec_field_default(f: dc.Field) -> lang.Maybe[ta.Any]:
46
+ return std_to_spec_field_default(
47
+ default=f.default,
48
+ default_factory=f.default_factory,
49
+ )
50
+
51
+
52
+ class StdDefaults(ta.NamedTuple):
53
+ default: ta.Any
54
+ default_factory: ta.Any
55
+
56
+
57
+ def spec_field_default_to_std_defaults(dfl: lang.Maybe[DefaultFactory | ta.Any]) -> StdDefaults:
58
+ if not dfl.present:
59
+ return StdDefaults(dc.MISSING, dc.MISSING)
60
+ elif isinstance(dfv := dfl.must(), DefaultFactory):
61
+ return StdDefaults(dc.MISSING, dfv.fn)
62
+ else:
63
+ return StdDefaults(dfv, dc.MISSING)
64
+
65
+
66
+ #
67
+
68
+
69
+ def std_field_to_field_spec(
70
+ f: dc.Field,
71
+ *,
72
+ ignore_metadata: bool = False,
73
+ ignore_extra_params: bool = False,
74
+ set_metadata: bool = False,
75
+ ) -> FieldSpec:
76
+ if not ignore_metadata:
77
+ try:
78
+ fs = f.metadata[FieldSpec]
79
+ except KeyError:
80
+ pass
81
+ else:
82
+ check_field_spec_against_field(f, fs)
83
+ return fs
84
+
85
+ extra_params = {}
86
+ if not ignore_extra_params:
87
+ extra_params.update(f.metadata.get(_ExtraFieldParamsMetadata, {}))
88
+
89
+ fs = FieldSpec(
90
+ name=check.non_empty_str(f.name),
91
+ annotation=check.is_not(f.type, dc.MISSING),
92
+
93
+ default=std_field_to_spec_field_default(f),
94
+
95
+ init=check.isinstance(f.init, bool) if DEBUG else f.init,
96
+ repr=check.isinstance(f.repr, bool) if DEBUG else f.repr,
97
+ hash=check.isinstance(f.hash, (bool, None)) if DEBUG else f.hash,
98
+ compare=check.isinstance(f.compare, bool) if DEBUG else f.compare,
99
+ metadata=f.metadata,
100
+ kw_only=None if f.kw_only is dc.MISSING else (check.isinstance(f.kw_only, bool) if DEBUG else f.kw_only),
101
+
102
+ **lang.opt_kw(
103
+ coerce=extra_params.get('coerce'),
104
+ validate=extra_params.get('validate'),
105
+ check_type=extra_params.get('check_type'),
106
+ override=extra_params.get('override'),
107
+ repr_fn=extra_params.get('repr_fn'),
108
+ repr_priority=extra_params.get('repr_priority'),
109
+ ),
110
+
111
+ field_type=SPEC_FIELD_TYPE_BY_STD_FIELD_TYPE[std_field_type(f)],
112
+ )
113
+
114
+ if set_metadata:
115
+ set_field_spec_metadata(f, fs)
116
+
117
+ return fs
118
+
119
+
120
+ ##
121
+
122
+
123
+ def field_spec_to_std_field(fs: FieldSpec) -> dc.Field:
124
+ sdf = spec_field_default_to_std_defaults(fs.default)
125
+
126
+ f = dc.Field(
127
+ default=sdf.default,
128
+ default_factory=sdf.default_factory,
129
+
130
+ init=fs.init,
131
+ repr=fs.repr,
132
+ hash=fs.hash,
133
+ compare=fs.compare,
134
+ **lang.opt_kw(metadata=fs.metadata),
135
+ kw_only=dc.MISSING if fs.kw_only is None else fs.kw_only, # type: ignore[arg-type]
136
+ )
137
+
138
+ f.name = fs.name
139
+ f.type = fs.annotation
140
+
141
+ f._field_type = STD_FIELD_TYPE_BY_SPEC_FIELD_TYPE[fs.field_type].value # type: ignore[attr-defined] # noqa
142
+
143
+ set_field_spec_metadata(f, fs)
144
+
145
+ return f
146
+
147
+
148
+ ##
149
+
150
+
151
+ def check_field_spec_against_field(f: dc.Field, fs: FieldSpec) -> None:
152
+ f_dct = {
153
+ 'name': f.name,
154
+ 'type': f.type,
155
+
156
+ 'default': f.default,
157
+ 'default_factory': f.default_factory,
158
+
159
+ 'repr': f.repr,
160
+ 'hash': f.hash,
161
+ 'init': f.init,
162
+ 'compare': f.compare,
163
+ # f.metadata,
164
+ 'kw_only': f.kw_only if f.kw_only is not dc.MISSING else None,
165
+
166
+ 'std_field_type': f._field_type, # type: ignore[attr-defined] # noqa
167
+ }
168
+
169
+ fs_dct = {
170
+ 'name': fs.name,
171
+ 'type': fs.annotation,
172
+
173
+ **spec_field_default_to_std_defaults(fs.default)._asdict(),
174
+
175
+ 'repr': fs.repr,
176
+ 'hash': fs.hash,
177
+ 'init': fs.init,
178
+ 'compare': fs.compare,
179
+ # fs.metadata,
180
+ 'kw_only': fs.kw_only,
181
+
182
+ 'std_field_type': STD_FIELD_TYPE_BY_SPEC_FIELD_TYPE[fs.field_type].value,
183
+ }
184
+
185
+ if f_dct != fs_dct:
186
+ diff_dct = {
187
+ k: (f_v, fs_v)
188
+ for k, f_v in f_dct.items()
189
+ if (fs_v := fs_dct[k]) != f_v
190
+ }
191
+ raise RuntimeError(f'Field/FieldSpec mismatch: {diff_dct!r}')
@@ -0,0 +1,94 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from .... import check
5
+ from .... import lang
6
+ from ...debug import DEBUG
7
+ from ...specs import FieldSpec
8
+ from ...utils import chain_mapping_proxy
9
+
10
+
11
+ ##
12
+
13
+
14
+ class _ExtraFieldParamsMetadata(lang.Marker):
15
+ pass
16
+
17
+
18
+ def extra_field_params(**kwargs: ta.Any) -> ta.Mapping[ta.Any, ta.Any]:
19
+ return {_ExtraFieldParamsMetadata: kwargs}
20
+
21
+
22
+ ##
23
+
24
+
25
+ def get_field_spec(f: dc.Field) -> FieldSpec | None:
26
+ try:
27
+ fs = f.metadata[FieldSpec]
28
+ except KeyError:
29
+ return None
30
+
31
+ return check.isinstance(fs, FieldSpec)
32
+
33
+
34
+ ##
35
+
36
+
37
+ def set_field_metadata(
38
+ f: dc.Field,
39
+ metadata: ta.Mapping[ta.Any, ta.Any],
40
+ ) -> dc.Field:
41
+ if DEBUG:
42
+ check.isinstance(f, dc.Field)
43
+
44
+ md: ta.Any = f.metadata
45
+
46
+ mdu: dict = {}
47
+ for k, v in metadata.items():
48
+ if md is None or md.get(k) != v:
49
+ mdu[k] = v # noqa
50
+ if not mdu:
51
+ return f
52
+
53
+ if md is None:
54
+ ms = [mdu]
55
+ else:
56
+ ms = [mdu, md]
57
+
58
+ f.metadata = chain_mapping_proxy(*ms)
59
+ return f
60
+
61
+
62
+ #
63
+
64
+
65
+ def set_field_spec_metadata(
66
+ f: dc.Field,
67
+ fs: FieldSpec,
68
+ ) -> None:
69
+ set_field_metadata(
70
+ f,
71
+ {
72
+ FieldSpec: fs,
73
+ _ExtraFieldParamsMetadata: {},
74
+ },
75
+ )
76
+
77
+
78
+ ##
79
+
80
+
81
+ def update_extra_field_params(
82
+ f: dc.Field,
83
+ *,
84
+ unless_non_default: bool = False,
85
+ **kwargs: ta.Any,
86
+ ) -> dc.Field:
87
+ fe = f.metadata.get(_ExtraFieldParamsMetadata, {})
88
+ return set_field_metadata(f, {
89
+ _ExtraFieldParamsMetadata: {
90
+ **(fe if not unless_non_default else {}),
91
+ **kwargs,
92
+ **(fe if unless_non_default else {}),
93
+ },
94
+ })
@@ -0,0 +1,17 @@
1
+ from . import ( # noqa
2
+ abc,
3
+ copy,
4
+ doc,
5
+ eq,
6
+ fields,
7
+ frozen,
8
+ hash, # noqa
9
+ init,
10
+ matchargs,
11
+ order,
12
+ override,
13
+ params,
14
+ replace,
15
+ repr, # noqa
16
+ slots,
17
+ )
@@ -0,0 +1,15 @@
1
+ import abc
2
+
3
+ from ..processing.base import Processor
4
+ from ..processing.priority import ProcessorPriority
5
+ from ..processing.registry import register_processor_type
6
+
7
+
8
+ ##
9
+
10
+
11
+ @register_processor_type(priority=ProcessorPriority.POST_SLOTS)
12
+ class UpdateAbstractMethodsProcessor(Processor):
13
+ def process(self, cls: type) -> type:
14
+ abc.update_abstractmethods(cls) # noqa
15
+ return cls
@@ -0,0 +1,63 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ..generation.base import Generator
5
+ from ..generation.base import Plan
6
+ from ..generation.base import PlanResult
7
+ from ..generation.idents import CLS_IDENT
8
+ from ..generation.ops import AddMethodOp
9
+ from ..generation.ops import Op
10
+ from ..generation.registry import register_generator_type
11
+ from ..generation.utils import build_attr_kwargs_body_src_lines
12
+ from ..processing.base import ProcessingContext
13
+ from ..specs import FieldType
14
+
15
+
16
+ ##
17
+
18
+
19
+ @dc.dataclass(frozen=True)
20
+ class CopyPlan(Plan):
21
+ fields: tuple[str, ...]
22
+
23
+
24
+ @register_generator_type(CopyPlan)
25
+ class CopyGenerator(Generator[CopyPlan]):
26
+ def plan(self, ctx: ProcessingContext) -> PlanResult[CopyPlan] | None:
27
+ if '__copy__' in ctx.cls.__dict__:
28
+ return None
29
+
30
+ return PlanResult(CopyPlan(
31
+ tuple(f.name for f in ctx.cs.fields if f.field_type is not FieldType.CLASS_VAR),
32
+ ))
33
+
34
+ def generate(self, pl: CopyPlan) -> ta.Iterable[Op]:
35
+ return_lines: list[str]
36
+ if pl.fields:
37
+ return_lines = [
38
+ f' return {CLS_IDENT}( # noqa',
39
+ *build_attr_kwargs_body_src_lines(
40
+ 'self',
41
+ *pl.fields,
42
+ prefix=' ',
43
+ ),
44
+ f' )',
45
+ ]
46
+ else:
47
+ return_lines = [
48
+ f' return {CLS_IDENT}() # noqa',
49
+ ]
50
+
51
+ lines = [
52
+ f'def __copy__(self):',
53
+ f' if self.__class__ is not {CLS_IDENT}:',
54
+ f' raise TypeError(self)',
55
+ *return_lines,
56
+ ]
57
+
58
+ return [
59
+ AddMethodOp(
60
+ '__copy__',
61
+ '\n'.join(lines),
62
+ ),
63
+ ]
@@ -0,0 +1,53 @@
1
+ import inspect
2
+
3
+ from ..processing.base import ProcessingContext
4
+ from ..processing.base import Processor
5
+ from ..processing.priority import ProcessorPriority
6
+ from ..processing.registry import register_processor_type
7
+
8
+
9
+ ##
10
+
11
+
12
+ def _build_cls_doc(cls: type) -> str:
13
+ try:
14
+ text_sig = str(inspect.signature(cls)).replace(' -> None', '')
15
+ except (TypeError, ValueError):
16
+ text_sig = ''
17
+ return cls.__name__ + text_sig
18
+
19
+
20
+ class _LazyClsDocDescriptor:
21
+ def __get__(self, instance, owner):
22
+ if instance is not None:
23
+ owner = instance.__class__
24
+ if not owner:
25
+ raise RuntimeError
26
+ doc = _build_cls_doc(owner)
27
+ owner.__doc__ = doc
28
+ return doc
29
+
30
+
31
+ @register_processor_type(priority=ProcessorPriority.POST_GENERATION)
32
+ class DocProcessor(Processor):
33
+ def __init__(
34
+ self,
35
+ ctx: ProcessingContext,
36
+ *,
37
+ lazy: bool = True,
38
+ ) -> None:
39
+ super().__init__(ctx)
40
+
41
+ self._lazy = lazy
42
+
43
+ def process(self, cls: type) -> type:
44
+ # FIXME: doesn't update doc in subclasses lol, as per stdlib
45
+ if getattr(cls, '__doc__'):
46
+ return cls
47
+
48
+ if self._lazy:
49
+ cls.__doc__ = _LazyClsDocDescriptor() # type: ignore
50
+ else:
51
+ cls.__doc__ = _build_cls_doc(cls)
52
+
53
+ return cls
@@ -0,0 +1,60 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ..generation.base import Generator
5
+ from ..generation.base import Plan
6
+ from ..generation.base import PlanResult
7
+ from ..generation.ops import AddMethodOp
8
+ from ..generation.ops import Op
9
+ from ..generation.registry import register_generator_type
10
+ from ..processing.base import ProcessingContext
11
+ from .fields import InstanceFields
12
+
13
+
14
+ ##
15
+
16
+
17
+ @dc.dataclass(frozen=True)
18
+ class EqPlan(Plan):
19
+ fields: tuple[str, ...]
20
+
21
+
22
+ @register_generator_type(EqPlan)
23
+ class EqGenerator(Generator[EqPlan]):
24
+ def plan(self, ctx: ProcessingContext) -> PlanResult[EqPlan] | None:
25
+ if not ctx.cs.eq or '__eq__' in ctx.cls.__dict__:
26
+ return None
27
+
28
+ return PlanResult(EqPlan(
29
+ tuple(f.name for f in ctx[InstanceFields] if f.compare),
30
+ ))
31
+
32
+ def generate(self, pl: EqPlan) -> ta.Iterable[Op]:
33
+ ret_lines: list[str]
34
+ if pl.fields:
35
+ ret_lines = [
36
+ f' return (',
37
+ *[
38
+ f' self.{a} == other.{a}{" and" if i < len(pl.fields) - 1 else ""}'
39
+ for i, a in enumerate(pl.fields)
40
+ ],
41
+ f' )',
42
+ ]
43
+ else:
44
+ ret_lines = [
45
+ f' return True',
46
+ ]
47
+
48
+ return [
49
+ AddMethodOp(
50
+ '__eq__',
51
+ '\n'.join([
52
+ f'def __eq__(self, other):',
53
+ f' if self is other:',
54
+ f' return True',
55
+ f' if self.__class__ is not other.__class__:',
56
+ f' return NotImplemented',
57
+ *ret_lines,
58
+ ]),
59
+ ),
60
+ ]