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.
- omlish/__about__.py +2 -2
- omlish/dataclasses/__init__.py +58 -60
- omlish/dataclasses/api/__init__.py +25 -0
- omlish/dataclasses/api/classes/__init__.py +0 -0
- omlish/dataclasses/api/classes/conversion.py +30 -0
- omlish/dataclasses/api/classes/decorator.py +145 -0
- omlish/dataclasses/api/classes/make.py +109 -0
- omlish/dataclasses/api/classes/metadata.py +133 -0
- omlish/dataclasses/api/classes/params.py +78 -0
- omlish/dataclasses/api/fields/__init__.py +0 -0
- omlish/dataclasses/api/fields/building.py +120 -0
- omlish/dataclasses/api/fields/constructor.py +56 -0
- omlish/dataclasses/api/fields/conversion.py +191 -0
- omlish/dataclasses/api/fields/metadata.py +94 -0
- omlish/dataclasses/concerns/__init__.py +17 -0
- omlish/dataclasses/concerns/abc.py +15 -0
- omlish/dataclasses/concerns/copy.py +63 -0
- omlish/dataclasses/concerns/doc.py +53 -0
- omlish/dataclasses/concerns/eq.py +60 -0
- omlish/dataclasses/concerns/fields.py +119 -0
- omlish/dataclasses/concerns/frozen.py +133 -0
- omlish/dataclasses/concerns/hash.py +165 -0
- omlish/dataclasses/concerns/init.py +453 -0
- omlish/dataclasses/concerns/matchargs.py +27 -0
- omlish/dataclasses/concerns/mro.py +16 -0
- omlish/dataclasses/concerns/order.py +87 -0
- omlish/dataclasses/concerns/override.py +98 -0
- omlish/dataclasses/concerns/params.py +14 -0
- omlish/dataclasses/concerns/replace.py +48 -0
- omlish/dataclasses/concerns/repr.py +95 -0
- omlish/dataclasses/{impl → concerns}/slots.py +25 -1
- omlish/dataclasses/debug.py +2 -0
- omlish/dataclasses/errors.py +115 -0
- omlish/dataclasses/generation/__init__.py +0 -0
- omlish/dataclasses/generation/base.py +38 -0
- omlish/dataclasses/generation/compilation.py +258 -0
- omlish/dataclasses/generation/execution.py +195 -0
- omlish/dataclasses/generation/globals.py +83 -0
- omlish/dataclasses/generation/idents.py +6 -0
- omlish/dataclasses/generation/mangling.py +18 -0
- omlish/dataclasses/generation/manifests.py +20 -0
- omlish/dataclasses/generation/ops.py +97 -0
- omlish/dataclasses/generation/plans.py +35 -0
- omlish/dataclasses/generation/processor.py +174 -0
- omlish/dataclasses/generation/registry.py +42 -0
- omlish/dataclasses/generation/utils.py +83 -0
- omlish/dataclasses/{impl/reflect.py → inspect.py} +53 -90
- omlish/dataclasses/{impl/internals.py → internals.py} +26 -32
- omlish/dataclasses/metaclass/__init__.py +0 -0
- omlish/dataclasses/metaclass/bases.py +69 -0
- omlish/dataclasses/metaclass/confer.py +65 -0
- omlish/dataclasses/metaclass/meta.py +115 -0
- omlish/dataclasses/metaclass/specs.py +38 -0
- omlish/dataclasses/processing/__init__.py +0 -0
- omlish/dataclasses/processing/base.py +83 -0
- omlish/dataclasses/processing/driving.py +45 -0
- omlish/dataclasses/processing/priority.py +13 -0
- omlish/dataclasses/processing/registry.py +81 -0
- omlish/dataclasses/reflection.py +81 -0
- omlish/dataclasses/specs.py +224 -0
- omlish/dataclasses/tools/__init__.py +0 -0
- omlish/dataclasses/{impl → tools}/as_.py +23 -8
- omlish/dataclasses/tools/iter.py +27 -0
- omlish/dataclasses/tools/modifiers.py +52 -0
- omlish/dataclasses/tools/replace.py +17 -0
- omlish/dataclasses/tools/repr.py +12 -0
- omlish/dataclasses/{static.py → tools/static.py} +25 -4
- omlish/dataclasses/utils.py +54 -109
- omlish/diag/__init__.py +4 -4
- omlish/inject/impl/origins.py +1 -1
- omlish/lang/cached/function.py +4 -2
- omlish/marshal/objects/dataclasses.py +3 -7
- omlish/marshal/objects/helpers.py +3 -3
- omlish/secrets/marshal.py +1 -1
- omlish/secrets/secrets.py +1 -1
- omlish/sql/queries/base.py +1 -1
- omlish/typedvalues/marshal.py +2 -2
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/RECORD +83 -43
- omlish/dataclasses/impl/LICENSE +0 -279
- omlish/dataclasses/impl/__init__.py +0 -33
- omlish/dataclasses/impl/api.py +0 -278
- omlish/dataclasses/impl/copy.py +0 -30
- omlish/dataclasses/impl/errors.py +0 -53
- omlish/dataclasses/impl/fields.py +0 -245
- omlish/dataclasses/impl/frozen.py +0 -93
- omlish/dataclasses/impl/hashing.py +0 -86
- omlish/dataclasses/impl/init.py +0 -199
- omlish/dataclasses/impl/main.py +0 -93
- omlish/dataclasses/impl/metaclass.py +0 -235
- omlish/dataclasses/impl/metadata.py +0 -75
- omlish/dataclasses/impl/order.py +0 -57
- omlish/dataclasses/impl/overrides.py +0 -53
- omlish/dataclasses/impl/params.py +0 -128
- omlish/dataclasses/impl/processing.py +0 -24
- omlish/dataclasses/impl/replace.py +0 -40
- omlish/dataclasses/impl/repr.py +0 -66
- omlish/dataclasses/impl/simple.py +0 -50
- omlish/dataclasses/impl/utils.py +0 -167
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev284.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
|
-
|
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,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
|
+
)
|