Documenting Models¶
Introduction¶
This is the third tutorial in the Intro Series where we do a deeper dive into the topic of documenting models. One of the underlying principles of the Footings framework
is models should be self documenting.
To start, it is important to note that the Footings framework
supports numpy style docstrings and internally builds off numpydoc to generate docstrings and the associated objects for generating documentation in sphinx.
Documented Model¶
Below is the code for AddABC
from the prior two tutorials with documentations.
from footings.model import (
model,
step,
def_parameter,
def_intermediate,
def_return,
)
@model(steps=["_add_a_b", "_add_ab_c"])
class AddABC:
"""This model takes 3 parameters - a, b, and c and adds them together in two steps."""
a = def_parameter(dtype=int, description="This is parameter a.")
b = def_parameter(dtype=int, description="This is parameter b.")
c = def_parameter(dtype=int, description="This is parameter c.")
ab = def_intermediate(dtype=int, description="This holds a + b.")
abc = def_return(dtype=int, description="The sum of ab and c.")
@step(uses=["a", "b"], impacts=["ab"])
def _add_a_b(self):
"""Add a and b together and assign to ab."""
self.ab = self.a + self.b
@step(uses=["ab", "c"], impacts=["abc"])
def _add_ab_c(self):
"""Add ab and c together for final return."""
self.abc = self.ab + self.c
As the above code shows -
Documentation about the model is added as a standard python docstring directly underneath the class.
Documentation for attributes, including the data types, should be set within the
def_*
function.Information on each step can be added as a docstring underneath the method defining that step.
Do note the attributes could be documented in the associated docstring. However, it is recommended to put the attribute documentation within the def_*
function. Lastly, being able to pass documentation (i.e., dtype and description) into functions as code allows us to move the source of the documentation to be elsewhere.
The below code demonstrates this. Assume the model developer wants to have a data dictionary file that is the true source of information underlying the parameters.
import yaml
data_dictionary_file = """
a:
dtype: int
description: This is parameter a.
b:
dtype: int
description: This is parameter b.
c:
dtype: int
description: This is parameter c.
"""
data_dictionary = yaml.safe_load(data_dictionary_file)
data_dictionary
{'a': {'dtype': 'int', 'description': 'This is parameter a.'},
'b': {'dtype': 'int', 'description': 'This is parameter b.'},
'c': {'dtype': 'int', 'description': 'This is parameter c.'}}
We can then pass the information from the data dictionary file into the associated parameters.
@model(steps=["_add_a_b", "_add_ab_c"])
class AddABC:
"""This model takes 3 parameters - a, b, and c and adds them together in two steps."""
a = def_parameter(**data_dictionary["a"])
b = def_parameter(**data_dictionary["b"])
c = def_parameter(**data_dictionary["c"])
ab = def_intermediate(dtype=int, description="This holds a + b.")
abc = def_return(dtype=int, description="The sum of ab and c.")
@step(uses=["a", "b"], impacts=["ab"])
def _add_a_b(self):
"""Add a and b together and assign to ab."""
self.ab = self.a + self.b
@step(uses=["ab", "c"], impacts=["abc"])
def _add_ab_c(self):
"""Add ab and c together for final return."""
self.abc = self.ab + self.c
Viewing the Documentation¶
To see what the documentation looks like to a user call help
on the model.
help(AddABC)
Help on class AddABC in module footings.model:
class AddABC(__main__.AddABC, Model)
| AddABC(*, a: 'int', b: 'int', c: 'int') -> None
|
| This model takes 3 parameters - a, b, and c and adds them together in two steps.
|
| .. rubric:: Parameters
|
| - **a (int)** - This is parameter a.
| - **b (int)** - This is parameter b.
| - **c (int)** - This is parameter c.
|
| .. rubric:: Intermediates
|
| - **ab (int)** - This holds a + b.
|
| .. rubric:: Returns
|
| - **abc (int)** - The sum of ab and c.
|
| .. rubric:: Steps
|
| 1) **_add_a_b** - Add a and b together and assign to ab.
| 2) **_add_ab_c** - Add ab and c together for final return.
|
| Method resolution order:
| AddABC
| __main__.AddABC
| Model
| builtins.object
|
| Methods defined here:
|
| __eq__(self, other)
| Method generated by attrs for class AddABC.
|
| __ge__(self, other)
| Method generated by attrs for class AddABC.
|
| __getstate__ = slots_getstate(self)
| Automatically created by attrs.
|
| __gt__(self, other)
| Method generated by attrs for class AddABC.
|
| __init__(self, *, a: 'int', b: 'int', c: 'int') -> None
| Method generated by attrs for class AddABC.
|
| __le__(self, other)
| Method generated by attrs for class AddABC.
|
| __lt__(self, other)
| Method generated by attrs for class AddABC.
|
| __ne__(self, other)
| Method generated by attrs for class AddABC.
|
| __setattr__(self, name, val)
| Method generated by attrs for class AddABC.
|
| __setstate__ = slots_setstate(self, state)
| Automatically created by attrs.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| a
| Intermediate representation of attributes that uses a counter to preserve
| the order in which the attributes have been defined.
|
| *Internal* data structure of the attrs library. Running into is most
| likely the result of a bug like a forgotten `@attr.s` decorator.
|
| ab
| Intermediate representation of attributes that uses a counter to preserve
| the order in which the attributes have been defined.
|
| *Internal* data structure of the attrs library. Running into is most
| likely the result of a bug like a forgotten `@attr.s` decorator.
|
| abc
| Intermediate representation of attributes that uses a counter to preserve
| the order in which the attributes have been defined.
|
| *Internal* data structure of the attrs library. Running into is most
| likely the result of a bug like a forgotten `@attr.s` decorator.
|
| b
| Intermediate representation of attributes that uses a counter to preserve
| the order in which the attributes have been defined.
|
| *Internal* data structure of the attrs library. Running into is most
| likely the result of a bug like a forgotten `@attr.s` decorator.
|
| c
| Intermediate representation of attributes that uses a counter to preserve
| the order in which the attributes have been defined.
|
| *Internal* data structure of the attrs library. Running into is most
| likely the result of a bug like a forgotten `@attr.s` decorator.
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| __attrs_attrs__ = (Attribute(name='__model_steps__', default=NOTHI...e...
|
| __attrs_own_setattr__ = True
|
| __hash__ = None
|
| ----------------------------------------------------------------------
| Data descriptors inherited from __main__.AddABC:
|
| __weakref__
| list of weak references to the object (if defined)
|
| ----------------------------------------------------------------------
| Data and other attributes inherited from __main__.AddABC:
|
| __model_attribute_map__ = {'a': 'parameter.a', 'ab': 'intermediate.ab'...
|
| __model_intermediates__ = ('ab',)
|
| __model_meta__ = ()
|
| __model_parameters__ = ('a', 'b', 'c')
|
| __model_returns__ = ('abc',)
|
| __model_sensitivities__ = ()
|
| __model_steps__ = ('_add_a_b', '_add_ab_c')
|
| ----------------------------------------------------------------------
| Methods inherited from Model:
|
| audit(self, file: str = None, **kwargs)
| Audit the model which returns copies of the object as it is modified across each step.
|
| :param Union[str, None] file: The name of the audit output file.
| :param kwargs: Additional key words passed to audit.
|
| :return: If file is None, an AuditContainer else an audit file in specfified format (e.g., .xlsx).
|
| run(self, to_step=None)
| Runs the model and returns any returns defined.
|
| :param Union[str, None]: The name of the step to run model through.
|
| visualize(self)
| Visualize the model to get an understanding of what model attributes are used and when.
|
| ----------------------------------------------------------------------
| Data and other attributes inherited from Model:
|
| __annotations__ = {'__model_attribute_map__': <class 'dict'>, '__model...
When viewing the output, you will see the attributes are split into sections based on the def_*
function called. In addition, directly below the return detail is a section titled Steps
with the method name of the step and the description of the step.
This layout of the documentation will flow through if using sphinx. Be sure to include footings.doc_tools
as an extension in your conf.py
file. For an example of this see the footings-idi-model
repository and documentation.
Closing¶
When building a model using the Footings framework
, the developer needs to document the different components of the model and the footings
library will organize those components into consumable documentation. Model documentation is paired best with sphinx and a CI/CD pipeline producing versioned documentation along side your model code.