Components: Auto, blueprint, collect

from quartodoc import collect, preview
from quartodoc import MdRenderer
import yaml

Auto: Config Options

The Auto class contains data about how you wish to render a specific Python object, which you typically set in configuration options via the quartodoc section of your _quarto.yml file. In other words, Auto is a data structure that represents the configuration options for a specific Python object.

In the previous section, be demonstrated how we can find the Auto object corresponding to the MdRenderer class from our yaml configuration:

We can find the Auto object corresponding to the python object from our yaml configuration like this:

import yaml
from quartodoc import Builder

cfg = yaml.safe_load("""
quartodoc:
  package: quartodoc
  style: pkgdown
  sections:
    - title: "Some section"
      desc: "Some description"
      contents:
        - name: MdRenderer
          members: ["render", "summarize"]
          children: separate
""")

builder = Builder.from_quarto_config(cfg)
auto_from_yml = builder.layout.sections[0].contents[0]
print(auto_from_yml)
signature_name='relative' members=['render', 'summarize'] include_private=False include_imports=False include_empty=False include_inherited=False include_attributes=True include_classes=True include_functions=True include=None exclude=None dynamic=None children=<ChoicesChildren.separate: 'separate'> package=MISSING() member_order='alphabetical' member_options=None kind='auto' name='MdRenderer'

We can see many of the configuration options such as members, name and children as well as other options we didn’t specify but are set to their default values. For example, we didn’t set the option for dynamic but it is set to false by default.

However, we don’t have to start with yaml files to create an Auto object. We can also create an Auto object with the fully qualified name of any Python object. For example, we can create an Auto object for the MdRenderer class like this:

from quartodoc import Auto
auto = Auto(name = "quartodoc.MdRenderer", 
            signature_name = 'short')
print(auto)
signature_name='short' members=None include_private=False include_imports=False include_empty=False include_inherited=False include_attributes=True include_classes=True include_functions=True include=None exclude=None dynamic=None children=<ChoicesChildren.embedded: 'embedded'> package=MISSING() member_order='alphabetical' member_options=None kind='auto' name='quartodoc.MdRenderer'

However, since we didn’t specify any options, this is not the same Auto that we got from the yaml. For example, the members option is not set to ["render", "summarize"] as it was in the yaml. Instead, it is set to None which means that all members will be included. We can see this by looking at the obj attribute of the Auto object:

print(auto.members)
None

To set this option, we can pass it to the Auto constructor:

auto = Auto(name = "quartodoc.MdRenderer", 
            members = ["render", "summarize"]
        )

assert auto.members == auto_from_yml.members
print(auto)
signature_name='relative' members=['render', 'summarize'] include_private=False include_imports=False include_empty=False include_inherited=False include_attributes=True include_classes=True include_functions=True include=None exclude=None dynamic=None children=<ChoicesChildren.embedded: 'embedded'> package=MISSING() member_order='alphabetical' member_options=None kind='auto' name='quartodoc.MdRenderer'

Understanding the Auto object is helpful for debugging quartodoc. If you find that a configuration option is not being set as you expect, you can create an Auto object for the Python object in question and compare it to the Auto object that you expect to be created from your yaml configuration.

blueprint: Parse Metadata From Objects

blueprint parses all of the metadata about the python object and stores it in a hierarchal tree structure that is convenient for a renderer to transform into a renderable format like HTML or Markdown. For example, here is the blueprint for the MdRenderer class:

from quartodoc import blueprint
doc = blueprint(auto)
preview(doc, max_depth=2)
█─DocClass
├─name = 'quartodoc.MdRenderer'
├─obj = █─Alias
│       ├─name = 'MdRenderer'
│       ├─canonical_path = 'quartodoc.renderers.md_renderer.MdRenderer'
│       ├─classes = █─dict ...
│       ├─parameters = █─Parameters ...
│       ├─members = █─dict ...
│       ├─functions = █─dict ...
│       └─docstring = █─Docstring ...
├─anchor = 'quartodoc.MdRenderer'
├─members = █─list
│           ├─0 = █─DocFunction ...
│           └─1 = █─DocFunction ...
└─flat = False

To give you a sense of this tree structure, we can look at the obj.docstring field of the above blueprint, which contains information about the Python docstring:

preview(doc.obj.docstring, max_depth=2)
█─Docstring
├─parser = <Parser.numpy: 'numpy'>
└─parsed = █─list
           ├─0 = █─DocstringSectionText ...
           ├─1 = █─DocstringSectionParameters ...
           └─2 = █─DocstringSectionExamples ...

We can see from this output that the parser for the docstring is numpy, which means the docstring is expected to be in the numpy style.

Furthermore, we can see from the tree structure that the DocstringSectionText is stored as the first element in a list under the parsed attribute:

preview(doc.obj.docstring.parsed[0])
█─DocstringSectionText
├─kind = <DocstringSectionKind.text: 'text'>
├─title = None
└─value = 'Render docstrings to markdown.'

DocstringSectionText stores the “text” field of a numpy style docstring, which is the first line of the docstring, which is otherwise known as the short summary. Furthermore, we can see from the output above that the actual text of this short summary is stored in the value attribute:

docstr_firstln_value = doc.obj.docstring.parsed[0].value
print(docstr_firstln_value)
Render docstrings to markdown.

We can check the docstring of MdRenderer to see that this is indeed the first line of the docstring:

from inspect import getdoc
docstr_firstln = getdoc(MdRenderer).splitlines()[0]

# These are the same
assert docstr_firstln == docstr_firstln_value
print(docstr_firstln)
Render docstrings to markdown.

Layout & Sections

The Layout class stores how you wish to organize your documentation. For example, you may wish to organize your documentation into sections, where each section contains a title, description, and a list of objects to document. You can create a layout like this:

import yaml
from quartodoc import Builder

cfg = yaml.safe_load("""
quartodoc:
  package: quartodoc
  sections:
    - title: "Some section"
      desc: "Some description"
      contents:
        - name: MdRenderer
    - title: "Another section"
      desc: "Another description"
      contents:
        - Auto
        - blueprint 
""")

builder = Builder.from_quarto_config(cfg)
auto_from_yml = builder.layout.sections[0].contents[0]
preview(builder.layout, max_depth=3)
█─Layout
├─sections = █─list
│            ├─0 = █─Section
│            │     ├─title = 'Some section'
│            │     ├─desc = 'Some description'
│            │     └─contents = █─list ...
│            └─1 = █─Section
│                  ├─title = 'Another section'
│                  ├─desc = 'Another description'
│                  └─contents = █─list ...
└─package = 'quartodoc'

As you can see, the Layout stores the sections, which are stored in the sections attribute. Each section contains a title, desc, and contents attribute and is stored in a Section class.

The contents attribute is a list of objects to document. In this case, the first section contains a single object, the MdRenderer class, while the second section contains two objects. You can read more about Section options here.

In addition to building a layout from a yaml file, you can also build a layout in Python by instantiating the Layout class like so:

from quartodoc import Auto, layout

auto = Auto(name = "quartodoc.MdRenderer", 
            signature_name = 'short')

lay = layout.Layout(
    sections = [
      layout.Section(title = "A section", 
                      desc = "A description", 
                      contents = [auto])
    ]
)

We can view the layout by calling the preview function:

preview(lay, 
        max_depth=8, 
        compact=True)
█─Layout
└─sections = 
  █─list
  └─0 = 
    █─Section
    ├─title = 'A section'
    ├─desc = 'A description'
    └─contents = 
      █─list
      └─0 = 
        █─Auto
        ├─signature_name = 'short'
        └─name = 'quartodoc.MdRenderer'

Recall that the blueprint function parses all of the metadata about the Python object. We can see how a blueprint adds additional data pertaining to MdRenderer, that wasn’t present in the layout above:

bp_layout = blueprint(lay) 
preview(bp_layout, 
        max_depth=8, 
        compact=True)
█─Layout
└─sections = 
  █─list
  └─0 = 
    █─Section
    ├─title = 'A section'
    ├─desc = 'A description'
    └─contents = 
      █─list
      └─0 = 
        █─Page
        ├─path = 'quartodoc.MdRenderer'
        └─contents = 
          █─list
          └─0 = 
            █─DocClass
            ├─name = 'quartodoc.MdRenderer'
            ├─obj = 
            │ █─Alias
            │ ├─name = 'MdRenderer'
            │ ├─canonical_path = 'quartodoc.renderers.md_renderer.MdRenderer'
            │ ├─classes = █─dict ...
            │ ├─parameters = 
            │ │ █─Parameters ...
            │ ├─members = 
            │ │ █─dict ...
            │ ├─functions = 
            │ │ █─dict ...
            │ └─docstring = 
            │   █─Docstring ...
            ├─anchor = 'quartodoc.MdRenderer'
            ├─signature_name = 'short'
            ├─members = 
            │ █─list
            │ └─0 = 
            │   █─DocFunction ...
            └─flat = False

Grouping docs on a page

The Layout also calculates how to split your sections into pages based on the options you set in your yaml configuration. For example, if you set the children option to separate, then each object in a section will be placed on its own page.

Let’s see the difference between the separate and embedded options by creating two Auto objects for the MdRenderer class, one with children set to separate and the other with children set to embedded:

auto_sep = layout.Auto(name = "quartodoc.MdRenderer", 
                       children = "separate")
auto_emb = layout.Auto(name = "quartodoc.MdRenderer", 
                       children = "embedded")
bp_emb = blueprint(auto_emb)
preview(bp_emb, 
        max_depth=2,
        compact=True)
█─DocClass
├─name = 'quartodoc.MdRenderer'
├─obj = 
│ █─Alias
│ ├─name = 'MdRenderer'
│ ├─canonical_path = 'quartodoc.renderers.md_renderer.MdRenderer'
│ ├─classes = █─dict ...
│ ├─parameters = 
│ │ █─Parameters ...
│ ├─members = 
│ │ █─dict ...
│ ├─functions = 
│ │ █─dict ...
│ └─docstring = 
│   █─Docstring ...
├─anchor = 'quartodoc.MdRenderer'
├─members = 
│ █─list
│ └─0 = 
│   █─DocFunction ...
└─flat = False
bp_sep = blueprint(auto_sep)
preview(bp_sep, 
        max_depth=2,
        compact=True)
█─DocClass
├─name = 'quartodoc.MdRenderer'
├─obj = 
│ █─Alias
│ ├─name = 'MdRenderer'
│ ├─canonical_path = 'quartodoc.renderers.md_renderer.MdRenderer'
│ ├─classes = █─dict ...
│ ├─parameters = 
│ │ █─Parameters ...
│ ├─members = 
│ │ █─dict ...
│ ├─functions = 
│ │ █─dict ...
│ └─docstring = 
│   █─Docstring ...
├─anchor = 'quartodoc.MdRenderer'
├─members = 
│ █─list
│ └─0 = 
│   █─MemberPage ...
└─flat = False

Collect: fetch all pages and items

some_page = layout.Page(path = "some_doc_page", contents = [doc])
pages, items = collect(some_page, "reference")
pages
[Page(kind='page', path='some_doc_page', package=MISSING(), summary=None, flatten=False, contents=[DocClass(name='quartodoc.MdRenderer', obj=Alias('MdRenderer', 'quartodoc.renderers.MdRenderer'), anchor='quartodoc.MdRenderer', signature_name='relative', kind='class', members=[DocFunction(name='render', obj=Alias('render', 'quartodoc.renderers.md_renderer.MdRenderer.render'), anchor='quartodoc.MdRenderer.render', signature_name='relative', kind='function'), DocFunction(name='summarize', obj=Alias('summarize', 'quartodoc.renderers.md_renderer.MdRenderer.summarize'), anchor='quartodoc.MdRenderer.summarize', signature_name='relative', kind='function')], flat=False)])]