Drop Python 3.7

This commit is contained in:
Adam Turner 2022-04-18 17:33:56 +01:00
parent 7649eb1505
commit 4660b62de0
27 changed files with 167 additions and 291 deletions

View File

@ -13,8 +13,6 @@ jobs:
fail-fast: false
matrix:
include:
- python: "3.7"
docutils: du15
- python: "3.8"
docutils: du16
- python: "3.9"

View File

@ -5,6 +5,7 @@ Dependencies
------------
* #10468: Drop Python 3.6 support
* #10470: Drop Python 3.7 support. Patch by Adam Turner
Incompatible changes
--------------------

View File

@ -12,7 +12,7 @@ Installing Sphinx
Overview
--------
Sphinx is written in `Python`__ and supports Python 3.7+. It builds upon the
Sphinx is written in `Python`__ and supports Python 3.8+. It builds upon the
shoulders of many third-party libraries such as `Docutils`__ and `Jinja`__,
which are installed when Sphinx is installed.

View File

@ -13,7 +13,7 @@ urls.Download = "https://pypi.org/project/Sphinx/"
urls.Homepage = "https://www.sphinx-doc.org/"
urls."Issue tracker" = "https://github.com/sphinx-doc/sphinx/issues"
license.text = "BSD"
requires-python = ">=3.7"
requires-python = ">=3.8"
# Classifiers list: https://pypi.org/classifiers/
classifiers = [
@ -30,7 +30,6 @@ classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
@ -89,13 +88,11 @@ lint = [
"mypy>=0.981",
"sphinx-lint",
"docutils-stubs",
"types-typed-ast",
"types-requests",
]
test = [
"pytest>=4.6",
"html5lib",
"typed_ast; python_version < '3.8'",
"cython",
]
@ -144,7 +141,7 @@ disallow_incomplete_defs = true
follow_imports = "skip"
ignore_missing_imports = true
no_implicit_optional = true
python_version = "3.7"
python_version = "3.8"
show_column_numbers = true
show_error_codes = true
show_error_context = true

View File

@ -77,7 +77,7 @@ class TocTree(SphinxDirective):
return ret
def parse_content(self, toctree: addnodes.toctree) -> List[Node]:
generated_docnames = frozenset(self.env.domains['std'].initial_data['labels'].keys())
generated_docnames = frozenset(self.env.domains['std']._virtual_doc_names)
suffixes = self.config.source_suffix
# glob target documents

View File

@ -1,9 +1,9 @@
"""The Python domain."""
import ast
import builtins
import inspect
import re
import sys
import typing
from inspect import Parameter
from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Optional, Tuple, Type, cast
@ -21,7 +21,6 @@ from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, Index, IndexEntry, ObjType
from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __
from sphinx.pycode.ast import ast
from sphinx.pycode.ast import parse as ast_parse
from sphinx.roles import XRefRole
from sphinx.util import logging
@ -138,7 +137,7 @@ def _parse_annotation(annotation: str, env: BuildEnvironment) -> List[Node]:
return [addnodes.desc_sig_space(),
addnodes.desc_sig_punctuation('', '|'),
addnodes.desc_sig_space()]
elif isinstance(node, ast.Constant): # type: ignore
elif isinstance(node, ast.Constant):
if node.value is Ellipsis:
return [addnodes.desc_sig_punctuation('', "...")]
elif isinstance(node.value, bool):
@ -204,18 +203,6 @@ def _parse_annotation(annotation: str, env: BuildEnvironment) -> List[Node]:
return result
else:
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):
return [addnodes.desc_sig_punctuation('', "...")]
elif isinstance(node, ast.NameConstant):
return [nodes.Text(node.value)]
elif isinstance(node, ast.Num):
return [addnodes.desc_sig_literal_string('', repr(node.n))]
elif isinstance(node, ast.Str):
return [addnodes.desc_sig_literal_string('', repr(node.s))]
raise SyntaxError # unsupported syntax
try:

View File

@ -1,10 +1,9 @@
"""The standard domain."""
import re
import sys
from copy import copy
from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional,
Tuple, Type, Union, cast)
from typing import (TYPE_CHECKING, Any, Callable, Dict, Final, Iterable, Iterator, List,
Optional, Tuple, Type, Union, cast)
from docutils import nodes
from docutils.nodes import Element, Node, system_message
@ -29,11 +28,6 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
if sys.version_info[:2] >= (3, 8):
from typing import Final
else:
Final = Any
# RE for option descriptions
option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=]+)(=?\s*.*)')
# RE for grammar tokens
@ -589,7 +583,7 @@ class StandardDomain(Domain):
'doc': XRefRole(warn_dangling=True, innernodeclass=nodes.inline),
}
initial_data: Final = {
initial_data: Final = { # type: ignore[misc]
'progoptions': {}, # (program, name) -> docname, labelid
'objects': {}, # (type, name) -> docname, labelid
'labels': { # labelname -> docname, labelid, sectionname
@ -604,6 +598,12 @@ class StandardDomain(Domain):
},
}
_virtual_doc_names: Dict[str, Tuple[str, str]] = { # labelname -> docname, sectionname
'genindex': ('genindex', _('Index')),
'modindex': ('py-modindex', _('Module Index')),
'search': ('search', _('Search Page')),
}
dangling_warnings = {
'term': 'term not in glossary: %(target)r',
'numref': 'undefined label: %(target)r',

View File

@ -54,7 +54,7 @@ class TocTree:
"""
if toctree.get('hidden', False) and not includehidden:
return None
generated_docnames: Dict[str, Tuple[str, str, str]] = self.env.domains['std'].initial_data['labels'].copy() # NoQA: E501
generated_docnames: Dict[str, Tuple[str, str]] = self.env.domains['std']._virtual_doc_names.copy() # NoQA: E501
# For reading the following two helper function, it is useful to keep
# in mind the node structure of a toctree (using HTML-like node names
@ -141,7 +141,7 @@ class TocTree:
# don't show subitems
toc = nodes.bullet_list('', item)
elif ref in generated_docnames:
docname, _, sectionname = generated_docnames[ref]
docname, sectionname = generated_docnames[ref]
if not title:
title = sectionname
reference = nodes.reference('', title, internal=True,

View File

@ -236,7 +236,7 @@ class TocTreeCollector(EnvironmentCollector):
def assign_figure_numbers(self, env: BuildEnvironment) -> List[str]:
"""Assign a figure number to each figure under a numbered toctree."""
generated_docnames = frozenset(env.domains['std'].initial_data['labels'].keys())
generated_docnames = frozenset(env.domains['std']._virtual_doc_names)
rewrite_needed = []

View File

@ -6,8 +6,6 @@ and keep them not evaluated for readability.
import ast
import inspect
import sys
from inspect import Parameter
from typing import Any, Dict, List, Optional
from sphinx.application import Sphinx
@ -48,8 +46,6 @@ 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[:2] <= (3, 7): # only for py38+
return None
if position.lineno == position.end_lineno:
line = lines[position.lineno - 1]
return line[position.col_offset:position.end_col_offset]
@ -89,18 +85,18 @@ def update_defvalue(app: Sphinx, obj: Any, bound_method: bool) -> None:
default = defaults.pop(0)
value = get_default_value(lines, default)
if value is None:
value = ast_unparse(default) # type: ignore
value = ast_unparse(default)
parameters[i] = param.replace(default=DefaultValue(value))
else:
default = kw_defaults.pop(0)
value = get_default_value(lines, default)
if value is None:
value = ast_unparse(default) # type: ignore
value = ast_unparse(default)
parameters[i] = param.replace(default=DefaultValue(value))
if bound_method and inspect.ismethod(obj):
# classmethods
cls = inspect.Parameter('cls', Parameter.POSITIONAL_OR_KEYWORD)
cls = inspect.Parameter('cls', inspect.Parameter.POSITIONAL_OR_KEYWORD)
parameters.insert(0, cls)
sig = sig.replace(parameters=parameters)

View File

@ -1,12 +1,12 @@
"""Update annotations info of living objects using type_comments."""
import ast
from inspect import Parameter, Signature, getsource
from typing import Any, Dict, List, cast
import sphinx
from sphinx.application import Sphinx
from sphinx.locale import __
from sphinx.pycode.ast import ast
from sphinx.pycode.ast import parse as ast_parse
from sphinx.pycode.ast import unparse as ast_unparse
from sphinx.util import inspect, logging
@ -34,10 +34,9 @@ def signature_from_ast(node: ast.FunctionDef, bound_method: bool,
:param bound_method: Specify *node* is a bound method or not
"""
params = []
if hasattr(node.args, "posonlyargs"): # for py38+
for arg in node.args.posonlyargs: # type: ignore
param = Parameter(arg.arg, Parameter.POSITIONAL_ONLY, annotation=arg.type_comment)
params.append(param)
for arg in node.args.posonlyargs:
param = Parameter(arg.arg, Parameter.POSITIONAL_ONLY, annotation=arg.type_comment)
params.append(param)
for arg in node.args.args:
param = Parameter(arg.arg, Parameter.POSITIONAL_OR_KEYWORD,
@ -80,7 +79,7 @@ def get_type_comment(obj: Any, bound_method: bool = False) -> Signature:
"""Get type_comment'ed FunctionDef object from living object.
This tries to parse original code for living object and returns
Signature for given *obj*. It requires py38+ or typed_ast module.
Signature for given *obj*.
"""
try:
source = getsource(obj)

View File

@ -2,29 +2,25 @@
import gettext
import locale
from collections import UserString, defaultdict
from collections import defaultdict
from gettext import NullTranslations
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
class _TranslationProxy(UserString):
class _TranslationProxy:
"""
Class for proxy strings from gettext translations. This is a helper for the
lazy_* functions from this module.
The proxy implementation attempts to be as complete as possible, so that
the lazy objects should mostly work as expected, for example for sorting.
This inherits from UserString because some docutils versions use UserString
for their Text nodes, which then checks its argument for being either a
basestring or UserString, otherwise calls str() -- not unicode() -- on it.
"""
__slots__ = ('_func', '_args')
def __new__(cls, func: Callable, *args: str) -> object: # type: ignore
def __new__(cls, func: Callable, *args: str) -> "_TranslationProxy":
if not args:
# not called with "function" and "arguments", but a plain string
return str(func)
return str(func) # type: ignore[return-value]
return object.__new__(cls)
def __getnewargs__(self) -> Tuple[str]:
@ -34,50 +30,14 @@ class _TranslationProxy(UserString):
self._func = func
self._args = args
@property
def data(self) -> str: # type: ignore
return self._func(*self._args)
# replace function from UserString; it instantiates a self.__class__
# for the encoding result
def encode(self, encoding: str = None, errors: str = None) -> bytes: # type: ignore
if encoding:
if errors:
return self.data.encode(encoding, errors)
else:
return self.data.encode(encoding)
else:
return self.data.encode()
def __str__(self) -> str:
return str(self._func(*self._args))
def __dir__(self) -> List[str]:
return dir(str)
def __str__(self) -> str:
return str(self.data)
def __add__(self, other: str) -> str: # type: ignore
return self.data + other
def __radd__(self, other: str) -> str: # type: ignore
return other + self.data
def __mod__(self, other: str) -> str: # type: ignore
return self.data % other
def __rmod__(self, other: str) -> str: # type: ignore
return other % self.data
def __mul__(self, other: Any) -> str: # type: ignore
return self.data * other
def __rmul__(self, other: Any) -> str: # type: ignore
return other * self.data
def __getattr__(self, name: str) -> Any:
if name == '__members__':
return self.__dir__()
return getattr(self.data, name)
return getattr(self.__str__(), name)
def __getstate__(self) -> Tuple[Callable, Tuple[str, ...]]:
return self._func, self._args
@ -86,13 +46,49 @@ class _TranslationProxy(UserString):
self._func, self._args = tup
def __copy__(self) -> "_TranslationProxy":
return self
return _TranslationProxy(self._func, *self._args)
def __repr__(self) -> str:
try:
return 'i' + repr(str(self.data))
return 'i' + repr(str(self.__str__()))
except Exception:
return '<%s broken>' % self.__class__.__name__
return f'<{self.__class__.__name__} broken>'
def __add__(self, other: str) -> str:
return self.__str__() + other
def __radd__(self, other: str) -> str:
return other + self.__str__()
def __mod__(self, other: str) -> str:
return self.__str__() % other
def __rmod__(self, other: str) -> str:
return other % self.__str__()
def __mul__(self, other: Any) -> str:
return self.__str__() * other
def __rmul__(self, other: Any) -> str:
return other * self.__str__()
def __hash__(self):
return hash(self.__str__())
def __eq__(self, other):
return self.__str__() == other
def __lt__(self, string):
return self.__str__() < string
def __contains__(self, char):
return char in self.__str__()
def __len__(self):
return len(self.__str__())
def __getitem__(self, index):
return self.__str__()[index]
translators: Dict[Tuple[str, str], NullTranslations] = defaultdict(NullTranslations)
@ -219,7 +215,7 @@ def get_translation(catalog: str, namespace: str = 'general') -> Callable[[str],
def gettext(message: str, *args: Any) -> str:
if not is_translator_registered(catalog, namespace):
# not initialized yet
return _TranslationProxy(_lazy_translate, catalog, namespace, message) # type: ignore # NOQA
return _TranslationProxy(_lazy_translate, catalog, namespace, message) # type: ignore[return-value] # NOQA
else:
translator = get_translator(catalog, namespace)
if len(args) <= 1:

View File

@ -1,18 +1,8 @@
"""Helpers for AST (Abstract Syntax Tree)."""
import sys
import ast
from typing import Dict, List, Optional, Type, overload
if sys.version_info[:2] >= (3, 8):
import ast
else:
try:
# use typed_ast module if installed
from typed_ast import ast3 as ast
except ImportError:
import ast # type: ignore
OPERATORS: Dict[Type[ast.AST], str] = {
ast.Add: "+",
ast.And: "and",
@ -37,21 +27,13 @@ OPERATORS: Dict[Type[ast.AST], str] = {
def parse(code: str, mode: str = 'exec') -> "ast.AST":
"""Parse the *code* using the built-in ast or typed_ast libraries.
This enables "type_comments" feature if possible.
"""
"""Parse the *code* using the built-in ast module."""
try:
# type_comments parameter is available on py38+
return ast.parse(code, mode=mode, type_comments=True) # type: ignore
return ast.parse(code, mode=mode, type_comments=True)
except SyntaxError:
# Some syntax error found. To ignore invalid type comments, retry parsing without
# type_comments parameter (refs: https://github.com/sphinx-doc/sphinx/issues/8652).
return ast.parse(code, mode=mode)
except TypeError:
# fallback to ast module.
# typed_ast is used to parse type_comments if installed.
return ast.parse(code, mode=mode)
@overload
@ -102,10 +84,8 @@ class _UnparseVisitor(ast.NodeVisitor):
def visit_arguments(self, node: ast.arguments) -> str:
defaults: List[Optional[ast.expr]] = list(node.defaults)
positionals = len(node.args)
posonlyargs = 0
if hasattr(node, "posonlyargs"): # for py38+
posonlyargs += len(node.posonlyargs) # type:ignore
positionals += posonlyargs
posonlyargs = len(node.posonlyargs)
positionals += posonlyargs
for _ in range(len(defaults), positionals):
defaults.insert(0, None)
@ -114,12 +94,11 @@ class _UnparseVisitor(ast.NodeVisitor):
kw_defaults.insert(0, None)
args: List[str] = []
if hasattr(node, "posonlyargs"): # for py38+
for i, arg in enumerate(node.posonlyargs): # type: ignore
args.append(self._visit_arg_with_default(arg, defaults[i]))
for i, arg in enumerate(node.posonlyargs):
args.append(self._visit_arg_with_default(arg, defaults[i]))
if node.posonlyargs: # type: ignore
args.append('/')
if node.posonlyargs:
args.append('/')
for i, arg in enumerate(node.args):
args.append(self._visit_arg_with_default(arg, defaults[i + posonlyargs]))
@ -155,19 +134,19 @@ class _UnparseVisitor(ast.NodeVisitor):
["%s=%s" % (k.arg, self.visit(k.value)) for k in node.keywords])
return "%s(%s)" % (self.visit(node.func), ", ".join(args))
def visit_Constant(self, node: ast.Constant) -> str: # type: ignore
def visit_Constant(self, node: ast.Constant) -> str:
if node.value is Ellipsis:
return "..."
elif isinstance(node.value, (int, float, complex)):
if self.code and sys.version_info[:2] >= (3, 8):
return ast.get_source_segment(self.code, node) # type: ignore
if self.code:
return ast.get_source_segment(self.code, node) or repr(node.value)
else:
return repr(node.value)
else:
return repr(node.value)
def visit_Dict(self, node: ast.Dict) -> str:
keys = (self.visit(k) for k in node.keys)
keys = (self.visit(k) for k in node.keys if k is not None)
values = (self.visit(v) for v in node.values)
items = (k + ": " + v for k, v in zip(keys, values))
return "{" + ", ".join(items) + "}"
@ -219,22 +198,5 @@ class _UnparseVisitor(ast.NodeVisitor):
else:
return "(" + ", ".join(self.visit(e) for e in node.elts) + ")"
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)
def visit_Ellipsis(self, node: ast.Ellipsis) -> str:
return "..."
def visit_NameConstant(self, node: ast.NameConstant) -> str:
return repr(node.value)
def visit_Num(self, node: ast.Num) -> str:
return repr(node.n)
def visit_Str(self, node: ast.Str) -> str:
return repr(node.s)
def generic_visit(self, node):
raise NotImplementedError('Unable to parse %s object' % type(node).__name__)

View File

@ -1,4 +1,6 @@
"""Utilities parsing and analyzing Python code."""
import ast
import inspect
import itertools
import re
@ -9,8 +11,8 @@ from token import DEDENT, INDENT, NAME, NEWLINE, NUMBER, OP, STRING
from tokenize import COMMENT, NL
from typing import Any, Dict, List, Optional, Tuple
from sphinx.pycode.ast import ast # for py37 or older
from sphinx.pycode.ast import parse, unparse
from sphinx.pycode.ast import parse as ast_parse
from sphinx.pycode.ast import unparse as ast_unparse
comment_re = re.compile('^\\s*#: ?(.*)\r?\n?$')
indent_re = re.compile('^\\s*$')
@ -266,7 +268,7 @@ class VariableCommentPicker(ast.NodeVisitor):
qualname = self.get_qualname_for(name)
if qualname:
basename = ".".join(qualname[:-1])
self.annotations[(basename, name)] = unparse(annotation)
self.annotations[(basename, name)] = ast_unparse(annotation)
def is_final(self, decorators: List[ast.expr]) -> bool:
final = []
@ -277,7 +279,7 @@ class VariableCommentPicker(ast.NodeVisitor):
for decorator in decorators:
try:
if unparse(decorator) in final:
if ast_unparse(decorator) in final:
return True
except NotImplementedError:
pass
@ -293,7 +295,7 @@ class VariableCommentPicker(ast.NodeVisitor):
for decorator in decorators:
try:
if unparse(decorator) in overload:
if ast_unparse(decorator) in overload:
return True
except NotImplementedError:
pass
@ -304,12 +306,9 @@ class VariableCommentPicker(ast.NodeVisitor):
"""Returns the name of the first argument if in a function."""
if self.current_function and self.current_function.args.args:
return self.current_function.args.args[0]
elif (self.current_function and
getattr(self.current_function.args, 'posonlyargs', None)):
# for py38+
return self.current_function.args.posonlyargs[0] # type: ignore
else:
return None
if self.current_function and self.current_function.args.posonlyargs:
return self.current_function.args.posonlyargs[0]
return None
def get_line(self, lineno: int) -> str:
"""Returns specified line."""
@ -553,7 +552,7 @@ class Parser:
def parse_comments(self) -> None:
"""Parse the code and pick up comments."""
tree = parse(self.code)
tree = ast_parse(self.code)
picker = VariableCommentPicker(self.code.splitlines(True), self.encoding)
picker.visit(tree)
self.annotations = picker.annotations

View File

@ -1,5 +1,6 @@
"""Helpers for inspecting Python modules."""
import ast
import builtins
import contextlib
import enum
@ -8,15 +9,15 @@ import re
import sys
import types
import typing
from functools import partial, partialmethod
from functools import cached_property, partial, partialmethod, singledispatchmethod
from importlib import import_module
from inspect import Parameter, isclass, ismethod, ismethoddescriptor, ismodule # NOQA
from inspect import (Parameter, isasyncgenfunction, isclass, ismethod, # NOQA
ismethoddescriptor, ismodule)
from io import StringIO
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
from sphinx.pycode.ast import unparse as ast_unparse
from sphinx.util import logging
from sphinx.util.typing import ForwardRef
@ -285,11 +286,7 @@ def is_singledispatch_function(obj: Any) -> bool:
def is_singledispatch_method(obj: Any) -> bool:
"""Check if the object is singledispatch method."""
try:
from functools import singledispatchmethod # type: ignore
return isinstance(obj, singledispatchmethod)
except ImportError: # py37
return False
return isinstance(obj, singledispatchmethod)
def isfunction(obj: Any) -> bool:
@ -329,27 +326,9 @@ def iscoroutinefunction(obj: Any) -> bool:
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[:2] >= (3, 8):
from functools import cached_property # cached_property is available since py3.8
if isinstance(obj, cached_property):
return True
return isinstance(obj, property)
return isinstance(obj, (property, cached_property))
def isgenericalias(obj: Any) -> bool:
@ -723,7 +702,7 @@ def signature_from_str(signature: str) -> inspect.Signature:
"""Create a Signature object from string."""
code = 'def func' + signature + ': pass'
module = ast.parse(code)
function = cast(ast.FunctionDef, module.body[0]) # type: ignore
function = cast(ast.FunctionDef, module.body[0])
return signature_from_ast(function, code)
@ -734,7 +713,7 @@ def signature_from_ast(node: ast.FunctionDef, code: str = '') -> inspect.Signatu
defaults = list(args.defaults)
params = []
if hasattr(args, "posonlyargs"):
posonlyargs = len(args.posonlyargs) # type: ignore
posonlyargs = len(args.posonlyargs)
positionals = posonlyargs + len(args.args)
else:
posonlyargs = 0
@ -744,7 +723,7 @@ def signature_from_ast(node: ast.FunctionDef, code: str = '') -> inspect.Signatu
defaults.insert(0, Parameter.empty) # type: ignore
if hasattr(args, "posonlyargs"):
for i, arg in enumerate(args.posonlyargs): # type: ignore
for i, arg in enumerate(args.posonlyargs):
if defaults[i] is Parameter.empty:
default = Parameter.empty
else:

View File

@ -1,4 +1,3 @@
# for py34 or above
from functools import partialmethod

View File

@ -1,11 +0,0 @@
def foo(*, a, b):
pass
def bar(a, b, /, c, d):
pass
def baz(a, /, *, b):
pass
def qux(a, b, /):
pass

View File

@ -1,7 +1,6 @@
"""Tests the Python Domain"""
import re
import sys
from unittest.mock import Mock
import docutils.utils
@ -365,7 +364,6 @@ 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[: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"],
@ -451,37 +449,6 @@ def test_pyfunction_signature_full(app):
[desc_sig_punctuation, ":"],
desc_sig_space,
[desc_sig_name, pending_xref, "str"])])])
def test_pyfunction_with_unary_operators(app):
text = ".. py:function:: menu(egg=+1, bacon=-1, sausage=~1, spam=not spam)"
doctree = restructuredtext.parse(app, text)
assert_node(doctree[1][0][1],
[desc_parameterlist, ([desc_parameter, ([desc_sig_name, "egg"],
[desc_sig_operator, "="],
[nodes.inline, "+1"])],
[desc_parameter, ([desc_sig_name, "bacon"],
[desc_sig_operator, "="],
[nodes.inline, "-1"])],
[desc_parameter, ([desc_sig_name, "sausage"],
[desc_sig_operator, "="],
[nodes.inline, "~1"])],
[desc_parameter, ([desc_sig_name, "spam"],
[desc_sig_operator, "="],
[nodes.inline, "not spam"])])])
def test_pyfunction_with_binary_operators(app):
text = ".. py:function:: menu(spam=2**64)"
doctree = restructuredtext.parse(app, text)
assert_node(doctree[1][0][1],
[desc_parameterlist, ([desc_parameter, ([desc_sig_name, "spam"],
[desc_sig_operator, "="],
[nodes.inline, "2**64"])])])
@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)"
doctree = restructuredtext.parse(app, text)
@ -516,7 +483,33 @@ def test_pyfunction_signature_full_py38(app):
[desc_parameter, desc_sig_operator, "/"])])
@pytest.mark.skipif(sys.version_info[:2] <= (3, 7), reason='python 3.8+ is required.')
def test_pyfunction_with_unary_operators(app):
text = ".. py:function:: menu(egg=+1, bacon=-1, sausage=~1, spam=not spam)"
doctree = restructuredtext.parse(app, text)
assert_node(doctree[1][0][1],
[desc_parameterlist, ([desc_parameter, ([desc_sig_name, "egg"],
[desc_sig_operator, "="],
[nodes.inline, "+1"])],
[desc_parameter, ([desc_sig_name, "bacon"],
[desc_sig_operator, "="],
[nodes.inline, "-1"])],
[desc_parameter, ([desc_sig_name, "sausage"],
[desc_sig_operator, "="],
[nodes.inline, "~1"])],
[desc_parameter, ([desc_sig_name, "spam"],
[desc_sig_operator, "="],
[nodes.inline, "not spam"])])])
def test_pyfunction_with_binary_operators(app):
text = ".. py:function:: menu(spam=2**64)"
doctree = restructuredtext.parse(app, text)
assert_node(doctree[1][0][1],
[desc_parameterlist, ([desc_parameter, ([desc_sig_name, "spam"],
[desc_sig_operator, "="],
[nodes.inline, "2**64"])])])
def test_pyfunction_with_number_literals(app):
text = ".. py:function:: hello(age=0x10, height=1_6_0)"
doctree = restructuredtext.parse(app, text)

View File

@ -1072,8 +1072,6 @@ def test_autodoc_descriptor(app):
]
@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):
options = {"members": None,
@ -2059,8 +2057,6 @@ def test_singledispatch(app):
]
@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):
options = {"members": None}
@ -2088,8 +2084,6 @@ def test_singledispatchmethod(app):
]
@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):
options = {}
@ -2142,8 +2136,6 @@ def test_cython(app):
]
@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):
options = {"members": None}

View File

@ -4,8 +4,6 @@ This tests mainly the Documenters; the auto directives are tested in a test
source file translated by test_build.
"""
import sys
import pytest
from .test_ext_autodoc import do_autodoc
@ -40,7 +38,6 @@ def test_class_properties(app):
]
@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')

View File

@ -1,7 +1,5 @@
"""Test the autodoc extension."""
import sys
import pytest
from .test_ext_autodoc import do_autodoc
@ -10,10 +8,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[:2] <= (3, 7):
color = "16777215"
else:
color = "0xFFFFFF"
color = "0xFFFFFF"
options = {"members": None}
actual = do_autodoc(app, 'module', 'target.preserve_defaults', options)

View File

@ -185,8 +185,6 @@ def test_ModuleAnalyzer_find_attr_docs():
'Qux.attr2': 17}
@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'
' def __init__(self, /):\n'

View File

@ -1,10 +1,10 @@
"""Test pycode.ast"""
import sys
import ast
import pytest
from sphinx.pycode import ast
from sphinx.pycode.ast import unparse as ast_unparse
@pytest.mark.parametrize('source,expected', [
@ -48,23 +48,15 @@ from sphinx.pycode import ast
("(1, 2, 3)", "(1, 2, 3)"), # Tuple
("()", "()"), # Tuple (empty)
("(1,)", "(1,)"), # Tuple (single item)
("lambda x=0, /, y=1, *args, z, **kwargs: x + y + z",
"lambda x=0, /, y=1, *args, z, **kwargs: ..."), # posonlyargs
("0x1234", "0x1234"), # Constant
("1_000_000", "1_000_000"), # Constant
])
def test_unparse(source, expected):
module = ast.parse(source)
assert ast.unparse(module.body[0].value, source) == expected
assert ast_unparse(module.body[0].value, source) == expected
def test_unparse_None():
assert ast.unparse(None) is None
@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
("0x1234", "0x1234"), # Constant
("1_000_000", "1_000_000"), # Constant
])
def test_unparse_py38(source, expected):
module = ast.parse(source)
assert ast.unparse(module.body[0].value, source) == expected
assert ast_unparse(None) is None

View File

@ -151,7 +151,8 @@ def test_signature_partialmethod():
def test_signature_annotations():
from .typing_test_data import (Node, f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12,
f13, f14, f15, f16, f17, f18, f19, f20, f21)
f13, f14, f15, f16, f17, f18, f19, f20, f21, f22, f23, f24,
f25)
# Class annotations
sig = inspect.signature(f0)
@ -272,25 +273,19 @@ def test_signature_annotations():
else:
assert stringify_signature(sig, unqualified_typehints=True) == '(x: int = None, y: dict = {}) -> None'
@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
# case: separator at head
sig = inspect.signature(foo)
sig = inspect.signature(f22)
assert stringify_signature(sig) == '(*, a, b)'
# case: separator in the middle
sig = inspect.signature(bar)
sig = inspect.signature(f23)
assert stringify_signature(sig) == '(a, b, /, c, d)'
sig = inspect.signature(baz)
sig = inspect.signature(f24)
assert stringify_signature(sig) == '(a, /, *, b)'
# case: separator at tail
sig = inspect.signature(qux)
sig = inspect.signature(f25)
assert stringify_signature(sig) == '(a, b, /)'
@ -373,8 +368,6 @@ def test_signature_from_str_kwonly_args():
assert sig.parameters['b'].default == Parameter.empty
@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)')
assert list(sig.parameters.keys()) == ['a', 'b', 'c']

View File

@ -162,7 +162,6 @@ def test_restify_type_ForwardRef():
assert restify(ForwardRef("myint")) == ":py:class:`myint`"
@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']"
@ -408,7 +407,6 @@ def test_stringify_type_hints_alias():
assert stringify(MyTuple, "smart") == "~typing.Tuple[str, str]" # type: ignore
@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']"

View File

@ -105,6 +105,22 @@ def f21(arg1='whatever', arg2=Signature.empty):
pass
def f22(*, a, b):
pass
def f23(a, b, /, c, d):
pass
def f24(a, /, *, b):
pass
def f25(a, b, /):
pass
class Node:
def __init__(self, parent: Optional['Node']) -> None:
pass

View File

@ -1,6 +1,6 @@
[tox]
minversion = 2.4.0
envlist = docs,flake8,mypy,twine,py{37,38,39,310,311},du{14,15,16,17,18,19}
envlist = docs,flake8,mypy,twine,py{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{37,38,39,310,311}: Run unit tests against {envname}.
py{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.*