|
| 1 | +--- |
| 2 | +title: Rendering docstrings |
| 3 | +jupyter: |
| 4 | + kernelspec: |
| 5 | + display_name: Python 3 (ipykernel) |
| 6 | + language: python |
| 7 | + name: python3 |
| 8 | +--- |
| 9 | + |
| 10 | +The previous section covered how to read and preview parsed docstrings. |
| 11 | +In this section, we'll look at how to render a parsed docstring into a format |
| 12 | +that can be used in documentation, like markdown or HTML. |
| 13 | + |
| 14 | +## Setting up problem |
| 15 | + |
| 16 | +Suppose that we wanted to take a function like `get_object()` and render a summary, with: |
| 17 | + |
| 18 | +* The number of parameters it takes. |
| 19 | +* The number of sections in its parsed docstring. |
| 20 | + |
| 21 | +For `get_object()` it might look like the following: |
| 22 | + |
| 23 | +``` |
| 24 | +## get_object |
| 25 | +N PARAMETERS: 3 |
| 26 | +SECTIONS: A docstring with 4 pieces |
| 27 | +``` |
| 28 | + |
| 29 | +## Inspecting a function |
| 30 | + |
| 31 | +As covered in the previous section, we can preview information about `get_object()`. |
| 32 | + |
| 33 | +```{python} |
| 34 | +from quartodoc import get_object, preview |
| 35 | +
|
| 36 | +f_obj = get_object("quartodoc", "get_object") |
| 37 | +
|
| 38 | +preview(f_obj, max_depth=3) |
| 39 | +``` |
| 40 | + |
| 41 | +Note the following pieces: |
| 42 | + |
| 43 | +* `preview()` takes a max_depth argument, that limits how much information it shows. |
| 44 | +* `get_object()` takes 3 parameters. |
| 45 | +* `get_object()` has a docstring with 4 sections. |
| 46 | + |
| 47 | +Importantly, the nodes (`█`) in the tree mention the name class of the python objects |
| 48 | +being previewed (e.g. `Alias`, `Expression`, `Parameters`). |
| 49 | +We'll need these to specify how to render objects of each class. |
| 50 | + |
| 51 | +## Generic dispatch |
| 52 | + |
| 53 | +Generic dispatch is the main programming technique used by quartodoc renderers. |
| 54 | +It let's you define how a function (like `render()`) should operate on different |
| 55 | +types of objects. |
| 56 | + |
| 57 | +```{python} |
| 58 | +from plum import dispatch |
| 59 | +
|
| 60 | +import griffe.dataclasses as dc |
| 61 | +import griffe.docstrings.dataclasses as ds |
| 62 | +
|
| 63 | +
|
| 64 | +@dispatch |
| 65 | +def render(el: object): |
| 66 | + print(f"Default rendering: {type(el)}") |
| 67 | +
|
| 68 | +@dispatch |
| 69 | +def render(el: dc.Alias): |
| 70 | + print("Alias rendering") |
| 71 | + render(el.parameters) |
| 72 | +
|
| 73 | +@dispatch |
| 74 | +def render(el: list): |
| 75 | + print("List rendering") |
| 76 | + [render(entry) for entry in el] |
| 77 | +
|
| 78 | +
|
| 79 | +render(f_obj) |
| 80 | +``` |
| 81 | + |
| 82 | +## Defining a Renderer |
| 83 | + |
| 84 | +quartodoc uses tree visitors to render parsed docstrings to formats like markdown and HTML. |
| 85 | +Tree visitors define how each type of object in the parse tree should be handled. |
| 86 | + |
| 87 | +```{python} |
| 88 | +import griffe.dataclasses as dc |
| 89 | +import griffe.docstrings.dataclasses as ds |
| 90 | +
|
| 91 | +from quartodoc import get_object |
| 92 | +from plum import dispatch |
| 93 | +from typing import Union |
| 94 | +
|
| 95 | +
|
| 96 | +class SomeRenderer: |
| 97 | + def __init__(self, header_level: int = 1): |
| 98 | + self.header_level = header_level |
| 99 | +
|
| 100 | + @dispatch |
| 101 | + def visit(self, el): |
| 102 | + raise NotImplementedError(f"Unsupported type: {type(el)}") |
| 103 | +
|
| 104 | + @dispatch |
| 105 | + def visit(self, el: Union[dc.Alias, dc.Object]): |
| 106 | + header = "#" * self.header_level |
| 107 | + str_header = f"{header} {el.name}" |
| 108 | + str_params = f"N PARAMETERS: {len(el.parameters)}" |
| 109 | + str_sections = "SECTIONS: " + self.visit(el.docstring) |
| 110 | + |
| 111 | + # return something pretty |
| 112 | + return "\n".join([str_header, str_params, str_sections]) |
| 113 | +
|
| 114 | + @dispatch |
| 115 | + def visit(self, el: dc.Docstring): |
| 116 | + return f"A docstring with {len(el.parsed)} pieces" |
| 117 | +
|
| 118 | +
|
| 119 | +f_obj = get_object("quartodoc", "get_object") |
| 120 | +
|
| 121 | +print(SomeRenderer(header_level=2).visit(f_obj)) |
| 122 | +``` |
| 123 | + |
| 124 | +Note 3 big pieces: |
| 125 | + |
| 126 | +* **Generic dispatch**: The plum `dispatch` function decorates each `visit` method. The type annotations |
| 127 | + specify the types of data each version of visit should dispatch on. |
| 128 | +* **Default behavior**: The first `visit` method ensures a `NotImplementedError` is raised by default. |
| 129 | +* **Tree walking**: `visit` methods often call `visit` again on sub elements. |
| 130 | + |
| 131 | + |
0 commit comments