Skip to content

Commit

Permalink
feat: Support "Keyword Args" sections for G-style
Browse files Browse the repository at this point in the history
Add support for "Keyword Args" and "Keyword Arguments" sections for
Google-style docstrings.

Closes mkdocstrings#88.
  • Loading branch information
HacKanCuBa committed May 8, 2021
1 parent 8e1b1b2 commit fedfc7f
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 22 deletions.
2 changes: 2 additions & 0 deletions src/pytkdocs/parsers/docstrings/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"arguments:": Section.Type.PARAMETERS,
"params:": Section.Type.PARAMETERS,
"parameters:": Section.Type.PARAMETERS,
"keyword args:": Section.Type.PARAMETERS,
"keyword arguments:": Section.Type.PARAMETERS,
"raise:": Section.Type.EXCEPTIONS,
"raises:": Section.Type.EXCEPTIONS,
"except:": Section.Type.EXCEPTIONS,
Expand Down
122 changes: 100 additions & 22 deletions tests/test_parsers/test_docstrings/test_google.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ def test_sections_without_signature():
nada: SEGFAULT.
rien: SEGFAULT.
Keyword Args:
keywd: SEGFAULT.
Exceptions:
GlobalError: when nothing works as expected.
Expand All @@ -60,8 +63,8 @@ def test_sections_without_signature():
"""
)

assert len(sections) == 4
assert len(errors) == 5 # missing annotations for params and return
assert len(sections) == 5
assert len(errors) == 6 # missing annotations for params and return
for error in errors[:-1]:
assert "param" in error
assert "return" in errors[-1]
Expand All @@ -79,43 +82,49 @@ def test_property_docstring():
def test_function_without_annotations():
"""Parse a function docstring without signature annotations."""

def f(x, y):
def f(x, y, *, z):
"""
This function has no annotations.
Parameters:
x: X value.
y: Y value.
Keyword Args:
z: Z value.
Returns:
Sum X + Y.
Sum X + Y + Z.
"""
return x + y
return x + y + z

sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
assert len(sections) == 3
assert len(sections) == 4
assert len(errors) == 1
assert "No type in return" in errors[0]


def test_function_with_annotations():
"""Parse a function docstring with signature annotations."""

def f(x: int, y: int) -> int:
def f(x: int, y: int, *, z: int) -> int:
"""
This function has annotations.
Parameters:
x: X value.
y: Y value.
Keyword Arguments:
z: Z value.
Returns:
Sum X + Y.
"""
return x + y

sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
assert len(sections) == 3
assert len(sections) == 4
assert not errors


Expand Down Expand Up @@ -188,25 +197,29 @@ def f(x: int, y: int) -> int:
def test_types_in_docstring():
"""Parse types in docstring."""

def f(x, y):
def f(x, y, *, z):
"""
The types are written in the docstring.
Parameters:
x (int): X value.
y (int): Y value.
Keyword Args:
z (int): Z value.
Returns:
int: Sum X + Y.
int: Sum X + Y + Z.
"""
return x + y
return x + y + z

sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
assert len(sections) == 3
assert len(sections) == 4
assert not errors

x, y = sections[1].value
r = sections[2].value
(z,) = sections[2].value
r = sections[3].value

assert x.name == "x"
assert x.annotation == "int"
Expand All @@ -220,31 +233,41 @@ def f(x, y):
assert y.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
assert y.default is inspect.Signature.empty

assert z.name == "z"
assert z.annotation == "int"
assert z.description == "Z value."
assert z.kind is inspect.Parameter.KEYWORD_ONLY
assert z.default is inspect.Signature.empty

assert r.annotation == "int"
assert r.description == "Sum X + Y."
assert r.description == "Sum X + Y + Z."


def test_types_and_optional_in_docstring():
"""Parse optional types in docstring."""

def f(x=1, y=None):
def f(x=1, y=None, *, z=None):
"""
The types are written in the docstring.
Parameters:
x (int): X value.
y (int, optional): Y value.
Keyword Args:
z (int, optional): Z value.
Returns:
int: Sum X + Y.
int: Sum X + Y + Z.
"""
return x + (y or 1)
return x + (y or 1) + (z or 1)

sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
assert len(sections) == 3
assert len(sections) == 4
assert not errors

x, y = sections[1].value
(z,) = sections[2].value

assert x.name == "x"
assert x.annotation == "int"
Expand All @@ -258,25 +281,34 @@ def f(x=1, y=None):
assert y.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
assert y.default is None

assert z.name == "z"
assert z.annotation == "int"
assert z.description == "Z value."
assert z.kind is inspect.Parameter.KEYWORD_ONLY
assert z.default is None


def test_types_in_signature_and_docstring():
"""Parse types in both signature and docstring."""

def f(x: int, y: int) -> int:
def f(x: int, y: int, *, z: int) -> int:
"""
The types are written both in the signature and in the docstring.
Parameters:
x (int): X value.
y (int): Y value.
Keyword Args:
z (int): Z value.
Returns:
int: Sum X + Y.
int: Sum X + Y + Z.
"""
return x + y
return x + y + z

sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
assert len(sections) == 3
assert len(sections) == 4
assert not errors


Expand Down Expand Up @@ -401,6 +433,23 @@ def f(x: int):
assert "Empty" in errors[1]


def test_param_line_without_colon_keyword_only():
"""Warn when missing colon."""

def f(*, x: int):
"""
Keyword Args:
x is an integer.
"""
return x

sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
assert not sections # getting x fails, so the section is empty and discarded
assert len(errors) == 2
assert "pair" in errors[0]
assert "Empty" in errors[1]


def test_admonitions():
"""Parse admonitions."""

Expand Down Expand Up @@ -493,6 +542,35 @@ def f(a, *args, **kwargs):
assert not errors


def test_parse_args_kwargs_keyword_only():
"""Parse args and kwargs."""

def f(a, *args, **kwargs):
"""
Arguments:
a: a parameter.
*args: args parameters.
Keyword Args:
**kwargs: kwargs parameters.
"""
return 1

sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
assert len(sections) == 2
expected_parameters = {"a": "a parameter.", "*args": "args parameters."}
for param in sections[0].value:
assert param.name in expected_parameters
assert expected_parameters[param.name] == param.description

expected_parameters = {"**kwargs": "kwargs parameters."}
for param in sections[1].value:
assert param.name in expected_parameters
assert expected_parameters[param.name] == param.description

assert not errors


def test_different_indentation():
"""Parse different indentations, warn on confusing indentation."""

Expand Down

0 comments on commit fedfc7f

Please sign in to comment.