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,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
+ ]