|
2 | 2 | import sys
|
3 | 3 | import textwrap
|
4 | 4 | import typing
|
5 |
| -from typing import get_type_hints, TypeVar, Generic |
| 5 | +from typing import get_type_hints, TypeVar, Any, AnyStr, Tuple |
6 | 6 |
|
7 | 7 | from sphinx.util import logging
|
8 | 8 | from sphinx.util.inspect import Signature
|
9 | 9 |
|
10 |
| -try: |
11 |
| - from typing_extensions import Protocol |
12 |
| -except ImportError: |
13 |
| - Protocol = None |
14 |
| - |
15 | 10 | logger = logging.getLogger(__name__)
|
16 | 11 | pydata_annotations = {'Any', 'AnyStr', 'Callable', 'ClassVar', 'Literal', 'NoReturn', 'Optional',
|
17 | 12 | 'Tuple', 'Union'}
|
18 | 13 |
|
19 | 14 |
|
20 |
| -def format_annotation(annotation, fully_qualified=False): |
21 |
| - if inspect.isclass(annotation) and annotation.__module__ == 'builtins': |
22 |
| - if annotation.__qualname__ == 'NoneType': |
23 |
| - return '``None``' |
24 |
| - else: |
25 |
| - return ':py:class:`{}`'.format(annotation.__qualname__) |
| 15 | +def get_annotation_module(annotation) -> str: |
| 16 | + # Special cases |
| 17 | + if annotation is None: |
| 18 | + return 'builtins' |
26 | 19 |
|
27 |
| - annotation_cls = annotation if inspect.isclass(annotation) else type(annotation) |
28 |
| - if annotation_cls.__module__ in ('typing', 'typing_extensions'): |
29 |
| - params = None |
30 |
| - module = 'typing' |
31 |
| - extra = '' |
| 20 | + if hasattr(annotation, '__module__'): |
| 21 | + return annotation.__module__ |
| 22 | + |
| 23 | + if hasattr(annotation, '__origin__'): |
| 24 | + return annotation.__origin__.__module__ |
| 25 | + |
| 26 | + raise ValueError('Cannot determine the module of {}'.format(annotation)) |
| 27 | + |
| 28 | + |
| 29 | +def get_annotation_class_name(annotation) -> str: |
| 30 | + # Special cases |
| 31 | + if annotation is None: |
| 32 | + return 'None' |
| 33 | + elif annotation is Any: |
| 34 | + return 'Any' |
| 35 | + elif inspect.isfunction(annotation) and hasattr(annotation, '__supertype__'): |
| 36 | + return 'NewType' |
32 | 37 |
|
33 |
| - if inspect.isclass(annotation): |
34 |
| - class_name = annotation.__name__ |
| 38 | + if getattr(annotation, '__name__', None): |
| 39 | + return annotation.__name__ |
| 40 | + elif getattr(annotation, '_name', None): # Required for generic aliases on Python 3.7+ |
| 41 | + return annotation._name |
| 42 | + elif getattr(annotation, 'name', None): # Required for at least Pattern |
| 43 | + return annotation.name |
| 44 | + |
| 45 | + origin = getattr(annotation, '__origin__', None) |
| 46 | + if origin: |
| 47 | + if getattr(origin, '__name__', None): # Required for Protocol subclasses |
| 48 | + return origin.__name__ |
| 49 | + elif getattr(origin, '_name', None): # Required for Union on Python 3.7+ |
| 50 | + return origin._name |
35 | 51 | else:
|
36 |
| - class_name = str(annotation).split('[')[0].split('.')[-1] |
37 |
| - |
38 |
| - origin = getattr(annotation, '__origin__', None) |
39 |
| - if inspect.isclass(origin): |
40 |
| - annotation_cls = annotation.__origin__ |
41 |
| - try: |
42 |
| - mro = annotation_cls.mro() |
43 |
| - if Generic in mro or (Protocol and Protocol in mro): |
44 |
| - module = annotation_cls.__module__ |
45 |
| - except TypeError: |
46 |
| - pass # annotation_cls was either the "type" object or typing.Type |
47 |
| - |
48 |
| - if class_name == 'Any': |
49 |
| - return ':py:data:`{}typing.Any`'.format("" if fully_qualified else "~") |
50 |
| - elif class_name == '~AnyStr': |
51 |
| - return ':py:data:`{}typing.AnyStr`'.format("" if fully_qualified else "~") |
52 |
| - elif isinstance(annotation, TypeVar): |
53 |
| - return '\\%r' % annotation |
54 |
| - elif class_name == 'Union': |
55 |
| - if hasattr(annotation, '__union_params__'): |
56 |
| - params = annotation.__union_params__ |
57 |
| - elif hasattr(annotation, '__args__'): |
58 |
| - params = annotation.__args__ |
59 |
| - |
60 |
| - if params and len(params) == 2 and (hasattr(params[1], '__qualname__') and |
61 |
| - params[1].__qualname__ == 'NoneType'): |
62 |
| - class_name = 'Optional' |
63 |
| - params = (params[0],) |
64 |
| - elif class_name == 'Tuple' and hasattr(annotation, '__tuple_params__'): |
65 |
| - params = annotation.__tuple_params__ |
66 |
| - if annotation.__tuple_use_ellipsis__: |
67 |
| - params += (Ellipsis,) |
68 |
| - elif class_name == 'Callable': |
69 |
| - arg_annotations = result_annotation = None |
70 |
| - if hasattr(annotation, '__result__'): |
71 |
| - arg_annotations = annotation.__args__ |
72 |
| - result_annotation = annotation.__result__ |
73 |
| - elif getattr(annotation, '__args__', None): |
74 |
| - arg_annotations = annotation.__args__[:-1] |
75 |
| - result_annotation = annotation.__args__[-1] |
76 |
| - |
77 |
| - if arg_annotations in (Ellipsis, (Ellipsis,)): |
78 |
| - params = [Ellipsis, result_annotation] |
79 |
| - elif arg_annotations is not None: |
80 |
| - params = [ |
81 |
| - '\\[{}]'.format( |
82 |
| - ', '.join( |
83 |
| - format_annotation(param, fully_qualified) |
84 |
| - for param in arg_annotations)), |
85 |
| - result_annotation |
86 |
| - ] |
87 |
| - elif class_name == 'Literal': |
88 |
| - annotation_args = getattr(annotation, '__args__', ()) or annotation.__values__ |
89 |
| - extra = '\\[{}]'.format(', '.join(repr(arg) for arg in annotation_args)) |
90 |
| - elif class_name == 'ClassVar' and hasattr(annotation, '__type__'): |
91 |
| - # < py3.7 |
92 |
| - params = (annotation.__type__,) |
93 |
| - elif hasattr(annotation, 'type_var'): |
94 |
| - # Type alias |
95 |
| - class_name = annotation.name |
96 |
| - params = (annotation.type_var,) |
97 |
| - elif getattr(annotation, '__args__', None) is not None: |
98 |
| - params = annotation.__args__ |
99 |
| - elif hasattr(annotation, '__parameters__'): |
100 |
| - params = annotation.__parameters__ |
101 |
| - |
102 |
| - if params and annotation is not getattr(sys.modules[module], class_name): |
103 |
| - extra = '\\[{}]'.format(', '.join( |
104 |
| - format_annotation(param, fully_qualified) for param in params)) |
105 |
| - |
106 |
| - return '{prefix}`{qualify}{module}.{name}`{extra}'.format( |
107 |
| - prefix=':py:data:' if class_name in pydata_annotations else ':py:class:', |
108 |
| - qualify="" if fully_qualified else "~", |
109 |
| - module=module, |
110 |
| - name=class_name, |
111 |
| - extra=extra |
112 |
| - ) |
| 52 | + return origin.__class__.__name__.lstrip('_') # Required for Union on Python < 3.7 |
| 53 | + |
| 54 | + annotation_cls = annotation if inspect.isclass(annotation) else annotation.__class__ |
| 55 | + return annotation_cls.__name__.lstrip('_') |
| 56 | + |
| 57 | + |
| 58 | +def get_annotation_args(annotation, module: str, class_name: str) -> Tuple: |
| 59 | + try: |
| 60 | + original = getattr(sys.modules[module], class_name) |
| 61 | + except AttributeError: |
| 62 | + pass |
| 63 | + else: |
| 64 | + if annotation is original: |
| 65 | + return () # This is the original, unparametrized type |
| 66 | + |
| 67 | + # Special cases |
| 68 | + if class_name in ('Pattern', 'Match') and hasattr(annotation, 'type_var'): # Python < 3.7 |
| 69 | + return annotation.type_var, |
| 70 | + elif class_name == 'Callable' and hasattr(annotation, '__result__'): # Python < 3.5.3 |
| 71 | + argtypes = (Ellipsis,) if annotation.__args__ is Ellipsis else annotation.__args__ |
| 72 | + return argtypes + (annotation.__result__,) |
| 73 | + elif class_name == 'Union' and hasattr(annotation, '__union_params__'): # Union on Python 3.5 |
| 74 | + return annotation.__union_params__ |
| 75 | + elif class_name == 'Tuple' and hasattr(annotation, '__tuple_params__'): # Tuple on Python 3.5 |
| 76 | + params = annotation.__tuple_params__ |
| 77 | + if getattr(annotation, '__tuple_use_ellipsis__', False): |
| 78 | + params += (Ellipsis,) |
| 79 | + |
| 80 | + return params |
| 81 | + elif class_name == 'ClassVar' and hasattr(annotation, '__type__'): # ClassVar on Python < 3.7 |
| 82 | + return annotation.__type__, |
| 83 | + elif class_name == 'NewType' and hasattr(annotation, '__supertype__'): |
| 84 | + return annotation.__supertype__, |
| 85 | + elif class_name == 'Literal' and hasattr(annotation, '__values__'): |
| 86 | + return annotation.__values__ |
| 87 | + elif class_name == 'Generic': |
| 88 | + return annotation.__parameters__ |
| 89 | + |
| 90 | + return getattr(annotation, '__args__', ()) |
| 91 | + |
| 92 | + |
| 93 | +def format_annotation(annotation, fully_qualified: bool = False) -> str: |
| 94 | + # Special cases |
| 95 | + if annotation is None or annotation is type(None): # noqa: E721 |
| 96 | + return '``None``' |
113 | 97 | elif annotation is Ellipsis:
|
114 | 98 | return '...'
|
115 |
| - elif (inspect.isfunction(annotation) and annotation.__module__ == 'typing' and |
116 |
| - hasattr(annotation, '__name__') and hasattr(annotation, '__supertype__')): |
117 |
| - return ':py:func:`{qualify}typing.NewType`\\(:py:data:`~{name}`, {extra})'.format( |
118 |
| - qualify="" if fully_qualified else "~", |
119 |
| - name=annotation.__name__, |
120 |
| - extra=format_annotation(annotation.__supertype__, fully_qualified), |
121 |
| - ) |
122 |
| - elif inspect.isclass(annotation) or inspect.isclass(getattr(annotation, '__origin__', None)): |
123 |
| - if not inspect.isclass(annotation): |
124 |
| - annotation_cls = annotation.__origin__ |
125 |
| - |
126 |
| - extra = '' |
127 |
| - try: |
128 |
| - mro = annotation_cls.mro() |
129 |
| - except TypeError: |
130 |
| - pass |
131 |
| - else: |
132 |
| - if Generic in mro or (Protocol and Protocol in mro): |
133 |
| - params = (getattr(annotation, '__parameters__', None) or |
134 |
| - getattr(annotation, '__args__', None)) |
135 |
| - if params: |
136 |
| - extra = '\\[{}]'.format(', '.join( |
137 |
| - format_annotation(param, fully_qualified) for param in params)) |
138 |
| - |
139 |
| - return ':py:class:`{qualify}{module}.{name}`{extra}'.format( |
140 |
| - qualify="" if fully_qualified else "~", |
141 |
| - module=annotation.__module__, |
142 |
| - name=annotation_cls.__qualname__, |
143 |
| - extra=extra |
144 |
| - ) |
145 |
| - |
146 |
| - return str(annotation) |
| 99 | + |
| 100 | + # Type variables are also handled specially |
| 101 | + try: |
| 102 | + if isinstance(annotation, TypeVar) and annotation is not AnyStr: |
| 103 | + return '\\' + repr(annotation) |
| 104 | + except TypeError: |
| 105 | + pass |
| 106 | + |
| 107 | + try: |
| 108 | + module = get_annotation_module(annotation) |
| 109 | + class_name = get_annotation_class_name(annotation) |
| 110 | + args = get_annotation_args(annotation, module, class_name) |
| 111 | + except ValueError: |
| 112 | + return str(annotation) |
| 113 | + |
| 114 | + # Redirect all typing_extensions types to the stdlib typing module |
| 115 | + if module == 'typing_extensions': |
| 116 | + module = 'typing' |
| 117 | + |
| 118 | + full_name = (module + '.' + class_name) if module != 'builtins' else class_name |
| 119 | + prefix = '' if fully_qualified or full_name == class_name else '~' |
| 120 | + role = 'data' if class_name in pydata_annotations else 'class' |
| 121 | + args_format = '\\[{}]' |
| 122 | + formatted_args = '' |
| 123 | + |
| 124 | + # Some types require special handling |
| 125 | + if full_name == 'typing.NewType': |
| 126 | + args_format = '\\(:py:data:`~{name}`, {{}})'.format(prefix=prefix, |
| 127 | + name=annotation.__name__) |
| 128 | + role = 'func' |
| 129 | + elif full_name == 'typing.Union' and len(args) == 2 and type(None) in args: |
| 130 | + full_name = 'typing.Optional' |
| 131 | + args = tuple(x for x in args if x is not type(None)) # noqa: E721 |
| 132 | + elif full_name == 'typing.Callable' and args and args[0] is not ...: |
| 133 | + formatted_args = '\\[\\[' + ', '.join(format_annotation(arg) for arg in args[:-1]) + ']' |
| 134 | + formatted_args += ', ' + format_annotation(args[-1]) + ']' |
| 135 | + elif full_name == 'typing.Literal': |
| 136 | + formatted_args = '\\[' + ', '.join(repr(arg) for arg in args) + ']' |
| 137 | + |
| 138 | + if args and not formatted_args: |
| 139 | + formatted_args = args_format.format(', '.join(format_annotation(arg, fully_qualified) |
| 140 | + for arg in args)) |
| 141 | + |
| 142 | + return ':py:{role}:`{prefix}{full_name}`{formatted_args}'.format( |
| 143 | + role=role, prefix=prefix, full_name=full_name, formatted_args=formatted_args) |
147 | 144 |
|
148 | 145 |
|
149 | 146 | def process_signature(app, what: str, name: str, obj, options, signature, return_annotation):
|
|
0 commit comments