Sphinx Extension
pyTooling.Documentation.Sphinx is a Sphinx extension providing the roles and directives pyTooling’s
documentation uses - and any other project’s documentation can use. It is enabled in conf.py:
# doc/conf.py
extensions = [
...,
"pyTooling.Documentation.Sphinx",
]
Attention
The extension needs Sphinx and docutils, which pyTooling itself does not depend on. Install it as
pyTooling[sphinx], which requires Python 3.12 or newer.
Roles
The extension registers roles for styling inline text, one for inline Python code, and two for a line break and a horizontal rule - and a stylesheet for the styles, linked into every HTML page.
Style roles
Role |
Renders |
|---|---|
|
bold and italic |
|
underlined |
|
struck through |
|
extra large |
|
red |
|
green |
|
blue |
|
purple |
|
deleted |
|
added |
A :red:`warning` and a :deletion:`removed` word.
A style role puts CSS classes on the text; the stylesheet gives them their meaning. :deletion: and
:addition: are the two a diff needs.
Every colour and size is a CSS custom property, so a project changes it without replacing the stylesheet - in
a stylesheet of its own, listed in html_css_files:
:root {
--pyTooling-color-red: #b00020;
}
The properties are --pyTooling-color-red, -green, -blue, -purple and
--pyTooling-xlarge-size.
Inline Python code
:pycode: renders inline Python code, syntax-highlighted: isinstance(value, int).
:pycode:`isinstance(value, int)`
Line break and horizontal rule
|br| is a line break, |hr| a horizontal rule - in HTML and in LaTeX. They are substitutions,
appended to rst_prolog, and delegate to the roles :br: and :hr:. |degree| writes a degree sign.
:param value: The first line. |br|
The second line.
condensed-class
The condensed-class directive renders a class’ public interface as a code block: the class line, its
class variables, its methods and its properties, each with the signature it is declared with, and ... for
a body.
The source is parsed, not imported. Annotations appear as they are written - Nullable[str], not the
Optional[str] an import resolves it to - members appear in the order of the file, and the file becomes a
dependency of the page, so editing the class rebuilds the page.
Left out is what the surrounding text is for: bodies, doc-strings, private members (one leading underscore), and the annotated fields of a slotted class.
.. condensed-class:: pyTooling.Stopwatch.Stopwatch
:members: Methods, Properties
This is how the example renders:
@export
class Stopwatch(SlottedObject):
def Start(self) -> None:
...
def Split(self) -> float:
...
def Pause(self) -> float:
...
def Resume(self) -> float:
...
def Stop(self) -> float:
...
@property
def Digits(self) -> int:
...
@Digits.setter
def Digits(self, digits: int) -> None:
...
@readonly
def Name(self) -> Nullable[str]:
...
@readonly
def IsStarted(self) -> bool:
...
@readonly
def IsRunning(self) -> bool:
...
@readonly
def IsPaused(self) -> bool:
...
@readonly
def IsStopped(self) -> bool:
...
@readonly
def StartTime(self) -> Nullable[datetime]:
...
@readonly
def StopTime(self) -> Nullable[datetime]:
...
@readonly
def HasSplitTimes(self) -> bool:
...
@readonly
def SplitCount(self) -> int:
...
@readonly
def ActiveCount(self) -> int:
...
@readonly
def InactiveCount(self) -> int:
...
@readonly
def Activity(self) -> float:
...
@readonly
def Inactivity(self) -> float:
...
@readonly
def Duration(self) -> float:
...
@readonly
def DurationInNanoseconds(self) -> int:
...
@readonly
def Exclude(self) -> ExcludeContextManager:
...
- .. condensed-class:: <dotted name of a class>
Renders the interface of the class the argument names. The longest prefix of the name that is a module is the module; the rest is the class, and may name a class nested in a class.
- :members: <kinds>
The kinds of member to render, separated by commas, in any case:
ClassVariables,Dunders,Methods,PropertiesorAll. Default:All.A property is a method decorated with
@property,@readonly,@cached_propertyor as a setter or deleter; a dunder is a method named__<name>__.
- :exclude-members: <names>
Names of methods not to render, separated by commas.
- :indent: <columns>
Width of one indentation level. Default: 2.
- :width: <columns>
Column a signature is wrapped at, one parameter per line. Default: 100.
- :caption: <text>
A caption for the code block.
dependency-table
The dependency-table directive renders a project’s dependencies as a table - per package the version
required, its license and what it requires in turn - from the requirements themselves rather than by hand. The data
is fetched from the package index while the documentation is built.
Attention
The directive needs the pypi extra as well: pyTooling[sphinx,pypi].
Configuration
A table names an entrypoint, which conf.py declares:
# doc/conf.py
pyTooling_Dependency_Requirements = {
"package": {"file": "../requirements.txt"},
"documentation": {"file": "requirements.txt"},
"yaml": {"package": "pyTooling[yaml]"},
}
Each entrypoint states exactly one of:
file/files→ a requirements file, or several, read relative to
conf.pywith their-rincludes followed. They are read whileconf.pyis processed, so a path that doesn’t exist ends the build with one message.package/packages→ a package, or several, as the package index publishes its latest release:
pyToolingfor its own requirements,pyTooling[yaml]for what the extrayamladds.
Name |
Value |
|---|---|
|
The entrypoints, by identifier. |
|
Optional, a YAML file stating the license of a package the index can’t answer for, relative to
|
|
Optional, the package index. Default: |
|
Optional, the index’s JSON API. Default: |
The tables of a build share one view of the index, so a package several tables require is downloaded once. Each table logs what it cost, and the build ends with the total and the packages whose license couldn’t be resolved - the list the override file answers.
- .. dependency-table:: <entrypoint>
Renders the dependencies of the entrypoint the argument names.
- :depth: <levels>
Levels of sub-dependencies to expand. Default: 0, which expands until the tree ends.
- :simplified-versions: yes | no
Whether a version constraint is reduced to its lower bound:
≥9.1instead of≥9.1, <10. Default:yes.
- :version-format: Major | MajorMinor | MajorMinorPatch | All
How many parts of a version number are printed. Default:
MajorMinor.
- :dependency-format: Package | PackageVersion | PackageLicense | PackageVersionLicense
What a line of a dependency tree states. Default:
PackageVersionLicense.
- :caption: <text>
A caption for the table.
.. dependency-table:: package
:caption: Mandatory dependencies of the pyTooling package.
:depth: 4
Dependencies shows the tables pyTooling’s own documentation renders.
xsd-graph
The xsd-graph directive draws an XML schema as a Graphviz graph, from the schema file itself:
every complex type is a record of its name, its attributes, and its simple-typed child elements with their cardinality;
every complex-typed child element is an edge, labelled with its name and cardinality - so containment and recursion are edges rather than repeated type names;
an enumeration is a node of its own, listing its values;
a root element is a double circle.
The schema is read with xmlschema, part of the sphinx extra, and drawn by
sphinx.ext.graphviz, which the extension sets up itself. The schema file becomes a dependency of the
page.
.. xsd-graph:: ../../pyTooling/Resources/TestReport-v0.1.xsd
:caption: The types of TestReport-v0.1.xsd.
- .. xsd-graph:: <path of an XML schema>
Draws the schema the argument names, relative to the document.
- :caption: <text>
A caption under the graph.
Overview shows the graphs of the schemas pyTooling ships.
Another schema language
SchemaGraph is the directive’s language-neutral base-class,
and DotGraph assembles the graph. A directive for another schema
language derives from the base-class, names itself, and overrides _RenderGraph():
class JSONSchemaGraph(SchemaGraph):
directiveName: str = "json-schema-graph"
@classmethod
def _RenderGraph(cls, schemaFile: Path) -> str:
graph = DotGraph()
...
return str(graph)
shields
The shields directive renders a project’s badges from shields.io - where the
project lives, how it is licensed, whether it builds, where it is published.
The options state the project’s coordinates: its GitHub repository, its PyPI package, its licenses, its workflow and its documentation’s URL. The content names the badges and is the layout: badges appear in the order written, and each line is a row.
A badge needs only the options it is made of, so a project states what its badges use and nothing else.
.. shields::
:github: pyTooling/pyTooling
:pypi: pyTooling
:codacy: 08ef744c0b70490289712b02a7a4cebe
:source-license: github:LICENSE.md
:documentation-license: CC-BY-4.0 github:doc/Doc-License.rst
:github-action: Pipeline.yml@main
:documentation: github-pages
github, src-license, ghp-doc, doc-license
pypi-tag, pypi-status, pypi-python
github-action, lib-status, codacy-quality, codacy-coverage, codecov-coverage
This is how the example renders:
Options
- .. shields::
Renders a project’s badges, in rows. Each line of the content is a row of badge identifiers, separated by commas.
- :github: <organization>/<repository>
The GitHub repository, for the badges showing it and for
github:links.
- :pypi: <package>
The package’s name on PyPI.
- :codacy: <project ID>
The Codacy project ID, as shown in Codacy’s badge settings.
- :gitter: <room>
The Gitter room, e.g.
hdl/community.
- :source-license: [<SPDX expression> ]<link>
Where the source code’s license is written. Without a license before the link, the badge shows what PyPI reports for the package when
shields:pypiis stated, and what GitHub reports for the repository otherwise.
- :documentation-license: <SPDX expression> <link>
The documentation’s license and where it is written. The license is required: nothing reports a documentation’s license.
- :github-action: <workflow file>[@<branch>]
The workflow whose status is shown. Without a branch, the badge shows the workflow’s latest run on any branch.
- :documentation: github-pages | <URL>
Where the documentation is published.
github-pagesis the GitHub Pages site ofshields:github; any other value is anhttp://orhttps://URL.
- :class: <CSS classes>
Additional CSS classes on the rows.
Links
shields:source-license and shields:documentation-license end in a link, which is one of:
github:<path>→ the file at
<path>on the default branch of the repositoryshields:githubnames.http://…orhttps://…→ used as written.
Licenses
A license is an SPDX license expression,
parsed by LicenseExpression.Parse(): Apache-2.0,
CC-BY-4.0 or MIT OR Apache-2.0. A misspelt identifier is reported. A license that isn’t on the SPDX License
List is written LicenseRef-<name>.
Badges
Identifier |
Shows |
Options |
|---|---|---|
|
the repository |
|
|
the source code’s license |
|
|
the documentation’s license |
|
|
whether the documentation is online |
|
|
the latest tag, including pre-releases |
|
|
the date of the latest release |
|
|
the workflow’s status |
|
|
Codacy’s code quality grade |
|
|
Codacy’s line coverage |
|
|
Codecov’s branch coverage |
|
|
the latest version on PyPI |
|
|
the development status on PyPI |
|
|
the Python versions on PyPI |
|
|
whether the dependencies are up to date, by Libraries.io |
|
|
the SourceRank, by Libraries.io |
|
|
how many repositories depend on the package |
|
|
a link to the Gitter room |
|
A github: link, a license GitHub reports, and github-pages need shields:github as well.
HTML and LaTeX
The directive emits both variants, each wrapped in an only node: HTML embeds the SVG from
img.shields.io, LaTeX the PNG from raster.shields.io - a PDF cannot embed an SVG.
Errors
A mistake is reported on the page, where the badges would be, and in the build’s log:
shields: 'gha-test' is not a known badge. Known are: codacy-coverage, codacy-quality, codecov-coverage, ...
shields: Badge 'pypi-tag' needs option ':pypi:'.
shields: Option ':github:' is 'pyTooling', not '<organization>/<repository>'.
shields: Option ':source-license:' links to 'LICENSE.md', neither 'github:<path>' nor a URL.
shields: Option ':documentation-license:' states 'CC-BY-5.0', which isn't an SPDX license expression.