The previous section covered how to read and preview parsed docstrings. In this section, we’ll look at how to render a parsed docstring into a format that can be used in documentation, like markdown or HTML.
Setting up problem
Suppose that we wanted to take a function like get_object() and render a summary, with:
The number of parameters it takes.
The number of sections in its parsed docstring.
For get_object() it might look like the following:
## get_object
N PARAMETERS: 3
SECTIONS: A docstring with 4 pieces
Inspecting a function
As covered in the previous section, we can preview information about get_object().
from quartodoc import get_object, previewf_obj = get_object("quartodoc", "get_object")preview(f_obj, max_depth=3)
preview() takes a max_depth argument, that limits how much information it shows.
get_object() takes 3 parameters.
get_object() has a docstring with 4 sections.
Importantly, the nodes (█) in the tree mention the name class of the python objects being previewed (e.g. Alias, Expression, Parameters). We’ll need these to specify how to render objects of each class.
Generic dispatch
Generic dispatch is the main programming technique used by quartodoc renderers. It let’s you define how a function (like render()) should operate on different types of objects.
from plum import dispatchfrom griffe import Alias, Object, Docstring@dispatchdef render(el: object):print(f"Default rendering: {type(el)}")@dispatchdef render(el: Alias):print("Alias rendering") render(el.parameters)@dispatchdef render(el: list):print("List rendering") [render(entry) for entry in el]render(f_obj)
Alias rendering
Default rendering: <class '_griffe.models.Parameters'>
Defining a Renderer
quartodoc uses tree visitors to render parsed docstrings to formats like markdown and HTML. Tree visitors define how each type of object in the parse tree should be handled.
## get_object
N PARAMETERS: 6
SECTIONS: A docstring with 5 pieces
Note 3 big pieces:
Generic dispatch: The plum dispatch function decorates each render method. The type annotations specify the types of data each version of render should dispatch on.
Default behavior: The first render method ensures a NotImplementedError is raised by default.
Tree walking: render methods often call render again on sub elements.
Completing the Renderer
While the above example showed a simple example with a .render method, a complete renderer will often do two more things:
Subclass an existing renderer.
Also override other methods like .summarize()
from quartodoc import MdRendererclass NewRenderer(MdRenderer): style ="new_renderer"@dispatchdef render(self, el):print("calling parent method for render")returnsuper().render(el)@dispatchdef summarize(self, el):print("calling parent method for summarize")returnsuper().summarize(el)