Coverage for tests/test_parsers/test_docstrings/test_google.py: 94.06%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Tests for [the `parsers.docstrings.google` module][pytkdocs.parsers.docstrings.google]."""
3import inspect
4from textwrap import dedent
5from typing import Iterator
7from pytkdocs.loader import Loader
8from pytkdocs.parsers.docstrings.base import Section
9from pytkdocs.parsers.docstrings.google import Google
10from pytkdocs.serializer import serialize_attribute
13class DummyObject:
14 path = "o"
17def parse(
18 docstring,
19 signature=None,
20 return_type=inspect.Signature.empty,
21 admonitions=True,
22 trim_doctest=False,
23):
24 """Helper to parse a doctring."""
25 parser = Google(replace_admonitions=admonitions, trim_doctest_flags=trim_doctest)
27 return parser.parse(
28 dedent(docstring).strip(),
29 context={"obj": DummyObject(), "signature": signature, "type": return_type},
30 )
33def test_simple_docstring():
34 """Parse a simple docstring."""
35 sections, errors = parse("A simple docstring.")
36 assert len(sections) == 1
37 assert not errors
40def test_multi_line_docstring():
41 """Parse a multi-line docstring."""
42 sections, errors = parse(
43 """
44 A somewhat longer docstring.
46 Blablablabla.
47 """
48 )
49 assert len(sections) == 1
50 assert not errors
53def test_sections_without_signature():
54 """Parse a docstring without a signature."""
55 sections, errors = parse(
56 """
57 Sections without signature.
59 Parameters:
60 void: SEGFAULT.
61 niet: SEGFAULT.
62 nada: SEGFAULT.
63 rien: SEGFAULT.
65 Keyword Args:
66 keywd: SEGFAULT.
68 Exceptions:
69 GlobalError: when nothing works as expected.
71 Returns:
72 Itself.
73 """
74 )
76 assert len(sections) == 5
77 assert len(errors) == 6 # missing annotations for params and return
78 for error in errors[:-1]:
79 assert "param" in error
80 assert "return" in errors[-1]
83def test_property_docstring():
84 """Parse a property docstring."""
85 class_ = Loader().get_object_documentation("tests.fixtures.parsing.docstrings.NotDefinedYet")
86 prop = class_.attributes[0]
87 sections, errors = prop.docstring_sections, prop.docstring_errors
88 assert len(sections) == 2
89 assert not errors
92def test_function_without_annotations():
93 """Parse a function docstring without signature annotations."""
95 def f(x, y, *, z):
96 """
97 This function has no annotations.
99 Parameters:
100 x: X value.
101 y: Y value.
103 Keyword Args:
104 z: Z value.
106 Returns:
107 Sum X + Y + Z.
108 """
109 return x + y + z
111 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
112 assert len(sections) == 4
113 assert len(errors) == 1
114 assert "No return type/annotation in" in errors[0]
117def test_function_with_annotations():
118 """Parse a function docstring with signature annotations."""
120 def f(x: int, y: int, *, z: int) -> int:
121 """
122 This function has annotations.
124 Parameters:
125 x: X value.
126 y: Y value.
128 Keyword Arguments:
129 z: Z value.
131 Returns:
132 Sum X + Y.
133 """
134 return x + y
136 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
137 assert len(sections) == 4
138 assert not errors
141def test_function_with_examples_trim_doctest():
142 """Parse example docstring with trim_doctest_flags option."""
144 def f(x: int) -> int:
145 """Test function.
147 Example:
149 We want to skip the following test.
150 >>> 1 + 1 == 3 # doctest: +SKIP
151 True
153 And then a few more examples here:
154 >>> print("a\\n\\nb")
155 a
156 <BLANKLINE>
157 b
158 >>> 1 + 1 == 2 # doctest: +SKIP
159 >>> print(list(range(1, 100))) # doctest: +ELLIPSIS
160 [1, 2, ..., 98, 99]
161 """
162 return x
164 sections, errors = parse(
165 inspect.getdoc(f),
166 inspect.signature(f),
167 trim_doctest=True,
168 )
169 assert len(sections) == 2
170 assert len(sections[1].value) == 4
171 assert not errors
173 # Verify that doctest flags have indeed been trimmed
174 example_str = sections[1].value[1][1]
175 assert "# doctest: +SKIP" not in example_str
176 example_str = sections[1].value[3][1]
177 assert "<BLANKLINE>" not in example_str
178 assert "\n>>> print(list(range(1, 100)))\n" in example_str
181def test_function_with_examples():
182 """Parse a function docstring with examples."""
184 def f(x: int, y: int) -> int:
185 """
186 This function has annotations.
188 Examples:
189 Some examples that will create an unified code block:
191 >>> 2 + 2 == 5
192 False
193 >>> print("examples")
194 "examples"
196 This is just a random comment in the examples section.
198 These examples will generate two different code blocks. Note the blank line.
200 >>> print("I'm in the first code block!")
201 "I'm in the first code block!"
203 >>> print("I'm in other code block!")
204 "I'm in other code block!"
206 We also can write multiline examples:
208 >>> x = 3 + 2
209 >>> y = x + 10
210 >>> y
211 15
213 This is just a typical Python code block:
215 ```python
216 print("examples")
217 return 2 + 2
218 ```
220 Even if it contains doctests, the following block is still considered a normal code-block.
222 ```python
223 >>> print("examples")
224 "examples"
225 >>> 2 + 2
226 4
227 ```
229 The blank line before an example is optional.
230 >>> x = 3
231 >>> y = "apple"
232 >>> z = False
233 >>> l = [x, y, z]
234 >>> my_print_list_function(l)
235 3
236 "apple"
237 False
238 """
239 return x + y
241 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
242 assert len(sections) == 2
243 assert len(sections[1].value) == 9
244 assert not errors
247def test_types_in_docstring():
248 """Parse types in docstring."""
250 def f(x, y, *, z):
251 """
252 The types are written in the docstring.
254 Parameters:
255 x (int): X value.
256 y (int): Y value.
258 Keyword Args:
259 z (int): Z value.
261 Returns:
262 int: Sum X + Y + Z.
263 """
264 return x + y + z
266 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
267 assert len(sections) == 4
268 assert not errors
270 assert sections[0].type == Section.Type.MARKDOWN
271 assert sections[1].type == Section.Type.PARAMETERS
272 assert sections[2].type == Section.Type.KEYWORD_ARGS
273 assert sections[3].type == Section.Type.RETURN
275 x, y = sections[1].value
276 (z,) = sections[2].value
277 r = sections[3].value
279 assert x.name == "x"
280 assert x.annotation == "int"
281 assert x.description == "X value."
282 assert x.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
283 assert x.default is inspect.Signature.empty
285 assert y.name == "y"
286 assert y.annotation == "int"
287 assert y.description == "Y value."
288 assert y.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
289 assert y.default is inspect.Signature.empty
291 assert z.name == "z"
292 assert z.annotation == "int"
293 assert z.description == "Z value."
294 assert z.kind is inspect.Parameter.KEYWORD_ONLY
295 assert z.default is inspect.Signature.empty
297 assert r.annotation == "int"
298 assert r.description == "Sum X + Y + Z."
301def test_types_and_optional_in_docstring():
302 """Parse optional types in docstring."""
304 def f(x=1, y=None, *, z=None):
305 """
306 The types are written in the docstring.
308 Parameters:
309 x (int): X value.
310 y (int, optional): Y value.
312 Keyword Args:
313 z (int, optional): Z value.
315 Returns:
316 int: Sum X + Y + Z.
317 """
318 return x + (y or 1) + (z or 1)
320 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
321 assert len(sections) == 4
322 assert not errors
324 assert sections[0].type == Section.Type.MARKDOWN
325 assert sections[1].type == Section.Type.PARAMETERS
326 assert sections[2].type == Section.Type.KEYWORD_ARGS
328 x, y = sections[1].value
329 (z,) = sections[2].value
331 assert x.name == "x"
332 assert x.annotation == "int"
333 assert x.description == "X value."
334 assert x.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
335 assert x.default == 1
337 assert y.name == "y"
338 assert y.annotation == "int"
339 assert y.description == "Y value."
340 assert y.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
341 assert y.default is None
343 assert z.name == "z"
344 assert z.annotation == "int"
345 assert z.description == "Z value."
346 assert z.kind is inspect.Parameter.KEYWORD_ONLY
347 assert z.default is None
350def test_types_in_signature_and_docstring():
351 """Parse types in both signature and docstring. Should prefer the docstring type"""
353 def f(x: int, y: int, *, z: int) -> int:
354 """
355 The types are written both in the signature and in the docstring.
357 Parameters:
358 x (str): X value.
359 y (str): Y value.
361 Keyword Args:
362 z (str): Z value.
364 Returns:
365 str: Sum X + Y + Z.
366 """
367 return x + y + z
369 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
370 assert len(sections) == 4
371 assert not errors
373 assert sections[0].type == Section.Type.MARKDOWN
374 assert sections[1].type == Section.Type.PARAMETERS
375 assert sections[2].type == Section.Type.KEYWORD_ARGS
376 assert sections[3].type == Section.Type.RETURN
378 x, y = sections[1].value
379 (z,) = sections[2].value
380 r = sections[3].value
382 assert x.name == "x"
383 assert x.annotation == "str"
384 assert x.description == "X value."
385 assert x.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
386 assert x.default is inspect.Signature.empty
388 assert y.name == "y"
389 assert y.annotation == "str"
390 assert y.description == "Y value."
391 assert y.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
392 assert y.default is inspect.Signature.empty
394 assert z.name == "z"
395 assert z.annotation == "str"
396 assert z.description == "Z value."
397 assert z.kind is inspect.Parameter.KEYWORD_ONLY
398 assert z.default is inspect.Signature.empty
400 assert r.annotation == "str"
401 assert r.description == "Sum X + Y + Z."
404def test_close_sections():
405 """Parse sections without blank lines in between."""
407 def f(x, y, z):
408 """
409 Parameters:
410 x: X.
411 Parameters:
412 y: Y.
414 Parameters:
415 z: Z.
416 Exceptions:
417 Error2: error.
418 Exceptions:
419 Error1: error.
420 Returns:
421 1.
422 Returns:
423 2.
424 """
425 return x + y + z
427 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
428 assert len(sections) == 7
429 assert len(errors) == 2 # no return type annotations
432def test_code_blocks():
433 """Parse code blocks."""
435 def f(s): # noqa: D300,D301 (escape sequences)
436 """
437 This docstring contains a docstring in a code block o_O!
439 ```python
440 \"\"\"
441 This docstring is contained in another docstring O_o!
443 Parameters:
444 s: A string.
445 \"\"\"
446 ```
447 """
448 return s
450 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
451 assert len(sections) == 1
452 assert not errors
455def test_indented_code_block():
456 """Parse indented code blocks."""
458 def f(s): # noqa: D300,D301 (escape sequences)
459 """
460 This docstring contains a docstring in a code block o_O!
462 \"\"\"
463 This docstring is contained in another docstring O_o!
465 Parameters:
466 s: A string.
467 \"\"\"
468 """
469 return s
471 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
472 assert len(sections) == 1
473 assert not errors
476def test_extra_parameter():
477 """Warn on extra parameter in docstring."""
479 def f(x):
480 """
481 Parameters:
482 x: Integer.
483 y: Integer.
484 """
485 return x
487 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
488 assert len(sections) == 1
489 assert len(errors) == 1
490 assert "No type" in errors[0]
493def test_missing_parameter():
494 """Don't warn on missing parameter in docstring."""
495 # FIXME: could warn
496 def f(x, y):
497 """
498 Parameters:
499 x: Integer.
500 """
501 return x + y
503 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
504 assert len(sections) == 1
505 assert not errors
508def test_param_line_without_colon():
509 """Warn when missing colon."""
511 def f(x: int):
512 """
513 Parameters:
514 x is an integer.
515 """
516 return x
518 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
519 assert not sections # getting x fails, so the section is empty and discarded
520 assert len(errors) == 2
521 assert "pair" in errors[0]
522 assert "Empty" in errors[1]
525def test_param_line_without_colon_keyword_only():
526 """Warn when missing colon."""
528 def f(*, x: int):
529 """
530 Keyword Args:
531 x is an integer.
532 """
533 return x
535 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
536 assert not sections # getting x fails, so the section is empty and discarded
537 assert len(errors) == 2
538 assert "pair" in errors[0]
539 assert "Empty" in errors[1]
542def test_admonitions():
543 """Parse admonitions."""
545 def f():
546 """
547 Note:
548 Hello.
550 Note: With title.
551 Hello again.
553 Something:
554 Something.
555 """
557 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
558 assert len(sections) == 1
559 assert not errors
562def test_invalid_sections():
563 """Warn on invalid (empty) sections."""
565 def f():
566 """
567 Parameters:
568 Exceptions:
569 Exceptions:
571 Returns:
572 Note:
574 Important:
575 """
577 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
578 assert len(sections) == 1
579 for error in errors[:3]:
580 assert "Empty" in error
581 assert "Empty return section at line" in errors[3]
582 assert "Empty" in errors[-1]
585def test_multiple_lines_in_sections_items():
586 """Parse multi-line item description."""
588 def f(p: str, q: str):
589 """
590 Hi.
592 Arguments:
593 p: This argument
594 has a description
595 spawning on multiple lines.
597 It even has blank lines in it.
598 Some of these lines
599 are indented for no reason.
600 q:
601 What if the first line is blank?
602 """
603 return p + q
605 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
606 assert len(sections) == 2
607 assert len(sections[1].value) == 2
608 assert errors
609 for error in errors:
610 assert "should be 4 * 2 = 8 spaces, not" in error
613def test_parse_args_kwargs():
614 """Parse args and kwargs."""
616 def f(a, *args, **kwargs):
617 """
618 Arguments:
619 a: a parameter.
620 *args: args parameters.
621 **kwargs: kwargs parameters.
622 """
623 return 1
625 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
626 assert len(sections) == 1
627 expected_parameters = {"a": "a parameter.", "*args": "args parameters.", "**kwargs": "kwargs parameters."}
628 for param in sections[0].value:
629 assert param.name in expected_parameters
630 assert expected_parameters[param.name] == param.description
631 assert not errors
634def test_parse_args_kwargs_keyword_only():
635 """Parse args and kwargs."""
637 def f(a, *args, **kwargs):
638 """
639 Arguments:
640 a: a parameter.
641 *args: args parameters.
643 Keyword Args:
644 **kwargs: kwargs parameters.
645 """
646 return 1
648 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
649 assert len(sections) == 2
650 expected_parameters = {"a": "a parameter.", "*args": "args parameters."}
651 for param in sections[0].value:
652 assert param.name in expected_parameters
653 assert expected_parameters[param.name] == param.description
655 expected_parameters = {"**kwargs": "kwargs parameters."}
656 for param in sections[1].value:
657 assert param.name in expected_parameters
658 assert expected_parameters[param.name] == param.description
660 assert not errors
663def test_different_indentation():
664 """Parse different indentations, warn on confusing indentation."""
666 def f():
667 """
668 Hello.
670 Raises:
671 StartAt5: this section's items starts with 5 spaces of indentation.
672 Well indented continuation line.
673 Badly indented continuation line (will trigger an error).
675 Empty lines are preserved, as well as extra-indentation (this line is a code block).
676 AnyOtherLine: ...starting with exactly 5 spaces is a new item.
677 AnyLine: ...indented with less than 5 spaces signifies the end of the section.
678 """
680 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
681 assert len(sections) == 3
682 assert len(sections[1].value) == 2
683 assert sections[1].value[0].description == (
684 "this section's items starts with 5 spaces of indentation.\n"
685 "Well indented continuation line.\n"
686 "Badly indented continuation line (will trigger an error).\n"
687 "\n"
688 " Empty lines are preserved, as well as extra-indentation (this line is a code block)."
689 )
690 assert sections[2].value == " AnyLine: ...indented with less than 5 spaces signifies the end of the section."
691 assert len(errors) == 1
692 assert "should be 5 * 2 = 10 spaces, not 6" in errors[0]
695def test_parse_module_attributes_section():
696 """Parse attributes section in modules."""
697 loader = Loader()
698 obj = loader.get_object_documentation("tests.fixtures.docstring_attributes_section")
699 assert len(obj.docstring_sections) == 2
700 assert not obj.docstring_errors
701 attr_section = obj.docstring_sections[1]
702 assert attr_section.type == Section.Type.ATTRIBUTES
703 assert len(attr_section.value) == 5
704 expected = [
705 {"name": "A", "annotation": "int", "description": "Alpha."},
706 {"name": "B", "annotation": "bytes", "description": "Beta."},
707 {"name": "C", "annotation": "bool", "description": "Gamma."},
708 {"name": "D", "annotation": "", "description": "Delta."},
709 {"name": "E", "annotation": "float", "description": "Epsilon."},
710 ]
711 assert [serialize_attribute(attr) for attr in attr_section.value] == expected
714def test_docstring_with_yield_section():
715 """Parse Yields section."""
717 def f():
718 """A useless range wrapper.
720 Yields:
721 int: Integers.
722 """
723 yield from range(10)
725 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
726 assert len(sections) == 2
727 annotated = sections[1].value
728 assert annotated.annotation == "int"
729 assert annotated.description == "Integers."
730 assert not errors
733def test_docstring_with_yield_section_and_return_annotation():
734 """Parse Yields section."""
736 def f() -> Iterator[int]:
737 """A useless range wrapper.
739 Yields:
740 Integers.
741 """
742 yield from range(10)
744 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
745 assert len(sections) == 2
746 annotated = sections[1].value
747 assert annotated.annotation is Iterator[int]
748 assert annotated.description == "Integers."
749 assert not errors
752def test_keyword_args_no_type():
753 """Parse types for keyword arguments."""
755 def f(**kwargs):
756 """Do nothing.
758 Keyword arguments:
759 a: No type.
760 """
762 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
763 assert len(sections) == 2
764 kwargs = sections[1].value
765 assert kwargs[0].name == "a"
766 assert kwargs[0].annotation is inspect.Parameter.empty
767 assert kwargs[0].description == "No type."
768 assert kwargs[0].kind is inspect.Parameter.KEYWORD_ONLY
769 assert kwargs[0].default is inspect.Parameter.empty
770 assert len(errors) == 1
771 assert "No type annotation for parameter" in errors[0]
774def test_keyword_args_type():
775 """Parse types for keyword arguments."""
777 def f(**kwargs):
778 """Do nothing.
780 Keyword arguments:
781 a (int): Typed.
782 """
784 sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
785 assert len(sections) == 2
786 kwargs = sections[1].value
787 assert kwargs[0].name == "a"
788 assert kwargs[0].annotation == "int"
789 assert kwargs[0].description == "Typed."
790 assert kwargs[0].kind is inspect.Parameter.KEYWORD_ONLY
791 assert kwargs[0].default is inspect.Parameter.empty
792 assert not errors