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.
- omlish/__about__.py +2 -2
- omlish/algorithm/__init__.py +0 -0
- omlish/algorithm/all.py +13 -0
- omlish/algorithm/distribute.py +46 -0
- omlish/algorithm/toposort.py +26 -0
- omlish/algorithm/unify.py +31 -0
- omlish/antlr/dot.py +13 -6
- omlish/collections/__init__.py +0 -2
- omlish/collections/utils.py +0 -46
- omlish/dataclasses/__init__.py +1 -0
- omlish/dataclasses/impl/api.py +10 -0
- omlish/dataclasses/impl/fields.py +8 -1
- omlish/dataclasses/impl/init.py +1 -1
- omlish/dataclasses/impl/main.py +1 -1
- omlish/dataclasses/impl/metaclass.py +112 -29
- omlish/dataclasses/impl/overrides.py +53 -0
- omlish/dataclasses/impl/params.py +3 -0
- omlish/dataclasses/impl/reflect.py +17 -5
- omlish/dataclasses/impl/simple.py +0 -42
- omlish/docker/oci/building.py +122 -0
- omlish/docker/oci/data.py +62 -8
- omlish/docker/oci/datarefs.py +98 -0
- omlish/docker/oci/loading.py +120 -0
- omlish/docker/oci/media.py +44 -14
- omlish/docker/oci/repositories.py +72 -0
- omlish/graphs/trees.py +2 -1
- omlish/http/coro/server.py +53 -24
- omlish/http/{simple.py → coro/simple.py} +17 -17
- omlish/http/handlers.py +8 -0
- omlish/io/fileno.py +11 -0
- omlish/lang/__init__.py +4 -1
- omlish/lang/cached.py +0 -1
- omlish/lang/classes/__init__.py +3 -1
- omlish/lang/classes/abstract.py +14 -1
- omlish/lang/classes/restrict.py +5 -5
- omlish/lang/classes/virtual.py +0 -1
- omlish/lang/clsdct.py +0 -1
- omlish/lang/contextmanagers.py +0 -8
- omlish/lang/descriptors.py +0 -1
- omlish/lang/maybes.py +0 -1
- omlish/lang/objects.py +0 -2
- omlish/secrets/ssl.py +9 -0
- omlish/secrets/tempssl.py +50 -0
- omlish/sockets/bind.py +6 -1
- omlish/sockets/server/server.py +18 -5
- omlish/specs/irc/__init__.py +0 -0
- omlish/specs/irc/format/LICENSE +11 -0
- omlish/specs/irc/format/__init__.py +61 -0
- omlish/specs/irc/format/consts.py +6 -0
- omlish/specs/irc/format/errors.py +30 -0
- omlish/specs/irc/format/message.py +18 -0
- omlish/specs/irc/format/nuh.py +52 -0
- omlish/specs/irc/format/parsing.py +155 -0
- omlish/specs/irc/format/rendering.py +150 -0
- omlish/specs/irc/format/tags.py +99 -0
- omlish/specs/irc/format/utils.py +27 -0
- omlish/specs/irc/numerics/__init__.py +0 -0
- omlish/specs/irc/numerics/formats.py +94 -0
- omlish/specs/irc/numerics/numerics.py +808 -0
- omlish/specs/irc/numerics/types.py +59 -0
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/RECORD +66 -38
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
File without changes
|
omlish/algorithm/all.py
ADDED
@@ -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(
|
7
|
-
|
8
|
-
|
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)
|
omlish/collections/__init__.py
CHANGED
omlish/collections/utils.py
CHANGED
@@ -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
|
omlish/dataclasses/__init__.py
CHANGED
omlish/dataclasses/impl/api.py
CHANGED
@@ -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(
|
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
|
omlish/dataclasses/impl/init.py
CHANGED
@@ -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:
|
omlish/dataclasses/impl/main.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
|