From e2389b451c49a3ab154b3e875ed38072adbdd64e Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 27 Jan 2018 12:20:39 +0900 Subject: [PATCH] Fix #4490: autodoc: type annotation is broken with python 3.7.0a4+ --- CHANGES | 1 + sphinx/util/inspect.py | 49 ++++++++++++++++++++++++++++++++++++++ tests/test_util_inspect.py | 7 +----- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 22a5f593649..cfddb83086f 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,7 @@ Bugs fixed * #4415: autodoc classifies inherited classmethods as regular methods * #4415: autodoc classifies inherited staticmethods as regular methods * #4472: DOCUMENTATION_OPTIONS is not defined +* #4490: autodoc: type annotation is broken with python 3.7.0a4+ Testing -------- diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index fd13d6fd6cb..1221427f971 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -431,6 +431,55 @@ def format_annotation(self, annotation): Displaying complex types from ``typing`` relies on its private API. """ + if sys.version_info >= (3, 7): # py37 and above + return self.format_annotation_new(annotation) + else: + return self.format_annotation_old(annotation) + + def format_annotation_new(self, annotation): + # type: (Any) -> str + """format_annotation() for py37+""" + module = getattr(annotation, '__module__', None) + if isinstance(annotation, string_types): + return annotation # type: ignore + elif isinstance(annotation, typing.TypeVar): + return annotation.__name__ + elif not annotation: + return repr(annotation) + elif module == 'builtins': + return annotation.__qualname__ + elif annotation is Ellipsis: + return '...' + + if module == 'typing': + if getattr(annotation, '_name', None): + qualname = annotation._name + elif getattr(annotation, '__qualname__', None): + qualname = annotation.__qualname__ + else: + qualname = self.format_annotation(annotation.__origin__) # ex. Union + elif hasattr(annotation, '__qualname__'): + qualname = '%s.%s' % (module, annotation.__qualname__) + else: + qualname = repr(annotation) + + if getattr(annotation, '__args__', None): + if qualname == 'Union': + args = ', '.join(self.format_annotation(a) for a in annotation.__args__) + return '%s[%s]' % (qualname, args) + elif qualname == 'Callable': + args = ', '.join(self.format_annotation(a) for a in annotation.__args__[:-1]) + returns = self.format_annotation(annotation.__args__[-1]) + return '%s[[%s], %s]' % (qualname, args, returns) + else: + args = ', '.join(self.format_annotation(a) for a in annotation.__args__) + return '%s[%s]' % (qualname, args) + + return qualname + + def format_annotation_old(self, annotation): + # type: (Any) -> str + """format_annotation() for py36 or below""" if isinstance(annotation, string_types): return annotation # type: ignore if isinstance(annotation, typing.TypeVar): # type: ignore diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 136536ec688..b5d50ed71a0 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -215,12 +215,7 @@ def test_Signature_annotations(): # TypeVars and generic types with TypeVars sig = inspect.Signature(f2).format_args() - if sys.version_info < (3, 7): - sig == ('(x: typing.List[T], y: typing.List[T_co], z: T) -> ' - 'typing.List[T_contra]') - else: - sig == ('(x: typing.List[~T], y: typing.List[+T_co], z: T) -> ' - 'typing.List[-T_contra]') + assert sig == '(x: List[T], y: List[T_co], z: T) -> List[T_contra]' # Union types sig = inspect.Signature(f3).format_args()