The API reference
smalldocs writes a reference page for each module of your package. A reference page lists the classes and the functions of a module, with their parameters. smalldocs reads what the code already says, so you write nothing twice:
- The types come from the annotations.
- The description of each parameter comes from the comment beside it.
- The summary comes from the docstring.
The Reference of this site is made from the code of smalldocs in this way.
Write the code
Put one parameter on each line, with its comment beside it. Put the comment for the return value after
the colon of the def line. fastcore calls these comments docments.
def add(
a: int, # The first number
b: int = 2, # The second number
) -> int: # Their sum
"Add two numbers."
return a + bsmalldocs shows other kinds of class in their own way:
- A dataclass shows its constructor, with the comment beside each field.
- A pydantic model shows its fields. The next section tells where the description of each field comes from.
- A
TypedDictshows its keys, with the comment beside each key. - A click command shows its function, with the comment beside each parameter. Then it shows a table of its options, with the help of each option. A click group shows the same.
If the __init__.py of a package defines functions or classes, the package has a page of its own.
The fields of a pydantic model
For each field, the description in the reference is:
- The
descriptionof the field, when it has one. This coversField(description="..."), and a docstring under the field when the model setsConfigDict(use_attribute_docstrings=True). - Otherwise, the comment beside the field.
- Otherwise, nothing, and
smalldocs api checkreports the gap, for examplePage.title: no description.
A field with a description and a comment is documented, and the reference shows the description. A
comment for a tool, such as # type: ignore or # noqa, is not a description.
class Page(BaseModel):
"""A page of Wikipedia: its title and the summary of its text."""
title: str # The title of the page
extract: str = Field(description="The summary: the first paragraph of the page")Each style is good for a different kind of model:
Field(description="...")is data that pydantic keeps when the program runs. It goes intomodel_json_schema(), and so into an OpenAPI schema, a FastAPI page or the tool schema of an LLM. For a model that is the schema of an API, the description does the work of the comment and more. Use it there.- The comment is lighter, and it reads like the comments of a function. For a model that only parses data, nothing else reads a description. There, the comment is enough.
Turn on the reference
Add an api section to _smalldocs.yml:
api:
packages: [mypkg] # the importable packages to document
out: api # the folder of pages, inside the site; `api` by default
root: .. # the root of the repository, from the folder of the site
source: https://github.com/me/mypkg/blob/main # each entry links to its line here
check: ["*.py", "tests/**/*.py"] # more files that `smalldocs api check` holds to the conventionIn a _quarto.yml, write the same section under smalldocs:, as smalldocs.api.
| Key | What it does | Default |
|---|---|---|
packages |
The importable packages to document. package with one name works too |
None. An api section without it stops the build |
out |
The folder of the reference pages, from the folder of the site. It must be inside the site | api |
root |
The root of the repository, from the folder of the site. The globs of check start here |
.., or . when the folder of the site holds pyproject.toml or .git |
source |
The address of the code. Each entry links to its line there | None: no links to the code |
check |
Globs of more files that smalldocs api check holds to the convention |
None: only the packages |
linkify |
Names in backticks link to their entries. The top-level linkify wins over it |
On |
Each build then writes a page api/<module>.qmd for each public module. A module is public when no
part of its name starts with _. The page has the name of the module without its package, such as
site.qmd for smalldocs.site. When two modules have the same last name, each page takes the full name.
A package whose __init__.py defines functions or classes gets a page too.
smalldocs notes the pages that it writes in the file api/.smalldocs-api. It deletes a page only if an
earlier build wrote the page and the new build did not. So a page of your own in the folder, such as
api/index.qmd, is safe.
To list the pages in the sidebar, use a glob:
website:
sidebar:
contents:
- section: Reference
contents: api/*smalldocs imports your package. So install smalldocs in the environment of the package. If the import
fails, smalldocs also looks in the root of the repository and in its src folder.
The index of names
Each build also writes _symbols.json, in the folder of the site, before it builds the pages. It is a
JSON object. Each key is a name, and its value is the entry of that name, as a page and an anchor:
{
"Site": "/api/site.qmd#Site",
"smalldocs.site.Site": "/api/site.qmd#Site",
"Site.build": "/api/site.qmd#Site.build",
"smalldocs.site.Site.build": "/api/site.qmd#Site.build"
}smalldocs makes the index from the reference. For each class, method and function, it adds two keys:
- the name in its module, such as
SiteorSite.build - the full name, with the module, such as
smalldocs.site.Site.build
Two modules can have a function with the same name, such as build. Then the short name links to the
first one, in the order of packages, and then of the names of the modules. The full names link to each one.
smalldocs writes the file only when its content changes. Linkify then reads it.
Links from the text
When the reference is on, a name in backticks can become a link to its entry, on every page of the site. Linkify looks only at inline code, the text between backticks. It never changes the words of a sentence.
The name must be in the reference, and it must look like code. A name looks like code when it has one of these forms:
- a call, which ends in
(), such asinventory() - a dotted name, such as
Site.buildorsmalldocs.markdown.render - a name with an underscore, such as
package_modules - a name that starts with a capital letter, such as the class
Site
A plain lowercase word, such as main or check, stays as code. Docs often name a branch, an option
or a command with such a word, and a link would go to the wrong place. To link a function, write it
as a call, check(), or with its module, smalldocs.api.check.
These names also stay as code:
- a name inside a link
- a name that the reference does not know
- a name with the class
nolink, written as`Site`{.nolink}. Use it when a name looks like code but means something else, such as aSitein another program
Use the reference at the terminal
smalldocs api build --dir docs # write the pages and the index
smalldocs api module site --dir docs # print one module as Markdown, for people and agents
smalldocs api list --dir docs # every module, class, method and function, one line each
smalldocs api check --dir docs # what is typed and documented, and every gap; exit 1 if anycheck reports each gap in the code:
- a function without a docstring
- a parameter without a type or a docment
- a return value without a type or a docment
- a field of a pydantic model without a description or a comment
- a key of a
TypedDictwithout a comment
The globs of api.check start at the root of the repository. In them, * stays inside one folder,
and **/ is any number of folders, or none. So *.py finds the files at the root only, and
tests/**/*.py finds tests/conftest.py and tests/unit/test_x.py. check leaves out the files of
the packages in packages, because it already checks them as part of their package.
check names each file by its path from the root, such as tests/test_x.py. So the output is the same
on every computer:
tests/test_x.py:test_add: no docstringRun check in your tests, or before each commit.