Merge branch '3.x'

This commit is contained in:
Takeshi KOMIYA
2020-11-22 16:04:07 +09:00
31 changed files with 345 additions and 105 deletions

View File

@@ -37,12 +37,10 @@ jobs:
with:
python-version: ${{ matrix.python }}
- name: Set up Python ${{ matrix.python }} (deadsnakes)
uses: deadsnakes/action@v1.0.0
uses: deadsnakes/action@v2.0.1
if: endsWith(matrix.python, '-dev')
with:
python-version: ${{ matrix.python }}
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
- name: Check Python version
run: python --version
- name: Install graphviz

View File

@@ -69,6 +69,8 @@ Deprecated
* The ``follow_wrapped`` argument of ``sphinx.util.inspect.signature()``
* ``sphinx.ext.autodoc.DataDeclarationDocumenter``
* ``sphinx.ext.autodoc.importer._getannotations()``
* ``sphinx.pycode.ModuleAnalyzer.parse()``
* ``sphinx.util.requests.is_ssl_error()``
Features added
@@ -84,6 +86,7 @@ Features added
value equal to None is set.
* #8209: autodoc: Add ``:no-value:`` option to :rst:dir:`autoattribute` and
:rst:dir:`autodata` directive to suppress the default value of the variable
* #8460: autodoc: Support custom types defined by typing.NewType
* #6914: Add a new event :event:`warn-missing-reference` to custom warning
messages when failed to resolve a cross-reference
* #6914: Emit a detailed warning when failed to resolve a ``:ref:`` reference
@@ -101,7 +104,11 @@ Bugs fixed
type annotated variables
* #8443: autodoc: autoattribute directive can't create document for PEP-526
based uninitalized variables
* #8452: autodoc: autodoc_type_aliases doesn't work when autodoc_typehints is
set to "description"
* #8419: html search: Do not load ``language_data.js`` in non-search pages
* #8454: graphviz: The layout option for graph and digraph directives don't work
* #8437: Makefile: ``make clean`` with empty BUILDDIR is dangerous
Testing
--------

View File

@@ -66,6 +66,16 @@ The following is a list of deprecated interfaces.
- 5.0
- ``sphinx.ext.autodoc.DataDocumenter``
* - ``sphinx.ext.autodoc.importer._getannotations()``
- 3.4
- 4.0
- ``sphinx.util.inspect.getannotations()``
* - ``sphinx.pycode.ModuleAnalyzer.parse()``
- 3.4
- 5.0
- ``sphinx.pycode.ModuleAnalyzer.analyze()``
* - ``sphinx.util.requests.is_ssl_error()``
- 3.4
- 5.0

View File

@@ -573,7 +573,8 @@ class Documenter:
else:
return 'docstring of %s' % fullname
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
def add_content(self, more_content: Optional[StringList], no_docstring: bool = False
) -> None:
"""Add content from docstrings, attribute documentation and user."""
# set sourcename and add content from attribute documentation
sourcename = self.get_sourcename()
@@ -844,7 +845,7 @@ class Documenter:
return documenters
def generate(self, more_content: Any = None, real_modname: str = None,
def generate(self, more_content: Optional[StringList] = None, real_modname: str = None,
check_module: bool = False, all_members: bool = False) -> None:
"""Generate reST for the object given by *self.name*, and possibly for
its members.
@@ -983,6 +984,10 @@ class ModuleDocumenter(Documenter):
try:
if not self.options.ignore_module_all:
self.__all__ = inspect.getall(self.object)
except AttributeError as exc:
# __all__ raises an error.
logger.warning(__('%s.__all__ raises an error. Ignored: %r'),
(self.fullname, exc), type='autodoc')
except ValueError as exc:
# invalid __all__ found.
logger.warning(__('__all__ should be a list of strings, not %r '
@@ -1522,7 +1527,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
for cls in self._signature_class.__mro__:
try:
analyzer = ModuleAnalyzer.for_module(cls.__module__)
analyzer.parse()
analyzer.analyze()
qualname = '.'.join([cls.__qualname__, self._signature_method_name])
if qualname in analyzer.overloads:
return analyzer.overloads.get(qualname)
@@ -1603,7 +1608,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
tab_width = self.directive.state.document.settings.tab_width
return [prepare_docstring(docstring, ignore, tab_width) for docstring in docstrings]
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
def add_content(self, more_content: Optional[StringList], no_docstring: bool = False
) -> None:
if self.doc_as_attr:
classname = safe_getattr(self.object, '__qualname__', None)
if not classname:
@@ -1623,7 +1629,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
return
super().document_members(all_members)
def generate(self, more_content: Any = None, real_modname: str = None,
def generate(self, more_content: Optional[StringList] = None, real_modname: str = None,
check_module: bool = False, all_members: bool = False) -> None:
# Do not pass real_modname and use the name from the __module__
# attribute of the class.
@@ -1651,7 +1657,25 @@ class ExceptionDocumenter(ClassDocumenter):
return isinstance(member, type) and issubclass(member, BaseException)
class DataDocumenter(ModuleLevelDocumenter):
class NewTypeMixin:
"""
Mixin for DataDocumenter and AttributeDocumenter to provide the feature for
supporting NewTypes.
"""
def should_suppress_directive_header(self) -> bool:
"""Check directive header should be suppressed."""
return inspect.isNewType(self.object) # type: ignore
def update_content(self, more_content: StringList) -> None:
"""Update docstring for the NewType object."""
if inspect.isNewType(self.object): # type: ignore
supertype = restify(self.object.__supertype__) # type: ignore
more_content.append(_('alias of %s') % supertype, '')
more_content.append('', '')
class DataDocumenter(ModuleLevelDocumenter, NewTypeMixin):
"""
Specialized Documenter subclass for data items.
"""
@@ -1692,7 +1716,12 @@ class DataDocumenter(ModuleLevelDocumenter):
def add_directive_header(self, sig: str) -> None:
super().add_directive_header(sig)
sourcename = self.get_sourcename()
if not self.options.annotation:
if self.options.annotation is SUPPRESS or self.should_suppress_directive_header():
pass
elif self.options.annotation:
self.add_line(' :annotation: %s' % self.options.annotation,
sourcename)
else:
# obtain annotation for this data
annotations = get_type_hints(self.parent, None, self.config.autodoc_type_aliases)
if self.objpath[-1] in annotations:
@@ -1712,11 +1741,6 @@ class DataDocumenter(ModuleLevelDocumenter):
self.add_line(' :value: ' + objrepr, sourcename)
except ValueError:
pass
elif self.options.annotation is SUPPRESS:
pass
else:
self.add_line(' :annotation: %s' % self.options.annotation,
sourcename)
def document_members(self, all_members: bool = False) -> None:
pass
@@ -1725,11 +1749,16 @@ class DataDocumenter(ModuleLevelDocumenter):
return self.get_attr(self.parent or self.object, '__module__', None) \
or self.modname
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
def add_content(self, more_content: Optional[StringList], no_docstring: bool = False
) -> None:
if self.object is UNINITIALIZED_ATTR:
# suppress docstring of the value
super().add_content(more_content, no_docstring=True)
else:
if not more_content:
more_content = StringList()
self.update_content(more_content)
super().add_content(more_content, no_docstring=no_docstring)
@@ -1770,12 +1799,31 @@ class GenericAliasDocumenter(DataDocumenter):
self.options['annotation'] = SUPPRESS
super().add_directive_header(sig)
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
def add_content(self, more_content: Optional[StringList], no_docstring: bool = False
) -> None:
name = stringify_typehint(self.object)
content = StringList([_('alias of %s') % name], source='')
super().add_content(content)
class NewTypeDataDocumenter(DataDocumenter):
"""
Specialized Documenter subclass for NewTypes.
Note: This must be invoked before FunctionDocumenter because NewType is a kind of
function object.
"""
objtype = 'newtypedata'
directivetype = 'data'
priority = FunctionDocumenter.priority + 1
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
return inspect.isNewType(member) and isattr
class TypeVarDocumenter(DataDocumenter):
"""
Specialized Documenter subclass for TypeVars.
@@ -1806,7 +1854,8 @@ class TypeVarDocumenter(DataDocumenter):
else:
return []
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
def add_content(self, more_content: Optional[StringList], no_docstring: bool = False
) -> None:
attrs = [repr(self.object.__name__)]
for constraint in self.object.__constraints__:
attrs.append(stringify_typehint(constraint))
@@ -1980,7 +2029,7 @@ class SingledispatchMethodDocumenter(MethodDocumenter):
super().__init__(*args, **kwargs)
class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore
class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter, NewTypeMixin): # type: ignore # NOQA
"""
Specialized Documenter subclass for attributes.
"""
@@ -2076,7 +2125,11 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
def add_directive_header(self, sig: str) -> None:
super().add_directive_header(sig)
sourcename = self.get_sourcename()
if not self.options.annotation:
if self.options.annotation is SUPPRESS or self.should_suppress_directive_header():
pass
elif self.options.annotation:
self.add_line(' :annotation: %s' % self.options.annotation, sourcename)
else:
# obtain type annotation for this attribute
annotations = get_type_hints(self.parent, None, self.config.autodoc_type_aliases)
if self.objpath[-1] in annotations:
@@ -2098,10 +2151,6 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
self.add_line(' :value: ' + objrepr, sourcename)
except ValueError:
pass
elif self.options.annotation is SUPPRESS:
pass
else:
self.add_line(' :annotation: %s' % self.options.annotation, sourcename)
def get_doc(self, ignore: int = None) -> List[List[str]]:
try:
@@ -2114,11 +2163,16 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
finally:
self.config.autodoc_inherit_docstrings = orig # type: ignore
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
def add_content(self, more_content: Optional[StringList], no_docstring: bool = False
) -> None:
if not self._datadescriptor:
# if it's not a data descriptor, its docstring is very probably the
# wrong thing to display
no_docstring = True
if more_content is None:
more_content = StringList()
self.update_content(more_content)
super().add_content(more_content, no_docstring)
@@ -2192,7 +2246,8 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
self._datadescriptor = False
return True
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
def add_content(self, more_content: Optional[StringList], no_docstring: bool = False
) -> None:
"""Never try to get a docstring from the object."""
super().add_content(more_content, no_docstring=True)
@@ -2243,14 +2298,38 @@ class SlotsAttributeDocumenter(AttributeDocumenter):
% self.__class__.__name__,
RemovedInSphinx50Warning, stacklevel=2)
name = self.objpath[-1]
__slots__ = inspect.getslots(self.parent)
if __slots__ and isinstance(__slots__.get(name, None), str):
docstring = prepare_docstring(__slots__[name])
return [docstring]
else:
try:
__slots__ = inspect.getslots(self.parent)
if __slots__ and isinstance(__slots__.get(name, None), str):
docstring = prepare_docstring(__slots__[name])
return [docstring]
else:
return []
except (AttributeError, ValueError) as exc:
logger.warning(__('Invalid __slots__ found on %s. Ignored.'),
(self.parent.__qualname__, exc), type='autodoc')
return []
class NewTypeAttributeDocumenter(AttributeDocumenter):
"""
Specialized Documenter subclass for NewTypes.
Note: This must be invoked before MethodDocumenter because NewType is a kind of
function object.
"""
objtype = 'newvarattribute'
directivetype = 'attribute'
priority = MethodDocumenter.priority + 1
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
return not isinstance(parent, ModuleDocumenter) and inspect.isNewType(member)
def get_documenters(app: Sphinx) -> Dict[str, Type[Documenter]]:
"""Returns registered Documenter classes"""
warnings.warn("get_documenters() is deprecated.", RemovedInSphinx50Warning, stacklevel=2)
@@ -2280,6 +2359,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_autodocumenter(ExceptionDocumenter)
app.add_autodocumenter(DataDocumenter)
app.add_autodocumenter(GenericAliasDocumenter)
app.add_autodocumenter(NewTypeDataDocumenter)
app.add_autodocumenter(TypeVarDocumenter)
app.add_autodocumenter(FunctionDocumenter)
app.add_autodocumenter(DecoratorDocumenter)
@@ -2288,6 +2368,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_autodocumenter(PropertyDocumenter)
app.add_autodocumenter(InstanceAttributeDocumenter)
app.add_autodocumenter(SlotsAttributeDocumenter)
app.add_autodocumenter(NewTypeAttributeDocumenter)
app.add_config_value('autoclass_content', 'class', True, ENUM('both', 'class', 'init'))
app.add_config_value('autodoc_member_order', 'alphabetical', True,

View File

@@ -15,7 +15,7 @@ from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Optional, Tup
from sphinx.pycode import ModuleAnalyzer
from sphinx.util import logging
from sphinx.util.inspect import getslots, isclass, isenumclass, safe_getattr
from sphinx.util.inspect import getannotations, getslots, isclass, isenumclass, safe_getattr
if False:
# For type annotation
@@ -148,10 +148,12 @@ def get_module_members(module: Any) -> List[Tuple[str, Any]]:
continue
# annotation only member (ex. attr: int)
if hasattr(module, '__annotations__'):
for name in module.__annotations__:
try:
for name in getannotations(module):
if name not in members:
members[name] = (name, INSTANCEATTR)
except AttributeError:
pass
return sorted(list(members.values()))
@@ -172,12 +174,9 @@ def _getmro(obj: Any) -> Tuple["Type", ...]:
def _getannotations(obj: Any) -> Mapping[str, Any]:
"""Get __annotations__ from given *obj* safely."""
__annotations__ = safe_getattr(obj, '__annotations__', None)
if isinstance(__annotations__, Mapping):
return __annotations__
else:
return {}
warnings.warn('sphinx.ext.autodoc.importer._getannotations() is deprecated.',
RemovedInSphinx40Warning)
return getannotations(obj)
def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
@@ -210,7 +209,7 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
for name in __slots__:
members[name] = Attribute(name, True, SLOTSATTR)
except (TypeError, ValueError):
except (AttributeError, TypeError, ValueError):
pass
# other members
@@ -226,10 +225,13 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
# annotation only member (ex. attr: int)
for i, cls in enumerate(_getmro(subject)):
for name in _getannotations(cls):
name = unmangle(cls, name)
if name and name not in members:
members[name] = Attribute(name, i == 0, INSTANCEATTR)
try:
for name in getannotations(cls):
name = unmangle(cls, name)
if name and name not in members:
members[name] = Attribute(name, i == 0, INSTANCEATTR)
except AttributeError:
pass
if analyzer:
# append instance attributes (cf. self.attr1) if analyzer knows

View File

@@ -27,7 +27,7 @@ def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
if callable(obj):
annotations = app.env.temp_data.setdefault('annotations', {})
annotation = annotations.setdefault(name, OrderedDict())
sig = inspect.signature(obj)
sig = inspect.signature(obj, type_aliases=app.config.autodoc_type_aliases)
for param in sig.parameters.values():
if param.annotation is not param.empty:
annotation[param.name] = typing.stringify(param.annotation)

View File

@@ -85,13 +85,15 @@ def setup_documenters(app: Any) -> None:
DecoratorDocumenter, ExceptionDocumenter,
FunctionDocumenter, GenericAliasDocumenter,
InstanceAttributeDocumenter, MethodDocumenter,
ModuleDocumenter, PropertyDocumenter,
ModuleDocumenter, NewTypeAttributeDocumenter,
NewTypeDataDocumenter, PropertyDocumenter,
SingledispatchFunctionDocumenter, SlotsAttributeDocumenter)
documenters = [
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
SlotsAttributeDocumenter, GenericAliasDocumenter, SingledispatchFunctionDocumenter,
FunctionDocumenter, MethodDocumenter, NewTypeAttributeDocumenter,
NewTypeDataDocumenter, AttributeDocumenter, InstanceAttributeDocumenter,
DecoratorDocumenter, PropertyDocumenter, SlotsAttributeDocumenter,
GenericAliasDocumenter, SingledispatchFunctionDocumenter,
] # type: List[Type[Documenter]]
for documenter in documenters:
app.registry.add_documenter(documenter.objtype, documenter)

View File

@@ -182,7 +182,8 @@ class GraphvizSimple(SphinxDirective):
'alt': directives.unchanged,
'align': align_spec,
'caption': directives.unchanged,
'graphviz_dot': directives.unchanged,
'layout': directives.unchanged,
'graphviz_dot': directives.unchanged, # an old alias of `layout` option
'name': directives.unchanged,
'class': directives.class_option,
}
@@ -194,6 +195,8 @@ class GraphvizSimple(SphinxDirective):
node['options'] = {'docname': self.env.docname}
if 'graphviz_dot' in self.options:
node['options']['graphviz_dot'] = self.options['graphviz_dot']
if 'layout' in self.options:
node['options']['graphviz_dot'] = self.options['layout']
if 'alt' in self.options:
node['alt'] = self.options['alt']
if 'align' in self.options:

View File

@@ -18,6 +18,7 @@ from os import path
from typing import IO, Any, Dict, List, Optional, Tuple
from zipfile import ZipFile
from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.errors import PycodeError
from sphinx.pycode.parser import Parser
@@ -132,18 +133,24 @@ class ModuleAnalyzer:
# cache the source code as well
self.code = source.read()
# will be filled by parse()
# will be filled by analyze()
self.annotations = None # type: Dict[Tuple[str, str], str]
self.attr_docs = None # type: Dict[Tuple[str, str], List[str]]
self.finals = None # type: List[str]
self.overloads = None # type: Dict[str, List[Signature]]
self.tagorder = None # type: Dict[str, int]
self.tags = None # type: Dict[str, Tuple[str, int, int]]
self._parsed = False
self._analyzed = False
def parse(self) -> None:
"""Parse the source code."""
if self._parsed:
warnings.warn('ModuleAnalyzer.parse() is deprecated.',
RemovedInSphinx50Warning, stacklevel=2)
self.analyze()
def analyze(self) -> None:
"""Analyze the source code."""
if self._analyzed:
return None
try:
@@ -168,10 +175,10 @@ class ModuleAnalyzer:
def find_attr_docs(self) -> Dict[Tuple[str, str], List[str]]:
"""Find class and module-level attributes and their documentation."""
self.parse()
self.analyze()
return self.attr_docs
def find_tags(self) -> Dict[str, Tuple[str, int, int]]:
"""Find class, function and method definitions and their location."""
self.parse()
self.analyze()
return self.tags

View File

@@ -50,7 +50,7 @@ help:
.PHONY: clean
clean:
rm -rf $(BUILDDIR)/*
rm -rf $(BUILDDIR)
.PHONY: latexpdf
latexpdf:

View File

@@ -141,6 +141,7 @@ def getall(obj: Any) -> Optional[Sequence[str]]:
"""Get __all__ attribute of the module as dict.
Return None if given *obj* does not have __all__.
Raises AttributeError if given *obj* raises an error on accessing __all__.
Raises ValueError if given *obj* have invalid __all__.
"""
__all__ = safe_getattr(obj, '__all__', None)
@@ -153,10 +154,24 @@ def getall(obj: Any) -> Optional[Sequence[str]]:
raise ValueError(__all__)
def getannotations(obj: Any) -> Mapping[str, Any]:
"""Get __annotations__ from given *obj* safely.
Raises AttributeError if given *obj* raises an error on accessing __attribute__.
"""
__annotations__ = safe_getattr(obj, '__annotations__', None)
if isinstance(__annotations__, Mapping):
return __annotations__
else:
return {}
def getslots(obj: Any) -> Optional[Dict]:
"""Get __slots__ attribute of the class as dict.
Return None if gienv *obj* does not have __slots__.
Raises AttributeError if given *obj* raises an error on accessing __slots__.
Raises ValueError if given *obj* have invalid __slots__.
"""
if not inspect.isclass(obj):
raise TypeError
@@ -174,6 +189,16 @@ def getslots(obj: Any) -> Optional[Dict]:
raise ValueError
def isNewType(obj: Any) -> bool:
"""Check the if object is a kind of NewType."""
__module__ = safe_getattr(obj, '__module__', None)
__qualname__ = safe_getattr(obj, '__qualname__', None)
if __module__ == 'typing' and __qualname__ == 'NewType.<locals>.new_type':
return True
else:
return False
def isenumclass(x: Any) -> bool:
"""Check if the object is subclass of enum."""
return inspect.isclass(x) and issubclass(x, enum.Enum)

View File

@@ -85,10 +85,14 @@ def is_system_TypeVar(typ: Any) -> bool:
def restify(cls: Optional["Type"]) -> str:
"""Convert python class to a reST reference."""
from sphinx.util import inspect # lazy loading
if cls is None or cls is NoneType:
return ':obj:`None`'
elif cls is Ellipsis:
return '...'
elif inspect.isNewType(cls):
return ':class:`%s`' % cls.__name__
elif cls.__module__ in ('__builtin__', 'builtins'):
return ':class:`%s`' % cls.__name__
else:
@@ -231,6 +235,8 @@ def _restify_py36(cls: Optional["Type"]) -> str:
def stringify(annotation: Any) -> str:
"""Stringify type annotation object."""
from sphinx.util import inspect # lazy loading
if isinstance(annotation, str):
if annotation.startswith("'") and annotation.endswith("'"):
# might be a double Forward-ref'ed type. Go unquoting.
@@ -239,6 +245,9 @@ def stringify(annotation: Any) -> str:
return annotation
elif isinstance(annotation, TypeVar):
return annotation.__name__
elif inspect.isNewType(annotation):
# Could not get the module where it defiend
return annotation.__name__
elif not annotation:
return repr(annotation)
elif annotation is NoneType:

0
tests/__init__.py Normal file
View File

View File

@@ -2,10 +2,6 @@ class Foo:
__slots__ = ['attr']
class FooSingleString:
__slots__ = 'attr'
class Bar:
__slots__ = {'attr1': 'docstring of attr1',
'attr2': 'docstring of attr2',
@@ -13,3 +9,7 @@ class Bar:
def __init__(self):
self.attr2 = None #: docstring of instance attr2
class Baz:
__slots__ = 'attr'

View File

@@ -1,4 +1,4 @@
from typing import TypeVar
from typing import NewType, TypeVar
#: T1
T1 = TypeVar("T1")
@@ -13,3 +13,13 @@ T4 = TypeVar("T4", covariant=True)
#: T5
T5 = TypeVar("T5", contravariant=True)
#: T6
T6 = NewType("T6", int)
class Class:
# TODO: TypeVar
#: T6
T6 = NewType("T6", int)

View File

@@ -6,7 +6,7 @@ source_suffix = '.txt'
exclude_patterns = ['_build']
doctest_global_setup = '''
from test_ext_doctest import record
from tests.test_ext_doctest import record
record('doctest_global_setup', 'body', True)
'''

View File

@@ -139,7 +139,7 @@ Special directives
.. testcleanup:: *
import test_ext_doctest
from tests import test_ext_doctest
test_ext_doctest.cleanup_call()
non-ASCII result

View File

@@ -16,7 +16,6 @@ from shutil import copyfile
from subprocess import PIPE, CalledProcessError
import pytest
from test_build_html import ENV_WARNINGS
from sphinx.builders.latex import default_latex_documents
from sphinx.config import Config
@@ -25,6 +24,8 @@ from sphinx.testing.util import strip_escseq
from sphinx.util.osutil import cd, ensuredir
from sphinx.writers.latex import LaTeXTranslator
from .test_build_html import ENV_WARNINGS
LATEX_ENGINES = ['pdflatex', 'lualatex', 'xelatex']
DOCCLASSES = ['howto', 'manual']
STYLEFILES = ['article.cls', 'fancyhdr.sty', 'titlesec.sty', 'amsmath.sty',

View File

@@ -15,7 +15,7 @@ import textwrap
import pytest
import requests
from utils import CERT_FILE, http_server, https_server, modify_env
from .utils import CERT_FILE, http_server, https_server, modify_env
@pytest.mark.sphinx('linkcheck', testroot='linkcheck', freshenv=True)

View File

@@ -15,7 +15,6 @@ from subprocess import PIPE, CalledProcessError
from unittest.mock import Mock
import pytest
from test_build_html import ENV_WARNINGS
from sphinx.builders.texinfo import default_texinfo_documents
from sphinx.config import Config
@@ -23,6 +22,8 @@ from sphinx.testing.util import strip_escseq
from sphinx.util.docutils import new_document
from sphinx.writers.texinfo import TexinfoTranslator
from .test_build_html import ENV_WARNINGS
TEXINFO_WARNINGS = ENV_WARNINGS + """\
%(root)s/index.rst:\\d+: WARNING: unknown option: &option
%(root)s/index.rst:\\d+: WARNING: citation not found: missing

View File

@@ -1193,6 +1193,14 @@ def test_slots(app):
' :module: target.slots',
'',
'',
'.. py:class:: Baz()',
' :module: target.slots',
'',
'',
' .. py:attribute:: Baz.attr',
' :module: target.slots',
'',
'',
'.. py:class:: Foo()',
' :module: target.slots',
'',
@@ -1200,14 +1208,6 @@ def test_slots(app):
' .. py:attribute:: Foo.attr',
' :module: target.slots',
'',
'',
'.. py:class:: FooSingleString()',
' :module: target.slots',
'',
'',
' .. py:attribute:: FooSingleString.attr',
' :module: target.slots',
'',
]
@@ -1728,6 +1728,18 @@ def test_autodoc_TypeVar(app):
'.. py:module:: target.typevar',
'',
'',
'.. py:class:: Class()',
' :module: target.typevar',
'',
'',
' .. py:attribute:: Class.T6',
' :module: target.typevar',
'',
' T6',
'',
' alias of :class:`int`',
'',
'',
'.. py:data:: T1',
' :module: target.typevar',
'',
@@ -1755,6 +1767,14 @@ def test_autodoc_TypeVar(app):
' T5',
'',
" alias of TypeVar('T5', contravariant=True)",
'',
'.. py:data:: T6',
' :module: target.typevar',
'',
' T6',
'',
' alias of :class:`int`',
'',
]

View File

@@ -12,7 +12,8 @@
import sys
import pytest
from test_ext_autodoc import do_autodoc
from .test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')
@@ -69,3 +70,18 @@ def test_autoattribute_instance_variable(app):
' attr4',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autoattribute_NewType(app):
actual = do_autodoc(app, 'attribute', 'target.typevar.Class.T6')
assert list(actual) == [
'',
'.. py:attribute:: Class.T6',
' :module: target.typevar',
'',
' T6',
'',
' alias of :class:`int`',
'',
]

View File

@@ -12,7 +12,8 @@
import sys
import pytest
from test_ext_autodoc import do_autodoc
from .test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@@ -12,7 +12,8 @@
import sys
import pytest
from test_ext_autodoc import do_autodoc
from .test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')
@@ -72,3 +73,18 @@ def test_autodata_type_comment(app):
' attr3',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodata_NewType(app):
actual = do_autodoc(app, 'data', 'target.typevar.T6')
assert list(actual) == [
'',
'.. py:data:: T6',
' :module: target.typevar',
'',
' T6',
'',
' alias of :class:`int`',
'',
]

View File

@@ -10,7 +10,8 @@
"""
import pytest
from test_ext_autodoc import do_autodoc
from .test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@@ -12,10 +12,11 @@ import platform
import sys
import pytest
from test_ext_autodoc import do_autodoc
from sphinx.testing import restructuredtext
from .test_ext_autodoc import do_autodoc
IS_PYPY = platform.python_implementation() == 'PyPy'
@@ -777,6 +778,28 @@ def test_autodoc_type_aliases(app):
]
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
@pytest.mark.sphinx('text', testroot='ext-autodoc',
srcdir='autodoc_typehints_description_and_type_aliases',
confoverrides={'autodoc_typehints': "description",
'autodoc_type_aliases': {'myint': 'myint'}})
def test_autodoc_typehints_description_and_type_aliases(app):
(app.srcdir / 'annotations.rst').write_text('.. autofunction:: target.annotations.sum')
app.build()
context = (app.outdir / 'annotations.txt').read_text()
assert ('target.annotations.sum(x, y)\n'
'\n'
' docstring\n'
'\n'
' Parameters:\n'
' * **x** (*myint*) --\n'
'\n'
' * **y** (*myint*) --\n'
'\n'
' Return type:\n'
' myint\n' == context)
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_default_options(app):
# no settings

View File

@@ -9,10 +9,11 @@
"""
import pytest
from test_ext_autodoc import do_autodoc
from sphinx.ext.autodoc import between, cut_lines
from .test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_process_docstring(app):

View File

@@ -9,7 +9,8 @@
"""
import pytest
from test_ext_autodoc import do_autodoc
from .test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@@ -15,14 +15,15 @@ from unittest import mock
import pytest
from docutils import nodes
from test_util_inventory import inventory_v2, inventory_v2_not_having_version
from sphinx import addnodes
from sphinx.ext.intersphinx import (INVENTORY_FILENAME, _get_safe_url, _strip_basic_auth,
fetch_inventory, inspect_main, load_mappings,
missing_reference, normalize_intersphinx_mapping)
from sphinx.ext.intersphinx import setup as intersphinx_setup
from utils import http_server
from .test_util_inventory import inventory_v2, inventory_v2_not_having_version
from .utils import http_server
def fake_node(domain, type, target, content, **attrs):

View File

@@ -129,8 +129,8 @@ def test_signature_partialmethod():
def test_signature_annotations():
from typing_test_data import (Node, f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12,
f13, f14, f15, f16, f17, f18, f19, f20, f21)
from .typing_test_data import (Node, f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12,
f13, f14, f15, f16, f17, f18, f19, f20, f21)
# Class annotations
sig = inspect.signature(f0)
@@ -223,10 +223,10 @@ def test_signature_annotations():
# type hints by string
sig = inspect.signature(Node.children)
assert stringify_signature(sig) == '(self) -> List[typing_test_data.Node]'
assert stringify_signature(sig) == '(self) -> List[tests.typing_test_data.Node]'
sig = inspect.signature(Node.__init__)
assert stringify_signature(sig) == '(self, parent: Optional[typing_test_data.Node]) -> None'
assert stringify_signature(sig) == '(self, parent: Optional[tests.typing_test_data.Node]) -> None'
# show_annotation is False
sig = inspect.signature(f7)

View File

@@ -10,7 +10,8 @@
import sys
from numbers import Integral
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, TypeVar, Union
from typing import (Any, Callable, Dict, Generator, List, NewType, Optional, Tuple, TypeVar,
Union)
import pytest
@@ -26,6 +27,7 @@ class MyClass2(MyClass1):
T = TypeVar('T')
MyInt = NewType('MyInt', int)
class MyList(List[T]):
@@ -53,7 +55,7 @@ def test_restify_type_hints_containers():
assert restify(Tuple[str, str, str]) == ":class:`Tuple`\\ [:class:`str`, :class:`str`, :class:`str`]"
assert restify(Tuple[str, ...]) == ":class:`Tuple`\\ [:class:`str`, ...]"
assert restify(List[Dict[str, Tuple]]) == ":class:`List`\\ [:class:`Dict`\\ [:class:`str`, :class:`Tuple`]]"
assert restify(MyList[Tuple[int, int]]) == ":class:`test_util_typing.MyList`\\ [:class:`Tuple`\\ [:class:`int`, :class:`int`]]"
assert restify(MyList[Tuple[int, int]]) == ":class:`tests.test_util_typing.MyList`\\ [:class:`Tuple`\\ [:class:`int`, :class:`int`]]"
assert restify(Generator[None, None, None]) == ":class:`Generator`\\ [:obj:`None`, :obj:`None`, :obj:`None`]"
@@ -76,10 +78,10 @@ def test_restify_type_hints_Union():
if sys.version_info >= (3, 7):
assert restify(Union[int, Integral]) == ":obj:`Union`\\ [:class:`int`, :class:`numbers.Integral`]"
assert (restify(Union[MyClass1, MyClass2]) ==
":obj:`Union`\\ [:class:`test_util_typing.MyClass1`, :class:`test_util_typing.<MyClass2>`]")
":obj:`Union`\\ [:class:`tests.test_util_typing.MyClass1`, :class:`tests.test_util_typing.<MyClass2>`]")
else:
assert restify(Union[int, Integral]) == ":class:`numbers.Integral`"
assert restify(Union[MyClass1, MyClass2]) == ":class:`test_util_typing.MyClass1`"
assert restify(Union[MyClass1, MyClass2]) == ":class:`tests.test_util_typing.MyClass1`"
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
@@ -88,15 +90,16 @@ def test_restify_type_hints_typevars():
T_co = TypeVar('T_co', covariant=True)
T_contra = TypeVar('T_contra', contravariant=True)
assert restify(T) == ":obj:`test_util_typing.T`"
assert restify(T_co) == ":obj:`test_util_typing.T_co`"
assert restify(T_contra) == ":obj:`test_util_typing.T_contra`"
assert restify(List[T]) == ":class:`List`\\ [:obj:`test_util_typing.T`]"
assert restify(T) == ":obj:`tests.test_util_typing.T`"
assert restify(T_co) == ":obj:`tests.test_util_typing.T_co`"
assert restify(T_contra) == ":obj:`tests.test_util_typing.T_contra`"
assert restify(List[T]) == ":class:`List`\\ [:obj:`tests.test_util_typing.T`]"
assert restify(MyInt) == ":class:`MyInt`"
def test_restify_type_hints_custom_class():
assert restify(MyClass1) == ":class:`test_util_typing.MyClass1`"
assert restify(MyClass2) == ":class:`test_util_typing.<MyClass2>`"
assert restify(MyClass1) == ":class:`tests.test_util_typing.MyClass1`"
assert restify(MyClass2) == ":class:`tests.test_util_typing.<MyClass2>`"
def test_restify_type_hints_alias():
@@ -107,7 +110,7 @@ def test_restify_type_hints_alias():
def test_restify_broken_type_hints():
assert restify(BrokenType) == ':class:`test_util_typing.BrokenType`'
assert restify(BrokenType) == ':class:`tests.test_util_typing.BrokenType`'
def test_stringify():
@@ -127,7 +130,7 @@ def test_stringify_type_hints_containers():
assert stringify(Tuple[str, str, str]) == "Tuple[str, str, str]"
assert stringify(Tuple[str, ...]) == "Tuple[str, ...]"
assert stringify(List[Dict[str, Tuple]]) == "List[Dict[str, Tuple]]"
assert stringify(MyList[Tuple[int, int]]) == "test_util_typing.MyList[Tuple[int, int]]"
assert stringify(MyList[Tuple[int, int]]) == "tests.test_util_typing.MyList[Tuple[int, int]]"
assert stringify(Generator[None, None, None]) == "Generator[None, None, None]"
@@ -164,10 +167,10 @@ def test_stringify_type_hints_Union():
if sys.version_info >= (3, 7):
assert stringify(Union[int, Integral]) == "Union[int, numbers.Integral]"
assert (stringify(Union[MyClass1, MyClass2]) ==
"Union[test_util_typing.MyClass1, test_util_typing.<MyClass2>]")
"Union[tests.test_util_typing.MyClass1, tests.test_util_typing.<MyClass2>]")
else:
assert stringify(Union[int, Integral]) == "numbers.Integral"
assert stringify(Union[MyClass1, MyClass2]) == "test_util_typing.MyClass1"
assert stringify(Union[MyClass1, MyClass2]) == "tests.test_util_typing.MyClass1"
def test_stringify_type_hints_typevars():
@@ -179,11 +182,12 @@ def test_stringify_type_hints_typevars():
assert stringify(T_co) == "T_co"
assert stringify(T_contra) == "T_contra"
assert stringify(List[T]) == "List[T]"
assert stringify(MyInt) == "MyInt"
def test_stringify_type_hints_custom_class():
assert stringify(MyClass1) == "test_util_typing.MyClass1"
assert stringify(MyClass2) == "test_util_typing.<MyClass2>"
assert stringify(MyClass1) == "tests.test_util_typing.MyClass1"
assert stringify(MyClass2) == "tests.test_util_typing.<MyClass2>"
def test_stringify_type_hints_alias():
@@ -194,4 +198,4 @@ def test_stringify_type_hints_alias():
def test_stringify_broken_type_hints():
assert stringify(BrokenType) == 'test_util_typing.BrokenType'
assert stringify(BrokenType) == 'tests.test_util_typing.BrokenType'