Similarly to my python colors that you can just copy and use, my arsenal of do it yourselfs has grown by a typechecking function wrapper.
I'd normally say it's not necessary, since if you're serious and using python specifically, static type checking should be enough, but I've recently had to do it without external libraries in many places, and repeating myself many times sounded too annoying.
If you ever find yourself in such a place too, here's the code (requires python >= 3.8):
1from functools import wraps
2import inspect
3from typing import Any, get_args, get_origin, get_type_hints
4
5def check_type(obj: Any, expected_type: type) -> bool:
6 if get_origin(expected_type) is list:
7 elem_type = get_args(expected_type)[0]
8 return isinstance(obj, list) and all([check_type(x, elem_type) for x in obj])
9 return isinstance(obj, expected_type)
10
11def typeme(fn):
12 @wraps(fn)
13 def wrapper(*args, **kwargs):
14 sig = inspect.signature(fn)
15 annotations = get_type_hints(fn)
16 bound = sig.bind(*args, **kwargs)
17 bound.apply_defaults()
18
19 for name, value in bound.arguments.items():
20 if name in annotations:
21 expected_type = annotations[name]
22 if not check_type(value, expected_type):
23 tes = f"Argument {name} should be {expected_type.__name__}, got {type(value).__name__}"
24 raise TypeError(tes)
25
26 return fn(*args, **kwargs)
27 return wrapper