ovld 0.5.6__py3-none-any.whl → 0.5.8__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/core.py CHANGED
@@ -45,6 +45,7 @@ def bootstrap_dispatch(ov, name):
45
45
  dispatch.register = ov.register
46
46
  dispatch.resolve_for_values = ov.resolve_for_values
47
47
  dispatch.resolve = ov.resolve
48
+ dispatch.resolve_all = ov.resolve_all
48
49
  dispatch.copy = ov.copy
49
50
  dispatch.variant = ov.variant
50
51
  dispatch.display_methods = ov.display_methods
@@ -250,6 +251,11 @@ class Ovld:
250
251
  else:
251
252
  return self.map[args]
252
253
 
254
+ def resolve_all(self, *args, **kwargs):
255
+ """Yield all methods that match the arguments, in priority order."""
256
+ self.ensure_compiled()
257
+ return self.map.resolve_all(*args, **kwargs)
258
+
253
259
  def register_signature(self, sig, orig_fn):
254
260
  """Register a function for the given signature."""
255
261
  fn = adapt_function(orig_fn, self, f"{self.__name__}[{sigstring(sig.types)}]")
ovld/medley.py CHANGED
@@ -164,7 +164,7 @@ class medley_cls_dict(dict):
164
164
  super().__setitem__(attr, value)
165
165
 
166
166
  def __setitem__(self, attr, value):
167
- if attr == "__annotations__":
167
+ if attr == "__annotations__" or attr == "__annotate_func__":
168
168
  self.set_direct(attr, value)
169
169
  return
170
170
 
@@ -365,6 +365,7 @@ def meld_classes(classes):
365
365
  merged = medley_cls_dict(medleys)
366
366
  merged.set_direct("_ovld_codegen_fields", tuple(cg_fields))
367
367
  merged.set_direct("_ovld_medleys", tuple(medleys))
368
+ merged.set_direct("__annotations__", {name: t for name, t, f in dc_fields})
368
369
 
369
370
  if "__qualname__" in merged._combiners:
370
371
  del merged._combiners["__qualname__"]
ovld/mro.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from enum import Enum
3
3
  from graphlib import TopologicalSorter
4
- from typing import get_args, get_origin
4
+ from typing import Annotated, Any, get_args, get_origin
5
5
 
6
6
  from .utils import UnionTypes, is_dependent
7
7
 
@@ -52,6 +52,10 @@ def typeorder(t1, t2):
52
52
  """
53
53
  if t1 == t2:
54
54
  return Order.SAME
55
+ if t1 is Any:
56
+ return Order.MORE
57
+ if t2 is Any:
58
+ return Order.LESS
55
59
 
56
60
  if (
57
61
  hasattr(t1, "__type_order__")
@@ -67,14 +71,35 @@ def typeorder(t1, t2):
67
71
  o1 = get_origin(t1)
68
72
  o2 = get_origin(t2)
69
73
 
74
+ if o1 is Annotated and o2 is Annotated:
75
+ t1, *a1 = get_args(t1)
76
+ t2, *a2 = get_args(t2)
77
+ p1 = max([getattr(ann, "annotation_priority", 0) for ann in a1], default=0)
78
+ p2 = max([getattr(ann, "annotation_priority", 0) for ann in a2], default=0)
79
+ if p1 < p2:
80
+ return Order.MORE
81
+ elif p2 < p1:
82
+ return Order.LESS
83
+ else:
84
+ return typeorder(t1, t2)
85
+
86
+ if o1 is Annotated:
87
+ if t2 is Annotated:
88
+ return Order.LESS
89
+ return typeorder(get_args(t1)[0], t2)
90
+ if o2 is Annotated:
91
+ if t1 is Annotated:
92
+ return Order.MORE
93
+ return typeorder(t1, get_args(t2)[0])
94
+
70
95
  if o2 and not o1:
71
96
  return typeorder(t2, t1).opposite()
72
97
 
73
98
  if o1:
74
99
  if not o2:
75
100
  order = typeorder(o1, t2)
76
- if order is order.SAME:
77
- order = order.LESS
101
+ if order is Order.SAME:
102
+ order = Order.LESS
78
103
  return order
79
104
 
80
105
  if (order := typeorder(o1, o2)) is not Order.SAME:
@@ -93,6 +118,9 @@ def typeorder(t1, t2):
93
118
  ords = [typeorder(a1, a2) for a1, a2 in zip(args1, args2)]
94
119
  return Order.merge(ords)
95
120
 
121
+ if not isinstance(t1, type) or not isinstance(t2, type): # pragma: no cover
122
+ return Order.SAME
123
+
96
124
  sx = issubclass(t1, t2)
97
125
  sy = issubclass(t2, t1)
98
126
  if sx and sy: # pragma: no cover
@@ -108,7 +136,7 @@ def typeorder(t1, t2):
108
136
 
109
137
  def subclasscheck(t1, t2):
110
138
  """Check whether t1 is a "subclass" of t2."""
111
- if t1 == t2:
139
+ if t1 == t2 or t2 is Any:
112
140
  return True
113
141
 
114
142
  if (
@@ -134,12 +162,20 @@ def subclasscheck(t1, t2):
134
162
  o1 = get_origin(t1)
135
163
  o2 = get_origin(t2)
136
164
 
165
+ if o1 is Annotated and o2 is Annotated:
166
+ t1, *a1 = get_args(t1)
167
+ t2, *a2 = get_args(t2)
168
+ return subclasscheck(t1, t2) and any(ann in a2 for ann in a1)
169
+
170
+ if o1 is Annotated:
171
+ return t2 is Annotated
172
+
137
173
  if not isinstance(o1, type):
138
174
  o1 = None
139
175
  if not isinstance(o2, type):
140
176
  o2 = None
141
177
 
142
- if o1 or o2:
178
+ if (o1 or o2) and o2 not in UnionTypes:
143
179
  o1 = o1 or t1
144
180
  o2 = o2 or t2
145
181
  if isinstance(o1, type) and isinstance(o2, type) and issubclass(o1, o2):
ovld/typemap.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import inspect
2
2
  import math
3
3
  from dataclasses import dataclass
4
+ from functools import partial
4
5
  from itertools import count
5
6
  from types import CodeType
6
7
 
@@ -248,7 +249,7 @@ class MultiTypeMap(dict):
248
249
  co = h.__code__
249
250
  print(f"{'':{width - 2}} @ {co.co_filename}:{co.co_firstlineno}")
250
251
 
251
- def display_resolution(self, *args, **kwargs):
252
+ def _resolve_all_helper(self, *args, **kwargs):
252
253
  def dependent_match(tup, args):
253
254
  for t, a in zip(tup, args):
254
255
  if isinstance(t, tuple):
@@ -258,21 +259,35 @@ class MultiTypeMap(dict):
258
259
  return False
259
260
  return True
260
261
 
261
- message = "No method will be called."
262
262
  argt = [
263
263
  *map(subtler_type, args),
264
264
  *[(k, subtler_type(v)) for k, v in kwargs.items()],
265
265
  ]
266
- finished = False
267
- rank = 1
268
266
  for grp in self.mro(tuple(argt)):
269
- grp.sort(key=lambda x: x.handler.__name__)
270
- match = [
271
- dependent_match(self.type_tuples[c.base_handler], [*args, *kwargs.items()])
267
+ yield [
268
+ (
269
+ c,
270
+ dependent_match(
271
+ self.type_tuples[c.base_handler], [*args, *kwargs.items()]
272
+ ),
273
+ )
272
274
  for c in grp
273
275
  ]
274
- ambiguous = len([m for m in match if m]) > 1
275
- for m, c in zip(match, grp):
276
+
277
+ def resolve_all(self, *args, **kwargs):
278
+ for grp in self._resolve_all_helper(*args, **kwargs):
279
+ for c, m in grp:
280
+ if m:
281
+ yield partial(c.handler, *args, **kwargs)
282
+
283
+ def display_resolution(self, *args, **kwargs):
284
+ message = "No method will be called."
285
+ finished = False
286
+ rank = 1
287
+ for grp in self._resolve_all_helper(*args, **kwargs):
288
+ grp.sort(key=lambda x: x[0].handler.__name__)
289
+ ambiguous = len([m for _, m in grp if m]) > 1
290
+ for c, m in grp:
276
291
  handler = c.handler
277
292
  color = "\033[0m"
278
293
  if finished:
ovld/utils.py CHANGED
@@ -199,6 +199,7 @@ _standard_instancechecks = {
199
199
  type(list[object]).__instancecheck__,
200
200
  ABCMeta.__instancecheck__,
201
201
  type(typing.Protocol).__instancecheck__,
202
+ type(typing.Any).__instancecheck__,
202
203
  }
203
204
 
204
205
 
ovld/version.py CHANGED
@@ -1 +1 @@
1
- version = "0.5.6"
1
+ version = "0.5.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ovld
3
- Version: 0.5.6
3
+ Version: 0.5.8
4
4
  Summary: Overloading Python functions
5
5
  Project-URL: Homepage, https://ovld.readthedocs.io/en/latest/
6
6
  Project-URL: Documentation, https://ovld.readthedocs.io/en/latest/
@@ -22,13 +22,53 @@ With ovld, you can write a version of the same function for every type signature
22
22
 
23
23
  * ⚡️ **[Fast](https://ovld.readthedocs.io/en/latest/compare/#results):** ovld is the fastest multiple dispatch library around, by some margin.
24
24
  * 🚀 [**Variants**](https://ovld.readthedocs.io/en/latest/usage/#variants), [**mixins**](https://ovld.readthedocs.io/en/latest/usage/#mixins) and [**medleys**](https://ovld.readthedocs.io/en/latest/medley) of functions and methods.
25
- * 🦄 **[Dependent types](https://ovld.readthedocs.io/en/latest/dependent/):** Overloaded functions can depend on more than argument types: they can depend on actual values.
25
+ * 🦄 **[Value-based dispatch](https://ovld.readthedocs.io/en/latest/dependent/):** Overloaded functions can depend on more than argument types: they can depend on actual values.
26
26
  * 🔑 **[Extensive](https://ovld.readthedocs.io/en/latest/usage/#keyword-arguments):** Dispatch on functions, methods, positional arguments and even keyword arguments (with some restrictions).
27
27
  * ⚙️ **[Codegen](https://ovld.readthedocs.io/en/latest/codegen/):** (Experimental) For advanced use cases, you can generate custom code for overloads.
28
28
 
29
+ Install with `pip install ovld`
30
+
31
+
29
32
  ## Example
30
33
 
31
- Here's a function that recursively adds lists, tuples and dictionaries:
34
+ Define one version of your function for each type signature you want to support. `ovld` supports all basic types, plus literals and value-dependent types such as `Regexp`.
35
+
36
+ ```python
37
+ from ovld import ovld
38
+ from ovld.dependent import Regexp
39
+ from typing import Literal
40
+
41
+ @ovld
42
+ def f(x: str):
43
+ return f"The string {x!r}"
44
+
45
+ @ovld
46
+ def f(x: int):
47
+ return f"The number {x}"
48
+
49
+ @ovld
50
+ def f(x: int, y: int):
51
+ return "Two numbers!"
52
+
53
+ @ovld
54
+ def f(x: Literal[0]):
55
+ return "zero"
56
+
57
+ @ovld
58
+ def f(x: Regexp[r"^X"]):
59
+ return "A string that starts with X"
60
+
61
+ assert f("hello") == "The string 'hello'"
62
+ assert f(3) == "The number 3"
63
+ assert f(1, 2) == "Two numbers!"
64
+ assert f(0) == "zero"
65
+ assert f("XSECRET") == "A string that starts with X"
66
+ ```
67
+
68
+
69
+ ## Recursive example
70
+
71
+ `ovld` shines particularly with recursive definitions, for example tree maps or serialization. Here we define a function that recursively adds lists of lists and integers:
32
72
 
33
73
  ```python
34
74
  from ovld import ovld, recurse
@@ -38,18 +78,19 @@ def add(x: list, y: list):
38
78
  return [recurse(a, b) for a, b in zip(x, y)]
39
79
 
40
80
  @ovld
41
- def add(x: tuple, y: tuple):
42
- return tuple(recurse(a, b) for a, b in zip(x, y))
81
+ def add(x: list, y: int):
82
+ return [recurse(a, y) for a in x]
43
83
 
44
84
  @ovld
45
- def add(x: dict, y: dict):
46
- return {k: recurse(v, y[k]) for k, v in x.items()}
85
+ def add(x: int, y: list):
86
+ return [recurse(x, a) for a in y]
47
87
 
48
88
  @ovld
49
- def add(x: object, y: object):
89
+ def add(x: int, y: int):
50
90
  return x + y
51
91
 
52
92
  assert add([1, 2], [3, 4]) == [4, 6]
93
+ assert add([1, 2, [3]], 7) == [8, 9, [10]]
53
94
  ```
54
95
 
55
96
  The `recurse` function is special: it will recursively call the current ovld object. You may ask: how is it different from simply calling `add`? The difference is that if you create a *variant* of `add`, `recurse` will automatically call the variant.
@@ -63,7 +104,7 @@ A *variant* of an `ovld` is a copy of the `ovld`, with some methods added or cha
63
104
 
64
105
  ```python
65
106
  @add.variant
66
- def mul(x: object, y: object):
107
+ def mul(x: int, y: int):
67
108
  return x * y
68
109
 
69
110
  assert mul([1, 2], [3, 4]) == [3, 8]
@@ -92,9 +133,9 @@ assert f(10) == 121
92
133
 
93
134
  Both definitions above have the same type signature, but since the first has higher priority, that is the one that will be called.
94
135
 
95
- However, that does not mean there is no way to call the second one. Indeed, when the first function calls the special function `call_next(x + 1)`, it will call the next function in the list below itself.
136
+ However, that does not mean there is no way to call the second one. Indeed, when the first function calls the special function `call_next(x + 1)`, it will call the next function in line, in order of priority and specificity.
96
137
 
97
- The pattern you see above is how you may wrap each call with some generic behavior. For instance, if you did something like that:
138
+ The pattern you see above is how you may wrap each call with some generic behavior. For instance, if you did something like this:
98
139
 
99
140
  ```python
100
141
  @f.variant(priority=1000)
@@ -103,12 +144,12 @@ def f2(x: object)
103
144
  return call_next(x)
104
145
  ```
105
146
 
106
- You would effectively be creating a clone of `f` that traces every call.
147
+ The above is effectively a clone of `f` that traces every call. Useful for debugging.
107
148
 
108
149
 
109
150
  ## Dependent types
110
151
 
111
- A dependent type is a type that depends on a value. `ovld` supports this, either through `Literal[value]` or `Dependent[bound, check]`. For example, this definition of factorial:
152
+ A dependent type is a type that depends on a value. This enables dispatching based on the actual value of an argument. The simplest example of a dependent type is `typing.Literal[value]`, which matches one single value. `ovld` also supports `Dependent[bound, check]` for arbitrary checks. For example, this definition of factorial:
112
153
 
113
154
  ```python
114
155
  from typing import Literal
@@ -0,0 +1,18 @@
1
+ ovld/__init__.py,sha256=JuCM8Sj65gobV0KYyLr95cSI23Pi6RYZ7X3_F3fdsSw,1821
2
+ ovld/abc.py,sha256=4qpZyYwI8dWgY1Oiv5FhdKg2uzNcyWxIpGmGJVcjXrs,1177
3
+ ovld/codegen.py,sha256=27tmamlanuTPDT-x31ISyqP0wGKW9BCFZJGVyq9qLg8,9728
4
+ ovld/core.py,sha256=WqZ1lvcAGSri02XZeY73Bj5AKB9RYBCAvHLbyns8u68,17792
5
+ ovld/dependent.py,sha256=JIgsc_5ddPH51_2IrZ6JW6bWE5RyrrrOwR2e9UvDhZ4,8922
6
+ ovld/medley.py,sha256=Pq6j4qPl8osyQ4Xp-ktnt2uJXytC9Jo70toS5LYHRf8,12840
7
+ ovld/mro.py,sha256=5h0JoMGUYNoj4RmVi2uElZB9PGWj_IEvLvBWYtAtYRw,6033
8
+ ovld/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ ovld/recode.py,sha256=vXg9XLExp_9LdAHO0JWR4wvwHhpOLu2Xcrg9ZYg1nms,16407
10
+ ovld/signatures.py,sha256=Q8JucSOun0ESGx14aWtHtBzLEiM6FxY5HP3imyqXoDo,8984
11
+ ovld/typemap.py,sha256=wkLuCc6xa2VZJOMaAhuYYgnNrywhovkQwbkBnoRfCsY,13985
12
+ ovld/types.py,sha256=CRL6Vuzg5moXgAAhIj2698GvZoyF4HWbUDYz2hKt6us,13373
13
+ ovld/utils.py,sha256=8nvycMWpTmwGq7ojjDA7yi2NdkU78NBdUlvRk_vDECY,5086
14
+ ovld/version.py,sha256=IAa3xnF9S8cqANXapf16xG7B-aQG7AHxJ3Iio8QzYuc,18
15
+ ovld-0.5.8.dist-info/METADATA,sha256=b1vd6kxUQ18CJ7t6b3H8zAmyTZaCpFowtZNq_sxJhSE,10458
16
+ ovld-0.5.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ ovld-0.5.8.dist-info/licenses/LICENSE,sha256=cSwNTIzd1cbI89xt3PeZZYJP2y3j8Zus4bXgo4svpX8,1066
18
+ ovld-0.5.8.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- ovld/__init__.py,sha256=JuCM8Sj65gobV0KYyLr95cSI23Pi6RYZ7X3_F3fdsSw,1821
2
- ovld/abc.py,sha256=4qpZyYwI8dWgY1Oiv5FhdKg2uzNcyWxIpGmGJVcjXrs,1177
3
- ovld/codegen.py,sha256=27tmamlanuTPDT-x31ISyqP0wGKW9BCFZJGVyq9qLg8,9728
4
- ovld/core.py,sha256=HEREHblKcjM9dhFBr0FNwUCyec7o-9XjCsCfJ23SnNw,17544
5
- ovld/dependent.py,sha256=JIgsc_5ddPH51_2IrZ6JW6bWE5RyrrrOwR2e9UvDhZ4,8922
6
- ovld/medley.py,sha256=0fseIntzJRCPYXq-tmnxgy5ipNa4ZxR0D_6So0xstdQ,12729
7
- ovld/mro.py,sha256=Aw1r5Zz7V9cVBDwWzQ-WNnbpBwoGztgbw3wLAyS6Y60,4863
8
- ovld/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- ovld/recode.py,sha256=vXg9XLExp_9LdAHO0JWR4wvwHhpOLu2Xcrg9ZYg1nms,16407
10
- ovld/signatures.py,sha256=Q8JucSOun0ESGx14aWtHtBzLEiM6FxY5HP3imyqXoDo,8984
11
- ovld/typemap.py,sha256=EH3oM_QmX-SHLEz14saVrQtRlqG_ltPyJGORLvcGOFk,13520
12
- ovld/types.py,sha256=CRL6Vuzg5moXgAAhIj2698GvZoyF4HWbUDYz2hKt6us,13373
13
- ovld/utils.py,sha256=cyy9pcuMhmo1_UdPonH9JT6B9QlI4oH6_JK89cM3_gk,5046
14
- ovld/version.py,sha256=FtfC8ptaFH0Unc72bPRynMH_N62bxa4tnazGgIcgnTY,18
15
- ovld-0.5.6.dist-info/METADATA,sha256=NuqYyUraNAzGzrl8XUVET_xO6xBSzbPhy5F5AtzITWk,9383
16
- ovld-0.5.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- ovld-0.5.6.dist-info/licenses/LICENSE,sha256=cSwNTIzd1cbI89xt3PeZZYJP2y3j8Zus4bXgo4svpX8,1066
18
- ovld-0.5.6.dist-info/RECORD,,
File without changes