Clean up after dropping Python 3.6

This commit is contained in:
Adam Turner
2022-04-17 04:01:17 +01:00
parent 51927bb6e4
commit 7649eb1505
23 changed files with 95 additions and 122 deletions

View File

@@ -7,7 +7,7 @@ permissions:
jobs: jobs:
ubuntu: ubuntu:
runs-on: ubuntu-18.04 runs-on: ubuntu-20.04
name: Python ${{ matrix.python }} (${{ matrix.docutils }}) name: Python ${{ matrix.python }} (${{ matrix.docutils }})
strategy: strategy:
fail-fast: false fail-fast: false

View File

@@ -4,7 +4,7 @@ Release 6.0.0 (in development)
Dependencies Dependencies
------------ ------------
* Drop python 3.6 support * #10468: Drop Python 3.6 support
Incompatible changes Incompatible changes
-------------------- --------------------

View File

@@ -113,13 +113,15 @@ April 2023 and shipping Python 3.8.
This is a summary table with the current policy: This is a summary table with the current policy:
========== ========= ====== ========== ========= ====== ======
Date Ubuntu Python Date Ubuntu Python Sphinx
========== ========= ====== ========== ========= ====== ======
April 2023 20.04 LTS 3.8+ April 2021 18.04 LTS 3.6+ 4, 5
---------- --------- ------ ---------- --------- ------ ------
April 2025 22.04 LTS 3.10+ April 2023 20.04 LTS 3.8+ 6, 7
========== ========= ====== ---------- --------- ------ ------
April 2025 22.04 LTS 3.10+ 8
========== ========= ====== ======
Release procedures Release procedures
------------------ ------------------

View File

@@ -204,7 +204,7 @@ def _parse_annotation(annotation: str, env: BuildEnvironment) -> List[Node]:
return result return result
else: else:
if sys.version_info < (3, 8): if sys.version_info[:2] <= (3, 7):
if isinstance(node, ast.Bytes): if isinstance(node, ast.Bytes):
return [addnodes.desc_sig_literal_string('', repr(node.s))] return [addnodes.desc_sig_literal_string('', repr(node.s))]
elif isinstance(node, ast.Ellipsis): elif isinstance(node, ast.Ellipsis):

View File

@@ -820,6 +820,8 @@ class Documenter:
# sort by group; alphabetically within groups # sort by group; alphabetically within groups
documenters.sort(key=lambda e: (e[0].member_order, e[0].name)) documenters.sort(key=lambda e: (e[0].member_order, e[0].name))
elif order == 'bysource': elif order == 'bysource':
# By default, member discovery order matches source order,
# as dicts are insertion-ordered from Python 3.7.
if self.analyzer: if self.analyzer:
# sort by source order, by virtue of the module analyzer # sort by source order, by virtue of the module analyzer
tagorder = self.analyzer.tagorder tagorder = self.analyzer.tagorder
@@ -828,11 +830,6 @@ class Documenter:
fullname = entry[0].name.split('::')[1] fullname = entry[0].name.split('::')[1]
return tagorder.get(fullname, len(tagorder)) return tagorder.get(fullname, len(tagorder))
documenters.sort(key=keyfunc) documenters.sort(key=keyfunc)
else:
# Assume that member discovery order matches source order.
# This is a reasonable assumption in Python 3.6 and up, where
# module.__dict__ is insertion-ordered.
pass
else: # alphabetical else: # alphabetical
documenters.sort(key=lambda e: e[0].name) documenters.sort(key=lambda e: e[0].name)

View File

@@ -48,9 +48,9 @@ def get_function_def(obj: Any) -> Optional[ast.FunctionDef]:
def get_default_value(lines: List[str], position: ast.AST) -> Optional[str]: def get_default_value(lines: List[str], position: ast.AST) -> Optional[str]:
try: try:
if sys.version_info < (3, 8): # only for py38+ if sys.version_info[:2] <= (3, 7): # only for py38+
return None return None
elif position.lineno == position.end_lineno: if position.lineno == position.end_lineno:
line = lines[position.lineno - 1] line = lines[position.lineno - 1]
return line[position.col_offset:position.end_col_offset] return line[position.col_offset:position.end_col_offset]
else: else:

View File

@@ -597,8 +597,6 @@ class GoogleDocstring:
self._parsed_lines = self._consume_empty() self._parsed_lines = self._consume_empty()
if self._name and self._what in ('attribute', 'data', 'property'): if self._name and self._what in ('attribute', 'data', 'property'):
# Implicit stop using StopIteration no longer allowed in
# Python 3.7; see PEP 479
res: List[str] = [] res: List[str] = []
try: try:
res = self._parse_attribute_docstring() res = self._parse_attribute_docstring()

View File

@@ -3,7 +3,7 @@
import sys import sys
from typing import Dict, List, Optional, Type, overload from typing import Dict, List, Optional, Type, overload
if sys.version_info > (3, 8): if sys.version_info[:2] >= (3, 8):
import ast import ast
else: else:
try: try:
@@ -159,7 +159,7 @@ class _UnparseVisitor(ast.NodeVisitor):
if node.value is Ellipsis: if node.value is Ellipsis:
return "..." return "..."
elif isinstance(node.value, (int, float, complex)): elif isinstance(node.value, (int, float, complex)):
if self.code and sys.version_info > (3, 8): if self.code and sys.version_info[:2] >= (3, 8):
return ast.get_source_segment(self.code, node) # type: ignore return ast.get_source_segment(self.code, node) # type: ignore
else: else:
return repr(node.value) return repr(node.value)
@@ -219,7 +219,7 @@ class _UnparseVisitor(ast.NodeVisitor):
else: else:
return "(" + ", ".join(self.visit(e) for e in node.elts) + ")" return "(" + ", ".join(self.visit(e) for e in node.elts) + ")"
if sys.version_info < (3, 8): if sys.version_info[:2] <= (3, 7):
# these ast nodes were deprecated in python 3.8 # these ast nodes were deprecated in python 3.8
def visit_Bytes(self, node: ast.Bytes) -> str: def visit_Bytes(self, node: ast.Bytes) -> str:
return repr(node.s) return repr(node.s)

View File

@@ -107,5 +107,5 @@ def test_svg(h: bytes, f: Optional[BinaryIO]) -> Optional[str]:
# install test_svg() to imghdr # install test_svg() to imghdr
# refs: https://docs.python.org/3.6/library/imghdr.html#imghdr.tests # refs: https://docs.python.org/3.9/library/imghdr.html#imghdr.tests
imghdr.tests.append(test_svg) imghdr.tests.append(test_svg)

View File

@@ -12,7 +12,8 @@ from functools import partial, partialmethod
from importlib import import_module from importlib import import_module
from inspect import Parameter, isclass, ismethod, ismethoddescriptor, ismodule # NOQA from inspect import Parameter, isclass, ismethod, ismethoddescriptor, ismodule # NOQA
from io import StringIO from io import StringIO
from types import MethodType, ModuleType from types import (ClassMethodDescriptorType, MethodDescriptorType, MethodType, ModuleType,
WrapperDescriptorType)
from typing import Any, Callable, Dict, Mapping, Optional, Sequence, Tuple, Type, cast from typing import Any, Callable, Dict, Mapping, Optional, Sequence, Tuple, Type, cast
from sphinx.pycode.ast import ast # for py37 from sphinx.pycode.ast import ast # for py37
@@ -21,13 +22,6 @@ from sphinx.util import logging
from sphinx.util.typing import ForwardRef from sphinx.util.typing import ForwardRef
from sphinx.util.typing import stringify as stringify_annotation from sphinx.util.typing import stringify as stringify_annotation
if sys.version_info > (3, 7):
from types import ClassMethodDescriptorType, MethodDescriptorType, WrapperDescriptorType
else:
ClassMethodDescriptorType = type(object.__init__)
MethodDescriptorType = type(str.join)
WrapperDescriptorType = type(dict.__dict__['fromkeys'])
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE) memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE)
@@ -149,7 +143,7 @@ def getslots(obj: Any) -> Optional[Dict]:
def isNewType(obj: Any) -> bool: def isNewType(obj: Any) -> bool:
"""Check the if object is a kind of NewType.""" """Check the if object is a kind of NewType."""
if sys.version_info >= (3, 10): if sys.version_info[:2] >= (3, 10):
return isinstance(obj, typing.NewType) return isinstance(obj, typing.NewType)
else: else:
__module__ = safe_getattr(obj, '__module__', None) __module__ = safe_getattr(obj, '__module__', None)
@@ -335,19 +329,22 @@ def iscoroutinefunction(obj: Any) -> bool:
return False return False
def isasyncgenfunction(obj: Any) -> bool: if sys.version_info[:2] <= (3, 7):
"""Check if the object is async-gen function.""" def isasyncgenfunction(obj: Any) -> bool:
if hasattr(obj, '__code__') and inspect.isasyncgenfunction(obj): """Check if the object is async-gen function."""
# check obj.__code__ because isasyncgenfunction() crashes for custom method-like if hasattr(obj, '__code__') and inspect.isasyncgenfunction(obj):
# objects on python3.7 (see https://github.com/sphinx-doc/sphinx/issues/9838) # check obj.__code__ because isasyncgenfunction() crashes for custom method-like
return True # objects on python3.7 (see https://github.com/sphinx-doc/sphinx/issues/9838)
else: return True
return False else:
return False
else:
isasyncgenfunction = inspect.isasyncgenfunction
def isproperty(obj: Any) -> bool: def isproperty(obj: Any) -> bool:
"""Check if the object is property.""" """Check if the object is property."""
if sys.version_info >= (3, 8): if sys.version_info[:2] >= (3, 8):
from functools import cached_property # cached_property is available since py3.8 from functools import cached_property # cached_property is available since py3.8
if isinstance(obj, cached_property): if isinstance(obj, cached_property):
return True return True
@@ -619,7 +616,7 @@ def evaluate_signature(sig: inspect.Signature, globalns: Optional[Dict] = None,
"""Evaluate unresolved type annotations in a signature object.""" """Evaluate unresolved type annotations in a signature object."""
def evaluate_forwardref(ref: ForwardRef, globalns: Dict, localns: Dict) -> Any: def evaluate_forwardref(ref: ForwardRef, globalns: Dict, localns: Dict) -> Any:
"""Evaluate a forward reference.""" """Evaluate a forward reference."""
if sys.version_info > (3, 9): if sys.version_info[:2] >= (3, 9):
return ref._evaluate(globalns, localns, frozenset()) return ref._evaluate(globalns, localns, frozenset())
else: else:
return ref._evaluate(globalns, localns) return ref._evaluate(globalns, localns)

View File

@@ -1,7 +1,6 @@
"""Parallel building utilities.""" """Parallel building utilities."""
import os import os
import sys
import time import time
import traceback import traceback
from math import sqrt from math import sqrt
@@ -18,13 +17,6 @@ from sphinx.util import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if sys.platform != "win32":
ForkContext = multiprocessing.context.ForkContext
ForkProcess = multiprocessing.context.ForkProcess
else:
# For static typing, as ForkProcess doesn't exist on Windows
ForkContext = ForkProcess = Any
# our parallel functionality only works for the forking Process # our parallel functionality only works for the forking Process
parallel_available = multiprocessing and os.name == 'posix' parallel_available = multiprocessing and os.name == 'posix'
@@ -59,7 +51,7 @@ class ParallelTasks:
# task arguments # task arguments
self._args: Dict[int, Optional[List[Any]]] = {} self._args: Dict[int, Optional[List[Any]]] = {}
# list of subprocesses (both started and waiting) # list of subprocesses (both started and waiting)
self._procs: Dict[int, ForkProcess] = {} self._procs: Dict[int, Any] = {}
# list of receiving pipe connections of running subprocesses # list of receiving pipe connections of running subprocesses
self._precvs: Dict[int, Any] = {} self._precvs: Dict[int, Any] = {}
# list of receiving pipe connections of waiting subprocesses # list of receiving pipe connections of waiting subprocesses
@@ -93,7 +85,7 @@ class ParallelTasks:
self._result_funcs[tid] = result_func or (lambda arg, result: None) self._result_funcs[tid] = result_func or (lambda arg, result: None)
self._args[tid] = arg self._args[tid] = arg
precv, psend = multiprocessing.Pipe(False) precv, psend = multiprocessing.Pipe(False)
context: ForkContext = multiprocessing.get_context('fork') context: Any = multiprocessing.get_context('fork')
proc = context.Process(target=self._process, args=(psend, task_func, arg)) proc = context.Process(target=self._process, args=(psend, task_func, arg))
self._procs[tid] = proc self._procs[tid] = proc
self._precvsWaiting[tid] = precv self._precvsWaiting[tid] = precv

View File

@@ -5,27 +5,14 @@ import typing
import warnings import warnings
from struct import Struct from struct import Struct
from types import TracebackType from types import TracebackType
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, TypeVar, Union from typing import (Any, Callable, Dict, ForwardRef, Generator, List, Optional, Tuple, Type,
TypeVar, Union)
from docutils import nodes from docutils import nodes
from docutils.parsers.rst.states import Inliner from docutils.parsers.rst.states import Inliner
from sphinx.deprecation import RemovedInSphinx70Warning from sphinx.deprecation import RemovedInSphinx70Warning
if sys.version_info > (3, 7):
from typing import ForwardRef
else:
from typing import _ForwardRef # type: ignore
class ForwardRef:
"""A pseudo ForwardRef class for py36."""
def __init__(self, arg: Any, is_argument: bool = True) -> None:
self.arg = arg
def _evaluate(self, globalns: Dict, localns: Dict) -> Any:
ref = _ForwardRef(self.arg)
return ref._eval_type(globalns, localns)
try: try:
from types import UnionType # type: ignore # python 3.10 or above from types import UnionType # type: ignore # python 3.10 or above
except ImportError: except ImportError:
@@ -137,7 +124,7 @@ def restify(cls: Optional[Type], mode: str = 'fully-qualified-except-typing') ->
elif is_invalid_builtin_class(cls): elif is_invalid_builtin_class(cls):
return ':py:class:`%s%s`' % (modprefix, INVALID_BUILTIN_CLASSES[cls]) return ':py:class:`%s%s`' % (modprefix, INVALID_BUILTIN_CLASSES[cls])
elif inspect.isNewType(cls): elif inspect.isNewType(cls):
if sys.version_info > (3, 10): if sys.version_info[:2] >= (3, 10):
# newtypes have correct module info since Python 3.10+ # newtypes have correct module info since Python 3.10+
return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls.__name__) return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls.__name__)
else: else:
@@ -211,7 +198,7 @@ def _restify_py37(cls: Optional[Type], mode: str = 'fully-qualified-except-typin
return text return text
elif isinstance(cls, typing._SpecialForm): elif isinstance(cls, typing._SpecialForm):
return ':py:obj:`~%s.%s`' % (cls.__module__, cls._name) return ':py:obj:`~%s.%s`' % (cls.__module__, cls._name)
elif sys.version_info >= (3, 11) and cls is typing.Any: elif sys.version_info[:2] >= (3, 11) and cls is typing.Any:
# handle bpo-46998 # handle bpo-46998
return f':py:obj:`~{cls.__module__}.{cls.__name__}`' return f':py:obj:`~{cls.__module__}.{cls.__name__}`'
elif hasattr(cls, '__qualname__'): elif hasattr(cls, '__qualname__'):
@@ -362,7 +349,7 @@ def stringify(annotation: Any, mode: str = 'fully-qualified-except-typing') -> s
else: else:
return modprefix + '.'.join([annotation.__module__, annotation.__name__]) return modprefix + '.'.join([annotation.__module__, annotation.__name__])
elif inspect.isNewType(annotation): elif inspect.isNewType(annotation):
if sys.version_info > (3, 10): if sys.version_info[:2] >= (3, 10):
# newtypes have correct module info since Python 3.10+ # newtypes have correct module info since Python 3.10+
return modprefix + '%s.%s' % (annotation.__module__, annotation.__name__) return modprefix + '%s.%s' % (annotation.__module__, annotation.__name__)
else: else:

View File

@@ -1,7 +1,6 @@
"""Test all builders.""" """Test all builders."""
import sys import sys
from textwrap import dedent
from unittest import mock from unittest import mock
import pytest import pytest
@@ -19,7 +18,7 @@ def request_session_head(url, **kwargs):
@pytest.fixture @pytest.fixture
def nonascii_srcdir(request, rootdir, sphinx_test_tempdir): def nonascii_srcdir(request, rootdir, sphinx_test_tempdir):
# If supported, build in a non-ASCII source dir # Build in a non-ASCII source dir
test_name = '\u65e5\u672c\u8a9e' test_name = '\u65e5\u672c\u8a9e'
basedir = sphinx_test_tempdir / request.node.originalname basedir = sphinx_test_tempdir / request.node.originalname
srcdir = basedir / test_name srcdir = basedir / test_name
@@ -27,18 +26,17 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir):
(rootdir / 'test-root').copytree(srcdir) (rootdir / 'test-root').copytree(srcdir)
# add a doc with a non-ASCII file name to the source dir # add a doc with a non-ASCII file name to the source dir
(srcdir / (test_name + '.txt')).write_text(dedent(""" (srcdir / (test_name + '.txt')).write_text("""
nonascii file name page nonascii file name page
======================= =======================
"""), encoding='utf8') """, encoding='utf8')
root_doc = srcdir / 'index.txt' root_doc = srcdir / 'index.txt'
root_doc.write_text(root_doc.read_text(encoding='utf8') + dedent(""" root_doc.write_text(root_doc.read_text(encoding='utf8') + f"""
.. toctree:: .. toctree::
%(test_name)s/%(test_name)s
""" % {'test_name': test_name}), encoding='utf8')
{test_name}/{test_name}
""", encoding='utf8')
return srcdir return srcdir

View File

@@ -365,7 +365,7 @@ def test_parse_annotation_suppress(app):
assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="typing.Dict") assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="typing.Dict")
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 7), reason='python 3.8+ is required.')
def test_parse_annotation_Literal(app): def test_parse_annotation_Literal(app):
doctree = _parse_annotation("Literal[True, False]", app.env) doctree = _parse_annotation("Literal[True, False]", app.env)
assert_node(doctree, ([pending_xref, "Literal"], assert_node(doctree, ([pending_xref, "Literal"],
@@ -480,7 +480,7 @@ def test_pyfunction_with_binary_operators(app):
[nodes.inline, "2**64"])])]) [nodes.inline, "2**64"])])])
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 7), reason='python 3.8+ is required.')
def test_pyfunction_signature_full_py38(app): def test_pyfunction_signature_full_py38(app):
# case: separator at head # case: separator at head
text = ".. py:function:: hello(*, a)" text = ".. py:function:: hello(*, a)"
@@ -516,7 +516,7 @@ def test_pyfunction_signature_full_py38(app):
[desc_parameter, desc_sig_operator, "/"])]) [desc_parameter, desc_sig_operator, "/"])])
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 7), reason='python 3.8+ is required.')
def test_pyfunction_with_number_literals(app): def test_pyfunction_with_number_literals(app):
text = ".. py:function:: hello(age=0x10, height=1_6_0)" text = ".. py:function:: hello(age=0x10, height=1_6_0)"
doctree = restructuredtext.parse(app, text) doctree = restructuredtext.parse(app, text)

View File

@@ -1072,7 +1072,7 @@ def test_autodoc_descriptor(app):
] ]
@pytest.mark.skipif(sys.version_info < (3, 8), @pytest.mark.skipif(sys.version_info[:2] <= (3, 7),
reason='cached_property is available since python3.8.') reason='cached_property is available since python3.8.')
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_cached_property(app): def test_autodoc_cached_property(app):
@@ -1404,7 +1404,7 @@ def test_enum_class(app):
options = {"members": None} options = {"members": None}
actual = do_autodoc(app, 'class', 'target.enums.EnumCls', options) actual = do_autodoc(app, 'class', 'target.enums.EnumCls', options)
if sys.version_info > (3, 11): if sys.version_info[:2] >= (3, 11):
args = ('(value, names=None, *, module=None, qualname=None, ' args = ('(value, names=None, *, module=None, qualname=None, '
'type=None, start=1, boundary=None)') 'type=None, start=1, boundary=None)')
else: else:
@@ -1976,7 +1976,7 @@ def test_autodoc_TypeVar(app):
] ]
@pytest.mark.skipif(sys.version_info < (3, 9), reason='py39+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 8), reason='py39+ is required.')
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_Annotated(app): def test_autodoc_Annotated(app):
options = {"members": None} options = {"members": None}
@@ -2059,7 +2059,7 @@ def test_singledispatch(app):
] ]
@pytest.mark.skipif(sys.version_info < (3, 8), @pytest.mark.skipif(sys.version_info[:2] <= (3, 7),
reason='singledispatchmethod is available since python3.8') reason='singledispatchmethod is available since python3.8')
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_singledispatchmethod(app): def test_singledispatchmethod(app):
@@ -2088,7 +2088,7 @@ def test_singledispatchmethod(app):
] ]
@pytest.mark.skipif(sys.version_info < (3, 8), @pytest.mark.skipif(sys.version_info[:2] <= (3, 7),
reason='singledispatchmethod is available since python3.8') reason='singledispatchmethod is available since python3.8')
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_singledispatchmethod_automethod(app): def test_singledispatchmethod_automethod(app):
@@ -2108,7 +2108,7 @@ def test_singledispatchmethod_automethod(app):
] ]
@pytest.mark.skipif(sys.version_info > (3, 11), @pytest.mark.skipif(sys.version_info[:2] >= (3, 11),
reason=('cython does not support python-3.11 yet. ' reason=('cython does not support python-3.11 yet. '
'see https://github.com/cython/cython/issues/4365')) 'see https://github.com/cython/cython/issues/4365'))
@pytest.mark.skipif(pyximport is None, reason='cython is not installed') @pytest.mark.skipif(pyximport is None, reason='cython is not installed')
@@ -2142,7 +2142,7 @@ def test_cython(app):
] ]
@pytest.mark.skipif(sys.version_info < (3, 8), @pytest.mark.skipif(sys.version_info[:2] <= (3, 7),
reason='typing.final is available since python3.8') reason='typing.final is available since python3.8')
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_final(app): def test_final(app):

View File

@@ -40,7 +40,7 @@ def test_class_properties(app):
] ]
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 7), reason='python 3.8+ is required.')
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_cached_properties(app): def test_cached_properties(app):
actual = do_autodoc(app, 'property', 'target.cached_property.Foo.prop') actual = do_autodoc(app, 'property', 'target.cached_property.Foo.prop')

View File

@@ -661,7 +661,7 @@ def test_mocked_module_imports(app, warning):
@pytest.mark.sphinx('html', testroot='ext-autodoc', @pytest.mark.sphinx('html', testroot='ext-autodoc',
confoverrides={'autodoc_typehints': "signature"}) confoverrides={'autodoc_typehints': "signature"})
def test_autodoc_typehints_signature(app): def test_autodoc_typehints_signature(app):
if sys.version_info < (3, 11): if sys.version_info[:2] <= (3, 10):
type_o = "~typing.Optional[~typing.Any]" type_o = "~typing.Optional[~typing.Any]"
else: else:
type_o = "~typing.Any" type_o = "~typing.Any"
@@ -1399,7 +1399,7 @@ def test_autodoc_typehints_description_and_type_aliases(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc', @pytest.mark.sphinx('html', testroot='ext-autodoc',
confoverrides={'autodoc_typehints_format': "fully-qualified"}) confoverrides={'autodoc_typehints_format': "fully-qualified"})
def test_autodoc_typehints_format_fully_qualified(app): def test_autodoc_typehints_format_fully_qualified(app):
if sys.version_info < (3, 11): if sys.version_info[:2] <= (3, 10):
type_o = "typing.Optional[typing.Any]" type_o = "typing.Optional[typing.Any]"
else: else:
type_o = "typing.Any" type_o = "typing.Any"

View File

@@ -10,7 +10,7 @@ from .test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc', @pytest.mark.sphinx('html', testroot='ext-autodoc',
confoverrides={'autodoc_preserve_defaults': True}) confoverrides={'autodoc_preserve_defaults': True})
def test_preserve_defaults(app): def test_preserve_defaults(app):
if sys.version_info < (3, 8): if sys.version_info[:2] <= (3, 7):
color = "16777215" color = "16777215"
else: else:
color = "0xFFFFFF" color = "0xFFFFFF"

View File

@@ -185,7 +185,7 @@ def test_ModuleAnalyzer_find_attr_docs():
'Qux.attr2': 17} 'Qux.attr2': 17}
@pytest.mark.skipif(sys.version_info < (3, 8), @pytest.mark.skipif(sys.version_info[:2] <= (3, 7),
reason='posonlyargs are available since python3.8.') reason='posonlyargs are available since python3.8.')
def test_ModuleAnalyzer_find_attr_docs_for_posonlyargs_method(): def test_ModuleAnalyzer_find_attr_docs_for_posonlyargs_method():
code = ('class Foo(object):\n' code = ('class Foo(object):\n'

View File

@@ -58,7 +58,7 @@ def test_unparse_None():
assert ast.unparse(None) is None assert ast.unparse(None) is None
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 7), reason='python 3.8+ is required.')
@pytest.mark.parametrize('source,expected', [ @pytest.mark.parametrize('source,expected', [
("lambda x=0, /, y=1, *args, z, **kwargs: x + y + z", ("lambda x=0, /, y=1, *args, z, **kwargs: x + y + z",
"lambda x=0, /, y=1, *args, z, **kwargs: ..."), # posonlyargs "lambda x=0, /, y=1, *args, z, **kwargs: ..."), # posonlyargs

View File

@@ -186,7 +186,7 @@ def test_signature_annotations():
# Space around '=' for defaults # Space around '=' for defaults
sig = inspect.signature(f7) sig = inspect.signature(f7)
if sys.version_info < (3, 11): if sys.version_info[:2] <= (3, 10):
assert stringify_signature(sig) == '(x: typing.Optional[int] = None, y: dict = {}) -> None' assert stringify_signature(sig) == '(x: typing.Optional[int] = None, y: dict = {}) -> None'
else: else:
assert stringify_signature(sig) == '(x: int = None, y: dict = {}) -> None' assert stringify_signature(sig) == '(x: int = None, y: dict = {}) -> None'
@@ -260,20 +260,20 @@ def test_signature_annotations():
# show_return_annotation is False # show_return_annotation is False
sig = inspect.signature(f7) sig = inspect.signature(f7)
if sys.version_info < (3, 11): if sys.version_info[:2] <= (3, 10):
assert stringify_signature(sig, show_return_annotation=False) == '(x: typing.Optional[int] = None, y: dict = {})' assert stringify_signature(sig, show_return_annotation=False) == '(x: typing.Optional[int] = None, y: dict = {})'
else: else:
assert stringify_signature(sig, show_return_annotation=False) == '(x: int = None, y: dict = {})' assert stringify_signature(sig, show_return_annotation=False) == '(x: int = None, y: dict = {})'
# unqualified_typehints is True # unqualified_typehints is True
sig = inspect.signature(f7) sig = inspect.signature(f7)
if sys.version_info < (3, 11): if sys.version_info[:2] <= (3, 10):
assert stringify_signature(sig, unqualified_typehints=True) == '(x: ~typing.Optional[int] = None, y: dict = {}) -> None' assert stringify_signature(sig, unqualified_typehints=True) == '(x: ~typing.Optional[int] = None, y: dict = {}) -> None'
else: else:
assert stringify_signature(sig, unqualified_typehints=True) == '(x: int = None, y: dict = {}) -> None' assert stringify_signature(sig, unqualified_typehints=True) == '(x: int = None, y: dict = {}) -> None'
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 7), reason='python 3.8+ is required.')
@pytest.mark.sphinx(testroot='ext-autodoc') @pytest.mark.sphinx(testroot='ext-autodoc')
def test_signature_annotations_py38(app): def test_signature_annotations_py38(app):
from target.pep570 import bar, baz, foo, qux from target.pep570 import bar, baz, foo, qux
@@ -373,7 +373,7 @@ def test_signature_from_str_kwonly_args():
assert sig.parameters['b'].default == Parameter.empty assert sig.parameters['b'].default == Parameter.empty
@pytest.mark.skipif(sys.version_info < (3, 8), @pytest.mark.skipif(sys.version_info[:2] <= (3, 7),
reason='python-3.8 or above is required') reason='python-3.8 or above is required')
def test_signature_from_str_positionaly_only_args(): def test_signature_from_str_positionaly_only_args():
sig = inspect.signature_from_str('(a, b=0, /, c=1)') sig = inspect.signature_from_str('(a, b=0, /, c=1)')

View File

@@ -71,7 +71,7 @@ def test_restify_type_hints_containers():
":py:class:`str`]") ":py:class:`str`]")
assert restify(Tuple[str, ...]) == ":py:class:`~typing.Tuple`\\ [:py:class:`str`, ...]" assert restify(Tuple[str, ...]) == ":py:class:`~typing.Tuple`\\ [:py:class:`str`, ...]"
if sys.version_info < (3, 11): if sys.version_info[:2] <= (3, 10):
assert restify(Tuple[()]) == ":py:class:`~typing.Tuple`\\ [()]" assert restify(Tuple[()]) == ":py:class:`~typing.Tuple`\\ [()]"
else: else:
assert restify(Tuple[()]) == ":py:class:`~typing.Tuple`" assert restify(Tuple[()]) == ":py:class:`~typing.Tuple`"
@@ -89,6 +89,7 @@ def test_restify_type_hints_containers():
def test_restify_type_hints_Callable(): def test_restify_type_hints_Callable():
assert restify(Callable) == ":py:class:`~typing.Callable`" assert restify(Callable) == ":py:class:`~typing.Callable`"
assert restify(Callable[[str], int]) == (":py:class:`~typing.Callable`\\ " assert restify(Callable[[str], int]) == (":py:class:`~typing.Callable`\\ "
"[[:py:class:`str`], :py:class:`int`]") "[[:py:class:`str`], :py:class:`int`]")
assert restify(Callable[..., int]) == (":py:class:`~typing.Callable`\\ " assert restify(Callable[..., int]) == (":py:class:`~typing.Callable`\\ "
@@ -100,19 +101,20 @@ def test_restify_type_hints_Union():
assert restify(Union[str, None]) == ":py:obj:`~typing.Optional`\\ [:py:class:`str`]" assert restify(Union[str, None]) == ":py:obj:`~typing.Optional`\\ [:py:class:`str`]"
assert restify(Union[int, str]) == (":py:obj:`~typing.Union`\\ " assert restify(Union[int, str]) == (":py:obj:`~typing.Union`\\ "
"[:py:class:`int`, :py:class:`str`]") "[:py:class:`int`, :py:class:`str`]")
assert restify(Union[int, Integral]) == (":py:obj:`~typing.Union`\\ " assert restify(Union[int, Integral]) == (":py:obj:`~typing.Union`\\ "
"[:py:class:`int`, :py:class:`numbers.Integral`]") "[:py:class:`int`, :py:class:`numbers.Integral`]")
assert restify(Union[int, Integral], "smart") == (":py:obj:`~typing.Union`\\ " assert restify(Union[int, Integral], "smart") == (":py:obj:`~typing.Union`\\ "
"[:py:class:`int`," "[:py:class:`int`,"
" :py:class:`~numbers.Integral`]") " :py:class:`~numbers.Integral`]")
assert (restify(Union[MyClass1, MyClass2]) == (":py:obj:`~typing.Union`\\ " assert (restify(Union[MyClass1, MyClass2]) ==
"[:py:class:`tests.test_util_typing.MyClass1`, " (":py:obj:`~typing.Union`\\ "
":py:class:`tests.test_util_typing.<MyClass2>`]")) "[:py:class:`tests.test_util_typing.MyClass1`, "
assert (restify(Union[MyClass1, MyClass2], "smart") == (":py:obj:`~typing.Union`\\ " ":py:class:`tests.test_util_typing.<MyClass2>`]"))
"[:py:class:`~tests.test_util_typing.MyClass1`," assert (restify(Union[MyClass1, MyClass2], "smart") ==
" :py:class:`~tests.test_util_typing.<MyClass2>`]")) (":py:obj:`~typing.Union`\\ "
"[:py:class:`~tests.test_util_typing.MyClass1`,"
" :py:class:`~tests.test_util_typing.<MyClass2>`]"))
def test_restify_type_hints_typevars(): def test_restify_type_hints_typevars():
@@ -132,7 +134,7 @@ def test_restify_type_hints_typevars():
assert restify(List[T]) == ":py:class:`~typing.List`\\ [:py:obj:`tests.test_util_typing.T`]" assert restify(List[T]) == ":py:class:`~typing.List`\\ [:py:obj:`tests.test_util_typing.T`]"
assert restify(List[T], "smart") == ":py:class:`~typing.List`\\ [:py:obj:`~tests.test_util_typing.T`]" assert restify(List[T], "smart") == ":py:class:`~typing.List`\\ [:py:obj:`~tests.test_util_typing.T`]"
if sys.version_info >= (3, 10): if sys.version_info[:2] >= (3, 10):
assert restify(MyInt) == ":py:class:`tests.test_util_typing.MyInt`" assert restify(MyInt) == ":py:class:`tests.test_util_typing.MyInt`"
assert restify(MyInt, "smart") == ":py:class:`~tests.test_util_typing.MyInt`" assert restify(MyInt, "smart") == ":py:class:`~tests.test_util_typing.MyInt`"
else: else:
@@ -160,13 +162,13 @@ def test_restify_type_ForwardRef():
assert restify(ForwardRef("myint")) == ":py:class:`myint`" assert restify(ForwardRef("myint")) == ":py:class:`myint`"
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 7), reason='python 3.8+ is required.')
def test_restify_type_Literal(): def test_restify_type_Literal():
from typing import Literal # type: ignore from typing import Literal # type: ignore
assert restify(Literal[1, "2", "\r"]) == ":py:obj:`~typing.Literal`\\ [1, '2', '\\r']" assert restify(Literal[1, "2", "\r"]) == ":py:obj:`~typing.Literal`\\ [1, '2', '\\r']"
@pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 8), reason='python 3.9+ is required.')
def test_restify_pep_585(): def test_restify_pep_585():
assert restify(list[str]) == ":py:class:`list`\\ [:py:class:`str`]" # type: ignore assert restify(list[str]) == ":py:class:`list`\\ [:py:class:`str`]" # type: ignore
assert restify(dict[str, str]) == (":py:class:`dict`\\ " # type: ignore assert restify(dict[str, str]) == (":py:class:`dict`\\ " # type: ignore
@@ -176,7 +178,7 @@ def test_restify_pep_585():
"[:py:class:`int`, ...]]") "[:py:class:`int`, ...]]")
@pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 9), reason='python 3.10+ is required.')
def test_restify_type_union_operator(): def test_restify_type_union_operator():
assert restify(int | None) == ":py:class:`int` | :py:obj:`None`" # type: ignore assert restify(int | None) == ":py:class:`int` | :py:obj:`None`" # type: ignore
assert restify(int | str) == ":py:class:`int` | :py:class:`str`" # type: ignore assert restify(int | str) == ":py:class:`int` | :py:class:`str`" # type: ignore
@@ -250,7 +252,7 @@ def test_stringify_type_hints_containers():
assert stringify(Tuple[str, ...], "fully-qualified") == "typing.Tuple[str, ...]" assert stringify(Tuple[str, ...], "fully-qualified") == "typing.Tuple[str, ...]"
assert stringify(Tuple[str, ...], "smart") == "~typing.Tuple[str, ...]" assert stringify(Tuple[str, ...], "smart") == "~typing.Tuple[str, ...]"
if sys.version_info < (3, 11): if sys.version_info[:2] <= (3, 10):
assert stringify(Tuple[()]) == "Tuple[()]" assert stringify(Tuple[()]) == "Tuple[()]"
assert stringify(Tuple[()], "fully-qualified") == "typing.Tuple[()]" assert stringify(Tuple[()], "fully-qualified") == "typing.Tuple[()]"
assert stringify(Tuple[()], "smart") == "~typing.Tuple[()]" assert stringify(Tuple[()], "smart") == "~typing.Tuple[()]"
@@ -272,7 +274,7 @@ def test_stringify_type_hints_containers():
assert stringify(Generator[None, None, None], "smart") == "~typing.Generator[None, None, None]" assert stringify(Generator[None, None, None], "smart") == "~typing.Generator[None, None, None]"
@pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 8), reason='python 3.9+ is required.')
def test_stringify_type_hints_pep_585(): def test_stringify_type_hints_pep_585():
assert stringify(list[int]) == "list[int]" assert stringify(list[int]) == "list[int]"
assert stringify(list[int], "smart") == "list[int]" assert stringify(list[int], "smart") == "list[int]"
@@ -299,7 +301,7 @@ def test_stringify_type_hints_pep_585():
assert stringify(type[int], "smart") == "type[int]" assert stringify(type[int], "smart") == "type[int]"
@pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 8), reason='python 3.9+ is required.')
def test_stringify_Annotated(): def test_stringify_Annotated():
from typing import Annotated # type: ignore from typing import Annotated # type: ignore
assert stringify(Annotated[str, "foo", "bar"]) == "str" assert stringify(Annotated[str, "foo", "bar"]) == "str"
@@ -379,7 +381,7 @@ def test_stringify_type_hints_typevars():
assert stringify(List[T]) == "List[tests.test_util_typing.T]" assert stringify(List[T]) == "List[tests.test_util_typing.T]"
assert stringify(List[T], "smart") == "~typing.List[~tests.test_util_typing.T]" assert stringify(List[T], "smart") == "~typing.List[~tests.test_util_typing.T]"
if sys.version_info >= (3, 10): if sys.version_info[:2] >= (3, 10):
assert stringify(MyInt) == "tests.test_util_typing.MyInt" assert stringify(MyInt) == "tests.test_util_typing.MyInt"
assert stringify(MyInt, "smart") == "~tests.test_util_typing.MyInt" assert stringify(MyInt, "smart") == "~tests.test_util_typing.MyInt"
else: else:
@@ -406,7 +408,7 @@ def test_stringify_type_hints_alias():
assert stringify(MyTuple, "smart") == "~typing.Tuple[str, str]" # type: ignore assert stringify(MyTuple, "smart") == "~typing.Tuple[str, str]" # type: ignore
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 7), reason='python 3.8+ is required.')
def test_stringify_type_Literal(): def test_stringify_type_Literal():
from typing import Literal # type: ignore from typing import Literal # type: ignore
assert stringify(Literal[1, "2", "\r"]) == "Literal[1, '2', '\\r']" assert stringify(Literal[1, "2", "\r"]) == "Literal[1, '2', '\\r']"
@@ -414,7 +416,7 @@ def test_stringify_type_Literal():
assert stringify(Literal[1, "2", "\r"], "smart") == "~typing.Literal[1, '2', '\\r']" assert stringify(Literal[1, "2", "\r"], "smart") == "~typing.Literal[1, '2', '\\r']"
@pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.') @pytest.mark.skipif(sys.version_info[:2] <= (3, 9), reason='python 3.10+ is required.')
def test_stringify_type_union_operator(): def test_stringify_type_union_operator():
assert stringify(int | None) == "int | None" # type: ignore assert stringify(int | None) == "int | None" # type: ignore
assert stringify(int | None, "smart") == "int | None" # type: ignore assert stringify(int | None, "smart") == "int | None" # type: ignore

View File

@@ -1,6 +1,6 @@
[tox] [tox]
minversion = 2.4.0 minversion = 2.4.0
envlist = docs,flake8,mypy,twine,py{36,37,38,39,310,311},du{14,15,16,17,18,19} envlist = docs,flake8,mypy,twine,py{37,38,39,310,311},du{14,15,16,17,18,19}
isolated_build = True isolated_build = True
[testenv] [testenv]
@@ -16,7 +16,7 @@ passenv =
EPUBCHECK_PATH EPUBCHECK_PATH
TERM TERM
description = description =
py{36,37,38,39,310,311}: Run unit tests against {envname}. py{37,38,39,310,311}: Run unit tests against {envname}.
du{14,15,16,17,18,19}: Run unit tests with the given version of docutils. du{14,15,16,17,18,19}: Run unit tests with the given version of docutils.
deps = deps =
du14: docutils==0.14.* du14: docutils==0.14.*