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/dependent.py CHANGED
@@ -1,12 +1,20 @@
1
1
  import inspect
2
2
  import re
3
- from dataclasses import dataclass
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 TYPE_CHECKING, Any, Mapping, TypeVar, Union
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
- @dataclass
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: str
21
- substitutions: dict
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
- class DependentType:
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})", {"this": self})
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
- if not isinstance(other, DependentType):
108
- return NotImplemented
109
- return And(self, other)
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
- params = ", ".join(map(repr, self.parameters))
141
- return f"{type(self).__name__}({params})"
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
- fn = type(self).func
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
- t = type(fn.__name__, (FuncDependentType,), {"func": fn})
172
- if len(inspect.signature(fn).parameters) == 1:
173
- t = t()
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})", {"p": self.parameter})
265
+ return CodeGen("({arg} == {p})", p=self.parameter)
196
266
  else:
197
- return CodeGen("({arg} in {ps})", {"ps": self.parameters})
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 HasKeys(value: Mapping, *keys):
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
- def Regexp(value: str, regexp):
217
- return bool(re.search(pattern=regexp, string=value))
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
- "HasKeys",
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 = getattr(t1, "__origin__", None)
64
- o2 = getattr(t2, "__origin__", None)
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: # or getattr(t2, "__args__", None) is None:
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 = getattr(t1, "__args__", ())
96
- args2 = getattr(t2, "__args__", ())
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
- if Order.MORE in ords and Order.LESS in ords:
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 = _issubclass(t1, t2)
119
- sy = _issubclass(t2, t1)
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 = getattr(t1, "__origin__", None)
149
- o2 = getattr(t2, "__origin__", None)
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 _issubclass(o1, o2):
135
+ if issubclass(o1, o2):
169
136
  if o2 is t2: # pragma: no cover
170
137
  return True
171
138
  else:
172
- args1 = getattr(t1, "__args__", ())
173
- args2 = getattr(t2, "__args__", ())
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
- return _issubclass(t1, t2)
149
+ try:
150
+ return issubclass(t1, t2)
151
+ except TypeError:
152
+ return False
183
153
 
184
154
 
185
155
  def sort_types(cls, avail):