Source code for pymend.docstring_parser.common

"""Common methods for parsing."""

import enum
from collections import UserDict
from dataclasses import dataclass
from typing import Optional, TypeVar, Union

from typing_extensions import TypeAlias

PARAM_KEYWORDS = {
    "param",
    "parameter",
    "arg",
    "argument",
    "attribute",
    "generics",
    "key",
    "keyword",
}
# These could be made into frozen sets
# Then one could create a dictionary
# {KEYWORD: FUNCTION_THAT_HANDLES_THAT_KEYWORD_SECTION}  # noqa: ERA001
RAISES_KEYWORDS = {"raises", "raise", "except", "exception"}
DEPRECATION_KEYWORDS = {"deprecation", "deprecated"}
RETURNS_KEYWORDS = {"return", "returns"}
YIELDS_KEYWORDS = {"yield", "yields"}
EXAMPLES_KEYWORDS = {"example", "examples"}


[docs] def clean_str(string: str) -> Optional[str]: """Strip a string and return None if it is now empty. Parameters ---------- string : str String to clean Returns ------- Optional[str] None of the stripped string is empty. Otherwise the stripped string. """ string = string.strip() return string if string != "" else None
[docs] class ParseError(RuntimeError): """Base class for all parsing related errors."""
[docs] class DocstringStyle(enum.Enum): """Docstring style.""" REST = 1 GOOGLE = 2 NUMPYDOC = 3 EPYDOC = 4 AUTO = 255
[docs] class RenderingStyle(enum.Enum): """Rendering style when unparsing parsed docstrings.""" COMPACT = 1 CLEAN = 2 EXPANDED = 3
[docs] @dataclass class DocstringMeta: """Docstring meta information. Symbolizes lines in form of Parameters ---------- args : List[str] list of arguments. The exact content of this variable is dependent on the kind of docstring; it's used to distinguish between custom docstring meta information items. description : Optional[str] associated docstring description. """ args: list[str] description: Optional[str]
[docs] @dataclass class DocstringParam(DocstringMeta): """DocstringMeta symbolizing :param metadata.""" arg_name: str type_name: Optional[str] is_optional: Optional[bool] default: Optional[str]
[docs] @dataclass class DocstringReturns(DocstringMeta): """DocstringMeta symbolizing :returns metadata.""" type_name: Optional[str] is_generator: bool return_name: Optional[str] = None
[docs] @dataclass class DocstringYields(DocstringMeta): """DocstringMeta symbolizing :yields metadata.""" type_name: Optional[str] is_generator: bool yield_name: Optional[str] = None
[docs] @dataclass class DocstringRaises(DocstringMeta): """DocstringMeta symbolizing :raises metadata.""" type_name: Optional[str]
MainSections: TypeAlias = Union[ DocstringParam, DocstringRaises, DocstringReturns, DocstringYields ]
[docs] @dataclass class DocstringDeprecated(DocstringMeta): """DocstringMeta symbolizing deprecation metadata.""" version: Optional[str]
[docs] @dataclass class DocstringExample(DocstringMeta): """DocstringMeta symbolizing example metadata.""" snippet: Optional[str]
K = TypeVar("K") V = TypeVar("V")
[docs] class KeyReturnDict(UserDict[K, V]): """Custom dict that returns the key back in missing case.""" def __missing__(self, key: K) -> K: """Return the key. Parameters ---------- key : K key to look for. Returns ------- K Returns the key directly. """ return key
[docs] class Docstring: """Docstring object representation.""" def __init__( self, style: Optional[DocstringStyle] = None, section_titles: Optional[KeyReturnDict[str, str]] = None, ) -> None: """Initialize self. Parameters ---------- style : Optional[DocstringStyle] Style that this docstring was formatted in. (Default value = None) """ self.short_description: Optional[str] = None self.long_description: Optional[str] = None self.blank_after_short_description: bool = False self.blank_after_long_description: bool = False self.meta: list[DocstringMeta] = [] self.style: Optional[DocstringStyle] = style self.section_titles: KeyReturnDict[str, str] = section_titles or KeyReturnDict() def __bool__(self) -> bool: """Return True if the docstring has any content. Returns ------- bool True if the docstring has any content. """ return any( ( self.short_description, self.long_description, self.meta, ) ) @property def params(self) -> list[DocstringParam]: """Return a list of information on function params. Returns ------- list[DocstringParam] list of information on function params """ return [item for item in self.meta if isinstance(item, DocstringParam)] @property def raises(self) -> list[DocstringRaises]: """Return a list of the exceptions that the function may raise. Returns ------- list[DocstringRaises] list of the exceptions that the function may raise. """ return [item for item in self.meta if isinstance(item, DocstringRaises)] @property def returns(self) -> Optional[DocstringReturns]: """Return a single information on function return. Takes the first return information. Returns ------- Optional[DocstringReturns] Single information on function return. """ return next( (item for item in self.meta if isinstance(item, DocstringReturns)), None, ) @property def many_returns(self) -> list[DocstringReturns]: """Return a list of information on function return. Returns ------- list[DocstringReturns] list of information on function return. """ return [item for item in self.meta if isinstance(item, DocstringReturns)] @property def yields(self) -> Optional[DocstringYields]: """Return information on function yield. Takes the first generator information. Returns ------- Optional[DocstringYields] Single information on function yield. """ return next( ( item for item in self.meta if isinstance(item, DocstringYields) and item.is_generator ), None, ) @property def many_yields(self) -> list[DocstringYields]: """Return a list of information on function yields. Returns ------- list[DocstringYields] list of information on function yields. """ return [item for item in self.meta if isinstance(item, DocstringYields)] @property def deprecation(self) -> Optional[DocstringDeprecated]: """Return a single information on function deprecation notes. Returns ------- Optional[DocstringDeprecated] single information on function deprecation notes. """ return next( (item for item in self.meta if isinstance(item, DocstringDeprecated)), None, ) @property def examples(self) -> list[DocstringExample]: """Return a list of information on function examples. Returns ------- list[DocstringExample] list of information on function examples. """ return [item for item in self.meta if isinstance(item, DocstringExample)]
[docs] def split_description(docstring: Docstring, desc_chunk: str) -> None: """Break description into short and long parts. Parameters ---------- docstring : Docstring Docstring to fill with description information. desc_chunk : str Chunk of the raw docstring representing the description. """ parts = desc_chunk.split("\n", 1) docstring.short_description = parts[0] or None if len(parts) > 1: long_desc_chunk = parts[1] or "" docstring.blank_after_short_description = long_desc_chunk.startswith("\n") docstring.blank_after_long_description = long_desc_chunk.endswith("\n\n") docstring.long_description = long_desc_chunk.strip() or None
[docs] def append_description(docstring: Docstring, parts: list[str]) -> None: """Append the docstrings description to the output stream. Parameters ---------- docstring : Docstring Docstring whose information should be added. parts : list[str] List of strings representing the output of compose(). Descriptions should be added to this. """ if docstring.short_description: parts.append(docstring.short_description) if docstring.blank_after_short_description: parts.append("") if docstring.long_description: parts.append(docstring.long_description) if docstring.blank_after_long_description: parts.append("")