ovld 0.4.0__py3-none-any.whl → 0.4.2__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 CHANGED
@@ -70,4 +70,5 @@ __all__ = [
70
70
  "keyword_decorator",
71
71
  "call_next",
72
72
  "recurse",
73
+ "__version__",
73
74
  ]
ovld/core.py CHANGED
@@ -20,12 +20,6 @@ from .typemap import MultiTypeMap, is_type_of_type
20
20
  from .types import normalize_type
21
21
  from .utils import UsageError, keyword_decorator
22
22
 
23
- try:
24
- from types import UnionType
25
- except ImportError: # pragma: no cover
26
- UnionType = None
27
-
28
-
29
23
  _current_id = itertools.count()
30
24
 
31
25
 
@@ -91,6 +85,7 @@ class Signature:
91
85
  req_names: frozenset
92
86
  vararg: bool
93
87
  priority: float
88
+ tiebreak: int = 0
94
89
  is_method: bool = False
95
90
  arginfo: list[Arginfo] = field(
96
91
  default_factory=list, hash=False, compare=False
@@ -399,8 +394,8 @@ class _Ovld:
399
394
  )
400
395
  else:
401
396
  hlp = ""
402
- for p, prio, spc in possibilities:
403
- hlp += f"* {p.__name__} (priority: {prio}, specificity: {list(spc)})\n"
397
+ for c in possibilities:
398
+ hlp += f"* {c.handler.__name__} (priority: {c.priority}, specificity: {list(c.specificity)})\n"
404
399
  return TypeError(
405
400
  f"Ambiguous resolution in {self} for"
406
401
  f" argument types [{typenames}]\n"
@@ -509,7 +504,15 @@ class _Ovld:
509
504
  raise TypeError(
510
505
  f"There is already a method for {sigstring(sig.types)}"
511
506
  )
512
- self._defns[sig] = fn
507
+
508
+ def _set(sig, fn):
509
+ if sig in self._defns:
510
+ # Push down the existing handler with a lower tiebreak
511
+ msig = replace(sig, tiebreak=sig.tiebreak - 1)
512
+ _set(msig, self._defns[sig])
513
+ self._defns[sig] = fn
514
+
515
+ _set(sig, fn)
513
516
 
514
517
  self._update()
515
518
  return self
ovld/mro.py CHANGED
@@ -25,6 +25,16 @@ class TypeRelationship:
25
25
  matches: bool = None
26
26
 
27
27
 
28
+ def _issubclass(t1, t2):
29
+ try:
30
+ return issubclass(t1, t2)
31
+ except TypeError:
32
+ try:
33
+ return isinstance(t1, t2)
34
+ except TypeError: # pragma: no cover
35
+ return False
36
+
37
+
28
38
  def typeorder(t1, t2):
29
39
  """Order relation between two types.
30
40
 
@@ -56,6 +66,8 @@ def typeorder(t1, t2):
56
66
  o2 = getattr(t2, "__origin__", None)
57
67
 
58
68
  if o2 is typing.Union:
69
+ if t1 is typing.Union:
70
+ return Order.MORE
59
71
  compare = [
60
72
  x for t in t2.__args__ if (x := typeorder(t1, t)) is not Order.NONE
61
73
  ]
@@ -105,8 +117,8 @@ def typeorder(t1, t2):
105
117
  # Not sure when t1 != t2 and that happens
106
118
  return Order.SAME
107
119
 
108
- sx = issubclass(t1, t2)
109
- sy = issubclass(t2, t1)
120
+ sx = _issubclass(t1, t2)
121
+ sy = _issubclass(t2, t1)
110
122
  if sx and sy: # pragma: no cover
111
123
  # Not sure when t1 != t2 and that happens
112
124
  return Order.SAME
@@ -136,14 +148,23 @@ def subclasscheck(t1, t2):
136
148
  o2 = getattr(t2, "__origin__", None)
137
149
 
138
150
  if o2 is typing.Union:
139
- return any(subclasscheck(t1, t) for t in t2.__args__)
151
+ return t1 is typing.Union or any(
152
+ subclasscheck(t1, t) for t in t2.__args__
153
+ )
140
154
  elif o1 is typing.Union:
141
- return all(subclasscheck(t, t2) for t in t1.__args__)
155
+ return t2 is typing.Union or all(
156
+ subclasscheck(t, t2) for t in t1.__args__
157
+ )
158
+
159
+ if not isinstance(o1, type):
160
+ o1 = None
161
+ if not isinstance(o2, type):
162
+ o2 = None
142
163
 
143
164
  if o1 or o2:
144
165
  o1 = o1 or t1
145
166
  o2 = o2 or t2
146
- if issubclass(o1, o2):
167
+ if _issubclass(o1, o2):
147
168
  if o2 is t2: # pragma: no cover
148
169
  return True
149
170
  else:
@@ -157,7 +178,7 @@ def subclasscheck(t1, t2):
157
178
  else:
158
179
  return False
159
180
  else:
160
- return issubclass(t1, t2)
181
+ return _issubclass(t1, t2)
161
182
 
162
183
 
163
184
  def sort_types(cls, avail):
ovld/recode.py CHANGED
@@ -373,6 +373,7 @@ class NameConverter(ast.NodeTransformer):
373
373
  self.call_next_sym = call_next_sym
374
374
  self.ovld_mangled = ovld_mangled
375
375
  self.code_mangled = code_mangled
376
+ self.count = count()
376
377
 
377
378
  def visit_Name(self, node):
378
379
  if node.id == self.recurse_sym:
@@ -396,15 +397,16 @@ class NameConverter(ast.NodeTransformer):
396
397
  return self.generic_visit(node)
397
398
 
398
399
  cn = node.func.id == self.call_next_sym
400
+ tmp = f"__TMP{next(self.count)}_"
399
401
 
400
402
  def _make_lookup_call(key, arg):
401
403
  value = ast.NamedExpr(
402
- target=ast.Name(id=f"__TMP{key}", ctx=ast.Store()),
404
+ target=ast.Name(id=f"{tmp}{key}", ctx=ast.Store()),
403
405
  value=self.visit(arg),
404
406
  )
405
407
  if self.analysis.lookup_for(key) == "self.map.transform":
406
408
  func = ast.Attribute(
407
- value=ast.Name(id="__TMPM", ctx=ast.Load()),
409
+ value=ast.Name(id=f"{tmp}M", ctx=ast.Load()),
408
410
  attr="transform",
409
411
  ctx=ast.Load(),
410
412
  )
@@ -437,7 +439,7 @@ class NameConverter(ast.NodeTransformer):
437
439
  type_parts.insert(0, ast.Name(id=self.code_mangled, ctx=ast.Load()))
438
440
  method = ast.Subscript(
439
441
  value=ast.NamedExpr(
440
- target=ast.Name(id="__TMPM", ctx=ast.Store()),
442
+ target=ast.Name(id=f"{tmp}M", ctx=ast.Store()),
441
443
  value=ast.Attribute(
442
444
  value=ast.Name(id=self.ovld_mangled, ctx=ast.Load()),
443
445
  attr="map",
@@ -464,13 +466,13 @@ class NameConverter(ast.NodeTransformer):
464
466
  new_node = ast.Call(
465
467
  func=method,
466
468
  args=[
467
- ast.Name(id=f"__TMP{i}", ctx=ast.Load())
469
+ ast.Name(id=f"{tmp}{i}", ctx=ast.Load())
468
470
  for i, arg in enumerate(node.args)
469
471
  ],
470
472
  keywords=[
471
473
  ast.keyword(
472
474
  arg=kw.arg,
473
- value=ast.Name(id=f"__TMP{kw.arg}", ctx=ast.Load()),
475
+ value=ast.Name(id=f"{tmp}{kw.arg}", ctx=ast.Load()),
474
476
  )
475
477
  for kw in node.keywords
476
478
  ],
ovld/typemap.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import inspect
2
2
  import math
3
3
  import typing
4
+ from dataclasses import dataclass
4
5
  from itertools import count
5
6
  from types import CodeType
6
7
 
@@ -69,6 +70,27 @@ class TypeMap(dict):
69
70
  raise KeyError(obj_t)
70
71
 
71
72
 
73
+ @dataclass
74
+ class Candidate:
75
+ handler: object
76
+ priority: float
77
+ specificity: tuple
78
+ tiebreak: int
79
+
80
+ def sort_key(self):
81
+ return self.priority, sum(self.specificity), self.tiebreak
82
+
83
+ def dominates(self, other):
84
+ if self.priority > other.priority:
85
+ return True
86
+ elif self.specificity != other.specificity:
87
+ return all(
88
+ s1 >= s2 for s1, s2 in zip(self.specificity, other.specificity)
89
+ )
90
+ else:
91
+ return self.tiebreak > other.tiebreak
92
+
93
+
72
94
  class MultiTypeMap(dict):
73
95
  """Represents a mapping from tuples of types to handlers.
74
96
 
@@ -91,6 +113,7 @@ class MultiTypeMap(dict):
91
113
  def __init__(self, name="_ovld", key_error=KeyError):
92
114
  self.maps = {}
93
115
  self.priorities = {}
116
+ self.tiebreaks = {}
94
117
  self.dependent = {}
95
118
  self.type_tuples = {}
96
119
  self.empty = MISSING
@@ -155,7 +178,12 @@ class MultiTypeMap(dict):
155
178
  specificities.setdefault(c, []).append(results[c])
156
179
 
157
180
  candidates = [
158
- (c, self.priorities.get(c, 0), tuple(specificities[c]))
181
+ Candidate(
182
+ handler=c,
183
+ priority=self.priorities.get(c, 0),
184
+ specificity=tuple(specificities[c]),
185
+ tiebreak=self.tiebreaks.get(c, 0),
186
+ )
159
187
  for c in candidates
160
188
  ]
161
189
 
@@ -164,33 +192,30 @@ class MultiTypeMap(dict):
164
192
  # other possibilities on all arguments, so the sum of all specificities
165
193
  # has to be greater.
166
194
  # Note: priority is always more important than specificity
167
- candidates.sort(key=lambda cspc: (cspc[1], sum(cspc[2])), reverse=True)
195
+
196
+ candidates.sort(key=Candidate.sort_key, reverse=True)
168
197
 
169
198
  self.all[obj_t_tup] = {
170
- getattr(c[0], "__code__", None) for c in candidates
199
+ getattr(c.handler, "__code__", None) for c in candidates
171
200
  }
172
201
 
173
202
  processed = set()
174
203
 
175
204
  def _pull(candidates):
176
- candidates = [
177
- (c, a, b) for (c, a, b) in candidates if c not in processed
178
- ]
205
+ candidates = [c for c in candidates if c.handler not in processed]
179
206
  if not candidates:
180
207
  return
181
208
  rval = [candidates[0]]
182
- c1, p1, spc1 = candidates[0]
183
- for c2, p2, spc2 in candidates[1:]:
184
- if p1 > p2 or (
185
- spc1 != spc2 and all(s1 >= s2 for s1, s2 in zip(spc1, spc2))
186
- ):
209
+ c1 = candidates[0]
210
+ for c2 in candidates[1:]:
211
+ if c1.dominates(c2):
187
212
  # Candidate 1 dominates candidate 2
188
213
  continue
189
214
  else:
190
- processed.add(c2)
215
+ processed.add(c2.handler)
191
216
  # Candidate 1 does not dominate candidate 2, so we add it
192
217
  # to the list.
193
- rval.append((c2, p2, spc2))
218
+ rval.append(c2)
194
219
  yield rval
195
220
  if len(rval) >= 1:
196
221
  yield from _pull(candidates[1:])
@@ -212,6 +237,7 @@ class MultiTypeMap(dict):
212
237
  self.empty = entry
213
238
 
214
239
  self.priorities[handler] = sig.priority
240
+ self.tiebreaks[handler] = sig.tiebreak
215
241
  self.type_tuples[handler] = obj_t_tup
216
242
  self.dependent[handler] = any(
217
243
  isinstance(t[1] if isinstance(t, tuple) else t, DependentType)
@@ -257,15 +283,16 @@ class MultiTypeMap(dict):
257
283
  finished = False
258
284
  rank = 1
259
285
  for grp in self.mro(tuple(argt)):
260
- grp.sort(key=lambda x: x[0].__name__)
286
+ grp.sort(key=lambda x: x.handler.__name__)
261
287
  match = [
262
288
  dependent_match(
263
- self.type_tuples[handler], [*args, *kwargs.items()]
289
+ self.type_tuples[c.handler], [*args, *kwargs.items()]
264
290
  )
265
- for handler, _, _ in grp
291
+ for c in grp
266
292
  ]
267
293
  ambiguous = len([m for m in match if m]) > 1
268
- for m, (handler, prio, spec) in zip(match, grp):
294
+ for m, c in zip(match, grp):
295
+ handler = c.handler
269
296
  color = "\033[0m"
270
297
  if finished:
271
298
  bullet = "--"
@@ -282,8 +309,8 @@ class MultiTypeMap(dict):
282
309
  message = f"{handler.__name__} will be called first."
283
310
  color = "\033[1;32m"
284
311
  rank += 1
285
- spec = ".".join(map(str, spec))
286
- lvl = f"[{prio}:{spec}]"
312
+ spec = ".".join(map(str, c.specificity))
313
+ lvl = f"[{c.priority}:{spec}]"
287
314
  width = 2 * len(args) + 6
288
315
  print(f"{color}{bullet} {lvl:{width}} {handler.__name__}")
289
316
  co = handler.__code__
@@ -320,8 +347,8 @@ class MultiTypeMap(dict):
320
347
 
321
348
  funcs = []
322
349
  for group in reversed(results):
323
- handlers = [fn for (fn, _, _) in group]
324
- dependent = any(self.dependent[fn] for (fn, _, _) in group)
350
+ handlers = [c.handler for c in group]
351
+ dependent = any(self.dependent[c.handler] for c in group)
325
352
  if dependent:
326
353
  nxt = self.wrap_dependent(
327
354
  obj_t_tup, handlers, group, funcs[-1] if funcs else None
ovld/version.py CHANGED
@@ -1 +1 @@
1
- version = "0.4.0"
1
+ version = "0.4.2"
@@ -1,7 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ovld
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Overloading Python functions
5
+ Project-URL: Homepage, https://ovld.readthedocs.io/en/latest/
6
+ Project-URL: Documentation, https://ovld.readthedocs.io/en/latest/
7
+ Project-URL: Repository, https://github.com/breuleux/ovld
5
8
  Author-email: Olivier Breuleux <breuleux@gmail.com>
6
9
  License-Expression: MIT
7
10
  License-File: LICENSE
@@ -0,0 +1,13 @@
1
+ ovld/__init__.py,sha256=Vp9wbIy_opFmJx-vCcToQcIP3cWFIvgbfYHITyiwPLs,1305
2
+ ovld/core.py,sha256=glexOiRU0ajoxFAQN6d69ToL6_cPvyv05Z7LEHqrJj8,25701
3
+ ovld/dependent.py,sha256=QITsWu2uCwqkHE0tunETy8Jqwc272uoG5YM0I5yy0m4,7303
4
+ ovld/mro.py,sha256=rS0pCLLWwLUI0NdthpoJSrdvWR4-XgRfx1pLAvJde14,5586
5
+ ovld/recode.py,sha256=zxhyotuc73qzuN4NEN_rKxaqoA0SOH7KFtE2l_CRBFs,17963
6
+ ovld/typemap.py,sha256=gw8XvB-N_15Dr0-am0v17x1seNjYK5AxvUT9L36mWic,13630
7
+ ovld/types.py,sha256=Zeb7xhHbL4T7qIRHI-I_cjG61UIWrfZv_EwjwqhB-rY,6381
8
+ ovld/utils.py,sha256=V6Y8oZ6ojq8JaODL1rMZbU5L9QG0YSqHNYmpIFiwy3M,1294
9
+ ovld/version.py,sha256=OHlqRD04Kvw_CFYmiIVx7UuOJRTQKtAx1Bl4rkUYHJE,18
10
+ ovld-0.4.2.dist-info/METADATA,sha256=fV3vxTKRAjCUkRVNmzTW14VajGSl7P0b9NnG06cjUJk,7713
11
+ ovld-0.4.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
12
+ ovld-0.4.2.dist-info/licenses/LICENSE,sha256=cSwNTIzd1cbI89xt3PeZZYJP2y3j8Zus4bXgo4svpX8,1066
13
+ ovld-0.4.2.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- ovld/__init__.py,sha256=nF8ymgr8dSL2WvDpOXVxqDuEGUywbkiCGr4ICumMD8U,1286
2
- ovld/core.py,sha256=siahCDuw78n02-2g7TGI1GerrGvPxT0zPouN6Z30Z-g,25497
3
- ovld/dependent.py,sha256=QITsWu2uCwqkHE0tunETy8Jqwc272uoG5YM0I5yy0m4,7303
4
- ovld/mro.py,sha256=dNuVW5DIOT8TJtY4vEpxT4q3R8fQxJ8UyDQSD9oFYYo,5116
5
- ovld/recode.py,sha256=Vc97Nv1j2GWuitLOzIuIhpscZ4gaOJQP3hLNp8SGTQ8,17890
6
- ovld/typemap.py,sha256=U_BmXtts1oYVa6gI3cgGHMX5kFcCJY_mt_cjWvDo3jQ,12979
7
- ovld/types.py,sha256=Zeb7xhHbL4T7qIRHI-I_cjG61UIWrfZv_EwjwqhB-rY,6381
8
- ovld/utils.py,sha256=V6Y8oZ6ojq8JaODL1rMZbU5L9QG0YSqHNYmpIFiwy3M,1294
9
- ovld/version.py,sha256=yhiWOz0HoJGRRI9-JQ2eh_0AbByy-6psK08-kpTSHJw,18
10
- ovld-0.4.0.dist-info/METADATA,sha256=4NkzmXbqa8ZNCwESYT4U8yILZyaIlw1rcghfCyaT50U,7526
11
- ovld-0.4.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
12
- ovld-0.4.0.dist-info/licenses/LICENSE,sha256=cSwNTIzd1cbI89xt3PeZZYJP2y3j8Zus4bXgo4svpX8,1066
13
- ovld-0.4.0.dist-info/RECORD,,
File without changes