Skip to content

Commit

Permalink
Merge branch 'master' into 801-signature-spans
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlatr authored Dec 13, 2024
2 parents cc82f10 + 1c6478e commit 07fc41d
Show file tree
Hide file tree
Showing 45 changed files with 1,323 additions and 572 deletions.
5 changes: 0 additions & 5 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
<!--
Thanks for your contribution!
Make sure the tests passes with the following command:
tox -p all
Don't forget to include a summary of your changes in the changelog located in the README file.
Read more about contributing to pydoctor here:
https://pydoctor.readthedocs.io/en/latest/contrib.html
-->
8 changes: 4 additions & 4 deletions .github/workflows/unit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ jobs:

strategy:
matrix:
python-version: ['pypy-3.8', 'pypy-3.9', 'pypy-3.10',
'3.8', '3.9', '3.10', '3.11', '3.12', '3.13.0-rc.2']
python-version: ['pypy-3.9', 'pypy-3.10',
'3.9', '3.10', '3.11', '3.12', '3.13']
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
Expand All @@ -42,9 +42,9 @@ jobs:
python -c "print('\nENVIRONMENT VARIABLES\n=====================\n')"
python -c "import os; [print(f'{k}={v}') for k, v in os.environ.items()]"
- name: Run unit tests
- name: Run unit tests and coverage reports
run: |
tox -e test
tox -e test-cov
- name: Run unit tests with latest Twisted version
run: |
Expand Down
17 changes: 17 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ What's New?
in development
^^^^^^^^^^^^^^

* Drop support for Python 3.8.

pydoctor 24.11.1
^^^^^^^^^^^^^^^^

* Fix a bug that would cause a variable marked as `Final` not being considered as a constant if
it was declared under a control-flow block.
* Fix a bug in google and numpy "Attributes" section in module docstring:
the module attributes now shows as "Variables" instead of "Instance Variables".

pydoctor 24.11.0
^^^^^^^^^^^^^^^^

* Drop Python 3.7 and support Python 3.13.
* Implement canonical HTML element (``<link rel="canonical" href="..."/>``) to help search engines reduce outdated content.
Enable this feature by passing the base URL of the API documentation with option ``--html-base-url``.
Expand All @@ -91,6 +104,10 @@ in development
from the ``pydoctor_url_path`` config option now includes a project name which defaults to 'main' (instead of putting None),
use mapping instead of a list to define your own project name.
* Improve the themes so the adds injected by ReadTheDocs are rendered with the correct width and do not overlap too much with the main content.
* Fix an issue in the readthedocs theme that prevented to use the search bar from the summary pages (like the class hierarchy).
* The generated documentation now includes a help page under the path ``/apidocs-help.html``.
This page is accessible by clicking on the information icon in the navbar (````).
* Improve the javascript searching code to better understand terms that contains a dot (``.``).

pydoctor 24.3.3
^^^^^^^^^^^^^^^
Expand Down
9 changes: 5 additions & 4 deletions docs/tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,15 @@ def test_search(query:str, expected:List[str], order_is_important:bool=True) ->
['pydoctor.model.Class',
'pydoctor.factory.Factory.Class',
'pydoctor.model.DocumentableKind.CLASS',
'pydoctor.model.System.Class'])
'pydoctor.model.System.Class',
])

to_stan_results = [
'pydoctor.epydoc.markup.ParsedDocstring.to_stan',
'pydoctor.epydoc.markup.plaintext.ParsedPlaintextDocstring.to_stan',
'pydoctor.epydoc.markup._types.ParsedTypeDocstring.to_stan',
'pydoctor.epydoc.markup._pyval_repr.ColorizedPyvalRepr.to_stan',
'pydoctor.epydoc2stan.ParsedStanOnly.to_stan'
'pydoctor.epydoc2stan.ParsedStanOnly.to_stan',
]
test_search('to_stan*', to_stan_results, order_is_important=False)
test_search('to_stan', to_stan_results, order_is_important=False)
Expand All @@ -207,7 +208,7 @@ def test_search(query:str, expected:List[str], order_is_important:bool=True) ->
'pydoctor.epydoc.markup._types.ParsedTypeDocstring.to_node',
'pydoctor.epydoc.markup.restructuredtext.ParsedRstDocstring.to_node',
'pydoctor.epydoc.markup.epytext.ParsedEpytextDocstring.to_node',
'pydoctor.epydoc2stan.ParsedStanOnly.to_node'
'pydoctor.epydoc2stan.ParsedStanOnly.to_node',
]
test_search('to_node*', to_node_results, order_is_important=False)
test_search('to_node', to_node_results, order_is_important=False)
Expand Down Expand Up @@ -250,7 +251,7 @@ def test_missing_subclasses():
'pydoctor.epydoc.markup.epytext.ParsedEpytextDocstring',
'pydoctor.epydoc.markup.plaintext.ParsedPlaintextDocstring',
'pydoctor.epydoc.markup.restructuredtext.ParsedRstDocstring',
'pydoctor.epydoc2stan.ParsedStanOnly')
'pydoctor.epydoc2stan.ParsedStanOnly', )

with open(BASE_DIR / 'api' / 'pydoctor.epydoc.markup.ParsedDocstring.html', 'r', encoding='utf-8') as stream:
page = stream.read()
Expand Down
12 changes: 1 addition & 11 deletions pydoctor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,7 @@
Warning: PyDoctor's API isn't stable YET, custom builds are prone to break!
"""

from typing import TYPE_CHECKING

# On Python 3.8+, use importlib.metadata from the standard library.
# On older versions, a compatibility package can be installed from PyPI.
try:
import importlib.metadata as importlib_metadata
except ImportError:
if not TYPE_CHECKING:
import importlib_metadata

import importlib.metadata as importlib_metadata

__version__ = importlib_metadata.version('pydoctor')

Expand Down
15 changes: 13 additions & 2 deletions pydoctor/_configparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import argparse
from collections import OrderedDict
import re
import sys
from typing import Any, Callable, Dict, List, Optional, Tuple, TextIO, Union
import csv
import functools
Expand All @@ -33,7 +34,17 @@
import warnings

from configargparse import ConfigFileParserException, ConfigFileParser, ArgumentParser
import toml

if sys.version_info >= (3, 11):
from tomllib import load as _toml_load
import io
# The tomllib module from the standard library
# expect a binary IO and will fail if receives otherwise.
# So we hack a compat function that will work with TextIO and assume the utf-8 encoding.
def toml_load(stream: TextIO) -> Any:
return _toml_load(io.BytesIO(stream.read().encode()))
else:
from toml import load as toml_load

# I did not invented these regex, just put together some stuff from:
# - https://stackoverflow.com/questions/11859442/how-to-match-string-in-quotes-using-regex
Expand Down Expand Up @@ -163,7 +174,7 @@ def parse(self, stream:TextIO) -> Dict[str, Any]:
"""Parses the keys and values from a TOML config file."""
# parse with configparser to allow multi-line values
try:
config = toml.load(stream)
config = toml_load(stream)
except Exception as e:
raise ConfigFileParserException("Couldn't parse TOML file: %s" % e)

Expand Down
60 changes: 28 additions & 32 deletions pydoctor/astbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ def parseFile(path: Path) -> ast.Module:
src = f.read() + b'\n'
return _parse(src, filename=str(path))

if sys.version_info >= (3,8):
_parse = partial(ast.parse, type_comments=True)
else:
_parse = ast.parse
_parse = partial(ast.parse, type_comments=True)

def _maybeAttribute(cls: model.Class, name: str) -> bool:
"""Check whether a name is a potential attribute of the given class.
Expand Down Expand Up @@ -90,9 +87,11 @@ def is_constant(obj: model.Attribute,
@note: Must be called after setting obj.annotation to detect variables using Final.
"""
if is_using_typing_final(annotation, obj):
return True
if not is_attribute_overridden(obj, value) and value:
if not any(isinstance(n, _CONTROL_FLOW_BLOCKS) for n in get_parents(value)):
return obj.name.isupper() or is_using_typing_final(annotation, obj)
return obj.name.isupper()
return False

class TypeAliasVisitorExt(extensions.ModuleVisitorExt):
Expand Down Expand Up @@ -148,24 +147,14 @@ def is_attribute_overridden(obj: model.Attribute, new_value: Optional[ast.expr])
"""
return obj.value is not None and new_value is not None

def _extract_annotation_subscript(annotation: ast.Subscript) -> ast.AST:
"""
Extract the "str, bytes" part from annotations like "Union[str, bytes]".
"""
ann_slice = annotation.slice
if sys.version_info < (3,9) and isinstance(ann_slice, ast.Index):
return ann_slice.value
else:
return ann_slice

def extract_final_subscript(annotation: ast.Subscript) -> ast.expr:
"""
Extract the "str" part from annotations like "Final[str]".
@raises ValueError: If the "Final" annotation is not valid.
"""
ann_slice = _extract_annotation_subscript(annotation)
if isinstance(ann_slice, (ast.ExtSlice, ast.Slice, ast.Tuple)):
ann_slice = annotation.slice
if isinstance(ann_slice, (ast.Slice, ast.Tuple)):
raise ValueError("Annotation is invalid, it should not contain slices.")
else:
assert isinstance(ann_slice, ast.expr)
Expand Down Expand Up @@ -219,6 +208,14 @@ def _infer_attr_annotations(self, scope: model.Documentable) -> None:
if attrib.annotation is None and attrib.value is not None:
# do not override explicit annotation
attrib.annotation = infer_type(attrib.value)

def _tweak_constants_annotations(self, scope: model.Documentable) -> None:
# tweak constants annotations when we leave the scope so we can still
# check whether the annotation uses Final while we're visiting other nodes.
for attrib in scope.contents.values():
if not isinstance(attrib, model.Attribute) or attrib.kind is not model.DocumentableKind.CONSTANT :
continue
self._tweak_constant_annotation(attrib)

def visit_If(self, node: ast.If) -> None:
if isinstance(node.test, ast.Compare):
Expand Down Expand Up @@ -261,6 +258,7 @@ def visit_Module(self, node: ast.Module) -> None:
epydoc2stan.extract_fields(self.module)

def depart_Module(self, node: ast.Module) -> None:
self._tweak_constants_annotations(self.builder.current)
self._infer_attr_annotations(self.builder.current)
self.builder.pop(self.module)

Expand Down Expand Up @@ -349,6 +347,7 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None:


def depart_ClassDef(self, node: ast.ClassDef) -> None:
self._tweak_constants_annotations(self.builder.current)
self._infer_attr_annotations(self.builder.current)
self.builder.popClass()

Expand Down Expand Up @@ -565,29 +564,31 @@ def _handleConstant(cls, obj:model.Attribute,
defaultKind:model.DocumentableKind) -> None:
if is_constant(obj, annotation=annotation, value=value):
obj.kind = model.DocumentableKind.CONSTANT
cls._tweakConstantAnnotation(obj=obj, annotation=annotation,
value=value, lineno=lineno)
# do not call tweak annotation just yet...
elif obj.kind is model.DocumentableKind.CONSTANT:
obj.kind = defaultKind
# reset to the default kind only for attributes that were heuristically
# declared as constants
if not is_using_typing_final(obj.annotation, obj):
obj.kind = defaultKind

@staticmethod
def _tweakConstantAnnotation(obj: model.Attribute, annotation:Optional[ast.expr],
value: Optional[ast.expr], lineno: int) -> None:
def _tweak_constant_annotation(obj: model.Attribute) -> None:
# Display variables annotated with Final with the real type instead.
annotation = obj.annotation
if is_using_typing_final(annotation, obj):
if isinstance(annotation, ast.Subscript):
try:
annotation = extract_final_subscript(annotation)
except ValueError as e:
obj.report(str(e), section='ast', lineno_offset=lineno-obj.linenumber)
obj.annotation = infer_type(value) if value else None
obj.report(str(e), section='ast', lineno_offset=annotation.lineno-obj.linenumber)
obj.annotation = infer_type(obj.value) if obj.value else None
else:
# Will not display as "Final[str]" but rather only "str"
obj.annotation = annotation
else:
# Just plain "Final" annotation.
# Simply ignore it because it's duplication of information.
obj.annotation = infer_type(value) if value else None
obj.annotation = infer_type(obj.value) if obj.value else None

@staticmethod
def _setAttributeAnnotation(obj: model.Attribute,
Expand Down Expand Up @@ -1019,8 +1020,7 @@ def _handleFunctionDef(self,
elif is_classmethod:
func.kind = model.DocumentableKind.CLASS_METHOD

# Position-only arguments were introduced in Python 3.8.
posonlyargs: Sequence[ast.arg] = getattr(node.args, 'posonlyargs', ())
posonlyargs: Sequence[ast.arg] = node.args.posonlyargs

num_pos_args = len(posonlyargs) + len(node.args.args)
defaults = node.args.defaults
Expand Down Expand Up @@ -1129,11 +1129,7 @@ def _annotations_from_function(
"""
def _get_all_args() -> Iterator[ast.arg]:
base_args = func.args
# New on Python 3.8 -- handle absence gracefully
try:
yield from base_args.posonlyargs
except AttributeError:
pass
yield from base_args.posonlyargs
yield from base_args.args
varargs = base_args.vararg
if varargs:
Expand Down
Loading

0 comments on commit 07fc41d

Please sign in to comment.