3. Docstrings - minimal and full examples¶
In this section, we will systematically look at the docstrings for the various categories, modules, classes, and functions or methods. The aim is to clarify which sections and subsections are optional and which are mandatory, and what purpose they serve.
3.1. Module docstrings – minimal example¶
The minimum requirements for any Waterloo docstring are a valid Preamble
with subsections profile and normative_sections,
and a Contract.
Mandatory
Preamble – The Preamble represents the metadata of the docstring.
It specifies what type of object is being documented (here profile: module)
and which sections of the docstring are normative (normative_sections). Sections not listed
here are not normative.
Mandatory and always normative
Contract – The Contract is the central section in which it is normatively documented
what an object is used for, what it must be capable of, what preconditions must be fulfiled
in order to use it, and what guarantees the object provides for the result.
In the case of the module, there is a subsection named general.
"""
Preamble:
profile:
module
normative_sections:
Contract
Contract:
general:
|Must| demonstrate the minimal module docstring.
"""
|
|
|
|
Module |
test_docitem_module_minimal |
|---|
3.2. Module docstrings – full example¶
The following example shows all possible sections and subsections in a module docstring. Some of the sections must always be listed as normative, some only under certain conditions (e.g. if they contain normativity keywords), while others are explicitly non-normative and must not be listed as normative.
Mandatory and always normative
Sections Contract
Optional and always normative
Sections Definitions, Public_classes, Public_functions,
Public_types, Public_variables, Public_constants
Optional and normative under conditions
Sections Description and See_also are normative under
certain conditions.
If the description contains at least one normativity keyword in tokenized form,
it must be listed under normative_sections – and vice versa.
For See_also the following rule applies:
Any validator will check whether the objects listed under See_also
can be resolved. If the section is listed as normative, resolution failure leads
to an error. If it is not listed, resolution failure only leads to a warning.
Optional and never normative
The
TerminologyandNotessections are intended for human-readable explanations and notes. Since one of the basic principles of the Waterloo format is the strict separation between normative and informative content, certain sections must never be listed as normative.Sections
Class_overviewandFunction_overvieware informative by design. Normative statements in*_overviewsections would violate SSoT by introducing an additional normative source besides the class or function docstring. They would also violate LoII, as normative information would be distributed across multiple docstrings. The split between*_overviewandPublic_*sections is therefore necessary to satisfy BinNorm. Without this split, sections would contain a mixture of normative and informative content.
r"""
Preamble:
profile:
module
normative_sections:
Contract, Definitions, Public_classes, Public_functions,
Public_types, Public_variables, Public_constants
Definitions:
MyItem:
This is a term named |term|`MyItem` in section "Definitions".
This section is always normative and |must| be listed
in "normative_sections". You can refer to this term by using the
syntax token "|" + term + "|" followed by the referred term in
backticks. Any validator will ensure that the terms you refer to
by this token are defined in section "Definitions".
My_Other_Item:
Here is another example. Note that term names are Identifiers
like variable names in most programming languages, restricted to
letters, digits, and underscore.
Terminology:
Fancy-Unicorn:
The section "Terminology" allows to define terms in an informative
manner. Typically, this subsection would start like
"A Fancy-Unicorn is ..." followed by an explanation.
|
The section "Terminology" is never normative, never listed
in "normative_sections" and does not contain normativity keywords
in tokenized form.
|
As opposed to section "Definitions", any non-empty
string is allowed, like here "Fancy-Unicorn".
Take care, however, that the name does not collide with syntax rules
of the intermediate document language (like reST in our case).
Contract:
general:
|Must| demonstrate all sections and subsections\
and syntax tokens like backslash for continued lines.
|Must| point out that logical lines are interpreted\
as items, so here we have two logical lines, i.e. two items.
This docstring |may| refer to |term|`MyItem` for demonstration purposes.
Description:
The purpose of this module is to demonstrate waterloo docstrings
on module level. A linter will accept this docstring, but
coverage is not given, because the classes and function mentioned
herein do not have docstrings.
|
The section "Description" allows free form text and the usage of
the pipe character "|" in order to subdivide the text into paragraphs.
|
A section "Description" may be normative, but in this example it is not,
since it does not contain normativity keywords in tokenized form.
Notes:
What is it good for:
Section "Notes" allows you to add informative content.
What it is not good for:
The section is never normative, it is never listed in
"normative_sections" and it must not contain normativity
keywords in tokenized form.
Syntax:
The section "Notes" is subdivided into subsections with
user-defined labels like in this example "What is it good for:",
"What it is not good for:" and "Syntax:"
Public_classes:
MyClass
Class_overview:
MyClass:
Class for nothing
Public_functions:
my_function
Function_overview:
my_function:
Important for demonstration but does nothing
Public_types:
MyTypeAlias_t:
|Must| represent a union of :wtrl_type:`float` and :wtrl_type:`int`.\
This section is always normative. All relevant information about the\
type alias is specified here, as type aliases do not provide their own\
machine-verifiable docstrings.
Public_variables:
my_variable:
|Must| represent a value for this and that.\
This section is always normative. All relevant information is specified\
here, since variables do not have standardized, machine-verifiable\
docstrings.
Public_constants:
MY_CONSTANT:
|Must| represent a constant value annotated as :wtrl_type:`Final`.\
This section is always normative. All relevant information is specified\
here, as constants do not provide their own machine-verifiable\
docstrings.
See_also:
test_docitem_module_minimal
"""
from typing import Final, TypeAlias
class MyClass:
pass
def my_function() -> None:
pass
MyTypeAlias_t: TypeAlias = float | int
my_variable : MyTypeAlias_t = 1.2345
MY_CONSTANT : Final[int] = 42
|
|
|
|
|
|
|
|
|
The purpose of this module is to demonstrate waterloo docstrings on module level. A linter will accept this docstring, but coverage is not given, because the classes and function mentioned herein do not have docstrings. The section “Description” allows free form text and the usage of the pipe character “|” in order to subdivide the text into paragraphs. A section “Description” may be normative, but in this example it is not, since it does not contain normativity keywords in tokenized form. |
|
What is it good for Section “Notes” allows you to add informative content. What it is not good for The section is never normative, it is never listed in “normative_sections” and it must not contain normativity keywords in tokenized form. Syntax The section “Notes” is subdivided into subsections with user-defined labels like in this example “What is it good for:”, “What it is not good for:” and “Syntax:” |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Module |
test_docitem_module_full |
|---|
3.3. Class docstrings – minimal example¶
Many sections and subsections are equivalent to their counterparts in module docstring, but there are sections and subsections specific to class docstrings:
Mandatory and always normative
Contract.constructor – Each instance of a class is constructed in one or the other way.
Therefore the Waterloo format demands a normative statement
on the class constructor in section Contract.
class MyClass:
"""
Preamble:
profile:
class
normative_sections:
Contract
Contract:
general:
|Must| demonstrate the minimal class docstring.
constructor:
|Must| be default-constructible
"""
pass
|
|
|
|
Class |
MyClass |
|---|
3.4. Class docstrings – full example¶
For demonstration purposes, the constructor signature in this example
covers all supported ways to pass and accept parameters. Subsection
Contract.constructor should describe the constructor
parameters, how they are passed, and what the constructor does.
In addition to the sections described for module docstrings, class docstrings support the following optional sections and subsections.
Optional and always normative
Contract.traits –
This optional subsection allows to declare certain semantic properties
of a class. A limited set of trait identifiers is defined by this
specification, including abstract for classes that are
intended to serve primarily as base classes, and final
for classes that are explicitly not meant to be extended.
If the subsection is absent, the default is that the class is neither
abstract nor final, meaning that it can be
instantiated and may serve as a base class.
The purpose of this subsection is to provide a normative way to express
design intent, such as indicating that a class is part of an API surface
only, or that it must not be used as a base for further extensions.
Derived_from –
This optional section is related to Contract.traits. It
normatively extends the public API of the documented class by declaring
which base classes contribute public methods, types, variables, constants,
or semantic guarantees.
Validators verify that the classes listed in this section can be resolved
using Python’s import mechanism. This serves as an anti-drift mechanism:
if a base class is renamed, removed, or moved without updating this
section, validation fails.
Public_methods –
This section defines which methods of the class are part of the public
API and is therefore normative.
Method_overview –
This section is informative. It may be used to provide human-readable
descriptions for selected methods, explaining their purpose or typical
usage. This section may only be present if section
Public_methods exists.
from typing import Any, Dict, Final, TypeAlias
# A base class which becomes part of the API.
class MyBaseClass:
pass
# A private base class which prefer not o propagate as API.
class MyOtherBaseClass:
pass
class MyClass(MyBaseClass,MyOtherBaseClass):
"""
Preamble:
profile:
class
normative_sections:
Contract, Definitions, Derived_from, Public_classes,
Public_methods, Public_types, Public_variables, Public_constants
Definitions:
MyExampleItem:
Define a term normatively. Same as for modules.
Terminology:
Fancy-Unicorn:
Define a term informatively. Same as for modules.
Contract:
general:
|Must| demonstrate the minimal class docstring.
constructor:
|Must| demand an int-valued positional parameter 'a'.
|Must| demand a str-valued positional or keyword parameter 'b'.
|Must| accept a float-valued positional or keyword parameter 'c'.
|Must| demand a bool-valued keyword parameter 'd'.
|Must| accept (variadic) keyword parameters and dispatch them as follows:
|Must| accept a keyword parameter 'e' and assert it is str-valued (if present).
The constructor |must| print all parameter values to 'stdout'.
traits:
final
Derived_from:
MyBaseClass
Description:
As for modules
Notes:
Purpose and Syntax:
Same as for modules
Public_classes:
MyNestedClass
Class_overview:
MyNestedClass:
Use this section for informative reminders what the class\
is good for.
Multiple lines are possible. Do not use Normativity Keywords.
Public_methods:
my_method
Method_overview:
my_method:
Important for demonstration but does nothing
Public_types:
MyTypeAlias_t:
Important type for annotations
Public_variables:
my_variable:
A variable
Public_constants:
MY_CONSTANT:
Another constant
See_also:
test_docitem_module_minimal
"""
class MyNestedClass:
pass
MyTypeAlias_t: TypeAlias = float | int
my_variable: MyTypeAlias_t = 123
MY_CONSTANT: Final[str] = "hello"
def __init__(self, a: int, /, b: str, c: float = 1.23, *, d: bool, **kwargs: Any) -> None:
# 'a' is positional-only
# 'b' and 'c' are positional or keyword
# 'd' is keyword-only
print(a, b, c, d, kwargs)
# Validation of 'e' from variadic parameters
if "e" in kwargs:
assert isinstance(kwargs["e"], str), "Parameter 'e' must be a string"
def my_method(self, q : MyTypeAlias_t) -> None:
pass
if __name__ == "__main__":
m = MyClass(1,"hi",2.34,e="xyz",d=False)
m = MyClass(2,"hi",3.45,e="yzx",d=False)
m = MyClass(3,c=4.56,b="hi",d=True,e="zxy")
|
|
|
|
|
|
|
|
|
|
|
As for modules |
|
Purpose and Syntax Same as for modules |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Class |
MyClass |
|---|
3.5. Function docstrings – minimal example¶
The following minimal example illustrates the essential structure of a function docstring. While deliberately simple, it highlights the sections that appear in every function docstring.
Apart from Preamble and Contract, which are already
known from module and class docstrings, the following sections are present.
Mandatory and always normative
Parameters –
Each subsection of this normative section represents a parameter of the
function. The minimal example shown below does not define any parameters.
Returns –
This section describes the return value. It is normative by definition,
but it is up to the author whether to formulate requirements using
Normativity Keywords or to keep the description purely descriptive.
Raises –
Each subsection is labelled by an exception class and describes, normatively,
the circumstances under which the exception is raised.
def spam() -> None:
"""
Preamble:
profile:
function
normative_sections:
Contract, Parameters, Returns, Raises
Contract:
general:
|Must| demonstrate the minimal function docstring.
Parameters:
Returns:
|None|
Raises:
"""
|
test_docitem_function_minimal.spam(
) -> None
|
|
|
|
|
|
|
|
|
|
<empty> |
Function |
spam |
|---|
A typical way to populate the Raises section is as follows:
An exception must be raised if it is a direct consequence of the function’s
own behavior. An exception may be raised if it originates from a function
called internally, potentially even from a different package.
Since the behavior of external functions may change over time (for example due to refactoring), the most conservative approach for such cases is to document exception propagation explicitly:
"""
Raises:
BaseException:
|May| propagate from functions ... and ...
"""
3.6. Function and method docstrings – intermediate example¶
The following example demonstrates a non-trivial method docstring that goes beyond the minimal structure, but does not yet use all available sections and subsections.
The method shown here is part of an abstract base class. It documents expected behavior, required preconditions, and possible failure modes, even though the method itself is not implemented in the base class.
This pattern is typical for framework-style code, where derived classes are expected to provide the actual implementation while adhering to a shared contract.
Most of the sections used in this example have already been introduced
for module and class docstrings. However, the Contract
section now contains a requires subsection, which is
optional but normative as part of the contract.
The requires subsection should be used to specify the
conditions that must be satisfied by the caller in order for the
function to operate correctly.
The functionality of requires partially overlaps with
Raises, as unmet prerequisites often lead to error
conditions. Since such error behavior can also be described in
Raises, the requires subsection itself is
optional.
from typing import List, TypeAlias, Union
class tracer:
pass
DocstringSubtree: TypeAlias = Union[str, List["DocstringSubtree"]]
class docitem_base:
def parse(self,tr : tracer,subtree : DocstringSubtree) -> None:
"""
Preamble:
profile:
method
normative_sections:
Contract, Parameters, Returns, Raises
Description:
This docstring is located in the base class of all docitem
node classes. The method is not implemented here and will
raise an exception if it is invoked without a corresponding
implementation in a derived class.
Contract:
general:
|Must| parse a docstring subtree and create the related child items.
requires:
:wtrl_var:`subtree` |must| be a formally correct docstring subtree\
from a Waterloo docstring; otherwise parsing will fail.
Parameters:
tr:
The tracer for collecting diagnostics.
subtree:
A subtree of the tree matching this instance.
Returns:
|None|
Raises:
NotImplementedError:
|Must| raise if not implemented in the derived class.
RuntimeError:
|Must| raise if the subtree does not match the expected format.
"""
raise NotImplementedError
Note that the function signature is not repeated in the docstring. Instead, it is included in the documentation via dedicated directives, ensuring that the rendered documentation remains complete without violating the Single Source of Truth principle (SSoT).
In this example, the function signature is annotated and treated as the sole authoritative source of type information, in accordance with the Single Source of Truth principle. This prevents the function signature and its documentation from drifting apart, supporting the Drift Prevention (DrPrv) principle.
As a consequence, the function signature must be explicitly included in the rendered documentation. This can be achieved using the directive
.. wtrl_function_signature:: test_docitem_function_medium.docitem_base.parse
For more complex type annotations, the directive
.. wtrl_function_signature_block:: test_docitem_function_medium.docitem_base.parse
may be used, which renders one line per parameter:
While there is some flexibility in how the signature is presented, it must be included in order to make the documentation complete.
|
test_docitem_function_medium.docitem_base.parse(
tr: tracer
subtree: str | typing.List[ForwardRef(‘DocstringSubtree’)]
) -> None
|
|
|
|
This docstring is located in the base class of all docitem node classes. The method is not implemented here and will raise an exception if it is invoked without a corresponding implementation in a derived class. |
|
|
|
|
|
|
|
|
Method |
docitem_base.parse |
|---|
3.7. Interlude – Docstring for inherited methods¶
The following example shows how to build a docstring for an inherited method.
It is noticeable that, apart from the usual sections Preamble
and Contract, there are no other mandatory sections. In particular,
the sections Parameters, Returns, and Raises
are missing because they are already included in the normative documentation of the
base method. In section Contract, we see how this base method
is explicitly referenced using the mandatory subsection base.
class docitem_list_of_symbols_base(docitem_base):
def _parse(self, tr: tracer, refs: DocstringSubtree, pattern = str):
# Some implementation here...
# ...
pass
class docitem_traits(docitem_list_of_symbols_base):
def parse(self, tr: tracer, refs: DocstringSubtree) -> None:
"""
Preamble:
profile:
inherited_method
normative_sections:
Contract
Contract:
general:
|Must| set rules-on-fail in the tracer and delegate\
to method :wtrl_func:`._parse` in class\
:wtrl_type:`docitem_list_of_symbols_base`.
base:
test_docitem_function_medium.docitem_base.parse
"""
# Some implementation here...
# ...
pass
|
test_docitem_function_medium.docitem_traits.parse(
tr: tracer
refs: str | typing.List[ForwardRef(‘DocstringSubtree’)]
) -> None
|
|
|
|
|
Inherited_method |
docitem_traits.parse |
|---|
Apart from these differences, there are optional sections that are used in the same way as for modules, classes, and functions. So, all in all we have:
Mandatory and always normative
Subsection base is mandatory and normative. It unambiguously refers
to the base method whose contract is authoritative for the derived method.
A derived method docstring may add additional normative statements, but it must
remain substitutable for the base method.
Preconditions: A derived method
must notstrengthen the base method’s preconditions. In particular, any requirement that constrains valid inputsmustbe expressed inContract.requiresandmustbe compatible with the base method.Guarantees: A derived method
mustpreserve all guarantees of the base method. A derived methodmayprovide additional guarantees by adding or refiningContract.ensures. Callers that only rely on the base contractmayignore such additional guarantees.Failure modes documented in the base method
must notbe replaced by weaker guarantees. Additional failure modesmust notoccur for inputs that satisfy the base preconditions, unless they are explicitly permitted by the base contract.
Optional and always normative
Section
DefinitionsSubsection
Contract.requiresas discussed above under “Preconditions”Subsection
Contract.ensuresas discussed above under “Guarantees”
Optional and normative under conditions
Sections Description and See_also
Optional and never normative
Sections Terminology and Notes
3.8. Function docstrings – full example¶
from __future__ import annotations
from typing import Final
_MAX_N: Final[int] = 20 # 20! fits into 64-bit signed; adjust as desired
def test() -> None:
"""
Preamble:
profile:
function
normative_sections:
Contract, Parameters, Returns, Raises
Description:
By this function we test the See_also section in factorial.
Since :trl_label:`See_also` is normative (in this case)
the docstring in the referred object must be a valid Waterloo string.
Contract:
general:
|Must| do nothing.
Parameters:
Returns:
|None|
Raises:
"""
def factorial(n: int) -> int:
"""
Preamble:
profile:
function
normative_sections:
Definitions, Contract, Parameters, Returns, Raises, See_also
Definitions:
factorial:
For a non-negative integer n, factorial(n) is defined recursively by:
factorial(0) = 1 and factorial(n) = n * factorial(n-1) for n > 0.
positive:
An integer x is called ``positive`` if x > 0.
Description:
This function provides a simple, deterministic reference implementation.
It is intended to demonstrate a full Waterloo function docstring with a
clear contract and well-defined failure modes.
Terminology:
recursion:
The definition uses recursion; the implementation does not have to.
Contract:
general:
|Must| compute |term|`factorial` for the given argument.
|Must| be thread-safe.
requires:
|var|`n` |must| be an integer.
|var|`n` |must| be greater than or equal to 0.
|var|`n` |must| be less than or equal to |var|`_MAX_N`.
ensures:
The return value |must| be an integer.
If |var|`n` is 0, the result |must| be 1.
If |var|`n` is greater than 0, the result |must| be |term|`positive`.
The result |must| be divisible by |var|`n` for all |var|`n` > 0.
Parameters:
n:
Non-negative integer input.
Returns:
The factorial value.
Raises:
TypeError:
|Must| raise if |var|`n` is not an |type|`int`.
ValueError:
|Must| raise if |var|`n` is negative.
OverflowError:
|Must| raise if |var|`n` is greater than |var|`_MAX_N`.
See_also:
math.factorial, test
"""
if not isinstance(n, int):
raise TypeError("factorial: n must be an int")
if n < 0:
raise ValueError("factorial: n must be >= 0")
if n > _MAX_N:
raise OverflowError(f"factorial: n must be <= {_MAX_N}")
# Deterministic iterative implementation
result = 1
for k in range(2, n + 1):
result *= k
return result
|
test_docitem_function_full.factorial(
n: int
) -> int
|
|
|
|
|
|
This function provides a simple, deterministic reference implementation. It is intended to demonstrate a full Waterloo function docstring with a clear contract and well-defined failure modes. |
|
|
|
|
|
|
|
The factorial value. |
|
|
|
|
Function |
factorial |
|---|