Check whether an object agrees deeply with a type

I have an arbitrary object (e.g. returned from json.parse) and I want to check whether it is deeply consistent with a type, and issue an error if it isn’t. I’m using “deep” here in the same way that copy.deepcopy uses it. For example, the type that I need to check consistency with might be dict[str, dict[str, list[str]]]; assert isinstance(obj, dict) only checks for shallow consistency, and you can’t apply type arguments to the second parameter of isinstance.

I’m aware of TypeGuard, but this requires me to write the code for the deep check myself. I’m also aware that this is pretty much pydantic’s entire value proposition, but for reasons outside the scope of this question, adding a dependency on pydantic to the thing I’m working on right now is not an option.

Using only the facilities of the Python 3.10[1] standard library, is there any built-in means to do this kind of check, or at least any helpers that can make it less tedious than a bunch of hand-written TypeGuard functions along the lines of

def require_list_str(obj: Any) -> TypeGuard[list[str]]:
    if not isinstance(obj, list):
        raise TypeError(f"expected a list, not a {type(obj).__name__}")
    for i, item in enumerate(list):
        if not isinstance(item, str):
            raise TypeError(f"item {i}: expected a str, not a {type(item).__name__}")
    return True

def require_dict_str_list_str(obj: Any) -> TypeGuard[dict[str, list[str]]]:
    if not isinstance(obj, dict):
        raise TypeError(f"expected a dict, not a {type(obj).__name__}")
    for key, val in obj.items():
        if not isinstance(key, str):
            raise TypeError(f"dict key: expected a str, not a {type(key).__name__}")
        require_list_str(val)
    return True

# etc

?


  1. Yes, I am aware that Python 3.10 is EOL. The project’s range of supported Python versions is fixed by factors outside the scope of this question. ↩︎

There isn’t anything like that in the standard library, although you could build it yourself.

I’d say cattrs is better suited for the general case than pydantic.

1 Like