Docments

Document parameters using comments.

docments provides programmatic access to comments in function parameters and return types. It can be used to create more developer-friendly documentation, CLI, etc tools.

Why?

Without docments, if you want to document your parameters, you have to repeat param names in docstrings, since they’re already in the function signature. The parameters have to be kept synchronized in the two places as you change your code. Readers of your code have to look back and forth between two places to understand what’s happening. So it’s more work for you, and for your users.

Furthermore, to have parameter documentation formatted nicely without docments, you have to use special magic docstring formatting, often with odd quirks, which is a pain to create and maintain, and awkward to read in code. For instance, using numpy-style documentation:

def add_np(a:int, b:int=0)->int:
    """The sum of two numbers.
    
    Used to demonstrate numpy-style docstrings.

Parameters
----------
a : int
    the 1st number to add
b : int
    the 2nd number to add (default: 0)

Returns
-------
int
    the result of adding `a` to `b`"""
    return a+b

By comparison, here’s the same thing using docments:

def add(
    a:int, # the 1st number to add
    b=0,   # the 2nd number to add
)->int:    # the result of adding `a` to `b`
    "The sum of two numbers."
    return a+b

Numpy docstring helper functions

docments also supports numpy-style docstrings, or a mix or numpy-style and docments parameter documentation. The functions in this section help get and parse this information.


docstring

 docstring (sym)

Get docstring for sym for functions ad classes

test_eq(docstring(add), "The sum of two numbers.")

parse_docstring

 parse_docstring (sym)

Parse a numpy-style docstring in sym

parse_docstring(add_np)
{ 'Extended': 'Used to demonstrate numpy-style docstrings.',
  'Parameters': { 'a': Parameter(name='a', type='int', desc=['the 1st number to add']),
                  'b': Parameter(name='b', type='int', desc=['the 2nd number to add (default: 0)'])},
  'Returns': Parameter(name='', type='int', desc=['the result of adding `a` to `b`']),
  'Summary': 'The sum of two numbers.'}

Usage


get_source

 get_source (s)

Get source code for string, function object or dataclass s


get_dataclass_source

 get_dataclass_source (s)

Get source code for dataclass s


isdataclass

 isdataclass (s)

Check if s is a dataclass but not a dataclass’ instance


get_name

 get_name (obj)

Get the name of obj

test_eq(get_name(in_ipython), 'in_ipython')
test_eq(get_name(L.map), 'map')

qual_name

 qual_name (obj)

Get the qualified name of obj

assert qual_name(docscrape) == 'fastcore.docscrape'

docments

 docments (elt, full=False, returns=True, eval_str=False)

Generates a docment

Type Default Details
elt
full bool False
returns bool True Argument passed to _docments
eval_str bool False Argument passed to _docments

The returned dict has parameter names as keys, docments as values. The return value comment appears in the return, unless returns=False. Using the add definition above, we get:

docments(add)
{ 'a': 'the 1st number to add',
  'b': 'the 2nd number to add',
  'return': 'the result of adding `a` to `b`'}

If you pass full=False, the values are dict of defaults, types, and docments as values. Note that the type annotation is inferred from the default value, if the annotation is empty and a default is supplied.

docments(add, full=True)
{ 'a': { 'anno': 'int',
         'default': <class 'inspect._empty'>,
         'docment': 'the 1st number to add'},
  'b': { 'anno': <class 'int'>,
         'default': 0,
         'docment': 'the 2nd number to add'},
  'return': { 'anno': 'int',
              'default': <class 'inspect._empty'>,
              'docment': 'the result of adding `a` to `b`'}}
docments(add, full=True)
{ 'a': { 'anno': 'int',
         'default': <class 'inspect._empty'>,
         'docment': 'the 1st number to add'},
  'b': { 'anno': <class 'int'>,
         'default': 0,
         'docment': 'the 2nd number to add'},
  'return': { 'anno': 'int',
              'default': <class 'inspect._empty'>,
              'docment': 'the result of adding `a` to `b`'}}

To evaluate stringified annotations (from python 3.10), use eval_str:

docments(add, full=True, eval_str=True)['a']
{ 'anno': <class 'int'>,
  'default': <class 'inspect._empty'>,
  'docment': 'the 1st number to add'}

If you need more space to document a parameter, place one or more lines of comments above the parameter, or above the return type. You can mix-and-match these docment styles:

def add(
    # The first operand
    a:int,
    # This is the second of the operands to the *addition* operator.
    # Note that passing a negative value here is the equivalent of the *subtraction* operator.
    b:int,
)->int: # The result is calculated using Python's builtin `+` operator.
    "Add `a` to `b`"
    return a+b
docments(add)
{ 'a': 'The first operand',
  'b': 'This is the second of the operands to the *addition* operator.\n'
       'Note that passing a negative value here is the equivalent of the '
       '*subtraction* operator.',
  'return': "The result is calculated using Python's builtin `+` operator."}

You can also use docments with classes and methods:

class Adder:
    "An addition calculator"
    def __init__(self,
        a:int, # First operand
        b:int, # 2nd operand
    ): self.a,self.b = a,b
    
    def calculate(self
                 )->int: # Integral result of addition operator
        "Add `a` to `b`"
        return a+b
docments(Adder)
{'a': 'First operand', 'b': '2nd operand', 'return': None, 'self': None}
docments(Adder.calculate)
{'return': 'Integral result of addition operator', 'self': None}

docments can also be extracted from numpy-style docstrings:

print(add_np.__doc__)
The sum of two numbers.
    
    Used to demonstrate numpy-style docstrings.

Parameters
----------
a : int
    the 1st number to add
b : int
    the 2nd number to add (default: 0)

Returns
-------
int
    the result of adding `a` to `b`
docments(add_np)
{ 'a': 'the 1st number to add',
  'b': 'the 2nd number to add (default: 0)',
  'return': 'the result of adding `a` to `b`'}

You can even mix and match docments and numpy parameters:

def add_mixed(a:int, # the first number to add
              b
             )->int: # the result
    """The sum of two numbers.

Parameters
----------
b : int
    the 2nd number to add (default: 0)"""
    return a+b
docments(add_mixed, full=True)
{ 'a': { 'anno': 'int',
         'default': <class 'inspect._empty'>,
         'docment': 'the first number to add'},
  'b': { 'anno': 'int',
         'default': <class 'inspect._empty'>,
         'docment': 'the 2nd number to add (default: 0)'},
  'return': { 'anno': 'int',
              'default': <class 'inspect._empty'>,
              'docment': 'the result'}}

You can use docments with dataclasses:

@dataclass
class Person:
    name:str # The name of the person
    age:int # The age of the person
    weight:float # The weight of the person

docments(Person)
{'age': None, 'name': None, 'return': None, 'weight': None}

Caveat: if class was defined in online notebook, docments will not contain parameters’ comments. This is because the source code is not available in the notebook. After converting the notebook to a script, the docments will be available. Thus, documentation will have correct parameters’ comments.

Docments even works with delegates:

from fastcore.meta import delegates
def _a(a:int=2): return a # First

@delegates(_a)
def _b(b:str, **kwargs): return b, (_a(**kwargs)) # Second
docments(_b)
{'a': 'First passed to `_a`', 'b': 'Second', 'return': None}

If you pass verbose=False to delegates, the 'passed to{func}' part will be excluded from docments:

@delegates(_a, verbose=False)
def _e(b:str, **kwargs): return b, (_a(**kwargs)) # Second, not passed
docments(_e)
{'a': 'First', 'b': 'Second, not passed', 'return': None}