Class for nothing
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.
"""
Preamble |
normative sections
|
Contract |
general
|
| «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 Terminology and Notes sections 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_overview and Function_overview are informative by design. Normative statements in *_overview sections 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 *_overview and Public_* 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
Preamble |
normative sections
|
Definitions |
MyItem This is a term named 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
|
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 |
|
Class overview |
|
Public functions |
|
Function overview |
Important for demonstration but does nothing |
Public types |
Type Alias for float | int 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 |
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 |
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 |
|
| «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
Preamble |
normative sections
|
Contract |
general
constructor
|
| «Class» | MyClass – from test_docitem_class_minimal |
|---|
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")
Preamble |
normative sections
|
Definitions |
MyExampleItem Define a term normatively. Same as for modules. |
Terminology |
Fancy-Unicorn Define a term informatively. Same as for modules. |
Contract |
general
constructor
traits
|
Derived from |
|
Description |
As for modules |
Notes |
Purpose and Syntax Same as for modules |
Public classes |
|
Class overview |
Use this section for informative reminders what the class is good for. Multiple lines are possible. Do not use Normativity Keywords. |
Public methods |
|
Method overview |
Important for demonstration but does nothing |
Public types |
Type Alias for float | int Important type for annotations |
Public variables |
A variable |
Public constants |
Another constant |
See also |
|
| «Class» | MyClass – from test_docitem_class_full |
|---|
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:
"""
Signature |
test_docitem_function_minimal.spam(
) -> None
|
Preamble |
normative sections
|
Contract |
general
|
Parameters |
<empty> |
Returns |
|
Raises |
<empty> |
| «Function» | spam – from test_docitem_function_minimal |
|---|
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.
Signature |
test_docitem_function_medium.docitem_base.parse(
tr: tracer
subtree: str | typing.List[ForwardRef(‘DocstringSubtree’)]
) -> None
|
Preamble |
normative sections
|
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
requires
|
Parameters |
The tracer for collecting diagnostics.
A subtree of the tree matching this instance. |
Returns |
|
Raises |
|
| «Method» | docitem_base.parse – from test_docitem_function_medium |
|---|
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
Signature |
test_docitem_function_medium.docitem_traits.parse(
tr: tracer
refs: str | typing.List[ForwardRef(‘DocstringSubtree’)]
) -> None
|
Preamble |
normative sections
|
Contract |
general
base |
| «Inherited_method» | docitem_traits.parse – from test_docitem_function_medium |
|---|
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 not strengthen the base method’s preconditions. In particular, any requirement that constrains valid inputs must be expressed in Contract.requires and must be compatible with the base method.
Guarantees: A derived method must preserve all guarantees of the base method. A derived method may provide additional guarantees by adding or refining Contract.ensures. Callers that only rely on the base contract may ignore such additional guarantees.
Failure modes documented in the base method must not be replaced by weaker guarantees. Additional failure modes must not occur for inputs that satisfy the base preconditions, unless they are explicitly permitted by the base contract.
Optional and always normative
Section Definitions
Subsection Contract.requires as discussed above under “Preconditions”
Subsection Contract.ensures as 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
Signature |
test_docitem_function_full.factorial(
n: int
) -> int
|
Preamble |
normative sections
|
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
requires
ensures
|
Parameters |
Non-negative integer input. |
Returns |
The factorial value. |
Raises |
|
See also |
|
| «Function» | factorial – from test_docitem_function_full |
|---|