""" sphinx.util.typing ~~~~~~~~~~~~~~~~~~ The composit types for Sphinx. :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import sys import typing from typing import Any, Callable, Dict, List, Tuple, TypeVar, Union from docutils import nodes from docutils.parsers.rst.states import Inliner # An entry of Directive.option_spec DirectiveOption = Callable[[str], Any] # Text like nodes which are initialized with text and rawsource TextlikeNode = Union[nodes.Text, nodes.TextElement] # type of None NoneType = type(None) # path matcher PathMatcher = Callable[[str], bool] # common role functions RoleFunction = Callable[[str, str, str, int, Inliner, Dict[str, Any], List[str]], Tuple[List[nodes.Node], List[nodes.system_message]]] # title getter functions for enumerable nodes (see sphinx.domains.std) TitleGetter = Callable[[nodes.Node], str] # inventory data on memory Inventory = Dict[str, Dict[str, Tuple[str, str, str, str]]] def is_system_TypeVar(typ: Any) -> bool: """Check *typ* is system defined TypeVar.""" modname = getattr(typ, '__module__', '') return modname == 'typing' and isinstance(typ, TypeVar) # type: ignore def stringify(annotation: Any) -> str: """Stringify type annotation object.""" if isinstance(annotation, str): return annotation elif isinstance(annotation, TypeVar): # type: ignore return annotation.__name__ elif not annotation: return repr(annotation) elif annotation is NoneType: return 'None' elif (getattr(annotation, '__module__', None) == 'builtins' and hasattr(annotation, '__qualname__')): return annotation.__qualname__ elif annotation is Ellipsis: return '...' if sys.version_info >= (3, 7): # py37+ return _stringify_py37(annotation) else: return _stringify_py36(annotation) def _stringify_py37(annotation: Any) -> str: """stringify() for py37+.""" module = getattr(annotation, '__module__', None) if module == 'typing': if getattr(annotation, '_name', None): qualname = annotation._name elif getattr(annotation, '__qualname__', None): qualname = annotation.__qualname__ elif getattr(annotation, '__forward_arg__', None): qualname = annotation.__forward_arg__ else: qualname = stringify(annotation.__origin__) # ex. Union elif hasattr(annotation, '__qualname__'): qualname = '%s.%s' % (module, annotation.__qualname__) elif hasattr(annotation, '__origin__'): # instantiated generic provided by a user qualname = stringify(annotation.__origin__) else: # we weren't able to extract the base type, appending arguments would # only make them appear twice return repr(annotation) if getattr(annotation, '__args__', None): if qualname == 'Union': if len(annotation.__args__) > 1 and annotation.__args__[-1] is NoneType: if len(annotation.__args__) > 2: args = ', '.join(stringify(a) for a in annotation.__args__[:-1]) return 'Optional[Union[%s]]' % args else: return 'Optional[%s]' % stringify(annotation.__args__[0]) else: args = ', '.join(stringify(a) for a in annotation.__args__) return 'Union[%s]' % args elif qualname == 'Callable': args = ', '.join(stringify(a) for a in annotation.__args__[:-1]) returns = stringify(annotation.__args__[-1]) return '%s[[%s], %s]' % (qualname, args, returns) elif str(annotation).startswith('typing.Annotated'): # for py39+ return stringify(annotation.__args__[0]) elif all(is_system_TypeVar(a) for a in annotation.__args__): # Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT]) return qualname else: args = ', '.join(stringify(a) for a in annotation.__args__) return '%s[%s]' % (qualname, args) return qualname def _stringify_py36(annotation: Any) -> str: """stringify() for py35 and py36.""" module = getattr(annotation, '__module__', None) if module == 'typing': if getattr(annotation, '_name', None): qualname = annotation._name elif getattr(annotation, '__qualname__', None): qualname = annotation.__qualname__ elif getattr(annotation, '__forward_arg__', None): qualname = annotation.__forward_arg__ elif getattr(annotation, '__origin__', None): qualname = stringify(annotation.__origin__) # ex. Union else: qualname = repr(annotation).replace('typing.', '') elif hasattr(annotation, '__qualname__'): qualname = '%s.%s' % (module, annotation.__qualname__) else: qualname = repr(annotation) if (isinstance(annotation, typing.TupleMeta) and # type: ignore not hasattr(annotation, '__tuple_params__')): # for Python 3.6 params = annotation.__args__ if params: param_str = ', '.join(stringify(p) for p in params) return '%s[%s]' % (qualname, param_str) else: return qualname elif isinstance(annotation, typing.GenericMeta): params = None if hasattr(annotation, '__args__'): # for Python 3.5.2+ if annotation.__args__ is None or len(annotation.__args__) <= 2: # type: ignore # NOQA params = annotation.__args__ # type: ignore else: # typing.Callable args = ', '.join(stringify(arg) for arg in annotation.__args__[:-1]) # type: ignore result = stringify(annotation.__args__[-1]) # type: ignore return '%s[[%s], %s]' % (qualname, args, result) elif hasattr(annotation, '__parameters__'): # for Python 3.5.0 and 3.5.1 params = annotation.__parameters__ # type: ignore if params is not None: param_str = ', '.join(stringify(p) for p in params) return '%s[%s]' % (qualname, param_str) elif (hasattr(typing, 'UnionMeta') and isinstance(annotation, typing.UnionMeta) and # type: ignore hasattr(annotation, '__union_params__')): # for Python 3.5 params = annotation.__union_params__ if params is not None: if len(params) == 2 and params[1] is NoneType: return 'Optional[%s]' % stringify(params[0]) else: param_str = ', '.join(stringify(p) for p in params) return '%s[%s]' % (qualname, param_str) elif (hasattr(annotation, '__origin__') and annotation.__origin__ is typing.Union): # for Python 3.5.2+ params = annotation.__args__ if params is not None: if len(params) > 1 and params[-1] is NoneType: if len(params) > 2: param_str = ", ".join(stringify(p) for p in params[:-1]) return 'Optional[Union[%s]]' % param_str else: return 'Optional[%s]' % stringify(params[0]) else: param_str = ', '.join(stringify(p) for p in params) return 'Union[%s]' % param_str elif (isinstance(annotation, typing.CallableMeta) and # type: ignore getattr(annotation, '__args__', None) is not None and hasattr(annotation, '__result__')): # for Python 3.5 # Skipped in the case of plain typing.Callable args = annotation.__args__ if args is None: return qualname elif args is Ellipsis: args_str = '...' else: formatted_args = (stringify(a) for a in args) args_str = '[%s]' % ', '.join(formatted_args) return '%s[%s, %s]' % (qualname, args_str, stringify(annotation.__result__)) elif (isinstance(annotation, typing.TupleMeta) and # type: ignore hasattr(annotation, '__tuple_params__') and hasattr(annotation, '__tuple_use_ellipsis__')): # for Python 3.5 params = annotation.__tuple_params__ if params is not None: param_strings = [stringify(p) for p in params] if annotation.__tuple_use_ellipsis__: param_strings.append('...') return '%s[%s]' % (qualname, ', '.join(param_strings)) return qualname