omlish 0.0.0.dev219__py3-none-any.whl → 0.0.0.dev221__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 (66) hide show
  1. omlish/__about__.py +2 -2
  2. omlish/algorithm/__init__.py +0 -0
  3. omlish/algorithm/all.py +13 -0
  4. omlish/algorithm/distribute.py +46 -0
  5. omlish/algorithm/toposort.py +26 -0
  6. omlish/algorithm/unify.py +31 -0
  7. omlish/antlr/dot.py +13 -6
  8. omlish/collections/__init__.py +0 -2
  9. omlish/collections/utils.py +0 -46
  10. omlish/dataclasses/__init__.py +1 -0
  11. omlish/dataclasses/impl/api.py +10 -0
  12. omlish/dataclasses/impl/fields.py +8 -1
  13. omlish/dataclasses/impl/init.py +1 -1
  14. omlish/dataclasses/impl/main.py +1 -1
  15. omlish/dataclasses/impl/metaclass.py +112 -29
  16. omlish/dataclasses/impl/overrides.py +53 -0
  17. omlish/dataclasses/impl/params.py +3 -0
  18. omlish/dataclasses/impl/reflect.py +17 -5
  19. omlish/dataclasses/impl/simple.py +0 -42
  20. omlish/docker/oci/building.py +122 -0
  21. omlish/docker/oci/data.py +62 -8
  22. omlish/docker/oci/datarefs.py +98 -0
  23. omlish/docker/oci/loading.py +120 -0
  24. omlish/docker/oci/media.py +44 -14
  25. omlish/docker/oci/repositories.py +72 -0
  26. omlish/graphs/trees.py +2 -1
  27. omlish/http/coro/server.py +53 -24
  28. omlish/http/{simple.py → coro/simple.py} +17 -17
  29. omlish/http/handlers.py +8 -0
  30. omlish/io/fileno.py +11 -0
  31. omlish/lang/__init__.py +4 -1
  32. omlish/lang/cached.py +0 -1
  33. omlish/lang/classes/__init__.py +3 -1
  34. omlish/lang/classes/abstract.py +14 -1
  35. omlish/lang/classes/restrict.py +5 -5
  36. omlish/lang/classes/virtual.py +0 -1
  37. omlish/lang/clsdct.py +0 -1
  38. omlish/lang/contextmanagers.py +0 -8
  39. omlish/lang/descriptors.py +0 -1
  40. omlish/lang/maybes.py +0 -1
  41. omlish/lang/objects.py +0 -2
  42. omlish/secrets/ssl.py +9 -0
  43. omlish/secrets/tempssl.py +50 -0
  44. omlish/sockets/bind.py +6 -1
  45. omlish/sockets/server/server.py +18 -5
  46. omlish/specs/irc/__init__.py +0 -0
  47. omlish/specs/irc/format/LICENSE +11 -0
  48. omlish/specs/irc/format/__init__.py +61 -0
  49. omlish/specs/irc/format/consts.py +6 -0
  50. omlish/specs/irc/format/errors.py +30 -0
  51. omlish/specs/irc/format/message.py +18 -0
  52. omlish/specs/irc/format/nuh.py +52 -0
  53. omlish/specs/irc/format/parsing.py +155 -0
  54. omlish/specs/irc/format/rendering.py +150 -0
  55. omlish/specs/irc/format/tags.py +99 -0
  56. omlish/specs/irc/format/utils.py +27 -0
  57. omlish/specs/irc/numerics/__init__.py +0 -0
  58. omlish/specs/irc/numerics/formats.py +94 -0
  59. omlish/specs/irc/numerics/numerics.py +808 -0
  60. omlish/specs/irc/numerics/types.py +59 -0
  61. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/METADATA +1 -1
  62. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/RECORD +66 -38
  63. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/LICENSE +0 -0
  64. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/WHEEL +0 -0
  65. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/entry_points.txt +0 -0
  66. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/top_level.txt +0 -0
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev219'
2
- __revision__ = '5e741e0670fb75899653bd454587a1cf2add710f'
1
+ __version__ = '0.0.0.dev221'
2
+ __revision__ = '2c4c1480e414176972a21bd34529721b39818ad8'
3
3
 
4
4
 
5
5
  #
File without changes
@@ -0,0 +1,13 @@
1
+ from .distribute import ( # noqa
2
+ distribute_evenly,
3
+ )
4
+
5
+ from .toposort import ( # noqa
6
+ mut_toposort,
7
+ toposort,
8
+ )
9
+
10
+ from .unify import ( # noqa
11
+ mut_unify_sets,
12
+ unify_sets,
13
+ )
@@ -0,0 +1,46 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import collections
4
+ import heapq
5
+ import typing as ta
6
+
7
+
8
+ T = ta.TypeVar('T')
9
+
10
+
11
+ def distribute_evenly(
12
+ items: ta.Iterable[ta.Tuple[T, float]],
13
+ n_bins: int,
14
+ ) -> ta.List[ta.List[ta.Tuple[T, float]]]:
15
+ """
16
+ Distribute items into n bins as evenly as possible in terms of total size.
17
+ - Sorting ensures larger items are placed first, preventing large leftover gaps in bins.
18
+ - A min-heap efficiently finds the least loaded bin in O(log n), keeping the distribution balanced.
19
+ - Each item is placed in the lightest bin, preventing a few bins from getting overloaded early.
20
+
21
+ :param items: List of tuples (item, size).
22
+ :param n_bins: Number of bins.
23
+ :return: List of n_bins lists, each containing items assigned to that bin.
24
+ """
25
+
26
+ # Sort items by size in descending order
27
+ items_sorted = sorted(items, key=lambda x: x[1], reverse=True)
28
+
29
+ # Min-heap to track bin loads (size, index)
30
+ bins = [(0, i) for i in range(n_bins)] # (current size, bin index)
31
+ heapq.heapify(bins)
32
+
33
+ # Allocate items to bins
34
+ bin_contents = collections.defaultdict(list)
35
+
36
+ for item, size in items_sorted:
37
+ # Get the least loaded bin
38
+ bin_size, bin_index = heapq.heappop(bins)
39
+
40
+ # Assign item to this bin
41
+ bin_contents[bin_index].append((item, size))
42
+
43
+ # Update bin load and push back to heap
44
+ heapq.heappush(bins, (bin_size + size, bin_index)) # type: ignore
45
+
46
+ return [bin_contents[i] for i in range(n_bins)]
@@ -0,0 +1,26 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import functools
4
+ import typing as ta
5
+
6
+
7
+ T = ta.TypeVar('T')
8
+
9
+
10
+ def mut_toposort(data: ta.Dict[T, ta.Set[T]]) -> ta.Iterator[ta.Set[T]]:
11
+ for k, v in data.items():
12
+ v.discard(k)
13
+ extra_items_in_deps = functools.reduce(set.union, data.values()) - set(data.keys())
14
+ data.update({item: set() for item in extra_items_in_deps})
15
+ while True:
16
+ ordered = {item for item, dep in data.items() if not dep}
17
+ if not ordered:
18
+ break
19
+ yield ordered
20
+ data = {item: (dep - ordered) for item, dep in data.items() if item not in ordered}
21
+ if data:
22
+ raise ValueError('Cyclic dependencies exist among these items: ' + ' '.join(repr(x) for x in data.items()))
23
+
24
+
25
+ def toposort(data: ta.Mapping[T, ta.AbstractSet[T]]) -> ta.Iterator[ta.Set[T]]:
26
+ return mut_toposort({k: set(v) for k, v in data.items()})
@@ -0,0 +1,31 @@
1
+ import itertools
2
+ import typing as ta
3
+
4
+
5
+ T = ta.TypeVar('T')
6
+
7
+
8
+ def mut_unify_sets(sets: ta.Iterable[set[T]]) -> list[set[T]]:
9
+ rem: list[set[T]] = list(sets)
10
+ ret: list[set[T]] = []
11
+ while rem:
12
+ cur = rem.pop()
13
+ while True:
14
+ moved = False
15
+ for i in range(len(rem) - 1, -1, -1):
16
+ if any(e in cur for e in rem[i]):
17
+ cur.update(rem.pop(i))
18
+ moved = True
19
+ if not moved:
20
+ break
21
+ ret.append(cur)
22
+ if ret:
23
+ all_ = set(itertools.chain.from_iterable(ret))
24
+ num = sum(map(len, ret))
25
+ if len(all_) != num:
26
+ raise ValueError('Length mismatch')
27
+ return ret
28
+
29
+
30
+ def unify_sets(sets: ta.Iterable[ta.AbstractSet[T]]) -> list[set[T]]:
31
+ return mut_unify_sets([set(s) for s in sets])
omlish/antlr/dot.py CHANGED
@@ -1,12 +1,19 @@
1
+ import typing as ta
2
+
1
3
  from ..graphs import dot
2
4
  from . import runtime as antlr4
3
5
  from .utils import yield_contexts
4
6
 
5
7
 
6
- def dot_ctx(root: antlr4.ParserRuleContext) -> dot.Graph:
7
- stmts: list[dot.Stmt] = [
8
- dot.RawStmt('rankdir=LR;'),
9
- ]
8
+ def dot_ctx(
9
+ root: antlr4.ParserRuleContext,
10
+ *,
11
+ left_to_right: bool = False,
12
+ ) -> dot.Graph:
13
+ stmts: list[dot.Stmt] = []
14
+
15
+ if left_to_right:
16
+ stmts.append(dot.RawStmt('rankdir=LR;'))
10
17
 
11
18
  for c in yield_contexts(root):
12
19
  if isinstance(c, antlr4.TerminalNode):
@@ -27,5 +34,5 @@ def dot_ctx(root: antlr4.ParserRuleContext) -> dot.Graph:
27
34
  return dot.Graph(stmts)
28
35
 
29
36
 
30
- def open_dot_ctx(root: antlr4.ParserRuleContext) -> None:
31
- dot.open_dot(dot.render(dot_ctx(root)))
37
+ def open_dot_ctx(root: antlr4.ParserRuleContext, **kwargs: ta.Any) -> None:
38
+ dot.open_dot(dot.render(dot_ctx(root)), **kwargs)
@@ -126,8 +126,6 @@ from .utils import ( # noqa
126
126
  make_map_by,
127
127
  multi_map,
128
128
  multi_map_by,
129
- mut_toposort,
130
129
  partition,
131
- toposort,
132
130
  unique,
133
131
  )
@@ -1,8 +1,5 @@
1
- import functools
2
- import itertools
3
1
  import typing as ta
4
2
 
5
- from .. import check
6
3
  from .. import lang
7
4
  from .exceptions import DuplicateKeyError
8
5
  from .identity import IdentityKeyDict
@@ -17,28 +14,6 @@ V = ta.TypeVar('V')
17
14
  ##
18
15
 
19
16
 
20
- def mut_toposort(data: dict[T, set[T]]) -> ta.Iterator[set[T]]:
21
- for k, v in data.items():
22
- v.discard(k)
23
- extra_items_in_deps = functools.reduce(set.union, data.values()) - set(data.keys())
24
- data.update({item: set() for item in extra_items_in_deps})
25
- while True:
26
- ordered = {item for item, dep in data.items() if not dep}
27
- if not ordered:
28
- break
29
- yield ordered
30
- data = {item: (dep - ordered) for item, dep in data.items() if item not in ordered}
31
- if data:
32
- raise ValueError('Cyclic dependencies exist among these items: ' + ' '.join(repr(x) for x in data.items()))
33
-
34
-
35
- def toposort(data: ta.Mapping[T, ta.AbstractSet[T]]) -> ta.Iterator[set[T]]:
36
- return mut_toposort({k: set(v) for k, v in data.items()})
37
-
38
-
39
- ##
40
-
41
-
42
17
  class PartitionResult(ta.NamedTuple, ta.Generic[T]):
43
18
  t: list[T]
44
19
  f: list[T]
@@ -153,24 +128,3 @@ def key_cmp(fn: ta.Callable[[K, K], int]) -> ta.Callable[[tuple[K, V], tuple[K,
153
128
 
154
129
  def indexes(it: ta.Iterable[T]) -> dict[T, int]:
155
130
  return {e: i for i, e in enumerate(it)}
156
-
157
-
158
- def mut_unify_sets(sets: ta.Iterable[set[T]]) -> list[set[T]]:
159
- rem: list[set[T]] = list(sets)
160
- ret: list[set[T]] = []
161
- while rem:
162
- cur = rem.pop()
163
- while True:
164
- moved = False
165
- for i in range(len(rem) - 1, -1, -1):
166
- if any(e in cur for e in rem[i]):
167
- cur.update(rem.pop(i))
168
- moved = True
169
- if not moved:
170
- break
171
- ret.append(cur)
172
- if ret:
173
- all_ = set(itertools.chain.from_iterable(ret))
174
- num = sum(map(len, ret))
175
- check.equal(len(all_), num)
176
- return ret
@@ -67,6 +67,7 @@ from .impl.metaclass import ( # noqa
67
67
  DataMeta,
68
68
  Data,
69
69
  Frozen,
70
+ Case,
70
71
  Box,
71
72
  )
72
73
 
@@ -1,3 +1,7 @@
1
+ """
2
+ TODO:
3
+ - fix code redundancy
4
+ """
1
5
  import collections.abc
2
6
  import contextlib
3
7
  import dataclasses as dc
@@ -91,6 +95,7 @@ def dataclass( # noqa
91
95
  reorder=MISSING,
92
96
  cache_hash=MISSING,
93
97
  generic_init=MISSING,
98
+ override=MISSING,
94
99
  ):
95
100
  def wrap(cls):
96
101
  pkw = dict(
@@ -113,6 +118,7 @@ def dataclass( # noqa
113
118
  reorder=reorder,
114
119
  cache_hash=cache_hash,
115
120
  generic_init=generic_init,
121
+ override=override,
116
122
  )))
117
123
  pex = ParamsExtras(**epk)
118
124
 
@@ -161,6 +167,7 @@ def make_dataclass( # noqa
161
167
  reorder=MISSING,
162
168
  cache_hash=MISSING,
163
169
  generic_init=MISSING,
170
+ override=MISSING,
164
171
  ):
165
172
  if namespace is None:
166
173
  namespace = {}
@@ -221,6 +228,7 @@ def make_dataclass( # noqa
221
228
  reorder=reorder,
222
229
  cache_hash=cache_hash,
223
230
  generic_init=generic_init,
231
+ override=override,
224
232
  )
225
233
 
226
234
 
@@ -233,6 +241,7 @@ def extra_params( # noqa
233
241
  reorder=MISSING,
234
242
  cache_hash=MISSING,
235
243
  generic_init=MISSING,
244
+ override=MISSING,
236
245
  ):
237
246
  def inner(cls):
238
247
  if PARAMS_ATTR in cls.__dict__:
@@ -249,6 +258,7 @@ def extra_params( # noqa
249
258
  reorder=reorder,
250
259
  cache_hash=cache_hash,
251
260
  generic_init=generic_init,
261
+ override=override,
252
262
  ))
253
263
 
254
264
  return cls
@@ -165,6 +165,7 @@ def field_init(
165
165
  locals: dict[str, ta.Any], # noqa
166
166
  self_name: str,
167
167
  slots: bool,
168
+ cls_override: bool,
168
169
  ) -> ta.Sequence[str]:
169
170
  default_name = f'__dataclass_dflt_{f.name}__'
170
171
  fx = get_field_extras(f)
@@ -235,6 +236,12 @@ def field_init(
235
236
  )
236
237
 
237
238
  if value is not None and field_type(f) is not FieldType.INIT:
238
- lines.append(field_assign(frozen, f.name, value, self_name, fx.override)) # noqa
239
+ lines.append(field_assign(
240
+ frozen,
241
+ f.name,
242
+ value,
243
+ self_name,
244
+ fx.override or cls_override,
245
+ ))
239
246
 
240
247
  return lines
@@ -74,7 +74,6 @@ def init_param(f: dc.Field) -> str:
74
74
 
75
75
 
76
76
  class InitBuilder:
77
-
78
77
  def __init__(
79
78
  self,
80
79
  info: ClassInfo,
@@ -128,6 +127,7 @@ class InitBuilder:
128
127
  locals,
129
128
  self._self_name,
130
129
  self._info.params.slots,
130
+ self._info.params_extras.override,
131
131
  )
132
132
 
133
133
  if f_lines:
@@ -13,6 +13,7 @@ from .internals import FIELDS_ATTR
13
13
  from .internals import PARAMS_ATTR
14
14
  from .internals import Params
15
15
  from .order import OrderProcessor
16
+ from .overrides import OverridesProcessor
16
17
  from .params import ParamsExtras
17
18
  from .processing import Processor
18
19
  from .reflect import ClassInfo
@@ -21,7 +22,6 @@ from .repr import ReprProcessor
21
22
  from .simple import DocProcessor
22
23
  from .simple import EqProcessor
23
24
  from .simple import MatchArgsProcessor
24
- from .simple import OverridesProcessor
25
25
  from .slots import add_slots
26
26
 
27
27
 
@@ -9,6 +9,7 @@ import dataclasses as dc
9
9
  import typing as ta
10
10
 
11
11
  from ... import lang
12
+ from .api import MISSING
12
13
  from .api import dataclass
13
14
  from .api import field # noqa
14
15
  from .params import MetaclassParams
@@ -20,6 +21,28 @@ from .params import get_params_extras
20
21
  T = ta.TypeVar('T')
21
22
 
22
23
 
24
+ ##
25
+
26
+
27
+ _CONFER_PARAMS: tuple[str, ...] = (
28
+ 'frozen',
29
+ 'kw_only',
30
+ )
31
+
32
+ _CONFER_PARAMS_EXTRAS: tuple[str, ...] = (
33
+ 'reorder',
34
+ 'cache_hash',
35
+ 'generic_init',
36
+ 'override',
37
+ )
38
+
39
+ _CONFER_METACLASS_PARAMS: tuple[str, ...] = (
40
+ 'confer',
41
+ 'final_subclasses',
42
+ 'abstract_immediate_subclasses',
43
+ )
44
+
45
+
23
46
  def confer_kwarg(out: dict[str, ta.Any], k: str, v: ta.Any) -> None:
24
47
  if k in out:
25
48
  if out[k] != v:
@@ -44,22 +67,13 @@ def confer_kwargs(
44
67
  if ck in kwargs:
45
68
  continue
46
69
 
47
- if ck in (
48
- 'frozen',
49
- 'kw_only',
50
- ):
70
+ if ck in _CONFER_PARAMS:
51
71
  confer_kwarg(out, ck, getattr(get_params(base), ck))
52
72
 
53
- elif ck in (
54
- 'cache_hash',
55
- 'generic_init',
56
- 'reorder',
57
- ):
73
+ elif ck in _CONFER_PARAMS_EXTRAS:
58
74
  confer_kwarg(out, ck, getattr(get_params_extras(base), ck))
59
75
 
60
- elif ck in (
61
- 'confer',
62
- ):
76
+ elif ck in _CONFER_METACLASS_PARAMS:
63
77
  confer_kwarg(out, ck, getattr(bmp, ck))
64
78
 
65
79
  else:
@@ -68,6 +82,9 @@ def confer_kwargs(
68
82
  return out
69
83
 
70
84
 
85
+ ##
86
+
87
+
71
88
  class DataMeta(abc.ABCMeta):
72
89
  def __new__(
73
90
  mcls,
@@ -76,25 +93,21 @@ class DataMeta(abc.ABCMeta):
76
93
  namespace,
77
94
  *,
78
95
 
79
- # confer=frozenset(),
96
+ abstract=False,
97
+ sealed=False,
98
+ final=False,
80
99
 
81
100
  metadata=None,
82
101
  **kwargs,
83
102
  ):
84
- cls = lang.super_meta(
85
- super(),
86
- mcls,
87
- name,
88
- bases,
89
- namespace,
90
- )
91
-
92
103
  ckw = confer_kwargs(bases, kwargs)
93
104
  nkw = {**kwargs, **ckw}
94
105
 
95
- mcp = MetaclassParams(
96
- confer=nkw.pop('confer', frozenset()),
97
- )
106
+ mcp = MetaclassParams(**{
107
+ mpa: nkw.pop(mpa)
108
+ for mpa in _CONFER_METACLASS_PARAMS
109
+ if mpa in nkw
110
+ })
98
111
 
99
112
  mmd = {
100
113
  MetaclassParams: mcp,
@@ -104,13 +117,74 @@ class DataMeta(abc.ABCMeta):
104
117
  else:
105
118
  metadata = mmd
106
119
 
120
+ #
121
+
122
+ xbs: list[type] = []
123
+
124
+ if any(get_metaclass_params(b).abstract_immediate_subclasses for b in bases if dc.is_dataclass(b)):
125
+ abstract = True
126
+
127
+ final |= (mcp.final_subclasses and not abstract)
128
+
129
+ if final and abstract:
130
+ raise TypeError(f'Class cannot be abstract and final: {name!r}')
131
+
132
+ if abstract:
133
+ xbs.append(lang.Abstract)
134
+ if sealed:
135
+ xbs.append(lang.Sealed)
136
+ if final:
137
+ xbs.append(lang.Final)
138
+
139
+ if xbs:
140
+ if bases and bases[-1] is ta.Generic:
141
+ bases = (*bases[:-1], *xbs, bases[-1])
142
+ else:
143
+ bases = (*bases, *xbs)
144
+ if ob := namespace.get('__orig_bases__'):
145
+ if getattr(ob[-1], '__origin__', None) is ta.Generic:
146
+ namespace['__orig_bases__'] = (*ob[:-1], *xbs, ob[-1])
147
+ else:
148
+ namespace['__orig_bases__'] = (*ob, *xbs)
149
+
150
+ #
151
+
152
+ ofs: set[str] = set()
153
+ if any(issubclass(b, lang.Abstract) for b in bases) and nkw.get('override'):
154
+ ofs.update(a for a in namespace.get('__annotations__', []) if a not in namespace)
155
+ namespace.update((a, MISSING) for a in ofs)
156
+
157
+ #
158
+
159
+ cls = lang.super_meta(
160
+ super(),
161
+ mcls,
162
+ name,
163
+ bases,
164
+ namespace,
165
+ )
166
+
167
+ #
168
+
169
+ for a in ofs:
170
+ delattr(cls, a)
171
+
172
+ #
173
+
107
174
  return dataclass(cls, metadata=metadata, **nkw)
108
175
 
109
176
 
177
+ ##
178
+
179
+
110
180
  # @ta.dataclass_transform(field_specifiers=(field,)) # FIXME: ctor
111
181
  class Data(
112
182
  eq=False,
113
183
  order=False,
184
+ confer=frozenset([
185
+ 'confer',
186
+ 'final_subclasses',
187
+ ]),
114
188
  metaclass=DataMeta,
115
189
  ):
116
190
  def __init__(self, *args, **kwargs):
@@ -132,24 +206,33 @@ class Frozen(
132
206
  eq=False,
133
207
  order=False,
134
208
  confer=frozenset([
209
+ *get_metaclass_params(Data).confer,
135
210
  'frozen',
136
- 'cache_hash',
137
- 'confer',
138
211
  'reorder',
212
+ 'cache_hash',
213
+ 'override',
139
214
  ]),
140
215
  ):
141
216
  pass
142
217
 
143
218
 
219
+ class Case(
220
+ Frozen,
221
+ abstract=True,
222
+ override=True,
223
+ final_subclasses=True,
224
+ abstract_immediate_subclasses=True,
225
+ ):
226
+ pass
227
+
228
+
144
229
  class Box(
145
230
  Frozen,
146
231
  ta.Generic[T],
147
232
  generic_init=True,
148
233
  confer=frozenset([
149
- 'frozen',
150
- 'cache_hash',
234
+ *get_metaclass_params(Frozen).confer,
151
235
  'generic_init',
152
- 'confer',
153
236
  ]),
154
237
  ):
155
238
  v: T
@@ -0,0 +1,53 @@
1
+ from ... import lang
2
+ from .fields import field_assign
3
+ from .params import get_field_extras
4
+ from .processing import Processor
5
+ from .utils import create_fn
6
+ from .utils import set_new_attribute
7
+
8
+
9
+ class OverridesProcessor(Processor):
10
+ def _process(self) -> None:
11
+ for f in self._info.instance_fields:
12
+ fx = get_field_extras(f)
13
+ if not (fx.override or self._info.params_extras.override):
14
+ continue
15
+
16
+ if self._info.params.slots:
17
+ raise TypeError
18
+
19
+ self_name = '__dataclass_self__' if 'self' in self._info.fields else 'self'
20
+
21
+ getter = create_fn(
22
+ f.name,
23
+ (self_name,),
24
+ [f'return {self_name}.__dict__[{f.name!r}]'],
25
+ globals=self._info.globals,
26
+ return_type=lang.just(f.type),
27
+ )
28
+ prop = property(getter)
29
+
30
+ if not self._info.params.frozen:
31
+ setter = create_fn(
32
+ f.name,
33
+ (self_name, f'{f.name}: __dataclass_type_{f.name}__'),
34
+ [
35
+ field_assign(
36
+ self._info.params.frozen,
37
+ f.name,
38
+ f.name,
39
+ self_name,
40
+ True,
41
+ ),
42
+ ],
43
+ globals=self._info.globals,
44
+ locals={f'__dataclass_type_{f.name}__': f.type},
45
+ return_type=lang.just(None),
46
+ )
47
+ prop = prop.setter(setter)
48
+
49
+ set_new_attribute(
50
+ self._cls,
51
+ f.name,
52
+ prop,
53
+ )
@@ -91,6 +91,7 @@ class ParamsExtras(lang.Final):
91
91
  reorder: bool = False
92
92
  cache_hash: bool = False
93
93
  generic_init: bool = False
94
+ override: bool = False
94
95
 
95
96
 
96
97
  DEFAULT_PARAMS_EXTRAS = ParamsExtras()
@@ -110,6 +111,8 @@ def get_params_extras(obj: ta.Any) -> ParamsExtras:
110
111
  @dc.dataclass(frozen=True)
111
112
  class MetaclassParams:
112
113
  confer: frozenset[str] = frozenset()
114
+ final_subclasses: bool = False
115
+ abstract_immediate_subclasses: bool = False
113
116
 
114
117
 
115
118
  DEFAULT_METACLASS_PARAMS = MetaclassParams()
@@ -30,8 +30,20 @@ from .utils import Namespace
30
30
  MISSING = dc.MISSING
31
31
 
32
32
 
33
- class ClassInfo:
33
+ ##
34
+
35
+
36
+ def get_cls_annotations(cls: type) -> ta.Mapping[str, ta.Any]:
37
+ # Does not use ta.get_type_hints because that's what std dataclasses do [1]. Might be worth revisiting? A part
38
+ # of why they don't is to not import typing for efficiency but we don't care about that degree of startup speed.
39
+ # [1]: https://github.com/python/cpython/blob/54c63a32d06cb5f07a66245c375eac7d7efb964a/Lib/dataclasses.py#L985-L986 # noqa
40
+ return rfl.get_annotations(cls)
41
+
42
+
43
+ ##
34
44
 
45
+
46
+ class ClassInfo:
35
47
  def __init__(self, cls: type, *, _constructing: bool = False) -> None:
36
48
  check.isinstance(cls, type)
37
49
  self._constructing = _constructing
@@ -53,10 +65,7 @@ class ClassInfo:
53
65
 
54
66
  @cached.property
55
67
  def cls_annotations(self) -> ta.Mapping[str, ta.Any]:
56
- # Does not use ta.get_type_hints because that's what std dataclasses do [1]. Might be worth revisiting? A part
57
- # of why they don't is to not import typing for efficiency but we don't care about that degree of startup speed.
58
- # [1]: https://github.com/python/cpython/blob/54c63a32d06cb5f07a66245c375eac7d7efb964a/Lib/dataclasses.py#L985-L986 # noqa
59
- return rfl.get_annotations(self._cls)
68
+ return get_cls_annotations(self._cls)
60
69
 
61
70
  ##
62
71
 
@@ -157,6 +166,9 @@ class ClassInfo:
157
166
  return {k: rfl.to_annotation(v) for k, v in self.generic_replaced_field_types.items()}
158
167
 
159
168
 
169
+ ##
170
+
171
+
160
172
  _CLASS_INFO_CACHE: ta.MutableMapping[type, ClassInfo] = weakref.WeakKeyDictionary()
161
173
 
162
174