Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to document small changes to complex API functions?

Let's say we have a complex API function, imported from some library.

def complex_api_function(
        number, <lots of positional arguments>,
        <lots of keyword arguments>):
    '''really long docstring'''
    # lots of code

I want to write a simple wrapper around that function to make a tiny change. For example, it should be possible to pass the first argument as a string. How to document this? I considered the following options:

Option 1:

def my_complex_api_function(number_or_str, *args, **kwargs):
    '''
    Do something complex.

    Like `complex_api_function`, but first argument can be a string.

    Parameters
    ----------
    number_or_str : int or float or str
        Can be a number or a string that can be interpreted as a float.
        <copy paste description from complex_api_function docstring>
    *args
        Positional arguments passed to `complex_api_function`.
    **kwargs
        Keyword arguments passed to `complex_api_function`.

    Returns
    -------
    <copy paste from complex_api_function docstring>

    Examples
    --------
    <example where first argument is a string, e.g. '-5.0'>

    '''
    return complex_api_function(float(number_or_str), *args, **kwargs)

Disadvantage: User has to look at the docs of complex_api_function to get information about *args and **kwargs. Needs adjustment when the copy pasted sections from complex_api_function change.

Option 2:

Copy and paste complex_api_function's signature (instead of using *args and **kwargs) and its docstring. Make a tiny change to the docstring that mentions that the first argument can also be a string. Add an example.

Disadvantage: verbose, has to be changed when complex_api_function changes.

Option 3:

Decorate my_complex_api_function with functools.wraps(complex_api_function).

Disadvantage: There's no information that number can also be a string.


I'm looking for an answer that does not hinge on the details of what changes in my_complex_api_function. The procedure should work for any tiny adjustment to the original complex_api_function.

like image 339
actual_panda Avatar asked Mar 24 '20 08:03

actual_panda


2 Answers

You could automate the "specialization" of the original docstring with an addendum. For instance, pydoc is using the special attribute __doc__. You could write a decorator which automatically overrides the original function __doc__ with your addendum.

For instance:

def extend_docstring(original, addendum):
    def callable(func):
        func.__doc__ = original + addendum
        return func

    return callable


def complex_api_function(a, b, c):
    '''
    This is a very complex function.

    Parameters
    ----------
    a: int or float
        This is the argument A.
    b: ....
    '''
    print('do something')

@extend_docstring(
    complex_api_function.__doc__,
    '''
    Addendum
    --------
    Parameter a can also be a string
    '''
)
def my_complex_api_function(a, b, c):
    return complex_api_function(float(a), b, c)

or...

def extend_docstring(original):
    def callable(func):
        func.__doc__ = original + func.__doc__
        return func

    return callable


def complex_api_function(a, b, c):
    '''
    This is a very complex function.

    Parameters
    ----------
    a: int or float
        This is the argument A.
    b: ....
    '''
    print('do something')

@extend_docstring(complex_api_function.__doc__)
def my_complex_api_function(a, b, c):
    '''
    Addendum
    --------
    Parameter a can also be a string
    '''
    return complex_api_function(float(a), b, c)

If you run pydoc (pydoc3 -w my_module.py) it produces: preview of html generated by pydoc

Additional note: If you are using Python 3 you could use annotations to document the type(s) of your function parameters. It offers a lot of benefits, not only documentation. For instance:

from typing import Union

def my_complex_api_function(number_or_str: Union[int, float, str], *args, **kwargs):
like image 63
Raphael Medaer Avatar answered Nov 19 '22 06:11

Raphael Medaer


I'd recommend something like the following:

def my_complex_api_function(number_or_str, *args, **kwargs):
    """This function is a light wrapper to `complex_api_function`.
    It allows you to pass a string or a number, whereas `complex_api_function` requires a 
    number. See :ref:`complex_api_function` for more details.

    :param number_or_str: number or str to convert to a number and pass to `complex_api_function`.
    :param args: Arguments to pass to `complex_api_function`
    :param kwargs: Keyword arguments to pass to `complex_api_function`
    :return: Output of `complex_api_function`, called with passed parameters
    """

This is clear and concise. But please also remember that, if using a documentation system like sphinx, to link the functions with :ref:`bob` or similar.

like image 3
Legorooj Avatar answered Nov 19 '22 06:11

Legorooj