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.
- omlish/__about__.py +6 -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 +179 -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 +49 -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/lang/maybes.py +17 -0
- omlish/lite/maybes.py +17 -0
- 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/text/minja.py +81 -25
- omlish/text/templating.py +116 -0
- omlish/typedvalues/marshal.py +2 -2
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/METADATA +4 -1
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/RECORD +87 -46
- 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.dev286.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from ... import check
|
5
|
+
from ..debug import DEBUG
|
6
|
+
from ..generation.idents import IDENT_PREFIX
|
7
|
+
from ..inspect import FieldsInspection
|
8
|
+
from ..internals import STD_FIELDS_ATTR
|
9
|
+
from ..processing.base import ProcessingContext
|
10
|
+
from ..processing.base import Processor
|
11
|
+
from ..processing.priority import ProcessorPriority
|
12
|
+
from ..processing.registry import register_processing_context_item_factory
|
13
|
+
from ..processing.registry import register_processor_type
|
14
|
+
from ..specs import FieldSpec
|
15
|
+
from ..specs import FieldType
|
16
|
+
|
17
|
+
|
18
|
+
##
|
19
|
+
|
20
|
+
|
21
|
+
class InstanceFields(list[FieldSpec]):
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
def get_instance_fields(fields: ta.Iterable[FieldSpec]) -> InstanceFields:
|
26
|
+
return InstanceFields([f for f in fields if f.field_type is FieldType.INSTANCE])
|
27
|
+
|
28
|
+
|
29
|
+
@register_processing_context_item_factory(InstanceFields)
|
30
|
+
def _instance_fields_processing_context_item_factory(ctx: ProcessingContext) -> InstanceFields:
|
31
|
+
return get_instance_fields(ctx.cs.fields)
|
32
|
+
|
33
|
+
|
34
|
+
##
|
35
|
+
|
36
|
+
|
37
|
+
class InitFields(ta.NamedTuple):
|
38
|
+
all: ta.Sequence[FieldSpec]
|
39
|
+
ordered: ta.Sequence[FieldSpec]
|
40
|
+
std: ta.Sequence[FieldSpec]
|
41
|
+
kw_only: ta.Sequence[FieldSpec]
|
42
|
+
|
43
|
+
|
44
|
+
def calc_init_fields(
|
45
|
+
fields: ta.Iterable[FieldSpec],
|
46
|
+
*,
|
47
|
+
reorder: bool,
|
48
|
+
class_kw_only: bool,
|
49
|
+
) -> InitFields:
|
50
|
+
all_init_fields = [
|
51
|
+
f
|
52
|
+
for f in fields
|
53
|
+
if f.field_type in (FieldType.INSTANCE, FieldType.INIT_VAR)
|
54
|
+
]
|
55
|
+
|
56
|
+
def f_kw_only(f: FieldSpec) -> bool:
|
57
|
+
return f.kw_only if f.kw_only is not None else class_kw_only
|
58
|
+
|
59
|
+
ordered_init_fields = list(all_init_fields)
|
60
|
+
if reorder:
|
61
|
+
ordered_init_fields.sort(key=lambda f: (f.default.present, not f_kw_only(f)))
|
62
|
+
|
63
|
+
std_init_fields = tuple(f1 for f1 in ordered_init_fields if f1.init and not f_kw_only(f1))
|
64
|
+
kw_only_init_fields = tuple(f1 for f1 in ordered_init_fields if f1.init and f_kw_only(f1))
|
65
|
+
|
66
|
+
return InitFields(
|
67
|
+
all=all_init_fields,
|
68
|
+
ordered=ordered_init_fields,
|
69
|
+
std=std_init_fields,
|
70
|
+
kw_only=kw_only_init_fields,
|
71
|
+
)
|
72
|
+
|
73
|
+
|
74
|
+
@register_processing_context_item_factory(InitFields)
|
75
|
+
def _init_fields_processing_context_item_factory(ctx: ProcessingContext) -> InitFields:
|
76
|
+
return calc_init_fields(
|
77
|
+
ctx.cs.fields,
|
78
|
+
reorder=ctx.cs.reorder,
|
79
|
+
class_kw_only=ctx.cs.kw_only,
|
80
|
+
)
|
81
|
+
|
82
|
+
|
83
|
+
##
|
84
|
+
|
85
|
+
|
86
|
+
StdFields = ta.NewType('StdFields', ta.Mapping[str, dc.Field])
|
87
|
+
|
88
|
+
|
89
|
+
@register_processing_context_item_factory(StdFields)
|
90
|
+
def _std_fields_processing_context_item_factory(ctx: ProcessingContext) -> StdFields:
|
91
|
+
fld_dct = ctx.cls.__dict__[STD_FIELDS_ATTR]
|
92
|
+
for fn, f in fld_dct.items():
|
93
|
+
if DEBUG:
|
94
|
+
check.isinstance(f, dc.Field)
|
95
|
+
check.equal(f.name, fn)
|
96
|
+
check.equal(set(fld_dct), set(ctx.cs.fields_by_name))
|
97
|
+
return StdFields(fld_dct)
|
98
|
+
|
99
|
+
|
100
|
+
##
|
101
|
+
|
102
|
+
|
103
|
+
@register_processing_context_item_factory(FieldsInspection)
|
104
|
+
def _fields_inspection_processing_context_item_factory(ctx: ProcessingContext) -> FieldsInspection:
|
105
|
+
return FieldsInspection(
|
106
|
+
ctx.cls,
|
107
|
+
cls_fields=ctx[StdFields],
|
108
|
+
)
|
109
|
+
|
110
|
+
|
111
|
+
##
|
112
|
+
|
113
|
+
|
114
|
+
@register_processor_type(priority=ProcessorPriority.BOOTSTRAP)
|
115
|
+
class FieldsProcessor(Processor):
|
116
|
+
def check(self) -> None:
|
117
|
+
check.not_none(self._ctx[StdFields])
|
118
|
+
for f in self._ctx.cs.fields:
|
119
|
+
check.arg(not f.name.startswith(IDENT_PREFIX))
|
@@ -0,0 +1,133 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- prebuild field frozenset for getters/setters
|
4
|
+
- and one field per line
|
5
|
+
"""
|
6
|
+
import dataclasses as dc
|
7
|
+
import typing as ta
|
8
|
+
import weakref
|
9
|
+
|
10
|
+
from ... import check
|
11
|
+
from ..generation.base import Generator
|
12
|
+
from ..generation.base import Plan
|
13
|
+
from ..generation.base import PlanResult
|
14
|
+
from ..generation.globals import FROZEN_INSTANCE_ERROR_GLOBAL
|
15
|
+
from ..generation.idents import CLS_IDENT
|
16
|
+
from ..generation.idents import IDENT_PREFIX
|
17
|
+
from ..generation.ops import AddMethodOp
|
18
|
+
from ..generation.ops import Op
|
19
|
+
from ..generation.registry import register_generator_type
|
20
|
+
from ..internals import STD_FIELDS_ATTR
|
21
|
+
from ..internals import STD_PARAMS_ATTR
|
22
|
+
from ..processing.base import ProcessingContext
|
23
|
+
|
24
|
+
|
25
|
+
##
|
26
|
+
|
27
|
+
|
28
|
+
_UNCHECKED_FROZEN_BASES: ta.MutableSet[type] = weakref.WeakSet()
|
29
|
+
|
30
|
+
|
31
|
+
def unchecked_frozen_base(cls):
|
32
|
+
_UNCHECKED_FROZEN_BASES.add(check.isinstance(cls, type))
|
33
|
+
return cls
|
34
|
+
|
35
|
+
|
36
|
+
def check_frozen_bases(cls: type, frozen: bool) -> None:
|
37
|
+
all_frozen_bases = None
|
38
|
+
any_frozen_base = False
|
39
|
+
has_dataclass_bases = False
|
40
|
+
|
41
|
+
for b in cls.__mro__[-1:0:-1]:
|
42
|
+
if b in _UNCHECKED_FROZEN_BASES:
|
43
|
+
continue
|
44
|
+
|
45
|
+
base_fields = getattr(b, STD_FIELDS_ATTR, None)
|
46
|
+
if base_fields is None:
|
47
|
+
continue
|
48
|
+
|
49
|
+
has_dataclass_bases = True
|
50
|
+
if all_frozen_bases is None:
|
51
|
+
all_frozen_bases = True
|
52
|
+
|
53
|
+
current_frozen = getattr(b, STD_PARAMS_ATTR).frozen
|
54
|
+
all_frozen_bases = all_frozen_bases and current_frozen
|
55
|
+
any_frozen_base = any_frozen_base or current_frozen
|
56
|
+
|
57
|
+
if has_dataclass_bases:
|
58
|
+
if any_frozen_base and not frozen:
|
59
|
+
raise TypeError('cannot inherit non-frozen dataclass from a frozen one')
|
60
|
+
|
61
|
+
if all_frozen_bases is False and frozen:
|
62
|
+
raise TypeError('cannot inherit frozen dataclass from a non-frozen one')
|
63
|
+
|
64
|
+
|
65
|
+
##
|
66
|
+
|
67
|
+
|
68
|
+
@dc.dataclass(frozen=True)
|
69
|
+
class FrozenPlan(Plan):
|
70
|
+
fields: tuple[str, ...]
|
71
|
+
|
72
|
+
|
73
|
+
@register_generator_type(FrozenPlan)
|
74
|
+
class FrozenGenerator(Generator[FrozenPlan]):
|
75
|
+
def plan(self, ctx: ProcessingContext) -> PlanResult[FrozenPlan] | None:
|
76
|
+
check_frozen_bases(ctx.cls, ctx.cs.frozen)
|
77
|
+
|
78
|
+
if not ctx.cs.frozen:
|
79
|
+
return None
|
80
|
+
|
81
|
+
return PlanResult(FrozenPlan(tuple(f.name for f in ctx.cs.fields)))
|
82
|
+
|
83
|
+
def _generate_one(
|
84
|
+
self,
|
85
|
+
plan: FrozenPlan,
|
86
|
+
mth: str,
|
87
|
+
params: ta.Sequence[str],
|
88
|
+
exc_args: str,
|
89
|
+
) -> AddMethodOp:
|
90
|
+
preamble = []
|
91
|
+
# https://github.com/python/cpython/commit/ee6f8413a99d0ee4828e1c81911e203d3fff85d5
|
92
|
+
condition = f'type(self) is {CLS_IDENT}'
|
93
|
+
|
94
|
+
if plan.fields:
|
95
|
+
set_ident = f'{IDENT_PREFIX}_{mth}_frozen_fields'
|
96
|
+
preamble.extend([
|
97
|
+
f'{set_ident} = {{',
|
98
|
+
*[
|
99
|
+
f' {f!r},'
|
100
|
+
for f in plan.fields
|
101
|
+
],
|
102
|
+
f'}}',
|
103
|
+
f'',
|
104
|
+
])
|
105
|
+
condition += f' or name in {set_ident}'
|
106
|
+
|
107
|
+
return AddMethodOp(
|
108
|
+
f'__{mth}__',
|
109
|
+
'\n'.join([
|
110
|
+
*preamble,
|
111
|
+
f'def __{mth}__(self, {", ".join(params)}):',
|
112
|
+
f' if {condition}:',
|
113
|
+
f' raise {FROZEN_INSTANCE_ERROR_GLOBAL.ident}{exc_args}',
|
114
|
+
f' super({CLS_IDENT}, self).__{mth}__({", ".join(params)})',
|
115
|
+
]),
|
116
|
+
frozenset([FROZEN_INSTANCE_ERROR_GLOBAL]),
|
117
|
+
)
|
118
|
+
|
119
|
+
def generate(self, plan: FrozenPlan) -> ta.Iterable[Op]:
|
120
|
+
return [
|
121
|
+
self._generate_one(
|
122
|
+
plan,
|
123
|
+
'setattr',
|
124
|
+
['name', 'value'],
|
125
|
+
'(f"cannot assign to field {name!r}")',
|
126
|
+
),
|
127
|
+
self._generate_one(
|
128
|
+
plan,
|
129
|
+
'delattr',
|
130
|
+
['name'],
|
131
|
+
'(f"cannot delete field {name!r}")',
|
132
|
+
),
|
133
|
+
]
|
@@ -0,0 +1,165 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from ... import check
|
5
|
+
from ..generation.base import Generator
|
6
|
+
from ..generation.base import Plan
|
7
|
+
from ..generation.base import PlanResult
|
8
|
+
from ..generation.ops import AddMethodOp
|
9
|
+
from ..generation.ops import Op
|
10
|
+
from ..generation.ops import SetAttrOp
|
11
|
+
from ..generation.registry import register_generator_type
|
12
|
+
from ..generation.utils import build_attr_tuple_body_src_lines
|
13
|
+
from ..processing.base import ProcessingContext
|
14
|
+
from .fields import InstanceFields
|
15
|
+
|
16
|
+
|
17
|
+
##
|
18
|
+
|
19
|
+
|
20
|
+
HashAction: ta.TypeAlias = ta.Literal['set_none', 'add', 'exception']
|
21
|
+
|
22
|
+
|
23
|
+
# See https://bugs.python.org/issue32929#msg312829 for an if-statement version of this table.
|
24
|
+
HASH_ACTIONS: ta.Mapping[tuple[bool, bool, bool, bool], HashAction | None] = {
|
25
|
+
#
|
26
|
+
# +-------------------------------------- unsafe_hash?
|
27
|
+
# | +------------------------------- eq?
|
28
|
+
# | | +------------------------ frozen?
|
29
|
+
# | | | +---------------- has-explicit-hash?
|
30
|
+
# v v v v
|
31
|
+
(False, False, False, False): None,
|
32
|
+
(False, False, False, True): None,
|
33
|
+
(False, False, True, False): None,
|
34
|
+
(False, False, True, True): None,
|
35
|
+
(False, True, False, False): 'set_none',
|
36
|
+
(False, True, False, True): None,
|
37
|
+
(False, True, True, False): 'add',
|
38
|
+
(False, True, True, True): None,
|
39
|
+
(True, False, False, False): 'add',
|
40
|
+
(True, False, False, True): 'exception',
|
41
|
+
(True, False, True, False): 'add',
|
42
|
+
(True, False, True, True): 'exception',
|
43
|
+
(True, True, False, False): 'add',
|
44
|
+
(True, True, False, True): 'exception',
|
45
|
+
(True, True, True, False): 'add',
|
46
|
+
(True, True, True, True): 'exception',
|
47
|
+
}
|
48
|
+
|
49
|
+
|
50
|
+
def _raise_hash_action_exception(cls: type) -> ta.NoReturn:
|
51
|
+
raise TypeError(f'Cannot overwrite attribute __hash__ in class {cls.__name__}')
|
52
|
+
|
53
|
+
|
54
|
+
CACHED_HASH_ATTR = '__dataclass_hash__'
|
55
|
+
|
56
|
+
|
57
|
+
#
|
58
|
+
|
59
|
+
|
60
|
+
@dc.dataclass(frozen=True)
|
61
|
+
class HashPlan(Plan):
|
62
|
+
action: HashAction
|
63
|
+
|
64
|
+
_: dc.KW_ONLY
|
65
|
+
|
66
|
+
fields: tuple[str, ...] | None = None
|
67
|
+
cache: bool | None = None
|
68
|
+
|
69
|
+
|
70
|
+
@register_generator_type(HashPlan)
|
71
|
+
class HashGenerator(Generator[HashPlan]):
|
72
|
+
def plan(self, ctx: ProcessingContext) -> PlanResult[HashPlan] | None:
|
73
|
+
class_hash = ctx.cls.__dict__.get('__hash__', dc.MISSING)
|
74
|
+
has_explicit_hash = not (class_hash is dc.MISSING or (class_hash is None and '__eq__' in ctx.cls.__dict__))
|
75
|
+
|
76
|
+
action = HASH_ACTIONS[(
|
77
|
+
bool(ctx.cs.unsafe_hash),
|
78
|
+
bool(ctx.cs.eq),
|
79
|
+
bool(ctx.cs.frozen),
|
80
|
+
has_explicit_hash,
|
81
|
+
)]
|
82
|
+
|
83
|
+
if action == 'set_none':
|
84
|
+
return PlanResult(HashPlan(action)) # noqa
|
85
|
+
|
86
|
+
elif action == 'exception':
|
87
|
+
_raise_hash_action_exception(ctx.cls)
|
88
|
+
|
89
|
+
elif action == 'add':
|
90
|
+
fields = tuple(
|
91
|
+
f.name
|
92
|
+
for f in ctx[InstanceFields]
|
93
|
+
if (f.compare if f.hash is None else f.hash)
|
94
|
+
)
|
95
|
+
|
96
|
+
return PlanResult(HashPlan(
|
97
|
+
'add',
|
98
|
+
fields=fields,
|
99
|
+
cache=ctx.cs.cache_hash,
|
100
|
+
))
|
101
|
+
|
102
|
+
elif action is None:
|
103
|
+
return None
|
104
|
+
|
105
|
+
else:
|
106
|
+
raise ValueError(action)
|
107
|
+
|
108
|
+
def generate(self, pl: HashPlan) -> ta.Iterable[Op]:
|
109
|
+
if pl.action == 'set_none':
|
110
|
+
return [SetAttrOp('__hash__', None, if_present='replace')]
|
111
|
+
|
112
|
+
elif pl.action != 'add':
|
113
|
+
raise ValueError(pl.action)
|
114
|
+
|
115
|
+
lines = [
|
116
|
+
'def __hash__(self):',
|
117
|
+
]
|
118
|
+
|
119
|
+
hash_lines: list[str]
|
120
|
+
if pl.fields:
|
121
|
+
hash_lines = [
|
122
|
+
'hash((',
|
123
|
+
*build_attr_tuple_body_src_lines(
|
124
|
+
'self',
|
125
|
+
*check.not_none(pl.fields),
|
126
|
+
prefix=' ',
|
127
|
+
),
|
128
|
+
'))',
|
129
|
+
]
|
130
|
+
else:
|
131
|
+
hash_lines = ['hash(())']
|
132
|
+
|
133
|
+
if pl.cache:
|
134
|
+
lines.extend([
|
135
|
+
f' try:',
|
136
|
+
f' return self.{CACHED_HASH_ATTR}',
|
137
|
+
f' except AttributeError:',
|
138
|
+
f' pass',
|
139
|
+
f' object.__setattr__(',
|
140
|
+
f' self,',
|
141
|
+
f' {CACHED_HASH_ATTR!r},',
|
142
|
+
f' h := {hash_lines[0]}',
|
143
|
+
*[
|
144
|
+
f' {l}'
|
145
|
+
for l in hash_lines[1:]
|
146
|
+
],
|
147
|
+
f' )',
|
148
|
+
f' return h',
|
149
|
+
])
|
150
|
+
else:
|
151
|
+
lines.extend([
|
152
|
+
f' return {hash_lines[0]}',
|
153
|
+
*[
|
154
|
+
f' {l}'
|
155
|
+
for l in hash_lines[1:]
|
156
|
+
],
|
157
|
+
])
|
158
|
+
|
159
|
+
return [
|
160
|
+
AddMethodOp(
|
161
|
+
'__hash__',
|
162
|
+
'\n'.join(lines),
|
163
|
+
if_present='replace',
|
164
|
+
),
|
165
|
+
]
|