ovld 0.4.3__py3-none-any.whl → 0.4.5__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.
- ovld/__init__.py +1 -2
- ovld/abc.py +48 -0
- ovld/core.py +197 -214
- ovld/dependent.py +182 -74
- ovld/mro.py +30 -60
- ovld/recode.py +92 -90
- ovld/typemap.py +9 -31
- ovld/types.py +282 -72
- ovld/utils.py +72 -0
- ovld/version.py +1 -1
- {ovld-0.4.3.dist-info → ovld-0.4.5.dist-info}/METADATA +11 -10
- ovld-0.4.5.dist-info/RECORD +14 -0
- ovld-0.4.3.dist-info/RECORD +0 -13
- {ovld-0.4.3.dist-info → ovld-0.4.5.dist-info}/WHEEL +0 -0
- {ovld-0.4.3.dist-info → ovld-0.4.5.dist-info}/licenses/LICENSE +0 -0
ovld/dependent.py
CHANGED
@@ -1,12 +1,20 @@
|
|
1
1
|
import inspect
|
2
2
|
import re
|
3
|
-
from
|
3
|
+
from collections.abc import Callable as _Callable
|
4
|
+
from collections.abc import Mapping, Sequence
|
5
|
+
from functools import partial
|
4
6
|
from itertools import count
|
5
|
-
from typing import
|
7
|
+
from typing import (
|
8
|
+
TYPE_CHECKING,
|
9
|
+
Any,
|
10
|
+
Collection,
|
11
|
+
TypeVar,
|
12
|
+
)
|
6
13
|
|
7
14
|
from .types import (
|
8
15
|
Intersection,
|
9
16
|
Order,
|
17
|
+
clsstring,
|
10
18
|
normalize_type,
|
11
19
|
subclasscheck,
|
12
20
|
typeorder,
|
@@ -15,10 +23,17 @@ from .types import (
|
|
15
23
|
_current = count()
|
16
24
|
|
17
25
|
|
18
|
-
|
26
|
+
def generate_checking_code(typ):
|
27
|
+
if hasattr(typ, "codegen"):
|
28
|
+
return typ.codegen()
|
29
|
+
else:
|
30
|
+
return CodeGen("isinstance({arg}, {this})", this=typ)
|
31
|
+
|
32
|
+
|
19
33
|
class CodeGen:
|
20
|
-
template:
|
21
|
-
|
34
|
+
def __init__(self, template, substitutions={}, **substitutions_kw):
|
35
|
+
self.template = template
|
36
|
+
self.substitutions = {**substitutions, **substitutions_kw}
|
22
37
|
|
23
38
|
def mangle(self):
|
24
39
|
renamings = {
|
@@ -30,10 +45,7 @@ class CodeGen:
|
|
30
45
|
for k, newk in renamings.items()
|
31
46
|
if k in self.substitutions
|
32
47
|
}
|
33
|
-
return CodeGen(
|
34
|
-
template=self.template.format(**renamings),
|
35
|
-
substitutions=new_subs,
|
36
|
-
)
|
48
|
+
return CodeGen(self.template.format(**renamings), new_subs)
|
37
49
|
|
38
50
|
|
39
51
|
def combine(master_template, args):
|
@@ -46,9 +58,22 @@ def combine(master_template, args):
|
|
46
58
|
return CodeGen(master_template.format(*fmts), subs)
|
47
59
|
|
48
60
|
|
49
|
-
|
61
|
+
def is_dependent(t):
|
62
|
+
if isinstance(t, DependentType):
|
63
|
+
return True
|
64
|
+
elif any(is_dependent(subt) for subt in getattr(t, "__args__", ())):
|
65
|
+
return True
|
66
|
+
return False
|
67
|
+
|
68
|
+
|
69
|
+
class DependentType(type):
|
50
70
|
exclusive_type = False
|
51
71
|
keyable_type = False
|
72
|
+
bound_is_name = False
|
73
|
+
|
74
|
+
def __new__(cls, *args, **kwargs):
|
75
|
+
value = super().__new__(cls, cls.__name__, (), {})
|
76
|
+
return value
|
52
77
|
|
53
78
|
def __init__(self, bound):
|
54
79
|
self.bound = bound
|
@@ -64,7 +89,7 @@ class DependentType:
|
|
64
89
|
raise NotImplementedError()
|
65
90
|
|
66
91
|
def codegen(self):
|
67
|
-
return CodeGen("{this}.check({arg})",
|
92
|
+
return CodeGen("{this}.check({arg})", this=self)
|
68
93
|
|
69
94
|
def __type_order__(self, other):
|
70
95
|
if isinstance(other, DependentType):
|
@@ -95,18 +120,19 @@ class DependentType:
|
|
95
120
|
else:
|
96
121
|
return False
|
97
122
|
|
123
|
+
def __instancecheck__(self, other):
|
124
|
+
return isinstance(other, self.bound) and self.check(other)
|
125
|
+
|
98
126
|
def __lt__(self, other):
|
99
127
|
return False
|
100
128
|
|
101
|
-
def __or__(self, other):
|
102
|
-
if not isinstance(other, DependentType):
|
103
|
-
return NotImplemented
|
104
|
-
return Or(self, other)
|
105
|
-
|
106
129
|
def __and__(self, other):
|
107
|
-
|
108
|
-
|
109
|
-
|
130
|
+
return Intersection[self, other]
|
131
|
+
|
132
|
+
def __rand__(self, other):
|
133
|
+
return Intersection[other, self]
|
134
|
+
|
135
|
+
__repr__ = __str__ = clsstring
|
110
136
|
|
111
137
|
|
112
138
|
class ParametrizedDependentType(DependentType):
|
@@ -114,7 +140,12 @@ class ParametrizedDependentType(DependentType):
|
|
114
140
|
super().__init__(
|
115
141
|
self.default_bound(*parameters) if bound is None else bound
|
116
142
|
)
|
117
|
-
self.parameters = parameters
|
143
|
+
self.__args__ = self.parameters = parameters
|
144
|
+
self.__origin__ = None
|
145
|
+
self.__post_init__()
|
146
|
+
|
147
|
+
def __post_init__(self):
|
148
|
+
pass
|
118
149
|
|
119
150
|
@property
|
120
151
|
def parameter(self):
|
@@ -137,18 +168,24 @@ class ParametrizedDependentType(DependentType):
|
|
137
168
|
return hash(self.parameters) ^ hash(self.bound)
|
138
169
|
|
139
170
|
def __str__(self):
|
140
|
-
|
141
|
-
|
171
|
+
if self.bound_is_name:
|
172
|
+
origin = self.bound
|
173
|
+
bound = ""
|
174
|
+
else:
|
175
|
+
origin = self
|
176
|
+
if self.bound != self.default_bound(*self.parameters):
|
177
|
+
bound = f" < {clsstring(self.bound)}"
|
178
|
+
else:
|
179
|
+
bound = ""
|
180
|
+
args = ", ".join(map(clsstring, self.__args__))
|
181
|
+
return f"{origin.__name__}[{args}]{bound}"
|
142
182
|
|
143
183
|
__repr__ = __str__
|
144
184
|
|
145
185
|
|
146
186
|
class FuncDependentType(ParametrizedDependentType):
|
147
187
|
def default_bound(self, *_):
|
148
|
-
|
149
|
-
return normalize_type(
|
150
|
-
list(inspect.signature(fn).parameters.values())[0].annotation, fn
|
151
|
-
)
|
188
|
+
return self._default_bound
|
152
189
|
|
153
190
|
def __lt__(self, other):
|
154
191
|
if len(self.parameters) != len(other.parameters):
|
@@ -167,10 +204,43 @@ class FuncDependentType(ParametrizedDependentType):
|
|
167
204
|
return type(self).func(value, *self.parameters)
|
168
205
|
|
169
206
|
|
170
|
-
def dependent_check(fn):
|
171
|
-
|
172
|
-
|
173
|
-
|
207
|
+
def dependent_check(fn=None, bound_is_name=False):
|
208
|
+
if fn is None:
|
209
|
+
return partial(dependent_check, bound_is_name=bound_is_name)
|
210
|
+
|
211
|
+
if isinstance(fn, type):
|
212
|
+
params = inspect.signature(fn.check).parameters
|
213
|
+
bound = normalize_type(
|
214
|
+
list(inspect.signature(fn.check).parameters.values())[1].annotation,
|
215
|
+
fn.check,
|
216
|
+
)
|
217
|
+
t = type(
|
218
|
+
fn.__name__,
|
219
|
+
(FuncDependentType,),
|
220
|
+
{
|
221
|
+
"bound_is_name": bound_is_name,
|
222
|
+
"_default_bound": bound,
|
223
|
+
**vars(fn),
|
224
|
+
},
|
225
|
+
)
|
226
|
+
|
227
|
+
else:
|
228
|
+
params = inspect.signature(fn).parameters
|
229
|
+
bound = normalize_type(
|
230
|
+
list(inspect.signature(fn).parameters.values())[0].annotation, fn
|
231
|
+
)
|
232
|
+
t = type(
|
233
|
+
fn.__name__,
|
234
|
+
(FuncDependentType,),
|
235
|
+
{
|
236
|
+
"func": fn,
|
237
|
+
"bound_is_name": bound_is_name,
|
238
|
+
"_default_bound": bound,
|
239
|
+
},
|
240
|
+
)
|
241
|
+
if len(params) == 1:
|
242
|
+
t = t()
|
243
|
+
|
174
244
|
return t
|
175
245
|
|
176
246
|
|
@@ -192,13 +262,82 @@ class Equals(ParametrizedDependentType):
|
|
192
262
|
|
193
263
|
def codegen(self):
|
194
264
|
if len(self.parameters) == 1:
|
195
|
-
return CodeGen("({arg} == {p})",
|
265
|
+
return CodeGen("({arg} == {p})", p=self.parameter)
|
196
266
|
else:
|
197
|
-
return CodeGen("({arg} in {ps})",
|
267
|
+
return CodeGen("({arg} in {ps})", ps=self.parameters)
|
268
|
+
|
269
|
+
|
270
|
+
class ProductType(ParametrizedDependentType):
|
271
|
+
bound_is_name = True
|
272
|
+
|
273
|
+
def default_bound(self, *subtypes):
|
274
|
+
return tuple
|
275
|
+
|
276
|
+
def check(self, value):
|
277
|
+
return (
|
278
|
+
isinstance(value, tuple)
|
279
|
+
and len(value) == len(self.parameters)
|
280
|
+
and all(isinstance(x, t) for x, t in zip(value, self.parameters))
|
281
|
+
)
|
282
|
+
|
283
|
+
def codegen(self):
|
284
|
+
checks = ["len({arg}) == {n}"]
|
285
|
+
params = {"n": len(self.parameters)}
|
286
|
+
for i, p in enumerate(self.parameters):
|
287
|
+
checks.append(f"isinstance({{arg}}[{i}], {{p{i}}})")
|
288
|
+
params[f"p{i}"] = p
|
289
|
+
return CodeGen(" and ".join(checks), params)
|
290
|
+
|
291
|
+
def __type_order__(self, other):
|
292
|
+
if isinstance(other, ProductType):
|
293
|
+
if len(other.parameters) == len(self.parameters):
|
294
|
+
return Order.merge(
|
295
|
+
typeorder(a, b)
|
296
|
+
for a, b in zip(self.parameters, other.parameters)
|
297
|
+
)
|
298
|
+
else:
|
299
|
+
return Order.NONE
|
300
|
+
else:
|
301
|
+
return NotImplemented
|
302
|
+
|
303
|
+
|
304
|
+
@dependent_check(bound_is_name=True)
|
305
|
+
def SequenceFastCheck(value: Sequence, typ):
|
306
|
+
return not value or isinstance(value[0], typ)
|
307
|
+
|
308
|
+
|
309
|
+
@dependent_check(bound_is_name=True)
|
310
|
+
def CollectionFastCheck(value: Collection, typ):
|
311
|
+
for x in value:
|
312
|
+
return isinstance(x, typ)
|
313
|
+
else:
|
314
|
+
return True
|
315
|
+
|
316
|
+
|
317
|
+
@dependent_check(bound_is_name=True)
|
318
|
+
def MappingFastCheck(value: Mapping, kt, vt):
|
319
|
+
if not value:
|
320
|
+
return True
|
321
|
+
for k in value:
|
322
|
+
break
|
323
|
+
return isinstance(k, kt) and isinstance(value[k], vt)
|
198
324
|
|
199
325
|
|
200
326
|
@dependent_check
|
201
|
-
def
|
327
|
+
def Callable(fn: _Callable, argt, rett):
|
328
|
+
from .core import Signature
|
329
|
+
|
330
|
+
sig = Signature.extract(fn)
|
331
|
+
return (
|
332
|
+
sig.max_pos >= len(argt) >= sig.req_pos
|
333
|
+
and not sig.req_names
|
334
|
+
and all(subclasscheck(t1, t2) for t1, t2 in zip(argt, sig.types))
|
335
|
+
and subclasscheck(sig.return_type, rett)
|
336
|
+
)
|
337
|
+
|
338
|
+
|
339
|
+
@dependent_check
|
340
|
+
def HasKey(value: Mapping, *keys):
|
202
341
|
return all(k in value for k in keys)
|
203
342
|
|
204
343
|
|
@@ -213,8 +352,15 @@ def EndsWith(value: str, suffix):
|
|
213
352
|
|
214
353
|
|
215
354
|
@dependent_check
|
216
|
-
|
217
|
-
|
355
|
+
class Regexp:
|
356
|
+
def __post_init__(self):
|
357
|
+
self.rx = re.compile(self.parameter)
|
358
|
+
|
359
|
+
def check(self, value: str):
|
360
|
+
return bool(self.rx.search(value))
|
361
|
+
|
362
|
+
def codegen(self):
|
363
|
+
return CodeGen("bool({rx}.search({arg}))", rx=self.rx)
|
218
364
|
|
219
365
|
|
220
366
|
class Dependent:
|
@@ -225,44 +371,6 @@ class Dependent:
|
|
225
371
|
return dt.with_bound(bound)
|
226
372
|
|
227
373
|
|
228
|
-
class Or(DependentType):
|
229
|
-
def __init__(self, *types, bound=None):
|
230
|
-
self.types = types
|
231
|
-
super().__init__(bound or self.default_bound())
|
232
|
-
|
233
|
-
def default_bound(self):
|
234
|
-
return Union[tuple([t.bound for t in self.types])]
|
235
|
-
|
236
|
-
def codegen(self):
|
237
|
-
template = " or ".join("{}" for t in self.types)
|
238
|
-
return combine(template, [t.codegen() for t in self.types])
|
239
|
-
|
240
|
-
def check(self, value):
|
241
|
-
return any(t.check(value) for t in self.types)
|
242
|
-
|
243
|
-
|
244
|
-
class And(DependentType):
|
245
|
-
def __init__(self, *types, bound=None):
|
246
|
-
self.types = types
|
247
|
-
super().__init__(bound or self.default_bound())
|
248
|
-
|
249
|
-
def default_bound(self):
|
250
|
-
bounds = frozenset(t.bound for t in self.types)
|
251
|
-
return Intersection[tuple(bounds)]
|
252
|
-
|
253
|
-
def codegen(self):
|
254
|
-
template = " and ".join("{}" for t in self.types)
|
255
|
-
return combine(template, [t.codegen() for t in self.types])
|
256
|
-
|
257
|
-
def check(self, value):
|
258
|
-
return all(t.check(value) for t in self.types)
|
259
|
-
|
260
|
-
def __str__(self):
|
261
|
-
return " & ".join(map(repr, self.types))
|
262
|
-
|
263
|
-
__repr__ = __str__
|
264
|
-
|
265
|
-
|
266
374
|
if TYPE_CHECKING: # pragma: no cover
|
267
375
|
from typing import Annotated, TypeAlias
|
268
376
|
|
@@ -275,7 +383,7 @@ __all__ = [
|
|
275
383
|
"Dependent",
|
276
384
|
"DependentType",
|
277
385
|
"Equals",
|
278
|
-
"
|
386
|
+
"HasKey",
|
279
387
|
"StartsWith",
|
280
388
|
"EndsWith",
|
281
389
|
"dependent_check",
|
ovld/mro.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
import typing
|
2
1
|
from dataclasses import dataclass
|
3
2
|
from enum import Enum
|
4
3
|
from graphlib import TopologicalSorter
|
4
|
+
from typing import get_args, get_origin
|
5
5
|
|
6
6
|
|
7
7
|
class Order(Enum):
|
@@ -18,6 +18,18 @@ class Order(Enum):
|
|
18
18
|
else:
|
19
19
|
return self
|
20
20
|
|
21
|
+
@staticmethod
|
22
|
+
def merge(orders):
|
23
|
+
orders = set(orders)
|
24
|
+
if orders == {Order.SAME}:
|
25
|
+
return Order.SAME
|
26
|
+
elif not (orders - {Order.LESS, Order.SAME}):
|
27
|
+
return Order.LESS
|
28
|
+
elif not (orders - {Order.MORE, Order.SAME}):
|
29
|
+
return Order.MORE
|
30
|
+
else:
|
31
|
+
return Order.NONE
|
32
|
+
|
21
33
|
|
22
34
|
@dataclass
|
23
35
|
class TypeRelationship:
|
@@ -26,16 +38,6 @@ class TypeRelationship:
|
|
26
38
|
subtype: bool = NotImplemented
|
27
39
|
|
28
40
|
|
29
|
-
def _issubclass(t1, t2):
|
30
|
-
try:
|
31
|
-
return issubclass(t1, t2)
|
32
|
-
except TypeError:
|
33
|
-
try:
|
34
|
-
return isinstance(t1, t2)
|
35
|
-
except TypeError: # pragma: no cover
|
36
|
-
return False
|
37
|
-
|
38
|
-
|
39
41
|
def typeorder(t1, t2):
|
40
42
|
"""Order relation between two types.
|
41
43
|
|
@@ -60,30 +62,14 @@ def typeorder(t1, t2):
|
|
60
62
|
):
|
61
63
|
return result.opposite()
|
62
64
|
|
63
|
-
o1 =
|
64
|
-
o2 =
|
65
|
-
|
66
|
-
if o2 is typing.Union:
|
67
|
-
if t1 is typing.Union:
|
68
|
-
return Order.MORE
|
69
|
-
compare = [
|
70
|
-
x for t in t2.__args__ if (x := typeorder(t1, t)) is not Order.NONE
|
71
|
-
]
|
72
|
-
if not compare:
|
73
|
-
return Order.NONE
|
74
|
-
elif any(x is Order.LESS or x is Order.SAME for x in compare):
|
75
|
-
return Order.LESS
|
76
|
-
else:
|
77
|
-
return Order.MORE
|
78
|
-
|
79
|
-
if o1 is typing.Union:
|
80
|
-
return typeorder(t2, t1).opposite()
|
65
|
+
o1 = get_origin(t1)
|
66
|
+
o2 = get_origin(t2)
|
81
67
|
|
82
68
|
if o2 and not o1:
|
83
69
|
return typeorder(t2, t1).opposite()
|
84
70
|
|
85
71
|
if o1:
|
86
|
-
if not o2:
|
72
|
+
if not o2:
|
87
73
|
order = typeorder(o1, t2)
|
88
74
|
if order is order.SAME:
|
89
75
|
order = order.LESS
|
@@ -92,8 +78,8 @@ def typeorder(t1, t2):
|
|
92
78
|
if (order := typeorder(o1, o2)) is not Order.SAME:
|
93
79
|
return order
|
94
80
|
|
95
|
-
args1 =
|
96
|
-
args2 =
|
81
|
+
args1 = get_args(t1)
|
82
|
+
args2 = get_args(t2)
|
97
83
|
|
98
84
|
if args1 and not args2:
|
99
85
|
return Order.LESS
|
@@ -103,20 +89,10 @@ def typeorder(t1, t2):
|
|
103
89
|
return Order.NONE
|
104
90
|
|
105
91
|
ords = [typeorder(a1, a2) for a1, a2 in zip(args1, args2)]
|
106
|
-
|
107
|
-
return Order.NONE
|
108
|
-
elif Order.NONE in ords:
|
109
|
-
return Order.NONE
|
110
|
-
elif Order.MORE in ords:
|
111
|
-
return Order.MORE
|
112
|
-
elif Order.LESS in ords:
|
113
|
-
return Order.LESS
|
114
|
-
else: # pragma: no cover
|
115
|
-
# Not sure when t1 != t2 and that happens
|
116
|
-
return Order.SAME
|
92
|
+
return Order.merge(ords)
|
117
93
|
|
118
|
-
sx =
|
119
|
-
sy =
|
94
|
+
sx = issubclass(t1, t2)
|
95
|
+
sy = issubclass(t2, t1)
|
120
96
|
if sx and sy: # pragma: no cover
|
121
97
|
# Not sure when t1 != t2 and that happens
|
122
98
|
return Order.SAME
|
@@ -145,17 +121,8 @@ def subclasscheck(t1, t2):
|
|
145
121
|
):
|
146
122
|
return result
|
147
123
|
|
148
|
-
o1 =
|
149
|
-
o2 =
|
150
|
-
|
151
|
-
if o2 is typing.Union:
|
152
|
-
return t1 is typing.Union or any(
|
153
|
-
subclasscheck(t1, t) for t in t2.__args__
|
154
|
-
)
|
155
|
-
elif o1 is typing.Union:
|
156
|
-
return t2 is typing.Union or all(
|
157
|
-
subclasscheck(t, t2) for t in t1.__args__
|
158
|
-
)
|
124
|
+
o1 = get_origin(t1)
|
125
|
+
o2 = get_origin(t2)
|
159
126
|
|
160
127
|
if not isinstance(o1, type):
|
161
128
|
o1 = None
|
@@ -165,12 +132,12 @@ def subclasscheck(t1, t2):
|
|
165
132
|
if o1 or o2:
|
166
133
|
o1 = o1 or t1
|
167
134
|
o2 = o2 or t2
|
168
|
-
if
|
135
|
+
if issubclass(o1, o2):
|
169
136
|
if o2 is t2: # pragma: no cover
|
170
137
|
return True
|
171
138
|
else:
|
172
|
-
args1 =
|
173
|
-
args2 =
|
139
|
+
args1 = get_args(t1)
|
140
|
+
args2 = get_args(t2)
|
174
141
|
if len(args1) != len(args2):
|
175
142
|
return False
|
176
143
|
return all(
|
@@ -179,7 +146,10 @@ def subclasscheck(t1, t2):
|
|
179
146
|
else:
|
180
147
|
return False
|
181
148
|
else:
|
182
|
-
|
149
|
+
try:
|
150
|
+
return issubclass(t1, t2)
|
151
|
+
except TypeError:
|
152
|
+
return False
|
183
153
|
|
184
154
|
|
185
155
|
def sort_types(cls, avail):
|