ovld 0.5.7__tar.gz → 0.5.9__tar.gz

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.
Files changed (69) hide show
  1. {ovld-0.5.7 → ovld-0.5.9}/.github/workflows/python-package.yml +2 -0
  2. {ovld-0.5.7 → ovld-0.5.9}/PKG-INFO +1 -1
  3. {ovld-0.5.7 → ovld-0.5.9}/pyproject.toml +1 -1
  4. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/medley.py +2 -1
  5. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/mro.py +50 -5
  6. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/utils.py +1 -0
  7. ovld-0.5.9/src/ovld/version.py +1 -0
  8. {ovld-0.5.7 → ovld-0.5.9}/tests/test_medley.py +11 -0
  9. {ovld-0.5.7 → ovld-0.5.9}/tests/test_mro.py +64 -1
  10. {ovld-0.5.7 → ovld-0.5.9}/tests/test_ovld.py +77 -1
  11. {ovld-0.5.7 → ovld-0.5.9}/uv.lock +2 -2
  12. ovld-0.5.7/src/ovld/version.py +0 -1
  13. {ovld-0.5.7 → ovld-0.5.9}/.github/workflows/publish.yml +0 -0
  14. {ovld-0.5.7 → ovld-0.5.9}/.gitignore +0 -0
  15. {ovld-0.5.7 → ovld-0.5.9}/.python-version +0 -0
  16. {ovld-0.5.7 → ovld-0.5.9}/.readthedocs.yaml +0 -0
  17. {ovld-0.5.7 → ovld-0.5.9}/LICENSE +0 -0
  18. {ovld-0.5.7 → ovld-0.5.9}/README.md +0 -0
  19. {ovld-0.5.7 → ovld-0.5.9}/benchmarks/__init__.py +0 -0
  20. {ovld-0.5.7 → ovld-0.5.9}/benchmarks/common.py +0 -0
  21. {ovld-0.5.7 → ovld-0.5.9}/benchmarks/conftest.py +0 -0
  22. {ovld-0.5.7 → ovld-0.5.9}/benchmarks/test_add.py +0 -0
  23. {ovld-0.5.7 → ovld-0.5.9}/benchmarks/test_ast.py +0 -0
  24. {ovld-0.5.7 → ovld-0.5.9}/benchmarks/test_calc.py +0 -0
  25. {ovld-0.5.7 → ovld-0.5.9}/benchmarks/test_fib.py +0 -0
  26. {ovld-0.5.7 → ovld-0.5.9}/benchmarks/test_multer.py +0 -0
  27. {ovld-0.5.7 → ovld-0.5.9}/benchmarks/test_regexp.py +0 -0
  28. {ovld-0.5.7 → ovld-0.5.9}/benchmarks/test_trivial.py +0 -0
  29. {ovld-0.5.7 → ovld-0.5.9}/benchmarks/test_tweaknum.py +0 -0
  30. {ovld-0.5.7 → ovld-0.5.9}/docs/codegen.md +0 -0
  31. {ovld-0.5.7 → ovld-0.5.9}/docs/compare.md +0 -0
  32. {ovld-0.5.7 → ovld-0.5.9}/docs/dependent.md +0 -0
  33. {ovld-0.5.7 → ovld-0.5.9}/docs/features.md +0 -0
  34. {ovld-0.5.7 → ovld-0.5.9}/docs/index.md +0 -0
  35. {ovld-0.5.7 → ovld-0.5.9}/docs/medley.md +0 -0
  36. {ovld-0.5.7 → ovld-0.5.9}/docs/types.md +0 -0
  37. {ovld-0.5.7 → ovld-0.5.9}/docs/usage.md +0 -0
  38. {ovld-0.5.7 → ovld-0.5.9}/mkdocs.yml +0 -0
  39. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/__init__.py +0 -0
  40. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/abc.py +0 -0
  41. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/codegen.py +0 -0
  42. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/core.py +0 -0
  43. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/dependent.py +0 -0
  44. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/py.typed +0 -0
  45. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/recode.py +0 -0
  46. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/signatures.py +0 -0
  47. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/typemap.py +0 -0
  48. {ovld-0.5.7 → ovld-0.5.9}/src/ovld/types.py +0 -0
  49. {ovld-0.5.7 → ovld-0.5.9}/tests/__init__.py +0 -0
  50. {ovld-0.5.7 → ovld-0.5.9}/tests/modules/gingerbread.py +0 -0
  51. {ovld-0.5.7 → ovld-0.5.9}/tests/test_abc.py +0 -0
  52. {ovld-0.5.7 → ovld-0.5.9}/tests/test_codegen/test_dataclass_gen.txt +0 -0
  53. {ovld-0.5.7 → ovld-0.5.9}/tests/test_codegen/test_method.txt +0 -0
  54. {ovld-0.5.7 → ovld-0.5.9}/tests/test_codegen/test_method_metaclass.txt +0 -0
  55. {ovld-0.5.7 → ovld-0.5.9}/tests/test_codegen/test_method_per_instance.txt +0 -0
  56. {ovld-0.5.7 → ovld-0.5.9}/tests/test_codegen/test_simple.txt +0 -0
  57. {ovld-0.5.7 → ovld-0.5.9}/tests/test_codegen/test_variant_generation.txt +0 -0
  58. {ovld-0.5.7 → ovld-0.5.9}/tests/test_codegen.py +0 -0
  59. {ovld-0.5.7 → ovld-0.5.9}/tests/test_dependent.py +0 -0
  60. {ovld-0.5.7 → ovld-0.5.9}/tests/test_examples.py +0 -0
  61. {ovld-0.5.7 → ovld-0.5.9}/tests/test_global.py +0 -0
  62. {ovld-0.5.7 → ovld-0.5.9}/tests/test_ovld/test_display.txt +0 -0
  63. {ovld-0.5.7 → ovld-0.5.9}/tests/test_ovld/test_display_more.txt +0 -0
  64. {ovld-0.5.7 → ovld-0.5.9}/tests/test_ovld/test_doc.txt +0 -0
  65. {ovld-0.5.7 → ovld-0.5.9}/tests/test_ovld/test_doc2.txt +0 -0
  66. {ovld-0.5.7 → ovld-0.5.9}/tests/test_ovld/test_method_doc.txt +0 -0
  67. {ovld-0.5.7 → ovld-0.5.9}/tests/test_typemap.py +0 -0
  68. {ovld-0.5.7 → ovld-0.5.9}/tests/test_types.py +0 -0
  69. {ovld-0.5.7 → ovld-0.5.9}/tests/test_utils.py +0 -0
@@ -45,6 +45,8 @@ jobs:
45
45
  coverage: false
46
46
  - python: '3.13'
47
47
  coverage: true
48
+ - python: '3.14'
49
+ coverage: false
48
50
  steps:
49
51
  - name: Check out the code
50
52
  uses: actions/checkout@v3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ovld
3
- Version: 0.5.7
3
+ Version: 0.5.9
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/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ovld"
3
- version = "0.5.7"
3
+ version = "0.5.9"
4
4
  description = "Overloading Python functions"
5
5
  authors = [
6
6
  { name = "Olivier Breuleux", email = "breuleux@gmail.com" }
@@ -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__"]
@@ -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
@@ -106,9 +134,18 @@ def typeorder(t1, t2):
106
134
  return Order.NONE
107
135
 
108
136
 
137
+ def _find_ann(main, others):
138
+ if main in others:
139
+ return True
140
+ elif isinstance(main, type):
141
+ return any(isinstance(x, main) for x in others)
142
+ else:
143
+ return False
144
+
145
+
109
146
  def subclasscheck(t1, t2):
110
147
  """Check whether t1 is a "subclass" of t2."""
111
- if t1 == t2:
148
+ if t1 == t2 or t2 is Any:
112
149
  return True
113
150
 
114
151
  if (
@@ -134,12 +171,20 @@ def subclasscheck(t1, t2):
134
171
  o1 = get_origin(t1)
135
172
  o2 = get_origin(t2)
136
173
 
174
+ if o1 is Annotated and o2 is Annotated:
175
+ t1, *a1 = get_args(t1)
176
+ t2, *a2 = get_args(t2)
177
+ return subclasscheck(t1, t2) and any(_find_ann(main, a1) for main in a2)
178
+
179
+ if o1 is Annotated:
180
+ return t2 is Annotated
181
+
137
182
  if not isinstance(o1, type):
138
183
  o1 = None
139
184
  if not isinstance(o2, type):
140
185
  o2 = None
141
186
 
142
- if o1 or o2:
187
+ if (o1 or o2) and o2 not in UnionTypes:
143
188
  o1 = o1 or t1
144
189
  o2 = o2 or t2
145
190
  if isinstance(o1, type) and isinstance(o2, type) and issubclass(o1, o2):
@@ -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
 
@@ -0,0 +1 @@
1
+ version = "0.5.9"
@@ -530,3 +530,14 @@ def test_absent():
530
530
  lx.two
531
531
  with pytest.raises(AttributeError, match="no attribute 'three'"):
532
532
  lx.three
533
+
534
+
535
+ def test_default_factory_inheritance():
536
+ class Lost(Medley):
537
+ xs: list = field(default_factory=list)
538
+
539
+ class Lust(Lost):
540
+ pass
541
+
542
+ lu = Lust()
543
+ assert lu.xs == []
@@ -1,5 +1,8 @@
1
+ import sys
1
2
  from dataclasses import dataclass
2
- from typing import Iterable, Mapping
3
+ from typing import Annotated, Any, Iterable, Mapping
4
+
5
+ import pytest
3
6
 
4
7
  from ovld.dependent import Dependent, Regexp
5
8
  from ovld.mro import Order, subclasscheck, typeorder
@@ -132,6 +135,66 @@ def test_subclasscheck_type():
132
135
  assert subclasscheck(type[int], type[object])
133
136
 
134
137
 
138
+ def test_subclasscheck_anntype():
139
+ assert subclasscheck(type[Annotated[int, "hello"]], type[Annotated])
140
+ assert subclasscheck(type[Annotated[int, "hello"]], type[Annotated[object, "hello"]])
141
+ assert subclasscheck(
142
+ type[Annotated[int, "hello"]], type[Annotated[object, "hello", "world"]]
143
+ )
144
+ assert subclasscheck(
145
+ type[Annotated[int, "hello", "world"]], type[Annotated[object, "hello"]]
146
+ )
147
+ assert not subclasscheck(type[Annotated[object, "hello"]], type[Annotated[int, "hello"]])
148
+ assert not subclasscheck(type[Annotated[int, "hello"]], type[Annotated[int, "world"]])
149
+ assert not subclasscheck(type[Annotated[int, "hello"]], type[int])
150
+ assert not subclasscheck(type[int], type[Annotated[int, "hello"]])
151
+
152
+
153
+ def test_subclasscheck_anntype_type():
154
+ assert subclasscheck(type[Annotated[int, "hello"]], type[Annotated[int, str]])
155
+ assert subclasscheck(type[Annotated[int, str]], type[Annotated[object, str]])
156
+ assert not subclasscheck(type[Annotated[int, "hello"]], type[Annotated[int, int]])
157
+
158
+
159
+ @pytest.mark.skipif(
160
+ sys.version_info < (3, 12), reason="Python 3.11 is more strict on Annotated"
161
+ )
162
+ def test_subclasscheck_anntype_any():
163
+ assert subclasscheck(type[Annotated[int, "hello"]], type[Annotated[Any, "hello"]])
164
+ assert subclasscheck(type[Annotated[123, "hello"]], type[Annotated[Any, "hello"]])
165
+
166
+
167
+ def test_typeorder_any():
168
+ assert typeorder(int, Any) is Order.LESS
169
+ assert typeorder(Any, int) is Order.MORE
170
+ assert typeorder(Any, Any) is Order.SAME
171
+
172
+
173
+ @dataclass(frozen=True)
174
+ class Anno:
175
+ name: str
176
+ annotation_priority: int = 0
177
+
178
+
179
+ def test_typeorder_anntype():
180
+ assert typeorder(type[Annotated], type[Annotated[int, "hello"]]) is Order.MORE
181
+ assert typeorder(type[Annotated[int, "hello"]], type[Annotated]) is Order.LESS
182
+ assert (
183
+ typeorder(type[Annotated[int, "hello"]], type[Annotated[int, "world"]]) is Order.SAME
184
+ )
185
+ assert (
186
+ typeorder(type[Annotated[object, "hello"]], type[Annotated[int, "world"]])
187
+ is Order.MORE
188
+ )
189
+ assert typeorder(type[Annotated[int, "hello"]], type[int]) is Order.SAME
190
+ assert typeorder(type[int], type[Annotated[int, "hello"]]) is Order.SAME
191
+
192
+ zap = Anno("zap", 2)
193
+ zop = Anno("zap", 5)
194
+ assert typeorder(type[Annotated[int, zap]], type[Annotated[int, zop]]) is Order.MORE
195
+ assert typeorder(type[Annotated[int, zop]], type[Annotated[int, zap]]) is Order.LESS
196
+
197
+
135
198
  def test_subclasscheck_type_union():
136
199
  assert subclasscheck(type[Union[int, str]], type[Union])
137
200
  assert subclasscheck(type[Union], object)
@@ -3,6 +3,7 @@ import re
3
3
  import sys
4
4
  import typing
5
5
  from dataclasses import dataclass
6
+ from typing import Annotated, Any
6
7
 
7
8
  import pytest
8
9
 
@@ -21,7 +22,7 @@ from ovld import (
21
22
  )
22
23
  from ovld.dependent import Dependent, Equals, Regexp, StartsWith
23
24
  from ovld.types import UnionTypes
24
- from ovld.utils import MISSING, UsageError
25
+ from ovld.utils import MISSING, ResolutionError, UsageError
25
26
 
26
27
  from .test_typemap import Animal, Bird, Mammal
27
28
 
@@ -1277,6 +1278,81 @@ def test_generic_type_argument():
1277
1278
  assert f(dict[str, int]) == "dict"
1278
1279
 
1279
1280
 
1281
+ def test_annotated_type_argument():
1282
+ @ovld
1283
+ def f(t: type[Annotated[str, "hello"]]):
1284
+ return "hello"
1285
+
1286
+ @ovld
1287
+ def f(t: type[Annotated[str, "world"]]):
1288
+ return "world"
1289
+
1290
+ @ovld
1291
+ def f(t: object):
1292
+ return False
1293
+
1294
+ assert f(Annotated[str, "hello"]) == "hello"
1295
+ assert f(Annotated[str, "world"]) == "world"
1296
+ assert f(Annotated[str, "hello", "X"]) == "hello"
1297
+ assert f(Annotated[str, 1, 2, 3, "world"]) == "world"
1298
+
1299
+
1300
+ @pytest.mark.skipif(
1301
+ sys.version_info < (3, 12), reason="Python 3.11 is more strict on Annotated"
1302
+ )
1303
+ def test_annotated_type_argument_any():
1304
+ @ovld
1305
+ def f(t: type[Annotated[Any, "hello"]]):
1306
+ return "hello"
1307
+
1308
+ @ovld
1309
+ def f(t: object):
1310
+ return False
1311
+
1312
+ assert f(Annotated[str, "hello"]) == "hello"
1313
+ assert f(Annotated[123, "hello", "X"]) == "hello"
1314
+ assert f(Annotated[str, 1, 2, 3, "hello"]) == "hello"
1315
+
1316
+
1317
+ @dataclass(frozen=True)
1318
+ class Anno:
1319
+ name: str
1320
+ annotation_priority: int = 0
1321
+
1322
+
1323
+ def test_annotated_multiple_ann():
1324
+ hello = Anno("hello", 1)
1325
+ world = Anno("hello", 2)
1326
+ foo = Anno("foo", 1)
1327
+ bar = Anno("bar", 1)
1328
+
1329
+ @ovld
1330
+ def f(t: type[Annotated[str, hello]]):
1331
+ return "hello"
1332
+
1333
+ @ovld
1334
+ def f(t: type[Annotated[str, world]]):
1335
+ return "world"
1336
+
1337
+ @ovld
1338
+ def f(t: type[Annotated[str, foo]]):
1339
+ return "foo"
1340
+
1341
+ @ovld
1342
+ def f(t: type[Annotated[object, bar]]):
1343
+ return "bar"
1344
+
1345
+ @ovld
1346
+ def f(t: object):
1347
+ return False
1348
+
1349
+ assert f(Annotated[str, hello, world]) == "world"
1350
+ assert f(Annotated[str, hello, bar]) == "hello"
1351
+ with pytest.raises(ResolutionError):
1352
+ assert f(Annotated[str, hello, foo]) == "world"
1353
+ assert f("ah") is False
1354
+
1355
+
1280
1356
  def test_any():
1281
1357
  @ovld
1282
1358
  def f(x: int):
@@ -171,7 +171,7 @@ name = "importlib-metadata"
171
171
  version = "8.7.0"
172
172
  source = { registry = "https://pypi.org/simple" }
173
173
  dependencies = [
174
- { name = "zipp" },
174
+ { name = "zipp", marker = "python_full_version < '3.10'" },
175
175
  ]
176
176
  sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" }
177
177
  wheels = [
@@ -370,7 +370,7 @@ wheels = [
370
370
 
371
371
  [[package]]
372
372
  name = "ovld"
373
- version = "0.5.6"
373
+ version = "0.5.8"
374
374
  source = { editable = "." }
375
375
 
376
376
  [package.dev-dependencies]
@@ -1 +0,0 @@
1
- version = "0.5.7"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes