2

I'm using Google-style docstrings with sphinx.ext.autodoc to automatically generate documentation for my functions, and make sure they are properly self-documented in the code.

I have a function def myfunc(id=None, size=None, hash=None) that returns information based on id or size + hash. If we have id as an argument, size and hash are not needed, if we have size and hash as arguments, then id isn't needed.

With sphinx, it is possible to specify an optional argument, but in this case we don't know what will be mandatory and what will be optional. Here's an example:

def get_file(id=0, size=0, hash="")
    """
    Get file metadata.

    Args:
        id (int): id of the file.
        size (int): file size in bytes.
        hash (str): md5 hash of file.

    Returns:
        file_data (str): metadata information about the file.
    """
    if id:
        # Get file with id.
        ...
    elif size and hash:
        # Get file with size and hash.
        ...
    else:
        # Bad arguments, handle error.
        ...

    return file_data

The question is: how to tell what arguments are necessary in the docstring?

You could easily argue that the function itself is the issue, that both argument pairs should be in separate functions even if the result is the same:

def get_file_by_id(id)
    """
    Get file metadata from id.

    Args:
        id (int): id of the file.

    Returns:
        file_data (str): metadata information about the file.
    """

    # Get file with id.
    ...

    return file_data

def get_file_by_hash(size, hash)
    """
    Get file metadata from hash.

    Args:
        size (int): file size in bytes.
        hash (str): md5 hash of file.

    Returns:
        file_data (str): metadata information about the file.
    """

    # Get file with hash+size.
    ...

    return file_data

But in this case a single function would be preferred if possible, since the function is a binding to another API that uses a single function.

bad_coder
  • 11,289
  • 20
  • 44
  • 72
toucanb
  • 523
  • 1
  • 3
  • 10

1 Answers1

1

According to the documentation, here, the following example method definition:

def module_level_function(param1, param2=None, *args, **kwargs):

Has the docstring defined as:

Args:
    param1 (int): The first parameter.
    param2 (:obj:`str`, optional): The second parameter. Defaults to None.
        Second line of description should be indented.
    *args: Variable length argument list.
    **kwargs: Arbitrary keyword arguments.

So, you explicitly state what is optional as indicated, otherwise it would be understood as a mandatory argument.

idjaw
  • 25,487
  • 7
  • 64
  • 83
  • This tells if the argument is optional, but if my case would apply to this example, **param1** would become optional if **param2** is used. – toucanb Jun 16 '17 at 02:33
  • That's a bit hard to follow for its usage. If you are designing a function that takes optional parameters, then it is understood that it is exactly *that*. If the pair of those arguments need to be given together, you might want to put them together in a named tuple or some other helpful structure. That would probably be the better design option. – idjaw Jun 16 '17 at 02:39
  • 1
    Personally, I think you should stick to splitting your functions for the sake of clarity and explicitness. Also, makes the documentation easier to write. – idjaw Jun 16 '17 at 02:52
  • A tuple `(size, hash)` would simplify the issue, but we are still left with a function that takes `id` **or** `(size, hash)`. I agree that the usage is complicated, I'm open to any alternatives. – toucanb Jun 16 '17 at 03:03
  • I'm not sure how this binding to the API is set up. But, what you can do is have a single method that your API communicates to, and in that method you look at the payload you receive. From that payload, you see what parameters are provided. Based on what is provided, you call the appropriate method. Otherwise you raise for not meeting the contract requirements. – idjaw Jun 16 '17 at 03:09