Skip to content

Commit ad8c148

Browse files
authored
Merge pull request #509 from yukinarit/empty-tuple
Handle empty tuple more properly
2 parents 31289c8 + 9a7a484 commit ad8c148

File tree

3 files changed

+40
-20
lines changed

3 files changed

+40
-20
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,5 +156,5 @@ select = [
156156
]
157157
line-length = 100
158158

159-
[tool.ruff.mccabe]
159+
[tool.ruff.lint.mccabe]
160160
max-complexity = 30

serde/core.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import re
1212
import casefy
1313
from dataclasses import dataclass
14+
from beartype.door import is_bearable
1415
from collections.abc import Mapping, Sequence, Callable
1516
from typing import (
1617
overload,
@@ -343,8 +344,8 @@ def add_func(serde_scope: Scope, func_name: str, func_code: str, globals: dict[s
343344

344345
def is_instance(obj: Any, typ: Any) -> bool:
345346
"""
346-
Type check function that works like `isinstance` but it accepts
347-
Subscripted Generics e.g. `list[int]`.
347+
pyserde's own `isinstance` helper. It accepts subscripted generics e.g. `list[int]` and
348+
deeply check object against declared type.
348349
"""
349350
if dataclasses.is_dataclass(typ):
350351
return isinstance(obj, typ)
@@ -375,7 +376,7 @@ def is_instance(obj: Any, typ: Any) -> bool:
375376
elif typ is Ellipsis:
376377
return True
377378
else:
378-
return isinstance(obj, typ)
379+
return is_bearable(obj, typ)
379380

380381

381382
def is_opt_instance(obj: Any, typ: type[Any]) -> bool:
@@ -413,18 +414,37 @@ def is_set_instance(obj: Any, typ: type[Any]) -> bool:
413414

414415

415416
def is_tuple_instance(obj: Any, typ: type[Any]) -> bool:
417+
args = type_args(typ)
418+
416419
if not isinstance(obj, tuple):
417420
return False
418-
if is_variable_tuple(typ):
421+
422+
# empty tuple
423+
if len(args) == 0 and len(obj) == 0:
424+
return True
425+
426+
# In the form of tuple[T, ...]
427+
elif is_variable_tuple(typ):
428+
# Get the first type arg. Since tuple[T, ...] is homogeneous tuple,
429+
# all the elements should be of this type.
419430
arg = type_args(typ)[0]
420431
for v in obj:
421432
if not is_instance(v, arg):
422433
return False
423-
if len(obj) == 0 or is_bare_tuple(typ):
424434
return True
425-
for i, arg in enumerate(type_args(typ)):
426-
if not is_instance(obj[i], arg):
427-
return False
435+
436+
# bare tuple "tuple" is equivalent to tuple[Any, ...]
437+
if is_bare_tuple(typ) and isinstance(obj, tuple):
438+
return True
439+
440+
# All the other tuples e.g. tuple[int, str]
441+
if len(obj) == len(args):
442+
for element, arg in zip(obj, args):
443+
if not is_instance(element, arg):
444+
return False
445+
else:
446+
return False
447+
428448
return True
429449

430450

tests/test_compat.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def test_union_args() -> None:
158158

159159

160160
def test_is_instance() -> None:
161-
# Primitive
161+
# primitive
162162
assert is_instance(10, int)
163163
assert is_instance("str", str)
164164
assert is_instance(1.0, float)
@@ -167,13 +167,13 @@ def test_is_instance() -> None:
167167
assert not is_instance("10", int)
168168
assert is_instance(True, int) # see why this is true https://stackoverflow.com/a/37888668
169169

170-
# Dataclass
170+
# dataclass
171171
p = Pri(i=10, s="foo", f=100.0, b=True)
172172
assert is_instance(p, Pri)
173173
p.i = 10.0 # type: ignore
174174
assert is_instance(p, Pri) # there is no way to check mulated properties.
175175

176-
# Dataclass (Nested)
176+
# dataclass (Nested)
177177
@dataclass
178178
class Foo:
179179
p: Pri
@@ -190,7 +190,7 @@ class Foo:
190190
assert is_instance([10], list[int])
191191
assert not is_instance([10.0], list[int])
192192

193-
# list of dataclasses
193+
# list of dataclass
194194
assert is_instance([Int(n) for n in range(1, 10)], list[Int])
195195
assert not is_instance([Str("foo")], list[Int])
196196

@@ -200,18 +200,18 @@ class Foo:
200200
assert is_instance({10}, set[int])
201201
assert not is_instance({10.0}, set[int])
202202

203-
# set of dataclasses
203+
# set of dataclass
204204
assert is_instance({Int(n) for n in range(1, 10)}, set[Int])
205205
assert not is_instance({Str("foo")}, set[Int])
206206

207207
# tuple
208-
assert is_instance((), tuple[int, str, float, bool])
209-
assert is_instance((10, "a"), tuple)
208+
assert not is_instance((), tuple[int, str, float, bool])
210209
assert is_instance((10, "a"), tuple)
211210
assert is_instance((10, "foo", 100.0, True), tuple[int, str, float, bool])
212211
assert not is_instance((10, "foo", 100.0, "last-type-is-wrong"), tuple[int, str, float, bool])
213212
assert is_instance((10, 20), tuple[int, ...])
214213
assert is_instance((10, 20, 30), tuple[int, ...])
214+
assert is_instance((), tuple[()])
215215
assert is_instance((), tuple[int, ...])
216216
assert not is_instance((10, "a"), tuple[int, ...])
217217

@@ -230,21 +230,21 @@ class Foo:
230230
assert is_instance({"foo": 10, "bar": 20}, dict[str, int])
231231
assert not is_instance({"foo": 10.0, "bar": 20}, dict[str, int])
232232

233-
# dict of dataclasses
233+
# dict of dataclass
234234
assert is_instance({Str("foo"): Int(10), Str("bar"): Int(20)}, dict[Str, Int])
235235
assert not is_instance({Str("foo"): Str("wrong-type"), Str("bar"): Int(10)}, dict[Str, Int])
236236

237-
# Optional
237+
# optional
238238
assert is_instance(None, type(None))
239239
assert is_instance(10, Optional[int])
240240
assert is_instance(None, Optional[int])
241241
assert not is_instance("wrong-type", Optional[int])
242242

243-
# Optional of dataclass
243+
# optional of dataclass
244244
assert is_instance(Int(10), Optional[Int])
245245
assert not is_instance("wrong-type", Optional[Int])
246246

247-
# Nested containers
247+
# nested containers
248248
assert is_instance([({"a": "b"}, 10, [True])], list[tuple[dict[str, str], int, list[bool]]])
249249
assert not is_instance(
250250
[({"a": "b"}, 10, ["wrong-type"])], list[tuple[dict[str, str], int, list[bool]]]

0 commit comments

Comments
 (0)