From 551b8dc035101da832b92935173695bc8cd02f17 Mon Sep 17 00:00:00 2001 From: tristanlatr <19967168+tristanlatr@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:39:21 -0400 Subject: [PATCH] Drop python 3.6 and enable future annotations everywhere (#735) * Remove python 3.6 from CI and add python 3.12 * Use python 3.12 rc3 * Remove all usages of ast.Str, ast.Bytes, etc in python 3.8 and later. * Ignore some warnings for now since astor uses these deprecated classes. * Add from __future__ import annotations everywhere * Fix extract_docstring_linenum for python 3.7 * Use __instancecheck__ to prove a way to type check the docstring node stuff. Also minimizes the number of time we call get_str_value * Use cpython 3.11, fixes #732 * Ensure that we can parse all files of the stdlib. --- .github/workflows/unit.yaml | 10 +-- README.rst | 4 + docs/tests/test_standard_library_docs.py | 9 ++- pydoctor/_configparser.py | 2 + pydoctor/astbuilder.py | 39 +++++----- pydoctor/astutils.py | 76 ++++++++++++++++--- pydoctor/driver.py | 1 + pydoctor/epydoc/doctest.py | 2 +- pydoctor/epydoc/docutils.py | 2 + pydoctor/epydoc/markup/__init__.py | 2 +- pydoctor/epydoc/markup/_napoleon.py | 2 + pydoctor/epydoc/markup/_pyval_repr.py | 33 +++++--- pydoctor/epydoc/markup/_types.py | 1 + pydoctor/epydoc/markup/epytext.py | 2 +- pydoctor/epydoc/markup/google.py | 5 +- pydoctor/epydoc/markup/numpy.py | 5 +- pydoctor/epydoc/markup/plaintext.py | 2 +- pydoctor/epydoc/markup/restructuredtext.py | 2 +- pydoctor/epydoc2stan.py | 1 + pydoctor/extensions/__init__.py | 2 + pydoctor/extensions/attrs.py | 2 +- pydoctor/extensions/deprecate.py | 1 + pydoctor/extensions/zopeinterface.py | 5 +- pydoctor/factory.py | 1 + pydoctor/linker.py | 1 + pydoctor/model.py | 3 +- pydoctor/mro.py | 1 + pydoctor/napoleon/docstring.py | 2 + pydoctor/napoleon/iterators.py | 1 + pydoctor/node2stan.py | 2 + pydoctor/options.py | 1 + pydoctor/qnmatch.py | 2 + pydoctor/sphinx.py | 1 + pydoctor/sphinx_ext/build_apidocs.py | 2 + pydoctor/templatewriter/__init__.py | 2 + pydoctor/templatewriter/pages/__init__.py | 1 + .../templatewriter/pages/attributechild.py | 2 + .../templatewriter/pages/functionchild.py | 2 + pydoctor/templatewriter/pages/sidebar.py | 2 + pydoctor/templatewriter/pages/table.py | 2 + pydoctor/templatewriter/search.py | 1 + pydoctor/templatewriter/summary.py | 1 + pydoctor/templatewriter/util.py | 1 + pydoctor/templatewriter/writer.py | 2 +- pydoctor/utils.py | 2 + pydoctor/visitor.py | 2 + setup.cfg | 18 +++-- tox.ini | 19 ++--- 48 files changed, 208 insertions(+), 76 deletions(-) diff --git a/.github/workflows/unit.yaml b/.github/workflows/unit.yaml index eb1fc3c87..afb94ef21 100644 --- a/.github/workflows/unit.yaml +++ b/.github/workflows/unit.yaml @@ -14,13 +14,13 @@ jobs: strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9, 3.10.8, 3.11.0, pypy-3.6] + python-version: [pypy-3.7, 3.7, 3.8, 3.9, '3.10', 3.11, '3.12.0-rc.3'] os: [ubuntu-20.04] include: - os: windows-latest - python-version: 3.6 + python-version: 3.7 - os: macos-latest - python-version: 3.6 + python-version: 3.7 steps: - uses: actions/checkout@v2 @@ -45,8 +45,8 @@ jobs: run: | tox -e test - - name: Run unit tests with latest Twisted version - if: matrix.python-version != '3.7' && matrix.python-version != '3.6' && matrix.python-version != 'pypy-3.6' + - name: Run unit tests with latest Twisted version (only for python 3.8 and later) + if: matrix.python-version != '3.7' && matrix.python-version != 'pypy-3.7' run: | tox -e test-latest-twisted diff --git a/README.rst b/README.rst index bdffdffe6..d21008259 100644 --- a/README.rst +++ b/README.rst @@ -76,6 +76,10 @@ What's New? in development ^^^^^^^^^^^^^^ +This is the last major release to support Python 3.7. + +* Drop support for Python 3.6 +* Add support for Python 3.12 * `ExtRegistrar.register_post_processor()` now supports a `priority` argument that is an int. Highest priority callables will be called first during post-processing. diff --git a/docs/tests/test_standard_library_docs.py b/docs/tests/test_standard_library_docs.py index cfae595b0..a074a79a1 100644 --- a/docs/tests/test_standard_library_docs.py +++ b/docs/tests/test_standard_library_docs.py @@ -23,4 +23,11 @@ def test_std_lib_docs() -> None: elif entry.is_dir() and entry.joinpath('__init__.py').exists(): # Package assert BASE_DIR.joinpath('Lib.'+entry.name+'.html').exists() - +def test_std_lib_logs() -> None: + """ + 'Cannot parse file' do not appear too much. + This test expect a run.log file in cpython-output directory + """ + log = (BASE_DIR / 'run.log').read_text() + assert log.count('cannot parse file') == 4 + diff --git a/pydoctor/_configparser.py b/pydoctor/_configparser.py index 8022943ec..71af80686 100644 --- a/pydoctor/_configparser.py +++ b/pydoctor/_configparser.py @@ -20,6 +20,8 @@ >>> parser = ArgumentParser(..., default_config_files=['./pyproject.toml', 'setup.cfg', 'my_super_tool.ini'], config_file_parser_class=MixedParser) """ +from __future__ import annotations + import argparse from collections import OrderedDict import re diff --git a/pydoctor/astbuilder.py b/pydoctor/astbuilder.py index 4ace10bc4..218bb5da9 100644 --- a/pydoctor/astbuilder.py +++ b/pydoctor/astbuilder.py @@ -1,4 +1,5 @@ """Convert ASTs into L{pydoctor.model.Documentable} instances.""" +from __future__ import annotations import ast import sys @@ -16,8 +17,8 @@ from pydoctor import epydoc2stan, model, node2stan, extensions, linker from pydoctor.epydoc.markup._pyval_repr import colorize_inline_pyval from pydoctor.astutils import (is_none_literal, is_typing_annotation, is_using_annotations, is_using_typing_final, node2dottedname, node2fullname, - is__name__equals__main__, unstring_annotation, iterassign, extract_docstring_linenum, infer_type, get_parents, - NodeVisitor, Parentage) + is__name__equals__main__, unstring_annotation, iterassign, extract_docstring_linenum, infer_type, get_parents, + get_docstring_node, NodeVisitor, Parentage, Str) def parseFile(path: Path) -> ast.Module: @@ -199,8 +200,9 @@ def visit_Module(self, node: ast.Module) -> None: Parentage().visit(node) self.builder.push(self.module, 0) - if len(node.body) > 0 and isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Str): - self.module.setDocstring(node.body[0].value) + doc_node = get_docstring_node(node) + if doc_node is not None: + self.module.setDocstring(doc_node) epydoc2stan.extract_fields(self.module) def depart_Module(self, node: ast.Module) -> None: @@ -257,8 +259,9 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: cls._initialbaseobjects = initialbaseobjects cls._initialbases = initialbases - if len(node.body) > 0 and isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Str): - cls.setDocstring(node.body[0].value) + doc_node = get_docstring_node(node) + if doc_node is not None: + cls.setDocstring(doc_node) epydoc2stan.extract_fields(cls) if node.decorator_list: @@ -752,7 +755,7 @@ def visit_Assign(self, node: ast.Assign) -> None: if type_comment is None: annotation = None else: - annotation = unstring_annotation(ast.Str(type_comment, lineno=lineno), self.builder.current) + annotation = unstring_annotation(ast.Constant(type_comment, lineno=lineno), self.builder.current) for target in node.targets: if isinstance(target, ast.Tuple): @@ -773,7 +776,7 @@ def visit_AugAssign(self, node:ast.AugAssign) -> None: def visit_Expr(self, node: ast.Expr) -> None: value = node.value - if isinstance(value, ast.Str): + if isinstance(value, Str): attr = self.builder.currentAttr if attr is not None: attr.setDocstring(value) @@ -803,11 +806,7 @@ def _handleFunctionDef(self, lineno = node.decorator_list[0].lineno # extracting docstring - docstring: Optional[ast.Str] = None - if len(node.body) > 0 and isinstance(node.body[0], ast.Expr) \ - and isinstance(node.body[0].value, ast.Str): - docstring = node.body[0].value - + doc_node = get_docstring_node(node) func_name = node.name # determine the function's kind @@ -840,7 +839,7 @@ def _handleFunctionDef(self, if is_property: # handle property and skip child nodes. - attr = self._handlePropertyDef(node, docstring, lineno) + attr = self._handlePropertyDef(node, doc_node, lineno) if is_classmethod: attr.report(f'{attr.fullName()} is both property and classmethod') if is_staticmethod: @@ -864,13 +863,13 @@ def _handleFunctionDef(self, func = self.builder.pushFunction(func_name, lineno) func.is_async = is_async - if docstring is not None: + if doc_node is not None: # Docstring not allowed on overload if is_overload_func: - docline = extract_docstring_linenum(docstring) + docline = extract_docstring_linenum(doc_node) func.report(f'{func.fullName()} overload has docstring, unsupported', lineno_offset=docline-func.linenumber) else: - func.setDocstring(docstring) + func.setDocstring(doc_node) func.decorators = node.decorator_list if is_staticmethod: if is_classmethod: @@ -942,7 +941,7 @@ def depart_FunctionDef(self, node: ast.FunctionDef) -> None: def _handlePropertyDef(self, node: Union[ast.AsyncFunctionDef, ast.FunctionDef], - docstring: Optional[ast.Str], + doc_node: Optional[Str], lineno: int ) -> model.Attribute: @@ -951,8 +950,8 @@ def _handlePropertyDef(self, parent=self.builder.current) attr.setLineNumber(lineno) - if docstring is not None: - attr.setDocstring(docstring) + if doc_node is not None: + attr.setDocstring(doc_node) assert attr.docstring is not None pdoc = epydoc2stan.parse_docstring(attr, attr.docstring, attr) other_fields = [] diff --git a/pydoctor/astutils.py b/pydoctor/astutils.py index 36fa38293..53e8c8089 100644 --- a/pydoctor/astutils.py +++ b/pydoctor/astutils.py @@ -1,12 +1,13 @@ """ Various bits of reusable code related to L{ast.AST} node processing. """ +from __future__ import annotations import inspect import platform import sys from numbers import Number -from typing import Iterator, Optional, List, Iterable, Sequence, TYPE_CHECKING, Tuple, Union, cast +from typing import Any, Iterator, Optional, List, Iterable, Sequence, TYPE_CHECKING, Tuple, Union, cast from inspect import BoundArguments, Signature import ast @@ -137,6 +138,7 @@ def _is_str_constant(expr: ast.expr, s: str) -> bool: return isinstance(expr, ast.Constant) and expr.value == s else: # Before Python 3.8 "foo" was parsed as ast.Str. + # TODO: remove me when python3.7 is not supported anymore def get_str_value(expr:ast.expr) -> Optional[str]: if isinstance(expr, ast.Str): return expr.s @@ -193,7 +195,11 @@ def is_using_annotations(expr: Optional[ast.AST], def is_none_literal(node: ast.expr) -> bool: """Does this AST node represent the literal constant None?""" - return isinstance(node, (ast.Constant, ast.NameConstant)) and node.value is None + if sys.version_info >= (3,8): + return isinstance(node, ast.Constant) and node.value is None + else: + # TODO: remove me when python3.7 is not supported anymore + return isinstance(node, (ast.Constant, ast.NameConstant)) and node.value is None def unstring_annotation(node: ast.expr, ctx:'model.Documentable', section:str='annotation') -> ast.expr: """Replace all strings in the given expression by parsed versions. @@ -258,9 +264,10 @@ def visit_Constant(self, node: ast.Constant) -> ast.expr: return const # For Python < 3.8: - - def visit_Str(self, node: ast.Str) -> ast.expr: - return ast.copy_location(self._parse_string(node.s), node) + if sys.version_info < (3,8): + # TODO: remove me when python3.7 is not supported anymore + def visit_Str(self, node: ast.Str) -> ast.expr: + return ast.copy_location(self._parse_string(node.s), node) TYPING_ALIAS = ( "typing.Hashable", @@ -357,6 +364,18 @@ def is_typing_annotation(node: ast.AST, ctx: 'model.Documentable') -> bool: return is_using_annotations(node, TYPING_ALIAS, ctx) or \ is_using_annotations(node, SUBSCRIPTABLE_CLASSES_PEP585, ctx) +def get_docstring_node(node: ast.AST) -> Str | None: + """ + Return the docstring node for the given class, function or module + or None if no docstring can be found. + """ + if not isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef, ast.ClassDef, ast.Module)) or not node.body: + return None + node = node.body[0] + if isinstance(node, ast.Expr): + if isinstance(node.value, Str): + return node.value + return None _string_lineno_is_end = sys.version_info < (3,8) \ and platform.python_implementation() != 'PyPy' @@ -364,7 +383,35 @@ def is_typing_annotation(node: ast.AST, ctx: 'model.Documentable') -> bool: line in the string, rather than the first line. """ -def extract_docstring_linenum(node: ast.Str) -> int: + +class _StrMeta(type): + if sys.version_info >= (3,8): + def __instancecheck__(self, instance: object) -> bool: + if isinstance(instance, ast.expr): + return get_str_value(instance) is not None + return False + else: + # TODO: remove me when python3.7 is not supported + def __instancecheck__(self, instance: object) -> bool: + return isinstance(instance, ast.Str) + +class Str(ast.expr, metaclass=_StrMeta): + """ + Wraps ast.Constant/ast.Str for `isinstance` checks and annotations. + Ensures that the value is actually a string. + Do not try to instanciate this class. + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + raise TypeError(f'{Str.__qualname__} cannot be instanciated') + + if sys.version_info >= (3,8): + value: str + else: + # TODO: remove me when python3.7 is not supported + s: str + +def extract_docstring_linenum(node: Str) -> int: r""" In older CPython versions, the AST only tells us the end line number and we must approximate the start line number. @@ -374,7 +421,11 @@ def extract_docstring_linenum(node: ast.Str) -> int: Leading blank lines are stripped by cleandoc(), so we must return the line number of the first non-blank line. """ - doc = node.s + if sys.version_info >= (3,8): + doc = node.value + else: + # TODO: remove me when python3.7 is not supported + doc = node.s lineno = node.lineno if _string_lineno_is_end: # In older CPython versions, the AST only tells us the end line @@ -393,7 +444,7 @@ def extract_docstring_linenum(node: ast.Str) -> int: return lineno -def extract_docstring(node: ast.Str) -> Tuple[int, str]: +def extract_docstring(node: Str) -> Tuple[int, str]: """ Extract docstring information from an ast node that represents the docstring. @@ -401,8 +452,13 @@ def extract_docstring(node: ast.Str) -> Tuple[int, str]: - The line number of the first non-blank line of the docsring. See L{extract_docstring_linenum}. - The docstring to be parsed, cleaned by L{inspect.cleandoc}. """ + if sys.version_info >= (3,8): + value = node.value + else: + # TODO: remove me when python3.7 is not supported + value = node.s lineno = extract_docstring_linenum(node) - return lineno, inspect.cleandoc(node.s) + return lineno, inspect.cleandoc(value) def infer_type(expr: ast.expr) -> Optional[ast.expr]: @@ -435,7 +491,7 @@ def _annotation_for_value(value: object) -> Optional[ast.expr]: ann_elem = ast.Tuple(elts=[ann_elem, ann_value]) if ann_elem is not None: if name == 'tuple': - ann_elem = ast.Tuple(elts=[ann_elem, ast.Ellipsis()]) + ann_elem = ast.Tuple(elts=[ann_elem, ast.Constant(value=...)]) return ast.Subscript(value=ast.Name(id=name), slice=ast.Index(value=ann_elem)) return ast.Name(id=name) diff --git a/pydoctor/driver.py b/pydoctor/driver.py index d1562c952..1bf8b9cd1 100644 --- a/pydoctor/driver.py +++ b/pydoctor/driver.py @@ -1,4 +1,5 @@ """The entry point.""" +from __future__ import annotations from typing import Sequence import datetime diff --git a/pydoctor/epydoc/doctest.py b/pydoctor/epydoc/doctest.py index e177a03a5..442ef131e 100644 --- a/pydoctor/epydoc/doctest.py +++ b/pydoctor/epydoc/doctest.py @@ -4,10 +4,10 @@ # # Created [06/28/03 02:52 AM] # - """ Syntax highlighting for blocks of Python code. """ +from __future__ import annotations __docformat__ = 'epytext en' diff --git a/pydoctor/epydoc/docutils.py b/pydoctor/epydoc/docutils.py index dfae3c150..44ce8c61e 100644 --- a/pydoctor/epydoc/docutils.py +++ b/pydoctor/epydoc/docutils.py @@ -1,6 +1,8 @@ """ Collection of helper functions and classes related to the creation and processing of L{docutils} nodes. """ +from __future__ import annotations + from typing import Iterable, Iterator, Optional import optparse diff --git a/pydoctor/epydoc/markup/__init__.py b/pydoctor/epydoc/markup/__init__.py index 709ff2c2b..3933934fb 100644 --- a/pydoctor/epydoc/markup/__init__.py +++ b/pydoctor/epydoc/markup/__init__.py @@ -4,7 +4,6 @@ # A python documentation Module # Edward Loper # - """ Markup language support for docstrings. Each submodule defines a parser for a single markup language. These parsers convert an @@ -31,6 +30,7 @@ classes record information about the cause, location, and severity of each error. """ +from __future__ import annotations __docformat__ = 'epytext en' from typing import Callable, ContextManager, List, Optional, Sequence, Iterator, TYPE_CHECKING diff --git a/pydoctor/epydoc/markup/_napoleon.py b/pydoctor/epydoc/markup/_napoleon.py index 471db9d9c..e31dd2c92 100644 --- a/pydoctor/epydoc/markup/_napoleon.py +++ b/pydoctor/epydoc/markup/_napoleon.py @@ -2,6 +2,8 @@ This module contains a class to wrap shared behaviour between L{pydoctor.epydoc.markup.numpy} and L{pydoctor.epydoc.markup.google}. """ +from __future__ import annotations + from typing import List, Optional, Type from pydoctor.epydoc.markup import ParsedDocstring, ParseError, processtypes diff --git a/pydoctor/epydoc/markup/_pyval_repr.py b/pydoctor/epydoc/markup/_pyval_repr.py index b1b8b4985..313325fa4 100644 --- a/pydoctor/epydoc/markup/_pyval_repr.py +++ b/pydoctor/epydoc/markup/_pyval_repr.py @@ -4,7 +4,6 @@ # Author: Edward Loper # URL: # - """ Syntax highlighter for Python values. Currently provides special colorization support for: @@ -32,6 +31,7 @@ B{Usage}: >>> """ +from __future__ import annotations __docformat__ = 'epytext en' @@ -512,19 +512,29 @@ def _colorize_str(self, pyval: AnyStr, state: _ColorizerState, prefix: AnyStr, @staticmethod def _is_ast_constant(node: ast.AST) -> bool: - return isinstance(node, (ast.Num, ast.Str, ast.Bytes, - ast.Constant, ast.NameConstant, ast.Ellipsis)) + if sys.version_info[:2] >= (3, 8): + return isinstance(node, ast.Constant) + else: + # TODO: remove me when python3.7 is not supported anymore + return isinstance(node, (ast.Num, ast.Str, ast.Bytes, + ast.Constant, ast.NameConstant, ast.Ellipsis)) @staticmethod def _get_ast_constant_val(node: ast.AST) -> Any: # Deprecated since version 3.8: Replaced by Constant - if isinstance(node, ast.Num): - return(node.n) - if isinstance(node, (ast.Str, ast.Bytes)): - return(node.s) - if isinstance(node, (ast.Constant, ast.NameConstant)): - return(node.value) - if isinstance(node, ast.Ellipsis): - return(...) + if sys.version_info[:2] >= (3, 8): + if isinstance(node, ast.Constant): + return node.value + else: + # TODO: remove me when python3.7 is not supported anymore + if isinstance(node, ast.Num): + return(node.n) + if isinstance(node, (ast.Str, ast.Bytes)): + return(node.s) + if isinstance(node, (ast.Constant, ast.NameConstant)): + return(node.value) + if isinstance(node, ast.Ellipsis): + return(...) + raise RuntimeError(f'expected a constant: {ast.dump(node)}') def _colorize_ast_constant(self, pyval: ast.AST, state: _ColorizerState) -> None: val = self._get_ast_constant_val(pyval) @@ -1043,4 +1053,3 @@ def _output(self, s: AnyStr, css_class: Optional[str], else: element = nodes.Text(segment) state.result += [element, self.LINEWRAP] - diff --git a/pydoctor/epydoc/markup/_types.py b/pydoctor/epydoc/markup/_types.py index b221ccb87..21b705daa 100644 --- a/pydoctor/epydoc/markup/_types.py +++ b/pydoctor/epydoc/markup/_types.py @@ -3,6 +3,7 @@ This module provides yet another L{ParsedDocstring} subclass. """ +from __future__ import annotations from typing import Callable, Dict, List, Tuple, Union diff --git a/pydoctor/epydoc/markup/epytext.py b/pydoctor/epydoc/markup/epytext.py index acd9df5d7..0026614d2 100644 --- a/pydoctor/epydoc/markup/epytext.py +++ b/pydoctor/epydoc/markup/epytext.py @@ -4,7 +4,6 @@ # # Created [04/10/01 12:00 AM] # - """ Parser for epytext strings. Epytext is a lightweight markup whose primary intended application is Python documentation strings. This @@ -122,6 +121,7 @@ """ # Note: the symbol list is appended to the docstring automatically, # below. +from __future__ import annotations __docformat__ = 'epytext en' diff --git a/pydoctor/epydoc/markup/google.py b/pydoctor/epydoc/markup/google.py index 9b484b46e..ea2203407 100644 --- a/pydoctor/epydoc/markup/google.py +++ b/pydoctor/epydoc/markup/google.py @@ -4,10 +4,13 @@ @See: L{pydoctor.epydoc.markup.numpy} @See: L{pydoctor.epydoc.markup._napoleon} """ +from __future__ import annotations + from typing import Optional -from pydoctor.model import Documentable + from pydoctor.epydoc.markup import ParserFunction from pydoctor.epydoc.markup._napoleon import NapoelonDocstringParser +from pydoctor.model import Documentable def get_parser(obj: Optional[Documentable]) -> ParserFunction: diff --git a/pydoctor/epydoc/markup/numpy.py b/pydoctor/epydoc/markup/numpy.py index e1e0c0f79..f44756bc9 100644 --- a/pydoctor/epydoc/markup/numpy.py +++ b/pydoctor/epydoc/markup/numpy.py @@ -4,10 +4,13 @@ @See: L{pydoctor.epydoc.markup.google} @See: L{pydoctor.epydoc.markup._napoleon} """ +from __future__ import annotations + from typing import Optional -from pydoctor.model import Documentable + from pydoctor.epydoc.markup import ParserFunction from pydoctor.epydoc.markup._napoleon import NapoelonDocstringParser +from pydoctor.model import Documentable def get_parser(obj: Optional[Documentable]) -> ParserFunction: diff --git a/pydoctor/epydoc/markup/plaintext.py b/pydoctor/epydoc/markup/plaintext.py index 22a438bb9..1c7b1fd71 100644 --- a/pydoctor/epydoc/markup/plaintext.py +++ b/pydoctor/epydoc/markup/plaintext.py @@ -4,11 +4,11 @@ # # Created [04/10/01 12:00 AM] # - """ Parser for plaintext docstrings. Plaintext docstrings are rendered as verbatim output, preserving all whitespace. """ +from __future__ import annotations __docformat__ = 'epytext en' from typing import List, Optional diff --git a/pydoctor/epydoc/markup/restructuredtext.py b/pydoctor/epydoc/markup/restructuredtext.py index 82398c612..9025083ee 100644 --- a/pydoctor/epydoc/markup/restructuredtext.py +++ b/pydoctor/epydoc/markup/restructuredtext.py @@ -4,7 +4,6 @@ # # Created [06/28/03 02:52 AM] # - """ Epydoc parser for ReStructuredText strings. ReStructuredText is the standard markup language used by the Docutils project. @@ -39,6 +38,7 @@ names of the field tags that should be used for individual entries in the list. """ +from __future__ import annotations __docformat__ = 'epytext en' from typing import Iterable, List, Optional, Sequence, Set, cast diff --git a/pydoctor/epydoc2stan.py b/pydoctor/epydoc2stan.py index fa34e94be..1b2e1b069 100644 --- a/pydoctor/epydoc2stan.py +++ b/pydoctor/epydoc2stan.py @@ -1,6 +1,7 @@ """ Convert L{pydoctor.epydoc} parsed markup into renderable content. """ +from __future__ import annotations from collections import defaultdict import enum diff --git a/pydoctor/extensions/__init__.py b/pydoctor/extensions/__init__.py index 6f92c1b0d..83965d838 100644 --- a/pydoctor/extensions/__init__.py +++ b/pydoctor/extensions/__init__.py @@ -3,6 +3,8 @@ An extension can be composed by mixin classes, AST builder visitor extensions and post processors. """ +from __future__ import annotations + import importlib import sys from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Type, Union, TYPE_CHECKING, cast diff --git a/pydoctor/extensions/attrs.py b/pydoctor/extensions/attrs.py index 364b41e22..212910f80 100644 --- a/pydoctor/extensions/attrs.py +++ b/pydoctor/extensions/attrs.py @@ -1,7 +1,7 @@ - """ Support for L{attrs}. """ +from __future__ import annotations import ast import inspect diff --git a/pydoctor/extensions/deprecate.py b/pydoctor/extensions/deprecate.py index 35622e335..dbdbf602c 100644 --- a/pydoctor/extensions/deprecate.py +++ b/pydoctor/extensions/deprecate.py @@ -5,6 +5,7 @@ """ Support for L{twisted.python.deprecate}. """ +from __future__ import annotations import ast import inspect diff --git a/pydoctor/extensions/zopeinterface.py b/pydoctor/extensions/zopeinterface.py index 8c22cc192..a321ae975 100644 --- a/pydoctor/extensions/zopeinterface.py +++ b/pydoctor/extensions/zopeinterface.py @@ -1,4 +1,5 @@ """Support for Zope interfaces.""" +from __future__ import annotations from typing import Iterable, Iterator, List, Optional, Union import ast @@ -210,7 +211,7 @@ def _handleZopeInterfaceAssignmentInClass(self, if funcName == 'zope.interface.Attribute': attr.kind = model.DocumentableKind.ATTRIBUTE args = expr.args - if len(args) == 1 and isinstance(args[0], ast.Str): + if len(args) == 1 and isinstance(args[0], astutils.Str): attr.setDocstring(args[0]) else: attr.report( @@ -232,7 +233,7 @@ def _handleZopeInterfaceAssignmentInClass(self, keywords = {arg.arg: arg.value for arg in expr.keywords} descrNode = keywords.get('description') - if isinstance(descrNode, ast.Str): + if isinstance(descrNode, astutils.Str): attr.setDocstring(descrNode) elif descrNode is not None: attr.report( diff --git a/pydoctor/factory.py b/pydoctor/factory.py index e56fb44a9..57be564f0 100644 --- a/pydoctor/factory.py +++ b/pydoctor/factory.py @@ -1,6 +1,7 @@ """ Create customizable model classes. """ +from __future__ import annotations from typing import Dict, List, Tuple, Type, Any, Union, Sequence, TYPE_CHECKING diff --git a/pydoctor/linker.py b/pydoctor/linker.py index c2430a4b2..5a0b47aee 100644 --- a/pydoctor/linker.py +++ b/pydoctor/linker.py @@ -1,6 +1,7 @@ """ This module provides implementations of epydoc's L{DocstringLinker} class. """ +from __future__ import annotations import contextlib from twisted.web.template import Tag, tags diff --git a/pydoctor/model.py b/pydoctor/model.py index 41bb4c10f..61f84a2bf 100644 --- a/pydoctor/model.py +++ b/pydoctor/model.py @@ -5,6 +5,7 @@ system being documented. An instance of L{System} represents the whole system being documented -- a System is a bad of Documentables, in some sense. """ +from __future__ import annotations import abc import ast @@ -158,7 +159,7 @@ def setup(self) -> None: self.contents: Dict[str, Documentable] = {} self._linker: Optional['linker.DocstringLinker'] = None - def setDocstring(self, node: ast.Str) -> None: + def setDocstring(self, node: astutils.Str) -> None: lineno, doc = astutils.extract_docstring(node) self.docstring = doc self.docstring_lineno = lineno diff --git a/pydoctor/mro.py b/pydoctor/mro.py index 39653653e..e8941e2aa 100644 --- a/pydoctor/mro.py +++ b/pydoctor/mro.py @@ -23,6 +23,7 @@ """ C3 linearization algorithm. """ +from __future__ import annotations from collections import deque from itertools import islice diff --git a/pydoctor/napoleon/docstring.py b/pydoctor/napoleon/docstring.py index 78e2c8fcb..16485180d 100644 --- a/pydoctor/napoleon/docstring.py +++ b/pydoctor/napoleon/docstring.py @@ -8,6 +8,8 @@ :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +from __future__ import annotations + import collections from enum import Enum, auto import re diff --git a/pydoctor/napoleon/iterators.py b/pydoctor/napoleon/iterators.py index 4235c0c25..e7ca2a8e7 100644 --- a/pydoctor/napoleon/iterators.py +++ b/pydoctor/napoleon/iterators.py @@ -8,6 +8,7 @@ :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +from __future__ import annotations import collections from typing import ( diff --git a/pydoctor/node2stan.py b/pydoctor/node2stan.py index 6acab4e91..bdc3cb543 100644 --- a/pydoctor/node2stan.py +++ b/pydoctor/node2stan.py @@ -1,6 +1,8 @@ """ Helper function to convert L{docutils} nodes to Stan tree. """ +from __future__ import annotations + import re import optparse from typing import Any, Callable, ClassVar, Iterable, List, Optional, Union, TYPE_CHECKING diff --git a/pydoctor/options.py b/pydoctor/options.py index 43e6aaf20..272539dce 100644 --- a/pydoctor/options.py +++ b/pydoctor/options.py @@ -1,6 +1,7 @@ """ The command-line parsing. """ +from __future__ import annotations import re from typing import Sequence, List, Optional, Type, Tuple, TYPE_CHECKING diff --git a/pydoctor/qnmatch.py b/pydoctor/qnmatch.py index b140f73bd..6bb875b0b 100644 --- a/pydoctor/qnmatch.py +++ b/pydoctor/qnmatch.py @@ -9,6 +9,8 @@ [seq] matches any character in seq [!seq] matches any char not in seq """ +from __future__ import annotations + import functools import re from typing import Any, Callable diff --git a/pydoctor/sphinx.py b/pydoctor/sphinx.py index 6e1a8ebad..5766934a7 100644 --- a/pydoctor/sphinx.py +++ b/pydoctor/sphinx.py @@ -1,6 +1,7 @@ """ Support for Sphinx compatibility. """ +from __future__ import annotations import logging import os diff --git a/pydoctor/sphinx_ext/build_apidocs.py b/pydoctor/sphinx_ext/build_apidocs.py index 74b1b501b..1247e0071 100644 --- a/pydoctor/sphinx_ext/build_apidocs.py +++ b/pydoctor/sphinx_ext/build_apidocs.py @@ -21,6 +21,8 @@ You must call pydoctor with C{--quiet} argument as otherwise any extra output is converted into Sphinx warnings. """ +from __future__ import annotations + import os import pathlib import shutil diff --git a/pydoctor/templatewriter/__init__.py b/pydoctor/templatewriter/__init__.py index 7a158a57d..78dc0caca 100644 --- a/pydoctor/templatewriter/__init__.py +++ b/pydoctor/templatewriter/__init__.py @@ -1,4 +1,6 @@ """Render pydoctor data as HTML.""" +from __future__ import annotations + from typing import Any, Iterable, Iterator, Optional, Union, TYPE_CHECKING if TYPE_CHECKING: from typing_extensions import Protocol, runtime_checkable diff --git a/pydoctor/templatewriter/pages/__init__.py b/pydoctor/templatewriter/pages/__init__.py index 272239605..efc8bb80f 100644 --- a/pydoctor/templatewriter/pages/__init__.py +++ b/pydoctor/templatewriter/pages/__init__.py @@ -1,4 +1,5 @@ """The classes that turn L{Documentable} instances into objects we can render.""" +from __future__ import annotations from typing import ( TYPE_CHECKING, Dict, Iterator, List, Optional, Mapping, Sequence, diff --git a/pydoctor/templatewriter/pages/attributechild.py b/pydoctor/templatewriter/pages/attributechild.py index 26603bbdb..e22e84fc2 100644 --- a/pydoctor/templatewriter/pages/attributechild.py +++ b/pydoctor/templatewriter/pages/attributechild.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import TYPE_CHECKING, List from twisted.web.iweb import ITemplateLoader diff --git a/pydoctor/templatewriter/pages/functionchild.py b/pydoctor/templatewriter/pages/functionchild.py index 4a486f5e9..0ddbff371 100644 --- a/pydoctor/templatewriter/pages/functionchild.py +++ b/pydoctor/templatewriter/pages/functionchild.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import TYPE_CHECKING, List from twisted.web.iweb import ITemplateLoader diff --git a/pydoctor/templatewriter/pages/sidebar.py b/pydoctor/templatewriter/pages/sidebar.py index c5bea1fc8..9d3c181d2 100644 --- a/pydoctor/templatewriter/pages/sidebar.py +++ b/pydoctor/templatewriter/pages/sidebar.py @@ -1,6 +1,8 @@ """ Classes for the sidebar generation. """ +from __future__ import annotations + from typing import Any, Iterator, List, Optional, Sequence, Tuple, Type, Union from twisted.web.iweb import IRequest, ITemplateLoader from twisted.web.template import TagLoader, renderer, Tag, Element, tags diff --git a/pydoctor/templatewriter/pages/table.py b/pydoctor/templatewriter/pages/table.py index 0099eb7df..05b486c19 100644 --- a/pydoctor/templatewriter/pages/table.py +++ b/pydoctor/templatewriter/pages/table.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import TYPE_CHECKING, Collection from twisted.web.iweb import ITemplateLoader diff --git a/pydoctor/templatewriter/search.py b/pydoctor/templatewriter/search.py index a938920af..3faf88c69 100644 --- a/pydoctor/templatewriter/search.py +++ b/pydoctor/templatewriter/search.py @@ -1,6 +1,7 @@ """ Code building ``all-documents.html``, ``searchindex.json`` and ``fullsearchindex.json``. """ +from __future__ import annotations from pathlib import Path from typing import Iterator, List, Optional, Tuple, Type, Dict, TYPE_CHECKING diff --git a/pydoctor/templatewriter/summary.py b/pydoctor/templatewriter/summary.py index 0aeea6c8f..73bebe401 100644 --- a/pydoctor/templatewriter/summary.py +++ b/pydoctor/templatewriter/summary.py @@ -1,4 +1,5 @@ """Classes that generate the summary pages.""" +from __future__ import annotations from collections import defaultdict from typing import ( diff --git a/pydoctor/templatewriter/util.py b/pydoctor/templatewriter/util.py index a207102d1..2ab28ee78 100644 --- a/pydoctor/templatewriter/util.py +++ b/pydoctor/templatewriter/util.py @@ -1,4 +1,5 @@ """Miscellaneous utilities for the HTML writer.""" +from __future__ import annotations import warnings from typing import (Any, Dict, Generic, Iterable, Iterator, List, Mapping, diff --git a/pydoctor/templatewriter/writer.py b/pydoctor/templatewriter/writer.py index 2adcc0afa..06df1d5b4 100644 --- a/pydoctor/templatewriter/writer.py +++ b/pydoctor/templatewriter/writer.py @@ -1,5 +1,5 @@ """Badly named module that contains the driving code for the rendering.""" - +from __future__ import annotations import itertools from pathlib import Path diff --git a/pydoctor/utils.py b/pydoctor/utils.py index b3a44edac..5f1231cf6 100644 --- a/pydoctor/utils.py +++ b/pydoctor/utils.py @@ -1,4 +1,6 @@ """General purpose utility functions.""" +from __future__ import annotations + from pathlib import Path import sys import functools diff --git a/pydoctor/visitor.py b/pydoctor/visitor.py index 4b87aebcf..9c086a68b 100644 --- a/pydoctor/visitor.py +++ b/pydoctor/visitor.py @@ -1,6 +1,8 @@ """ General purpose visitor pattern implementation, with extensions. """ +from __future__ import annotations + from collections import defaultdict import enum import abc diff --git a/setup.cfg b/setup.cfg index 71ae2f469..8c836ee23 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,12 +19,12 @@ classifiers = License :: OSI Approved :: MIT License Operating System :: OS Independent Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Documentation @@ -32,14 +32,14 @@ classifiers = [options] packages = find: -python_requires = >=3.6 +python_requires = >=3.7 install_requires = ; New requirements are OK but since pydotor is published as a debian package, ; we should mak sure requirements already exists in repository https://tracker.debian.org/. appdirs - CacheControl[filecache] + CacheControl[filecache]>=0.12.14 Twisted - urllib3<2 + urllib3~=2.0 requests astor attrs @@ -98,9 +98,13 @@ doctest_optionflags = ELLIPSIS IGNORE_EXCEPTION_DETAIL xfail_strict = true filterwarnings = error - - ; Sphinx imports distutils, this warning gets trigerred only in the test. See https://github.com/sphinx-doc/sphinx/issues/9820 - ignore:The distutils package is deprecated and slated for removal in Python 3.12\. Use setuptools or check PEP 632 for potential alternatives:DeprecationWarning: + ; astor uses the ast.Num, ast.Str etc + ignore:ast\.Num is deprecated and will be removed in Python 3\.14; use ast\.Constant instead:DeprecationWarning: + ignore:ast\.Str is deprecated and will be removed in Python 3\.14; use ast\.Constant instead:DeprecationWarning: + ignore:ast\.Bytes is deprecated and will be removed in Python 3\.14; use ast\.Constant instead:DeprecationWarning: + ignore:ast\.Ellipsis is deprecated and will be removed in Python 3\.14; use ast\.Constant instead:DeprecationWarning: + ignore:ast\.NameConstant is deprecated and will be removed in Python 3\.14; use ast\.Constant instead:DeprecationWarning: + [tool:pydoctor] intersphinx = diff --git a/tox.ini b/tox.ini index 049206426..706c60f15 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,7 @@ allowlist_externals = rm sh touch + mkdir passenv = * [testenv:test] @@ -72,24 +73,25 @@ commands = [testenv:cpython-apidocs] -description = Build CPython API documentation +description = Build CPython 3.11 API documentation deps = pytest commands = sh -c "if [ ! -d {toxworkdir}/cpython ]; then \ - git clone --depth 1 https://github.com/python/cpython.git {toxworkdir}/cpython; \ + git clone https://github.com/python/cpython.git {toxworkdir}/cpython; \ fi" - sh -c "cd {toxworkdir}/cpython && git pull" + sh -c "cd {toxworkdir}/cpython && git checkout 3.11" touch {toxworkdir}/cpython/Lib/__init__.py rm -rf {toxworkdir}/cpython-output - - pydoctor \ + mkdir {toxworkdir}/cpython-output + sh -c "pydoctor \ --docformat=restructuredtext \ --project-base-dir={toxworkdir}/cpython \ --html-output={toxworkdir}/cpython-output \ ; the sidebar is making the build time two times longer for the cpython docs, ; so we just skip it. --no-sidebar \ - {toxworkdir}/cpython/Lib + {toxworkdir}/cpython/Lib | tee {toxworkdir}/cpython-output/run.log" pytest -vv docs/tests/test_standard_library_docs.py [testenv:numpy-apidocs] @@ -160,16 +162,15 @@ commands = assert code in [0,2], 'pydoctor exited with code %s, expected code 0 or 2.'%code" [testenv:cpython-summary] -description = Parse CPython code and write a summary only +description = Parse CPython 3.11 code and write a summary only commands = sh -c "if [ ! -d {toxworkdir}/cpython ]; then \ - git clone --depth 1 https://github.com/python/cpython.git {toxworkdir}/cpython; \ + git clone https://github.com/python/cpython.git {toxworkdir}/cpython; \ fi" - sh -c "cd {toxworkdir}/cpython && git pull" + sh -c "cd {toxworkdir}/cpython && git checkout 3.11" touch {toxworkdir}/cpython/Lib/__init__.py rm -rf {toxworkdir}/cpython-summary-output - # TODO: Switch to restructuredtext when #261 is fixed. pydoctor \ --docformat=plaintext \ --project-base-dir={toxworkdir}/cpython \