mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '3.x'
This commit is contained in:
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -41,6 +41,8 @@ jobs:
|
||||
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
|
||||
|
27
CHANGES
27
CHANGES
@@ -68,6 +68,8 @@ Deprecated
|
||||
----------
|
||||
|
||||
* The ``follow_wrapped`` argument of ``sphinx.util.inspect.signature()``
|
||||
* ``sphinx.ext.autodoc.DataDeclarationDocumenter``
|
||||
* ``sphinx.util.requests.is_ssl_error()``
|
||||
|
||||
Features added
|
||||
--------------
|
||||
@@ -80,6 +82,8 @@ Features added
|
||||
* autodoc: Add ``Documenter.config`` as a shortcut to access the config object
|
||||
* autodoc: Add Optional[t] to annotation of function and method if a default
|
||||
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
|
||||
* #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
|
||||
@@ -91,11 +95,18 @@ Bugs fixed
|
||||
* #4606: autodoc: the location of the warning is incorrect for inherited method
|
||||
* #8105: autodoc: the signature of class constructor is incorrect if the class
|
||||
is decorated
|
||||
* #8434: autodoc: :confval:`autodoc_type_aliases` does not effect to variables
|
||||
and attributes
|
||||
* #8443: autodoc: autodata directive can't create document for PEP-526 based
|
||||
type annotated variables
|
||||
* #8443: autodoc: autoattribute directive can't create document for PEP-526
|
||||
based uninitalized variables
|
||||
* #8419: html search: Do not load ``language_data.js`` in non-search pages
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.3.1 (in development)
|
||||
Release 3.3.2 (in development)
|
||||
==============================
|
||||
|
||||
Dependencies
|
||||
@@ -113,13 +124,21 @@ Features added
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.3.1 (released Nov 12, 2020)
|
||||
=====================================
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #8372: autodoc: autoclass directive became slower than Sphinx-3.2
|
||||
* #7727: autosummary: raise PycodeError when documenting python package
|
||||
without __init__.py
|
||||
* #8350: autosummary: autosummary_mock_imports causes slow down builds
|
||||
* #8364: C, properly initialize attributes in empty symbols.
|
||||
|
||||
Testing
|
||||
--------
|
||||
* #8399: i18n: Put system locale path after the paths specified by configuration
|
||||
|
||||
Release 3.3.0 (released Nov 02, 2020)
|
||||
=====================================
|
||||
|
@@ -61,6 +61,16 @@ The following is a list of deprecated interfaces.
|
||||
- 5.0
|
||||
- N/A
|
||||
|
||||
* - ``sphinx.ext.autodoc.DataDeclarationDocumenter``
|
||||
- 3.4
|
||||
- 5.0
|
||||
- ``sphinx.ext.autodoc.DataDocumenter``
|
||||
|
||||
* - ``sphinx.util.requests.is_ssl_error()``
|
||||
- 3.4
|
||||
- 5.0
|
||||
- N/A
|
||||
|
||||
* - ``sphinx.builders.latex.LaTeXBuilder.usepackages``
|
||||
- 3.3
|
||||
- 5.0
|
||||
|
@@ -326,6 +326,15 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
|
||||
By default, without ``annotation`` option, Sphinx tries to obtain the value of
|
||||
the variable and print it after the name.
|
||||
|
||||
The ``no-value`` option can be used instead of a blank ``annotation`` to show the
|
||||
type hint but not the value::
|
||||
|
||||
.. autodata:: CD_DRIVE
|
||||
:no-value:
|
||||
|
||||
If both the ``annotation`` and ``no-value`` options are used, ``no-value`` has no
|
||||
effect.
|
||||
|
||||
For module data members and class attributes, documentation can either be put
|
||||
into a comment with special formatting (using a ``#:`` to start the comment
|
||||
instead of just ``#``), or in a docstring *after* the definition. Comments
|
||||
@@ -365,6 +374,9 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
|
||||
option.
|
||||
.. versionchanged:: 2.0
|
||||
:rst:dir:`autodecorator` added.
|
||||
.. versionchanged:: 3.4
|
||||
:rst:dir:`autodata` and :rst:dir:`autoattribute` now have a ``no-value``
|
||||
option.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@@ -29,9 +29,11 @@ directory = sphinx/locale/
|
||||
[flake8]
|
||||
max-line-length = 95
|
||||
ignore = E116,E241,E251,E741,W504,I101
|
||||
exclude = .git,.tox,.venv,tests/*,build/*,doc/_build/*,sphinx/search/*,doc/usage/extensions/example*.py
|
||||
exclude = .git,.tox,.venv,tests/roots/*,build/*,doc/_build/*,sphinx/search/*,doc/usage/extensions/example*.py
|
||||
application-import-names = sphinx
|
||||
import-order-style = smarkets
|
||||
per-file-ignores =
|
||||
tests/*: E501
|
||||
|
||||
[flake8:local-plugins]
|
||||
extension =
|
||||
|
@@ -130,7 +130,7 @@ class Sphinx:
|
||||
:ivar outdir: Directory for storing build documents.
|
||||
"""
|
||||
|
||||
def __init__(self, srcdir: str, confdir: str, outdir: str, doctreedir: str,
|
||||
def __init__(self, srcdir: str, confdir: Optional[str], outdir: str, doctreedir: str,
|
||||
buildername: str, confoverrides: Dict = None,
|
||||
status: IO = sys.stdout, warning: IO = sys.stderr,
|
||||
freshenv: bool = False, warningiserror: bool = False, tags: List[str] = None,
|
||||
@@ -289,8 +289,8 @@ class Sphinx:
|
||||
if catalog.domain == 'sphinx' and catalog.is_outdated():
|
||||
catalog.write_mo(self.config.language)
|
||||
|
||||
locale_dirs = [None] # type: List[Optional[str]]
|
||||
locale_dirs += list(repo.locale_dirs)
|
||||
locale_dirs = list(repo.locale_dirs) # type: List[Optional[str]]
|
||||
locale_dirs += [None]
|
||||
locale_dirs += [path.join(package_dir, 'locale')]
|
||||
|
||||
self.translator, has_translation = locale.init(locale_dirs, self.config.language)
|
||||
|
@@ -295,7 +295,6 @@ class StandaloneHTMLBuilder(Builder):
|
||||
self.add_js_file('jquery.js')
|
||||
self.add_js_file('underscore.js')
|
||||
self.add_js_file('doctools.js')
|
||||
self.add_js_file('language_data.js')
|
||||
|
||||
for filename, attrs in self.app.registry.js_files:
|
||||
self.add_js_file(filename, **attrs)
|
||||
|
@@ -28,7 +28,6 @@ from sphinx.locale import __
|
||||
from sphinx.util import encode_uri, logging, requests
|
||||
from sphinx.util.console import darkgray, darkgreen, purple, red, turquoise # type: ignore
|
||||
from sphinx.util.nodes import get_node_line
|
||||
from sphinx.util.requests import is_ssl_error
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -108,9 +107,7 @@ class CheckExternalLinksBuilder(Builder):
|
||||
self.workers.append(thread)
|
||||
|
||||
def check_thread(self) -> None:
|
||||
kwargs = {
|
||||
'allow_redirects': True,
|
||||
} # type: Dict
|
||||
kwargs = {}
|
||||
if self.app.config.linkcheck_timeout:
|
||||
kwargs['timeout'] = self.app.config.linkcheck_timeout
|
||||
|
||||
@@ -171,8 +168,9 @@ class CheckExternalLinksBuilder(Builder):
|
||||
try:
|
||||
# try a HEAD request first, which should be easier on
|
||||
# the server and the network
|
||||
response = requests.head(req_url, config=self.app.config,
|
||||
auth=auth_info, **kwargs)
|
||||
response = requests.head(req_url, allow_redirects=True,
|
||||
config=self.app.config, auth=auth_info,
|
||||
**kwargs)
|
||||
response.raise_for_status()
|
||||
except HTTPError:
|
||||
# retry with GET request if that fails, some servers
|
||||
@@ -190,10 +188,7 @@ class CheckExternalLinksBuilder(Builder):
|
||||
else:
|
||||
return 'broken', str(err), 0
|
||||
except Exception as err:
|
||||
if is_ssl_error(err):
|
||||
return 'ignored', str(err), 0
|
||||
else:
|
||||
return 'broken', str(err), 0
|
||||
return 'broken', str(err), 0
|
||||
if response.url.rstrip('/') == req_url.rstrip('/'):
|
||||
return 'working', '', 0
|
||||
else:
|
||||
|
@@ -16,7 +16,7 @@ import warnings
|
||||
from inspect import Parameter, Signature
|
||||
from types import ModuleType
|
||||
from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Sequence,
|
||||
Set, Tuple, Type, TypeVar, Union, get_type_hints)
|
||||
Set, Tuple, Type, TypeVar, Union)
|
||||
|
||||
from docutils.statemachine import StringList
|
||||
|
||||
@@ -33,7 +33,7 @@ from sphinx.util import inspect, logging
|
||||
from sphinx.util.docstrings import extract_metadata, prepare_docstring
|
||||
from sphinx.util.inspect import (evaluate_signature, getdoc, object_description, safe_getattr,
|
||||
stringify_signature)
|
||||
from sphinx.util.typing import restify
|
||||
from sphinx.util.typing import get_type_hints, restify
|
||||
from sphinx.util.typing import stringify as stringify_typehint
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -954,7 +954,7 @@ class ModuleDocumenter(Documenter):
|
||||
def __init__(self, *args: Any) -> None:
|
||||
super().__init__(*args)
|
||||
merge_members_option(self.options)
|
||||
self.__all__ = None
|
||||
self.__all__ = None # type: Optional[Sequence[str]]
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
||||
@@ -978,26 +978,16 @@ class ModuleDocumenter(Documenter):
|
||||
return ret
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
def is_valid_module_all(__all__: Any) -> bool:
|
||||
"""Check the given *__all__* is valid for a module."""
|
||||
if (isinstance(__all__, (list, tuple)) and
|
||||
all(isinstance(e, str) for e in __all__)):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
ret = super().import_object(raiseerror)
|
||||
|
||||
if not self.options.ignore_module_all:
|
||||
__all__ = getattr(self.object, '__all__', None)
|
||||
if is_valid_module_all(__all__):
|
||||
# valid __all__ found. copy it to self.__all__
|
||||
self.__all__ = __all__
|
||||
elif __all__:
|
||||
# invalid __all__ found.
|
||||
logger.warning(__('__all__ should be a list of strings, not %r '
|
||||
'(in module %s) -- ignoring __all__') %
|
||||
(__all__, self.fullname), type='autodoc')
|
||||
try:
|
||||
if not self.options.ignore_module_all:
|
||||
self.__all__ = inspect.getall(self.object)
|
||||
except ValueError as exc:
|
||||
# invalid __all__ found.
|
||||
logger.warning(__('__all__ should be a list of strings, not %r '
|
||||
'(in module %s) -- ignoring __all__') %
|
||||
(exc.args[0], self.fullname), type='autodoc')
|
||||
|
||||
return ret
|
||||
|
||||
@@ -1670,28 +1660,41 @@ class DataDocumenter(ModuleLevelDocumenter):
|
||||
priority = -10
|
||||
option_spec = dict(ModuleLevelDocumenter.option_spec)
|
||||
option_spec["annotation"] = annotation_option
|
||||
option_spec["no-value"] = bool_option
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
||||
) -> bool:
|
||||
return isinstance(parent, ModuleDocumenter) and isattr
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
try:
|
||||
return super().import_object(raiseerror=True)
|
||||
except ImportError as exc:
|
||||
# annotation only instance variable (PEP-526)
|
||||
try:
|
||||
self.parent = importlib.import_module(self.modname)
|
||||
annotations = get_type_hints(self.parent, None,
|
||||
self.config.autodoc_type_aliases)
|
||||
if self.objpath[-1] in annotations:
|
||||
self.object = UNINITIALIZED_ATTR
|
||||
return True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if raiseerror:
|
||||
raise
|
||||
else:
|
||||
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
|
||||
self.env.note_reread()
|
||||
return False
|
||||
|
||||
def add_directive_header(self, sig: str) -> None:
|
||||
super().add_directive_header(sig)
|
||||
sourcename = self.get_sourcename()
|
||||
if not self.options.annotation:
|
||||
# obtain annotation for this data
|
||||
try:
|
||||
annotations = get_type_hints(self.parent)
|
||||
except NameError:
|
||||
# Failed to evaluate ForwardRef (maybe TYPE_CHECKING)
|
||||
annotations = safe_getattr(self.parent, '__annotations__', {})
|
||||
except TypeError:
|
||||
annotations = {}
|
||||
except KeyError:
|
||||
# a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084)
|
||||
annotations = {}
|
||||
|
||||
annotations = get_type_hints(self.parent, None, self.config.autodoc_type_aliases)
|
||||
if self.objpath[-1] in annotations:
|
||||
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
|
||||
self.add_line(' :type: ' + objrepr, sourcename)
|
||||
@@ -1702,7 +1705,7 @@ class DataDocumenter(ModuleLevelDocumenter):
|
||||
sourcename)
|
||||
|
||||
try:
|
||||
if self.object is UNINITIALIZED_ATTR:
|
||||
if self.object is UNINITIALIZED_ATTR or self.options.no_value:
|
||||
pass
|
||||
else:
|
||||
objrepr = object_description(self.object)
|
||||
@@ -1722,6 +1725,13 @@ 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:
|
||||
if self.object is UNINITIALIZED_ATTR:
|
||||
# suppress docstring of the value
|
||||
super().add_content(more_content, no_docstring=True)
|
||||
else:
|
||||
super().add_content(more_content, no_docstring=no_docstring)
|
||||
|
||||
|
||||
class DataDeclarationDocumenter(DataDocumenter):
|
||||
"""
|
||||
@@ -1735,30 +1745,10 @@ class DataDeclarationDocumenter(DataDocumenter):
|
||||
# must be higher than AttributeDocumenter
|
||||
priority = 11
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
||||
) -> bool:
|
||||
"""This documents only INSTANCEATTR members."""
|
||||
return (isinstance(parent, ModuleDocumenter) and
|
||||
isattr and
|
||||
member is INSTANCEATTR)
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
"""Never import anything."""
|
||||
# disguise as a data
|
||||
self.objtype = 'data'
|
||||
self.object = UNINITIALIZED_ATTR
|
||||
try:
|
||||
# import module to obtain type annotation
|
||||
self.parent = importlib.import_module(self.modname)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
|
||||
"""Never try to get a docstring from the object."""
|
||||
super().add_content(more_content, no_docstring=True)
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
warnings.warn("%s is deprecated." % self.__class__.__name__,
|
||||
RemovedInSphinx50Warning, stacklevel=2)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class GenericAliasDocumenter(DataDocumenter):
|
||||
@@ -1998,6 +1988,7 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
member_order = 60
|
||||
option_spec = dict(ModuleLevelDocumenter.option_spec)
|
||||
option_spec["annotation"] = annotation_option
|
||||
option_spec["no-value"] = bool_option
|
||||
|
||||
# must be higher than the MethodDocumenter, else it will recognize
|
||||
# some non-data descriptors as methods
|
||||
@@ -2024,6 +2015,22 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
|
||||
def isinstanceattribute(self) -> bool:
|
||||
"""Check the subject is an instance attribute."""
|
||||
# uninitialized instance variable (PEP-526)
|
||||
with mock(self.config.autodoc_mock_imports):
|
||||
try:
|
||||
ret = import_object(self.modname, self.objpath[:-1], 'class',
|
||||
attrgetter=self.get_attr,
|
||||
warningiserror=self.config.autodoc_warningiserror)
|
||||
self.parent = ret[3]
|
||||
annotations = get_type_hints(self.parent, None,
|
||||
self.config.autodoc_type_aliases)
|
||||
if self.objpath[-1] in annotations:
|
||||
self.object = UNINITIALIZED_ATTR
|
||||
return True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# An instance variable defined inside __init__().
|
||||
try:
|
||||
analyzer = ModuleAnalyzer.for_module(self.modname)
|
||||
attr_docs = analyzer.find_attr_docs()
|
||||
@@ -2034,7 +2041,9 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
|
||||
return False
|
||||
except PycodeError:
|
||||
return False
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
try:
|
||||
@@ -2069,17 +2078,7 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
sourcename = self.get_sourcename()
|
||||
if not self.options.annotation:
|
||||
# obtain type annotation for this attribute
|
||||
try:
|
||||
annotations = get_type_hints(self.parent)
|
||||
except NameError:
|
||||
# Failed to evaluate ForwardRef (maybe TYPE_CHECKING)
|
||||
annotations = safe_getattr(self.parent, '__annotations__', {})
|
||||
except TypeError:
|
||||
annotations = {}
|
||||
except KeyError:
|
||||
# a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084)
|
||||
annotations = {}
|
||||
|
||||
annotations = get_type_hints(self.parent, None, self.config.autodoc_type_aliases)
|
||||
if self.objpath[-1] in annotations:
|
||||
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
|
||||
self.add_line(' :type: ' + objrepr, sourcename)
|
||||
@@ -2092,7 +2091,7 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
# data descriptors do not have useful values
|
||||
if not self._datadescriptor:
|
||||
try:
|
||||
if self.object is INSTANCEATTR:
|
||||
if self.object is INSTANCEATTR or self.options.no_value:
|
||||
pass
|
||||
else:
|
||||
objrepr = object_description(self.object)
|
||||
@@ -2244,8 +2243,8 @@ class SlotsAttributeDocumenter(AttributeDocumenter):
|
||||
% self.__class__.__name__,
|
||||
RemovedInSphinx50Warning, stacklevel=2)
|
||||
name = self.objpath[-1]
|
||||
__slots__ = safe_getattr(self.parent, '__slots__', [])
|
||||
if isinstance(__slots__, dict) and isinstance(__slots__.get(name), str):
|
||||
__slots__ = inspect.getslots(self.parent)
|
||||
if __slots__ and isinstance(__slots__.get(name, None), str):
|
||||
docstring = prepare_docstring(__slots__[name])
|
||||
return [docstring]
|
||||
else:
|
||||
@@ -2280,7 +2279,6 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.add_autodocumenter(ClassDocumenter)
|
||||
app.add_autodocumenter(ExceptionDocumenter)
|
||||
app.add_autodocumenter(DataDocumenter)
|
||||
app.add_autodocumenter(DataDeclarationDocumenter)
|
||||
app.add_autodocumenter(GenericAliasDocumenter)
|
||||
app.add_autodocumenter(TypeVarDocumenter)
|
||||
app.add_autodocumenter(FunctionDocumenter)
|
||||
|
@@ -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 isclass, isenumclass, safe_getattr
|
||||
from sphinx.util.inspect import getslots, isclass, isenumclass, safe_getattr
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
@@ -203,14 +203,15 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
||||
members[name] = Attribute(name, True, value)
|
||||
|
||||
# members in __slots__
|
||||
if isclass(subject) and getattr(subject, '__slots__', None) is not None:
|
||||
from sphinx.ext.autodoc import SLOTSATTR
|
||||
try:
|
||||
__slots__ = getslots(subject)
|
||||
if __slots__:
|
||||
from sphinx.ext.autodoc import SLOTSATTR
|
||||
|
||||
slots = subject.__slots__
|
||||
if isinstance(slots, str):
|
||||
slots = [slots]
|
||||
for name in slots:
|
||||
members[name] = Attribute(name, True, SLOTSATTR)
|
||||
for name in __slots__:
|
||||
members[name] = Attribute(name, True, SLOTSATTR)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
# other members
|
||||
for name in dir(subject):
|
||||
|
@@ -81,8 +81,7 @@ class AutosummaryEntry(NamedTuple):
|
||||
|
||||
|
||||
def setup_documenters(app: Any) -> None:
|
||||
from sphinx.ext.autodoc import (AttributeDocumenter, ClassDocumenter,
|
||||
DataDeclarationDocumenter, DataDocumenter,
|
||||
from sphinx.ext.autodoc import (AttributeDocumenter, ClassDocumenter, DataDocumenter,
|
||||
DecoratorDocumenter, ExceptionDocumenter,
|
||||
FunctionDocumenter, GenericAliasDocumenter,
|
||||
InstanceAttributeDocumenter, MethodDocumenter,
|
||||
@@ -92,8 +91,7 @@ def setup_documenters(app: Any) -> None:
|
||||
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
|
||||
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
|
||||
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
|
||||
SlotsAttributeDocumenter, DataDeclarationDocumenter, GenericAliasDocumenter,
|
||||
SingledispatchFunctionDocumenter,
|
||||
SlotsAttributeDocumenter, GenericAliasDocumenter, SingledispatchFunctionDocumenter,
|
||||
] # type: List[Type[Documenter]]
|
||||
for documenter in documenters:
|
||||
app.registry.add_documenter(documenter.objtype, documenter)
|
||||
|
@@ -12,6 +12,7 @@
|
||||
{%- block scripts %}
|
||||
{{ super() }}
|
||||
<script src="{{ pathto('_static/searchtools.js', 1) }}"></script>
|
||||
<script src="{{ pathto('_static/language_data.js', 1) }}"></script>
|
||||
{%- endblock %}
|
||||
{% block extrahead %}
|
||||
<script src="{{ pathto('searchindex.js', 1) }}" defer></script>
|
||||
|
@@ -20,7 +20,7 @@ import warnings
|
||||
from functools import partial, partialmethod
|
||||
from inspect import Parameter, isclass, ismethod, ismethoddescriptor, ismodule # NOQA
|
||||
from io import StringIO
|
||||
from typing import Any, Callable, Dict, cast
|
||||
from typing import Any, Callable, Dict, Optional, Sequence, cast
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx50Warning
|
||||
from sphinx.pycode.ast import ast # for py36-37
|
||||
@@ -107,7 +107,11 @@ def getargspec(func: Callable) -> Any:
|
||||
def unwrap(obj: Any) -> Any:
|
||||
"""Get an original object from wrapped object (wrapped functions)."""
|
||||
try:
|
||||
return inspect.unwrap(obj)
|
||||
if hasattr(obj, '__sphinx_mock__'):
|
||||
# Skip unwrapping mock object to avoid RecursionError
|
||||
return obj
|
||||
else:
|
||||
return inspect.unwrap(obj)
|
||||
except ValueError:
|
||||
# might be a mock object
|
||||
return obj
|
||||
@@ -133,6 +137,43 @@ def unwrap_all(obj: Any, *, stop: Callable = None) -> Any:
|
||||
return obj
|
||||
|
||||
|
||||
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 ValueError if given *obj* have invalid __all__.
|
||||
"""
|
||||
__all__ = safe_getattr(obj, '__all__', None)
|
||||
if __all__ is None:
|
||||
return None
|
||||
else:
|
||||
if (isinstance(__all__, (list, tuple)) and all(isinstance(e, str) for e in __all__)):
|
||||
return __all__
|
||||
else:
|
||||
raise ValueError(__all__)
|
||||
|
||||
|
||||
def getslots(obj: Any) -> Optional[Dict]:
|
||||
"""Get __slots__ attribute of the class as dict.
|
||||
|
||||
Return None if gienv *obj* does not have __slots__.
|
||||
"""
|
||||
if not inspect.isclass(obj):
|
||||
raise TypeError
|
||||
|
||||
__slots__ = safe_getattr(obj, '__slots__', None)
|
||||
if __slots__ is None:
|
||||
return None
|
||||
elif isinstance(__slots__, dict):
|
||||
return __slots__
|
||||
elif isinstance(__slots__, str):
|
||||
return {__slots__: None}
|
||||
elif isinstance(__slots__, (list, tuple)):
|
||||
return {e: None for e in __slots__}
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
|
||||
def isenumclass(x: Any) -> bool:
|
||||
"""Check if the object is subclass of enum."""
|
||||
return inspect.isclass(x) and issubclass(x, enum.Enum)
|
||||
|
@@ -18,6 +18,7 @@ import requests
|
||||
|
||||
import sphinx
|
||||
from sphinx.config import Config
|
||||
from sphinx.deprecation import RemovedInSphinx50Warning
|
||||
|
||||
try:
|
||||
from requests.packages.urllib3.exceptions import SSLError
|
||||
@@ -43,6 +44,10 @@ useragent_header = [('User-Agent',
|
||||
|
||||
def is_ssl_error(exc: Exception) -> bool:
|
||||
"""Check an exception is SSLError."""
|
||||
warnings.warn(
|
||||
"is_ssl_error() is outdated and likely returns incorrect results "
|
||||
"for modern versions of Requests.",
|
||||
RemovedInSphinx50Warning)
|
||||
if isinstance(exc, SSLError):
|
||||
return True
|
||||
else:
|
||||
|
@@ -57,6 +57,29 @@ TitleGetter = Callable[[nodes.Node], str]
|
||||
Inventory = Dict[str, Dict[str, Tuple[str, str, str, str]]]
|
||||
|
||||
|
||||
def get_type_hints(obj: Any, globalns: Dict = None, localns: Dict = None) -> Dict[str, Any]:
|
||||
"""Return a dictionary containing type hints for a function, method, module or class object.
|
||||
|
||||
This is a simple wrapper of `typing.get_type_hints()` that does not raise an error on
|
||||
runtime.
|
||||
"""
|
||||
from sphinx.util.inspect import safe_getattr # lazy loading
|
||||
|
||||
try:
|
||||
return typing.get_type_hints(obj, None, localns)
|
||||
except NameError:
|
||||
# Failed to evaluate ForwardRef (maybe TYPE_CHECKING)
|
||||
return safe_getattr(obj, '__annotations__', {})
|
||||
except TypeError:
|
||||
return {}
|
||||
except KeyError:
|
||||
# a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084)
|
||||
return {}
|
||||
except AttributeError:
|
||||
# AttributeError is raised on 3.5.2 (fixed by 3.5.3)
|
||||
return {}
|
||||
|
||||
|
||||
def is_system_TypeVar(typ: Any) -> bool:
|
||||
"""Check *typ* is system defined TypeVar."""
|
||||
modname = getattr(typ, '__module__', '')
|
||||
|
50
tests/certs/cert.pem
Normal file
50
tests/certs/cert.pem
Normal file
@@ -0,0 +1,50 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC9fzHGBPNaZNcN
|
||||
nL/1nvO2xJR/E64vFua3QfPQQ5HpigjrK/HRUlRGztRKJ+CEjCXNYNfQ4dUcV45o
|
||||
k5uPH3U1CkAw2d/We+kZnAHkNuw4mRC0ohdzpUByyDOA5WtUWPn9SwhXCVz6fM7e
|
||||
I52auvzpUE6soVDM3nucnqZDJ3Ua9KgB02FrqX13S76Uq+uf8Q2hpTruO/nBzB4p
|
||||
6xFwJJ1taXEEWi8swg6HO8/+0x0AeripV6JieNUptEFuV9kLvRz9qGg0CO2f7AdI
|
||||
jNeFDGrgO7qJ+VxXV9Gnbi6ph4vsUwtJZB3phRGGomdgiRd6PSma81nvTe1z69x/
|
||||
g+8P091pAgMBAAECggEAIrTABfd0JpMffAPAeJjjJA8+70NIfKFiIiA3Kmalu7Mn
|
||||
TQMgZ+j/PHS3FtnU2hHc/o+FF2G1KVqz311heUYWrl8xQIE26M6K88DJ6+VPQFJw
|
||||
Z9TkHK8gbaVTIYFjNfCR4J00atRxLgNb0/2L6QHkPksSDbYB2XPKCfZYlyYL4aKq
|
||||
dePghFu9ePXhUXooPCqke+kP0b8OmHzPlmJpxbeb8ujiox2+4wYjN8lWPz8xHv8i
|
||||
IM7V5hAbPIaQfu/joKrRKk+Kk8UqGurkKQ75KLLL+1oaJO/GLTQ4bk5tpRgfWPda
|
||||
aEBzSPrnqame2CKUWtBughuRWSxdTIMvdXIC/ym1gQKBgQDx6Nyio/L6I5CdlXwC
|
||||
HAzBCy1mnj70Kj97tQc+A/0z8dD7fCSE/oo8IiEKixcjnaSxHk8VjINF/w17n63W
|
||||
8neE7pVsuDwxfhiQ9ZRI1WpV0LsFEoTrEWG7Ax8UzbHXCQbNJ9SI0HJRo9UN0f/Z
|
||||
t+ZT+HNUzdcpCwTvdRVDisbXcQKBgQDIiMz58GFEwdGPXJKEhSyQ3kSQBjeqo0Vl
|
||||
wMDuDvFEckHl/p1RnDo0lzaq6FivOX84ymvGNdQW14TnQp3A/mkQ5o6k/e1pfAA6
|
||||
X0Y6tBH/QppVo5sFvOufyn02k48k5pFAjLHH9L9i0dyWqq4V6PgA2uk4qilFxEg/
|
||||
CJEVfq4ZeQKBgQCZPHKWq9f8T48J42kcRPxnRFdMC63BKQnxqOifhhNcVi+VPjw7
|
||||
6qlSEiRv80+DBhcPAy4BbnKxYjD+QFX0NL80+5S3u7SVfVS+bnGx+U5UcdYmDmcY
|
||||
KHiJ6B5GJU4j8tnWFwbwa2ofAPKywHWbSnyicF1OON20aACGVtpTYJM4YQKBgBW4
|
||||
09NDGZY0FHoeAfT+4/vxR6X+NmtyciL6hSuETNgoNEEwmmPrs1ZdBtvufSTF6qUB
|
||||
MDlxPT8YK1pNmf78z+63ur3ej6f8eZ3ZEidruANZeJRMO4+cjj1p1rRhuYC6xQMj
|
||||
+mH5ff27U9SyOlc/PBYDoH212PCouVaym9yjM0KpAoGBALr583slY55ESOthLrfX
|
||||
1ecoET5xxRm431XbZMnxu0uUvHWNfqoojtmD7laclb9HwkpShPB6PT1egBIvDWWM
|
||||
bVUuXzJ8gP0tIG3dHgiiUlld3ahOiaMYSU77uLFBRWv5sQqfewLuFvlzHn/2ZSt7
|
||||
TcipT4f67b18W8iuLJELEs57
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDuTCCAqGgAwIBAgIUUNvkPwe0W8C2I0+KnLpMaQ+S+vowDQYJKoZIhvcNAQEL
|
||||
BQAwYTELMAkGA1UEBhMCRlIxETAPBgNVBAgMCEJyZXRhZ25lMQ8wDQYDVQQHDAZS
|
||||
ZW5uZXMxGjAYBgNVBAoMEVNwaGlueCB0ZXN0IHN1aXRlMRIwEAYDVQQDDAlsb2Nh
|
||||
bGhvc3QwHhcNMjAxMTE1MTcyNDExWhcNMzAxMTEzMTcyNDExWjBhMQswCQYDVQQG
|
||||
EwJGUjERMA8GA1UECAwIQnJldGFnbmUxDzANBgNVBAcMBlJlbm5lczEaMBgGA1UE
|
||||
CgwRU3BoaW54IHRlc3Qgc3VpdGUxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJ
|
||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAL1/McYE81pk1w2cv/We87bElH8Tri8W
|
||||
5rdB89BDkemKCOsr8dFSVEbO1Eon4ISMJc1g19Dh1RxXjmiTm48fdTUKQDDZ39Z7
|
||||
6RmcAeQ27DiZELSiF3OlQHLIM4Dla1RY+f1LCFcJXPp8zt4jnZq6/OlQTqyhUMze
|
||||
e5yepkMndRr0qAHTYWupfXdLvpSr65/xDaGlOu47+cHMHinrEXAknW1pcQRaLyzC
|
||||
Doc7z/7THQB6uKlXomJ41Sm0QW5X2Qu9HP2oaDQI7Z/sB0iM14UMauA7uon5XFdX
|
||||
0aduLqmHi+xTC0lkHemFEYaiZ2CJF3o9KZrzWe9N7XPr3H+D7w/T3WkCAwEAAaNp
|
||||
MGcwHQYDVR0OBBYEFN1iHZj88N6eI2FlRzza52xzOU5EMB8GA1UdIwQYMBaAFN1i
|
||||
HZj88N6eI2FlRzza52xzOU5EMA8GA1UdEwEB/wQFMAMBAf8wFAYDVR0RBA0wC4IJ
|
||||
bG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQBVUZm1iw7N7uZu/SF3hailxS+1
|
||||
3KChItWu3ZOIjlmDIkaJ9kWqP2ficUg3tBUx6/UOjHQAwRC4rj87BoSV2mEy+0OX
|
||||
fyy+ER/BeHYly5v+hpjVojVKeqysk5CKttZM+cOibT2SzLLYf0InNqZRQRJco+nL
|
||||
QNR0hVo/Lz6Mf1gF2ywf9bXSF3+XECU4K6sVm4QpFbJNm+fHqJBuh1LXHRrcTAsP
|
||||
LM6PBnd3P5QTcr/G0s/tYMPmero9YHZUO8FMvMVoI2K8k6/duG/EbBaNzriRI1OM
|
||||
PpZGCWxbJfyApnzc5lGAG4zJnV/wpOyNhKJuW9N1fr2oEwPpJlS3VzrgeKcY
|
||||
-----END CERTIFICATE-----
|
@@ -4,6 +4,9 @@ from typing import overload
|
||||
|
||||
myint = int
|
||||
|
||||
#: docstring
|
||||
variable: myint
|
||||
|
||||
|
||||
def sum(x: myint, y: myint) -> myint:
|
||||
"""docstring"""
|
||||
@@ -23,3 +26,10 @@ def mult(x: float, y: float) -> float:
|
||||
def mult(x, y):
|
||||
"""docstring"""
|
||||
return x, y
|
||||
|
||||
|
||||
class Foo:
|
||||
"""docstring"""
|
||||
|
||||
#: docstring
|
||||
attr: myint
|
||||
|
2
tests/roots/test-linkcheck-localserver-anchor/conf.py
Normal file
2
tests/roots/test-linkcheck-localserver-anchor/conf.py
Normal file
@@ -0,0 +1,2 @@
|
||||
exclude_patterns = ['_build']
|
||||
linkcheck_anchors = True
|
1
tests/roots/test-linkcheck-localserver-anchor/index.rst
Normal file
1
tests/roots/test-linkcheck-localserver-anchor/index.rst
Normal file
@@ -0,0 +1 @@
|
||||
`local server <http://localhost:7777/#anchor>`_
|
1
tests/roots/test-linkcheck-localserver-https/conf.py
Normal file
1
tests/roots/test-linkcheck-localserver-https/conf.py
Normal file
@@ -0,0 +1 @@
|
||||
exclude_patterns = ['_build']
|
1
tests/roots/test-linkcheck-localserver-https/index.rst
Normal file
1
tests/roots/test-linkcheck-localserver-https/index.rst
Normal file
@@ -0,0 +1 @@
|
||||
`HTTPS server <https://localhost:7777/>`_
|
@@ -1,2 +1 @@
|
||||
exclude_patterns = ['_build']
|
||||
linkcheck_anchors = True
|
||||
|
@@ -1 +1 @@
|
||||
`local server <http://localhost:7777/#anchor>`_
|
||||
`local server <http://localhost:7777/>`_
|
||||
|
@@ -20,7 +20,7 @@ from test_build_html import ENV_WARNINGS
|
||||
|
||||
from sphinx.builders.latex import default_latex_documents
|
||||
from sphinx.config import Config
|
||||
from sphinx.errors import SphinxError, ThemeError
|
||||
from sphinx.errors import SphinxError
|
||||
from sphinx.testing.util import strip_escseq
|
||||
from sphinx.util.osutil import cd, ensuredir
|
||||
from sphinx.writers.latex import LaTeXTranslator
|
||||
@@ -762,7 +762,7 @@ def test_reference_in_caption_and_codeblock_in_footnote(app, status, warning):
|
||||
assert ('\\caption{This is the figure caption with a footnote to '
|
||||
'\\sphinxfootnotemark[7].}\\label{\\detokenize{index:id29}}\\end{figure}\n'
|
||||
'%\n\\begin{footnotetext}[7]\\sphinxAtStartFootnote\n'
|
||||
'Footnote in caption\n%\n\\end{footnotetext}')in result
|
||||
'Footnote in caption\n%\n\\end{footnotetext}') in result
|
||||
assert ('\\sphinxcaption{footnote \\sphinxfootnotemark[8] in '
|
||||
'caption of normal table}\\label{\\detokenize{index:id30}}') in result
|
||||
assert ('\\caption{footnote \\sphinxfootnotemark[9] '
|
||||
|
@@ -10,12 +10,12 @@
|
||||
|
||||
import http.server
|
||||
import json
|
||||
import re
|
||||
from unittest import mock
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from utils import http_server
|
||||
from utils import CERT_FILE, http_server, https_server, modify_env
|
||||
|
||||
|
||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck', freshenv=True)
|
||||
@@ -57,7 +57,7 @@ def test_defaults_json(app):
|
||||
assert len(rows) == 10
|
||||
# the output order of the rows is not stable
|
||||
# due to possible variance in network latency
|
||||
rowsby = {row["uri"]:row for row in rows}
|
||||
rowsby = {row["uri"]: row for row in rows}
|
||||
assert rowsby["https://www.google.com#!bar"] == {
|
||||
'filename': 'links.txt',
|
||||
'lineno': 10,
|
||||
@@ -110,7 +110,8 @@ def test_anchors_ignored(app):
|
||||
# expect all ok when excluding #top
|
||||
assert not content
|
||||
|
||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
|
||||
|
||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver-anchor', freshenv=True)
|
||||
def test_raises_for_invalid_status(app):
|
||||
class InternalServerErrorHandler(http.server.BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
@@ -126,56 +127,258 @@ def test_raises_for_invalid_status(app):
|
||||
)
|
||||
|
||||
|
||||
class HeadersDumperHandler(http.server.BaseHTTPRequestHandler):
|
||||
def do_HEAD(self):
|
||||
self.do_GET()
|
||||
|
||||
def do_GET(self):
|
||||
self.send_response(200, "OK")
|
||||
self.end_headers()
|
||||
print(self.headers.as_string())
|
||||
|
||||
|
||||
@pytest.mark.sphinx(
|
||||
'linkcheck', testroot='linkcheck', freshenv=True,
|
||||
'linkcheck', testroot='linkcheck-localserver', freshenv=True,
|
||||
confoverrides={'linkcheck_auth': [
|
||||
(r'.+google\.com/image.+', 'authinfo1'),
|
||||
(r'.+google\.com.+', 'authinfo2'),
|
||||
]
|
||||
})
|
||||
def test_auth(app):
|
||||
mock_req = mock.MagicMock()
|
||||
mock_req.return_value = 'fake-response'
|
||||
|
||||
with mock.patch.multiple('requests', get=mock_req, head=mock_req):
|
||||
(r'^$', ('no', 'match')),
|
||||
(r'^http://localhost:7777/$', ('user1', 'password')),
|
||||
(r'.*local.*', ('user2', 'hunter2')),
|
||||
]})
|
||||
def test_auth_header_uses_first_match(app, capsys):
|
||||
with http_server(HeadersDumperHandler):
|
||||
app.builder.build_all()
|
||||
for c_args, c_kwargs in mock_req.call_args_list:
|
||||
if 'google.com/image' in c_args[0]:
|
||||
assert c_kwargs['auth'] == 'authinfo1'
|
||||
elif 'google.com' in c_args[0]:
|
||||
assert c_kwargs['auth'] == 'authinfo2'
|
||||
else:
|
||||
assert not c_kwargs['auth']
|
||||
stdout, stderr = capsys.readouterr()
|
||||
auth = requests.auth._basic_auth_str('user1', 'password')
|
||||
assert "Authorization: %s\n" % auth in stdout
|
||||
|
||||
|
||||
@pytest.mark.sphinx(
|
||||
'linkcheck', testroot='linkcheck', freshenv=True,
|
||||
'linkcheck', testroot='linkcheck-localserver', freshenv=True,
|
||||
confoverrides={'linkcheck_auth': [(r'^$', ('user1', 'password'))]})
|
||||
def test_auth_header_no_match(app, capsys):
|
||||
with http_server(HeadersDumperHandler):
|
||||
app.builder.build_all()
|
||||
stdout, stderr = capsys.readouterr()
|
||||
assert "Authorization" not in stdout
|
||||
|
||||
|
||||
@pytest.mark.sphinx(
|
||||
'linkcheck', testroot='linkcheck-localserver', freshenv=True,
|
||||
confoverrides={'linkcheck_request_headers': {
|
||||
"https://localhost:7777/": {
|
||||
"http://localhost:7777/": {
|
||||
"Accept": "text/html",
|
||||
},
|
||||
"http://www.sphinx-doc.org": { # no slash at the end
|
||||
"Accept": "application/json",
|
||||
},
|
||||
"*": {
|
||||
"X-Secret": "open sesami",
|
||||
}
|
||||
}})
|
||||
def test_linkcheck_request_headers(app):
|
||||
mock_req = mock.MagicMock()
|
||||
mock_req.return_value = 'fake-response'
|
||||
|
||||
with mock.patch.multiple('requests', get=mock_req, head=mock_req):
|
||||
def test_linkcheck_request_headers(app, capsys):
|
||||
with http_server(HeadersDumperHandler):
|
||||
app.builder.build_all()
|
||||
for args, kwargs in mock_req.call_args_list:
|
||||
url = args[0]
|
||||
headers = kwargs.get('headers', {})
|
||||
if "https://localhost:7777" in url:
|
||||
assert headers["Accept"] == "text/html"
|
||||
elif 'http://www.sphinx-doc.org' in url:
|
||||
assert headers["Accept"] == "application/json"
|
||||
elif 'https://www.google.com' in url:
|
||||
assert headers["Accept"] == "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8"
|
||||
assert headers["X-Secret"] == "open sesami"
|
||||
|
||||
stdout, _stderr = capsys.readouterr()
|
||||
assert "Accept: text/html\n" in stdout
|
||||
assert "X-Secret" not in stdout
|
||||
assert "sesami" not in stdout
|
||||
|
||||
|
||||
@pytest.mark.sphinx(
|
||||
'linkcheck', testroot='linkcheck-localserver', freshenv=True,
|
||||
confoverrides={'linkcheck_request_headers': {
|
||||
"http://localhost:7777": {"Accept": "application/json"},
|
||||
"*": {"X-Secret": "open sesami"}
|
||||
}})
|
||||
def test_linkcheck_request_headers_no_slash(app, capsys):
|
||||
with http_server(HeadersDumperHandler):
|
||||
app.builder.build_all()
|
||||
|
||||
stdout, _stderr = capsys.readouterr()
|
||||
assert "Accept: application/json\n" in stdout
|
||||
assert "X-Secret" not in stdout
|
||||
assert "sesami" not in stdout
|
||||
|
||||
|
||||
@pytest.mark.sphinx(
|
||||
'linkcheck', testroot='linkcheck-localserver', freshenv=True,
|
||||
confoverrides={'linkcheck_request_headers': {
|
||||
"http://do.not.match.org": {"Accept": "application/json"},
|
||||
"*": {"X-Secret": "open sesami"}
|
||||
}})
|
||||
def test_linkcheck_request_headers_default(app, capsys):
|
||||
with http_server(HeadersDumperHandler):
|
||||
app.builder.build_all()
|
||||
|
||||
stdout, _stderr = capsys.readouterr()
|
||||
assert "Accepts: application/json\n" not in stdout
|
||||
assert "X-Secret: open sesami\n" in stdout
|
||||
|
||||
|
||||
def make_redirect_handler(*, support_head):
|
||||
class RedirectOnceHandler(http.server.BaseHTTPRequestHandler):
|
||||
def do_HEAD(self):
|
||||
if support_head:
|
||||
self.do_GET()
|
||||
else:
|
||||
assert headers["Accept"] == "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8"
|
||||
self.send_response(405, "Method Not Allowed")
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == "/?redirected=1":
|
||||
self.send_response(204, "No content")
|
||||
else:
|
||||
self.send_response(302, "Found")
|
||||
self.send_header("Location", "http://localhost:7777/?redirected=1")
|
||||
self.end_headers()
|
||||
|
||||
def log_date_time_string(self):
|
||||
"""Strip date and time from logged messages for assertions."""
|
||||
return ""
|
||||
|
||||
return RedirectOnceHandler
|
||||
|
||||
|
||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
|
||||
def test_follows_redirects_on_HEAD(app, capsys):
|
||||
with http_server(make_redirect_handler(support_head=True)):
|
||||
app.builder.build_all()
|
||||
stdout, stderr = capsys.readouterr()
|
||||
content = (app.outdir / 'output.txt').read_text()
|
||||
assert content == (
|
||||
"index.rst:1: [redirected with Found] "
|
||||
"http://localhost:7777/ to http://localhost:7777/?redirected=1\n"
|
||||
)
|
||||
assert stderr == textwrap.dedent(
|
||||
"""\
|
||||
127.0.0.1 - - [] "HEAD / HTTP/1.1" 302 -
|
||||
127.0.0.1 - - [] "HEAD /?redirected=1 HTTP/1.1" 204 -
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
|
||||
def test_follows_redirects_on_GET(app, capsys):
|
||||
with http_server(make_redirect_handler(support_head=False)):
|
||||
app.builder.build_all()
|
||||
stdout, stderr = capsys.readouterr()
|
||||
content = (app.outdir / 'output.txt').read_text()
|
||||
assert content == (
|
||||
"index.rst:1: [redirected with Found] "
|
||||
"http://localhost:7777/ to http://localhost:7777/?redirected=1\n"
|
||||
)
|
||||
assert stderr == textwrap.dedent(
|
||||
"""\
|
||||
127.0.0.1 - - [] "HEAD / HTTP/1.1" 405 -
|
||||
127.0.0.1 - - [] "GET / HTTP/1.1" 302 -
|
||||
127.0.0.1 - - [] "GET /?redirected=1 HTTP/1.1" 204 -
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class OKHandler(http.server.BaseHTTPRequestHandler):
|
||||
def do_HEAD(self):
|
||||
self.send_response(200, "OK")
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
self.do_HEAD()
|
||||
self.wfile.write(b"ok\n")
|
||||
|
||||
|
||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver-https', freshenv=True)
|
||||
def test_invalid_ssl(app):
|
||||
# Link indicates SSL should be used (https) but the server does not handle it.
|
||||
with http_server(OKHandler):
|
||||
app.builder.build_all()
|
||||
|
||||
with open(app.outdir / 'output.json') as fp:
|
||||
content = json.load(fp)
|
||||
assert content["status"] == "broken"
|
||||
assert content["filename"] == "index.rst"
|
||||
assert content["lineno"] == 1
|
||||
assert content["uri"] == "https://localhost:7777/"
|
||||
assert "SSLError" in content["info"]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver-https', freshenv=True)
|
||||
def test_connect_to_selfsigned_fails(app):
|
||||
with https_server(OKHandler):
|
||||
app.builder.build_all()
|
||||
|
||||
with open(app.outdir / 'output.json') as fp:
|
||||
content = json.load(fp)
|
||||
assert content["status"] == "broken"
|
||||
assert content["filename"] == "index.rst"
|
||||
assert content["lineno"] == 1
|
||||
assert content["uri"] == "https://localhost:7777/"
|
||||
assert "[SSL: CERTIFICATE_VERIFY_FAILED]" in content["info"]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver-https', freshenv=True)
|
||||
def test_connect_to_selfsigned_with_tls_verify_false(app):
|
||||
app.config.tls_verify = False
|
||||
with https_server(OKHandler):
|
||||
app.builder.build_all()
|
||||
|
||||
with open(app.outdir / 'output.json') as fp:
|
||||
content = json.load(fp)
|
||||
assert content == {
|
||||
"code": 0,
|
||||
"status": "working",
|
||||
"filename": "index.rst",
|
||||
"lineno": 1,
|
||||
"uri": "https://localhost:7777/",
|
||||
"info": "",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver-https', freshenv=True)
|
||||
def test_connect_to_selfsigned_with_tls_cacerts(app):
|
||||
app.config.tls_cacerts = CERT_FILE
|
||||
with https_server(OKHandler):
|
||||
app.builder.build_all()
|
||||
|
||||
with open(app.outdir / 'output.json') as fp:
|
||||
content = json.load(fp)
|
||||
assert content == {
|
||||
"code": 0,
|
||||
"status": "working",
|
||||
"filename": "index.rst",
|
||||
"lineno": 1,
|
||||
"uri": "https://localhost:7777/",
|
||||
"info": "",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver-https', freshenv=True)
|
||||
def test_connect_to_selfsigned_with_requests_env_var(app):
|
||||
with modify_env(REQUESTS_CA_BUNDLE=CERT_FILE), https_server(OKHandler):
|
||||
app.builder.build_all()
|
||||
|
||||
with open(app.outdir / 'output.json') as fp:
|
||||
content = json.load(fp)
|
||||
assert content == {
|
||||
"code": 0,
|
||||
"status": "working",
|
||||
"filename": "index.rst",
|
||||
"lineno": 1,
|
||||
"uri": "https://localhost:7777/",
|
||||
"info": "",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver-https', freshenv=True)
|
||||
def test_connect_to_selfsigned_nonexistent_cert_file(app):
|
||||
app.config.tls_cacerts = "does/not/exist"
|
||||
with https_server(OKHandler):
|
||||
app.builder.build_all()
|
||||
|
||||
with open(app.outdir / 'output.json') as fp:
|
||||
content = json.load(fp)
|
||||
assert content == {
|
||||
"code": 0,
|
||||
"status": "broken",
|
||||
"filename": "index.rst",
|
||||
"lineno": 1,
|
||||
"uri": "https://localhost:7777/",
|
||||
"info": "Could not find a suitable TLS CA certificate bundle, invalid path: does/not/exist",
|
||||
}
|
||||
|
@@ -75,12 +75,12 @@ def _check(name, input, idDict, output, key, asTextOutput):
|
||||
idExpected.append(idExpected[i - 1])
|
||||
idActual = [None]
|
||||
for i in range(1, _max_id + 1):
|
||||
#try:
|
||||
# try:
|
||||
id = ast.get_id(version=i)
|
||||
assert id is not None
|
||||
idActual.append(id[len(_id_prefix[i]):])
|
||||
#except NoOldIdError:
|
||||
# idActual.append(None)
|
||||
# except NoOldIdError:
|
||||
# idActual.append(None)
|
||||
|
||||
res = [True]
|
||||
for i in range(1, _max_id + 1):
|
||||
@@ -94,7 +94,7 @@ def _check(name, input, idDict, output, key, asTextOutput):
|
||||
print("Error in id version %d." % i)
|
||||
print("result: %s" % idActual[i])
|
||||
print("expected: %s" % idExpected[i])
|
||||
#print(rootSymbol.dump(0))
|
||||
# print(rootSymbol.dump(0))
|
||||
raise DefinitionError("")
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ def check(name, input, idDict, output=None, key=None, asTextOutput=None):
|
||||
if name != 'macro':
|
||||
# Second, check with semicolon
|
||||
_check(name, input + ' ;', idDict, output + ';', key,
|
||||
asTextOutput + ';' if asTextOutput is not None else None)
|
||||
asTextOutput + ';' if asTextOutput is not None else None)
|
||||
|
||||
|
||||
def test_expressions():
|
||||
@@ -422,7 +422,7 @@ def test_nested_name():
|
||||
check('function', 'void f(.A.B a)', {1: "f"})
|
||||
|
||||
|
||||
def test_union_definitions():
|
||||
def test_struct_definitions():
|
||||
check('struct', '{key}A', {1: 'A'})
|
||||
|
||||
|
||||
@@ -482,7 +482,7 @@ def test_attributes():
|
||||
# style: user-defined paren
|
||||
check('member', 'paren_attr() int f', {1: 'f'})
|
||||
check('member', 'paren_attr(a) int f', {1: 'f'})
|
||||
check('member', 'paren_attr("") int f',{1: 'f'})
|
||||
check('member', 'paren_attr("") int f', {1: 'f'})
|
||||
check('member', 'paren_attr(()[{}][]{}) int f', {1: 'f'})
|
||||
with pytest.raises(DefinitionError):
|
||||
parse('member', 'paren_attr(() int f')
|
||||
@@ -521,7 +521,7 @@ def test_attributes():
|
||||
|
||||
|
||||
def filter_warnings(warning, file):
|
||||
lines = warning.getvalue().split("\n");
|
||||
lines = warning.getvalue().split("\n")
|
||||
res = [l for l in lines if "domain-c" in l and "{}.rst".format(file) in l and
|
||||
"WARNING: document isn't included in any toctree" not in l]
|
||||
print("Filtered warnings for file '{}':".format(file))
|
||||
@@ -602,6 +602,7 @@ def _get_obj(app, queryName):
|
||||
return (docname, anchor, objectType)
|
||||
return (queryName, "not", "found")
|
||||
|
||||
|
||||
def test_cfunction(app):
|
||||
text = (".. c:function:: PyObject* "
|
||||
"PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)")
|
||||
|
@@ -181,9 +181,9 @@ def test_expressions():
|
||||
expr = i + l + u
|
||||
exprCheck(expr, 'L' + expr + 'E')
|
||||
decimalFloats = ['5e42', '5e+42', '5e-42',
|
||||
'5.', '5.e42', '5.e+42', '5.e-42',
|
||||
'.5', '.5e42', '.5e+42', '.5e-42',
|
||||
'5.0', '5.0e42', '5.0e+42', '5.0e-42']
|
||||
'5.', '5.e42', '5.e+42', '5.e-42',
|
||||
'.5', '.5e42', '.5e+42', '.5e-42',
|
||||
'5.0', '5.0e42', '5.0e+42', '5.0e-42']
|
||||
hexFloats = ['ApF', 'Ap+F', 'Ap-F',
|
||||
'A.', 'A.pF', 'A.p+F', 'A.p-F',
|
||||
'.A', '.ApF', '.Ap+F', '.Ap-F',
|
||||
@@ -424,9 +424,9 @@ def test_member_definitions():
|
||||
check('member', 'int b : 8 = 42', {1: 'b__i', 2: '1b'})
|
||||
check('member', 'int b : 8{42}', {1: 'b__i', 2: '1b'})
|
||||
# TODO: enable once the ternary operator is supported
|
||||
#check('member', 'int b : true ? 8 : a = 42', {1: 'b__i', 2: '1b'})
|
||||
# check('member', 'int b : true ? 8 : a = 42', {1: 'b__i', 2: '1b'})
|
||||
# TODO: enable once the ternary operator is supported
|
||||
#check('member', 'int b : (true ? 8 : a) = 42', {1: 'b__i', 2: '1b'})
|
||||
# check('member', 'int b : (true ? 8 : a) = 42', {1: 'b__i', 2: '1b'})
|
||||
check('member', 'int b : 1 || new int{0}', {1: 'b__i', 2: '1b'})
|
||||
|
||||
|
||||
@@ -536,8 +536,8 @@ def test_function_definitions():
|
||||
check('function', 'int foo(const A*...)', {1: "foo__ACPDp", 2: "3fooDpPK1A"})
|
||||
check('function', 'int foo(const int A::*... a)', {2: "3fooDpM1AKi"})
|
||||
check('function', 'int foo(const int A::*...)', {2: "3fooDpM1AKi"})
|
||||
#check('function', 'int foo(int (*a)(A)...)', {1: "foo__ACRDp", 2: "3fooDpPK1A"})
|
||||
#check('function', 'int foo(int (*)(A)...)', {1: "foo__ACRDp", 2: "3fooDpPK1A"})
|
||||
# check('function', 'int foo(int (*a)(A)...)', {1: "foo__ACRDp", 2: "3fooDpPK1A"})
|
||||
# check('function', 'int foo(int (*)(A)...)', {1: "foo__ACRDp", 2: "3fooDpPK1A"})
|
||||
check('function', 'virtual void f()', {1: "f", 2: "1fv"})
|
||||
# test for ::nestedName, from issue 1738
|
||||
check("function", "result(int val, ::std::error_category const &cat)",
|
||||
@@ -706,7 +706,6 @@ def test_class_definitions():
|
||||
check('class', 'template<class T> {key}has_var<T, std::void_t<decltype(&T::var)>>',
|
||||
{2: 'I0E7has_varI1TNSt6void_tIDTadN1T3varEEEEE'})
|
||||
|
||||
|
||||
check('class', 'template<typename ...Ts> {key}T<int (*)(Ts)...>',
|
||||
{2: 'IDpE1TIJPFi2TsEEE'})
|
||||
check('class', 'template<int... Is> {key}T<(Is)...>',
|
||||
@@ -1000,7 +999,7 @@ def test_build_domain_cpp_warn_template_param_qualified_name(app, status, warnin
|
||||
|
||||
|
||||
@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True})
|
||||
def test_build_domain_cpp_backslash_ok(app, status, warning):
|
||||
def test_build_domain_cpp_backslash_ok_true(app, status, warning):
|
||||
app.builder.build_all()
|
||||
ws = filter_warnings(warning, "backslash")
|
||||
assert len(ws) == 0
|
||||
@@ -1015,7 +1014,7 @@ def test_build_domain_cpp_semicolon(app, status, warning):
|
||||
|
||||
@pytest.mark.sphinx(testroot='domain-cpp',
|
||||
confoverrides={'nitpicky': True, 'strip_signature_backslash': True})
|
||||
def test_build_domain_cpp_backslash_ok(app, status, warning):
|
||||
def test_build_domain_cpp_backslash_ok_false(app, status, warning):
|
||||
app.builder.build_all()
|
||||
ws = filter_warnings(warning, "backslash")
|
||||
assert len(ws) == 1
|
||||
@@ -1245,4 +1244,4 @@ def test_mix_decl_duplicate(app, warning):
|
||||
assert "Declaration is '.. cpp:function:: void A()'." in ws[1]
|
||||
assert "index.rst:3: WARNING: Duplicate C++ declaration, also defined at index:1." in ws[2]
|
||||
assert "Declaration is '.. cpp:struct:: A'." in ws[3]
|
||||
assert ws[4] == ""
|
||||
assert ws[4] == ""
|
||||
|
@@ -330,7 +330,7 @@ def test_multiple_cmdoptions(app):
|
||||
def test_productionlist(app, status, warning):
|
||||
app.builder.build_all()
|
||||
|
||||
warnings = warning.getvalue().split("\n");
|
||||
warnings = warning.getvalue().split("\n")
|
||||
assert len(warnings) == 2
|
||||
assert warnings[-1] == ''
|
||||
assert "Dup2.rst:4: WARNING: duplicate token description of Dup, other instance in Dup1" in warnings[0]
|
||||
|
@@ -38,8 +38,9 @@ def test_create_single_index(app):
|
||||
('upgrade', [('', '#index-3')])], None]),
|
||||
('Python', [[('', '#index-1')], [], None])])
|
||||
assert index[3] == ('S', [('Sphinx', [[('', '#index-4')], [], None])])
|
||||
assert index[4] == ('Е', [('ёлка', [[('', '#index-6')], [], None]),
|
||||
('Ель', [[('', '#index-5')], [], None])])
|
||||
assert index[4] == ('Е',
|
||||
[('ёлка', [[('', '#index-6')], [], None]),
|
||||
('Ель', [[('', '#index-5')], [], None])])
|
||||
assert index[5] == ('ת', [('תירבע', [[('', '#index-7')], [], None])])
|
||||
|
||||
|
||||
@@ -69,8 +70,9 @@ def test_create_pair_index(app):
|
||||
('ёлка', [('', '#index-5')]),
|
||||
('Ель', [('', '#index-4')])],
|
||||
None])])
|
||||
assert index[6] == ('Е', [('ёлка', [[], [('Sphinx', [('', '#index-5')])], None]),
|
||||
('Ель', [[], [('Sphinx', [('', '#index-4')])], None])])
|
||||
assert index[6] == ('Е',
|
||||
[('ёлка', [[], [('Sphinx', [('', '#index-5')])], None]),
|
||||
('Ель', [[], [('Sphinx', [('', '#index-4')])], None])])
|
||||
|
||||
|
||||
@pytest.mark.sphinx('dummy', freshenv=True)
|
||||
|
@@ -177,7 +177,6 @@ def test_format_signature(app):
|
||||
for C in (D, E):
|
||||
assert formatsig('class', 'D', C, None, None) == '()'
|
||||
|
||||
|
||||
class SomeMeta(type):
|
||||
def __call__(cls, a, b=None):
|
||||
return type.__call__(cls, a, b)
|
||||
@@ -209,7 +208,6 @@ def test_format_signature(app):
|
||||
assert formatsig('class', 'C', C, None, None) == '(a, b=None)'
|
||||
assert formatsig('class', 'C', D, 'a, b', 'X') == '(a, b) -> X'
|
||||
|
||||
|
||||
class ListSubclass(list):
|
||||
pass
|
||||
|
||||
@@ -219,7 +217,6 @@ def test_format_signature(app):
|
||||
else:
|
||||
assert formatsig('class', 'C', ListSubclass, None, None) == ''
|
||||
|
||||
|
||||
class ExceptionSubclass(Exception):
|
||||
pass
|
||||
|
||||
@@ -227,7 +224,6 @@ def test_format_signature(app):
|
||||
if getattr(Exception, '__text_signature__', None) is None:
|
||||
assert formatsig('class', 'C', ExceptionSubclass, None, None) == ''
|
||||
|
||||
|
||||
# __init__ have signature at first line of docstring
|
||||
directive.env.config.autoclass_content = 'both'
|
||||
|
||||
|
71
tests/test_ext_autodoc_autoattribute.py
Normal file
71
tests/test_ext_autodoc_autoattribute.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
test_ext_autodoc_autoattribute
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Test the autodoc extension. This tests mainly the Documenters; the auto
|
||||
directives are tested in a test source file translated by test_build.
|
||||
|
||||
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from test_ext_autodoc import do_autodoc
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoattribute(app):
|
||||
actual = do_autodoc(app, 'attribute', 'target.Class.attr')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:attribute:: Class.attr',
|
||||
' :module: target',
|
||||
" :value: 'bar'",
|
||||
'',
|
||||
' should be documented -- süß',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoattribute_novalue(app):
|
||||
options = {'no-value': True}
|
||||
actual = do_autodoc(app, 'attribute', 'target.Class.attr', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:attribute:: Class.attr',
|
||||
' :module: target',
|
||||
'',
|
||||
' should be documented -- süß',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoattribute_typed_variable(app):
|
||||
actual = do_autodoc(app, 'attribute', 'target.typed_vars.Class.attr2')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:attribute:: Class.attr2',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoattribute_instance_variable(app):
|
||||
actual = do_autodoc(app, 'attribute', 'target.typed_vars.Class.attr4')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:attribute:: Class.attr4',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
' attr4',
|
||||
'',
|
||||
]
|
74
tests/test_ext_autodoc_autodata.py
Normal file
74
tests/test_ext_autodoc_autodata.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
test_ext_autodoc_autodata
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Test the autodoc extension. This tests mainly the Documenters; the auto
|
||||
directives are tested in a test source file translated by test_build.
|
||||
|
||||
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from test_ext_autodoc import do_autodoc
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodata(app):
|
||||
actual = do_autodoc(app, 'data', 'target.integer')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:data:: integer',
|
||||
' :module: target',
|
||||
' :value: 1',
|
||||
'',
|
||||
' documentation for the integer',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodata_novalue(app):
|
||||
options = {'no-value': True}
|
||||
actual = do_autodoc(app, 'data', 'target.integer', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:data:: integer',
|
||||
' :module: target',
|
||||
'',
|
||||
' documentation for the integer',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodata_typed_variable(app):
|
||||
actual = do_autodoc(app, 'data', 'target.typed_vars.attr2')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:data:: attr2',
|
||||
' :module: target.typed_vars',
|
||||
' :type: str',
|
||||
'',
|
||||
' attr2',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodata_type_comment(app):
|
||||
actual = do_autodoc(app, 'data', 'target.typed_vars.attr3')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:data:: attr3',
|
||||
' :module: target.typed_vars',
|
||||
' :type: str',
|
||||
" :value: ''",
|
||||
'',
|
||||
' attr3',
|
||||
'',
|
||||
]
|
@@ -700,6 +700,19 @@ def test_autodoc_type_aliases(app):
|
||||
'.. py:module:: target.annotations',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Foo()',
|
||||
' :module: target.annotations',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Foo.attr',
|
||||
' :module: target.annotations',
|
||||
' :type: int',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: mult(x: int, y: int) -> int',
|
||||
' mult(x: float, y: float) -> float',
|
||||
' :module: target.annotations',
|
||||
@@ -712,6 +725,13 @@ def test_autodoc_type_aliases(app):
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:data:: variable',
|
||||
' :module: target.annotations',
|
||||
' :type: int',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
]
|
||||
|
||||
# define aliases
|
||||
@@ -722,6 +742,19 @@ def test_autodoc_type_aliases(app):
|
||||
'.. py:module:: target.annotations',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Foo()',
|
||||
' :module: target.annotations',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Foo.attr',
|
||||
' :module: target.annotations',
|
||||
' :type: myint',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: mult(x: myint, y: myint) -> myint',
|
||||
' mult(x: float, y: float) -> float',
|
||||
' :module: target.annotations',
|
||||
@@ -734,6 +767,13 @@ def test_autodoc_type_aliases(app):
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:data:: variable',
|
||||
' :module: target.annotations',
|
||||
' :type: myint',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
|
@@ -11,7 +11,6 @@
|
||||
import http.server
|
||||
import os
|
||||
import unittest
|
||||
from io import BytesIO
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
@@ -1070,7 +1070,7 @@ Methods:
|
||||
|
||||
|
||||
description
|
||||
"""
|
||||
""" # NOQA
|
||||
config = Config()
|
||||
actual = str(GoogleDocstring(docstring, config=config, app=None, what='module',
|
||||
options={'noindex': True}))
|
||||
@@ -2222,7 +2222,7 @@ definition_after_normal_text : int
|
||||
["{", "'F'", ", ", "'C'", ", ", "'N or C'", "}", ", ", "default", " ", "'F'"],
|
||||
["str", ", ", "default", ": ", "'F or C'"],
|
||||
["int", ", ", "default", ": ", "None"],
|
||||
["int", ", " , "default", " ", "None"],
|
||||
["int", ", ", "default", " ", "None"],
|
||||
["int", ", ", "default", " ", ":obj:`None`"],
|
||||
['"ma{icious"'],
|
||||
[r"'with \'quotes\''"],
|
||||
|
@@ -89,15 +89,6 @@ def assert_count(expected_expr, result, count):
|
||||
assert len(re.findall(*find_pair)) == count, find_pair
|
||||
|
||||
|
||||
@sphinx_intl
|
||||
@pytest.mark.sphinx('text')
|
||||
@pytest.mark.test_params(shared_result='test_intl_basic')
|
||||
def test_text_toctree(app):
|
||||
app.build()
|
||||
result = (app.outdir / 'index.txt').read_text()
|
||||
assert_startswith(result, "CONTENTS\n********\n\nTABLE OF CONTENTS\n")
|
||||
|
||||
|
||||
@sphinx_intl
|
||||
@pytest.mark.sphinx('text')
|
||||
@pytest.mark.test_params(shared_result='test_intl_basic')
|
||||
@@ -436,11 +427,16 @@ def test_text_admonitions(app):
|
||||
@pytest.mark.test_params(shared_result='test_intl_gettext')
|
||||
def test_gettext_toctree(app):
|
||||
app.build()
|
||||
# --- toctree
|
||||
# --- toctree (index.rst)
|
||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'index.po')
|
||||
actual = read_po(app.outdir / 'index.pot')
|
||||
for expect_msg in [m for m in expect if m.id]:
|
||||
assert expect_msg.id in [m.id for m in actual if m.id]
|
||||
# --- toctree (toctree.rst)
|
||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'toctree.po')
|
||||
actual = read_po(app.outdir / 'toctree.pot')
|
||||
for expect_msg in [m for m in expect if m.id]:
|
||||
assert expect_msg.id in [m.id for m in actual if m.id]
|
||||
|
||||
|
||||
@sphinx_intl
|
||||
@@ -467,24 +463,17 @@ def test_text_table(app):
|
||||
assert expect_msg.string in result
|
||||
|
||||
|
||||
@sphinx_intl
|
||||
@pytest.mark.sphinx('gettext')
|
||||
@pytest.mark.test_params(shared_result='test_intl_gettext')
|
||||
def test_gettext_toctree(app):
|
||||
app.build()
|
||||
# --- toctree
|
||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'toctree.po')
|
||||
actual = read_po(app.outdir / 'toctree.pot')
|
||||
for expect_msg in [m for m in expect if m.id]:
|
||||
assert expect_msg.id in [m.id for m in actual if m.id]
|
||||
|
||||
|
||||
@sphinx_intl
|
||||
@pytest.mark.sphinx('text')
|
||||
@pytest.mark.test_params(shared_result='test_intl_basic')
|
||||
def test_text_toctree(app):
|
||||
app.build()
|
||||
# --- toctree
|
||||
# --- toctree (index.rst)
|
||||
# Note: index.rst contains contents that is not shown in text.
|
||||
result = (app.outdir / 'index.txt').read_text()
|
||||
assert 'CONTENTS' in result
|
||||
assert 'TABLE OF CONTENTS' in result
|
||||
# --- toctree (toctree.rst)
|
||||
result = (app.outdir / 'toctree.txt').read_text()
|
||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'toctree.po')
|
||||
for expect_msg in [m for m in expect if m.id]:
|
||||
|
@@ -17,7 +17,6 @@ from docutils.parsers.rst import Parser as RstParser
|
||||
from sphinx import addnodes
|
||||
from sphinx.builders.html.transforms import KeyboardTransform
|
||||
from sphinx.builders.latex import LaTeXBuilder
|
||||
from sphinx.builders.latex.theming import ThemeFactory
|
||||
from sphinx.roles import XRefRole
|
||||
from sphinx.testing.util import Struct, assert_node
|
||||
from sphinx.transforms import SphinxSmartQuotes
|
||||
|
@@ -13,7 +13,6 @@ from collections import OrderedDict
|
||||
import pytest
|
||||
|
||||
from sphinx.project import Project
|
||||
from sphinx.testing.comparer import PathComparer
|
||||
|
||||
|
||||
def test_project_discover(rootdir):
|
||||
|
@@ -19,6 +19,7 @@ from sphinx.pycode import ModuleAnalyzer
|
||||
|
||||
SPHINX_MODULE_PATH = os.path.splitext(sphinx.__file__)[0] + '.py'
|
||||
|
||||
|
||||
def test_ModuleAnalyzer_get_module_source():
|
||||
assert ModuleAnalyzer.get_module_source('sphinx') == (sphinx.__file__, sphinx.__loader__.get_source('sphinx'))
|
||||
|
||||
|
@@ -14,8 +14,7 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
import sphinx
|
||||
from sphinx.errors import ExtensionError, PycodeError
|
||||
from sphinx.errors import ExtensionError
|
||||
from sphinx.testing.util import strip_escseq
|
||||
from sphinx.util import (SkipProgressMessage, display_chunk, encode_uri, ensuredir,
|
||||
import_object, logging, parselinenos, progress_message,
|
||||
|
@@ -19,7 +19,7 @@ import _testcapi
|
||||
import pytest
|
||||
|
||||
from sphinx.util import inspect
|
||||
from sphinx.util.inspect import is_builtin_class_method, stringify_signature
|
||||
from sphinx.util.inspect import stringify_signature
|
||||
|
||||
|
||||
def test_signature():
|
||||
@@ -490,6 +490,28 @@ def test_dict_customtype():
|
||||
assert "<CustomType(2)>: 2" in description
|
||||
|
||||
|
||||
def test_getslots():
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
class Bar:
|
||||
__slots__ = ['attr']
|
||||
|
||||
class Baz:
|
||||
__slots__ = {'attr': 'docstring'}
|
||||
|
||||
class Qux:
|
||||
__slots__ = 'attr'
|
||||
|
||||
assert inspect.getslots(Foo) is None
|
||||
assert inspect.getslots(Bar) == {'attr': None}
|
||||
assert inspect.getslots(Baz) == {'attr': 'docstring'}
|
||||
assert inspect.getslots(Qux) == {'attr': None}
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
inspect.getslots(Bar())
|
||||
|
||||
|
||||
@pytest.mark.sphinx(testroot='ext-autodoc')
|
||||
def test_isclassmethod(app):
|
||||
from target.methods import Base, Inherited
|
||||
|
@@ -10,8 +10,7 @@
|
||||
|
||||
import sys
|
||||
from numbers import Integral
|
||||
from typing import (Any, Callable, Dict, Generator, Generic, List, Optional, Tuple, TypeVar,
|
||||
Union)
|
||||
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, TypeVar, Union
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -25,8 +24,10 @@ class MyClass1:
|
||||
class MyClass2(MyClass1):
|
||||
__qualname__ = '<MyClass2>'
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
class MyList(List[T]):
|
||||
pass
|
||||
|
||||
@@ -132,8 +133,8 @@ def test_stringify_type_hints_containers():
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.')
|
||||
def test_stringify_Annotated():
|
||||
from typing import Annotated
|
||||
assert stringify(Annotated[str, "foo", "bar"]) == "str"
|
||||
from typing import Annotated # type: ignore
|
||||
assert stringify(Annotated[str, "foo", "bar"]) == "str" # NOQA
|
||||
|
||||
|
||||
def test_stringify_type_hints_string():
|
||||
|
@@ -77,7 +77,7 @@ def f14() -> Any:
|
||||
pass
|
||||
|
||||
|
||||
def f15(x: "Unknown", y: "int") -> Any:
|
||||
def f15(x: "Unknown", y: "int") -> Any: # type: ignore # NOQA
|
||||
pass
|
||||
|
||||
|
||||
|
@@ -1,7 +1,15 @@
|
||||
import contextlib
|
||||
import http.server
|
||||
import os
|
||||
import pathlib
|
||||
import ssl
|
||||
import threading
|
||||
|
||||
# Generated with:
|
||||
# $ openssl req -new -x509 -days 3650 -nodes -out cert.pem \
|
||||
# -keyout cert.pem -addext "subjectAltName = DNS:localhost"
|
||||
CERT_FILE = str(pathlib.Path(__file__).parent / "certs" / "cert.pem")
|
||||
|
||||
|
||||
class HttpServerThread(threading.Thread):
|
||||
def __init__(self, handler, *args, **kwargs):
|
||||
@@ -17,11 +25,41 @@ class HttpServerThread(threading.Thread):
|
||||
self.join()
|
||||
|
||||
|
||||
class HttpsServerThread(HttpServerThread):
|
||||
def __init__(self, handler, *args, **kwargs):
|
||||
super().__init__(handler, *args, **kwargs)
|
||||
self.server.socket = ssl.wrap_socket(
|
||||
self.server.socket,
|
||||
certfile=CERT_FILE,
|
||||
server_side=True,
|
||||
)
|
||||
|
||||
|
||||
def create_server(thread_class):
|
||||
def server(handler):
|
||||
server_thread = thread_class(handler, daemon=True)
|
||||
server_thread.start()
|
||||
try:
|
||||
yield server_thread
|
||||
finally:
|
||||
server_thread.terminate()
|
||||
return contextlib.contextmanager(server)
|
||||
|
||||
|
||||
http_server = create_server(HttpServerThread)
|
||||
https_server = create_server(HttpsServerThread)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def http_server(handler):
|
||||
server_thread = HttpServerThread(handler, daemon=True)
|
||||
server_thread.start()
|
||||
def modify_env(**env):
|
||||
original_env = os.environ.copy()
|
||||
for k, v in env.items():
|
||||
os.environ[k] = v
|
||||
try:
|
||||
yield server_thread
|
||||
yield
|
||||
finally:
|
||||
server_thread.terminate()
|
||||
for k in env:
|
||||
try:
|
||||
os.environ[k] = original_env[k]
|
||||
except KeyError:
|
||||
os.unsetenv(k)
|
||||
|
Reference in New Issue
Block a user