Skip to main content

Python run-time type check

Project description

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]
    
  • Custom checking hooks:

    Examples coming soon...

    For now, please refer to 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, the following is made possible:

@type_guard
@dataclass
class B:
    x: int

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

Super friendly stack trace

This is the output of test cases. You can run the test yourself!

================================== 01-simple ===================================
       
[ PASS ] 1 is int => True
       
[ PASS ] 1.0 is int => False
[REASON] float(1.0) is not int
       
[ PASS ] [1, 2, 3] is list[int] => True
       
[ PASS ] [1, 2, 3.0] is list[int] => False
[REASON] list[2] = float(3.0) is not int
       
[ PASS ] 1 is Literal[1] => True
       
[ PASS ] 2 is Literal[1] => False
[REASON] int(2) is not Literal[1]
       
[ PASS ] 'alex' is Literal['alex', 'bob'] => True
       
[ PASS ] 'alex' is Literal['bob'] => False
[REASON] str('alex') is not Literal['bob']
       
================================= 02-multiple ==================================
       
[ PASS ] [1, '2', 3.0] is list[int, str, float] => True
       
[ PASS ] [1, '2', '3'] is list[int, str, float] => False
[REASON] list[2] = str('3') is not float
       
[ PASS ] [1, '2'] is list[int, str, float] => False
[REASON] list([1, '2']) is not list[int, str, float]
       
[ PASS ] (1, '2', 3.0) is tuple[int, str, float] => True
       
[ PASS ] (1, '2', '3') is tuple[int, str, float] => False
[REASON] tuple[2] = str('3') is not float
       
[ PASS ] {1, 3.0, '2'} is set[int | str | float] => True
       
[ PASS ] {None, 1, '2'} is set[int | str | float] => False
[REASON] set['?'] = NoneType(None) is not int | str | float
       
[ PASS ] {1: '2', 3: 4.0} is dict[int, str | float] => True
       
[ PASS ] {1: '2', '3': 4.0} is dict[int, str | float] => False
[REASON] dict<key> = str('3') is not int
       
[ PASS ] {1: '2', 3: None} is dict[int, str | float] => False
[REASON] dict[3] = NoneType(None) is not str | float
       
================================== 03-nested ===================================
       
[ PASS ] [[1, 2], [3, 4]] is list[list[int]] => True
       
[ PASS ] [[1, 2], [3, '4']] is list[list[int]] => False
[REASON] list[1][1] = str('4') is not int
       
================================== 04-unions ===================================
       
[ PASS ] [[1, 2], [3.0, 4.0]] is list[list[int] | list[float]] => True
       
[ PASS ] [[1, 2.0], [3, 4.0]] is list[list[int] | list[float]] => False
[REASON] list[0] = list([1, 2.0]) is not list[int] | list[float]
       
================================== 05-inherit ==================================
       
[ PASS ] [1, 2, 3] is A => True
       
[ PASS ] [1, 2, 0.0] is A => False
[REASON] A[2] = float(0.0) is not int
       
[ PASS ] B(x=1, y='2') is tests.05-inherit.B[int, str] => True
       
[ PASS ] B(x=1, y=2.0) is tests.05-inherit.B[int, str] => False
[REASON] B.y = float(2.0) is not str
       
=================================== 06-guard ===================================
       
[ PASS ] C([1, 2, 3]) is C => True
       
[ PASS ] C([1, 2, 0.0]) is C => False
[REASON] C[2] = float(0.0) is not int
       
[ PASS ] C(x=1, y=2.0) is C => True
       
[ PASS ] C(x=1.0, y=2) is C => False
[REASON] C.x = float(1.0) is not int
       
[ PASS ] add(1, 2) is int => True
       
[ PASS ] add('1', '2') is str => True
       
[ PASS ] add([1], [2]) is int | str => False
[REASON] list([1, 2]) is not int | str
       
[ PASS ] All 33 tests passed

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

rttc-1.0.4.tar.gz (7.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rttc-1.0.4-py3-none-any.whl (8.4 kB view details)

Uploaded Python 3

File details

Details for the file rttc-1.0.4.tar.gz.

File metadata

  • Download URL: rttc-1.0.4.tar.gz
  • Upload date:
  • Size: 7.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.13.0

File hashes

Hashes for rttc-1.0.4.tar.gz
Algorithm Hash digest
SHA256 2502e687e2db862199c9640e2e5ab0594e83ea6d5874e3eccd86b90dc42a164d
MD5 3550df130df75c48a0d8f8d71886ac25
BLAKE2b-256 4ae0afa38030a1ff087e4d4f40f48b3af203477e36ee0a6e2b0ee22b754f90c0

See more details on using hashes here.

File details

Details for the file rttc-1.0.4-py3-none-any.whl.

File metadata

  • Download URL: rttc-1.0.4-py3-none-any.whl
  • Upload date:
  • Size: 8.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.13.0

File hashes

Hashes for rttc-1.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 8f6f15ac7310bc60d9b8c60e26529bbafd9d070c1783cd7b979af83fd1c889c8
MD5 276e984357977f39f84bef98e53276aa
BLAKE2b-256 4cfc2799d748470d0795f25137dd276574edba3e180d7c4de5e3af7d4f84e9a5

See more details on using hashes here.

Supported by

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