docfiller#
Note
docfiller
assumes Numpy style docstrings
It is common to want to share documentation information across functions/methods. The module docfiller
provides a way to do this:
For example, if you have these function:
def a_function(x, y, z) -> None:
"""
A function.
A longer description
Parameters
----------
x : float
An x param
y : int
A y param
z : str
A z param
Returns
-------
output : float
An output
"""
def b_function(x, y, z) -> None:
"""
A different function.
A longer description of b_function
Parameters
----------
x : float
An x param
y : float
A different y param
z : str
A z param
Returns
-------
output : float
An different output
"""
It would be nice to share info. DocFiller
provides a few ways to do this.
First, we can define common docstrings like so:
from module_utilities.docfiller import DocFiller
docstrings = """
Parameters
----------
x : float
An x param
z : str
A z param
"""
d = DocFiller.from_docstring(docstrings, combine_keys="parameters")
d.data
{'summary': '',
'extended_summary': '',
'parameters': {'x': 'x : float\n An x param',
'z': 'z : str\n A z param'},
'returns': '',
'yields': '',
'notes': '',
'warnings': '',
'other_parameters': '',
'attributes': '',
'methods': '',
'references': '',
'examples': '',
'x': 'x : float\n An x param',
'z': 'z : str\n A z param'}
This parses the docstring
(using numpydoc). Then, we can define functions as:
from textwrap import dedent
@d()
def a_function2(x, y, z) -> None:
"""
A function.
A longer description
Parameters
----------
{x}
y : int
A y param
{z}
Returns
-------
output : float
An output
"""
@d()
def b_function2(x, y, z) -> None:
"""
A different function.
A longer description of b_function
Parameters
----------
{x}
y : float
A different y param
{z}
Returns
-------
output : float
An different output
"""
assert dedent(a_function.__doc__) == a_function2.__doc__
assert dedent(b_function.__doc__) == b_function2.__doc__
print(a_function2.__doc__)
print(b_function2.__doc__)
A function.
A longer description
Parameters
----------
x : float
An x param
y : int
A y param
z : str
A z param
Returns
-------
output : float
An output
A different function.
A longer description of b_function
Parameters
----------
x : float
An x param
y : float
A different y param
z : str
A z param
Returns
-------
output : float
An different output
Using templates#
Better still is to generalize the template:
@(
(
d.update(
summary="A function.",
extended_summary="""
A longer description
""",
)
.assign_param("y", "int", "A y param")
.assign_param("output", "float", "An output")
)
.dedent()
.decorate
)
def a_function3(x, y, z) -> None:
"""
{summary}
{extended_summary}
Parameters
----------
{x}
{y}
{z}
Returns
-------
{output}
"""
assert a_function3.__doc__ == a_function2.__doc__
@(
d.update(
summary="A different function.",
extended_summary="A longer description of b_function",
)
.assign_param("y", "float", "A different y param")
.assign_param("output", "float", "An different output")
).dedent()(a_function3)
def b_function3(x, y, z) -> None:
pass
assert b_function3.__doc__ == b_function2.__doc__
Note that for b_function3
, we used the template/docstring from a_function3
. DocFiller
defaults to using
the functions docstring for replacement. But you can pass a string or function to use as the template to be filled. Above, we also used DocFiller.assign_param()
to create a correctly formatted parameter.
Define parameters/returns that have different key values#
You may find that you can define a host of shared parameter documentation, except that there are a few edge cases.
For example, say we have a parameter beta
that in some cases is an array, and in others is a float. You could handle this as follows
docstring = """
Parameters
----------
beta_float | beta : float
A float value for beta
beta_array | beta : array-like
An array of beta values
x : float
x parameter
y : float
y parameter
"""
d = DocFiller.from_docstring(docstring, combine_keys="parameters")
# using `.decorate` uses the decorator without template or parameters
@d.decorate
def func_float(x, y, beta) -> None:
"""
A thing
Parameters
----------
{x}
{y}
{beta_float}
"""
@d.decorate
def func_array(x, y, beta) -> None:
"""
An array thing
Parameters
----------
{x}
{y}
{beta_array}
"""
print(func_float.__doc__)
print(func_array.__doc__)
A thing
Parameters
----------
x : float
x parameter
y : float
y parameter
beta : float
A float value for beta
An array thing
Parameters
----------
x : float
x parameter
y : float
y parameter
beta : array-like
An array of beta values
Or, if everything else is the same, you could do:
# back to call because passing parameters
@d(beta=d["beta_float"], summary="A thing")
def func_float2(x, y, beta) -> None:
"""
{summary}
Parameters
----------
{x}
{y}
{beta}
"""
@d(func_float2, beta=d["beta_array"], summary="An array thing")
def func_array2(x, y, beta) -> None:
pass
assert func_float2.__doc__ == func_float.__doc__
assert func_array2.__doc__ == func_array.__doc__
Better still, you can use DocFiller.assign_keys()
:
@d.assign_keys(beta="beta_float")(summary="A thing")
def func_float3(x, y, beta) -> None:
"""
{summary}
Parameters
----------
{x}
{y}
{beta}
"""
@d.assign_keys(beta="beta_array")(func_float3, summary="An array thing")
def func_array3(x, y, beta) -> None:
pass
assert func_float3.__doc__ == func_float.__doc__
assert func_array3.__doc__ == func_array.__doc__
from module_utilities.docfiller import DocFiller
d = DocFiller.from_docstring(
"""
Parameters
----------
x : int
x param
y : float
y param
z0 | z : int
z int param
z1 | z : float
z float param
""",
combine_keys="parameters",
)
@d()
def func0() -> None:
"""
Parameters
----------
{x}
{y}
{z0}
"""
print(func0.__doc__)
Parameters
----------
x : int
x param
y : float
y param
z : int
z int param
Works with classes#
This also works with classes
expected = """
A summary
A longer summary
Parameters
----------
x : float
x param
some other stuff
y : float
y param
Returns
-------
out : float
output
"""
d = DocFiller.from_docstring(expected, combine_keys="parameters")
@d()
class Hello:
"""
{summary}
{extended_summary}
Parameters
----------
{x}
{y}
Returns
-------
{returns.out}
"""
assert Hello.__doc__ == expected
@d(Hello)
class Hello2(Hello):
pass
assert Hello2.__doc__ == expected
print(Hello2.__doc__)
A summary
A longer summary
Parameters
----------
x : float
x param
some other stuff
y : float
y param
Returns
-------
out : float
output
There are cases where you’d like to update the documentation from one implementation to another. For example, say you have the following functions:
@d.decorate
def func_xy(x, y):
"""
Add numbers
Parameters
----------
{x}
{y}
Returns
-------
output : float
Sum of input
"""
return x + y
print(func_xy.__doc__)
Add numbers
Parameters
----------
x : float
x param
some other stuff
y : float
y param
Returns
-------
output : float
Sum of input
But what if you want a new version that adds three numbers func_xyz(x, y, z)
? Most everything is the same as for func_xy
above, but we need to add z
. For this, we have an interface to docstring-inheritance
@d.inherit(func_xy)
def func_xyz(x, y, z):
"""
Parameters
----------
z : float
Another parameter
"""
return x + y + z
print(func_xyz.__doc__)
Add numbers
Parameters
----------
x : float
x param
some other stuff
y : float
y param
z : float
Another parameter
Returns
-------
output : float
Sum of input
For the special case that you want to inherit from methods of a baseclass, you can use the following:
class Base:
def prop(self) -> int:
"""A property"""
return 1
@d.decorate
def meth(self, x):
"""
A method
Parameters
----------
{x}
Returns
-------
float
Double x
"""
return x * 2
d_from_base = d.factory_inherit_from_parent(Base)
class Derived:
@d_from_base()
def prop(self) -> None:
pass
@d_from_base()
def meth(self, x, y):
"""
Parameters
----------
{y}
"""
return (x + y) * 2
help(Base)
Help on class Base in module __main__:
class Base(builtins.object)
| Methods defined here:
|
| meth(self, x)
| A method
|
|
| Parameters
| ----------
| x : float
| x param
| some other stuff
|
|
| Returns
| -------
| float
| Double x
|
| prop(self) -> int
| A property
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables
|
| __weakref__
| list of weak references to the object
help(Derived)
Help on class Derived in module __main__:
class Derived(builtins.object)
| Methods defined here:
|
| meth(self, x, y)
| A method
|
| Parameters
| ----------
| x : float
| x param
| some other stuff
| y : float
| y param
|
| Returns
| -------
| float
| Double x
|
| prop(self) -> None
| A property
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables
|
| __weakref__
| list of weak references to the object