Source code for sphinx.ext.autodoc._event_listeners

"""Some useful event listener factories for autodoc-process-docstring."""

from __future__ import annotations

import re
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Sequence
    from typing import Any, Protocol

    from sphinx.application import Sphinx
    from sphinx.ext.autodoc._property_types import _AutodocObjType

    class _AutodocProcessDocstringListener(Protocol):
        # parameter names are non-normative
        def __call__(
            self,
            app: Sphinx,
            obj_type: _AutodocObjType,
            full_name: str,
            obj: Any,
            options: Any,
            docstring_lines: list[str],
            /,
        ) -> None: ...

    class _AutodocBeforeProcessSignatureListener(Protocol):  # NoQA: PYI046
        # parameter names are non-normative
        def __call__(
            self,
            app: Sphinx,
            obj: Any,
            is_bound_method: bool,
            /,
        ) -> None: ...

    class _AutodocProcessSignatureListener(Protocol):  # NoQA: PYI046
        # parameter names are non-normative
        # returns: (args, retann) | None
        def __call__(
            self,
            app: Sphinx,
            obj_type: _AutodocObjType,
            full_name: str,
            obj: Any,
            options: Any,
            args: str | None,
            retann: str | None,
            /,
        ) -> tuple[str | None, str | None] | None: ...

    class _AutodocProcessBasesListener(Protocol):  # NoQA: PYI046
        # parameter names are non-normative
        def __call__(
            self,
            app: Sphinx,
            full_name: str,
            obj: Any,
            _unused: None,  # previously: options
            obj_bases: list[type],
            /,
        ) -> None: ...

    class _AutodocSkipMemberListener(Protocol):  # NoQA: PYI046
        # parameter names are non-normative
        # returns: skip_member
        def __call__(
            self,
            app: Sphinx,
            obj_type: _AutodocObjType,
            member_name: str,
            member_obj: Any,
            skip: bool,
            options: Any,
            /,
        ) -> bool | None: ...


[docs] def cut_lines( pre: int, post: int = 0, what: Sequence[str] | None = None ) -> _AutodocProcessDocstringListener: """Return a listener that removes the first *pre* and last *post* lines of every docstring. If *what* is a sequence of strings, only docstrings of a type in *what* will be processed. Use like this (e.g. in the ``setup()`` function of :file:`conf.py`):: from sphinx.ext.autodoc import cut_lines app.connect('autodoc-process-docstring', cut_lines(4, what={'module'})) This can (and should) be used in place of :confval:`automodule_skip_lines`. """ if not what: what_unique: frozenset[str] = frozenset() elif isinstance(what, str): # strongly discouraged what_unique = frozenset({what}) else: what_unique = frozenset(what) def process( app: Sphinx, what_: _AutodocObjType, name: str, obj: Any, options: Any, lines: list[str], ) -> None: if what_unique and what_ not in what_unique: return del lines[:pre] if post: # remove one trailing blank line. if lines and not lines[-1]: lines.pop(-1) del lines[-post:] # make sure there is a blank line at the end if lines and lines[-1]: lines.append('') return process
[docs] def between( marker: str, what: Sequence[str] | None = None, keepempty: bool = False, exclude: bool = False, ) -> _AutodocProcessDocstringListener: """Return a listener that either keeps, or if *exclude* is True excludes, lines between lines that match the *marker* regular expression. If no line matches, the resulting docstring would be empty, so no change will be made unless *keepempty* is true. If *what* is a sequence of strings, only docstrings of a type in *what* will be processed. """ marker_re = re.compile(marker) def process( app: Sphinx, what_: _AutodocObjType, name: str, obj: Any, options: Any, lines: list[str], ) -> None: if what and what_ not in what: return deleted = 0 delete = not exclude orig_lines = lines.copy() for i, line in enumerate(orig_lines): if delete: lines.pop(i - deleted) deleted += 1 if marker_re.match(line): delete = not delete if delete: lines.pop(i - deleted) deleted += 1 if not lines and not keepempty: lines[:] = orig_lines # make sure there is a blank line at the end if lines and lines[-1]: lines.append('') return process