Skip to content

Commit

Permalink
fix: make isinstancex robust
Browse files Browse the repository at this point in the history
  • Loading branch information
PrettyWood committed Jan 20, 2021
1 parent 5b69350 commit 3a42c5d
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 12 deletions.
2 changes: 2 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def test_isinstancex_set(obj, tp, expected):
((3,), Tuple[int, int], False),
((3,), Tuple[str], False),
((3,), Tuple[int, str], False),
([3], Tuple[int], False),
],
)
def test_isinstancex_tuple(obj, tp, expected):
Expand Down Expand Up @@ -269,6 +270,7 @@ def test_isinstancex_literal(obj, tp, expected):
@pytest.mark.parametrize(
"obj,tp,expected",
[
(1, Sequence[str], False),
("pika", Sequence[str], True),
(["pika", "chu"], Sequence[str], True),
(("pika", "chu"), Sequence[str], True),
Expand Down
28 changes: 16 additions & 12 deletions typingx/main.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import collections.abc
from typing import Any, Dict, List, Mapping, Sequence, Set, Union
from typing import Any, Dict, List, Set, Union

from .types import Listx, Tuplex
from .typing_compat import TypedDict, get_args, get_origin, get_type_hints, is_literal, is_typeddict
from .utils import TypeLike, lenient_isinstance, lenient_issubclass
from .utils import OneOrManyTypes, TypeLike, lenient_isinstance, lenient_issubclass

__all__ = ("isinstancex",)

TYPED_DICT_EXTRA_KEY = "__extra__"


def isinstancex(obj: Any, tp: Any) -> bool:
def isinstancex(obj: Any, tp: OneOrManyTypes) -> bool:
try:
return _isinstancex(obj, tp)
except (AttributeError, TypeError):
return False


def _isinstancex(obj: Any, tp: Any) -> bool:
"""Extend `isinstance` with `typing` types"""
if tp is Any:
return True
Expand Down Expand Up @@ -48,7 +55,7 @@ def isinstancex(obj: Any, tp: Any) -> bool:

# e.g. Tuple[int, ...] or Tuplex[int, str, ...]
elif origin is tuple:
return _is_valid_sequence(obj, tp, is_list=False)
return isinstance(obj, tuple) and _is_valid_sequence(obj, tp, is_list=False)

# e.g. Type[int]
elif origin is type:
Expand Down Expand Up @@ -85,7 +92,7 @@ def isinstancex(obj: Any, tp: Any) -> bool:
return lenient_isinstance(obj, tp)


def _is_valid_sequence(obj: Sequence[Any], tp: TypeLike, *, is_list: bool) -> bool:
def _is_valid_sequence(obj: Any, tp: TypeLike, *, is_list: bool) -> bool:
"""
Check that a sequence respects a type with args like [str], [str, int], [str, ...]
but also args like [str, int, ...] or even [str, int, ..., bool, ..., float]
Expand Down Expand Up @@ -122,14 +129,11 @@ def _is_valid_sequence(obj: Sequence[Any], tp: TypeLike, *, is_list: bool) -> bo
return expected_types[current_index:] in ((), (...,))


def _is_valid_mapping(obj: Mapping[Any, Any], tp: TypeLike) -> bool:
def _is_valid_mapping(obj: Any, tp: TypeLike) -> bool:
keys_type, values_type = get_args(tp)
try:
return all(isinstancex(key, keys_type) for key in obj.keys()) and all(
isinstancex(value, values_type) for value in obj.values()
)
except AttributeError:
return False
return all(isinstancex(key, keys_type) for key in obj.keys()) and all(
isinstancex(value, values_type) for value in obj.values()
)


def _is_valid_typeddict(obj: Dict[str, Any], tp: TypedDict) -> bool:
Expand Down

0 comments on commit 3a42c5d

Please sign in to comment.