Skip to main content

Extend typing package functionalities

Project description

typingx

Tests codecov pypi versions license

How many times have you wanted to check the shape of your data either in application code or while testing? With this library, you can leverage typing types at runtime to do that!

This is even more powerful when used with generic standard collections (e.g. list[str]) introduced in python 3.9 and new union operator | introduced in python 3.10. If you want to use them with older version, a backport future-typing exists!

Example

# Check if `x` is a string or an integer
isinstancex(x, str | int)

# Check if `my_list` has only numbers
isinstancex(my_list, list[int | float])

# Check if `my_set` has only integers greater than 2 with at least 3 distinct ones
Gt2Int = Annotated[int, Constraints(gt=2)]
isinstancex(my_set, Annotated[set[Gt2Int], Constraints(min_length=3)])  # or `typing.Set`

# Check deeper the shape of `my_dict`
isinstancex(my_dict, {'a': int, 'b': bool, ...: str})  # shortcut for `typing.TypedDict('TD', {'a': int, 'b': bool, __extra__: str})`

# Check if all the keys are 1 lower case letter and values are integers with 1 digit
OneLowerStr = Annotated[str, Constraints(regex='^[a-z]$')]
OneDigitUInt = Annotated[int, Constraints(ge=0, lt=10)]
isinstancex(my_dict, dict[OneLowerStr, OneDigitUInt])

@func_check
def my_func(a: int, b: Annotated[int, Constraints(ge=5)]) -> Annotated[int, Constraints(le=10)]:
    return a + b

try:
    my_func(1, 4)
except TypeError as e:
    assert str(e) == "Input b (value: 4) is not a valid Annotated[int, Constraints(ge=5)]"

try:
    my_func(5, 6)
except TypeError as e:
    assert str(e) == "Output (value: 11) is not a valid Annotated[int, Constraints(le=10)]"

Since typing changed a lot since python 3.6, this library also makes sure the whole behaviour is consistent with 3.10 for all python versions.

It hence provides:

  • isinstancex: like isinstance and issubclass but with typing types and extra types provided by this library

    :warning: using a tuple as second parameter will validate against Tuplex. If you want to check against multiple types (int, str), wrap it into Union[(int, str)]!

  • issubclassx: same as isinstancex but for issubclass

  • func_check: a decorator to check inputs and output of a function based on annotation

  • get_args and get_origin that have the exact same behaviour as the typing module with python 3.10, no matter which python version is used!

  • is_literal, is_newtype, is_typeddict helpers

  • most typing types but with homogeneous behaviour (e.g. with 3.8, this libray will choose typing_extensions.TypedDict instead of typing.TypedDict since the latter doesn't store information to distinguish optional and required keys)

  • extanded types:

    • TypedDict with ... field to allow type checking on optional fields
  • extra types:

    • Listx and Tuplex: more sophisticated versions of List and Tuple to add ... anywhere in the parameters

Installation

    pip install typingx

isinstancex

from collections import ChainMap, Counter

from typingx import *

T, U = TypeVar('T'), TypeVar('U')

# Annotated
GT2 = Annotated[int, Constraints(gt=2)]
assert isinstancex(1, int) is True
assert isinstancex(1, GT2) is False
assert isinstancex(3, GT2) is True
assert isinstancex([3, 3], list[GT2]) is True

Between2And5 = Annotated[Union[float, int], Constraints(ge=2, le=5)]
assert isinstancex(3, Between2And5) is True
assert isinstancex(3.5, Between2And5) is True
assert isinstancex('3.5', Between2And5) is False

assert isinstancex(3, Annotated[int, Constraints(multiple_of=1.5)]) is True
assert isinstancex(3., Annotated[int, Constraints(multiple_of=1.5)]) is False
assert isinstancex(3., Annotated[Union[int, float], Constraints(multiple_of=1.5)]) is True

Mult1_5AndLe2 = Annotated[Union[int, float], Constraints(multiple_of=1.5, le=10)]
assert isinstancex(15, Mult1_5AndLe2) is False
assert isinstancex([3.0, 4.5, 9], list[Mult1_5AndLe2]) is True
assert isinstancex([3., 4.3], list[Mult1_5AndLe2]) is False

OneLowerStr = Annotated[str, Constraints(regex='^[a-z]$')]
OneDigitUInt = Annotated[int, Constraints(ge=0, lt=10)]
assert isinstancex({'a': 1, 'b': 2}, dict[OneLowerStr, OneDigitUInt]) is True
assert isinstancex({'a': 1, 'bc': 2}, dict[OneLowerStr, OneDigitUInt]) is False

# Callable
def f(x: int, y: float) -> str:
    return f'{x}{y}'

assert isinstancex(f, Callable) is True
assert isinstancex(f, Callable[..., Any]) is True
assert isinstancex(f, Callable[..., str]) is True
assert isinstancex(f, Callable[[int, float], str]) is True
assert isinstancex(f, Callable[[T, float], U][int, str]) is True

# Dict
assert isinstancex({"a": 1, "b": 2}, Dict[str, int]) is True
assert isinstancex({"a": 1, "b": 2}, Dict[str, str]) is False
assert isinstancex({"a": 1, "b": 2}, Dict[int, str]) is False
assert isinstancex({"a": 1, "b": 2}, Dict[str, Any]) is True

# List
assert isinstancex([1, 2, 3], List[int]) is True
assert isinstancex([1, 2, "q"], List[int]) is False
assert isinstancex([1, 2, "q"], List[Union[str, int]]) is True

# Listx
assert isinstancex([1, 2, 3, 4], Listx[int]) is False
assert isinstancex([1, 2, 3, 4], Listx[int, ...]) is True
assert isinstancex([1, 2, "q"], Listx[int, ..., str]) is True
assert isinstancex([1, 2, "q", "w", "e"], Listx[int, ..., str]) is False
assert isinstancex([1, 2, "q", "w", "e"], Listx[int, ..., str, ...]) is True
assert isinstancex([1, 2, "q", "w", b"xyz", "e"], Listx[int, ..., str, ...]) is False
assert isinstancex([1, 2, "q", "w", b"xyz", "e"], Listx[int, ..., Union[str, bytes], ...]) is True

# Listx (shortcut)
assert isinstancex([1, 2, 3, 4, "q"], [int, ..., str]) is True

# Literal
assert isinstancex("a", Literal["a"]) is True
assert isinstancex(Literal["a"], Literal["a"]) is True
assert isinstancex("b", Literal["a"]) is False
assert isinstancex("b", Literal["a", Literal[Literal["b"]]]) is True
assert isinstancex(Literal["a", "b"], Literal["b", "a", "c"]) is True

# Mapping
assert isinstancex(Counter({"red": 4, "blue": 2}), Mapping[str, int]) is True
assert isinstancex(ChainMap({"art": "van gogh"}, {"music": "bach"}), Mapping[str, str]) is True

# NewType
UserId = NewType("UserId", int)
ProUserId = NewType("ProUserId", UserId)

assert isinstancex(1, UserId) is True
assert isinstancex(1, ProUserId) is True
assert isinstancex(UserId(1), UserId) is True
assert isinstancex("3", UserId) is False

# None
assert isinstancex([None, None], List[None]) is True
assert isinstancex([None, None], List[NoneType]) is True
assert isinstancex([None, None], List[type(None)]) is True
assert isinstancex([None, None], List[Literal[None]]) is True

# Sequence
assert isinstancex("abc", Sequence[Any]) is True
assert isinstancex("abc", Sequence[int]) is False
assert isinstancex((1, 3, 5), Sequence[int]) is True

# Set
assert isinstancex({"a", "b"}, Set[str]) is True
assert isinstancex({"a", "b"}, Set[int]) is False

# Tuple
assert isinstancex((), Tuple[()]) is True
assert isinstancex((1,), Tuple[()]) is True
assert isinstancex((1, 2), Tuple[int, ...]) is True
assert isinstancex((1, 2), Tuple[int, int]) is True
assert isinstancex((1, 2), Tuple[int, int, int]) is False

# Tuplex
assert isinstancex((3, "a", "b"), Tuplex[int, str, ...]) is True
assert isinstancex((3, "a", "b", "c"), Tuplex[int, str, ...]) is True
assert isinstancex((3, "a", "b", "c"), Tuplex[int, str, ..., bool]) is False
assert isinstancex((3, "a", "b", "c", True), Tuplex[int, str, ..., bool]) is True
assert isinstancex((3, "a", "b", "c", 3), Tuplex[int, str, ..., bool]) is False
assert isinstancex((3, "a", "b", "c", True, False), Tuplex[int, str, ..., bool, ...]) is True

# Tuplex (shortcut)
assert isinstancex((True, "q", "q", "q", "q"), (bool, Literal["q"], ...)) is True

# Type
class User: ...
class BaseUser(User): ...

assert isinstancex(BaseUser, Type[BaseUser]) is True
assert isinstancex(BaseUser, Type[User]) is True
assert isinstancex(User, Type[User]) is True
assert isinstancex(User, Type[BaseUser]) is False

# TypedDict
FullMovie = TypedDict("FullMovie", {"name": str, "year": int})

class PartialMovie(TypedDict, total=False):
    name: str
    year: int

class ExtraMovie(TypedDict):
    name: str
    year: int
    __extra__: str

assert isinstancex({"name": "The Matrix", "year": 1999}, FullMovie) is True
assert isinstancex({"name": "The Matrix", "year": "1999"}, FullMovie) is False
assert isinstancex({"name": "The Matrix"}, FullMovie) is False
assert isinstancex({"name": "The Matrix", "year": 1999, "extra": "qwe"}, FullMovie) is False

assert isinstancex({"name": "The Matrix", "year": 1999}, PartialMovie) is True
assert isinstancex({"name": "The Matrix"}, PartialMovie) is True
assert isinstancex({"name": "The Matrix", "year": 1999, "extra": "qwe"}, PartialMovie) is False

assert isinstancex({"name": "The Matrix", "year": 1999}, ExtraMovie) is True
assert isinstancex({"name": "The Matrix", "year": 1999, "q": "w", "e": "r"}, ExtraMovie) is True
assert isinstancex({"name": "The Matrix", "year": 1999, "q": "w", "e": 1}, ExtraMovie) is False

# TypedDict (shortcut)
assert isinstancex({"name": "The Matrix", "year": 1999, "q": "w", "e": "r"}, {"name": str, "year": int, ...: str}) is True

# Union
assert isinstancex(3, Union[str, int]) is True
assert isinstancex(3, Union[str, float]) is False
assert isinstancex(3.14, Union[int, T, str][bool]) is False
assert isinstancex(3.14, Union[int, T, str][float]) is True

issubclassx (:warning: still in WIP)

from typingx import *

assert issubclassx(int, int) is True
assert issubclassx(int, object) is True
assert issubclassx(int, float) is False
assert issubclassx(int, int | str) is True
assert issubclassx(tuple[int], tuple) is True
assert issubclassx(tuple[int], tuple[Any]) is True
assert issubclassx(tuple[int], tuple[Any, ...]) is True
assert issubclassx(tuple[int, str], tuple[object, ...]) is True
assert issubclassx(tuple[int, str], tuple[Any]) is False
assert issubclassx(tuple[int, int], (int, ...)) is True
assert issubclassx(tuple[int, int], (int, str)) is False
assert issubclassx(tuple[int, int], (int, Any)) is True

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

typingx-0.6.0.tar.gz (14.7 kB view details)

Uploaded Source

Built Distribution

typingx-0.6.0-py3-none-any.whl (12.3 kB view details)

Uploaded Python 3

File details

Details for the file typingx-0.6.0.tar.gz.

File metadata

  • Download URL: typingx-0.6.0.tar.gz
  • Upload date:
  • Size: 14.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.12 CPython/3.10.0rc2 Darwin/21.1.0

File hashes

Hashes for typingx-0.6.0.tar.gz
Algorithm Hash digest
SHA256 1a13e4b38832aaf89ce1a5d5437daf01358923d2e29288355ea90b5b806260f2
MD5 b55ece0f5cb02206b89ea3ff911bd38d
BLAKE2b-256 cd9d196eacc0553d4f958bb3370884f9f600b0fff8a0deb14977754bfd0ff7e3

See more details on using hashes here.

File details

Details for the file typingx-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: typingx-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 12.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.12 CPython/3.10.0rc2 Darwin/21.1.0

File hashes

Hashes for typingx-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9250517e145bf97e97ee1366fef27cd5d4e98250ebfc1d3f73b3dd9f34c9e8fa
MD5 fccf9c998ddc8ee5cb9e4dd5bc8e4a80
BLAKE2b-256 ed8cd387f5d2a331a7da589dacfa3d5f349bce7507f52b52c4a66c880fe5111d

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page