mitmproxy/mitmproxy/utils/typecheck.py

95 lines
2.9 KiB
Python

import typing
Type = typing.Union[
typing.Any # anything more elaborate really fails with mypy at the moment.
]
def sequence_type(typeinfo: typing.Type[typing.List]) -> Type:
"""Return the type of a sequence, e.g. typing.List"""
try:
return typeinfo.__args__[0] # type: ignore
except AttributeError: # Python 3.5.0
return typeinfo.__parameters__[0] # type: ignore
def tuple_types(typeinfo: typing.Type[typing.Tuple]) -> typing.Sequence[Type]:
"""Return the types of a typing.Tuple"""
try:
return typeinfo.__args__ # type: ignore
except AttributeError: # Python 3.5.x
return typeinfo.__tuple_params__ # type: ignore
def union_types(typeinfo: typing.Type[typing.Tuple]) -> typing.Sequence[Type]:
"""return the types of a typing.Union"""
try:
return typeinfo.__args__ # type: ignore
except AttributeError: # Python 3.5.x
return typeinfo.__union_params__ # type: ignore
def mapping_types(typeinfo: typing.Type[typing.Mapping]) -> typing.Tuple[Type, Type]:
"""return the types of a mapping, e.g. typing.Dict"""
return typeinfo.__args__ # type: ignore
def check_option_type(name: str, value: typing.Any, typeinfo: Type) -> None:
"""
Check if the provided value is an instance of typeinfo and raises a
TypeError otherwise. This function supports only those types required for
options.
"""
e = TypeError("Expected {} for {}, but got {}.".format(
typeinfo,
name,
type(value)
))
typename = str(typeinfo)
if typename.startswith("typing.Union"):
for T in union_types(typeinfo):
try:
check_option_type(name, value, T)
except TypeError:
pass
else:
return
raise e
elif typename.startswith("typing.Tuple"):
types = tuple_types(typeinfo)
if not isinstance(value, (tuple, list)):
raise e
if len(types) != len(value):
raise e
for i, (x, T) in enumerate(zip(value, types)):
check_option_type("{}[{}]".format(name, i), x, T)
return
elif typename.startswith("typing.Sequence"):
T = sequence_type(typeinfo)
if not isinstance(value, (tuple, list)):
raise e
for v in value:
check_option_type(name, v, T)
elif typename.startswith("typing.IO"):
if hasattr(value, "read"):
return
else:
raise e
elif typename.startswith("typing.Any"):
return
elif not isinstance(value, typeinfo):
raise e
def typespec_to_str(typespec: typing.Any) -> str:
if typespec in (str, int, bool):
t = typespec.__name__
elif typespec == typing.Optional[str]:
t = 'optional str'
elif typespec == typing.Sequence[str]:
t = 'sequence of str'
else:
raise NotImplementedError
return t