Theming and other customizations
Theming
Currently, there are 2 main themes packaged with pydoctor: classic
and readthedocs
.
Choose your theme with option:
--theme
Note
Additionnaly, the base
theme can be used as a base for customizations.
Tweak HTML templates
They are 3 special files designed to be included in specific places of each pages.
header.html
: at the very beginning of the bodysubheader.html
: after the main header, before the page titleextra.css
: extra CSS sheet for layout customization
To include a file, write your custom HTML or CSS files to a directory and use the following option:
--template-dir=./pydoctor_templates
If you want more customization, you can override the default templates in pydoctor/themes/base with the same method.
HTML templates have their own versioning system and warnings will be triggered when an outdated custom template is used.
Override objects privacy (show/hide)
Pydoctor supports 3 types of privacy. Below is the description of each type and the default association:
PRIVATE
: By default for objects whose name starts with an underscore and are not a dunder method. Rendered in HTML, but hidden via CSS by default.PUBLIC
: By default everything else that is not private. Always rendered and visible in HTML.HIDDEN
: Nothing is hidden by default. Not rendered at all and no links can be created to hidden objects. Not present in the search index nor the intersphinx inventory. Basically excluded from API documentation. If a module/package/class is hidden, then all it’s members are hidden as well.
When the default rules regarding privacy doesn’t fit your use case,
use the --privacy
command line option.
It can be used multiple times to define multiple privacy rules:
--privacy=<PRIVACY>:<PATTERN>
where <PRIVACY>
can be one of PUBLIC
, PRIVATE
or HIDDEN
(case insensitive), and <PATTERN>
is fnmatch-like
pattern matching objects fullName.
Privacy tweak examples
--privacy="PUBLIC:**"
Makes everything public.--privacy="HIDDEN:twisted.test.*" --privacy="PUBLIC:twisted.test.proto_helpers"
Makes everything undertwisted.test
hidden excepttwisted.test.proto_helpers
, which will be public.--privacy="PRIVATE:**.__*__" --privacy="PUBLIC:**.__init__"
Makes all dunder methods private except__init__
.
Important
The order of arguments matters. Pattern added last have priority over a pattern added before, but an exact match wins over a fnmatch.
Note
See pydoctor.qnmatch
for more informations regarding the pattern syntax.
Note
Quotation marks should be added around each rule to avoid shell expansions.
Unless the arguments are passed directly to pydoctor, like in Sphinx’s conf.py
, in this case you must not quote the privacy rules.
Use a custom system class
You can subclass the pydoctor.model.System
and pass your custom class dotted name with the following argument:
--system-class=mylib._pydoctor.CustomSystem
System class allows you to customize certain aspect of the system and configure the enabled extensions. If what you want to achieve has something to do with the state of some objects in the Documentable tree, it’s very likely that you can do it without the need to override any system method, by using the extension mechanism described below.
Brief on pydoctor extensions
The AST builder can now be customized with extension modules.
This is how we handle Zope Interfaces declarations and twisted.python.deprecate
warnings.
Each pydocotor extension is a Python module with at least a setup_pydoctor_extension()
function.
This function is called at initialization of the system with one argument,
the pydoctor.extensions.ExtRegistrar
object representing the system.
- An extension can register multiple kind of components:
AST builder visitors
Mixin classes for
pydoctor.model.Documentable
Post processors
Take a look at built-in extensions pydoctor.extensions.zopeinterface
and pydoctor.extensions.deprecate
.
Navigate to the source code for a better overview.
A concrete example
Let’s say you want to write a extension for simple pydantic classes like this one:
from typing import ClassVar
from pydantic import BaseModel
class Model(BaseModel):
a: int
b: int = Field(...)
name:str = 'Jane Doe'
kind:ClassVar = 'person'
First, we need to create a new module that will hold our extension code: mylib._pydoctor
.
This module will contain visitor code that visits ast.AnnAssign
nodes after the main visitor.
It will check if the current context object is a class derived from pydantic.BaseModel
and
transform each class variable into instance variables accordingly.
# Module mylib._pydoctor
import ast
from pydoctor import astutils, extensions, model
class PydanticModVisitor(extensions.ModuleVisitorExt):
def depart_AnnAssign(self, node: ast.AnnAssign) -> None:
"""
Called after an annotated assignment definition is visited.
"""
ctx = self.visitor.builder.current
if not isinstance(ctx, model.Class):
# check if the current context object is a class
return
if not any(ctx.expandName(b) == 'pydantic.BaseModel' for b in ctx.bases):
# check if the current context object if a class derived from ``pydantic.BaseModel``
return
dottedname = astutils.node2dottedname(node.target)
if not dottedname or len(dottedname)!=1:
# check if the assignment is a simple name, otherwise ignore it
return
# Get the attribute from current context
attr = ctx.contents[dottedname[0]]
assert isinstance(attr, model.Attribute)
# All class variables that are not annotated with ClassVar will be transformed to instance variables.
if astutils.is_using_typing_classvar(attr.annotation, attr):
return
if attr.kind == model.DocumentableKind.CLASS_VARIABLE:
attr.kind = model.DocumentableKind.INSTANCE_VARIABLE
def setup_pydoctor_extension(r:extensions.ExtRegistrar) -> None:
r.register_astbuilder_visitor(PydanticModVisitor)
class PydanticSystem(model.System):
# Declare that this system should load this additional extension
custom_extensions = ['mylib._pydoctor']
Then, we would pass our custom class dotted name with the argument --system-class
:
--system-class=mylib._pydoctor.PydanticSystem
Et voilà.
If this extension mechanism doesn’t support the tweak you want, you can consider overriding some
pydoctor.model.System
methods. For instance, overriding pydoctor.model.System.__init__()
method could be useful,
if some want to write a custom pydoctor.sphinx.SphinxInventory
.
Important
If you feel like other users of the community might benefit from your extension as well, please
don’t hesitate to open a pull request adding your extension module to the package pydoctor.extensions
.
Use a custom writer class
You can subclass the pydoctor.templatewriter.TemplateWriter
(or the abstract super class pydoctor.templatewriter.IWriter
)
and pass your custom class dotted name with the following argument:
--html-writer=mylib._pydoctor.CustomTemplateWriter
The option is actually badly named because, theorically one could write a subclass
of pydoctor.templatewriter.IWriter
(to be used alongside option --template-dir
)
that would output Markdown, reStructuredText or JSON.
Warning
Pydoctor does not have a stable API yet. Code customization is prone to break in future versions.