diff --git a/.travis.yml b/.travis.yml index f31e517d..e3054b6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,6 +35,11 @@ jobs: python: "3.8" after_success: *after_success + - stage: test + env: TOXENV=py39 + python: "3.9-dev" + after_success: *after_success + - stage: deploy to pypi install: skip script: skip diff --git a/tox.ini b/tox.ini index 82b571f7..ae8a69eb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 3.3.0 -envlist = pypy3, py35, py36, py37, py38, flake8 +envlist = pypy3, py35, py36, py37, py38, py39, flake8 skip_missing_interpreters = true isolated_build = true diff --git a/typeguard/__init__.py b/typeguard/__init__.py index 242989a7..27702086 100644 --- a/typeguard/__init__.py +++ b/typeguard/__init__.py @@ -173,7 +173,11 @@ def __init__(self, func: Callable, frame_locals: Optional[Dict[str, Any]] = None def resolve_forwardref(maybe_ref, memo: _TypeCheckMemo): if isinstance(maybe_ref, ForwardRef): - return evaluate_forwardref(maybe_ref, memo.globals, memo.locals) + if sys.version_info < (3, 9, 0): + return evaluate_forwardref(maybe_ref, memo.globals, memo.locals) + else: + return evaluate_forwardref(maybe_ref, memo.globals, memo.locals, frozenset()) + else: return maybe_ref @@ -247,7 +251,7 @@ def check_callable(argname: str, value, expected_type, memo: _TypeCheckMemo) -> if not callable(value): raise TypeError('{} must be a callable'.format(argname)) - if expected_type.__args__: + if getattr(expected_type, "__args__", None): try: signature = inspect.signature(value) except (TypeError, ValueError): @@ -297,7 +301,8 @@ def check_dict(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None format(argname, qualified_name(value))) if expected_type is not dict: - if expected_type.__args__ not in (None, expected_type.__parameters__): + if (hasattr(expected_type, "__args__") and + expected_type.__args__ not in (None, expected_type.__parameters__)): key_type, value_type = expected_type.__args__ if key_type is not Any or value_type is not Any: for k, v in value.items(): @@ -332,7 +337,8 @@ def check_list(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None format(argname, qualified_name(value))) if expected_type is not list: - if expected_type.__args__ not in (None, expected_type.__parameters__): + if hasattr(expected_type, "__args__") and expected_type.__args__ not in \ + (None, expected_type.__parameters__): value_type = expected_type.__args__[0] if value_type is not Any: for i, v in enumerate(value): @@ -344,7 +350,8 @@ def check_sequence(argname: str, value, expected_type, memo: _TypeCheckMemo) -> raise TypeError('type of {} must be a sequence; got {} instead'. format(argname, qualified_name(value))) - if expected_type.__args__ not in (None, expected_type.__parameters__): + if hasattr(expected_type, "__args__") and expected_type.__args__ not in \ + (None, expected_type.__parameters__): value_type = expected_type.__args__[0] if value_type is not Any: for i, v in enumerate(value): @@ -357,7 +364,8 @@ def check_set(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: format(argname, qualified_name(value))) if expected_type is not set: - if expected_type.__args__ not in (None, expected_type.__parameters__): + if hasattr(expected_type, "__args__") and expected_type.__args__ not in \ + (None, expected_type.__parameters__): value_type = expected_type.__args__[0] if value_type is not Any: for v in value: @@ -366,12 +374,23 @@ def check_set(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: def check_tuple(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: # Specialized check for NamedTuples - if hasattr(expected_type, '_field_types'): + is_named_tuple = False + if sys.version_info < (3, 8, 0): + is_named_tuple = hasattr(expected_type, '_field_types') # deprecated since python 3.8 + else: + is_named_tuple = hasattr(expected_type, '__annotations__') + + if is_named_tuple: if not isinstance(value, expected_type): raise TypeError('type of {} must be a named tuple of type {}; got {} instead'. format(argname, qualified_name(expected_type), qualified_name(value))) - for name, field_type in expected_type._field_types.items(): + if sys.version_info < (3, 8, 0): + field_types = expected_type._field_types + else: + field_types = expected_type.__annotations__ + + for name, field_type in field_types.items(): check_type('{}.{}'.format(argname, name), getattr(value, name), field_type, memo) return @@ -436,7 +455,9 @@ def check_class(argname: str, value, expected_type, memo: _TypeCheckMemo) -> Non if expected_type is Type: return - expected_class = expected_type.__args__[0] if expected_type.__args__ else None + expected_class = None + if hasattr(expected_type, "__args__") and expected_type.__args__: + expected_class = expected_type.__args__[0] if expected_class: if expected_class is Any: return @@ -728,10 +749,13 @@ def check_argument_types(memo: Optional[_CallMemo] = None) -> bool: class TypeCheckedGenerator: def __init__(self, wrapped: Generator, memo: _CallMemo): - rtype_args = memo.type_hints['return'].__args__ + rtype_args = [] + if hasattr(memo.type_hints['return'], "__args__"): + rtype_args = memo.type_hints['return'].__args__ + self.__wrapped = wrapped self.__memo = memo - self.__yield_type = rtype_args[0] + self.__yield_type = rtype_args[0] if rtype_args else Any self.__send_type = rtype_args[1] if len(rtype_args) > 1 else Any self.__return_type = rtype_args[2] if len(rtype_args) > 2 else Any self.__initialized = False