diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a350bde73..4889bb9a8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ permissions: jobs: ubuntu: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 name: Python ${{ matrix.python }} (${{ matrix.docutils }}) strategy: fail-fast: false diff --git a/CHANGES b/CHANGES index 069503d7e..ae031c69a 100644 --- a/CHANGES +++ b/CHANGES @@ -4,7 +4,7 @@ Release 6.0.0 (in development) Dependencies ------------ -* Drop python 3.6 support +* #10468: Drop Python 3.6 support Incompatible changes -------------------- diff --git a/doc/internals/release-process.rst b/doc/internals/release-process.rst index 0d89831bc..3463fc1eb 100644 --- a/doc/internals/release-process.rst +++ b/doc/internals/release-process.rst @@ -113,13 +113,15 @@ April 2023 and shipping Python 3.8. This is a summary table with the current policy: -========== ========= ====== -Date Ubuntu Python -========== ========= ====== -April 2023 20.04 LTS 3.8+ ----------- --------- ------ -April 2025 22.04 LTS 3.10+ -========== ========= ====== +========== ========= ====== ====== +Date Ubuntu Python Sphinx +========== ========= ====== ====== +April 2021 18.04 LTS 3.6+ 4, 5 +---------- --------- ------ ------ +April 2023 20.04 LTS 3.8+ 6, 7 +---------- --------- ------ ------ +April 2025 22.04 LTS 3.10+ 8 +========== ========= ====== ====== Release procedures ------------------ diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 8bf3ceff6..7ce7fb8b3 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -204,7 +204,7 @@ def _parse_annotation(annotation: str, env: BuildEnvironment) -> List[Node]: return result else: - if sys.version_info < (3, 8): + if sys.version_info[:2] <= (3, 7): if isinstance(node, ast.Bytes): return [addnodes.desc_sig_literal_string('', repr(node.s))] elif isinstance(node, ast.Ellipsis): diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index cb230f3e2..546aa3fc5 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -820,6 +820,8 @@ class Documenter: # sort by group; alphabetically within groups documenters.sort(key=lambda e: (e[0].member_order, e[0].name)) elif order == 'bysource': + # By default, member discovery order matches source order, + # as dicts are insertion-ordered from Python 3.7. if self.analyzer: # sort by source order, by virtue of the module analyzer tagorder = self.analyzer.tagorder @@ -828,11 +830,6 @@ class Documenter: fullname = entry[0].name.split('::')[1] return tagorder.get(fullname, len(tagorder)) 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 documenters.sort(key=lambda e: e[0].name) diff --git a/sphinx/ext/autodoc/preserve_defaults.py b/sphinx/ext/autodoc/preserve_defaults.py index a12f42fcf..4230335d7 100644 --- a/sphinx/ext/autodoc/preserve_defaults.py +++ b/sphinx/ext/autodoc/preserve_defaults.py @@ -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]: try: - if sys.version_info < (3, 8): # only for py38+ + if sys.version_info[:2] <= (3, 7): # only for py38+ return None - elif position.lineno == position.end_lineno: + if position.lineno == position.end_lineno: line = lines[position.lineno - 1] return line[position.col_offset:position.end_col_offset] else: diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 6989c9d66..8472a528a 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -597,8 +597,6 @@ class GoogleDocstring: self._parsed_lines = self._consume_empty() 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] = [] try: res = self._parse_attribute_docstring() diff --git a/sphinx/pycode/ast.py b/sphinx/pycode/ast.py index d4646f0b7..c0d6fc463 100644 --- a/sphinx/pycode/ast.py +++ b/sphinx/pycode/ast.py @@ -3,7 +3,7 @@ import sys from typing import Dict, List, Optional, Type, overload -if sys.version_info > (3, 8): +if sys.version_info[:2] >= (3, 8): import ast else: try: @@ -159,7 +159,7 @@ class _UnparseVisitor(ast.NodeVisitor): if node.value is Ellipsis: return "..." 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 else: return repr(node.value) @@ -219,7 +219,7 @@ class _UnparseVisitor(ast.NodeVisitor): else: 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 def visit_Bytes(self, node: ast.Bytes) -> str: return repr(node.s) diff --git a/sphinx/util/images.py b/sphinx/util/images.py index 2def252f4..2e13957f6 100644 --- a/sphinx/util/images.py +++ b/sphinx/util/images.py @@ -107,5 +107,5 @@ def test_svg(h: bytes, f: Optional[BinaryIO]) -> Optional[str]: # 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) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 76e3ca3ac..c3f4d1af3 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -12,7 +12,8 @@ from functools import partial, partialmethod from importlib import import_module from inspect import Parameter, isclass, ismethod, ismethoddescriptor, ismodule # NOQA 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 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 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__) 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: """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) else: __module__ = safe_getattr(obj, '__module__', None) @@ -335,19 +329,22 @@ def iscoroutinefunction(obj: Any) -> bool: return False -def isasyncgenfunction(obj: Any) -> bool: - """Check if the object is async-gen function.""" - if hasattr(obj, '__code__') and inspect.isasyncgenfunction(obj): - # check obj.__code__ because isasyncgenfunction() crashes for custom method-like - # objects on python3.7 (see https://github.com/sphinx-doc/sphinx/issues/9838) - return True - else: - return False +if sys.version_info[:2] <= (3, 7): + def isasyncgenfunction(obj: Any) -> bool: + """Check if the object is async-gen function.""" + if hasattr(obj, '__code__') and inspect.isasyncgenfunction(obj): + # check obj.__code__ because isasyncgenfunction() crashes for custom method-like + # objects on python3.7 (see https://github.com/sphinx-doc/sphinx/issues/9838) + return True + else: + return False +else: + isasyncgenfunction = inspect.isasyncgenfunction def isproperty(obj: Any) -> bool: """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 if isinstance(obj, cached_property): return True @@ -619,7 +616,7 @@ def evaluate_signature(sig: inspect.Signature, globalns: Optional[Dict] = None, """Evaluate unresolved type annotations in a signature object.""" def evaluate_forwardref(ref: ForwardRef, globalns: Dict, localns: Dict) -> Any: """Evaluate a forward reference.""" - if sys.version_info > (3, 9): + if sys.version_info[:2] >= (3, 9): return ref._evaluate(globalns, localns, frozenset()) else: return ref._evaluate(globalns, localns) diff --git a/sphinx/util/parallel.py b/sphinx/util/parallel.py index 16a95e039..201e5b5e2 100644 --- a/sphinx/util/parallel.py +++ b/sphinx/util/parallel.py @@ -1,7 +1,6 @@ """Parallel building utilities.""" import os -import sys import time import traceback from math import sqrt @@ -18,13 +17,6 @@ from sphinx.util import logging 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 parallel_available = multiprocessing and os.name == 'posix' @@ -59,7 +51,7 @@ class ParallelTasks: # task arguments self._args: Dict[int, Optional[List[Any]]] = {} # 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 self._precvs: Dict[int, Any] = {} # 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._args[tid] = arg 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)) self._procs[tid] = proc self._precvsWaiting[tid] = precv diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index a74ed30ca..b67ec3cd0 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -5,27 +5,14 @@ import typing import warnings from struct import Struct 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.parsers.rst.states import Inliner 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: from types import UnionType # type: ignore # python 3.10 or above except ImportError: @@ -137,7 +124,7 @@ def restify(cls: Optional[Type], mode: str = 'fully-qualified-except-typing') -> elif is_invalid_builtin_class(cls): return ':py:class:`%s%s`' % (modprefix, INVALID_BUILTIN_CLASSES[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+ return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls.__name__) else: @@ -211,7 +198,7 @@ def _restify_py37(cls: Optional[Type], mode: str = 'fully-qualified-except-typin return text elif isinstance(cls, typing._SpecialForm): 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 return f':py:obj:`~{cls.__module__}.{cls.__name__}`' elif hasattr(cls, '__qualname__'): @@ -362,7 +349,7 @@ def stringify(annotation: Any, mode: str = 'fully-qualified-except-typing') -> s else: return modprefix + '.'.join([annotation.__module__, annotation.__name__]) 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+ return modprefix + '%s.%s' % (annotation.__module__, annotation.__name__) else: diff --git a/tests/test_build.py b/tests/test_build.py index c5ce79da3..77211bb4d 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -1,7 +1,6 @@ """Test all builders.""" import sys -from textwrap import dedent from unittest import mock import pytest @@ -19,7 +18,7 @@ def request_session_head(url, **kwargs): @pytest.fixture 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' basedir = sphinx_test_tempdir / request.node.originalname srcdir = basedir / test_name @@ -27,18 +26,17 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir): (rootdir / 'test-root').copytree(srcdir) # add a doc with a non-ASCII file name to the source dir - (srcdir / (test_name + '.txt')).write_text(dedent(""" - nonascii file name page - ======================= - """), encoding='utf8') + (srcdir / (test_name + '.txt')).write_text(""" +nonascii file name page +======================= +""", encoding='utf8') root_doc = srcdir / 'index.txt' - root_doc.write_text(root_doc.read_text(encoding='utf8') + dedent(""" - .. toctree:: - - %(test_name)s/%(test_name)s - """ % {'test_name': test_name}), encoding='utf8') + root_doc.write_text(root_doc.read_text(encoding='utf8') + f""" +.. toctree:: +{test_name}/{test_name} +""", encoding='utf8') return srcdir diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 11e66fd27..a4e276226 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -365,7 +365,7 @@ def test_parse_annotation_suppress(app): 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): doctree = _parse_annotation("Literal[True, False]", app.env) assert_node(doctree, ([pending_xref, "Literal"], @@ -480,7 +480,7 @@ def test_pyfunction_with_binary_operators(app): [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): # case: separator at head text = ".. py:function:: hello(*, a)" @@ -516,7 +516,7 @@ def test_pyfunction_signature_full_py38(app): [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): text = ".. py:function:: hello(age=0x10, height=1_6_0)" doctree = restructuredtext.parse(app, text) diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index c994282f6..c980b5b26 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -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.') @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_cached_property(app): @@ -1404,7 +1404,7 @@ def test_enum_class(app): options = {"members": None} 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, ' 'type=None, start=1, boundary=None)') 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') def test_autodoc_Annotated(app): 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') @pytest.mark.sphinx('html', testroot='ext-autodoc') 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') @pytest.mark.sphinx('html', testroot='ext-autodoc') 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. ' 'see https://github.com/cython/cython/issues/4365')) @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') @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_final(app): diff --git a/tests/test_ext_autodoc_autoproperty.py b/tests/test_ext_autodoc_autoproperty.py index 3d50020b3..bf54be36c 100644 --- a/tests/test_ext_autodoc_autoproperty.py +++ b/tests/test_ext_autodoc_autoproperty.py @@ -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') def test_cached_properties(app): actual = do_autodoc(app, 'property', 'target.cached_property.Foo.prop') diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index dbd432c80..d97f3e6f9 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -661,7 +661,7 @@ def test_mocked_module_imports(app, warning): @pytest.mark.sphinx('html', testroot='ext-autodoc', confoverrides={'autodoc_typehints': "signature"}) 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]" else: type_o = "~typing.Any" @@ -1399,7 +1399,7 @@ def test_autodoc_typehints_description_and_type_aliases(app): @pytest.mark.sphinx('html', testroot='ext-autodoc', confoverrides={'autodoc_typehints_format': "fully-qualified"}) 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]" else: type_o = "typing.Any" diff --git a/tests/test_ext_autodoc_preserve_defaults.py b/tests/test_ext_autodoc_preserve_defaults.py index ba5ff6e62..b5548ad25 100644 --- a/tests/test_ext_autodoc_preserve_defaults.py +++ b/tests/test_ext_autodoc_preserve_defaults.py @@ -10,7 +10,7 @@ from .test_ext_autodoc import do_autodoc @pytest.mark.sphinx('html', testroot='ext-autodoc', confoverrides={'autodoc_preserve_defaults': True}) def test_preserve_defaults(app): - if sys.version_info < (3, 8): + if sys.version_info[:2] <= (3, 7): color = "16777215" else: color = "0xFFFFFF" diff --git a/tests/test_pycode.py b/tests/test_pycode.py index 1f9882eb9..cc3966b8d 100644 --- a/tests/test_pycode.py +++ b/tests/test_pycode.py @@ -185,7 +185,7 @@ def test_ModuleAnalyzer_find_attr_docs(): '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.') def test_ModuleAnalyzer_find_attr_docs_for_posonlyargs_method(): code = ('class Foo(object):\n' diff --git a/tests/test_pycode_ast.py b/tests/test_pycode_ast.py index 31018baca..e79e9d99b 100644 --- a/tests/test_pycode_ast.py +++ b/tests/test_pycode_ast.py @@ -58,7 +58,7 @@ def test_unparse_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', [ ("lambda x=0, /, y=1, *args, z, **kwargs: x + y + z", "lambda x=0, /, y=1, *args, z, **kwargs: ..."), # posonlyargs diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 3581b3946..3c2090838 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -186,7 +186,7 @@ def test_signature_annotations(): # Space around '=' for defaults 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' else: assert stringify_signature(sig) == '(x: int = None, y: dict = {}) -> None' @@ -260,20 +260,20 @@ def test_signature_annotations(): # show_return_annotation is False 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 = {})' else: assert stringify_signature(sig, show_return_annotation=False) == '(x: int = None, y: dict = {})' # unqualified_typehints is True 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' else: 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') def test_signature_annotations_py38(app): 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 -@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') def test_signature_from_str_positionaly_only_args(): sig = inspect.signature_from_str('(a, b=0, /, c=1)') diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py index a70f01431..658aa7168 100644 --- a/tests/test_util_typing.py +++ b/tests/test_util_typing.py @@ -71,7 +71,7 @@ def test_restify_type_hints_containers(): ":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`\\ [()]" else: assert restify(Tuple[()]) == ":py:class:`~typing.Tuple`" @@ -89,6 +89,7 @@ def test_restify_type_hints_containers(): def test_restify_type_hints_Callable(): assert restify(Callable) == ":py:class:`~typing.Callable`" + assert restify(Callable[[str], int]) == (":py:class:`~typing.Callable`\\ " "[[:py:class:`str`], :py:class:`int`]") 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[int, str]) == (":py:obj:`~typing.Union`\\ " "[:py:class:`int`, :py:class:`str`]") - assert restify(Union[int, Integral]) == (":py:obj:`~typing.Union`\\ " "[:py:class:`int`, :py:class:`numbers.Integral`]") assert restify(Union[int, Integral], "smart") == (":py:obj:`~typing.Union`\\ " "[:py:class:`int`," " :py:class:`~numbers.Integral`]") - assert (restify(Union[MyClass1, MyClass2]) == (":py:obj:`~typing.Union`\\ " - "[:py:class:`tests.test_util_typing.MyClass1`, " - ":py:class:`tests.test_util_typing.`]")) - assert (restify(Union[MyClass1, MyClass2], "smart") == (":py:obj:`~typing.Union`\\ " - "[:py:class:`~tests.test_util_typing.MyClass1`," - " :py:class:`~tests.test_util_typing.`]")) + assert (restify(Union[MyClass1, MyClass2]) == + (":py:obj:`~typing.Union`\\ " + "[:py:class:`tests.test_util_typing.MyClass1`, " + ":py:class:`tests.test_util_typing.`]")) + assert (restify(Union[MyClass1, MyClass2], "smart") == + (":py:obj:`~typing.Union`\\ " + "[:py:class:`~tests.test_util_typing.MyClass1`," + " :py:class:`~tests.test_util_typing.`]")) 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], "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, "smart") == ":py:class:`~tests.test_util_typing.MyInt`" else: @@ -160,13 +162,13 @@ def test_restify_type_ForwardRef(): 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(): from typing import Literal # type: ignore 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(): assert restify(list[str]) == ":py:class:`list`\\ [:py:class:`str`]" # type: ignore assert restify(dict[str, str]) == (":py:class:`dict`\\ " # type: ignore @@ -176,7 +178,7 @@ def test_restify_pep_585(): "[: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(): assert restify(int | None) == ":py:class:`int` | :py:obj:`None`" # 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, ...], "smart") == "~typing.Tuple[str, ...]" - if sys.version_info < (3, 11): + if sys.version_info[:2] <= (3, 10): assert stringify(Tuple[()]) == "Tuple[()]" assert stringify(Tuple[()], "fully-qualified") == "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]" -@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(): assert stringify(list[int]) == "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]" -@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(): from typing import Annotated # type: ignore 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], "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, "smart") == "~tests.test_util_typing.MyInt" else: @@ -406,7 +408,7 @@ def test_stringify_type_hints_alias(): 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(): from typing import Literal # type: ignore 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']" -@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(): assert stringify(int | None) == "int | None" # type: ignore assert stringify(int | None, "smart") == "int | None" # type: ignore diff --git a/tox.ini b/tox.ini index dfae569cf..31b7c5b31 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] 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 [testenv] @@ -16,7 +16,7 @@ passenv = EPUBCHECK_PATH TERM 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. deps = du14: docutils==0.14.*