Skip to content

zhangyx1998/rttc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Python run-time type checking utility

Author: Yuxuan Zhang | GitHub Repository

The rttc project originates from this post on the python discussion forum.

Usage

Want to do something like this?

>>> isinstance(["hello type check"], list[str])
TypeError: isinstance() argument 2 cannot be a parameterized generic

Just drop-in replace isinstance() with type_check()

from type_check import type_check

type_check(["hello type check"], list[str]) # True
type_check([1], list[str]) # False

And of course you can use type variables!

DataType = list[tuple[float, str]]

type_check([(1.0, "hello rttc")], DataType) # True
type_check([(1, 2), [3.0, "4"]] , DataType) # False

Wondering how far you can go?

These features all work recursively with each other!

  • Union types are supported:

    type_check(1   , int | bool) # True
    type_check(True, int | bool) # True
    type_check("1" , int | bool) # False
  • Literals are supported:

    type_check("alex", Literal["alex", "bob"]) # True
    type_check("hack", Literal["alex", "bob"]) # False
  • Inherited classes are supported:

    class C(list[int]):
        pass
    
    type_check(C([1])  , C) # True
    type_check(C([1.0]), C) # False
  • Type-hinted classes are supported:

    from typing import TypeVar, Generic, Literal
    from dataclasses import dataclass
    
    T = TypeVar("T")
    P = TypeVar("P")
    
    @dataclass
    class C(Generic[T, P]):
        x: T
        y: P
        z: Literal[1]
    
    type_check(C(x=1  , y="y", z=1), C[int, str]) # True
    type_check(C(x=1.0, y="y", z=1), C[int, str]) # False - C.x = float(1.0) is not int
    type_check(C(x=1  , y="y", z=2), C[int, str]) # False - C.z = int(2) is not Literal[1]

    Since 1.0.6, type-hinted objects can omit type (2nd) argument:

    type_check(C[int, str](x=1  , y="y", z=1)) # True
    type_check(C[int, str](x=1.0, y="y", z=1)) # False - C.x = float(1.0) is not int
    type_check(C[int, str](x=1  , y="y", z=2)) # False - C.z = int(2) is not Literal[1]
  • Custom type_check hooks:

    Examples coming soon...

    For now, please refer to type_check/builtin_checks.py.

Other tools in the box

type_assert()

Similar to type_check(), but it raises TypeCheckError instead of returns bool. The raised TypeCheckError contains debug-friendly information indicating what caused type check to fail (check below for details).

@type_guard

This decorator allows you to convert a class or a function into a type-guarded object. It is analogous to performing a type_assert on function return values or on returned class instances.

from type_check import type_guard

@type_guard
def fn(x) -> int | float | str:
    return x

fn(1) # ok

fn([]) # TypeCheckError: list([]) is not int | float | str

from dataclasses import dataclass

@type_guard
@dataclass
class A:
    x: int

A(x=1) # ok
A(x=1.0) # TypeCheckError: A.x = float(1.0) is not int

Since 1.0.4, templated classes are supported by type_guard:

@type_guard
@dataclass
class B[T]:
    x: T

B[int](x=1)   # ok
B[int](x=1.0) # TypeCheckError: A.x = float(1.0) is not int
B[float](x=1) # TypeCheckError: A.x = int(1) is not float

Info-rich return values and exceptions

TypeCheckResult

TypeCheckResult is the return type of type_check() function. It can be used directly like a bool or compared with another bool.

For example:

from type_check import type_check

result = type_check([1], list[int])

print(bool(result), result == True, result == False)
# True, True, False
print(str(result))
# [1] is list[int] => True
print(repr(result))
# type_check([1] is list[int] => True)

And when a result evaluates to False, you can use TypeCheckResult.reason to know why:

result = type_check(["1"], list[int])

print(result)
# ['1'] is list[int] => False
print(result.reason)
# list[0] = str('1') is not int

TypeCheckError

TypeCheckError is inherited from TypeError, it will be raised by type_assert() and @type_guard when type check fails.

It contains chained attributes and keys to help you locate the data that cause type check to fail:

from type_check import type_assert

type_assert([1, 2, '3', 4], list[int])

Will raise:

TypeCheckError                            Traceback (most recent call last)
Cell In[47], line 10
    6 print(result.reason)
    8 from type_check import type_assert
---> 10 type_assert([1, 2, "3", 4], list[int])

File rttc/type_check/core.py:45, in type_assert(obj, t, chain)
    43     # Clear traceback to avoid confusion
    44     type_check_error = e.with_traceback(None)
---> 45     raise type_check_error
    46 except TypeError as e:
    47     type_error = e.with_traceback(None)

TypeCheckError: list[2] = str('3') is not int

Testing

Test cases live under tests/ and are grouped by categories. Test results are available at docs/test-results.txt. PRs to add more test cases to it will be deeply appreciated.

Instructions to run tests locally

git clone [email protected]:zhangyx1998/rttc.git

cd rttc && python3 -m pip install -r tests/requirements.txt # termcolor

python3 -m tests

Testing against well-known library typeguard:

pip3 install typeguard

TARGET=typegurad python3 -m tests

About

Python3 Run-Time Type Check

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published