Skip to content

pytkdocs

pytkdocs package.

Load Python objects documentation.

__main__ special ¤

Entry-point module, in case you use python -m pytkdocs.

Why does this file exist, and why __main__? For more info, read:

cli ¤

Module that contains the command line application.

discarded_stdout() ¤

Discard standard output.

Yields:

Type Description
Nothing

We only yield to act as a context manager.

Source code in pytkdocs/cli.py
@contextmanager
def discarded_stdout():
    """
    Discard standard output.

    Yields:
        Nothing: We only yield to act as a context manager.
    """
    # Discard things printed at import time to avoid corrupting our JSON output
    # See https://github.com/pawamoy/pytkdocs/issues/24
    old_stdout = sys.stdout
    sys.stdout = StringIO()

    yield

    # Flush imported modules' output, and restore true sys.stdout
    sys.stdout.flush()
    sys.stdout = old_stdout

extract_docstring_parsing_errors(errors, obj) ¤

Recursion helper.

Update the errors dictionary by side-effect. Recurse on the object's children.

Parameters:

Name Type Description Default
errors dict

The dictionary to update.

required
obj Object

The object.

required
Source code in pytkdocs/cli.py
def extract_docstring_parsing_errors(errors: dict, obj: Object) -> None:
    """
    Recursion helper.

    Update the `errors` dictionary by side-effect. Recurse on the object's children.

    Arguments:
        errors: The dictionary to update.
        obj: The object.
    """
    if hasattr(obj, "docstring_errors") and obj.docstring_errors:  # noqa: WPS421 (hasattr)
        errors[obj.path] = obj.docstring_errors
    for child in obj.children:
        extract_docstring_parsing_errors(errors, child)

extract_errors(obj) ¤

Extract the docstring parsing errors of each object, recursively, into a flat dictionary.

Parameters:

Name Type Description Default
obj Object

An object from pytkdocs.objects.

required

Returns:

Type Description
dict

A flat dictionary. Keys are the objects' names.

Source code in pytkdocs/cli.py
def extract_errors(obj: Object) -> dict:
    """
    Extract the docstring parsing errors of each object, recursively, into a flat dictionary.

    Arguments:
        obj: An object from `pytkdocs.objects`.

    Returns:
        A flat dictionary. Keys are the objects' names.
    """
    parsing_errors: Dict[str, List[str]] = {}
    extract_docstring_parsing_errors(parsing_errors, obj)
    return parsing_errors

get_parser() ¤

Return the program argument parser.

Returns:

Type Description
ArgumentParser

The argument parser for the program.

Source code in pytkdocs/cli.py
def get_parser() -> argparse.ArgumentParser:
    """
    Return the program argument parser.

    Returns:
        The argument parser for the program.
    """
    parser = argparse.ArgumentParser(prog="pytkdocs")
    parser.add_argument(
        "-1",
        "--line-by-line",
        action="store_true",
        dest="line_by_line",
        help="Process each line read on stdin, one by one.",
    )
    return parser

main(args=None) ¤

Run the main program.

This function is executed when you type pytkdocs or python -m pytkdocs.

Parameters:

Name Type Description Default
args Optional[List[str]]

Arguments passed from the command line.

None

Returns:

Type Description
int

An exit code.

Source code in pytkdocs/cli.py
def main(args: Optional[List[str]] = None) -> int:
    """
    Run the main program.

    This function is executed when you type `pytkdocs` or `python -m pytkdocs`.

    Arguments:
        args: Arguments passed from the command line.

    Returns:
        An exit code.
    """
    parser = get_parser()
    parsed_args: argparse.Namespace = parser.parse_args(args)

    if parsed_args.line_by_line:
        for line in sys.stdin:
            with discarded_stdout():
                try:
                    output = json.dumps(process_json(line))
                except Exception as error:  # noqa: W0703 (we purposely catch everything)
                    # Don't fail on error. We must handle the next inputs.
                    # Instead, print error as JSON.
                    output = json.dumps({"error": str(error), "traceback": traceback.format_exc()})
            print(output)  # noqa: WPS421 (we need to print at some point)
    else:
        with discarded_stdout():
            output = json.dumps(process_json(sys.stdin.read()))
        print(output)  # noqa: WPS421 (we need to print at some point)

    return 0

process_config(config) ¤

Process a loading configuration.

The config argument is a dictionary looking like this:

{
    "objects": [
        {"path": "python.dotted.path.to.the.object1"},
        {"path": "python.dotted.path.to.the.object2"}
    ]
}

The result is a dictionary looking like this:

{
    "loading_errors": [
        "message1",
        "message2",
    ],
    "parsing_errors": {
        "path.to.object1": [
            "message1",
            "message2",
        ],
        "path.to.object2": [
            "message1",
            "message2",
        ]
    },
    "objects": [
        {
            "path": "path.to.object1",
            # other attributes, see the documentation for `pytkdocs.objects` or `pytkdocs.serializer`
        },
        {
            "path": "path.to.object2",
            # other attributes, see the documentation for `pytkdocs.objects` or `pytkdocs.serializer`
        },
    ]
}

Parameters:

Name Type Description Default
config dict

The configuration.

required

Returns:

Type Description
dict

The collected documentation along with the errors that occurred.

Source code in pytkdocs/cli.py
def process_config(config: dict) -> dict:
    """
    Process a loading configuration.

    The `config` argument is a dictionary looking like this:

    ```python
    {
        "objects": [
            {"path": "python.dotted.path.to.the.object1"},
            {"path": "python.dotted.path.to.the.object2"}
        ]
    }
    ```

    The result is a dictionary looking like this:

    ```python
    {
        "loading_errors": [
            "message1",
            "message2",
        ],
        "parsing_errors": {
            "path.to.object1": [
                "message1",
                "message2",
            ],
            "path.to.object2": [
                "message1",
                "message2",
            ]
        },
        "objects": [
            {
                "path": "path.to.object1",
                # other attributes, see the documentation for `pytkdocs.objects` or `pytkdocs.serializer`
            },
            {
                "path": "path.to.object2",
                # other attributes, see the documentation for `pytkdocs.objects` or `pytkdocs.serializer`
            },
        ]
    }
    ```

    Arguments:
        config: The configuration.

    Returns:
        The collected documentation along with the errors that occurred.
    """
    collected = []
    loading_errors = []
    parsing_errors = {}

    for obj_config in config["objects"]:
        path = obj_config.pop("path")
        members = obj_config.pop("members", set())

        if isinstance(members, list):
            members = set(members)
        loader = Loader(**obj_config)

        obj = loader.get_object_documentation(path, members)

        loading_errors.extend(loader.errors)
        parsing_errors.update(extract_errors(obj))

        serialized_obj = serialize_object(obj)
        collected.append(serialized_obj)

    return {"loading_errors": loading_errors, "parsing_errors": parsing_errors, "objects": collected}

process_json(json_input) ¤

Process JSON input.

Simply load the JSON as a Python dictionary, then pass it to process_config.

Parameters:

Name Type Description Default
json_input str

The JSON to load.

required

Returns:

Type Description
dict

The result of the call to process_config.

Source code in pytkdocs/cli.py
def process_json(json_input: str) -> dict:
    """
    Process JSON input.

    Simply load the JSON as a Python dictionary, then pass it to [`process_config`][pytkdocs.cli.process_config].

    Arguments:
        json_input: The JSON to load.

    Returns:
        The result of the call to [`process_config`][pytkdocs.cli.process_config].
    """
    return process_config(json.loads(json_input))

loader ¤

This module is responsible for loading the documentation from Python objects.

It uses inspect for introspecting objects, iterating over their members, etc.

Loader ¤

This class contains the object documentation loading mechanisms.

Any error that occurred during collection of the objects and their documentation is stored in the errors list.

Source code in pytkdocs/loader.py
class Loader:
    """
    This class contains the object documentation loading mechanisms.

    Any error that occurred during collection of the objects and their documentation is stored in the `errors` list.
    """

    def __init__(
        self,
        filters: Optional[List[str]] = None,
        docstring_style: str = "google",
        docstring_options: Optional[dict] = None,
        inherited_members: bool = False,
        new_path_syntax: bool = False,
    ) -> None:
        """
        Initialize the object.

        Arguments:
            filters: A list of regular expressions to fine-grain select members. It is applied recursively.
            docstring_style: The style to use when parsing docstrings.
            docstring_options: The options to pass to the docstrings parser.
            inherited_members: Whether to select inherited members for classes.
            new_path_syntax: Whether to use the "colon" syntax for the path.
        """
        if not filters:
            filters = []

        self.filters = [(filtr, re.compile(filtr.lstrip("!"))) for filtr in filters]
        self.docstring_parser = PARSERS[docstring_style](**(docstring_options or {}))  # type: ignore
        self.errors: List[str] = []
        self.select_inherited_members = inherited_members
        self.new_path_syntax = new_path_syntax

    def get_object_documentation(self, dotted_path: str, members: Optional[Union[Set[str], bool]] = None) -> Object:
        """
        Get the documentation for an object and its children.

        Arguments:
            dotted_path: The Python dotted path to the desired object.
            members: `True` to select members and filter them, `False` to select no members,
                or a list of names to explicitly select the members with these names.
                It is applied only on the root object.

        Returns:
            The documented object.
        """
        if members is True:
            members = set()

        root_object: Object
        leaf = get_object_tree(dotted_path, self.new_path_syntax)

        if leaf.is_module():
            root_object = self.get_module_documentation(leaf, members)
        elif leaf.is_class():
            root_object = self.get_class_documentation(leaf, members)
        elif leaf.is_staticmethod():
            root_object = self.get_staticmethod_documentation(leaf)
        elif leaf.is_classmethod():
            root_object = self.get_classmethod_documentation(leaf)
        elif leaf.is_method_descriptor():
            root_object = self.get_regular_method_documentation(leaf)
        elif leaf.is_method():
            root_object = self.get_regular_method_documentation(leaf)
        elif leaf.is_function():
            root_object = self.get_function_documentation(leaf)
        elif leaf.is_property():
            root_object = self.get_property_documentation(leaf)
        else:
            root_object = self.get_attribute_documentation(leaf)

        root_object.parse_all_docstrings(self.docstring_parser)

        return root_object

    def get_module_documentation(self, node: ObjectNode, select_members=None) -> Module:
        """
        Get the documentation for a module and its children.

        Arguments:
            node: The node representing the module and its parents.
            select_members: Explicit members to select.

        Returns:
            The documented module object.
        """
        module = node.obj
        path = node.dotted_path
        name = path.split(".")[-1]
        source: Optional[Source]

        try:
            source = Source(inspect.getsource(module), 1)
        except OSError as error:
            try:
                code = Path(node.file_path).read_text()
            except (OSError, UnicodeDecodeError):
                source = None
            else:
                source = Source(code, 1) if code else None

        root_object = Module(
            name=name,
            path=path,
            file_path=node.file_path,
            docstring=inspect.getdoc(module),
            source=source,
        )

        if select_members is False:
            return root_object

        select_members = select_members or set()

        attributes_data = get_module_attributes(module)
        root_object.parse_docstring(self.docstring_parser, attributes=attributes_data)

        for member_name, member in inspect.getmembers(module):
            if self.select(member_name, select_members):
                child_node = ObjectNode(member, member_name, parent=node)
                if child_node.is_class() and node.root.obj is inspect.getmodule(child_node.obj):
                    root_object.add_child(self.get_class_documentation(child_node))
                elif child_node.is_function() and node.root.obj is inspect.getmodule(child_node.obj):
                    root_object.add_child(self.get_function_documentation(child_node))
                elif member_name in attributes_data:
                    root_object.add_child(self.get_attribute_documentation(child_node, attributes_data[member_name]))

        if hasattr(module, "__path__"):  # noqa: WPS421 (hasattr)
            for _, modname, _ in pkgutil.iter_modules(module.__path__):
                if self.select(modname, select_members):
                    leaf = get_object_tree(f"{path}.{modname}")
                    root_object.add_child(self.get_module_documentation(leaf))

        return root_object

    @staticmethod
    def _class_path(cls):
        mod = cls.__module__
        qname = cls.__qualname__
        if mod == "builtins":
            return qname
        else:
            return f"{mod}.{qname}"

    def get_class_documentation(self, node: ObjectNode, select_members=None) -> Class:
        """
        Get the documentation for a class and its children.

        Arguments:
            node: The node representing the class and its parents.
            select_members: Explicit members to select.

        Returns:
            The documented class object.
        """
        class_ = node.obj
        docstring = inspect.cleandoc(class_.__doc__ or "")
        bases = [self._class_path(b) for b in class_.__bases__]

        source: Optional[Source]

        try:
            source = Source(*inspect.getsourcelines(node.obj))
        except (OSError, TypeError) as error:
            source = None

        root_object = Class(
            name=node.name,
            path=node.dotted_path,
            file_path=node.file_path,
            docstring=docstring,
            bases=bases,
            source=source,
        )

        # Even if we don't select members, we want to correctly parse the docstring
        attributes_data: Dict[str, Dict[str, Any]] = {}
        for parent_class in reversed(class_.__mro__[:-1]):
            merge(attributes_data, get_class_attributes(parent_class))
        context: Dict[str, Any] = {"attributes": attributes_data}
        if "__init__" in class_.__dict__:
            try:
                attributes_data.update(get_instance_attributes(class_.__init__))
                context["signature"] = inspect.signature(class_.__init__)
            except (TypeError, ValueError):
                pass
        root_object.parse_docstring(self.docstring_parser, **context)

        if select_members is False:
            return root_object

        select_members = select_members or set()

        # Build the list of members
        members = {}
        inherited = set()
        direct_members = class_.__dict__
        all_members = dict(inspect.getmembers(class_))
        for member_name, member in all_members.items():
            if member is class_:
                continue
            if not (member is type or member is object) and self.select(member_name, select_members):
                if member_name not in direct_members:
                    if self.select_inherited_members:
                        members[member_name] = member
                        inherited.add(member_name)
                else:
                    members[member_name] = member

        # Iterate on the selected members
        child: Object
        for member_name, member in members.items():
            child_node = ObjectNode(member, member_name, parent=node)
            if child_node.is_class():
                child = self.get_class_documentation(child_node)
            elif child_node.is_classmethod():
                child = self.get_classmethod_documentation(child_node)
            elif child_node.is_staticmethod():
                child = self.get_staticmethod_documentation(child_node)
            elif child_node.is_method():
                child = self.get_regular_method_documentation(child_node)
            elif child_node.is_property():
                child = self.get_property_documentation(child_node)
            elif member_name in attributes_data:
                child = self.get_attribute_documentation(child_node, attributes_data[member_name])
            else:
                continue
            if member_name in inherited:
                child.properties.append("inherited")
            root_object.add_child(child)

        for attr_name, properties, add_method in (
            ("__fields__", ["pydantic-model"], self.get_pydantic_field_documentation),
            ("_declared_fields", ["marshmallow-model"], self.get_marshmallow_field_documentation),
            ("_meta.get_fields", ["django-model"], self.get_django_field_documentation),
            ("__dataclass_fields__", ["dataclass"], self.get_annotated_dataclass_field),
        ):
            if self.detect_field_model(attr_name, direct_members, all_members):
                root_object.properties.extend(properties)
                self.add_fields(
                    node,
                    root_object,
                    attr_name,
                    all_members,
                    select_members,
                    class_,
                    add_method,
                )
                break

        return root_object

    def detect_field_model(self, attr_name: str, direct_members, all_members) -> bool:
        """
        Detect if an attribute is present in members.

        Arguments:
            attr_name: The name of the attribute to detect, can contain dots.
            direct_members: The direct members of the class.
            all_members: All members of the class.

        Returns:
            Whether the attribute is present.
        """

        first_order_attr_name, remainder = split_attr_name(attr_name)
        if not (
            first_order_attr_name in direct_members
            or (self.select_inherited_members and first_order_attr_name in all_members)
        ):
            return False

        if remainder and not attrgetter(remainder)(all_members[first_order_attr_name]):
            return False
        return True

    def add_fields(
        self,
        node: ObjectNode,
        root_object: Object,
        attr_name: str,
        members,
        select_members,
        base_class,
        add_method,
    ) -> None:
        """
        Add detected fields to the current object.

        Arguments:
            node: The current object node.
            root_object: The current object.
            attr_name: The fields attribute name.
            members: The members to pick the fields attribute in.
            select_members: The members to select.
            base_class: The class declaring the fields.
            add_method: The method to add the children object.
        """

        fields = get_fields(attr_name, members=members)

        for field_name, field in fields.items():
            select_field = self.select(field_name, select_members)
            is_inherited = field_is_inherited(field_name, attr_name, base_class)

            if select_field and (self.select_inherited_members or not is_inherited):
                child_node = ObjectNode(obj=field, name=field_name, parent=node)
                root_object.add_child(add_method(child_node))

    def get_function_documentation(self, node: ObjectNode) -> Function:
        """
        Get the documentation for a function.

        Arguments:
            node: The node representing the function and its parents.

        Returns:
            The documented function object.
        """
        function = node.obj
        path = node.dotted_path
        source: Optional[Source]
        signature: Optional[inspect.Signature]

        try:
            signature = inspect.signature(function)
        except TypeError as error:
            signature = None

        try:
            source = Source(*inspect.getsourcelines(function))
        except OSError as error:
            source = None

        properties: List[str] = []
        if node.is_coroutine_function():
            properties.append("async")

        return Function(
            name=node.name,
            path=node.dotted_path,
            file_path=node.file_path,
            docstring=inspect.getdoc(function),
            signature=signature,
            source=source,
            properties=properties,
        )

    def get_property_documentation(self, node: ObjectNode) -> Attribute:
        """
        Get the documentation for a property.

        Arguments:
            node: The node representing the property and its parents.

        Returns:
            The documented attribute object (properties are considered attributes for now).
        """
        prop = node.obj
        path = node.dotted_path
        properties = ["property"]
        if node.is_cached_property():
            # cached_property is always writable, see the docs
            properties.extend(["writable", "cached"])
            sig_source_func = prop.func
        else:
            properties.append("readonly" if prop.fset is None else "writable")
            sig_source_func = prop.fget

        source: Optional[Source]

        try:
            signature = inspect.signature(sig_source_func)
        except (TypeError, ValueError) as error:
            attr_type = None
        else:
            attr_type = signature.return_annotation

        try:
            source = Source(*inspect.getsourcelines(sig_source_func))
        except (OSError, TypeError) as error:
            source = None

        return Attribute(
            name=node.name,
            path=path,
            file_path=node.file_path,
            docstring=inspect.getdoc(prop),
            attr_type=attr_type,
            properties=properties,
            source=source,
        )

    @staticmethod
    def get_pydantic_field_documentation(node: ObjectNode) -> Attribute:
        """
        Get the documentation for a Pydantic Field.

        Arguments:
            node: The node representing the Field and its parents.

        Returns:
            The documented attribute object.
        """
        prop = node.obj
        path = node.dotted_path
        properties = ["pydantic-field"]
        if prop.required:
            properties.append("required")

        return Attribute(
            name=node.name,
            path=path,
            file_path=node.file_path,
            docstring=prop.field_info.description,
            attr_type=prop.outer_type_,
            properties=properties,
        )

    @staticmethod
    def get_django_field_documentation(node: ObjectNode) -> Attribute:
        """
        Get the documentation for a Django Field.

        Arguments:
            node: The node representing the Field and its parents.

        Returns:
            The documented attribute object.
        """
        prop = node.obj
        path = node.dotted_path
        properties = ["django-field"]

        if prop.null:
            properties.append("nullable")
        if prop.blank:
            properties.append("blank")

        # set correct docstring based on verbose_name and help_text
        # both should be converted to str type in case lazy translation
        # is being used, which is common scenario in django
        if prop.help_text:
            docstring = f"{prop.verbose_name}: {prop.help_text}"
        else:
            docstring = str(prop.verbose_name)

        return Attribute(
            name=node.name,
            path=path,
            file_path=node.file_path,
            docstring=docstring,
            attr_type=prop.__class__,
            properties=properties,
        )

    @staticmethod
    def get_marshmallow_field_documentation(node: ObjectNode) -> Attribute:
        """
        Get the documentation for a Marshmallow Field.

        Arguments:
            node: The node representing the Field and its parents.

        Returns:
            The documented attribute object.
        """
        prop = node.obj
        path = node.dotted_path
        properties = ["marshmallow-field"]
        if prop.required:
            properties.append("required")

        return Attribute(
            name=node.name,
            path=path,
            file_path=node.file_path,
            docstring=prop.metadata.get("description"),
            attr_type=type(prop),
            properties=properties,
        )

    @staticmethod
    def get_annotated_dataclass_field(node: ObjectNode, attribute_data: Optional[dict] = None) -> Attribute:
        """
        Get the documentation for a dataclass field.

        Arguments:
            node: The node representing the annotation and its parents.
            attribute_data: Docstring and annotation for this attribute.

        Returns:
            The documented attribute object.
        """
        if attribute_data is None:
            if node.parent_is_class():
                attribute_data = get_class_attributes(node.parent.obj).get(node.name, {})  # type: ignore
            else:
                attribute_data = get_module_attributes(node.root.obj).get(node.name, {})

        return Attribute(
            name=node.name,
            path=node.dotted_path,
            file_path=node.file_path,
            docstring=attribute_data["docstring"],
            attr_type=attribute_data["annotation"],
            properties=["dataclass-field"],
        )

    def get_classmethod_documentation(self, node: ObjectNode) -> Method:
        """
        Get the documentation for a class-method.

        Arguments:
            node: The node representing the class-method and its parents.

        Returns:
            The documented method object.
        """
        return self.get_method_documentation(node, ["classmethod"])

    def get_staticmethod_documentation(self, node: ObjectNode) -> Method:
        """
        Get the documentation for a static-method.

        Arguments:
            node: The node representing the static-method and its parents.

        Returns:
            The documented method object.
        """
        return self.get_method_documentation(node, ["staticmethod"])

    def get_regular_method_documentation(self, node: ObjectNode) -> Method:
        """
        Get the documentation for a regular method (not class- nor static-method).

        We do extra processing in this method to discard docstrings of `__init__` methods
        that were inherited from parent classes.

        Arguments:
            node: The node representing the method and its parents.

        Returns:
            The documented method object.
        """
        method = self.get_method_documentation(node)
        if node.parent:
            class_ = node.parent.obj
            if RE_SPECIAL.match(node.name):
                docstring = method.docstring
                parent_classes = class_.__mro__[1:]
                for parent_class in parent_classes:
                    try:
                        parent_method = getattr(parent_class, node.name)
                    except AttributeError:
                        continue
                    else:
                        if docstring == inspect.getdoc(parent_method):
                            method.docstring = ""
                        break
        return method

    def get_method_documentation(self, node: ObjectNode, properties: Optional[List[str]] = None) -> Method:
        """
        Get the documentation for a method or method descriptor.

        Arguments:
            node: The node representing the method and its parents.
            properties: A list of properties to apply to the method.

        Returns:
            The documented method object.
        """
        method = node.obj
        path = node.dotted_path
        signature: Optional[inspect.Signature]
        source: Optional[Source]

        try:
            source = Source(*inspect.getsourcelines(method))
        except OSError as error:
            source = None
        except TypeError:
            source = None

        if node.is_coroutine_function():
            if properties is None:
                properties = ["async"]
            else:
                properties.append("async")

        try:
            # for "built-in" functions, e.g. those implemented in C,
            # inspect.signature() uses the __text_signature__ attribute, which
            # provides a limited but still useful amount of signature information.
            # "built-in" functions with no __text_signature__ will
            # raise a ValueError().
            signature = inspect.signature(method)
        except ValueError as error:
            signature = None

        return Method(
            name=node.name,
            path=path,
            file_path=node.file_path,
            docstring=inspect.getdoc(method),
            signature=signature,
            properties=properties or [],
            source=source,
        )

    @staticmethod
    def get_attribute_documentation(node: ObjectNode, attribute_data: Optional[dict] = None) -> Attribute:
        """
        Get the documentation for an attribute.

        Arguments:
            node: The node representing the method and its parents.
            attribute_data: Docstring and annotation for this attribute.

        Returns:
            The documented attribute object.
        """
        if attribute_data is None:
            if node.parent_is_class():
                attribute_data = get_class_attributes(node.parent.obj).get(node.name, {})  # type: ignore
            else:
                attribute_data = get_module_attributes(node.root.obj).get(node.name, {})
        return Attribute(
            name=node.name,
            path=node.dotted_path,
            file_path=node.file_path,
            docstring=attribute_data.get("docstring", ""),
            attr_type=attribute_data.get("annotation", None),
        )

    def select(self, name: str, names: Set[str]) -> bool:
        """
        Tells whether we should select an object or not, given its name.

        If the set of names is not empty, we check against it, otherwise we check against filters.

        Arguments:
            name: The name of the object to select or not.
            names: An explicit list of names to select.

        Returns:
            Yes or no.
        """
        if names:
            return name in names
        return not self.filter_name_out(name)

    @lru_cache(maxsize=None)
    def filter_name_out(self, name: str) -> bool:
        """
        Filter a name based on the loader's filters.

        Arguments:
            name: The name to filter.

        Returns:
            True if the name was filtered out, False otherwise.
        """
        if not self.filters:
            return False
        keep = True
        for fltr, regex in self.filters:
            is_matching = bool(regex.search(name))
            if is_matching:
                if str(fltr).startswith("!"):
                    is_matching = not is_matching
                keep = is_matching
        return not keep

__init__(self, filters=None, docstring_style='google', docstring_options=None, inherited_members=False, new_path_syntax=False) special ¤

Initialize the object.

Parameters:

Name Type Description Default
filters Optional[List[str]]

A list of regular expressions to fine-grain select members. It is applied recursively.

None
docstring_style str

The style to use when parsing docstrings.

'google'
docstring_options Optional[dict]

The options to pass to the docstrings parser.

None
inherited_members bool

Whether to select inherited members for classes.

False
new_path_syntax bool

Whether to use the "colon" syntax for the path.

False
Source code in pytkdocs/loader.py
def __init__(
    self,
    filters: Optional[List[str]] = None,
    docstring_style: str = "google",
    docstring_options: Optional[dict] = None,
    inherited_members: bool = False,
    new_path_syntax: bool = False,
) -> None:
    """
    Initialize the object.

    Arguments:
        filters: A list of regular expressions to fine-grain select members. It is applied recursively.
        docstring_style: The style to use when parsing docstrings.
        docstring_options: The options to pass to the docstrings parser.
        inherited_members: Whether to select inherited members for classes.
        new_path_syntax: Whether to use the "colon" syntax for the path.
    """
    if not filters:
        filters = []

    self.filters = [(filtr, re.compile(filtr.lstrip("!"))) for filtr in filters]
    self.docstring_parser = PARSERS[docstring_style](**(docstring_options or {}))  # type: ignore
    self.errors: List[str] = []
    self.select_inherited_members = inherited_members
    self.new_path_syntax = new_path_syntax

add_fields(self, node, root_object, attr_name, members, select_members, base_class, add_method) ¤

Add detected fields to the current object.

Parameters:

Name Type Description Default
node ObjectNode

The current object node.

required
root_object Object

The current object.

required
attr_name str

The fields attribute name.

required
members

The members to pick the fields attribute in.

required
select_members

The members to select.

required
base_class

The class declaring the fields.

required
add_method

The method to add the children object.

required
Source code in pytkdocs/loader.py
def add_fields(
    self,
    node: ObjectNode,
    root_object: Object,
    attr_name: str,
    members,
    select_members,
    base_class,
    add_method,
) -> None:
    """
    Add detected fields to the current object.

    Arguments:
        node: The current object node.
        root_object: The current object.
        attr_name: The fields attribute name.
        members: The members to pick the fields attribute in.
        select_members: The members to select.
        base_class: The class declaring the fields.
        add_method: The method to add the children object.
    """

    fields = get_fields(attr_name, members=members)

    for field_name, field in fields.items():
        select_field = self.select(field_name, select_members)
        is_inherited = field_is_inherited(field_name, attr_name, base_class)

        if select_field and (self.select_inherited_members or not is_inherited):
            child_node = ObjectNode(obj=field, name=field_name, parent=node)
            root_object.add_child(add_method(child_node))

detect_field_model(self, attr_name, direct_members, all_members) ¤

Detect if an attribute is present in members.

Parameters:

Name Type Description Default
attr_name str

The name of the attribute to detect, can contain dots.

required
direct_members

The direct members of the class.

required
all_members

All members of the class.

required

Returns:

Type Description
bool

Whether the attribute is present.

Source code in pytkdocs/loader.py
def detect_field_model(self, attr_name: str, direct_members, all_members) -> bool:
    """
    Detect if an attribute is present in members.

    Arguments:
        attr_name: The name of the attribute to detect, can contain dots.
        direct_members: The direct members of the class.
        all_members: All members of the class.

    Returns:
        Whether the attribute is present.
    """

    first_order_attr_name, remainder = split_attr_name(attr_name)
    if not (
        first_order_attr_name in direct_members
        or (self.select_inherited_members and first_order_attr_name in all_members)
    ):
        return False

    if remainder and not attrgetter(remainder)(all_members[first_order_attr_name]):
        return False
    return True

filter_name_out(self, name) ¤

Filter a name based on the loader's filters.

Parameters:

Name Type Description Default
name str

The name to filter.

required

Returns:

Type Description
bool

True if the name was filtered out, False otherwise.

Source code in pytkdocs/loader.py
@lru_cache(maxsize=None)
def filter_name_out(self, name: str) -> bool:
    """
    Filter a name based on the loader's filters.

    Arguments:
        name: The name to filter.

    Returns:
        True if the name was filtered out, False otherwise.
    """
    if not self.filters:
        return False
    keep = True
    for fltr, regex in self.filters:
        is_matching = bool(regex.search(name))
        if is_matching:
            if str(fltr).startswith("!"):
                is_matching = not is_matching
            keep = is_matching
    return not keep

get_annotated_dataclass_field(node, attribute_data=None) staticmethod ¤

Get the documentation for a dataclass field.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the annotation and its parents.

required
attribute_data Optional[dict]

Docstring and annotation for this attribute.

None

Returns:

Type Description
Attribute

The documented attribute object.

Source code in pytkdocs/loader.py
@staticmethod
def get_annotated_dataclass_field(node: ObjectNode, attribute_data: Optional[dict] = None) -> Attribute:
    """
    Get the documentation for a dataclass field.

    Arguments:
        node: The node representing the annotation and its parents.
        attribute_data: Docstring and annotation for this attribute.

    Returns:
        The documented attribute object.
    """
    if attribute_data is None:
        if node.parent_is_class():
            attribute_data = get_class_attributes(node.parent.obj).get(node.name, {})  # type: ignore
        else:
            attribute_data = get_module_attributes(node.root.obj).get(node.name, {})

    return Attribute(
        name=node.name,
        path=node.dotted_path,
        file_path=node.file_path,
        docstring=attribute_data["docstring"],
        attr_type=attribute_data["annotation"],
        properties=["dataclass-field"],
    )

get_attribute_documentation(node, attribute_data=None) staticmethod ¤

Get the documentation for an attribute.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the method and its parents.

required
attribute_data Optional[dict]

Docstring and annotation for this attribute.

None

Returns:

Type Description
Attribute

The documented attribute object.

Source code in pytkdocs/loader.py
@staticmethod
def get_attribute_documentation(node: ObjectNode, attribute_data: Optional[dict] = None) -> Attribute:
    """
    Get the documentation for an attribute.

    Arguments:
        node: The node representing the method and its parents.
        attribute_data: Docstring and annotation for this attribute.

    Returns:
        The documented attribute object.
    """
    if attribute_data is None:
        if node.parent_is_class():
            attribute_data = get_class_attributes(node.parent.obj).get(node.name, {})  # type: ignore
        else:
            attribute_data = get_module_attributes(node.root.obj).get(node.name, {})
    return Attribute(
        name=node.name,
        path=node.dotted_path,
        file_path=node.file_path,
        docstring=attribute_data.get("docstring", ""),
        attr_type=attribute_data.get("annotation", None),
    )

get_class_documentation(self, node, select_members=None) ¤

Get the documentation for a class and its children.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the class and its parents.

required
select_members

Explicit members to select.

None

Returns:

Type Description
Class

The documented class object.

Source code in pytkdocs/loader.py
def get_class_documentation(self, node: ObjectNode, select_members=None) -> Class:
    """
    Get the documentation for a class and its children.

    Arguments:
        node: The node representing the class and its parents.
        select_members: Explicit members to select.

    Returns:
        The documented class object.
    """
    class_ = node.obj
    docstring = inspect.cleandoc(class_.__doc__ or "")
    bases = [self._class_path(b) for b in class_.__bases__]

    source: Optional[Source]

    try:
        source = Source(*inspect.getsourcelines(node.obj))
    except (OSError, TypeError) as error:
        source = None

    root_object = Class(
        name=node.name,
        path=node.dotted_path,
        file_path=node.file_path,
        docstring=docstring,
        bases=bases,
        source=source,
    )

    # Even if we don't select members, we want to correctly parse the docstring
    attributes_data: Dict[str, Dict[str, Any]] = {}
    for parent_class in reversed(class_.__mro__[:-1]):
        merge(attributes_data, get_class_attributes(parent_class))
    context: Dict[str, Any] = {"attributes": attributes_data}
    if "__init__" in class_.__dict__:
        try:
            attributes_data.update(get_instance_attributes(class_.__init__))
            context["signature"] = inspect.signature(class_.__init__)
        except (TypeError, ValueError):
            pass
    root_object.parse_docstring(self.docstring_parser, **context)

    if select_members is False:
        return root_object

    select_members = select_members or set()

    # Build the list of members
    members = {}
    inherited = set()
    direct_members = class_.__dict__
    all_members = dict(inspect.getmembers(class_))
    for member_name, member in all_members.items():
        if member is class_:
            continue
        if not (member is type or member is object) and self.select(member_name, select_members):
            if member_name not in direct_members:
                if self.select_inherited_members:
                    members[member_name] = member
                    inherited.add(member_name)
            else:
                members[member_name] = member

    # Iterate on the selected members
    child: Object
    for member_name, member in members.items():
        child_node = ObjectNode(member, member_name, parent=node)
        if child_node.is_class():
            child = self.get_class_documentation(child_node)
        elif child_node.is_classmethod():
            child = self.get_classmethod_documentation(child_node)
        elif child_node.is_staticmethod():
            child = self.get_staticmethod_documentation(child_node)
        elif child_node.is_method():
            child = self.get_regular_method_documentation(child_node)
        elif child_node.is_property():
            child = self.get_property_documentation(child_node)
        elif member_name in attributes_data:
            child = self.get_attribute_documentation(child_node, attributes_data[member_name])
        else:
            continue
        if member_name in inherited:
            child.properties.append("inherited")
        root_object.add_child(child)

    for attr_name, properties, add_method in (
        ("__fields__", ["pydantic-model"], self.get_pydantic_field_documentation),
        ("_declared_fields", ["marshmallow-model"], self.get_marshmallow_field_documentation),
        ("_meta.get_fields", ["django-model"], self.get_django_field_documentation),
        ("__dataclass_fields__", ["dataclass"], self.get_annotated_dataclass_field),
    ):
        if self.detect_field_model(attr_name, direct_members, all_members):
            root_object.properties.extend(properties)
            self.add_fields(
                node,
                root_object,
                attr_name,
                all_members,
                select_members,
                class_,
                add_method,
            )
            break

    return root_object

get_classmethod_documentation(self, node) ¤

Get the documentation for a class-method.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the class-method and its parents.

required

Returns:

Type Description
Method

The documented method object.

Source code in pytkdocs/loader.py
def get_classmethod_documentation(self, node: ObjectNode) -> Method:
    """
    Get the documentation for a class-method.

    Arguments:
        node: The node representing the class-method and its parents.

    Returns:
        The documented method object.
    """
    return self.get_method_documentation(node, ["classmethod"])

get_django_field_documentation(node) staticmethod ¤

Get the documentation for a Django Field.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the Field and its parents.

required

Returns:

Type Description
Attribute

The documented attribute object.

Source code in pytkdocs/loader.py
@staticmethod
def get_django_field_documentation(node: ObjectNode) -> Attribute:
    """
    Get the documentation for a Django Field.

    Arguments:
        node: The node representing the Field and its parents.

    Returns:
        The documented attribute object.
    """
    prop = node.obj
    path = node.dotted_path
    properties = ["django-field"]

    if prop.null:
        properties.append("nullable")
    if prop.blank:
        properties.append("blank")

    # set correct docstring based on verbose_name and help_text
    # both should be converted to str type in case lazy translation
    # is being used, which is common scenario in django
    if prop.help_text:
        docstring = f"{prop.verbose_name}: {prop.help_text}"
    else:
        docstring = str(prop.verbose_name)

    return Attribute(
        name=node.name,
        path=path,
        file_path=node.file_path,
        docstring=docstring,
        attr_type=prop.__class__,
        properties=properties,
    )

get_function_documentation(self, node) ¤

Get the documentation for a function.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the function and its parents.

required

Returns:

Type Description
Function

The documented function object.

Source code in pytkdocs/loader.py
def get_function_documentation(self, node: ObjectNode) -> Function:
    """
    Get the documentation for a function.

    Arguments:
        node: The node representing the function and its parents.

    Returns:
        The documented function object.
    """
    function = node.obj
    path = node.dotted_path
    source: Optional[Source]
    signature: Optional[inspect.Signature]

    try:
        signature = inspect.signature(function)
    except TypeError as error:
        signature = None

    try:
        source = Source(*inspect.getsourcelines(function))
    except OSError as error:
        source = None

    properties: List[str] = []
    if node.is_coroutine_function():
        properties.append("async")

    return Function(
        name=node.name,
        path=node.dotted_path,
        file_path=node.file_path,
        docstring=inspect.getdoc(function),
        signature=signature,
        source=source,
        properties=properties,
    )

get_marshmallow_field_documentation(node) staticmethod ¤

Get the documentation for a Marshmallow Field.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the Field and its parents.

required

Returns:

Type Description
Attribute

The documented attribute object.

Source code in pytkdocs/loader.py
@staticmethod
def get_marshmallow_field_documentation(node: ObjectNode) -> Attribute:
    """
    Get the documentation for a Marshmallow Field.

    Arguments:
        node: The node representing the Field and its parents.

    Returns:
        The documented attribute object.
    """
    prop = node.obj
    path = node.dotted_path
    properties = ["marshmallow-field"]
    if prop.required:
        properties.append("required")

    return Attribute(
        name=node.name,
        path=path,
        file_path=node.file_path,
        docstring=prop.metadata.get("description"),
        attr_type=type(prop),
        properties=properties,
    )

get_method_documentation(self, node, properties=None) ¤

Get the documentation for a method or method descriptor.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the method and its parents.

required
properties Optional[List[str]]

A list of properties to apply to the method.

None

Returns:

Type Description
Method

The documented method object.

Source code in pytkdocs/loader.py
def get_method_documentation(self, node: ObjectNode, properties: Optional[List[str]] = None) -> Method:
    """
    Get the documentation for a method or method descriptor.

    Arguments:
        node: The node representing the method and its parents.
        properties: A list of properties to apply to the method.

    Returns:
        The documented method object.
    """
    method = node.obj
    path = node.dotted_path
    signature: Optional[inspect.Signature]
    source: Optional[Source]

    try:
        source = Source(*inspect.getsourcelines(method))
    except OSError as error:
        source = None
    except TypeError:
        source = None

    if node.is_coroutine_function():
        if properties is None:
            properties = ["async"]
        else:
            properties.append("async")

    try:
        # for "built-in" functions, e.g. those implemented in C,
        # inspect.signature() uses the __text_signature__ attribute, which
        # provides a limited but still useful amount of signature information.
        # "built-in" functions with no __text_signature__ will
        # raise a ValueError().
        signature = inspect.signature(method)
    except ValueError as error:
        signature = None

    return Method(
        name=node.name,
        path=path,
        file_path=node.file_path,
        docstring=inspect.getdoc(method),
        signature=signature,
        properties=properties or [],
        source=source,
    )

get_module_documentation(self, node, select_members=None) ¤

Get the documentation for a module and its children.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the module and its parents.

required
select_members

Explicit members to select.

None

Returns:

Type Description
Module

The documented module object.

Source code in pytkdocs/loader.py
def get_module_documentation(self, node: ObjectNode, select_members=None) -> Module:
    """
    Get the documentation for a module and its children.

    Arguments:
        node: The node representing the module and its parents.
        select_members: Explicit members to select.

    Returns:
        The documented module object.
    """
    module = node.obj
    path = node.dotted_path
    name = path.split(".")[-1]
    source: Optional[Source]

    try:
        source = Source(inspect.getsource(module), 1)
    except OSError as error:
        try:
            code = Path(node.file_path).read_text()
        except (OSError, UnicodeDecodeError):
            source = None
        else:
            source = Source(code, 1) if code else None

    root_object = Module(
        name=name,
        path=path,
        file_path=node.file_path,
        docstring=inspect.getdoc(module),
        source=source,
    )

    if select_members is False:
        return root_object

    select_members = select_members or set()

    attributes_data = get_module_attributes(module)
    root_object.parse_docstring(self.docstring_parser, attributes=attributes_data)

    for member_name, member in inspect.getmembers(module):
        if self.select(member_name, select_members):
            child_node = ObjectNode(member, member_name, parent=node)
            if child_node.is_class() and node.root.obj is inspect.getmodule(child_node.obj):
                root_object.add_child(self.get_class_documentation(child_node))
            elif child_node.is_function() and node.root.obj is inspect.getmodule(child_node.obj):
                root_object.add_child(self.get_function_documentation(child_node))
            elif member_name in attributes_data:
                root_object.add_child(self.get_attribute_documentation(child_node, attributes_data[member_name]))

    if hasattr(module, "__path__"):  # noqa: WPS421 (hasattr)
        for _, modname, _ in pkgutil.iter_modules(module.__path__):
            if self.select(modname, select_members):
                leaf = get_object_tree(f"{path}.{modname}")
                root_object.add_child(self.get_module_documentation(leaf))

    return root_object

get_object_documentation(self, dotted_path, members=None) ¤

Get the documentation for an object and its children.

Parameters:

Name Type Description Default
dotted_path str

The Python dotted path to the desired object.

required
members Union[Set[str], bool]

True to select members and filter them, False to select no members, or a list of names to explicitly select the members with these names. It is applied only on the root object.

None

Returns:

Type Description
Object

The documented object.

Source code in pytkdocs/loader.py
def get_object_documentation(self, dotted_path: str, members: Optional[Union[Set[str], bool]] = None) -> Object:
    """
    Get the documentation for an object and its children.

    Arguments:
        dotted_path: The Python dotted path to the desired object.
        members: `True` to select members and filter them, `False` to select no members,
            or a list of names to explicitly select the members with these names.
            It is applied only on the root object.

    Returns:
        The documented object.
    """
    if members is True:
        members = set()

    root_object: Object
    leaf = get_object_tree(dotted_path, self.new_path_syntax)

    if leaf.is_module():
        root_object = self.get_module_documentation(leaf, members)
    elif leaf.is_class():
        root_object = self.get_class_documentation(leaf, members)
    elif leaf.is_staticmethod():
        root_object = self.get_staticmethod_documentation(leaf)
    elif leaf.is_classmethod():
        root_object = self.get_classmethod_documentation(leaf)
    elif leaf.is_method_descriptor():
        root_object = self.get_regular_method_documentation(leaf)
    elif leaf.is_method():
        root_object = self.get_regular_method_documentation(leaf)
    elif leaf.is_function():
        root_object = self.get_function_documentation(leaf)
    elif leaf.is_property():
        root_object = self.get_property_documentation(leaf)
    else:
        root_object = self.get_attribute_documentation(leaf)

    root_object.parse_all_docstrings(self.docstring_parser)

    return root_object

get_property_documentation(self, node) ¤

Get the documentation for a property.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the property and its parents.

required

Returns:

Type Description
Attribute

The documented attribute object (properties are considered attributes for now).

Source code in pytkdocs/loader.py
def get_property_documentation(self, node: ObjectNode) -> Attribute:
    """
    Get the documentation for a property.

    Arguments:
        node: The node representing the property and its parents.

    Returns:
        The documented attribute object (properties are considered attributes for now).
    """
    prop = node.obj
    path = node.dotted_path
    properties = ["property"]
    if node.is_cached_property():
        # cached_property is always writable, see the docs
        properties.extend(["writable", "cached"])
        sig_source_func = prop.func
    else:
        properties.append("readonly" if prop.fset is None else "writable")
        sig_source_func = prop.fget

    source: Optional[Source]

    try:
        signature = inspect.signature(sig_source_func)
    except (TypeError, ValueError) as error:
        attr_type = None
    else:
        attr_type = signature.return_annotation

    try:
        source = Source(*inspect.getsourcelines(sig_source_func))
    except (OSError, TypeError) as error:
        source = None

    return Attribute(
        name=node.name,
        path=path,
        file_path=node.file_path,
        docstring=inspect.getdoc(prop),
        attr_type=attr_type,
        properties=properties,
        source=source,
    )

get_pydantic_field_documentation(node) staticmethod ¤

Get the documentation for a Pydantic Field.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the Field and its parents.

required

Returns:

Type Description
Attribute

The documented attribute object.

Source code in pytkdocs/loader.py
@staticmethod
def get_pydantic_field_documentation(node: ObjectNode) -> Attribute:
    """
    Get the documentation for a Pydantic Field.

    Arguments:
        node: The node representing the Field and its parents.

    Returns:
        The documented attribute object.
    """
    prop = node.obj
    path = node.dotted_path
    properties = ["pydantic-field"]
    if prop.required:
        properties.append("required")

    return Attribute(
        name=node.name,
        path=path,
        file_path=node.file_path,
        docstring=prop.field_info.description,
        attr_type=prop.outer_type_,
        properties=properties,
    )

get_regular_method_documentation(self, node) ¤

Get the documentation for a regular method (not class- nor static-method).

We do extra processing in this method to discard docstrings of __init__ methods that were inherited from parent classes.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the method and its parents.

required

Returns:

Type Description
Method

The documented method object.

Source code in pytkdocs/loader.py
def get_regular_method_documentation(self, node: ObjectNode) -> Method:
    """
    Get the documentation for a regular method (not class- nor static-method).

    We do extra processing in this method to discard docstrings of `__init__` methods
    that were inherited from parent classes.

    Arguments:
        node: The node representing the method and its parents.

    Returns:
        The documented method object.
    """
    method = self.get_method_documentation(node)
    if node.parent:
        class_ = node.parent.obj
        if RE_SPECIAL.match(node.name):
            docstring = method.docstring
            parent_classes = class_.__mro__[1:]
            for parent_class in parent_classes:
                try:
                    parent_method = getattr(parent_class, node.name)
                except AttributeError:
                    continue
                else:
                    if docstring == inspect.getdoc(parent_method):
                        method.docstring = ""
                    break
    return method

get_staticmethod_documentation(self, node) ¤

Get the documentation for a static-method.

Parameters:

Name Type Description Default
node ObjectNode

The node representing the static-method and its parents.

required

Returns:

Type Description
Method

The documented method object.

Source code in pytkdocs/loader.py
def get_staticmethod_documentation(self, node: ObjectNode) -> Method:
    """
    Get the documentation for a static-method.

    Arguments:
        node: The node representing the static-method and its parents.

    Returns:
        The documented method object.
    """
    return self.get_method_documentation(node, ["staticmethod"])

select(self, name, names) ¤

Tells whether we should select an object or not, given its name.

If the set of names is not empty, we check against it, otherwise we check against filters.

Parameters:

Name Type Description Default
name str

The name of the object to select or not.

required
names Set[str]

An explicit list of names to select.

required

Returns:

Type Description
bool

Yes or no.

Source code in pytkdocs/loader.py
def select(self, name: str, names: Set[str]) -> bool:
    """
    Tells whether we should select an object or not, given its name.

    If the set of names is not empty, we check against it, otherwise we check against filters.

    Arguments:
        name: The name of the object to select or not.
        names: An explicit list of names to select.

    Returns:
        Yes or no.
    """
    if names:
        return name in names
    return not self.filter_name_out(name)

ObjectNode ¤

Helper class to represent an object tree.

It's not really a tree but more a backward-linked list: each node has a reference to its parent, but not to its child (for simplicity purposes and to avoid bugs).

Each node stores an object, its name, and a reference to its parent node.

Source code in pytkdocs/loader.py
class ObjectNode:
    """
    Helper class to represent an object tree.

    It's not really a tree but more a backward-linked list:
    each node has a reference to its parent, but not to its child (for simplicity purposes and to avoid bugs).

    Each node stores an object, its name, and a reference to its parent node.
    """

    def __init__(self, obj: Any, name: str, parent: Optional["ObjectNode"] = None) -> None:
        """
        Initialize the object.

        Arguments:
            obj: A Python object.
            name: The object's name.
            parent: The object's parent node.
        """
        try:
            obj = inspect.unwrap(obj)
        except Exception:  # noqa: S110,W0703 (we purposely catch every possible exception)
            # inspect.unwrap at some point runs hasattr(obj, "__wrapped__"),
            # which triggers the __getattr__ method of the object, which in
            # turn can raise various exceptions. Probably not just __getattr__.
            # See https://github.com/pawamoy/pytkdocs/issues/45
            pass  # noqa: WPS420 (no other way than passing)

        self.obj: Any = obj
        """The actual Python object."""

        self.name: str = name
        """The Python object's name."""

        self.parent: Optional[ObjectNode] = parent
        """The parent node."""

    @property
    def dotted_path(self) -> str:
        """
        Return the Python dotted path to the object.

        Returns:
            The Python dotted path to the object.
        """
        parts = [self.name]
        current = self.parent
        while current:
            parts.append(current.name)
            current = current.parent
        return ".".join(reversed(parts))

    @property
    def file_path(self) -> str:
        """
        Return the object's module file path.

        Returns:
            The object's module file path.
        """
        return inspect.getabsfile(self.root.obj)

    @property
    def root(self) -> "ObjectNode":
        """
        Return the root of the tree.

        Returns:
            The root of the tree.
        """
        if self.parent is not None:
            return self.parent.root
        return self

    def is_module(self) -> bool:
        """
        Tell if this node's object is a module.

        Returns:
            The root of the tree.
        """
        return inspect.ismodule(self.obj)

    def is_class(self) -> bool:
        """
        Tell if this node's object is a class.

        Returns:
            If this node's object is a class.
        """
        return inspect.isclass(self.obj)

    def is_function(self) -> bool:
        """
        Tell if this node's object is a function.

        Returns:
            If this node's object is a function.
        """
        return inspect.isfunction(self.obj)

    def is_coroutine_function(self) -> bool:
        """
        Tell if this node's object is a coroutine.

        Returns:
            If this node's object is a coroutine.
        """
        return inspect.iscoroutinefunction(self.obj)

    def is_property(self) -> bool:
        """
        Tell if this node's object is a property.

        Returns:
            If this node's object is a property.
        """
        return isinstance(self.obj, property) or self.is_cached_property()

    def is_cached_property(self) -> bool:
        """
        Tell if this node's object is a cached property.

        Returns:
            If this node's object is a cached property.
        """
        return isinstance(self.obj, cached_property)

    def parent_is_class(self) -> bool:
        """
        Tell if the object of this node's parent is a class.

        Returns:
            If the object of this node's parent is a class.
        """
        return bool(self.parent and self.parent.is_class())

    def is_method(self) -> bool:
        """
        Tell if this node's object is a method.

        Returns:
            If this node's object is a method.
        """
        function_type = type(lambda: None)
        return self.parent_is_class() and isinstance(self.obj, function_type)

    def is_method_descriptor(self) -> bool:
        """
        Tell if this node's object is a method descriptor.

        Built-in methods (e.g. those implemented in C/Rust) are often
        method descriptors, rather than normal methods.

        Returns:
            If this node's object is a method descriptor.
        """
        return inspect.ismethoddescriptor(self.obj)

    def is_staticmethod(self) -> bool:
        """
        Tell if this node's object is a staticmethod.

        Returns:
            If this node's object is a staticmethod.
        """
        if not self.parent:
            return False
        self_from_parent = self.parent.obj.__dict__.get(self.name, None)
        return self.parent_is_class() and isinstance(self_from_parent, staticmethod)

    def is_classmethod(self) -> bool:
        """
        Tell if this node's object is a classmethod.

        Returns:
            If this node's object is a classmethod.
        """
        if not self.parent:
            return False
        self_from_parent = self.parent.obj.__dict__.get(self.name, None)
        return self.parent_is_class() and isinstance(self_from_parent, classmethod)

dotted_path: str property readonly ¤

Return the Python dotted path to the object.

Returns:

Type Description
str

The Python dotted path to the object.

file_path: str property readonly ¤

Return the object's module file path.

Returns:

Type Description
str

The object's module file path.

root: ObjectNode property readonly ¤

Return the root of the tree.

Returns:

Type Description
ObjectNode

The root of the tree.

__init__(self, obj, name, parent=None) special ¤

Initialize the object.

Parameters:

Name Type Description Default
obj Any

A Python object.

required
name str

The object's name.

required
parent Optional[ObjectNode]

The object's parent node.

None
Source code in pytkdocs/loader.py
def __init__(self, obj: Any, name: str, parent: Optional["ObjectNode"] = None) -> None:
    """
    Initialize the object.

    Arguments:
        obj: A Python object.
        name: The object's name.
        parent: The object's parent node.
    """
    try:
        obj = inspect.unwrap(obj)
    except Exception:  # noqa: S110,W0703 (we purposely catch every possible exception)
        # inspect.unwrap at some point runs hasattr(obj, "__wrapped__"),
        # which triggers the __getattr__ method of the object, which in
        # turn can raise various exceptions. Probably not just __getattr__.
        # See https://github.com/pawamoy/pytkdocs/issues/45
        pass  # noqa: WPS420 (no other way than passing)

    self.obj: Any = obj
    """The actual Python object."""

    self.name: str = name
    """The Python object's name."""

    self.parent: Optional[ObjectNode] = parent
    """The parent node."""

is_cached_property(self) ¤

Tell if this node's object is a cached property.

Returns:

Type Description
bool

If this node's object is a cached property.

Source code in pytkdocs/loader.py
def is_cached_property(self) -> bool:
    """
    Tell if this node's object is a cached property.

    Returns:
        If this node's object is a cached property.
    """
    return isinstance(self.obj, cached_property)

is_class(self) ¤

Tell if this node's object is a class.

Returns:

Type Description
bool

If this node's object is a class.

Source code in pytkdocs/loader.py
def is_class(self) -> bool:
    """
    Tell if this node's object is a class.

    Returns:
        If this node's object is a class.
    """
    return inspect.isclass(self.obj)

is_classmethod(self) ¤

Tell if this node's object is a classmethod.

Returns:

Type Description
bool

If this node's object is a classmethod.

Source code in pytkdocs/loader.py
def is_classmethod(self) -> bool:
    """
    Tell if this node's object is a classmethod.

    Returns:
        If this node's object is a classmethod.
    """
    if not self.parent:
        return False
    self_from_parent = self.parent.obj.__dict__.get(self.name, None)
    return self.parent_is_class() and isinstance(self_from_parent, classmethod)

is_coroutine_function(self) ¤

Tell if this node's object is a coroutine.

Returns:

Type Description
bool

If this node's object is a coroutine.

Source code in pytkdocs/loader.py
def is_coroutine_function(self) -> bool:
    """
    Tell if this node's object is a coroutine.

    Returns:
        If this node's object is a coroutine.
    """
    return inspect.iscoroutinefunction(self.obj)

is_function(self) ¤

Tell if this node's object is a function.

Returns:

Type Description
bool

If this node's object is a function.

Source code in pytkdocs/loader.py
def is_function(self) -> bool:
    """
    Tell if this node's object is a function.

    Returns:
        If this node's object is a function.
    """
    return inspect.isfunction(self.obj)

is_method(self) ¤

Tell if this node's object is a method.

Returns:

Type Description
bool

If this node's object is a method.

Source code in pytkdocs/loader.py
def is_method(self) -> bool:
    """
    Tell if this node's object is a method.

    Returns:
        If this node's object is a method.
    """
    function_type = type(lambda: None)
    return self.parent_is_class() and isinstance(self.obj, function_type)

is_method_descriptor(self) ¤

Tell if this node's object is a method descriptor.

Built-in methods (e.g. those implemented in C/Rust) are often method descriptors, rather than normal methods.

Returns:

Type Description
bool

If this node's object is a method descriptor.

Source code in pytkdocs/loader.py
def is_method_descriptor(self) -> bool:
    """
    Tell if this node's object is a method descriptor.

    Built-in methods (e.g. those implemented in C/Rust) are often
    method descriptors, rather than normal methods.

    Returns:
        If this node's object is a method descriptor.
    """
    return inspect.ismethoddescriptor(self.obj)

is_module(self) ¤

Tell if this node's object is a module.

Returns:

Type Description
bool

The root of the tree.

Source code in pytkdocs/loader.py
def is_module(self) -> bool:
    """
    Tell if this node's object is a module.

    Returns:
        The root of the tree.
    """
    return inspect.ismodule(self.obj)

is_property(self) ¤

Tell if this node's object is a property.

Returns:

Type Description
bool

If this node's object is a property.

Source code in pytkdocs/loader.py
def is_property(self) -> bool:
    """
    Tell if this node's object is a property.

    Returns:
        If this node's object is a property.
    """
    return isinstance(self.obj, property) or self.is_cached_property()

is_staticmethod(self) ¤

Tell if this node's object is a staticmethod.

Returns:

Type Description
bool

If this node's object is a staticmethod.

Source code in pytkdocs/loader.py
def is_staticmethod(self) -> bool:
    """
    Tell if this node's object is a staticmethod.

    Returns:
        If this node's object is a staticmethod.
    """
    if not self.parent:
        return False
    self_from_parent = self.parent.obj.__dict__.get(self.name, None)
    return self.parent_is_class() and isinstance(self_from_parent, staticmethod)

parent_is_class(self) ¤

Tell if the object of this node's parent is a class.

Returns:

Type Description
bool

If the object of this node's parent is a class.

Source code in pytkdocs/loader.py
def parent_is_class(self) -> bool:
    """
    Tell if the object of this node's parent is a class.

    Returns:
        If the object of this node's parent is a class.
    """
    return bool(self.parent and self.parent.is_class())

field_is_inherited(field_name, fields_name, base_class) ¤

Check if a field with a certain name was inherited from parent classes.

Parameters:

Name Type Description Default
field_name str

The name of the field to check.

required
fields_name str

The name of the attribute in which the fields are stored.

required
base_class type

The base class in which the field appears.

required

Returns:

Type Description
bool

Whether the field was inherited.

Source code in pytkdocs/loader.py
def field_is_inherited(field_name: str, fields_name: str, base_class: type) -> bool:
    """
    Check if a field with a certain name was inherited from parent classes.

    Arguments:
        field_name: The name of the field to check.
        fields_name: The name of the attribute in which the fields are stored.
        base_class: The base class in which the field appears.

    Returns:
        Whether the field was inherited.
    """
    # To tell if a field was inherited, we check if it exists in parent classes __fields__ attributes.
    # We don't check the current class, nor the top one (object), hence __mro__[1:-1]
    return field_name in set(
        chain(
            *(getattr(parent_class, fields_name, {}).keys() for parent_class in base_class.__mro__[1:-1]),
        ),
    )

get_object_tree(path, new_path_syntax=False) ¤

Transform a path into an actual Python object.

The path can be arbitrary long. You can pass the path to a package, a module, a class, a function or a global variable, as deep as you want, as long as the deepest module is importable through importlib.import_module and each object is obtainable through the getattr method. It is not possible to load local objects.

Parameters:

Name Type Description Default
path str

The dot/colon-separated path of the object.

required
new_path_syntax bool

Whether to use the "colon" syntax for the path.

False

Exceptions:

Type Description
ValueError

When the path is not valid (evaluates to False).

ImportError

When the object or its parent module could not be imported.

Returns:

Type Description
ObjectNode

The leaf node representing the object and its parents.

Source code in pytkdocs/loader.py
def get_object_tree(path: str, new_path_syntax: bool = False) -> ObjectNode:
    """
    Transform a path into an actual Python object.

    The path can be arbitrary long. You can pass the path to a package,
    a module, a class, a function or a global variable, as deep as you
    want, as long as the deepest module is importable through
    `importlib.import_module` and each object is obtainable through
    the `getattr` method. It is not possible to load local objects.

    Args:
        path: The dot/colon-separated path of the object.
        new_path_syntax: Whether to use the "colon" syntax for the path.

    Raises:
        ValueError: When the path is not valid (evaluates to `False`).
        ImportError: When the object or its parent module could not be imported.

    Returns:
        The leaf node representing the object and its parents.
    """
    if not path:
        raise ValueError(f"path must be a valid Python path, not {path}")

    objects: List[str] = []

    if ":" in path or new_path_syntax:
        try:
            module_path, object_path = path.split(":")
        except ValueError:  # no colon
            module_path, objects = path, []
        else:
            objects = object_path.split(".")

        # let the ImportError bubble up
        parent_module = importlib.import_module(module_path)

    else:
        # We will try to import the longest dotted-path first.
        # If it fails, we remove the right-most part and put it in a list of "objects", used later.
        # We loop until we find the deepest importable submodule.
        obj_parent_modules = path.split(".")

        while True:
            parent_module_path = ".".join(obj_parent_modules)
            try:
                parent_module = importlib.import_module(parent_module_path)
            except ImportError as error:
                if len(obj_parent_modules) == 1:
                    raise ImportError(
                        f"Importing '{path}' failed, possible causes are:\n"
                        f"- an exception happened while importing\n"
                        f"- an element in the path does not exist",
                    ) from error
                objects.insert(0, obj_parent_modules.pop(-1))
            else:
                break

    # We now have the module containing the desired object.
    # We will build the object tree by iterating over the previously stored objects names
    # and trying to get them as attributes.
    current_node = ObjectNode(parent_module, parent_module.__name__)
    for obj_name in objects:
        obj = getattr(current_node.obj, obj_name)
        child = ObjectNode(obj, obj_name, parent=current_node)
        current_node = child

    leaf = current_node

    # We now try to get the "real" parent module, not the one the object was imported into.
    # This is important if we want to be able to retrieve the docstring of an attribute for example.
    # Once we find an object for which we could get the module, we stop trying to get the module.
    # Once we reach the node before the root, we apply the module if found, and break.
    real_module = None
    while current_node.parent is not None:
        if real_module is None:
            real_module = inspect.getmodule(current_node.obj)
        if inspect.ismodule(current_node.parent.obj):
            if real_module is not None and real_module is not current_node.parent.obj:
                current_node.parent = ObjectNode(real_module, real_module.__name__)
            break
        current_node = current_node.parent

    return leaf

split_attr_name(attr_name) ¤

Split an attribute name into a first-order attribute name and remainder.

Parameters:

Name Type Description Default
attr_name str

Attribute name (a)

required

Returns:

Type Description
Tuple containing

first_order_attr_name: Name of the first order attribute (a) remainder: The remainder (b.c)

Source code in pytkdocs/loader.py
def split_attr_name(attr_name: str) -> Tuple[str, Optional[str]]:
    """
    Split an attribute name into a first-order attribute name and remainder.

    Args:
        attr_name: Attribute name (a)

    Returns:
        Tuple containing:
            first_order_attr_name: Name of the first order attribute (a)
            remainder: The remainder (b.c)

    """
    first_order_attr_name, *remaining = attr_name.split(".", maxsplit=1)
    remainder = remaining[0] if remaining else None
    return first_order_attr_name, remainder

objects ¤

This module defines the documented objects classes.

Note that properties are considered attributes, because they are used like such.

It also defines a convenient Source class to represent source code.

Attribute (Object) ¤

A class to store information about an attribute.

It accepts an additional attr_type argument at instantiation.

Source code in pytkdocs/objects.py
class Attribute(Object):
    """
    A class to store information about an attribute.

    It accepts an additional `attr_type` argument at instantiation.
    """

    possible_name_properties: List[ApplicableNameProperty] = [NAME_SPECIAL, NAME_CLASS_PRIVATE, NAME_PRIVATE]

    def __init__(self, *args, attr_type=None, **kwargs):
        """
        Initialize the object.

        Arguments:
            *args: Arguments passed to the parent class Initialize the object.
            attr_type: The attribute type.
            **kwargs: Arguments passed to the parent class Initialize the object.
        """
        super().__init__(*args, **kwargs)
        self.type = attr_type

possible_name_properties: List[Tuple[str, Callable[[str], bool]]] ¤

The properties that we can apply to the object based on its name.

The applicable properties vary from one subclass of Object to another.

__init__(self, *args, *, attr_type=None, **kwargs) special ¤

Initialize the object.

Parameters:

Name Type Description Default
*args

Arguments passed to the parent class Initialize the object.

()
attr_type

The attribute type.

None
**kwargs

Arguments passed to the parent class Initialize the object.

{}
Source code in pytkdocs/objects.py
def __init__(self, *args, attr_type=None, **kwargs):
    """
    Initialize the object.

    Arguments:
        *args: Arguments passed to the parent class Initialize the object.
        attr_type: The attribute type.
        **kwargs: Arguments passed to the parent class Initialize the object.
    """
    super().__init__(*args, **kwargs)
    self.type = attr_type

Class (Object) ¤

A class to store information about a class.

Source code in pytkdocs/objects.py
class Class(Object):
    """A class to store information about a class."""

    possible_name_properties: List[ApplicableNameProperty] = [NAME_PRIVATE]

    def __init__(self, *args, bases: List[str] = None, **kwargs):
        """
        Initialize the object.

        Arguments:
            *args: Arguments passed to the parent class Initialize the object.
            bases: The base classes (dotted paths).
            **kwargs: Arguments passed to the parent class Initialize the object.
        """
        super().__init__(*args, **kwargs)
        self.bases = bases or ["object"]

possible_name_properties: List[Tuple[str, Callable[[str], bool]]] ¤

The properties that we can apply to the object based on its name.

The applicable properties vary from one subclass of Object to another.

__init__(self, *args, *, bases=None, **kwargs) special ¤

Initialize the object.

Parameters:

Name Type Description Default
*args

Arguments passed to the parent class Initialize the object.

()
bases List[str]

The base classes (dotted paths).

None
**kwargs

Arguments passed to the parent class Initialize the object.

{}
Source code in pytkdocs/objects.py
def __init__(self, *args, bases: List[str] = None, **kwargs):
    """
    Initialize the object.

    Arguments:
        *args: Arguments passed to the parent class Initialize the object.
        bases: The base classes (dotted paths).
        **kwargs: Arguments passed to the parent class Initialize the object.
    """
    super().__init__(*args, **kwargs)
    self.bases = bases or ["object"]

Function (Object) ¤

A class to store information about a function.

It accepts an additional signature argument at instantiation.

Source code in pytkdocs/objects.py
class Function(Object):
    """
    A class to store information about a function.

    It accepts an additional `signature` argument at instantiation.
    """

    possible_name_properties: List[ApplicableNameProperty] = [NAME_PRIVATE]

    def __init__(self, *args, signature=None, **kwargs):
        """
        Initialize the object.

        Arguments:
            *args: Arguments passed to the parent class Initialize the object.
            signature: The function signature.
            **kwargs: Arguments passed to the parent class Initialize the object.
        """
        super().__init__(*args, **kwargs)
        self.signature = signature

possible_name_properties: List[Tuple[str, Callable[[str], bool]]] ¤

The properties that we can apply to the object based on its name.

The applicable properties vary from one subclass of Object to another.

__init__(self, *args, *, signature=None, **kwargs) special ¤

Initialize the object.

Parameters:

Name Type Description Default
*args

Arguments passed to the parent class Initialize the object.

()
signature

The function signature.

None
**kwargs

Arguments passed to the parent class Initialize the object.

{}
Source code in pytkdocs/objects.py
def __init__(self, *args, signature=None, **kwargs):
    """
    Initialize the object.

    Arguments:
        *args: Arguments passed to the parent class Initialize the object.
        signature: The function signature.
        **kwargs: Arguments passed to the parent class Initialize the object.
    """
    super().__init__(*args, **kwargs)
    self.signature = signature

Method (Object) ¤

A class to store information about a method.

It accepts an additional signature argument at instantiation.

Source code in pytkdocs/objects.py
class Method(Object):
    """
    A class to store information about a method.

    It accepts an additional `signature` argument at instantiation.
    """

    possible_name_properties: List[ApplicableNameProperty] = [NAME_SPECIAL, NAME_PRIVATE]

    def __init__(self, *args, signature=None, **kwargs):
        """
        Initialize the object.

        Arguments:
            *args: Arguments passed to the parent class Initialize the object.
            signature: The function signature.
            **kwargs: Arguments passed to the parent class Initialize the object.
        """
        super().__init__(*args, **kwargs)
        self.signature = signature

possible_name_properties: List[Tuple[str, Callable[[str], bool]]] ¤

The properties that we can apply to the object based on its name.

The applicable properties vary from one subclass of Object to another.

__init__(self, *args, *, signature=None, **kwargs) special ¤

Initialize the object.

Parameters:

Name Type Description Default
*args

Arguments passed to the parent class Initialize the object.

()
signature

The function signature.

None
**kwargs

Arguments passed to the parent class Initialize the object.

{}
Source code in pytkdocs/objects.py
def __init__(self, *args, signature=None, **kwargs):
    """
    Initialize the object.

    Arguments:
        *args: Arguments passed to the parent class Initialize the object.
        signature: The function signature.
        **kwargs: Arguments passed to the parent class Initialize the object.
    """
    super().__init__(*args, **kwargs)
    self.signature = signature

Module (Object) ¤

A class to store information about a module.

Source code in pytkdocs/objects.py
class Module(Object):
    """A class to store information about a module."""

    possible_name_properties: List[ApplicableNameProperty] = [NAME_SPECIAL, NAME_PRIVATE]

    @property
    def file_name(self) -> str:
        """
        Return the base name of the module file, without the extension.

        Returns:
            The module file's base name.
        """
        return os.path.splitext(os.path.basename(self.file_path))[0]

    @property
    def name_to_check(self) -> str:  # noqa: D102
        return self.file_name

file_name: str property readonly ¤

Return the base name of the module file, without the extension.

Returns:

Type Description
str

The module file's base name.

name_to_check: str property readonly ¤

Return the attribute to check against name-properties regular expressions (private, class-private, special).

Returns:

Type Description
str

The attribute to check (its name).

possible_name_properties: List[Tuple[str, Callable[[str], bool]]] ¤

The properties that we can apply to the object based on its name.

The applicable properties vary from one subclass of Object to another.

Object ¤

A base class to store information about a Python object.

Each instance additionally stores references to its children, grouped by category.

Source code in pytkdocs/objects.py
class Object(metaclass=ABCMeta):
    """
    A base class to store information about a Python object.

    Each instance additionally stores references to its children, grouped by category.
    """

    possible_name_properties: List[ApplicableNameProperty] = []
    """
    The properties that we can apply to the object based on its name.

    The applicable properties vary from one subclass of `Object` to another.
    """

    def __init__(
        self,
        name: str,
        path: str,
        file_path: str,
        docstring: Optional[str] = "",
        properties: Optional[List[str]] = None,
        source: Optional[Source] = None,
    ) -> None:
        """
        Initialize the object.

        Arguments:
            name: The object's name.
            path: The object's dotted-path.
            file_path: The file path of the object's direct parent module.
            docstring: The object's docstring.
            properties: The object's properties.
            source: The object's source code.
        """
        self.name = name
        """The object's name."""
        self.path = path
        """The object's dotted-path."""
        self.file_path = file_path
        """The file path of the object's direct parent module."""
        self.docstring = docstring
        """The object's docstring."""
        self.docstring_sections: List[Section] = []
        """The object's docstring parsed into sections."""
        self.docstring_errors: List[str] = []
        """The errors detected while parsing the docstring."""
        self.properties = properties or []
        """The object's properties."""
        self.parent: Optional[Object] = None
        """The object's parent (another instance of a subclass of `Object`)."""
        self.source = source
        """The object's source code."""

        self._path_map = {self.path: self}
        self._parsed = False

        self.attributes: List[Attribute] = []
        """The list of all the object's attributes."""
        self.methods: List[Method] = []
        """The list of all the object's methods."""
        self.functions: List[Function] = []
        """The list of all the object's functions."""
        self.modules: List[Module] = []
        """The list of all the object's submodules."""
        self.classes: List[Class] = []
        """The list of all the object's classes."""
        self.children: List[Object] = []
        """The list of all the object's children."""

    def __str__(self) -> str:
        return self.path

    @property
    def category(self) -> str:
        """
        Return the object's category.

        Returns:
            The object's category (module, class, function, method or attribute).
        """
        return self.__class__.__name__.lower()

    @property
    def root(self) -> "Object":
        """
        Return the object's root.

        Returns:
            The object's root (top-most parent).
        """
        obj = self
        while obj.parent:
            obj = obj.parent
        return obj

    @property
    def relative_file_path(self) -> str:
        """
        Return the relative file path of the object.

        It is the relative path to the object's module,
        starting at the path of the top-most package it is contained in.

        For example:

        - package is `a`
        - package absolute path is `/abs/path/to/a`
        - module is `a.b.c`
        - object is `c` or anything defined in `c`
        - relative file path is `a/b/c.py`

        If the relative file path cannot be determined, the value returned is `""` (empty string).

        Returns:
            The path relative to the object's package.
        """
        parts = self.path.split(".")
        namespaces = [".".join(parts[:length]) for length in range(1, len(parts) + 1)]  # noqa: WPS221 (not complex)
        # Iterate through all sub namespaces including the last in case it is a module
        for namespace in namespaces:
            try:  # noqa: WPS229 (more compact)
                importlib.import_module(namespace)
                top_package = sys.modules[namespace]
            except (ModuleNotFoundError, ImportError, KeyError):
                # ImportError: Triggered if the namespace is not importable
                # ModuleNotFoundError: Triggered if the namespace is not a module
                # KeyError: Triggered if the imported package isn't referenced under the same fully qualified name
                # Namespace packages are importable, so this should work for them
                return ""

            try:  # noqa: WPS229 (more compact)
                top_package_path = Path(inspect.getabsfile(top_package)).parent
                return str(Path(self.file_path).relative_to(top_package_path.parent))
            except TypeError:
                # Triggered if getabsfile() can't be found in the case of a Namespace package
                pass  # noqa: WPS420 (passing is the only way)
            except ValueError:
                # Triggered if Path().relative_to can't find an appropriate path
                return ""

        return ""

    @property
    def name_to_check(self) -> str:
        """
        Return the attribute to check against name-properties regular expressions (private, class-private, special).

        Returns:
            The attribute to check (its name).
        """
        return self.name

    @property
    def name_properties(self) -> List[str]:
        """
        Return the object's name properties.

        Returns:
            The object's name properties (private, class-private, special).
        """
        properties = []
        for prop, predicate in self.possible_name_properties:
            if predicate(self.name_to_check):
                properties.append(prop)
        return properties

    @property
    def parent_path(self) -> str:
        """
        Return the parent's path, computed from the current path.

        The parent object path is not used: this property is used to see if an object is really related to another one,
        to add it as a child to the other. When we do that, the child doesn't even have a parent.

        Returns:
            The dotted path of the parent object.
        """
        return self.path.rsplit(".", 1)[0]

    def add_child(self, obj: "Object") -> None:  # noqa: WPS231 (not complex)
        """
        Add an object as a child of this object.

        If the child computed `parent_path` is not equal to this object's path, abort.

        Append the child to the `children` list, and to the right category list.

        Arguments:
            obj: An instance of documented object.
        """
        if obj.parent_path != self.path:
            return

        self.children.append(obj)
        if isinstance(obj, Module):
            self.modules.append(obj)
        elif isinstance(obj, Class):
            self.classes.append(obj)
        elif isinstance(obj, Function):
            self.functions.append(obj)
        elif isinstance(obj, Method):
            self.methods.append(obj)
        elif isinstance(obj, Attribute):
            # Dataclass attributes with default values will already be present in `self.attributes` as they are
            # resolved differently by the python interpreter. As they have a concrete value, they are already present
            # in the "original" class. They should be overridden with the new "dataclass" attribute coming in here
            # (having the "dataclass_field" property set)
            new_attribute_name = obj.name
            for attribute in self.attributes:
                if attribute.name == new_attribute_name:
                    self.attributes.remove(attribute)
            self.attributes.append(obj)
        obj.parent = self

        self._path_map[obj.path] = obj

    def add_children(self, children: List["Object"]) -> None:
        """
        Add a list of objects as children of this object.

        Arguments:
            children: The list of children to add.
        """
        for child in children:
            self.add_child(child)

    def parse_docstring(self, parser: Parser, **context) -> None:
        """
        Parse the docstring of this object.

        Arguments:
            parser: A parser to parse the docstrings.
            **context: Additional context to use when parsing.
        """
        if self.docstring and not self._parsed:
            sections, errors = parser.parse(self.docstring, {"obj": self, **context})
            self.docstring_sections = sections
            self.docstring_errors = errors
            self._parsed = True

    def parse_all_docstrings(self, parser: Parser) -> None:
        """
        Recursively parse the docstring of this object and its children.

        Arguments:
            parser: A parser to parse the docstrings.
        """
        self.parse_docstring(parser)
        for child in self.children:
            child.parse_all_docstrings(parser)

    @lru_cache()
    def has_contents(self) -> bool:
        """
        Tells if the object has "contents".

        An object has contents when:

        - it is the root of the object tree
        - it has a docstring
        - at least one of its children (whatever the depth) has contents

        The value is cached, so this method should be called last, when the tree doesn't change anymore.

        Returns:
            Whether this object has contents or not.
        """
        has_docstring = bool(self.docstring)
        is_root = not self.parent
        children_have_contents = any(child.has_contents() for child in self.children)
        return has_docstring or is_root or children_have_contents

category: str property readonly ¤

Return the object's category.

Returns:

Type Description
str

The object's category (module, class, function, method or attribute).

name_properties: List[str] property readonly ¤

Return the object's name properties.

Returns:

Type Description
List[str]

The object's name properties (private, class-private, special).

name_to_check: str property readonly ¤

Return the attribute to check against name-properties regular expressions (private, class-private, special).

Returns:

Type Description
str

The attribute to check (its name).

parent_path: str property readonly ¤

Return the parent's path, computed from the current path.

The parent object path is not used: this property is used to see if an object is really related to another one, to add it as a child to the other. When we do that, the child doesn't even have a parent.

Returns:

Type Description
str

The dotted path of the parent object.

possible_name_properties: List[Tuple[str, Callable[[str], bool]]] ¤

The properties that we can apply to the object based on its name.

The applicable properties vary from one subclass of Object to another.

relative_file_path: str property readonly ¤

Return the relative file path of the object.

It is the relative path to the object's module, starting at the path of the top-most package it is contained in.

For example:

  • package is a
  • package absolute path is /abs/path/to/a
  • module is a.b.c
  • object is c or anything defined in c
  • relative file path is a/b/c.py

If the relative file path cannot be determined, the value returned is "" (empty string).

Returns:

Type Description
str

The path relative to the object's package.

root: Object property readonly ¤

Return the object's root.

Returns:

Type Description
Object

The object's root (top-most parent).

__init__(self, name, path, file_path, docstring='', properties=None, source=None) special ¤

Initialize the object.

Parameters:

Name Type Description Default
name str

The object's name.

required
path str

The object's dotted-path.

required
file_path str

The file path of the object's direct parent module.

required
docstring Optional[str]

The object's docstring.

''
properties Optional[List[str]]

The object's properties.

None
source Optional[pytkdocs.objects.Source]

The object's source code.

None
Source code in pytkdocs/objects.py
def __init__(
    self,
    name: str,
    path: str,
    file_path: str,
    docstring: Optional[str] = "",
    properties: Optional[List[str]] = None,
    source: Optional[Source] = None,
) -> None:
    """
    Initialize the object.

    Arguments:
        name: The object's name.
        path: The object's dotted-path.
        file_path: The file path of the object's direct parent module.
        docstring: The object's docstring.
        properties: The object's properties.
        source: The object's source code.
    """
    self.name = name
    """The object's name."""
    self.path = path
    """The object's dotted-path."""
    self.file_path = file_path
    """The file path of the object's direct parent module."""
    self.docstring = docstring
    """The object's docstring."""
    self.docstring_sections: List[Section] = []
    """The object's docstring parsed into sections."""
    self.docstring_errors: List[str] = []
    """The errors detected while parsing the docstring."""
    self.properties = properties or []
    """The object's properties."""
    self.parent: Optional[Object] = None
    """The object's parent (another instance of a subclass of `Object`)."""
    self.source = source
    """The object's source code."""

    self._path_map = {self.path: self}
    self._parsed = False

    self.attributes: List[Attribute] = []
    """The list of all the object's attributes."""
    self.methods: List[Method] = []
    """The list of all the object's methods."""
    self.functions: List[Function] = []
    """The list of all the object's functions."""
    self.modules: List[Module] = []
    """The list of all the object's submodules."""
    self.classes: List[Class] = []
    """The list of all the object's classes."""
    self.children: List[Object] = []
    """The list of all the object's children."""

add_child(self, obj) ¤

Add an object as a child of this object.

If the child computed parent_path is not equal to this object's path, abort.

Append the child to the children list, and to the right category list.

Parameters:

Name Type Description Default
obj Object

An instance of documented object.

required
Source code in pytkdocs/objects.py
def add_child(self, obj: "Object") -> None:  # noqa: WPS231 (not complex)
    """
    Add an object as a child of this object.

    If the child computed `parent_path` is not equal to this object's path, abort.

    Append the child to the `children` list, and to the right category list.

    Arguments:
        obj: An instance of documented object.
    """
    if obj.parent_path != self.path:
        return

    self.children.append(obj)
    if isinstance(obj, Module):
        self.modules.append(obj)
    elif isinstance(obj, Class):
        self.classes.append(obj)
    elif isinstance(obj, Function):
        self.functions.append(obj)
    elif isinstance(obj, Method):
        self.methods.append(obj)
    elif isinstance(obj, Attribute):
        # Dataclass attributes with default values will already be present in `self.attributes` as they are
        # resolved differently by the python interpreter. As they have a concrete value, they are already present
        # in the "original" class. They should be overridden with the new "dataclass" attribute coming in here
        # (having the "dataclass_field" property set)
        new_attribute_name = obj.name
        for attribute in self.attributes:
            if attribute.name == new_attribute_name:
                self.attributes.remove(attribute)
        self.attributes.append(obj)
    obj.parent = self

    self._path_map[obj.path] = obj

add_children(self, children) ¤

Add a list of objects as children of this object.

Parameters:

Name Type Description Default
children List[Object]

The list of children to add.

required
Source code in pytkdocs/objects.py
def add_children(self, children: List["Object"]) -> None:
    """
    Add a list of objects as children of this object.

    Arguments:
        children: The list of children to add.
    """
    for child in children:
        self.add_child(child)

has_contents(self) ¤

Tells if the object has "contents".

An object has contents when:

  • it is the root of the object tree
  • it has a docstring
  • at least one of its children (whatever the depth) has contents

The value is cached, so this method should be called last, when the tree doesn't change anymore.

Returns:

Type Description
bool

Whether this object has contents or not.

Source code in pytkdocs/objects.py
@lru_cache()
def has_contents(self) -> bool:
    """
    Tells if the object has "contents".

    An object has contents when:

    - it is the root of the object tree
    - it has a docstring
    - at least one of its children (whatever the depth) has contents

    The value is cached, so this method should be called last, when the tree doesn't change anymore.

    Returns:
        Whether this object has contents or not.
    """
    has_docstring = bool(self.docstring)
    is_root = not self.parent
    children_have_contents = any(child.has_contents() for child in self.children)
    return has_docstring or is_root or children_have_contents

parse_all_docstrings(self, parser) ¤

Recursively parse the docstring of this object and its children.

Parameters:

Name Type Description Default
parser Parser

A parser to parse the docstrings.

required
Source code in pytkdocs/objects.py
def parse_all_docstrings(self, parser: Parser) -> None:
    """
    Recursively parse the docstring of this object and its children.

    Arguments:
        parser: A parser to parse the docstrings.
    """
    self.parse_docstring(parser)
    for child in self.children:
        child.parse_all_docstrings(parser)

parse_docstring(self, parser, **context) ¤

Parse the docstring of this object.

Parameters:

Name Type Description Default
parser Parser

A parser to parse the docstrings.

required
**context

Additional context to use when parsing.

{}
Source code in pytkdocs/objects.py
def parse_docstring(self, parser: Parser, **context) -> None:
    """
    Parse the docstring of this object.

    Arguments:
        parser: A parser to parse the docstrings.
        **context: Additional context to use when parsing.
    """
    if self.docstring and not self._parsed:
        sections, errors = parser.parse(self.docstring, {"obj": self, **context})
        self.docstring_sections = sections
        self.docstring_errors = errors
        self._parsed = True

Source ¤

Helper class to represent source code.

It is simply used to wrap the result of inspect.getsourceslines.

Source code in pytkdocs/objects.py
class Source:
    """
    Helper class to represent source code.

    It is simply used to wrap the result of
    [`inspect.getsourceslines`](https://docs.python.org/3/library/inspect.html#inspect.getsourcelines).
    """

    def __init__(self, lines: Union[str, List[str]], line_start: int) -> None:
        """
        Initialize the object.

        Arguments:
            lines: A list of strings. The strings should have trailing newlines.
            line_start: The line number of where the code starts in the file.
        """
        if isinstance(lines, list):
            code = "".join(lines)
        else:
            code = lines
        self.code = code
        """The code, as a single string."""
        self.line_start = line_start
        """The first line number."""

__init__(self, lines, line_start) special ¤

Initialize the object.

Parameters:

Name Type Description Default
lines Union[str, List[str]]

A list of strings. The strings should have trailing newlines.

required
line_start int

The line number of where the code starts in the file.

required
Source code in pytkdocs/objects.py
def __init__(self, lines: Union[str, List[str]], line_start: int) -> None:
    """
    Initialize the object.

    Arguments:
        lines: A list of strings. The strings should have trailing newlines.
        line_start: The line number of where the code starts in the file.
    """
    if isinstance(lines, list):
        code = "".join(lines)
    else:
        code = lines
    self.code = code
    """The code, as a single string."""
    self.line_start = line_start
    """The first line number."""

parsers special ¤

The docstrings parsers' package.

attributes ¤

Module containing functions to parse attributes in the source code.

docstrings special ¤

The parsers' package.

base ¤

The base module for docstring parsing.

AnnotatedObject ¤

A helper class to store information about an annotated object.

Source code in pytkdocs/parsers/docstrings/base.py
class AnnotatedObject:
    """A helper class to store information about an annotated object."""

    def __init__(self, annotation: Any, description: str) -> None:
        """
        Initialize the object.

        Arguments:
            annotation: The object's annotation.
            description: The object's description.
        """
        self.annotation = annotation
        self.description = description
__init__(self, annotation, description) special ¤

Initialize the object.

Parameters:

Name Type Description Default
annotation Any

The object's annotation.

required
description str

The object's description.

required
Source code in pytkdocs/parsers/docstrings/base.py
def __init__(self, annotation: Any, description: str) -> None:
    """
    Initialize the object.

    Arguments:
        annotation: The object's annotation.
        description: The object's description.
    """
    self.annotation = annotation
    self.description = description
Attribute (AnnotatedObject) ¤

A helper class to store information about a documented attribute.

Source code in pytkdocs/parsers/docstrings/base.py
class Attribute(AnnotatedObject):
    """A helper class to store information about a documented attribute."""

    def __init__(self, name: str, annotation: Any, description: str) -> None:
        """
        Initialize the object.

        Arguments:
            name: The attribute's name.
            annotation: The object's annotation.
            description: The object's description.
        """
        super().__init__(annotation, description)
        self.name = name
__init__(self, name, annotation, description) special ¤

Initialize the object.

Parameters:

Name Type Description Default
name str

The attribute's name.

required
annotation Any

The object's annotation.

required
description str

The object's description.

required
Source code in pytkdocs/parsers/docstrings/base.py
def __init__(self, name: str, annotation: Any, description: str) -> None:
    """
    Initialize the object.

    Arguments:
        name: The attribute's name.
        annotation: The object's annotation.
        description: The object's description.
    """
    super().__init__(annotation, description)
    self.name = name
Parameter (AnnotatedObject) ¤

A helper class to store information about a signature parameter.

Source code in pytkdocs/parsers/docstrings/base.py
class Parameter(AnnotatedObject):
    """A helper class to store information about a signature parameter."""

    def __init__(self, name: str, annotation: Any, description: str, kind: Any, default: Any = empty) -> None:
        """
        Initialize the object.

        Arguments:
            name: The parameter's name.
            annotation: The parameter's annotation.
            description: The parameter's description.
            kind: The parameter's kind (positional only, keyword only, etc.).
            default: The parameter's default value.
        """
        super().__init__(annotation, description)
        self.name = name
        self.kind = kind
        self.default = default

    def __str__(self):
        return self.name

    def __repr__(self):
        return f"<Parameter({self.name}, {self.annotation}, {self.description}, {self.kind}, {self.default})>"

    @property
    def is_optional(self):
        """Tell if this parameter is optional."""
        return self.default is not empty

    @property
    def is_required(self):
        """Tell if this parameter is required."""
        return not self.is_optional

    @property
    def is_args(self):
        """Tell if this parameter is positional."""
        return self.kind is inspect.Parameter.VAR_POSITIONAL

    @property
    def is_kwargs(self):
        """Tell if this parameter is a keyword."""
        return self.kind is inspect.Parameter.VAR_KEYWORD

    @property
    def default_string(self):
        """Return the default value as a string."""
        if self.is_kwargs:
            return "{}"
        if self.is_args:
            return "()"
        if self.is_required:
            return ""
        return repr(self.default)
default_string property readonly ¤

Return the default value as a string.

is_args property readonly ¤

Tell if this parameter is positional.

is_kwargs property readonly ¤

Tell if this parameter is a keyword.

is_optional property readonly ¤

Tell if this parameter is optional.

is_required property readonly ¤

Tell if this parameter is required.

__init__(self, name, annotation, description, kind, default) special ¤

Initialize the object.

Parameters:

Name Type Description Default
name str

The parameter's name.

required
annotation Any

The parameter's annotation.

required
description str

The parameter's description.

required
kind Any

The parameter's kind (positional only, keyword only, etc.).

required
default Any

The parameter's default value.

required
Source code in pytkdocs/parsers/docstrings/base.py
def __init__(self, name: str, annotation: Any, description: str, kind: Any, default: Any = empty) -> None:
    """
    Initialize the object.

    Arguments:
        name: The parameter's name.
        annotation: The parameter's annotation.
        description: The parameter's description.
        kind: The parameter's kind (positional only, keyword only, etc.).
        default: The parameter's default value.
    """
    super().__init__(annotation, description)
    self.name = name
    self.kind = kind
    self.default = default
Parser ¤

A class to parse docstrings.

It is instantiated with an object's path, docstring, signature and return type.

The parse method then returns structured data, in the form of a list of Sections. It also return the list of errors that occurred during parsing.

Source code in pytkdocs/parsers/docstrings/base.py
class Parser(metaclass=ABCMeta):
    """
    A class to parse docstrings.

    It is instantiated with an object's path, docstring, signature and return type.

    The `parse` method then returns structured data,
    in the form of a list of [`Section`][pytkdocs.parsers.docstrings.base.Section]s.
    It also return the list of errors that occurred during parsing.
    """

    def __init__(self) -> None:
        """Initialize the object."""
        self.context: dict = {}
        self.errors: List[str] = []

    def parse(self, docstring: str, context: Optional[dict] = None) -> Tuple[List[Section], List[str]]:
        """
        Parse a docstring and return a list of sections and parsing errors.

        Arguments:
            docstring: The docstring to parse.
            context: Some context helping to parse the docstring.

        Returns:
            A tuple containing the list of sections and the parsing errors.
        """
        self.context = context or {}
        self.errors = []
        sections = self.parse_sections(docstring)
        errors = self.errors
        return sections, errors

    def error(self, message) -> None:
        """
        Record a parsing error.

        Arguments:
            message: A message described the error.
        """
        if self.context["obj"]:
            message = f"{self.context['obj'].path}: {message}"
        self.errors.append(message)

    @abstractmethod
    def parse_sections(self, docstring: str) -> List[Section]:
        """
        Parse a docstring as a list of sections.

        Arguments:
            docstring: The docstring to parse.

        Returns:
            A list of [`Section`][pytkdocs.parsers.docstrings.base.Section]s.
        """
        raise NotImplementedError
__init__(self) special ¤

Initialize the object.

Source code in pytkdocs/parsers/docstrings/base.py
def __init__(self) -> None:
    """Initialize the object."""
    self.context: dict = {}
    self.errors: List[str] = []
error(self, message) ¤

Record a parsing error.

Parameters:

Name Type Description Default
message

A message described the error.

required
Source code in pytkdocs/parsers/docstrings/base.py
def error(self, message) -> None:
    """
    Record a parsing error.

    Arguments:
        message: A message described the error.
    """
    if self.context["obj"]:
        message = f"{self.context['obj'].path}: {message}"
    self.errors.append(message)
parse(self, docstring, context=None) ¤

Parse a docstring and return a list of sections and parsing errors.

Parameters:

Name Type Description Default
docstring str

The docstring to parse.

required
context Optional[dict]

Some context helping to parse the docstring.

None

Returns:

Type Description
Tuple[List[pytkdocs.parsers.docstrings.base.Section], List[str]]

A tuple containing the list of sections and the parsing errors.

Source code in pytkdocs/parsers/docstrings/base.py
def parse(self, docstring: str, context: Optional[dict] = None) -> Tuple[List[Section], List[str]]:
    """
    Parse a docstring and return a list of sections and parsing errors.

    Arguments:
        docstring: The docstring to parse.
        context: Some context helping to parse the docstring.

    Returns:
        A tuple containing the list of sections and the parsing errors.
    """
    self.context = context or {}
    self.errors = []
    sections = self.parse_sections(docstring)
    errors = self.errors
    return sections, errors
parse_sections(self, docstring) ¤

Parse a docstring as a list of sections.

Parameters:

Name Type Description Default
docstring str

The docstring to parse.

required

Returns:

Type Description
List[pytkdocs.parsers.docstrings.base.Section]

A list of Sections.

Source code in pytkdocs/parsers/docstrings/base.py
@abstractmethod
def parse_sections(self, docstring: str) -> List[Section]:
    """
    Parse a docstring as a list of sections.

    Arguments:
        docstring: The docstring to parse.

    Returns:
        A list of [`Section`][pytkdocs.parsers.docstrings.base.Section]s.
    """
    raise NotImplementedError
Section ¤

A helper class to store a docstring section.

Source code in pytkdocs/parsers/docstrings/base.py
class Section:
    """A helper class to store a docstring section."""

    class Type:
        """The possible section types."""

        MARKDOWN = "markdown"
        PARAMETERS = "parameters"
        EXCEPTIONS = "exceptions"
        RETURN = "return"
        YIELD = "yield"
        EXAMPLES = "examples"
        ATTRIBUTES = "attributes"
        KEYWORD_ARGS = "keyword_args"

    def __init__(self, section_type: str, value: Any) -> None:
        """
        Initialize the object.

        Arguments:
            section_type: The type of the section, from the [`Type`][pytkdocs.parsers.docstrings.base.Section.Type] enum.
            value: The section value.
        """
        self.type = section_type
        self.value = value

    def __str__(self):
        return self.type

    def __repr__(self):
        return f"<Section(type={self.type!r})>"
Type ¤

The possible section types.

Source code in pytkdocs/parsers/docstrings/base.py
class Type:
    """The possible section types."""

    MARKDOWN = "markdown"
    PARAMETERS = "parameters"
    EXCEPTIONS = "exceptions"
    RETURN = "return"
    YIELD = "yield"
    EXAMPLES = "examples"
    ATTRIBUTES = "attributes"
    KEYWORD_ARGS = "keyword_args"
__init__(self, section_type, value) special ¤

Initialize the object.

Parameters:

Name Type Description Default
section_type str

The type of the section, from the Type enum.

required
value Any

The section value.

required
Source code in pytkdocs/parsers/docstrings/base.py
def __init__(self, section_type: str, value: Any) -> None:
    """
    Initialize the object.

    Arguments:
        section_type: The type of the section, from the [`Type`][pytkdocs.parsers.docstrings.base.Section.Type] enum.
        value: The section value.
    """
    self.type = section_type
    self.value = value

google ¤

This module defines functions and classes to parse docstrings into structured data.

RE_DOCTEST_BLANKLINE: Pattern ¤

Regular expression to match lines of the form <BLANKLINE>.

RE_DOCTEST_FLAGS: Pattern ¤

Regular expression to match lines containing doctest flags of the form # doctest: +FLAG.

RE_GOOGLE_STYLE_ADMONITION: Pattern ¤

Regular expressions to match lines starting admonitions, of the form TYPE: [TITLE].

Google (Parser) ¤

A Google-style docstrings parser.

Source code in pytkdocs/parsers/docstrings/google.py
class Google(Parser):
    """A Google-style docstrings parser."""

    def __init__(self, replace_admonitions: bool = True, trim_doctest_flags: bool = True) -> None:
        """
        Initialize the object.

        Arguments:
            replace_admonitions: Whether to replace admonitions by their Markdown equivalent.
            trim_doctest_flags: Whether to remove doctest flags.
        """
        super().__init__()
        self.replace_admonitions = replace_admonitions
        self.trim_doctest_flags = trim_doctest_flags
        self.section_reader = {
            Section.Type.PARAMETERS: self.read_parameters_section,
            Section.Type.KEYWORD_ARGS: self.read_keyword_arguments_section,
            Section.Type.EXCEPTIONS: self.read_exceptions_section,
            Section.Type.EXAMPLES: self.read_examples_section,
            Section.Type.ATTRIBUTES: self.read_attributes_section,
            Section.Type.RETURN: self.read_return_section,
            Section.Type.YIELD: self.read_yield_section,
        }

    def parse_sections(self, docstring: str) -> List[Section]:  # noqa: D102
        if "signature" not in self.context:
            self.context["signature"] = getattr(self.context["obj"], "signature", None)
        if "annotation" not in self.context:
            self.context["annotation"] = getattr(self.context["obj"], "type", empty)
        if "attributes" not in self.context:
            self.context["attributes"] = {}

        sections = []
        current_section = []

        in_code_block = False

        lines = docstring.split("\n")
        i = 0

        while i < len(lines):
            line_lower = lines[i].lower()

            if in_code_block:
                if line_lower.lstrip(" ").startswith("```"):
                    in_code_block = False
                current_section.append(lines[i])

            elif line_lower in SECTIONS_TITLES:
                if current_section:
                    if any(current_section):
                        sections.append(Section(Section.Type.MARKDOWN, "\n".join(current_section)))
                    current_section = []
                section_reader = self.section_reader[SECTIONS_TITLES[line_lower]]
                section, i = section_reader(lines, i + 1)
                if section:
                    sections.append(section)

            elif line_lower.lstrip(" ").startswith("```"):
                in_code_block = True
                current_section.append(lines[i])

            else:
                if self.replace_admonitions and not in_code_block and i + 1 < len(lines):
                    match = RE_GOOGLE_STYLE_ADMONITION.match(lines[i])
                    if match:
                        groups = match.groupdict()
                        indent = groups["indent"]
                        if lines[i + 1].startswith(indent + " " * 4):
                            lines[i] = f"{indent}!!! {groups['type'].lower()}"
                            if groups["title"]:
                                lines[i] += f' "{groups["title"]}"'
                current_section.append(lines[i])

            i += 1

        if current_section:
            sections.append(Section(Section.Type.MARKDOWN, "\n".join(current_section)))

        return sections

    def read_block_items(self, lines: List[str], start_index: int) -> Tuple[List[str], int]:
        """
        Parse an indented block as a list of items.

        The first indentation level is used as a reference to determine if the next lines are new items
        or continuation lines.

        Arguments:
            lines: The block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing the list of concatenated lines and the index at which to continue parsing.
        """
        if start_index >= len(lines):
            return [], start_index

        i = start_index
        items: List[str] = []

        # skip first empty lines
        while is_empty_line(lines[i]):
            i += 1

        # get initial indent
        indent = len(lines[i]) - len(lines[i].lstrip())

        if indent == 0:
            # first non-empty line was not indented, abort
            return [], i - 1

        # start processing first item
        current_item = [lines[i][indent:]]
        i += 1

        # loop on next lines
        while i < len(lines):
            line = lines[i]

            if line.startswith(indent * 2 * " "):
                # continuation line
                current_item.append(line[indent * 2 :])

            elif line.startswith((indent + 1) * " "):
                # indent between initial and continuation: append but add error
                cont_indent = len(line) - len(line.lstrip())
                current_item.append(line[cont_indent:])
                self.error(
                    f"Confusing indentation for continuation line {i+1} in docstring, "
                    f"should be {indent} * 2 = {indent*2} spaces, not {cont_indent}"
                )

            elif line.startswith(indent * " "):
                # indent equal to initial one: new item
                items.append("\n".join(current_item))
                current_item = [line[indent:]]

            elif is_empty_line(line):
                # empty line: preserve it in the current item
                current_item.append("")

            else:
                # indent lower than initial one: end of section
                break

            i += 1

        if current_item:
            items.append("\n".join(current_item).rstrip("\n"))

        return items, i - 1

    def read_block(self, lines: List[str], start_index: int) -> Tuple[str, int]:
        """
        Parse an indented block.

        Arguments:
            lines: The block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing the list of lines and the index at which to continue parsing.
        """
        if start_index >= len(lines):
            return "", start_index

        i = start_index
        block: List[str] = []

        # skip first empty lines
        while is_empty_line(lines[i]):
            i += 1

        # get initial indent
        indent = len(lines[i]) - len(lines[i].lstrip())

        if indent == 0:
            # first non-empty line was not indented, abort
            return "", i - 1

        # start processing first item
        block.append(lines[i].lstrip())
        i += 1

        # loop on next lines
        while i < len(lines) and (lines[i].startswith(indent * " ") or is_empty_line(lines[i])):
            block.append(lines[i][indent:])
            i += 1

        return "\n".join(block).rstrip("\n"), i - 1

    def _parse_parameters_section(self, lines: List[str], start_index: int) -> Tuple[List[Parameter], int]:
        """
        Parse a "parameters" or "keyword args" section.

        Arguments:
            lines: The parameters block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        parameters = []
        type_: Any
        block, i = self.read_block_items(lines, start_index)

        for param_line in block:

            # Check that there is an annotation in the docstring
            try:
                name_with_type, description = param_line.split(":", 1)
            except ValueError:
                self.error(f"Failed to get 'name: description' pair from '{param_line}'")
                continue

            # Setting defaults
            default = empty
            annotation = empty
            kind = None
            # Can only get description from docstring - keep if no type was given
            description = description.lstrip()

            # If we have managed to find a type in the docstring use this
            if " " in name_with_type:
                name, type_ = name_with_type.split(" ", 1)
                annotation = type_.strip("()")
                if annotation.endswith(", optional"):  # type: ignore
                    annotation = annotation[:-10]  # type: ignore
            # Otherwise try to use the signature as `annotation` would still be empty
            else:
                name = name_with_type

            # Check in the signature to get extra details
            try:
                signature_param = self.context["signature"].parameters[name.lstrip("*")]
            except (AttributeError, KeyError):
                if annotation is empty:
                    self.error(f"No type annotation for parameter '{name}'")
            else:
                if annotation is empty:
                    annotation = signature_param.annotation
                # If signature_param.X are empty it doesnt matter as defaults are empty anyway
                default = signature_param.default
                kind = signature_param.kind

            parameters.append(
                Parameter(name=name, annotation=annotation, description=description, default=default, kind=kind)
            )

        return parameters, i

    def read_parameters_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse a "parameters" section.

        Arguments:
            lines: The parameters block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        parameters, i = self._parse_parameters_section(lines, start_index)

        if parameters:
            return Section(Section.Type.PARAMETERS, parameters), i

        self.error(f"Empty parameters section at line {start_index}")
        return None, i

    def read_keyword_arguments_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse a "keyword arguments" section.

        Arguments:
            lines: The parameters block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        parameters, i = self._parse_parameters_section(lines, start_index)
        for parameter in parameters:
            parameter.kind = inspect.Parameter.KEYWORD_ONLY

        if parameters:
            return Section(Section.Type.KEYWORD_ARGS, parameters), i

        self.error(f"Empty keyword arguments section at line {start_index}")
        return None, i

    def read_attributes_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse an "attributes" section.

        Arguments:
            lines: The parameters block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        attributes = []
        block, i = self.read_block_items(lines, start_index)

        for attr_line in block:
            try:
                name_with_type, description = attr_line.split(":", 1)
            except ValueError:
                self.error(f"Failed to get 'name: description' pair from '{attr_line}'")
                continue

            description = description.lstrip()

            if " " in name_with_type:
                name, annotation = name_with_type.split(" ", 1)
                annotation = annotation.strip("()")
                if annotation.endswith(", optional"):
                    annotation = annotation[:-10]
            else:
                name = name_with_type
                annotation = self.context["attributes"].get(name, {}).get("annotation", empty)

            attributes.append(Attribute(name=name, annotation=annotation, description=description))

        if attributes:
            return Section(Section.Type.ATTRIBUTES, attributes), i

        self.error(f"Empty attributes section at line {start_index}")
        return None, i

    def read_exceptions_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse an "exceptions" section.

        Arguments:
            lines: The exceptions block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        exceptions = []
        block, i = self.read_block_items(lines, start_index)

        for exception_line in block:
            try:
                annotation, description = exception_line.split(": ", 1)
            except ValueError:
                self.error(f"Failed to get 'exception: description' pair from '{exception_line}'")
            else:
                exceptions.append(AnnotatedObject(annotation, description.lstrip(" ")))

        if exceptions:
            return Section(Section.Type.EXCEPTIONS, exceptions), i

        self.error(f"Empty exceptions section at line {start_index}")
        return None, i

    def read_return_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse an "returns" section.

        Arguments:
            lines: The return block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        text, i = self.read_block(lines, start_index)

        # Early exit if there is no text in the return section
        if not text:
            self.error(f"Empty return section at line {start_index}")
            return None, i

        # First try to get the annotation and description from the docstring
        try:
            type_, text = text.split(":", 1)
        except ValueError:
            description = text
            annotation = self.context["annotation"]
            # If there was no annotation in the docstring then move to signature
            if annotation is empty and self.context["signature"]:
                annotation = self.context["signature"].return_annotation
        else:
            annotation = type_.lstrip()
            description = text.lstrip()

        # There was no type in the docstring and no annotation
        if annotation is empty:
            self.error("No return type/annotation in docstring/signature")

        return Section(Section.Type.RETURN, AnnotatedObject(annotation, description)), i

    def read_yield_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse a "yields" section.

        Arguments:
            lines: The return block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        text, i = self.read_block(lines, start_index)

        # Early exit if there is no text in the yield section
        if not text:
            self.error(f"Empty yield section at line {start_index}")
            return None, i

        # First try to get the annotation and description from the docstring
        try:
            type_, text = text.split(":", 1)
        except ValueError:
            description = text
            annotation = self.context["annotation"]
            # If there was no annotation in the docstring then move to signature
            if annotation is empty and self.context["signature"]:
                annotation = self.context["signature"].return_annotation
        else:
            annotation = type_.lstrip()
            description = text.lstrip()

        # There was no type in the docstring and no annotation
        if annotation is empty:
            self.error("No yield type/annotation in docstring/signature")

        return Section(Section.Type.YIELD, AnnotatedObject(annotation, description)), i

    def read_examples_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse an "examples" section.

        Arguments:
            lines: The examples block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        text, i = self.read_block(lines, start_index)

        sub_sections = []
        in_code_example = False
        in_code_block = False
        current_text: List[str] = []
        current_example: List[str] = []

        for line in text.split("\n"):
            if is_empty_line(line):
                if in_code_example:
                    if current_example:
                        sub_sections.append((Section.Type.EXAMPLES, "\n".join(current_example)))
                        current_example = []
                    in_code_example = False
                else:
                    current_text.append(line)

            elif in_code_example:
                if self.trim_doctest_flags:
                    line = RE_DOCTEST_FLAGS.sub("", line)
                    line = RE_DOCTEST_BLANKLINE.sub("", line)
                current_example.append(line)

            elif line.startswith("```"):
                in_code_block = not in_code_block
                current_text.append(line)

            elif in_code_block:
                current_text.append(line)

            elif line.startswith(">>>"):
                if current_text:
                    sub_sections.append((Section.Type.MARKDOWN, "\n".join(current_text)))
                    current_text = []
                in_code_example = True

                if self.trim_doctest_flags:
                    line = RE_DOCTEST_FLAGS.sub("", line)
                current_example.append(line)

            else:
                current_text.append(line)

        if current_text:
            sub_sections.append((Section.Type.MARKDOWN, "\n".join(current_text)))
        elif current_example:
            sub_sections.append((Section.Type.EXAMPLES, "\n".join(current_example)))

        if sub_sections:
            return Section(Section.Type.EXAMPLES, sub_sections), i

        self.error(f"Empty examples section at line {start_index}")
        return None, i
__init__(self, replace_admonitions=True, trim_doctest_flags=True) special ¤

Initialize the object.

Parameters:

Name Type Description Default
replace_admonitions bool

Whether to replace admonitions by their Markdown equivalent.

True
trim_doctest_flags bool

Whether to remove doctest flags.

True
Source code in pytkdocs/parsers/docstrings/google.py
def __init__(self, replace_admonitions: bool = True, trim_doctest_flags: bool = True) -> None:
    """
    Initialize the object.

    Arguments:
        replace_admonitions: Whether to replace admonitions by their Markdown equivalent.
        trim_doctest_flags: Whether to remove doctest flags.
    """
    super().__init__()
    self.replace_admonitions = replace_admonitions
    self.trim_doctest_flags = trim_doctest_flags
    self.section_reader = {
        Section.Type.PARAMETERS: self.read_parameters_section,
        Section.Type.KEYWORD_ARGS: self.read_keyword_arguments_section,
        Section.Type.EXCEPTIONS: self.read_exceptions_section,
        Section.Type.EXAMPLES: self.read_examples_section,
        Section.Type.ATTRIBUTES: self.read_attributes_section,
        Section.Type.RETURN: self.read_return_section,
        Section.Type.YIELD: self.read_yield_section,
    }
parse_sections(self, docstring) ¤

Parse a docstring as a list of sections.

Parameters:

Name Type Description Default
docstring str

The docstring to parse.

required

Returns:

Type Description
List[pytkdocs.parsers.docstrings.base.Section]

A list of Sections.

Source code in pytkdocs/parsers/docstrings/google.py
def parse_sections(self, docstring: str) -> List[Section]:  # noqa: D102
    if "signature" not in self.context:
        self.context["signature"] = getattr(self.context["obj"], "signature", None)
    if "annotation" not in self.context:
        self.context["annotation"] = getattr(self.context["obj"], "type", empty)
    if "attributes" not in self.context:
        self.context["attributes"] = {}

    sections = []
    current_section = []

    in_code_block = False

    lines = docstring.split("\n")
    i = 0

    while i < len(lines):
        line_lower = lines[i].lower()

        if in_code_block:
            if line_lower.lstrip(" ").startswith("```"):
                in_code_block = False
            current_section.append(lines[i])

        elif line_lower in SECTIONS_TITLES:
            if current_section:
                if any(current_section):
                    sections.append(Section(Section.Type.MARKDOWN, "\n".join(current_section)))
                current_section = []
            section_reader = self.section_reader[SECTIONS_TITLES[line_lower]]
            section, i = section_reader(lines, i + 1)
            if section:
                sections.append(section)

        elif line_lower.lstrip(" ").startswith("```"):
            in_code_block = True
            current_section.append(lines[i])

        else:
            if self.replace_admonitions and not in_code_block and i + 1 < len(lines):
                match = RE_GOOGLE_STYLE_ADMONITION.match(lines[i])
                if match:
                    groups = match.groupdict()
                    indent = groups["indent"]
                    if lines[i + 1].startswith(indent + " " * 4):
                        lines[i] = f"{indent}!!! {groups['type'].lower()}"
                        if groups["title"]:
                            lines[i] += f' "{groups["title"]}"'
            current_section.append(lines[i])

        i += 1

    if current_section:
        sections.append(Section(Section.Type.MARKDOWN, "\n".join(current_section)))

    return sections
read_attributes_section(self, lines, start_index) ¤

Parse an "attributes" section.

Parameters:

Name Type Description Default
lines List[str]

The parameters block lines.

required
start_index int

The line number to start at.

required

Returns:

Type Description
Tuple[Optional[pytkdocs.parsers.docstrings.base.Section], int]

A tuple containing a Section (or None) and the index at which to continue parsing.

Source code in pytkdocs/parsers/docstrings/google.py
def read_attributes_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
    """
    Parse an "attributes" section.

    Arguments:
        lines: The parameters block lines.
        start_index: The line number to start at.

    Returns:
        A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
    """
    attributes = []
    block, i = self.read_block_items(lines, start_index)

    for attr_line in block:
        try:
            name_with_type, description = attr_line.split(":", 1)
        except ValueError:
            self.error(f"Failed to get 'name: description' pair from '{attr_line}'")
            continue

        description = description.lstrip()

        if " " in name_with_type:
            name, annotation = name_with_type.split(" ", 1)
            annotation = annotation.strip("()")
            if annotation.endswith(", optional"):
                annotation = annotation[:-10]
        else:
            name = name_with_type
            annotation = self.context["attributes"].get(name, {}).get("annotation", empty)

        attributes.append(Attribute(name=name, annotation=annotation, description=description))

    if attributes:
        return Section(Section.Type.ATTRIBUTES, attributes), i

    self.error(f"Empty attributes section at line {start_index}")
    return None, i
read_block(self, lines, start_index) ¤

Parse an indented block.

Parameters:

Name Type Description Default
lines List[str]

The block lines.

required
start_index int

The line number to start at.

required

Returns:

Type Description
Tuple[str, int]

A tuple containing the list of lines and the index at which to continue parsing.

Source code in pytkdocs/parsers/docstrings/google.py
def read_block(self, lines: List[str], start_index: int) -> Tuple[str, int]:
    """
    Parse an indented block.

    Arguments:
        lines: The block lines.
        start_index: The line number to start at.

    Returns:
        A tuple containing the list of lines and the index at which to continue parsing.
    """
    if start_index >= len(lines):
        return "", start_index

    i = start_index
    block: List[str] = []

    # skip first empty lines
    while is_empty_line(lines[i]):
        i += 1

    # get initial indent
    indent = len(lines[i]) - len(lines[i].lstrip())

    if indent == 0:
        # first non-empty line was not indented, abort
        return "", i - 1

    # start processing first item
    block.append(lines[i].lstrip())
    i += 1

    # loop on next lines
    while i < len(lines) and (lines[i].startswith(indent * " ") or is_empty_line(lines[i])):
        block.append(lines[i][indent:])
        i += 1

    return "\n".join(block).rstrip("\n"), i - 1
read_block_items(self, lines, start_index) ¤

Parse an indented block as a list of items.

The first indentation level is used as a reference to determine if the next lines are new items or continuation lines.

Parameters:

Name Type Description Default
lines List[str]

The block lines.

required
start_index int

The line number to start at.

required

Returns:

Type Description
Tuple[List[str], int]

A tuple containing the list of concatenated lines and the index at which to continue parsing.

Source code in pytkdocs/parsers/docstrings/google.py
def read_block_items(self, lines: List[str], start_index: int) -> Tuple[List[str], int]:
    """
    Parse an indented block as a list of items.

    The first indentation level is used as a reference to determine if the next lines are new items
    or continuation lines.

    Arguments:
        lines: The block lines.
        start_index: The line number to start at.

    Returns:
        A tuple containing the list of concatenated lines and the index at which to continue parsing.
    """
    if start_index >= len(lines):
        return [], start_index

    i = start_index
    items: List[str] = []

    # skip first empty lines
    while is_empty_line(lines[i]):
        i += 1

    # get initial indent
    indent = len(lines[i]) - len(lines[i].lstrip())

    if indent == 0:
        # first non-empty line was not indented, abort
        return [], i - 1

    # start processing first item
    current_item = [lines[i][indent:]]
    i += 1

    # loop on next lines
    while i < len(lines):
        line = lines[i]

        if line.startswith(indent * 2 * " "):
            # continuation line
            current_item.append(line[indent * 2 :])

        elif line.startswith((indent + 1) * " "):
            # indent between initial and continuation: append but add error
            cont_indent = len(line) - len(line.lstrip())
            current_item.append(line[cont_indent:])
            self.error(
                f"Confusing indentation for continuation line {i+1} in docstring, "
                f"should be {indent} * 2 = {indent*2} spaces, not {cont_indent}"
            )

        elif line.startswith(indent * " "):
            # indent equal to initial one: new item
            items.append("\n".join(current_item))
            current_item = [line[indent:]]

        elif is_empty_line(line):
            # empty line: preserve it in the current item
            current_item.append("")

        else:
            # indent lower than initial one: end of section
            break

        i += 1

    if current_item:
        items.append("\n".join(current_item).rstrip("\n"))

    return items, i - 1
read_examples_section(self, lines, start_index) ¤

Parse an "examples" section.

Parameters:

Name Type Description Default
lines List[str]

The examples block lines.

required
start_index int

The line number to start at.

required

Returns:

Type Description
Tuple[Optional[pytkdocs.parsers.docstrings.base.Section], int]

A tuple containing a Section (or None) and the index at which to continue parsing.

Source code in pytkdocs/parsers/docstrings/google.py
def read_examples_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
    """
    Parse an "examples" section.

    Arguments:
        lines: The examples block lines.
        start_index: The line number to start at.

    Returns:
        A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
    """
    text, i = self.read_block(lines, start_index)

    sub_sections = []
    in_code_example = False
    in_code_block = False
    current_text: List[str] = []
    current_example: List[str] = []

    for line in text.split("\n"):
        if is_empty_line(line):
            if in_code_example:
                if current_example:
                    sub_sections.append((Section.Type.EXAMPLES, "\n".join(current_example)))
                    current_example = []
                in_code_example = False
            else:
                current_text.append(line)

        elif in_code_example:
            if self.trim_doctest_flags:
                line = RE_DOCTEST_FLAGS.sub("", line)
                line = RE_DOCTEST_BLANKLINE.sub("", line)
            current_example.append(line)

        elif line.startswith("```"):
            in_code_block = not in_code_block
            current_text.append(line)

        elif in_code_block:
            current_text.append(line)

        elif line.startswith(">>>"):
            if current_text:
                sub_sections.append((Section.Type.MARKDOWN, "\n".join(current_text)))
                current_text = []
            in_code_example = True

            if self.trim_doctest_flags:
                line = RE_DOCTEST_FLAGS.sub("", line)
            current_example.append(line)

        else:
            current_text.append(line)

    if current_text:
        sub_sections.append((Section.Type.MARKDOWN, "\n".join(current_text)))
    elif current_example:
        sub_sections.append((Section.Type.EXAMPLES, "\n".join(current_example)))

    if sub_sections:
        return Section(Section.Type.EXAMPLES, sub_sections), i

    self.error(f"Empty examples section at line {start_index}")
    return None, i
read_exceptions_section(self, lines, start_index) ¤

Parse an "exceptions" section.

Parameters:

Name Type Description Default
lines List[str]

The exceptions block lines.

required
start_index int

The line number to start at.

required

Returns:

Type Description
Tuple[Optional[pytkdocs.parsers.docstrings.base.Section], int]

A tuple containing a Section (or None) and the index at which to continue parsing.

Source code in pytkdocs/parsers/docstrings/google.py
def read_exceptions_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
    """
    Parse an "exceptions" section.

    Arguments:
        lines: The exceptions block lines.
        start_index: The line number to start at.

    Returns:
        A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
    """
    exceptions = []
    block, i = self.read_block_items(lines, start_index)

    for exception_line in block:
        try:
            annotation, description = exception_line.split(": ", 1)
        except ValueError:
            self.error(f"Failed to get 'exception: description' pair from '{exception_line}'")
        else:
            exceptions.append(AnnotatedObject(annotation, description.lstrip(" ")))

    if exceptions:
        return Section(Section.Type.EXCEPTIONS, exceptions), i

    self.error(f"Empty exceptions section at line {start_index}")
    return None, i
read_keyword_arguments_section(self, lines, start_index) ¤

Parse a "keyword arguments" section.

Parameters:

Name Type Description Default
lines List[str]

The parameters block lines.

required
start_index int

The line number to start at.

required

Returns:

Type Description
Tuple[Optional[pytkdocs.parsers.docstrings.base.Section], int]

A tuple containing a Section (or None) and the index at which to continue parsing.

Source code in pytkdocs/parsers/docstrings/google.py
def read_keyword_arguments_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
    """
    Parse a "keyword arguments" section.

    Arguments:
        lines: The parameters block lines.
        start_index: The line number to start at.

    Returns:
        A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
    """
    parameters, i = self._parse_parameters_section(lines, start_index)
    for parameter in parameters:
        parameter.kind = inspect.Parameter.KEYWORD_ONLY

    if parameters:
        return Section(Section.Type.KEYWORD_ARGS, parameters), i

    self.error(f"Empty keyword arguments section at line {start_index}")
    return None, i
read_parameters_section(self, lines, start_index) ¤

Parse a "parameters" section.

Parameters:

Name Type Description Default
lines List[str]

The parameters block lines.

required
start_index int

The line number to start at.

required

Returns:

Type Description
Tuple[Optional[pytkdocs.parsers.docstrings.base.Section], int]

A tuple containing a Section (or None) and the index at which to continue parsing.

Source code in pytkdocs/parsers/docstrings/google.py
def read_parameters_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
    """
    Parse a "parameters" section.

    Arguments:
        lines: The parameters block lines.
        start_index: The line number to start at.

    Returns:
        A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
    """
    parameters, i = self._parse_parameters_section(lines, start_index)

    if parameters:
        return Section(Section.Type.PARAMETERS, parameters), i

    self.error(f"Empty parameters section at line {start_index}")
    return None, i
read_return_section(self, lines, start_index) ¤

Parse an "returns" section.

Parameters:

Name Type Description Default
lines List[str]

The return block lines.

required
start_index int

The line number to start at.

required

Returns:

Type Description
Tuple[Optional[pytkdocs.parsers.docstrings.base.Section], int]

A tuple containing a Section (or None) and the index at which to continue parsing.

Source code in pytkdocs/parsers/docstrings/google.py
def read_return_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
    """
    Parse an "returns" section.

    Arguments:
        lines: The return block lines.
        start_index: The line number to start at.

    Returns:
        A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
    """
    text, i = self.read_block(lines, start_index)

    # Early exit if there is no text in the return section
    if not text:
        self.error(f"Empty return section at line {start_index}")
        return None, i

    # First try to get the annotation and description from the docstring
    try:
        type_, text = text.split(":", 1)
    except ValueError:
        description = text
        annotation = self.context["annotation"]
        # If there was no annotation in the docstring then move to signature
        if annotation is empty and self.context["signature"]:
            annotation = self.context["signature"].return_annotation
    else:
        annotation = type_.lstrip()
        description = text.lstrip()

    # There was no type in the docstring and no annotation
    if annotation is empty:
        self.error("No return type/annotation in docstring/signature")

    return Section(Section.Type.RETURN, AnnotatedObject(annotation, description)), i
read_yield_section(self, lines, start_index) ¤

Parse a "yields" section.

Parameters:

Name Type Description Default
lines List[str]

The return block lines.

required
start_index int

The line number to start at.

required

Returns:

Type Description
Tuple[Optional[pytkdocs.parsers.docstrings.base.Section], int]

A tuple containing a Section (or None) and the index at which to continue parsing.

Source code in pytkdocs/parsers/docstrings/google.py
def read_yield_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
    """
    Parse a "yields" section.

    Arguments:
        lines: The return block lines.
        start_index: The line number to start at.

    Returns:
        A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
    """
    text, i = self.read_block(lines, start_index)

    # Early exit if there is no text in the yield section
    if not text:
        self.error(f"Empty yield section at line {start_index}")
        return None, i

    # First try to get the annotation and description from the docstring
    try:
        type_, text = text.split(":", 1)
    except ValueError:
        description = text
        annotation = self.context["annotation"]
        # If there was no annotation in the docstring then move to signature
        if annotation is empty and self.context["signature"]:
            annotation = self.context["signature"].return_annotation
    else:
        annotation = type_.lstrip()
        description = text.lstrip()

    # There was no type in the docstring and no annotation
    if annotation is empty:
        self.error("No yield type/annotation in docstring/signature")

    return Section(Section.Type.YIELD, AnnotatedObject(annotation, description)), i
is_empty_line(line) ¤

Tell if a line is empty.

Parameters:

Name Type Description Default
line

The line to check.

required

Returns:

Type Description
bool

True if the line is empty or composed of blanks only, False otherwise.

Source code in pytkdocs/parsers/docstrings/google.py
def is_empty_line(line) -> bool:
    """
    Tell if a line is empty.

    Arguments:
        line: The line to check.

    Returns:
        True if the line is empty or composed of blanks only, False otherwise.
    """
    return not line.strip()

markdown ¤

This module defines functions and classes to parse docstrings into structured data.

Markdown (Parser) ¤

A Markdown docstrings parser.

Source code in pytkdocs/parsers/docstrings/markdown.py
class Markdown(Parser):
    """A Markdown docstrings parser."""

    def parse_sections(self, docstring: str) -> List[Section]:  # noqa: D102
        return [Section(Section.Type.MARKDOWN, docstring)]
parse_sections(self, docstring) ¤

Parse a docstring as a list of sections.

Parameters:

Name Type Description Default
docstring str

The docstring to parse.

required

Returns:

Type Description
List[pytkdocs.parsers.docstrings.base.Section]

A list of Sections.

Source code in pytkdocs/parsers/docstrings/markdown.py
def parse_sections(self, docstring: str) -> List[Section]:  # noqa: D102
    return [Section(Section.Type.MARKDOWN, docstring)]

numpy ¤

This module defines functions and classes to parse docstrings into structured data.

RE_DOCTEST_BLANKLINE: Pattern ¤

Regular expression to match lines of the form <BLANKLINE>.

RE_DOCTEST_FLAGS: Pattern ¤

Regular expression to match lines containing doctest flags of the form # doctest: +FLAG.

Numpy (Parser) ¤

A Numpy-style docstrings parser.

Source code in pytkdocs/parsers/docstrings/numpy.py
class Numpy(Parser):
    """A Numpy-style docstrings parser."""

    def __init__(self, trim_doctest_flags: bool = True) -> None:
        """
        Initialize the objects.

        Arguments:
            trim_doctest_flags: Whether to remove doctest flags.
        """
        super().__init__()
        self.trim_doctest_flags = trim_doctest_flags
        self.section_reader = {
            Section.Type.PARAMETERS: self.read_parameters_section,
            Section.Type.EXCEPTIONS: self.read_exceptions_section,
            Section.Type.EXAMPLES: self.read_examples_section,
            Section.Type.ATTRIBUTES: self.read_attributes_section,
            Section.Type.RETURN: self.read_return_section,
        }

    def parse_sections(self, docstring: str) -> List[Section]:  # noqa: D102
        if "signature" not in self.context:
            self.context["signature"] = getattr(self.context["obj"], "signature", None)
        if "annotation" not in self.context:
            self.context["annotation"] = getattr(self.context["obj"], "type", empty)
        if "attributes" not in self.context:
            self.context["attributes"] = {}

        docstring_obj = parse(docstring)
        description_all = (
            none_str_cast(docstring_obj.short_description) + "\n\n" + none_str_cast(docstring_obj.long_description)
        ).strip()
        sections = [Section(Section.Type.MARKDOWN, description_all)] if description_all else []
        sections_other = [
            reader(docstring_obj)  # type: ignore
            if sec == Section.Type.RETURN
            else reader(docstring, docstring_obj)  # type: ignore
            for (sec, reader) in self.section_reader.items()
        ]
        sections.extend([sec for sec in sections_other if sec])
        return sections

    def read_parameters_section(
        self,
        docstring: str,
        docstring_obj: Docstring,
    ) -> Optional[Section]:
        """
        Parse a "parameters" section.

        Arguments:
            docstring: The raw docstring.
            docstring_obj: Docstring object parsed by docstring_parser.

        Returns:
            A `Section` object (or `None` if section is empty).
        """
        parameters = []

        docstring_params = [p for p in docstring_obj.params if p.args[0] == "param"]

        for param in docstring_params:
            name = param.arg_name
            kind = None
            type_name = param.type_name
            default = param.default or empty
            try:
                signature_param = self.context["signature"].parameters[name.lstrip("*")]
            except (AttributeError, KeyError):
                self.error(f"No type annotation for parameter '{name}'")
            else:
                if signature_param.annotation is not empty:
                    type_name = signature_param.annotation
                if signature_param.default is not empty:
                    default = signature_param.default
                kind = signature_param.kind

            description = param.description or ""
            if not description:
                self.error(f"No description for parameter '{name}'")

            parameters.append(
                Parameter(
                    name=param.arg_name,
                    annotation=type_name,
                    description=description,
                    default=default,
                    kind=kind,
                )
            )

        if parameters:
            return Section(Section.Type.PARAMETERS, parameters)
        if re.search("Parameters\n", docstring):
            self.error("Empty parameter section")
        return None

    def read_attributes_section(
        self,
        docstring: str,
        docstring_obj: Docstring,
    ) -> Optional[Section]:
        """
        Parse an "attributes" section.

        Arguments:
            docstring: The raw docstring.
            docstring_obj: Docstring object parsed by docstring_parser.

        Returns:
            A `Section` object (or `None` if section is empty).
        """
        attributes = []
        docstring_attributes = [p for p in docstring_obj.params if p.args[0] == "attribute"]

        for attr in docstring_attributes:
            description = attr.description or ""
            if not description:
                self.error(f"No description for attribute '{attr.arg_name}'")
            attributes.append(
                Attribute(
                    name=attr.arg_name,
                    annotation=attr.type_name,
                    description=attr.description,
                )
            )

        if attributes:
            return Section(Section.Type.ATTRIBUTES, attributes)
        if re.search("Attributes\n", docstring):
            self.error("Empty attributes section")
        return None

    def read_exceptions_section(
        self,
        docstring: str,
        docstring_obj: Docstring,
    ) -> Optional[Section]:
        """
        Parse an "exceptions" section.

        Arguments:
            docstring: The raw docstring.
            docstring_obj: Docstring object parsed by docstring_parser.

        Returns:
            A `Section` object (or `None` if section is empty).
        """
        exceptions = []
        except_obj = docstring_obj.raises

        for exception in except_obj:
            description = exception.description or ""
            if not description:
                self.error(f"No description for exception '{exception.type_name}'")
            exceptions.append(AnnotatedObject(exception.type_name, description))

        if exceptions:
            return Section(Section.Type.EXCEPTIONS, exceptions)
        if re.search("Raises\n", docstring):
            self.error("Empty exceptions section")
        return None

    def read_return_section(
        self,
        docstring_obj: Docstring,
    ) -> Optional[Section]:
        """
        Parse a "returns" section.

        Arguments:
            docstring_obj: Docstring object parsed by docstring_parser.

        Returns:
            A `Section` object (or `None` if section is empty).
        """
        if docstring_obj.returns:
            return_obj = docstring_obj.returns

            if return_obj.description:
                description = return_obj.description
            else:
                self.error("Empty return description")
                description = ""

            if self.context["signature"]:
                annotation = self.context["signature"].return_annotation
            else:
                annotation = self.context["annotation"]

            if annotation is empty and return_obj.type_name:
                annotation = return_obj.type_name

            if not annotation:
                self.error("No return type annotation")
                annotation = ""

            if annotation or description:
                return Section(Section.Type.RETURN, AnnotatedObject(annotation, description))

        return None

    def read_examples_section(
        self,
        docstring: str,
        docstring_obj: Docstring,
    ) -> Optional[Section]:
        """
        Parse an "examples" section.

        Arguments:
            docstring: The raw docstring.
            docstring_obj: Docstring object parsed by docstring_parser.

        Returns:
            A `Section` object (or `None` if section is empty).
        """
        text = next(
            (
                meta.description
                for meta in docstring_obj.meta
                if isinstance(meta, DocstringMeta) and meta.args[0] == "examples"
            ),
            "",
        )

        sub_sections = []
        in_code_example = False
        in_code_block = False
        current_text: List[str] = []
        current_example: List[str] = []

        if text:
            for line in text.split("\n"):
                if is_empty_line(line):
                    if in_code_example:
                        if current_example:
                            sub_sections.append((Section.Type.EXAMPLES, "\n".join(current_example)))
                            current_example = []
                        in_code_example = False
                    else:
                        current_text.append(line)

                elif in_code_example:
                    if self.trim_doctest_flags:
                        line = RE_DOCTEST_FLAGS.sub("", line)
                        line = RE_DOCTEST_BLANKLINE.sub("", line)
                    current_example.append(line)

                elif line.startswith("```"):
                    in_code_block = not in_code_block
                    current_text.append(line)

                elif in_code_block:
                    current_text.append(line)

                elif line.startswith(">>>"):
                    if current_text:
                        sub_sections.append((Section.Type.MARKDOWN, "\n".join(current_text)))
                        current_text = []
                    in_code_example = True

                    if self.trim_doctest_flags:
                        line = RE_DOCTEST_FLAGS.sub("", line)
                    current_example.append(line)
                else:
                    current_text.append(line)

        if current_text:
            sub_sections.append((Section.Type.MARKDOWN, "\n".join(current_text)))
        elif current_example:
            sub_sections.append((Section.Type.EXAMPLES, "\n".join(current_example)))

        if sub_sections:
            return Section(Section.Type.EXAMPLES, sub_sections)

        if re.search("Examples\n", docstring):
            self.error("Empty examples section")
        return None
__init__(self, trim_doctest_flags=True) special ¤

Initialize the objects.

Parameters:

Name Type Description Default
trim_doctest_flags bool

Whether to remove doctest flags.

True
Source code in pytkdocs/parsers/docstrings/numpy.py
def __init__(self, trim_doctest_flags: bool = True) -> None:
    """
    Initialize the objects.

    Arguments:
        trim_doctest_flags: Whether to remove doctest flags.
    """
    super().__init__()
    self.trim_doctest_flags = trim_doctest_flags
    self.section_reader = {
        Section.Type.PARAMETERS: self.read_parameters_section,
        Section.Type.EXCEPTIONS: self.read_exceptions_section,
        Section.Type.EXAMPLES: self.read_examples_section,
        Section.Type.ATTRIBUTES: self.read_attributes_section,
        Section.Type.RETURN: self.read_return_section,
    }
parse_sections(self, docstring) ¤

Parse a docstring as a list of sections.

Parameters:

Name Type Description Default
docstring str

The docstring to parse.

required

Returns:

Type Description
List[pytkdocs.parsers.docstrings.base.Section]

A list of Sections.

Source code in pytkdocs/parsers/docstrings/numpy.py
def parse_sections(self, docstring: str) -> List[Section]:  # noqa: D102
    if "signature" not in self.context:
        self.context["signature"] = getattr(self.context["obj"], "signature", None)
    if "annotation" not in self.context:
        self.context["annotation"] = getattr(self.context["obj"], "type", empty)
    if "attributes" not in self.context:
        self.context["attributes"] = {}

    docstring_obj = parse(docstring)
    description_all = (
        none_str_cast(docstring_obj.short_description) + "\n\n" + none_str_cast(docstring_obj.long_description)
    ).strip()
    sections = [Section(Section.Type.MARKDOWN, description_all)] if description_all else []
    sections_other = [
        reader(docstring_obj)  # type: ignore
        if sec == Section.Type.RETURN
        else reader(docstring, docstring_obj)  # type: ignore
        for (sec, reader) in self.section_reader.items()
    ]
    sections.extend([sec for sec in sections_other if sec])
    return sections
read_attributes_section(self, docstring, docstring_obj) ¤

Parse an "attributes" section.

Parameters:

Name Type Description Default
docstring str

The raw docstring.

required
docstring_obj Docstring

Docstring object parsed by docstring_parser.

required

Returns:

Type Description
Optional[pytkdocs.parsers.docstrings.base.Section]

A Section object (or None if section is empty).

Source code in pytkdocs/parsers/docstrings/numpy.py
def read_attributes_section(
    self,
    docstring: str,
    docstring_obj: Docstring,
) -> Optional[Section]:
    """
    Parse an "attributes" section.

    Arguments:
        docstring: The raw docstring.
        docstring_obj: Docstring object parsed by docstring_parser.

    Returns:
        A `Section` object (or `None` if section is empty).
    """
    attributes = []
    docstring_attributes = [p for p in docstring_obj.params if p.args[0] == "attribute"]

    for attr in docstring_attributes:
        description = attr.description or ""
        if not description:
            self.error(f"No description for attribute '{attr.arg_name}'")
        attributes.append(
            Attribute(
                name=attr.arg_name,
                annotation=attr.type_name,
                description=attr.description,
            )
        )

    if attributes:
        return Section(Section.Type.ATTRIBUTES, attributes)
    if re.search("Attributes\n", docstring):
        self.error("Empty attributes section")
    return None
read_examples_section(self, docstring, docstring_obj) ¤

Parse an "examples" section.

Parameters:

Name Type Description Default
docstring str

The raw docstring.

required
docstring_obj Docstring

Docstring object parsed by docstring_parser.

required

Returns:

Type Description
Optional[pytkdocs.parsers.docstrings.base.Section]

A Section object (or None if section is empty).

Source code in pytkdocs/parsers/docstrings/numpy.py
def read_examples_section(
    self,
    docstring: str,
    docstring_obj: Docstring,
) -> Optional[Section]:
    """
    Parse an "examples" section.

    Arguments:
        docstring: The raw docstring.
        docstring_obj: Docstring object parsed by docstring_parser.

    Returns:
        A `Section` object (or `None` if section is empty).
    """
    text = next(
        (
            meta.description
            for meta in docstring_obj.meta
            if isinstance(meta, DocstringMeta) and meta.args[0] == "examples"
        ),
        "",
    )

    sub_sections = []
    in_code_example = False
    in_code_block = False
    current_text: List[str] = []
    current_example: List[str] = []

    if text:
        for line in text.split("\n"):
            if is_empty_line(line):
                if in_code_example:
                    if current_example:
                        sub_sections.append((Section.Type.EXAMPLES, "\n".join(current_example)))
                        current_example = []
                    in_code_example = False
                else:
                    current_text.append(line)

            elif in_code_example:
                if self.trim_doctest_flags:
                    line = RE_DOCTEST_FLAGS.sub("", line)
                    line = RE_DOCTEST_BLANKLINE.sub("", line)
                current_example.append(line)

            elif line.startswith("```"):
                in_code_block = not in_code_block
                current_text.append(line)

            elif in_code_block:
                current_text.append(line)

            elif line.startswith(">>>"):
                if current_text:
                    sub_sections.append((Section.Type.MARKDOWN, "\n".join(current_text)))
                    current_text = []
                in_code_example = True

                if self.trim_doctest_flags:
                    line = RE_DOCTEST_FLAGS.sub("", line)
                current_example.append(line)
            else:
                current_text.append(line)

    if current_text:
        sub_sections.append((Section.Type.MARKDOWN, "\n".join(current_text)))
    elif current_example:
        sub_sections.append((Section.Type.EXAMPLES, "\n".join(current_example)))

    if sub_sections:
        return Section(Section.Type.EXAMPLES, sub_sections)

    if re.search("Examples\n", docstring):
        self.error("Empty examples section")
    return None
read_exceptions_section(self, docstring, docstring_obj) ¤

Parse an "exceptions" section.

Parameters:

Name Type Description Default
docstring str

The raw docstring.

required
docstring_obj Docstring

Docstring object parsed by docstring_parser.

required

Returns:

Type Description
Optional[pytkdocs.parsers.docstrings.base.Section]

A Section object (or None if section is empty).

Source code in pytkdocs/parsers/docstrings/numpy.py
def read_exceptions_section(
    self,
    docstring: str,
    docstring_obj: Docstring,
) -> Optional[Section]:
    """
    Parse an "exceptions" section.

    Arguments:
        docstring: The raw docstring.
        docstring_obj: Docstring object parsed by docstring_parser.

    Returns:
        A `Section` object (or `None` if section is empty).
    """
    exceptions = []
    except_obj = docstring_obj.raises

    for exception in except_obj:
        description = exception.description or ""
        if not description:
            self.error(f"No description for exception '{exception.type_name}'")
        exceptions.append(AnnotatedObject(exception.type_name, description))

    if exceptions:
        return Section(Section.Type.EXCEPTIONS, exceptions)
    if re.search("Raises\n", docstring):
        self.error("Empty exceptions section")
    return None
read_parameters_section(self, docstring, docstring_obj) ¤

Parse a "parameters" section.

Parameters:

Name Type Description Default
docstring str

The raw docstring.

required
docstring_obj Docstring

Docstring object parsed by docstring_parser.

required

Returns:

Type Description
Optional[pytkdocs.parsers.docstrings.base.Section]

A Section object (or None if section is empty).

Source code in pytkdocs/parsers/docstrings/numpy.py
def read_parameters_section(
    self,
    docstring: str,
    docstring_obj: Docstring,
) -> Optional[Section]:
    """
    Parse a "parameters" section.

    Arguments:
        docstring: The raw docstring.
        docstring_obj: Docstring object parsed by docstring_parser.

    Returns:
        A `Section` object (or `None` if section is empty).
    """
    parameters = []

    docstring_params = [p for p in docstring_obj.params if p.args[0] == "param"]

    for param in docstring_params:
        name = param.arg_name
        kind = None
        type_name = param.type_name
        default = param.default or empty
        try:
            signature_param = self.context["signature"].parameters[name.lstrip("*")]
        except (AttributeError, KeyError):
            self.error(f"No type annotation for parameter '{name}'")
        else:
            if signature_param.annotation is not empty:
                type_name = signature_param.annotation
            if signature_param.default is not empty:
                default = signature_param.default
            kind = signature_param.kind

        description = param.description or ""
        if not description:
            self.error(f"No description for parameter '{name}'")

        parameters.append(
            Parameter(
                name=param.arg_name,
                annotation=type_name,
                description=description,
                default=default,
                kind=kind,
            )
        )

    if parameters:
        return Section(Section.Type.PARAMETERS, parameters)
    if re.search("Parameters\n", docstring):
        self.error("Empty parameter section")
    return None
read_return_section(self, docstring_obj) ¤

Parse a "returns" section.

Parameters:

Name Type Description Default
docstring_obj Docstring

Docstring object parsed by docstring_parser.

required

Returns:

Type Description
Optional[pytkdocs.parsers.docstrings.base.Section]

A Section object (or None if section is empty).

Source code in pytkdocs/parsers/docstrings/numpy.py
def read_return_section(
    self,
    docstring_obj: Docstring,
) -> Optional[Section]:
    """
    Parse a "returns" section.

    Arguments:
        docstring_obj: Docstring object parsed by docstring_parser.

    Returns:
        A `Section` object (or `None` if section is empty).
    """
    if docstring_obj.returns:
        return_obj = docstring_obj.returns

        if return_obj.description:
            description = return_obj.description
        else:
            self.error("Empty return description")
            description = ""

        if self.context["signature"]:
            annotation = self.context["signature"].return_annotation
        else:
            annotation = self.context["annotation"]

        if annotation is empty and return_obj.type_name:
            annotation = return_obj.type_name

        if not annotation:
            self.error("No return type annotation")
            annotation = ""

        if annotation or description:
            return Section(Section.Type.RETURN, AnnotatedObject(annotation, description))

    return None
is_empty_line(line) ¤

Tell if a line is empty.

Parameters:

Name Type Description Default
line str

The line to check.

required

Returns:

Type Description
bool

True if the line is empty or composed of blanks only, False otherwise.

Source code in pytkdocs/parsers/docstrings/numpy.py
def is_empty_line(line: str) -> bool:
    """
    Tell if a line is empty.

    Arguments:
        line: The line to check.

    Returns:
        True if the line is empty or composed of blanks only, False otherwise.
    """
    return not line.strip()

restructured_text ¤

This module defines functions and classes to parse docstrings into structured data.

AttributesDict (dict) ¤

Attribute details.

Source code in pytkdocs/parsers/docstrings/restructured_text.py
class AttributesDict(TypedDict):
    """Attribute details."""

    docstring: str
    annotation: Type  # TODO: Not positive this is correct
FieldType dataclass ¤

Maps directive names to parser functions.

Source code in pytkdocs/parsers/docstrings/restructured_text.py
@dataclass(frozen=True)
class FieldType:
    """Maps directive names to parser functions."""

    names: FrozenSet[str]
    reader: Callable[[List[str], int], int]

    def matches(self, line: str) -> bool:
        """
        Check if a line matches the field type.

        Args:
            line: Line to check against

        Returns:
            True if the line matches the field type, False otherwise.
        """
        return any(line.startswith(f":{name}") for name in self.names)
matches(self, line) ¤

Check if a line matches the field type.

Parameters:

Name Type Description Default
line str

Line to check against

required

Returns:

Type Description
bool

True if the line matches the field type, False otherwise.

Source code in pytkdocs/parsers/docstrings/restructured_text.py
def matches(self, line: str) -> bool:
    """
    Check if a line matches the field type.

    Args:
        line: Line to check against

    Returns:
        True if the line matches the field type, False otherwise.
    """
    return any(line.startswith(f":{name}") for name in self.names)
ParseContext ¤

Typed replacement for context dictionary.

Source code in pytkdocs/parsers/docstrings/restructured_text.py
class ParseContext:
    """Typed replacement for context dictionary."""

    obj: Any  # I think this might be pytkdos.Object & subclasses
    attributes: DefaultDict[str, AttributesDict]
    signature: Optional[Signature]
    # Not sure real type yet. Maybe Optional[Union[Literal[Signature.empty],str,Type]]
    annotation: Any

    # This might be be better as the obj & optional attributes
    def __init__(self, context: Dict):
        """
        Initialize the object.

        Args:
            context: Context of parsing operation.
        """
        self.obj = context["obj"]
        self.attributes = defaultdict(cast(Callable[[], AttributesDict], dict))
        attributes = context.get("attributes")
        if attributes is not None:
            self.attributes.update(attributes)

        self.signature = getattr(self.obj, "signature", None)
        self.annotation = getattr(self.obj, "type", empty)
__init__(self, context) special ¤

Initialize the object.

Parameters:

Name Type Description Default
context Dict

Context of parsing operation.

required
Source code in pytkdocs/parsers/docstrings/restructured_text.py
def __init__(self, context: Dict):
    """
    Initialize the object.

    Args:
        context: Context of parsing operation.
    """
    self.obj = context["obj"]
    self.attributes = defaultdict(cast(Callable[[], AttributesDict], dict))
    attributes = context.get("attributes")
    if attributes is not None:
        self.attributes.update(attributes)

    self.signature = getattr(self.obj, "signature", None)
    self.annotation = getattr(self.obj, "type", empty)
ParsedDirective dataclass ¤

Directive information that has been parsed from a docstring.

Source code in pytkdocs/parsers/docstrings/restructured_text.py
@dataclass
class ParsedDirective:
    """Directive information that has been parsed from a docstring."""

    line: str
    next_index: int
    directive_parts: List[str]
    value: str
    invalid: bool = False
ParsedValues dataclass ¤

Values parsed from the docstring to be used to produce sections.

Source code in pytkdocs/parsers/docstrings/restructured_text.py
@dataclass
class ParsedValues:
    """Values parsed from the docstring to be used to produce sections."""

    description: List[str] = field(default_factory=list)
    parameters: Dict[str, Parameter] = field(default_factory=dict)
    param_types: Dict[str, str] = field(default_factory=dict)
    attributes: Dict[str, Attribute] = field(default_factory=dict)
    attribute_types: Dict[str, str] = field(default_factory=dict)
    exceptions: List[AnnotatedObject] = field(default_factory=list)
    return_value: Optional[AnnotatedObject] = None
    return_type: Optional[str] = None
RestructuredText (Parser) ¤

A reStructuredText docstrings parser.

Source code in pytkdocs/parsers/docstrings/restructured_text.py
class RestructuredText(Parser):
    """A reStructuredText docstrings parser."""

    def __init__(self) -> None:
        """Initialize the object."""
        super().__init__()
        self._typed_context = ParseContext({"obj": None})
        self._parsed_values: ParsedValues = ParsedValues()
        # Ordering is significant so that directives like ":vartype" are checked before ":var"
        self.field_types = [
            FieldType(PARAM_TYPE_NAMES, self._read_parameter_type),
            FieldType(PARAM_NAMES, self._read_parameter),
            FieldType(ATTRIBUTE_TYPE_NAMES, self._read_attribute_type),
            FieldType(ATTRIBUTE_NAMES, self._read_attribute),
            FieldType(EXCEPTION_NAMES, self._read_exception),
            FieldType(RETURN_NAMES, self._read_return),
            FieldType(RETURN_TYPE_NAMES, self._read_return_type),
        ]

    def parse_sections(self, docstring: str) -> List[Section]:  # noqa: D102
        self._typed_context = ParseContext(self.context)
        self._parsed_values = ParsedValues()

        lines = docstring.split("\n")
        curr_line_index = 0

        while curr_line_index < len(lines):
            line = lines[curr_line_index]
            for field_type in self.field_types:
                if field_type.matches(line):
                    # https://github.com/python/mypy/issues/5485
                    curr_line_index = field_type.reader(lines, curr_line_index)  # type: ignore
                    break
            else:
                self._parsed_values.description.append(line)

            curr_line_index += 1

        return self._parsed_values_to_sections()

    def _read_parameter(self, lines: List[str], start_index: int) -> int:
        """
        Parse a parameter value.

        Arguments:
            lines: The docstring lines.
            start_index: The line number to start at.

        Returns:
            Index at which to continue parsing.
        """
        parsed_directive = self._parse_directive(lines, start_index)
        if parsed_directive.invalid:
            return parsed_directive.next_index

        directive_type = None
        if len(parsed_directive.directive_parts) == 2:
            # no type info
            name = parsed_directive.directive_parts[1]
        elif len(parsed_directive.directive_parts) == 3:
            directive_type = parsed_directive.directive_parts[1]
            name = parsed_directive.directive_parts[2]
        else:
            self.error(f"Failed to parse field directive from '{parsed_directive.line}'")
            return parsed_directive.next_index

        if name in self._parsed_values.parameters:
            self.errors.append(f"Duplicate parameter entry for '{name}'")
            return parsed_directive.next_index

        annotation = self._determine_param_annotation(name, directive_type)
        default, kind = self._determine_param_details(name)

        self._parsed_values.parameters[name] = Parameter(
            name=name,
            annotation=annotation,
            description=parsed_directive.value,
            default=default,
            kind=kind,
        )

        return parsed_directive.next_index

    def _determine_param_details(self, name: str) -> Tuple[Any, Any]:
        default = empty
        kind = empty

        if self._typed_context.signature is not None:
            param_signature = self._typed_context.signature.parameters.get(name.lstrip("*"))
            # an error for param_signature being none is already reported by _determine_param_annotation()
            if param_signature is not None:
                if param_signature.default is not empty:
                    default = param_signature.default
                kind = param_signature.kind  # type: ignore[assignment]

        return default, kind

    def _determine_param_annotation(self, name: str, directive_type: Optional[str]) -> Any:
        # Annotation precedence:
        # - signature annotation
        # - in-line directive type
        # - "type" directive type
        # - empty
        annotation = empty

        parsed_param_type = self._parsed_values.param_types.get(name)
        if parsed_param_type is not None:
            annotation = parsed_param_type  # type: ignore[assignment]

        if directive_type is not None:
            annotation = directive_type  # type: ignore[assignment]

        if directive_type is not None and parsed_param_type is not None:
            self.error(f"Duplicate parameter information for '{name}'")

        if self._typed_context.signature is not None:
            try:
                param_signature = self._typed_context.signature.parameters[name.lstrip("*")]
            except KeyError:
                self.error(f"No matching parameter for '{name}'")
            else:
                if param_signature.annotation is not empty:
                    annotation = param_signature.annotation

        return annotation

    def _read_parameter_type(self, lines: List[str], start_index: int) -> int:
        """
        Parse a parameter type.

        Arguments:
            lines: The docstring lines.
            start_index: The line number to start at.

        Returns:
            Index at which to continue parsing.
        """
        parsed_directive = self._parse_directive(lines, start_index)
        if parsed_directive.invalid:
            return parsed_directive.next_index
        param_type = _consolidate_descriptive_type(parsed_directive.value.strip())

        if len(parsed_directive.directive_parts) == 2:
            param_name = parsed_directive.directive_parts[1]
        else:
            self.error(f"Failed to get parameter name from '{parsed_directive.line}'")
            return parsed_directive.next_index

        self._parsed_values.param_types[param_name] = param_type
        param = self._parsed_values.parameters.get(param_name)
        if param is not None:
            if param.annotation is empty:
                param.annotation = param_type
            else:
                self.error(f"Duplicate parameter information for '{param_name}'")
        return parsed_directive.next_index

    def _read_attribute(self, lines: List[str], start_index: int) -> int:
        """
        Parse an attribute value.

        Arguments:
            lines: The docstring lines.
            start_index: The line number to start at.

        Returns:
            Index at which to continue parsing.
        """
        parsed_directive = self._parse_directive(lines, start_index)
        if parsed_directive.invalid:
            return parsed_directive.next_index

        if len(parsed_directive.directive_parts) == 2:
            name = parsed_directive.directive_parts[1]
        else:
            self.error(f"Failed to parse field directive from '{parsed_directive.line}'")
            return parsed_directive.next_index

        annotation = empty

        # Annotation precedence:
        # - external context type TODO: spend time understanding where this comes from
        # - "vartype" directive type
        # - empty

        parsed_attribute_type = self._parsed_values.attribute_types.get(name)
        if parsed_attribute_type is not None:
            annotation = parsed_attribute_type  # type: ignore[assignment]

        context_attribute_annotation = self._typed_context.attributes[name].get("annotation")
        if context_attribute_annotation is not None:
            annotation = context_attribute_annotation

        if name in self._parsed_values.attributes:
            self.errors.append(f"Duplicate attribute entry for '{name}'")
        else:
            self._parsed_values.attributes[name] = Attribute(
                name=name,
                annotation=annotation,
                description=parsed_directive.value,
            )

        return parsed_directive.next_index

    def _read_attribute_type(self, lines: List[str], start_index: int) -> int:
        """
        Parse a parameter type.

        Arguments:
            lines: The docstring lines.
            start_index: The line number to start at.

        Returns:
            Index at which to continue parsing.
        """
        parsed_directive = self._parse_directive(lines, start_index)
        if parsed_directive.invalid:
            return parsed_directive.next_index
        attribute_type = _consolidate_descriptive_type(parsed_directive.value.strip())

        if len(parsed_directive.directive_parts) == 2:
            attribute_name = parsed_directive.directive_parts[1]
        else:
            self.error(f"Failed to get attribute name from '{parsed_directive.line}'")
            return parsed_directive.next_index

        self._parsed_values.attribute_types[attribute_name] = attribute_type
        attribute = self._parsed_values.attributes.get(attribute_name)
        if attribute is not None:
            if attribute.annotation is empty:
                attribute.annotation = attribute_type
            else:
                self.error(f"Duplicate attribute information for '{attribute_name}'")
        return parsed_directive.next_index

    def _read_exception(self, lines: List[str], start_index: int) -> int:
        """
        Parse an exceptions value.

        Arguments:
            lines: The docstring lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        parsed_directive = self._parse_directive(lines, start_index)
        if parsed_directive.invalid:
            return parsed_directive.next_index

        if len(parsed_directive.directive_parts) == 2:
            ex_type = parsed_directive.directive_parts[1]
            self._parsed_values.exceptions.append(AnnotatedObject(ex_type, parsed_directive.value))
        else:
            self.error(f"Failed to parse exception directive from '{parsed_directive.line}'")

        return parsed_directive.next_index

    def _read_return(self, lines: List[str], start_index: int) -> int:
        """
        Parse an return value.

        Arguments:
            lines: The docstring lines.
            start_index: The line number to start at.

        Returns:
            Index at which to continue parsing.
        """
        parsed_directive = self._parse_directive(lines, start_index)
        if parsed_directive.invalid:
            return parsed_directive.next_index

        annotation = empty
        # Annotation precedence:
        # - signature annotation
        # - "rtype" directive type
        # - external context type TODO: spend time understanding where this comes from
        # - empty
        if self._typed_context.signature is not None and self._typed_context.signature.return_annotation is not empty:
            annotation = self._typed_context.signature.return_annotation
        elif self._parsed_values.return_type is not None:
            annotation = self._parsed_values.return_type  # type: ignore[assignment]
        else:
            annotation = self._typed_context.annotation

        self._parsed_values.return_value = AnnotatedObject(annotation, parsed_directive.value)

        return parsed_directive.next_index

    def _read_return_type(self, lines: List[str], start_index: int) -> int:
        """
        Parse an return type value.

        Arguments:
            lines: The docstring lines.
            start_index: The line number to start at.

        Returns:
            Index at which to continue parsing.
        """
        parsed_directive = self._parse_directive(lines, start_index)
        if parsed_directive.invalid:
            return parsed_directive.next_index

        return_type = _consolidate_descriptive_type(parsed_directive.value.strip())
        self._parsed_values.return_type = return_type
        return_value = self._parsed_values.return_value
        if return_value is not None:
            if return_value.annotation is empty:
                return_value.annotation = return_type
            else:
                self.error("Duplicate type information for return")

        return parsed_directive.next_index

    def _parsed_values_to_sections(self) -> List[Section]:
        markdown_text = "\n".join(_strip_blank_lines(self._parsed_values.description))
        result = [Section(Section.Type.MARKDOWN, markdown_text)]
        if self._parsed_values.parameters:
            param_values = list(self._parsed_values.parameters.values())
            result.append(Section(Section.Type.PARAMETERS, param_values))
        if self._parsed_values.attributes:
            attribute_values = list(self._parsed_values.attributes.values())
            result.append(Section(Section.Type.ATTRIBUTES, attribute_values))
        if self._parsed_values.return_value is not None:
            result.append(Section(Section.Type.RETURN, self._parsed_values.return_value))
        if self._parsed_values.exceptions:
            result.append(Section(Section.Type.EXCEPTIONS, self._parsed_values.exceptions))
        return result

    def _parse_directive(self, lines: List[str], start_index: int) -> ParsedDirective:
        line, next_index = _consolidate_continuation_lines(lines, start_index)
        try:
            _, directive, value = line.split(":", 2)
        except ValueError:
            self.error(f"Failed to get ':directive: value' pair from '{line}'")
            return ParsedDirective(line, next_index, [], "", invalid=True)

        value = value.strip()
        return ParsedDirective(line, next_index, directive.split(" "), value)
parse_sections(self, docstring) ¤

Parse a docstring as a list of sections.

Parameters:

Name Type Description Default
docstring str

The docstring to parse.

required

Returns:

Type Description
List[pytkdocs.parsers.docstrings.base.Section]

A list of Sections.

Source code in pytkdocs/parsers/docstrings/restructured_text.py
def parse_sections(self, docstring: str) -> List[Section]:  # noqa: D102
    self._typed_context = ParseContext(self.context)
    self._parsed_values = ParsedValues()

    lines = docstring.split("\n")
    curr_line_index = 0

    while curr_line_index < len(lines):
        line = lines[curr_line_index]
        for field_type in self.field_types:
            if field_type.matches(line):
                # https://github.com/python/mypy/issues/5485
                curr_line_index = field_type.reader(lines, curr_line_index)  # type: ignore
                break
        else:
            self._parsed_values.description.append(line)

        curr_line_index += 1

    return self._parsed_values_to_sections()

properties ¤

This module simply defines regular expressions and their associated predicates.

NAME_CLASS_PRIVATE: Tuple[str, Callable[[str], bool]] ¤

Applicable property: class-private.

NAME_PRIVATE: Tuple[str, Callable[[str], bool]] ¤

Applicable property: private.

NAME_SPECIAL: Tuple[str, Callable[[str], bool]] ¤

Applicable property: special.

RE_CLASS_PRIVATE: Pattern ¤

Regular expression to match __class_private names.

RE_PRIVATE: Pattern ¤

Regular expression to match _private names.

RE_SPECIAL: Pattern ¤

Regular expression to match __special__ names.

serializer ¤

This module defines function to serialize objects.

These functions simply take objects as parameters and return dictionaries that can be dumped by json.dumps.

RE_FORWARD_REF: Pattern ¤

Regular expression to match forward-reference annotations of the form _ForwardRef('T').

RE_OPTIONAL: Pattern ¤

Regular expression to match optional annotations of the form Union[T, NoneType].

GenericMeta (type) ¤

GenericMeta type.

Source code in pytkdocs/serializer.py
class GenericMeta(type):  # type: ignore  # noqa: WPS440 (variable overlap)
    """GenericMeta type."""

annotation_to_string(annotation) ¤

Return an annotation as a string.

Parameters:

Name Type Description Default
annotation Any

The annotation to return as a string.

required

Returns:

Type Description
str

The annotation as a string.

Source code in pytkdocs/serializer.py
def annotation_to_string(annotation: Any) -> str:
    """
    Return an annotation as a string.

    Arguments:
        annotation: The annotation to return as a string.

    Returns:
        The annotation as a string.
    """
    if annotation is inspect.Signature.empty:
        return ""

    if inspect.isclass(annotation) and not isinstance(annotation, GenericMeta):
        string = annotation.__name__
    else:
        string = str(annotation).replace("typing.", "")

    string = RE_FORWARD_REF.sub(lambda match: match.group(1), string)
    string = RE_OPTIONAL.sub(rebuild_optional, string)

    return string  # noqa: WPS331 (false-positive, string is not only used for the return)

rebuild_optional(match) ¤

Rebuild Union[T, None] as Optional[T].

Parameters:

Name Type Description Default
match Match

The match object when matching against a regular expression (by the parent caller).

required

Returns:

Type Description
str

The rebuilt type string.

Source code in pytkdocs/serializer.py
def rebuild_optional(match: Match) -> str:
    """
    Rebuild `Union[T, None]` as `Optional[T]`.

    Arguments:
        match: The match object when matching against a regular expression (by the parent caller).

    Returns:
        The rebuilt type string.
    """
    group = match.group(1)
    brackets_level = 0
    for char in group:
        if char == "," and brackets_level == 0:
            return f"Union[{group}]"
        if char == "[":
            brackets_level += 1
        elif char == "]":
            brackets_level -= 1
    return f"Optional[{group}]"

serialize_annotated_object(obj) ¤

Serialize an instance of AnnotatedObject.

Parameters:

Name Type Description Default
obj AnnotatedObject

The object to serialize.

required

Returns:

Type Description
dict

A JSON-serializable dictionary.

Source code in pytkdocs/serializer.py
def serialize_annotated_object(obj: AnnotatedObject) -> dict:
    """
    Serialize an instance of [`AnnotatedObject`][pytkdocs.parsers.docstrings.base.AnnotatedObject].

    Arguments:
        obj: The object to serialize.

    Returns:
        A JSON-serializable dictionary.
    """
    return {"description": obj.description, "annotation": annotation_to_string(obj.annotation)}

serialize_attribute(attribute) ¤

Serialize an instance of Attribute.

Parameters:

Name Type Description Default
attribute Attribute

The attribute to serialize.

required

Returns:

Type Description
dict

A JSON-serializable dictionary.

Source code in pytkdocs/serializer.py
def serialize_attribute(attribute: Attribute) -> dict:
    """
    Serialize an instance of [`Attribute`][pytkdocs.parsers.docstrings.base.Attribute].

    Arguments:
        attribute: The attribute to serialize.

    Returns:
        A JSON-serializable dictionary.
    """
    return {
        "name": attribute.name,
        "description": attribute.description,
        "annotation": annotation_to_string(attribute.annotation),
    }

serialize_docstring_section(section) ¤

Serialize an instance of inspect.Signature.

Parameters:

Name Type Description Default
section Section

The section to serialize.

required

Returns:

Type Description
dict

A JSON-serializable dictionary.

Source code in pytkdocs/serializer.py
def serialize_docstring_section(section: Section) -> dict:  # noqa: WPS231 (not complex)
    """
    Serialize an instance of `inspect.Signature`.

    Arguments:
        section: The section to serialize.

    Returns:
        A JSON-serializable dictionary.
    """
    serialized = {"type": section.type}
    if section.type == section.Type.MARKDOWN:
        serialized.update({"value": section.value})
    elif section.type == section.Type.RETURN:
        serialized.update({"value": serialize_annotated_object(section.value)})  # type: ignore
    elif section.type == section.Type.YIELD:
        serialized.update({"value": serialize_annotated_object(section.value)})  # type: ignore
    elif section.type == section.Type.EXCEPTIONS:
        serialized.update({"value": [serialize_annotated_object(exc) for exc in section.value]})  # type: ignore
    elif section.type == section.Type.PARAMETERS:
        serialized.update({"value": [serialize_parameter(param) for param in section.value]})  # type: ignore
    elif section.type == section.Type.KEYWORD_ARGS:
        serialized.update({"value": [serialize_parameter(param) for param in section.value]})  # type: ignore
    elif section.type == section.Type.ATTRIBUTES:
        serialized.update({"value": [serialize_attribute(attr) for attr in section.value]})  # type: ignore
    elif section.type == section.Type.EXAMPLES:
        serialized.update({"value": section.value})
    return serialized

serialize_object(obj) ¤

Serialize an instance of a subclass of Object.

Parameters:

Name Type Description Default
obj Object

The object to serialize.

required

Returns:

Type Description
dict

A JSON-serializable dictionary.

Source code in pytkdocs/serializer.py
def serialize_object(obj: Object) -> dict:
    """
    Serialize an instance of a subclass of [`Object`][pytkdocs.objects.Object].

    Arguments:
        obj: The object to serialize.

    Returns:
        A JSON-serializable dictionary.
    """
    serialized = {
        "name": obj.name,
        "path": obj.path,
        "category": obj.category,
        "file_path": obj.file_path,
        "relative_file_path": obj.relative_file_path,
        "properties": sorted(set(obj.properties + obj.name_properties)),
        "parent_path": obj.parent_path,
        "has_contents": obj.has_contents(),
        "docstring": obj.docstring,
        "docstring_sections": [serialize_docstring_section(sec) for sec in obj.docstring_sections],
        "source": serialize_source(obj.source),
        "children": {child.path: serialize_object(child) for child in obj.children},
        "attributes": [attr.path for attr in obj.attributes],
        "methods": [meth.path for meth in obj.methods],
        "functions": [func.path for func in obj.functions],
        "modules": [mod.path for mod in obj.modules],
        "classes": [clas.path for clas in obj.classes],
    }
    if hasattr(obj, "type"):  # noqa: WPS421 (hasattr)
        serialized["type"] = annotation_to_string(obj.type)  # type: ignore
    if hasattr(obj, "signature"):  # noqa: WPS421 (hasattr)
        serialized["signature"] = serialize_signature(obj.signature)  # type: ignore
    if hasattr(obj, "bases"):  # noqa: WPS421 (hasattr)
        serialized["bases"] = obj.bases  # type: ignore
    return serialized

serialize_parameter(parameter) ¤

Serialize an instance of Parameter.

Parameters:

Name Type Description Default
parameter Parameter

The parameter to serialize.

required

Returns:

Type Description
dict

A JSON-serializable dictionary.

Source code in pytkdocs/serializer.py
def serialize_parameter(parameter: Parameter) -> dict:
    """
    Serialize an instance of [`Parameter`][pytkdocs.parsers.docstrings.base.Parameter].

    Arguments:
        parameter: The parameter to serialize.

    Returns:
        A JSON-serializable dictionary.
    """
    serialized = serialize_annotated_object(parameter)
    serialized.update(
        {
            "name": parameter.name,
            "kind": str(parameter.kind),
            "default": parameter.default_string,
            "is_optional": parameter.is_optional,
            "is_required": parameter.is_required,
            "is_args": parameter.is_args,
            "is_kwargs": parameter.is_kwargs,
        },
    )
    return serialized

serialize_signature(signature) ¤

Serialize an instance of inspect.Signature.

Parameters:

Name Type Description Default
signature Signature

The signature to serialize.

required

Returns:

Type Description
dict

A JSON-serializable dictionary.

Source code in pytkdocs/serializer.py
def serialize_signature(signature: inspect.Signature) -> dict:
    """
    Serialize an instance of `inspect.Signature`.

    Arguments:
        signature: The signature to serialize.

    Returns:
        A JSON-serializable dictionary.
    """
    if signature is None:
        return {}
    serialized: dict = {
        "parameters": [serialize_signature_parameter(value) for name, value in signature.parameters.items()],
    }
    if signature.return_annotation is not inspect.Signature.empty:
        serialized["return_annotation"] = annotation_to_string(signature.return_annotation)
    return serialized

serialize_signature_parameter(parameter) ¤

Serialize an instance of inspect.Parameter.

Parameters:

Name Type Description Default
parameter Parameter

The parameter to serialize.

required

Returns:

Type Description
dict

A JSON-serializable dictionary.

Source code in pytkdocs/serializer.py
def serialize_signature_parameter(parameter: inspect.Parameter) -> dict:
    """
    Serialize an instance of `inspect.Parameter`.

    Arguments:
        parameter: The parameter to serialize.

    Returns:
        A JSON-serializable dictionary.
    """
    serialized = {"kind": str(parameter.kind), "name": parameter.name}
    if parameter.annotation is not parameter.empty:
        serialized["annotation"] = annotation_to_string(parameter.annotation)
    if parameter.default is not parameter.empty:
        serialized["default"] = repr(parameter.default)
    return serialized

serialize_source(source) ¤

Serialize an instance of Source.

Parameters:

Name Type Description Default
source Optional[pytkdocs.objects.Source]

The source to serialize.

required

Returns:

Type Description
dict

A JSON-serializable dictionary.

Source code in pytkdocs/serializer.py
def serialize_source(source: Optional[Source]) -> dict:
    """
    Serialize an instance of [`Source`][pytkdocs.objects.Source].

    Arguments:
        source: The source to serialize.

    Returns:
        A JSON-serializable dictionary.
    """
    if source:
        return {"code": source.code, "line_start": source.line_start}
    return {}
Back to top