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,48 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ..internals import STD_FIELDS_ATTR
5
+ from ..internals import StdFieldType
6
+ from ..internals import std_field_type
7
+ from ..internals import std_is_dataclass_instance
8
+ from ..processing.base import Processor
9
+ from ..processing.priority import ProcessorPriority
10
+ from ..processing.registry import register_processor_type
11
+ from ..utils import set_new_attribute
12
+
13
+
14
+ T = ta.TypeVar('T')
15
+
16
+
17
+ ##
18
+
19
+
20
+ def replace(obj, /, **changes): # noqa
21
+ if not std_is_dataclass_instance(obj):
22
+ raise TypeError('replace() should be called on dataclass instances')
23
+ return _replace(obj, **changes)
24
+
25
+
26
+ def _replace(obj, /, **changes):
27
+ for f in getattr(obj, STD_FIELDS_ATTR).values():
28
+ if (ft := std_field_type(f)) is StdFieldType.CLASS_VAR:
29
+ continue
30
+
31
+ if not f.init:
32
+ if f.name in changes:
33
+ raise TypeError(f'field {f.name} is declared with init=False, it cannot be specified with replace()')
34
+ continue
35
+
36
+ if f.name not in changes:
37
+ if ft is StdFieldType.INIT_VAR and f.default is dc.MISSING:
38
+ raise TypeError(f'InitVar {f.name!r} must be specified with replace()')
39
+ changes[f.name] = getattr(obj, f.name)
40
+
41
+ return obj.__class__(**changes)
42
+
43
+
44
+ @register_processor_type(priority=ProcessorPriority.POST_GENERATION)
45
+ class ReplaceProcessor(Processor):
46
+ def process(self, cls: type) -> type:
47
+ set_new_attribute(cls, '__replace__', _replace)
48
+ return cls
@@ -0,0 +1,95 @@
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.globals import REPRLIB_RECURSIVE_REPR_GLOBAL
8
+ from ..generation.ops import AddMethodOp
9
+ from ..generation.ops import Op
10
+ from ..generation.ops import OpRef
11
+ from ..generation.ops import Ref
12
+ from ..generation.registry import register_generator_type
13
+ from ..processing.base import ProcessingContext
14
+ from ..specs import FieldType
15
+ from ..specs import ReprFn
16
+
17
+
18
+ ##
19
+
20
+
21
+ @dc.dataclass(frozen=True)
22
+ class ReprPlan(Plan):
23
+ fields: tuple[str, ...]
24
+
25
+ @dc.dataclass(frozen=True)
26
+ class Fn:
27
+ field: str
28
+ fn: OpRef[ReprFn]
29
+
30
+ fns: tuple[Fn, ...] = ()
31
+
32
+ id: bool = False
33
+
34
+
35
+ @register_generator_type(ReprPlan)
36
+ class ReprGenerator(Generator[ReprPlan]):
37
+ def plan(self, ctx: ProcessingContext) -> PlanResult[ReprPlan] | None:
38
+ if not ctx.cs.repr or '__repr__' in ctx.cls.__dict__:
39
+ return None
40
+
41
+ fs = sorted(ctx.cs.fields, key=lambda f: f.repr_priority or 0)
42
+
43
+ orm = {}
44
+ rfs: list[ReprPlan.Fn] = []
45
+ for i, f in enumerate(fs):
46
+ if f.repr_fn is None:
47
+ continue
48
+ r: OpRef = OpRef(f'repr.fns.{i}.fn')
49
+ orm[r] = f.repr_fn
50
+ rfs.append(ReprPlan.Fn(f.name, r))
51
+
52
+ return PlanResult(
53
+ ReprPlan(
54
+ fields=tuple(f.name for f in fs if f.field_type is FieldType.INSTANCE and f.repr),
55
+ fns=tuple(rfs),
56
+ id=ctx.cs.repr_id,
57
+ ),
58
+ orm,
59
+ )
60
+
61
+ def generate(self, pl: ReprPlan) -> ta.Iterable[Op]:
62
+ ors: set[Ref] = {REPRLIB_RECURSIVE_REPR_GLOBAL}
63
+
64
+ part_lines: list[str] = []
65
+
66
+ rfd = {rf.field: rf.fn for rf in pl.fns}
67
+ for f in pl.fields:
68
+ if (rf := rfd.get(f)) is not None:
69
+ ors.add(rf)
70
+ part_lines.extend([
71
+ f' if (s := {rf.ident()}(self.{f})) is not None:',
72
+ f' parts.append(f"{f}={{s}}")',
73
+ ])
74
+ else:
75
+ part_lines.append(
76
+ f' parts.append(f"{f}={{self.{f}!r}}")',
77
+ )
78
+
79
+ return [
80
+ AddMethodOp(
81
+ '__repr__',
82
+ '\n'.join([
83
+ f'@{REPRLIB_RECURSIVE_REPR_GLOBAL.ident}()',
84
+ f'def __repr__(self):',
85
+ f' parts = []',
86
+ *part_lines,
87
+ f' return (',
88
+ f' f"{{self.__class__.__qualname__}}{'@{hex(id(self))[2:]}' if pl.id else ''}("',
89
+ f' f"{{\', \'.join(parts)}}"',
90
+ f' f")"',
91
+ f' )',
92
+ ]),
93
+ frozenset(ors),
94
+ ),
95
+ ]
@@ -3,8 +3,12 @@ import inspect
3
3
  import itertools
4
4
  import types
5
5
 
6
+ from ..processing.base import Processor
7
+ from ..processing.priority import ProcessorPriority
8
+ from ..processing.registry import register_processor_type
6
9
 
7
- MISSING = dc.MISSING
10
+
11
+ ##
8
12
 
9
13
 
10
14
  def _dataclass_getstate(self):
@@ -133,3 +137,23 @@ def add_slots(
133
137
  break
134
138
 
135
139
  return newcls
140
+
141
+
142
+ ##
143
+
144
+
145
+ @register_processor_type(priority=ProcessorPriority.SLOTS)
146
+ class SlotsProcessor(Processor):
147
+ def check(self) -> None:
148
+ if self._ctx.cs.weakref_slot and not self._ctx.cs.slots:
149
+ raise TypeError('weakref_slot is True but slots is False')
150
+
151
+ def process(self, cls: type) -> type:
152
+ if not self._ctx.cs.slots:
153
+ return cls
154
+
155
+ return add_slots(
156
+ cls,
157
+ is_frozen=self._ctx.cs.frozen,
158
+ weakref_slot=self._ctx.cs.weakref_slot,
159
+ )
@@ -0,0 +1,2 @@
1
+ DEBUG = False
2
+ # DEBUG = True
@@ -0,0 +1,115 @@
1
+ import types
2
+ import typing as ta
3
+
4
+
5
+ ##
6
+
7
+
8
+ def _hands_off_repr(obj: ta.Any) -> str:
9
+ return f'{obj.__class__.__qualname__}@{hex(id(obj))[2:]}'
10
+
11
+
12
+ def _fn_repr(fn: ta.Callable) -> str:
13
+ if (co := getattr(fn, '__code__', None)) is None or not isinstance(co, types.CodeType):
14
+ return repr(fn)
15
+
16
+ if not (co_filename := co.co_filename):
17
+ return repr(fn)
18
+
19
+ return f'{fn!r} ({co_filename}:{co.co_firstlineno})'
20
+
21
+
22
+ ##
23
+
24
+
25
+ class ValidationError(Exception):
26
+ def __init__(
27
+ self,
28
+ *,
29
+ obj: ta.Any,
30
+ ) -> None:
31
+ self.obj = obj
32
+
33
+ super().__init__(
34
+ f'{self.__class__.__name__} '
35
+ f'{", ".join(f"{k} {v}" for k, v in self._message_parts.items())}',
36
+ )
37
+
38
+ @property
39
+ def _message_parts(self) -> ta.Mapping[str, str]:
40
+ return {
41
+ 'obj': _hands_off_repr(self.obj),
42
+ }
43
+
44
+ def __repr__(self) -> str:
45
+ return f'{self.__class__.__name__}({", ".join([
46
+ f"{k}={v}" for k, v in self._message_parts.items()
47
+ ])})'
48
+
49
+
50
+ class FnValidationError(ValidationError):
51
+ def __init__(
52
+ self,
53
+ *,
54
+ fn: ta.Callable,
55
+ **kwargs: ta.Any,
56
+ ) -> None:
57
+ self.fn = fn
58
+
59
+ super().__init__(**kwargs)
60
+
61
+ @property
62
+ def _message_parts(self) -> ta.Mapping[str, str]:
63
+ return {
64
+ **super()._message_parts,
65
+ 'fn': _fn_repr(self.fn),
66
+ }
67
+
68
+
69
+ class FieldValidationError(ValidationError):
70
+ def __init__(
71
+ self,
72
+ *,
73
+ field: str,
74
+ value: ta.Any,
75
+ **kwargs: ta.Any,
76
+ ) -> None:
77
+ self.field = field
78
+ self.value = value
79
+
80
+ super().__init__(**kwargs)
81
+
82
+ @property
83
+ def _message_parts(self) -> ta.Mapping[str, str]:
84
+ return {
85
+ **super()._message_parts,
86
+ 'field': repr(self.field),
87
+ 'value': repr(self.value),
88
+ }
89
+
90
+
91
+ class FieldFnValidationError(FieldValidationError, FnValidationError):
92
+ pass
93
+
94
+
95
+ class TypeValidationError(ValidationError, TypeError):
96
+ def __init__(
97
+ self,
98
+ *,
99
+ type: ta.Any, # noqa
100
+ **kwargs: ta.Any,
101
+ ) -> None:
102
+ self.type = type
103
+
104
+ super().__init__(**kwargs)
105
+
106
+ @property
107
+ def _message_parts(self) -> ta.Mapping[str, str]:
108
+ return {
109
+ **super()._message_parts,
110
+ 'type': repr(self.type),
111
+ }
112
+
113
+
114
+ class FieldTypeValidationError(FieldValidationError, TypeValidationError):
115
+ pass
File without changes
@@ -0,0 +1,38 @@
1
+ import abc
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+ from ..processing.base import ProcessingContext
6
+ from .ops import Op
7
+ from .ops import OpRefMap
8
+
9
+
10
+ T = ta.TypeVar('T')
11
+ PlanT = ta.TypeVar('PlanT')
12
+
13
+
14
+ ##
15
+
16
+
17
+ @dc.dataclass(frozen=True)
18
+ class Plan(abc.ABC): # noqa
19
+ pass
20
+
21
+
22
+ ##
23
+
24
+
25
+ @dc.dataclass(frozen=True)
26
+ class PlanResult(ta.Generic[PlanT]):
27
+ plan: PlanT
28
+ ref_map: OpRefMap | None = None
29
+
30
+
31
+ class Generator(abc.ABC, ta.Generic[PlanT]):
32
+ @abc.abstractmethod
33
+ def plan(self, ctx: ProcessingContext) -> PlanResult[PlanT] | None:
34
+ raise NotImplementedError
35
+
36
+ @abc.abstractmethod
37
+ def generate(self, pl: PlanT) -> ta.Iterable[Op]:
38
+ raise NotImplementedError
@@ -0,0 +1,258 @@
1
+ """
2
+ TODO:
3
+ - md5 spec to fn name
4
+ """
5
+ import abc
6
+ import dataclasses as dc
7
+ import typing as ta
8
+
9
+ from ... import check
10
+ from ..utils import repr_round_trip_value
11
+ from .globals import FN_GLOBAL_IMPORTS
12
+ from .globals import FN_GLOBALS
13
+ from .globals import FUNCTION_TYPE_GLOBAL
14
+ from .globals import PROPERTY_GLOBAL
15
+ from .globals import TYPE_ERROR_GLOBAL
16
+ from .idents import CLS_IDENT
17
+ from .idents import IDENT_PREFIX
18
+ from .ops import AddMethodOp
19
+ from .ops import AddPropertyOp
20
+ from .ops import IfAttrPresent
21
+ from .ops import Op
22
+ from .ops import OpRef
23
+ from .ops import Ref
24
+ from .ops import SetAttrOp
25
+ from .ops import get_op_refs
26
+
27
+
28
+ T = ta.TypeVar('T')
29
+
30
+
31
+ ##
32
+
33
+
34
+ class OpCompiler:
35
+ class Style(abc.ABC):
36
+ @abc.abstractmethod
37
+ def header_lines(self) -> ta.Sequence[str]:
38
+ raise NotImplementedError
39
+
40
+ @abc.abstractmethod
41
+ def globals_ns(self) -> ta.Mapping[str, ta.Any]:
42
+ raise NotImplementedError
43
+
44
+ class JitStyle(Style):
45
+ def header_lines(self) -> ta.Sequence[str]:
46
+ return []
47
+
48
+ def globals_ns(self) -> ta.Mapping[str, ta.Any]:
49
+ return dict(FN_GLOBAL_IMPORTS)
50
+
51
+ class AotStyle(Style):
52
+ HEADER_LINES: ta.ClassVar[ta.Sequence[str]] = [
53
+ '# type: ignore',
54
+ '# ruff: noqa',
55
+ '# flake8: noqa',
56
+ '# @omlish-generated',
57
+ ]
58
+
59
+ def header_lines(self) -> ta.Sequence[str]:
60
+ return [
61
+ *self.HEADER_LINES,
62
+ *[
63
+ f'import {i}'
64
+ for i in FN_GLOBAL_IMPORTS
65
+ ],
66
+ '\n',
67
+ ]
68
+
69
+ def globals_ns(self) -> ta.Mapping[str, ta.Any]:
70
+ return {}
71
+
72
+ #
73
+
74
+ def __init__(
75
+ self,
76
+ style: Style,
77
+ ) -> None:
78
+ super().__init__()
79
+
80
+ self._style = style
81
+
82
+ @property
83
+ def style(self) -> Style:
84
+ return self._style
85
+
86
+ @dc.dataclass(frozen=True)
87
+ class CompileResult:
88
+ fn_name: str
89
+ params: ta.Sequence[str]
90
+ src: str
91
+ refs: frozenset[Ref]
92
+
93
+ @dc.dataclass(frozen=True)
94
+ class _FnParam:
95
+ name: str
96
+ src: str | None = None
97
+ noqa: bool = dc.field(default=False, kw_only=True)
98
+
99
+ def _compile_set_attr(
100
+ self,
101
+ attr_name: str,
102
+ value_src: str,
103
+ if_present: IfAttrPresent,
104
+ ) -> list[str]:
105
+ setattr_stmt = f'setattr({CLS_IDENT}, {attr_name!r}, {value_src})'
106
+
107
+ if if_present == 'skip':
108
+ return [
109
+ f'if {attr_name!r} not in {CLS_IDENT}.__dict__:',
110
+ f' {setattr_stmt}',
111
+ ]
112
+
113
+ elif if_present == 'replace':
114
+ return [
115
+ setattr_stmt,
116
+ ]
117
+
118
+ elif if_present == 'error':
119
+ return [
120
+ f'if {attr_name!r} in {CLS_IDENT}.__dict__:',
121
+ (
122
+ f' '
123
+ f'raise {TYPE_ERROR_GLOBAL.ident}'
124
+ f'(f"Cannot overwrite attribute {attr_name} in class {{{CLS_IDENT}.__name__}}")'
125
+ ),
126
+ setattr_stmt,
127
+ ]
128
+
129
+ else:
130
+ raise ValueError(if_present)
131
+
132
+ def compile(
133
+ self,
134
+ fn_name: str,
135
+ ops: ta.Sequence[Op],
136
+ ) -> CompileResult:
137
+ body_lines: list[str] = []
138
+
139
+ for op in ops:
140
+ if isinstance(op, SetAttrOp):
141
+ if isinstance(v := op.value, OpRef):
142
+ vs = v.ident()
143
+ body_lines.extend([
144
+ f'if isinstance({vs}, {FUNCTION_TYPE_GLOBAL.ident}):'
145
+ f' {vs}.__qualname__ = f"{{{CLS_IDENT}.__qualname__}}.{{{vs}.__name__}}"',
146
+ ])
147
+ else:
148
+ vs = repr(repr_round_trip_value(v))
149
+
150
+ body_lines.extend(self._compile_set_attr(
151
+ op.name,
152
+ vs,
153
+ op.if_present,
154
+ ))
155
+
156
+ elif isinstance(op, AddMethodOp):
157
+ body_lines.extend([
158
+ *op.src.splitlines(),
159
+ f'',
160
+ f'{op.name}.__qualname__ = f"{{{CLS_IDENT}.__qualname__}}.{op.name}"',
161
+ *self._compile_set_attr(
162
+ op.name,
163
+ op.name,
164
+ op.if_present,
165
+ ),
166
+ ])
167
+
168
+ elif isinstance(op, AddPropertyOp):
169
+ gen_ident = IDENT_PREFIX + f'property__{op.name}'
170
+
171
+ gen_lines = [
172
+ f'def {gen_ident}():',
173
+ f' @{PROPERTY_GLOBAL.ident}',
174
+ *[
175
+ f' {l}'
176
+ for l in check.not_none(op.get_src).splitlines()
177
+ ],
178
+ ]
179
+ if op.set_src is not None:
180
+ gen_lines.extend([
181
+ f'',
182
+ f' @{op.name}.setter',
183
+ *[
184
+ f' {l}'
185
+ for l in op.set_src.splitlines()
186
+ ],
187
+ ])
188
+ if op.del_src is not None:
189
+ raise NotImplementedError
190
+ gen_lines.extend([
191
+ f'',
192
+ f' return {op.name}',
193
+ ])
194
+
195
+ body_lines.extend([
196
+ *gen_lines,
197
+ f'',
198
+ f'setattr({CLS_IDENT}, {op.name!r}, {gen_ident}())',
199
+ ])
200
+
201
+ else:
202
+ raise TypeError(op)
203
+
204
+ body_lines.append('')
205
+
206
+ #
207
+
208
+ refs = frozenset.union(*[get_op_refs(o) for o in ops])
209
+
210
+ params: list[OpCompiler._FnParam] = [
211
+ OpCompiler._FnParam(CLS_IDENT),
212
+ *[
213
+ OpCompiler._FnParam(p)
214
+ for p in sorted(
215
+ r.ident()
216
+ for r in refs
217
+ if isinstance(r, OpRef)
218
+ )
219
+ ],
220
+ ]
221
+
222
+ params.extend([
223
+ OpCompiler._FnParam(
224
+ k.ident,
225
+ src=f'{k.ident}={v.src}' if not v.src.startswith('.') else k.ident,
226
+ noqa=k.ident != k.ident.lower() or not v.src.startswith('.'),
227
+ )
228
+ for k, v in FN_GLOBALS.items()
229
+ ])
230
+
231
+ lines: list[str] = []
232
+
233
+ lines.extend(self._style.header_lines())
234
+
235
+ lines.extend([
236
+ f'def {fn_name}(',
237
+ f' *,',
238
+ *[
239
+ f' {p.src if p.src is not None else p.name},{" # noqa" if p.noqa else ""}'
240
+ for p in params
241
+ ],
242
+ f'):',
243
+ *[
244
+ f' {l}'
245
+ for l in body_lines
246
+ ],
247
+ ])
248
+
249
+ #
250
+
251
+ src = '\n'.join(lines)
252
+
253
+ return self.CompileResult(
254
+ fn_name,
255
+ [p.name for p in params],
256
+ src,
257
+ refs,
258
+ )