Dropped support for Python 2 and Sphinx 1.x/2.x.

Removed all old compatibility code.
pull/253/head
Ashley Whetter 4 years ago
parent 40ebbc965f
commit 8e4cd49e1a

@ -3,6 +3,15 @@ Changelog
Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``).
V1.6.0 (TBC)
------------
Breaking Changes
^^^^^^^^^^^^^^^^
* Dropped support for Python 2 and Sphinx 1.x/2.x.
Python 2 source code can still be parsed.
V1.5.1 (2020-10-01)
-------------------

@ -1,6 +1,5 @@
import re
import sphinx
from sphinx.ext import autodoc
from .mappers.python import (
@ -17,13 +16,7 @@ from .mappers.python import (
class AutoapiDocumenter(autodoc.Documenter):
def get_attr(self, obj, name, *defargs):
if hasattr(self.env.app, "registry") and hasattr(
self.env.app.registry, "autodoc_attrgettrs"
):
attrgetters = self.env.app.registry.autodoc_attrgettrs
else:
# Needed for Sphinx 1.6
attrgetters = getattr(autodoc.AutoDirective, "_special_attrgetters")
attrgetters = self.env.app.registry.autodoc_attrgettrs
for type_, func in attrgetters.items():
if isinstance(obj, type_):
@ -118,43 +111,38 @@ class AutoapiFunctionDocumenter(
return "(" + self.object.args + ")"
def add_directive_header(self, sig):
if sphinx.version_info >= (2, 1):
autodoc.Documenter.add_directive_header(self, sig)
if "async" in self.object.properties:
sourcename = self.get_sourcename()
self.add_line(" :async:", sourcename)
else:
super(AutoapiFunctionDocumenter, self).add_directive_header(sig)
autodoc.Documenter.add_directive_header(self, sig)
if "async" in self.object.properties:
sourcename = self.get_sourcename()
self.add_line(" :async:", sourcename)
if sphinx.version_info >= (2,):
class AutoapiDecoratorDocumenter(
AutoapiFunctionDocumenter, AutoapiDocumenter, autodoc.DecoratorDocumenter
):
objtype = "apidecorator"
directivetype = "decorator"
priority = autodoc.DecoratorDocumenter.priority * 100 + 100
class AutoapiDecoratorDocumenter(
AutoapiFunctionDocumenter, AutoapiDocumenter, autodoc.DecoratorDocumenter
):
objtype = "apidecorator"
directivetype = "decorator"
priority = autodoc.DecoratorDocumenter.priority * 100 + 100
def format_signature(self, **kwargs):
if self.args is None:
self.args = self.format_args(**kwargs)
def format_signature(self, **kwargs):
if self.args is None:
self.args = self.format_args(**kwargs)
return super(AutoapiDecoratorDocumenter, self).format_signature(**kwargs)
return super(AutoapiDecoratorDocumenter, self).format_signature(**kwargs)
def format_args(self, **kwargs):
to_format = self.object.args
def format_args(self, **kwargs):
to_format = self.object.args
if re.match(r"func\W", to_format) or to_format == "func":
if "," not in to_format:
return None
if re.match(r"func\W", to_format) or to_format == "func":
if "," not in to_format:
return None
# We need to do better stripping here.
# An annotation with a comma will mess this up.
to_format = self.object.args.split(",", 1)[1]
# We need to do better stripping here.
# An annotation with a comma will mess this up.
to_format = self.object.args.split(",", 1)[1]
return "(" + to_format + ")"
return "(" + to_format + ")"
class AutoapiClassDocumenter(
@ -205,28 +193,23 @@ class AutoapiMethodDocumenter(
if result:
self.parent = self._method_parent
if self.object.method_type != "method":
if sphinx.version_info < (2, 1):
self.directivetype = self.object.method_type
# document class and static members before ordinary ones
self.member_order = self.member_order - 1
return result
def add_directive_header(self, sig):
if sphinx.version_info >= (2, 1):
autodoc.Documenter.add_directive_header(self, sig)
autodoc.Documenter.add_directive_header(self, sig)
sourcename = self.get_sourcename()
for property_type in (
"abstractmethod",
"async",
"classmethod",
"staticmethod",
):
if property_type in self.object.properties:
self.add_line(" :{}:".format(property_type), sourcename)
else:
autodoc.Documenter.add_directive_header(self, sig)
sourcename = self.get_sourcename()
for property_type in (
"abstractmethod",
"async",
"classmethod",
"staticmethod",
):
if property_type in self.object.properties:
self.add_line(" :{}:".format(property_type), sourcename)
class AutoapiDataDocumenter(AutoapiDocumenter, autodoc.DataDocumenter):

@ -284,11 +284,10 @@ def setup(app):
app.connect("doctree-read", doctree_read)
app.connect("build-finished", build_finished)
app.connect("env-updated", clear_env)
if sphinx.version_info >= (1, 8):
if "viewcode-find-source" in app.events.events:
app.connect("viewcode-find-source", viewcode_find)
if "viewcode-follow-imported" in app.events.events:
app.connect("viewcode-follow-imported", viewcode_follow_imported)
if "viewcode-find-source" in app.events.events:
app.connect("viewcode-find-source", viewcode_find)
if "viewcode-follow-imported" in app.events.events:
app.connect("viewcode-follow-imported", viewcode_follow_imported)
app.add_config_value("autoapi_type", "python", "html")
app.add_config_value("autoapi_root", API_ROOT, "html")
app.add_config_value("autoapi_ignore", [], "html")
@ -304,8 +303,7 @@ def setup(app):
app.add_config_value("autoapi_python_class_content", "class", "html")
app.add_config_value("autoapi_generate_api_docs", True, "html")
app.add_autodocumenter(documenters.AutoapiFunctionDocumenter)
if sphinx.version_info >= (2,):
app.add_autodocumenter(documenters.AutoapiDecoratorDocumenter)
app.add_autodocumenter(documenters.AutoapiDecoratorDocumenter)
app.add_autodocumenter(documenters.AutoapiClassDocumenter)
app.add_autodocumenter(documenters.AutoapiMethodDocumenter)
app.add_autodocumenter(documenters.AutoapiDataDocumenter)

@ -1,13 +1,6 @@
import sys
import astroid
import sphinx.ext.inheritance_diagram
if sys.version_info >= (3,):
_BUILTINS = "builtins"
else:
_BUILTINS = "__builtins__"
def _do_import_class(name, currmodule=None):
path_stack = list(reversed(name.split(".")))
@ -80,7 +73,7 @@ class _AutoapiInheritanceGraph(sphinx.ext.inheritance_diagram.InheritanceGraph):
def recurse(cls):
if cls in all_classes:
return
if not show_builtins and cls.root().name == _BUILTINS:
if not show_builtins and cls.root().name == "builtins":
return
if not private_bases and cls.name.startswith("_"):
return
@ -101,7 +94,7 @@ class _AutoapiInheritanceGraph(sphinx.ext.inheritance_diagram.InheritanceGraph):
return
for base in cls.ancestors(recurs=False):
if not show_builtins and base.root().name == _BUILTINS:
if not show_builtins and base.root().name == "builtins":
continue
if not private_bases and base.name.startswith("_"):
continue

@ -1,7 +1,4 @@
try:
import builtins
except ImportError:
import __builtin__ as builtins
import builtins
import itertools
import re
import sys
@ -13,17 +10,6 @@ import sphinx.util.logging
_LOGGER = sphinx.util.logging.getLogger(__name__)
if sys.version_info < (3,):
_EXCEPTIONS_MODULE = "exceptions"
# getattr to keep linter happy
_STRING_TYPES = getattr(builtins, "basestring")
_zip_longest = itertools.izip_longest # pylint: disable=invalid-name,no-member
else:
_EXCEPTIONS_MODULE = "builtins"
_STRING_TYPES = (str,)
_zip_longest = itertools.zip_longest # pylint: disable=invalid-name
def resolve_import_alias(name, import_names):
"""Resolve a name from an aliased import to its original name.
@ -196,8 +182,7 @@ def get_assign_annotation(node):
try:
annotation_node = node.annotation
except AttributeError:
# Python 2 has no support for type annotations, so use getattr
annotation_node = getattr(node, "type_annotation", None)
annotation_node = node.type_annotation
if annotation_node:
if isinstance(annotation_node, astroid.nodes.Const):
@ -336,10 +321,7 @@ def is_exception(node):
:returns: True if the class is an exception, False otherwise.
:rtype: bool
"""
if (
node.name in ("Exception", "BaseException")
and node.root().name == _EXCEPTIONS_MODULE
):
if node.name in ("Exception", "BaseException") and node.root().name == "builtins":
return True
if not hasattr(node, "ancestors"):
@ -396,7 +378,7 @@ def get_module_all(node):
continue
if isinstance(elt_name, astroid.Const) and isinstance(
elt_name.value, _STRING_TYPES
elt_name.value, str
):
all_.append(elt_name.value)
@ -411,7 +393,7 @@ def _is_ellipsis(node):
def merge_annotations(annotations, comment_annotations):
for ann, comment_ann in _zip_longest(annotations, comment_annotations):
for ann, comment_ann in itertools.zip_longest(annotations, comment_annotations):
if ann and not _is_ellipsis(ann):
yield ann
elif comment_ann and not _is_ellipsis(comment_ann):
@ -432,7 +414,7 @@ def _format_args(args, defaults=None, annotations=None):
if defaults is not None:
default_offset = len(args) - len(defaults)
packed = _zip_longest(args, annotations)
packed = itertools.zip_longest(args, annotations)
for i, (arg, annotation) in enumerate(packed):
if isinstance(arg, astroid.Tuple):
values.append("({})".format(_format_args(arg.elts)))
@ -465,19 +447,11 @@ def format_args(args_node): # pylint: disable=too-many-branches,too-many-statem
: len(args_node.defaults) - len(args)
]
plain_annotations = getattr(args_node, "annotations", ()) or ()
func_comment_annotations = getattr(args_node.parent, "type_comment_args", ()) or ()
comment_annotations = getattr(args_node, "type_comment_args", []) or []
if hasattr(args_node, "type_comment_posonlyargs"):
comment_annotations = args_node.type_comment_posonlyargs + comment_annotations
else:
# astroid used to not expose type comments of positional only arguments,
# so pad the comments with the number of positional only arguments.
comment_annotations = (
[None] * len(getattr(args_node, "posonlyargs", ()))
) + comment_annotations
if hasattr(args_node, "type_comment_kwonlyargs"):
comment_annotations += args_node.type_comment_kwonlyargs
plain_annotations = args_node.annotations or ()
func_comment_annotations = args_node.parent.type_comment_args or ()
comment_annotations = args_node.type_comment_args or []
comment_annotations = args_node.type_comment_posonlyargs + comment_annotations
comment_annotations += args_node.type_comment_kwonlyargs
annotations = list(
merge_annotations(
plain_annotations,
@ -486,7 +460,7 @@ def format_args(args_node): # pylint: disable=too-many-branches,too-many-statem
)
annotation_offset = 0
if getattr(args_node, "posonlyargs", None):
if args_node.posonlyargs:
posonlyargs_annotations = args_node.posonlyargs_annotations
if not any(args_node.posonlyargs_annotations):
num_args = len(args_node.posonlyargs)
@ -517,7 +491,7 @@ def format_args(args_node): # pylint: disable=too-many-branches,too-many-statem
if args_node.vararg:
vararg_result = "*{}".format(args_node.vararg)
if getattr(args_node, "varargannotation", None):
if args_node.varargannotation:
vararg_result = "{}: {}".format(
vararg_result, args_node.varargannotation.as_string()
)
@ -528,7 +502,7 @@ def format_args(args_node): # pylint: disable=too-many-branches,too-many-statem
annotation_offset += 1
result.append(vararg_result)
if getattr(args_node, "kwonlyargs", None):
if args_node.kwonlyargs:
if not args_node.vararg:
result.append("*")
@ -552,7 +526,7 @@ def format_args(args_node): # pylint: disable=too-many-branches,too-many-statem
if args_node.kwarg:
kwarg_result = "**{}".format(args_node.kwarg)
if getattr(args_node, "kwargannotation", None):
if args_node.kwargannotation:
kwarg_result = "{}: {}".format(
kwarg_result, args_node.kwargannotation.as_string()
)

@ -2,7 +2,6 @@ import collections
import copy
import operator
import os
import sys
import sphinx.util
from sphinx.util.console import bold
@ -218,20 +217,14 @@ class PythonSphinxMapper(SphinxMapperBase):
PythonException,
)
}
if sphinx.version_info >= (2, 1):
_OBJ_MAP["property"] = PythonMethod
else:
_OBJ_MAP["property"] = PythonAttribute
_OBJ_MAP["property"] = PythonMethod
def __init__(self, app, template_dir=None, url_root=None):
super(PythonSphinxMapper, self).__init__(app, template_dir, url_root)
if sys.version_info < (3, 3):
self._use_implicit_namespace = False
else:
self._use_implicit_namespace = (
self.app.config.autoapi_python_use_implicit_namespaces
)
self._use_implicit_namespace = (
self.app.config.autoapi_python_use_implicit_namespaces
)
def _find_files(self, patterns, dirs, ignore):
for dir_ in dirs:

@ -1,7 +1,6 @@
import collections
import itertools
import os
import sys
import astroid
import astroid.builder
@ -16,17 +15,6 @@ class Parser(object):
def _get_full_name(self, name):
return ".".join(self._name_stack + [name])
def _decode(self, to_decode):
if sys.version_info < (3,) and self._encoding:
# pylint: disable=undefined-variable
try:
return unicode(to_decode, self._encoding)
except TypeError:
# The string was already in the correct format
pass
return to_decode
def _parse_file(self, file_path, condition):
directory, filename = os.path.split(file_path)
module_parts = []
@ -76,13 +64,7 @@ class Parser(object):
return []
target = assign_value[0]
value = None
try:
value = self._decode(assign_value[1])
except UnicodeDecodeError:
# Ignore binary data on Python 2.7
if sys.version_info[0] >= 3:
raise
value = assign_value[1]
annotation = astroid_utils.get_assign_annotation(node)
@ -90,7 +72,7 @@ class Parser(object):
"type": type_,
"name": target,
"full_name": self._get_full_name(target),
"doc": self._decode(doc),
"doc": doc,
"value": value,
"from_line_no": node.fromlineno,
"to_line_no": node.tolineno,
@ -121,7 +103,7 @@ class Parser(object):
"full_name": self._get_full_name(node.name),
"args": args,
"bases": basenames,
"doc": self._decode(astroid_utils.get_class_docstring(node)),
"doc": astroid_utils.get_class_docstring(node),
"from_line_no": node.fromlineno,
"to_line_no": node.tolineno,
"children": [],
@ -182,10 +164,9 @@ class Parser(object):
properties.append("async")
return_annotation = None
if getattr(node, "returns", None):
if node.returns:
return_annotation = node.returns.as_string()
# Python 2 has no support for type annotations, so use getattr
elif getattr(node, "type_comment_returns", None):
elif node.type_comment_returns:
return_annotation = node.type_comment_returns.as_string()
arg_string = astroid_utils.format_args(node.args)
@ -195,7 +176,7 @@ class Parser(object):
"name": node.name,
"full_name": self._get_full_name(node.name),
"args": arg_string,
"doc": self._decode(astroid_utils.get_func_docstring(node)),
"doc": astroid_utils.get_func_docstring(node),
"from_line_no": node.fromlineno,
"to_line_no": node.tolineno,
"return_annotation": return_annotation,
@ -251,7 +232,7 @@ class Parser(object):
"type": type_,
"name": node.name,
"full_name": node.name,
"doc": self._decode(node.doc or ""),
"doc": node.doc or "",
"children": [],
"file_path": path,
"encoding": node.file_encoding,

@ -18,18 +18,17 @@ setup(
long_description=io.open("README.rst", "r", encoding="utf-8").read(),
include_package_data=True,
install_requires=[
'astroid;python_version>="3"',
'astroid<2;python_version<"3"',
"astroid>=2.4",
"Jinja2",
"PyYAML",
"sphinx>=1.6",
"sphinx>=3.0",
"unidecode",
],
extras_require={
"go": ["sphinxcontrib-golangdomain"],
"dotnet": ["sphinxcontrib-dotnetdomain"],
},
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*",
python_requires=">=3.6",
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Plugins",
@ -38,8 +37,6 @@ setup(
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",

@ -2,6 +2,7 @@
"""Test Python parser"""
from io import StringIO
import sys
import unittest
from textwrap import dedent
@ -11,11 +12,6 @@ import pytest
from autoapi.mappers.python.parser import Parser
if sys.version_info < (3, 0):
from StringIO import StringIO
else:
from io import StringIO
class PythonParserTests(unittest.TestCase):
def parse(self, source):

@ -60,8 +60,7 @@ class TestSimpleModule(object):
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
if sphinx.version_info >= (2,):
assert "@example.decorator_okay" in example_file
assert "@example.decorator_okay" in example_file
def check_integration(self, example_path):
with io.open(example_path, encoding="utf8") as example_handle:
@ -114,9 +113,6 @@ class TestSimpleModule(object):
assert "Bases:" in example_file
@pytest.mark.skipif(
sys.version_info < (3,), reason="Ellipsis is invalid method contents in Python 2"
)
class TestSimpleStubModule(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
@ -158,9 +154,6 @@ class TestSimpleStubModuleNotPreferred(object):
assert "Foo" in example_file
@pytest.mark.skipif(
sys.version_info < (3, 6), reason="Annotations are invalid in Python <3.5"
)
class TestPy3Module(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
@ -192,8 +185,7 @@ class TestPy3Module(object):
assert "global_a :A" in example_file
if sphinx.version_info >= (2, 1):
assert "my_method(self) -> str" in example_file
assert "my_method(self) -> str" in example_file
def test_overload(self):
example_path = "_build/text/autoapi/example/index.txt"
@ -223,17 +215,10 @@ class TestPy3Module(object):
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
if sphinx.version_info >= (2, 1):
assert "async async_method" in example_file
assert "async example.async_function" in example_file
else:
assert "async_method" in example_file
assert "async_function" in example_file
assert "async async_method" in example_file
assert "async example.async_function" in example_file
@pytest.mark.skipif(
sys.version_info < (3, 6), reason="Annotations are invalid in Python <3.5"
)
def test_py3_hiding_undoc_overloaded_members(builder):
confoverrides = {"autoapi_options": ["members", "special-members"]}
builder("py3example", confoverrides=confoverrides)
@ -248,9 +233,6 @@ def test_py3_hiding_undoc_overloaded_members(builder):
assert "undoc_overloaded_method" not in example_file
@pytest.mark.skipif(
sys.version_info < (3,), reason="Annotations are not supported in astroid<2"
)
class TestAnnotationCommentsModule(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
@ -702,9 +684,6 @@ class TestComplexPackageParallel(object):
builder("pypackagecomplex", parallel=2)
@pytest.mark.skipif(
sys.version_info < (3, 3), reason="Implicit namespace not supported in python < 3.3"
)
class TestImplicitNamespacePackage(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):

@ -1,15 +1,12 @@
[tox]
envlist =
py{27,36,37}-sphinx{16,17,18},py{36,37,38}-sphinx{20,21,22,23,24,30,31,32}
py{36,37,38}-sphinx{30,31,32}
formatting
lint
docs
[travis]
python =
2.7: py27
3.4: py34
3.5: py35
3.6: py36
3.7: py37
3.8: py38
@ -23,19 +20,11 @@ extras =
deps = -r{toxinidir}/requirements.txt
pytest
mock
sphinx16: Sphinx<1.7
sphinx17: Sphinx<1.8
sphinx18: Sphinx<1.9
sphinx20: Sphinx<2.1
sphinx21: Sphinx<2.2
sphinx22: Sphinx<2.3
sphinx23: Sphinx<2.4
sphinx24: Sphinx<2.5
sphinx30: Sphinx<3.1
sphinx31: Sphinx<3.2
sphinx32: Sphinx<3.3
commands =
py.test {posargs}
pytest {posargs}
[testenv:formatting]
basepython = python3
@ -54,7 +43,7 @@ commands =
[testenv:docs]
deps =
Sphinx~=3.0.0
Sphinx~=3.2.0
sphinx_rtd_theme
changedir = {toxinidir}/docs
commands =

Loading…
Cancel
Save