| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- """Utilities to allow inserting docstring fragments for common
- parameters into function and method docstrings."""
- from collections.abc import Callable, Iterable, Mapping
- from typing import Protocol, TypeVar
- import sys
- __all__ = [
- "docformat",
- "inherit_docstring_from",
- "indentcount_lines",
- "filldoc",
- "unindent_dict",
- "unindent_string",
- "extend_notes_in_docstring",
- "replace_notes_in_docstring",
- "doc_replace",
- ]
- _F = TypeVar("_F", bound=Callable[..., object])
- class Decorator(Protocol):
- """A decorator of a function."""
- def __call__(self, func: _F, /) -> _F: ...
- def docformat(docstring: str, docdict: Mapping[str, str] | None = None) -> str:
- """Fill a function docstring from variables in dictionary.
- Adapt the indent of the inserted docs
- Parameters
- ----------
- docstring : str
- A docstring from a function, possibly with dict formatting strings.
- docdict : dict[str, str], optional
- A dictionary with keys that match the dict formatting strings
- and values that are docstring fragments to be inserted. The
- indentation of the inserted docstrings is set to match the
- minimum indentation of the ``docstring`` by adding this
- indentation to all lines of the inserted string, except the
- first.
- Returns
- -------
- docstring : str
- string with requested ``docdict`` strings inserted.
- Examples
- --------
- >>> docformat(' Test string with %(value)s', {'value':'inserted value'})
- ' Test string with inserted value'
- >>> docstring = 'First line\\n Second line\\n %(value)s'
- >>> inserted_string = "indented\\nstring"
- >>> docdict = {'value': inserted_string}
- >>> docformat(docstring, docdict)
- 'First line\\n Second line\\n indented\\n string'
- """
- if not docstring:
- return docstring
- if docdict is None:
- docdict = {}
- if not docdict:
- return docstring
- lines = docstring.expandtabs().splitlines()
- # Find the minimum indent of the main docstring, after first line
- if len(lines) < 2:
- icount = 0
- else:
- icount = indentcount_lines(lines[1:])
- indent = " " * icount
- # Insert this indent to dictionary docstrings
- indented = {}
- for name, dstr in docdict.items():
- lines = dstr.expandtabs().splitlines()
- indented[name] = ("\n" + indent).join(lines)
- return docstring % indented
- def inherit_docstring_from(cls: object) -> Decorator:
- """This decorator modifies the decorated function's docstring by
- replacing occurrences of '%(super)s' with the docstring of the
- method of the same name from the class `cls`.
- If the decorated method has no docstring, it is simply given the
- docstring of `cls`s method.
- Parameters
- ----------
- cls : type or object
- A class with a method with the same name as the decorated method.
- The docstring of the method in this class replaces '%(super)s' in the
- docstring of the decorated method.
- Returns
- -------
- decfunc : function
- The decorator function that modifies the __doc__ attribute
- of its argument.
- Examples
- --------
- In the following, the docstring for Bar.func created using the
- docstring of `Foo.func`.
- >>> class Foo:
- ... def func(self):
- ... '''Do something useful.'''
- ... return
- ...
- >>> class Bar(Foo):
- ... @inherit_docstring_from(Foo)
- ... def func(self):
- ... '''%(super)s
- ... Do it fast.
- ... '''
- ... return
- ...
- >>> b = Bar()
- >>> b.func.__doc__
- 'Do something useful.\n Do it fast.\n '
- """
- def _doc(func: _F) -> _F:
- cls_docstring = getattr(cls, func.__name__).__doc__
- func_docstring = func.__doc__
- if func_docstring is None:
- func.__doc__ = cls_docstring
- else:
- new_docstring = func_docstring % dict(super=cls_docstring)
- func.__doc__ = new_docstring
- return func
- return _doc
- def extend_notes_in_docstring(cls: object, notes: str) -> Decorator:
- """This decorator replaces the decorated function's docstring
- with the docstring from corresponding method in `cls`.
- It extends the 'Notes' section of that docstring to include
- the given `notes`.
- Parameters
- ----------
- cls : type or object
- A class with a method with the same name as the decorated method.
- The docstring of the method in this class replaces the docstring of the
- decorated method.
- notes : str
- Additional notes to append to the 'Notes' section of the docstring.
- Returns
- -------
- decfunc : function
- The decorator function that modifies the __doc__ attribute
- of its argument.
- """
- def _doc(func: _F) -> _F:
- cls_docstring = getattr(cls, func.__name__).__doc__
- # If python is called with -OO option,
- # there is no docstring
- if cls_docstring is None:
- return func
- end_of_notes = cls_docstring.find(" References\n")
- if end_of_notes == -1:
- end_of_notes = cls_docstring.find(" Examples\n")
- if end_of_notes == -1:
- end_of_notes = len(cls_docstring)
- func.__doc__ = (
- cls_docstring[:end_of_notes] + notes + cls_docstring[end_of_notes:]
- )
- return func
- return _doc
- def replace_notes_in_docstring(cls: object, notes: str) -> Decorator:
- """This decorator replaces the decorated function's docstring
- with the docstring from corresponding method in `cls`.
- It replaces the 'Notes' section of that docstring with
- the given `notes`.
- Parameters
- ----------
- cls : type or object
- A class with a method with the same name as the decorated method.
- The docstring of the method in this class replaces the docstring of the
- decorated method.
- notes : str
- The notes to replace the existing 'Notes' section with.
- Returns
- -------
- decfunc : function
- The decorator function that modifies the __doc__ attribute
- of its argument.
- """
- def _doc(func: _F) -> _F:
- cls_docstring = getattr(cls, func.__name__).__doc__
- notes_header = " Notes\n -----\n"
- # If python is called with -OO option,
- # there is no docstring
- if cls_docstring is None:
- return func
- start_of_notes = cls_docstring.find(notes_header)
- end_of_notes = cls_docstring.find(" References\n")
- if end_of_notes == -1:
- end_of_notes = cls_docstring.find(" Examples\n")
- if end_of_notes == -1:
- end_of_notes = len(cls_docstring)
- func.__doc__ = (
- cls_docstring[: start_of_notes + len(notes_header)]
- + notes
- + cls_docstring[end_of_notes:]
- )
- return func
- return _doc
- def indentcount_lines(lines: Iterable[str]) -> int:
- """Minimum indent for all lines in line list
- Parameters
- ----------
- lines : Iterable[str]
- The lines to find the minimum indent of.
- Returns
- -------
- indent : int
- The minimum indent.
- Examples
- --------
- >>> lines = [' one', ' two', ' three']
- >>> indentcount_lines(lines)
- 1
- >>> lines = []
- >>> indentcount_lines(lines)
- 0
- >>> lines = [' one']
- >>> indentcount_lines(lines)
- 1
- >>> indentcount_lines([' '])
- 0
- """
- indentno = sys.maxsize
- for line in lines:
- stripped = line.lstrip()
- if stripped:
- indentno = min(indentno, len(line) - len(stripped))
- if indentno == sys.maxsize:
- return 0
- return indentno
- def filldoc(docdict: Mapping[str, str], unindent_params: bool = True) -> Decorator:
- """Return docstring decorator using docdict variable dictionary.
- Parameters
- ----------
- docdict : dict[str, str]
- A dictionary containing name, docstring fragment pairs.
- unindent_params : bool, optional
- If True, strip common indentation from all parameters in docdict.
- Default is False.
- Returns
- -------
- decfunc : function
- The decorator function that applies dictionary to its
- argument's __doc__ attribute.
- """
- if unindent_params:
- docdict = unindent_dict(docdict)
- def decorate(func: _F) -> _F:
- # __doc__ may be None for optimized Python (-OO)
- doc = func.__doc__ or ""
- func.__doc__ = docformat(doc, docdict)
- return func
- return decorate
- def unindent_dict(docdict: Mapping[str, str]) -> dict[str, str]:
- """Unindent all strings in a docdict.
- Parameters
- ----------
- docdict : dict[str, str]
- A dictionary with string values to unindent.
- Returns
- -------
- docdict : dict[str, str]
- The `docdict` dictionary but each of its string values are unindented.
- """
- can_dict: dict[str, str] = {}
- for name, dstr in docdict.items():
- can_dict[name] = unindent_string(dstr)
- return can_dict
- def unindent_string(docstring: str) -> str:
- """Set docstring to minimum indent for all lines, including first.
- Parameters
- ----------
- docstring : str
- The input docstring to unindent.
- Returns
- -------
- docstring : str
- The unindented docstring.
- Examples
- --------
- >>> unindent_string(' two')
- 'two'
- >>> unindent_string(' two\\n three')
- 'two\\n three'
- """
- lines = docstring.expandtabs().splitlines()
- icount = indentcount_lines(lines)
- if icount == 0:
- return docstring
- return "\n".join([line[icount:] for line in lines])
- def doc_replace(obj: object, oldval: str, newval: str) -> Decorator:
- """Decorator to take the docstring from obj, with oldval replaced by newval
- Equivalent to ``func.__doc__ = obj.__doc__.replace(oldval, newval)``
- Parameters
- ----------
- obj : object
- A class or object whose docstring will be used as the basis for the
- replacement operation.
- oldval : str
- The string to search for in the docstring.
- newval : str
- The string to replace `oldval` with in the docstring.
- Returns
- -------
- decfunc : function
- A decorator function that replaces occurrences of `oldval` with `newval`
- in the docstring of the decorated function.
- """
- # __doc__ may be None for optimized Python (-OO)
- doc = (obj.__doc__ or "").replace(oldval, newval)
- def inner(func: _F) -> _F:
- func.__doc__ = doc
- return func
- return inner
|