Coverage for tests / test_docstrings / test_sphinx.py: 100.00%
443 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-11 11:48 +0100
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-11 11:48 +0100
1"""Tests for the [Sphinx-style parser][griffe.docstrings.sphinx]."""
3from __future__ import annotations
5import inspect
6from typing import TYPE_CHECKING, Any
8import pytest
10from griffe import (
11 Attribute,
12 Class,
13 DocstringAttribute,
14 DocstringParameter,
15 DocstringRaise,
16 DocstringReturn,
17 DocstringSectionKind,
18 Expr,
19 ExprAttribute,
20 ExprBinOp,
21 ExprName,
22 ExprSubscript,
23 ExprTuple,
24 Function,
25 Module,
26 Parameter,
27 Parameters,
28)
30if TYPE_CHECKING:
31 from tests.test_docstrings.helpers import ParserType
33SOME_NAME = "foo"
34SOME_TEXT = "descriptive test text"
35SOME_EXTRA_TEXT = "more test text"
36SOME_EXCEPTION_NAME = "SomeException"
37SOME_OTHER_EXCEPTION_NAME = "SomeOtherException"
40@pytest.mark.parametrize(
41 "docstring",
42 [
43 "One line docstring description",
44 """
45 Multiple line docstring description.
47 With more text.
48 """,
49 ],
50)
51def test_parse__description_only_docstring__single_markdown_section(parse_sphinx: ParserType, docstring: str) -> None:
52 """Parse a single or multiline docstring.
54 Parameters:
55 parse_sphinx: Fixture parser.
56 docstring: A parametrized docstring.
57 """
58 sections, warnings = parse_sphinx(docstring)
60 assert len(sections) == 1
61 assert sections[0].kind is DocstringSectionKind.text
62 assert sections[0].value == inspect.cleandoc(docstring)
63 assert not warnings
66def test_parse__no_description__single_markdown_section(parse_sphinx: ParserType) -> None:
67 """Parse an empty docstring.
69 Parameters:
70 parse_sphinx: Fixture parser.
71 """
72 sections, warnings = parse_sphinx("")
74 assert len(sections) == 1
75 assert sections[0].kind is DocstringSectionKind.text
76 assert sections[0].value == ""
77 assert not warnings
80def test_parse__multiple_blank_lines_before_description__single_markdown_section(parse_sphinx: ParserType) -> None:
81 """Parse a docstring with initial blank lines.
83 Parameters:
84 parse_sphinx: Fixture parser.
85 """
86 sections, warnings = parse_sphinx(
87 """
90 Now text""",
91 )
93 assert len(sections) == 1
94 assert sections[0].kind is DocstringSectionKind.text
95 assert sections[0].value == "Now text"
96 assert not warnings
99def test_parse__param_field__param_section(parse_sphinx: ParserType) -> None:
100 """Parse a parameter section.
102 Parameters:
103 parse_sphinx: Fixture parser.
104 """
105 sections, _ = parse_sphinx(
106 f"""
107 Docstring with one line param.
109 :param {SOME_NAME}: {SOME_TEXT}
110 """,
111 )
112 assert len(sections) == 2
113 assert sections[1].kind is DocstringSectionKind.parameters
114 actual = sections[1].value[0]
115 expected = DocstringParameter(SOME_NAME, description=SOME_TEXT)
116 assert isinstance(actual, type(expected))
117 assert actual.as_dict() == expected.as_dict()
120def test_parse__only_param_field__empty_markdown(parse_sphinx: ParserType) -> None:
121 """Parse only a parameter section.
123 Parameters:
124 parse_sphinx: Fixture parser.
125 """
126 sections, _ = parse_sphinx(":param foo: text")
127 assert len(sections) == 2
128 assert sections[0].kind is DocstringSectionKind.text
129 assert sections[0].value == ""
132@pytest.mark.parametrize(
133 "param_directive_name",
134 [
135 "param",
136 "parameter",
137 "arg",
138 "arguments",
139 "key",
140 "keyword",
141 ],
142)
143def test_parse__all_param_names__param_section(parse_sphinx: ParserType, param_directive_name: str) -> None:
144 """Parse all parameters directives.
146 Parameters:
147 parse_sphinx: Fixture parser.
148 param_directive_name: A parametrized directive name.
149 """
150 sections, _ = parse_sphinx(
151 f"""
152 Docstring with one line param.
154 :{param_directive_name} {SOME_NAME}: {SOME_TEXT}
155 """,
156 )
157 assert len(sections) == 2
158 assert sections[1].kind is DocstringSectionKind.parameters
159 actual = sections[1].value[0]
160 expected = DocstringParameter(SOME_NAME, description=SOME_TEXT)
161 assert isinstance(actual, type(expected))
162 assert actual.as_dict() == expected.as_dict()
165@pytest.mark.parametrize(
166 "docstring",
167 [
168 f"""
169 Docstring with param with continuation, no indent.
171 :param {SOME_NAME}: {SOME_TEXT}
172 {SOME_EXTRA_TEXT}
173 """,
174 f"""
175 Docstring with param with continuation, with indent.
177 :param {SOME_NAME}: {SOME_TEXT}
178 {SOME_EXTRA_TEXT}
179 """,
180 ],
181)
182def test_parse__param_field_multi_line__param_section(parse_sphinx: ParserType, docstring: str) -> None:
183 """Parse multiline directives.
185 Parameters:
186 parse_sphinx: Fixture parser.
187 docstring: A parametrized docstring.
188 """
189 sections, _ = parse_sphinx(docstring)
190 assert len(sections) == 2
191 assert sections[1].kind is DocstringSectionKind.parameters
192 actual = sections[1].value[0]
193 expected = DocstringParameter(SOME_NAME, description=f"{SOME_TEXT}\n{SOME_EXTRA_TEXT}")
194 assert isinstance(actual, type(expected))
195 assert actual.as_dict() == expected.as_dict()
198def test_parse__param_field_for_function__param_section_with_kind(parse_sphinx: ParserType) -> None:
199 """Parse parameters.
201 Parameters:
202 parse_sphinx: Fixture parser.
203 """
204 docstring = f"""
205 Docstring with line continuation.
207 :param foo: {SOME_TEXT}
208 """
210 sections, _ = parse_sphinx(docstring)
211 assert len(sections) == 2
212 assert sections[1].kind is DocstringSectionKind.parameters
213 actual = sections[1].value[0]
214 expected = DocstringParameter(SOME_NAME, description=SOME_TEXT)
215 assert isinstance(actual, type(expected))
216 assert actual.as_dict() == expected.as_dict()
219def test_parse__param_field_docs_type__param_section_with_type(parse_sphinx: ParserType) -> None:
220 """Parse parameters with types.
222 Parameters:
223 parse_sphinx: Fixture parser.
224 """
225 docstring = f"""
226 Docstring with line continuation.
228 :param str foo: {SOME_TEXT}
229 """
231 sections, _ = parse_sphinx(docstring)
232 assert len(sections) == 2
233 assert sections[1].kind is DocstringSectionKind.parameters
234 actual = sections[1].value[0]
235 expected = DocstringParameter(SOME_NAME, annotation="str", description=SOME_TEXT)
236 assert isinstance(actual, type(expected))
237 assert actual.as_dict() == expected.as_dict()
240@pytest.mark.parametrize("type_", ["str", "int"])
241def test_parse__param_field_type_field__param_section_with_type(parse_sphinx: ParserType, type_: str) -> None:
242 """Parse parameters with separated types.
244 Parameters:
245 parse_sphinx: Fixture parser.
246 type_: The type to use in the type directive.
247 """
248 docstring = f"""
249 Docstring with line continuation.
251 :param {SOME_NAME}: {SOME_TEXT}
252 :type {SOME_NAME}: {type_}
253 """
255 sections, _ = parse_sphinx(docstring)
256 assert len(sections) == 2
257 assert sections[1].kind is DocstringSectionKind.parameters
258 actual = sections[1].value[0]
259 expected = DocstringParameter(SOME_NAME, annotation=f"{type_}", description=SOME_TEXT)
260 assert isinstance(actual, type(expected))
261 assert actual.as_dict() == expected.as_dict()
264@pytest.mark.parametrize("type_", ["str", "int"])
265def test_parse__param_field_type_field__param_section_with_type_with_parent(
266 parse_sphinx: ParserType,
267 type_: str,
268) -> None:
269 """Parse parameters with separated types.
271 Parameters:
272 parse_sphinx: Fixture parser.
273 type_: The type to use in the type directive.
274 """
275 docstring = f"""
276 Docstring with line continuation.
278 :param {SOME_NAME}: {SOME_TEXT}
279 :type {SOME_NAME}: {type_}
280 """
281 parent_fn = Function("func", parameters=Parameters(Parameter(SOME_NAME)))
282 sections, _ = parse_sphinx(docstring, parent=parent_fn)
283 assert len(sections) == 2
284 assert sections[1].kind is DocstringSectionKind.parameters
285 actual = sections[1].value[0]
286 expected_annotation = ExprName(name=f"{type_}")
287 expected = DocstringParameter(SOME_NAME, annotation=expected_annotation, description=SOME_TEXT)
288 assert isinstance(actual, type(expected))
289 assert actual.as_dict() == expected.as_dict()
290 assert isinstance(actual.annotation, type(expected.annotation))
291 assert isinstance(actual.annotation, ExprName)
292 assert isinstance(actual.annotation, Expr)
293 assert actual.annotation.as_dict() == expected_annotation.as_dict()
296def test_parse__param_field_type_field_first__param_section_with_type(parse_sphinx: ParserType) -> None:
297 """Parse parameters with separated types first.
299 Parameters:
300 parse_sphinx: Fixture parser.
301 """
302 docstring = f"""
303 Docstring with line continuation.
305 :type foo: str
306 :param foo: {SOME_TEXT}
307 """
309 sections, _ = parse_sphinx(docstring)
310 assert len(sections) == 2
311 assert sections[1].kind is DocstringSectionKind.parameters
312 actual = sections[1].value[0]
313 expected = DocstringParameter(SOME_NAME, annotation="str", description=SOME_TEXT)
314 assert isinstance(actual, type(expected))
315 assert actual.as_dict() == expected.as_dict()
318def test_parse__param_field_type_field_first__param_section_with_type_with_parent(parse_sphinx: ParserType) -> None:
319 """Parse parameters with separated types first.
321 Parameters:
322 parse_sphinx: Fixture parser.
323 """
324 docstring = f"""
325 Docstring with line continuation.
327 :type {SOME_NAME}: str
328 :param {SOME_NAME}: {SOME_TEXT}
329 """
330 parent_fn = Function("func", parameters=Parameters(Parameter(SOME_NAME)))
331 sections, _ = parse_sphinx(docstring, parent=parent_fn)
332 assert len(sections) == 2
333 assert sections[1].kind is DocstringSectionKind.parameters
334 actual = sections[1].value[0]
335 expected_annotation = ExprName("str", parent=Class("C"))
336 expected = DocstringParameter(SOME_NAME, annotation=expected_annotation, description=SOME_TEXT)
337 assert isinstance(actual, type(expected))
338 assert actual.as_dict() == expected.as_dict()
339 assert isinstance(actual.annotation, type(expected.annotation))
340 assert isinstance(actual.annotation, ExprName)
341 assert isinstance(actual.annotation, Expr)
342 assert actual.annotation.as_dict() == expected_annotation.as_dict()
345@pytest.mark.parametrize("union", ["str or None", "None or str", "str or int", "str or int or float"])
346def test_parse__param_field_type_field_or_none__param_section_with_optional(
347 parse_sphinx: ParserType,
348 union: str,
349) -> None:
350 """Parse parameters with separated union types.
352 Parameters:
353 parse_sphinx: Fixture parser.
354 union: A parametrized union type.
355 """
356 docstring = f"""
357 Docstring with line continuation.
359 :param foo: {SOME_TEXT}
360 :type foo: {union}
361 """
363 sections, _ = parse_sphinx(docstring)
364 assert len(sections) == 2
365 assert sections[1].kind is DocstringSectionKind.parameters
366 actual = sections[1].value[0]
367 expected = DocstringParameter(SOME_NAME, annotation=union.replace(" or ", " | "), description=SOME_TEXT)
368 assert isinstance(actual, type(expected))
369 assert actual.as_dict() == expected.as_dict()
372@pytest.mark.parametrize(
373 ("union", "expected_annotation"),
374 [
375 ("str or None", ExprBinOp(ExprName("str"), "|", "None")),
376 ("None or str", ExprBinOp("None", "|", ExprName("str"))),
377 ("str or int", ExprBinOp(ExprName("str"), "|", ExprName("int"))),
378 ("str or int or float", ExprBinOp(ExprBinOp(ExprName("str"), "|", ExprName("int")), "|", ExprName("float"))),
379 ],
380)
381def test_parse__param_field_type_field_or_none__param_section_with_optional_with_parent(
382 parse_sphinx: ParserType,
383 union: str,
384 expected_annotation: Expr,
385) -> None:
386 """Parse parameters with separated union types.
388 Parameters:
389 parse_sphinx: Fixture parser.
390 union: A parametrized union type.
391 expected_annotation: The expected annotation as an expression
392 """
393 docstring = f"""
394 Docstring with line continuation.
396 :param {SOME_NAME}: {SOME_TEXT}
397 :type {SOME_NAME}: {union}
398 """
400 parent_fn = Function("func", parameters=Parameters(Parameter(SOME_NAME)))
401 sections, _ = parse_sphinx(docstring, parent=parent_fn)
402 assert len(sections) == 2
403 assert sections[1].kind is DocstringSectionKind.parameters
404 actual = sections[1].value[0]
405 expected = DocstringParameter(SOME_NAME, annotation=expected_annotation, description=SOME_TEXT)
406 assert isinstance(actual, type(expected))
407 assert actual.as_dict() == expected.as_dict()
408 assert isinstance(actual.annotation, type(expected.annotation))
409 assert isinstance(actual.annotation, Expr)
410 assert actual.annotation.as_dict() == expected_annotation.as_dict()
413def test_parse__param_field_annotate_type__param_section_with_type(parse_sphinx: ParserType) -> None:
414 """Parse a simple docstring.
416 Parameters:
417 parse_sphinx: Fixture parser.
418 """
419 docstring = f"""
420 Docstring with line continuation.
422 :param foo: {SOME_TEXT}
423 """
425 sections, warnings = parse_sphinx(
426 docstring,
427 parent=Function("func", parameters=Parameters(Parameter("foo", annotation="str", kind=None))),
428 )
429 assert len(sections) == 2
430 assert sections[1].kind is DocstringSectionKind.parameters
431 actual = sections[1].value[0]
432 expected = DocstringParameter(SOME_NAME, annotation="str", description=SOME_TEXT)
433 assert isinstance(actual, type(expected))
434 assert actual.as_dict() == expected.as_dict()
435 assert not warnings
438def test_parse__param_field_no_matching_param__result_from_docstring(parse_sphinx: ParserType) -> None:
439 """Parse a simple docstring.
441 Parameters:
442 parse_sphinx: Fixture parser.
443 """
444 docstring = f"""
445 Docstring with line continuation.
447 :param other: {SOME_TEXT}
448 """
450 sections, _ = parse_sphinx(docstring)
451 assert len(sections) == 2
452 assert sections[1].kind is DocstringSectionKind.parameters
453 actual = sections[1].value[0]
454 expected = DocstringParameter("other", description=SOME_TEXT)
455 assert isinstance(actual, type(expected))
456 assert actual.as_dict() == expected.as_dict()
459def test_parse__param_field_with_default__result_from_docstring(parse_sphinx: ParserType) -> None:
460 """Parse a simple docstring.
462 Parameters:
463 parse_sphinx: Fixture parser.
464 """
465 docstring = f"""
466 Docstring with line continuation.
468 :param foo: {SOME_TEXT}
469 """
471 sections, warnings = parse_sphinx(
472 docstring,
473 parent=Function("func", parameters=Parameters(Parameter("foo", kind=None, default=repr("")))),
474 )
475 assert len(sections) == 2
476 assert sections[1].kind is DocstringSectionKind.parameters
477 actual = sections[1].value[0]
478 expected = DocstringParameter("foo", description=SOME_TEXT, value=repr(""))
479 assert isinstance(actual, type(expected))
480 assert actual.as_dict() == expected.as_dict()
481 assert not warnings
484def test_parse__param_field_no_matching_param__error_message(parse_sphinx: ParserType) -> None:
485 """Parse a simple docstring.
487 Parameters:
488 parse_sphinx: Fixture parser.
489 """
490 docstring = f"""
491 Docstring with line continuation.
493 :param other: {SOME_TEXT}
494 """
496 _, warnings = parse_sphinx(docstring)
497 assert "No matching parameter for 'other'" in warnings[0]
500def test_parse__invalid_param_field_only_initial_marker__error_message(parse_sphinx: ParserType) -> None:
501 """Parse a simple docstring.
503 Parameters:
504 parse_sphinx: Fixture parser.
505 """
506 docstring = f"""
507 Docstring with line continuation.
509 :param foo {SOME_TEXT}
510 """
512 _, warnings = parse_sphinx(docstring)
513 assert "Failed to get ':directive: value' pair" in warnings[0]
516def test_parse__invalid_param_field_wrong_part_count__error_message(parse_sphinx: ParserType) -> None:
517 """Parse a simple docstring.
519 Parameters:
520 parse_sphinx: Fixture parser.
521 """
522 docstring = f"""
523 Docstring with line continuation.
525 :param: {SOME_TEXT}
526 """
528 _, warnings = parse_sphinx(docstring)
529 assert "Failed to parse field directive" in warnings[0]
532def test_parse__invalid_param_field_wrong_part_count_spaces_4__error_message(parse_sphinx: ParserType) -> None:
533 """Parse a simple docstring.
535 Parameters:
536 parse_sphinx: Fixture parser.
537 """
538 docstring = f"""
539 Docstring with line continuation.
541 :param typing.Union[str, int] {SOME_NAME}: {SOME_TEXT}
542 """
544 sections, warnings = parse_sphinx(docstring)
546 # Assert that the warning is shown
547 assert "Failed to parse field directive" in warnings[0]
549 # Assert that the parameter is still collected, but ignores the invalid type
550 assert len(sections) == 2
551 assert sections[1].kind is DocstringSectionKind.parameters
552 actual = sections[1].value[0]
553 expected = DocstringParameter(SOME_NAME, annotation=None, description=SOME_TEXT)
554 assert isinstance(actual, type(expected))
555 assert actual.as_dict() == expected.as_dict()
558def test_parse__valid_param_field_part_count_3(parse_sphinx: ParserType) -> None:
559 """Parse a simple docstring.
561 Parameters:
562 parse_sphinx: Fixture parser.
563 """
564 docstring = f"""
565 Docstring with line continuation.
567 :param typing.Union[str,int] {SOME_NAME}: {SOME_TEXT}
568 """
570 sections, _ = parse_sphinx(docstring)
571 assert len(sections) == 2
572 assert sections[1].kind is DocstringSectionKind.parameters
573 actual = sections[1].value[0]
574 expected = DocstringParameter(SOME_NAME, annotation="typing.Union[str,int]", description=SOME_TEXT)
575 assert isinstance(actual, type(expected))
576 assert actual.as_dict() == expected.as_dict()
579def test_parse__valid_param_field_part_count_3_with_parent(parse_sphinx: ParserType) -> None:
580 """Parse a simple docstring.
582 Parameters:
583 parse_sphinx: Fixture parser.
584 """
585 docstring = f"""
586 Docstring with line continuation.
588 :param typing.Union[str,int] {SOME_NAME}: {SOME_TEXT}
589 """
591 parent_fn = Function("func3", parameters=Parameters(Parameter(name=SOME_NAME)))
592 sections, _ = parse_sphinx(docstring, parent=parent_fn)
593 assert len(sections) == 2
594 assert sections[1].kind is DocstringSectionKind.parameters
595 actual = sections[1].value[0]
596 typing_expr = ExprName("typing", parent=parent_fn)
597 expected_annotation = ExprSubscript(
598 left=ExprAttribute(values=[typing_expr, ExprName("Union", parent=typing_expr)]),
599 slice=ExprTuple([ExprName("str", parent=parent_fn), ExprName("int", parent=parent_fn)], implicit=True),
600 )
601 expected = DocstringParameter(SOME_NAME, annotation=expected_annotation, description=SOME_TEXT)
602 assert isinstance(actual, type(expected))
603 assert actual.as_dict() == expected.as_dict()
606def test_parse__param_twice__error_message(parse_sphinx: ParserType) -> None:
607 """Parse a simple docstring.
609 Parameters:
610 parse_sphinx: Fixture parser.
611 """
612 docstring = f"""
613 Docstring with line continuation.
615 :param foo: {SOME_TEXT}
616 :param foo: {SOME_TEXT} again
617 """
619 _, warnings = parse_sphinx(
620 docstring,
621 parent=Function("func", parameters=Parameters(Parameter("foo", kind=None))),
622 )
623 assert "Duplicate parameter entry for 'foo'" in warnings[0]
626def test_parse__param_type_twice_doc__error_message(parse_sphinx: ParserType) -> None:
627 """Parse a simple docstring.
629 Parameters:
630 parse_sphinx: Fixture parser.
631 """
632 docstring = f"""
633 Docstring with line continuation.
635 :param str foo: {SOME_TEXT}
636 :type foo: str
637 """
639 _, warnings = parse_sphinx(
640 docstring,
641 parent=Function("func", parameters=Parameters(Parameter("foo", kind=None))),
642 )
643 assert "Duplicate parameter information for 'foo'" in warnings[0]
646def test_parse__param_type_twice_type_directive_first__error_message(parse_sphinx: ParserType) -> None:
647 """Parse a simple docstring.
649 Parameters:
650 parse_sphinx: Fixture parser.
651 """
652 docstring = f"""
653 Docstring with line continuation.
655 :type foo: str
656 :param str foo: {SOME_TEXT}
657 """
659 _, warnings = parse_sphinx(
660 docstring,
661 parent=Function("func", parameters=Parameters(Parameter("foo", kind=None))),
662 )
663 assert "Duplicate parameter information for 'foo'" in warnings[0]
666def test_parse__param_type_twice_annotated__error_message(parse_sphinx: ParserType) -> None:
667 """Parse a simple docstring.
669 Parameters:
670 parse_sphinx: Fixture parser.
671 """
672 docstring = f"""
673 Docstring with line continuation.
675 :param str foo: {SOME_TEXT}
676 :type foo: str
677 """
679 _, warnings = parse_sphinx(
680 docstring,
681 parent=Function("func", parameters=Parameters(Parameter("foo", annotation="str", kind=None))),
682 )
683 assert "Duplicate parameter information for 'foo'" in warnings[0]
686def test_warn_about_unknown_parameters(parse_sphinx: ParserType) -> None:
687 """Warn about unknown parameters in "Parameters" sections.
689 Parameters:
690 parse_sphinx: Fixture parser.
691 """
692 docstring = """
694 :param str a: {SOME_TEXT}
695 """
697 _, warnings = parse_sphinx(
698 docstring,
699 parent=Function(
700 "func",
701 parameters=Parameters(
702 Parameter("b"),
703 ),
704 ),
705 )
706 assert len(warnings) == 1
707 assert "Parameter 'a' does not appear in the function signature" in warnings[0]
710def test_parse__param_type_no_type__error_message(parse_sphinx: ParserType) -> None:
711 """Parse a simple docstring.
713 Parameters:
714 parse_sphinx: Fixture parser.
715 """
716 docstring = f"""
717 Docstring with line continuation.
719 :param str foo: {SOME_TEXT}
720 :type str
721 """
723 _, warnings = parse_sphinx(
724 docstring,
725 parent=Function("func", parameters=Parameters(Parameter("foo", annotation="str", kind=None))),
726 )
727 assert "Failed to get ':directive: value' pair from" in warnings[0]
730def test_parse__param_type_no_name__error_message(parse_sphinx: ParserType) -> None:
731 """Parse a simple docstring.
733 Parameters:
734 parse_sphinx: Fixture parser.
735 """
736 docstring = f"""
737 Docstring with line continuation.
739 :param str foo: {SOME_TEXT}
740 :type: str
741 """
743 _, warnings = parse_sphinx(
744 docstring,
745 parent=Function("func", parameters=Parameters(Parameter("foo", annotation="str", kind=None))),
746 )
747 assert "Failed to get parameter name from" in warnings[0]
750def test_parse__param_multiline(parse_sphinx: ParserType) -> None:
751 """Parse multiline parameter descriptions.
753 Parameters:
754 parse_sphinx: Fixture parser.
755 """
756 docstring = """Do something.
758 :param foo: This is a docstring that is long enough to run onto a second line,
759 because it is quite long.
761 A second paragraph is also required.
762 :param bar: This is an example that is quite long, and also requires bullet
763 points to be clear about intent:
765 * First thing
767 ```
768 a code block
769 ```
771 * Second thing
772 * Third thing
773 """
775 sections, _ = parse_sphinx(docstring)
776 param_section = sections[1]
778 param_foo = param_section.value[0]
779 assert param_foo.description == (
780 "This is a docstring that is long enough to run onto a second line,\n"
781 "because it is quite long.\n\n"
782 "A second paragraph is also required."
783 )
785 param_bar = param_section.value[1]
786 assert param_bar.description == (
787 "This is an example that is quite long, and also requires bullet\n"
788 "points to be clear about intent:\n\n"
789 "* First thing\n\n"
790 " ```\n"
791 " a code block\n"
792 " ```\n\n"
793 "* Second thing\n"
794 "* Third thing"
795 )
798@pytest.mark.parametrize(
799 "docstring",
800 [
801 f"""
802 Docstring with param with continuation, no indent.
804 :var {SOME_NAME}: {SOME_TEXT}
805 {SOME_EXTRA_TEXT}
806 """,
807 f"""
808 Docstring with param with continuation, with indent.
810 :var {SOME_NAME}: {SOME_TEXT}
811 {SOME_EXTRA_TEXT}
812 """,
813 ],
814)
815def test_parse__attribute_field_multi_line__param_section(parse_sphinx: ParserType, docstring: str) -> None:
816 """Parse multiline attributes.
818 Parameters:
819 parse_sphinx: Fixture parser.
820 docstring: A parametrized docstring.
821 """
822 sections, warnings = parse_sphinx(docstring)
823 assert len(sections) == 2
824 assert sections[1].kind is DocstringSectionKind.attributes
825 actual = sections[1].value[0]
826 expected = DocstringAttribute(SOME_NAME, description=f"{SOME_TEXT}\n{SOME_EXTRA_TEXT}")
827 assert isinstance(actual, type(expected))
828 assert actual.as_dict() == expected.as_dict()
829 assert not warnings
832@pytest.mark.parametrize(
833 "attribute_directive_name",
834 [
835 "var",
836 "ivar",
837 "cvar",
838 ],
839)
840def test_parse__all_attribute_names__param_section(parse_sphinx: ParserType, attribute_directive_name: str) -> None:
841 """Parse all attributes directives.
843 Parameters:
844 parse_sphinx: Fixture parser.
845 attribute_directive_name: A parametrized directive name.
846 """
847 sections, warnings = parse_sphinx(
848 f"""
849 Docstring with one line attribute.
851 :{attribute_directive_name} {SOME_NAME}: {SOME_TEXT}
852 """,
853 )
854 assert len(sections) == 2
855 assert sections[1].kind is DocstringSectionKind.attributes
856 actual = sections[1].value[0]
857 expected = DocstringAttribute(SOME_NAME, description=SOME_TEXT)
858 assert isinstance(actual, type(expected))
859 assert actual.as_dict() == expected.as_dict()
860 assert not warnings
863def test_parse__class_attributes__attributes_section(parse_sphinx: ParserType) -> None:
864 """Parse class attributes.
866 Parameters:
867 parse_sphinx: Fixture parser.
868 """
869 docstring = f"""
870 Class docstring with attributes
872 :var foo: {SOME_TEXT}
873 """
875 sections, _ = parse_sphinx(docstring, parent=Class("klass"))
876 assert len(sections) == 2
877 assert sections[1].kind is DocstringSectionKind.attributes
878 actual = sections[1].value[0]
879 expected = DocstringAttribute(SOME_NAME, description=SOME_TEXT)
880 assert isinstance(actual, type(expected))
881 assert actual.as_dict() == expected.as_dict()
884def test_parse__class_attributes_with_type__annotation_in_attributes_section(parse_sphinx: ParserType) -> None:
885 """Parse typed class attributes.
887 Parameters:
888 parse_sphinx: Fixture parser.
889 """
890 docstring = f"""
891 Class docstring with attributes
893 :vartype foo: str
894 :var foo: {SOME_TEXT}
895 """
897 sections, _ = parse_sphinx(docstring, parent=Class("klass"))
898 assert len(sections) == 2
899 assert sections[1].kind is DocstringSectionKind.attributes
900 actual = sections[1].value[0]
901 expected = DocstringAttribute(SOME_NAME, annotation="str", description=SOME_TEXT)
902 assert isinstance(actual, type(expected))
903 assert actual.as_dict() == expected.as_dict()
906def test_parse__attribute_invalid_directive___error(parse_sphinx: ParserType) -> None:
907 """Warn on invalid attribute directive.
909 Parameters:
910 parse_sphinx: Fixture parser.
911 """
912 docstring = f"""
913 Class docstring with attributes
915 :var {SOME_TEXT}
916 """
918 _, warnings = parse_sphinx(docstring)
919 assert "Failed to get ':directive: value' pair from" in warnings[0]
922def test_parse__attribute_no_name__error(parse_sphinx: ParserType) -> None:
923 """Warn on invalid attribute directive.
925 Parameters:
926 parse_sphinx: Fixture parser.
927 """
928 docstring = f"""
929 Class docstring with attributes
931 :var: {SOME_TEXT}
932 """
934 _, warnings = parse_sphinx(docstring)
935 assert "Failed to parse field directive from" in warnings[0]
938def test_parse__attribute_duplicate__error(parse_sphinx: ParserType) -> None:
939 """Warn on duplicate attribute directive.
941 Parameters:
942 parse_sphinx: Fixture parser.
943 """
944 docstring = f"""
945 Class docstring with attributes
947 :var foo: {SOME_TEXT}
948 :var foo: {SOME_TEXT}
949 """
951 _, warnings = parse_sphinx(docstring)
952 assert "Duplicate attribute entry for 'foo'" in warnings[0]
955def test_parse__class_attributes_type_invalid__error(parse_sphinx: ParserType) -> None:
956 """Warn on invalid attribute type directive.
958 Parameters:
959 parse_sphinx: Fixture parser.
960 """
961 docstring = f"""
962 Class docstring with attributes
964 :vartype str
965 :var foo: {SOME_TEXT}
966 """
968 _, warnings = parse_sphinx(docstring)
969 assert "Failed to get ':directive: value' pair from " in warnings[0]
972def test_parse__class_attributes_type_no_name__error(parse_sphinx: ParserType) -> None:
973 """Warn on invalid attribute directive.
975 Parameters:
976 parse_sphinx: Fixture parser.
977 """
978 docstring = f"""
979 Class docstring with attributes
981 :vartype: str
982 :var foo: {SOME_TEXT}
983 """
985 _, warnings = parse_sphinx(docstring)
986 assert "Failed to get attribute name from" in warnings[0]
989def test_parse__return_directive__return_section_no_type(parse_sphinx: ParserType) -> None:
990 """Parse return directives.
992 Parameters:
993 parse_sphinx: Fixture parser.
994 """
995 docstring = f"""
996 Function with only return directive
998 :return: {SOME_TEXT}
999 """
1001 sections, _ = parse_sphinx(docstring)
1002 assert len(sections) == 2
1003 assert sections[1].kind is DocstringSectionKind.returns
1004 actual = sections[1].value[0]
1005 expected = DocstringReturn(name="", annotation=None, description=SOME_TEXT)
1006 assert isinstance(actual, type(expected))
1007 assert actual.as_dict() == expected.as_dict()
1010def test_parse__return_directive_rtype__return_section_with_type(parse_sphinx: ParserType) -> None:
1011 """Parse typed return directives.
1013 Parameters:
1014 parse_sphinx: Fixture parser.
1015 """
1016 docstring = f"""
1017 Function with only return & rtype directive
1019 :return: {SOME_TEXT}
1020 :rtype: str
1021 """
1023 sections, _ = parse_sphinx(docstring)
1024 assert len(sections) == 2
1025 assert sections[1].kind is DocstringSectionKind.returns
1026 actual = sections[1].value[0]
1027 expected = DocstringReturn(name="", annotation="str", description=SOME_TEXT)
1028 assert isinstance(actual, type(expected))
1029 assert actual.as_dict() == expected.as_dict()
1032def test_parse__return_directive_rtype_first__return_section_with_type(parse_sphinx: ParserType) -> None:
1033 """Parse typed-first return directives.
1035 Parameters:
1036 parse_sphinx: Fixture parser.
1037 """
1038 docstring = f"""
1039 Function with only return & rtype directive
1041 :rtype: str
1042 :return: {SOME_TEXT}
1043 """
1045 sections, _ = parse_sphinx(docstring)
1046 assert len(sections) == 2
1047 assert sections[1].kind is DocstringSectionKind.returns
1048 actual = sections[1].value[0]
1049 expected = DocstringReturn(name="", annotation="str", description=SOME_TEXT)
1050 assert isinstance(actual, type(expected))
1051 assert actual.as_dict() == expected.as_dict()
1054def test_parse__return_directive_annotation__return_section_with_type(parse_sphinx: ParserType) -> None:
1055 """Parse return directives with return annotation.
1057 Parameters:
1058 parse_sphinx: Fixture parser.
1059 """
1060 docstring = f"""
1061 Function with return directive, rtype directive, & annotation
1063 :return: {SOME_TEXT}
1064 """
1066 sections, _ = parse_sphinx(docstring, parent=Function("func", returns="str"))
1067 assert len(sections) == 2
1068 assert sections[1].kind is DocstringSectionKind.returns
1069 actual = sections[1].value[0]
1070 expected = DocstringReturn(name="", annotation="str", description=SOME_TEXT)
1071 assert isinstance(actual, type(expected))
1072 assert actual.as_dict() == expected.as_dict()
1075def test_parse__return_directive_annotation__prefer_return_directive(parse_sphinx: ParserType) -> None:
1076 """Prefer docstring type over return annotation.
1078 Parameters:
1079 parse_sphinx: Fixture parser.
1080 """
1081 docstring = f"""
1082 Function with return directive, rtype directive, & annotation
1084 :return: {SOME_TEXT}
1085 :rtype: str
1086 """
1088 sections, _ = parse_sphinx(docstring, parent=Function("func", returns="int"))
1089 assert len(sections) == 2
1090 assert sections[1].kind is DocstringSectionKind.returns
1091 actual = sections[1].value[0]
1092 expected = DocstringReturn(name="", annotation="str", description=SOME_TEXT)
1093 assert isinstance(actual, type(expected))
1094 assert actual.as_dict() == expected.as_dict()
1097def test_parse__return_invalid__error(parse_sphinx: ParserType) -> None:
1098 """Warn on invalid return directive.
1100 Parameters:
1101 parse_sphinx: Fixture parser.
1102 """
1103 docstring = f"""
1104 Function with only return directive
1106 :return {SOME_TEXT}
1107 """
1109 _, warnings = parse_sphinx(docstring)
1110 assert "Failed to get ':directive: value' pair from " in warnings[0]
1113def test_parse__rtype_invalid__error(parse_sphinx: ParserType) -> None:
1114 """Warn on invalid typed return directive.
1116 Parameters:
1117 parse_sphinx: Fixture parser.
1118 """
1119 docstring = """
1120 Function with only return directive
1122 :rtype str
1123 """
1125 _, warnings = parse_sphinx(docstring)
1126 assert "Failed to get ':directive: value' pair from " in warnings[0]
1129def test_parse__raises_directive__exception_section(parse_sphinx: ParserType) -> None:
1130 """Parse raise directives.
1132 Parameters:
1133 parse_sphinx: Fixture parser.
1134 """
1135 docstring = f"""
1136 Function with only return directive
1138 :raise SomeException: {SOME_TEXT}
1139 """
1141 sections, _ = parse_sphinx(docstring)
1142 assert len(sections) == 2
1143 assert sections[1].kind is DocstringSectionKind.raises
1144 actual = sections[1].value[0]
1145 expected = DocstringRaise(annotation=SOME_EXCEPTION_NAME, description=SOME_TEXT)
1146 assert isinstance(actual, type(expected))
1147 assert actual.as_dict() == expected.as_dict()
1150def test_parse__multiple_raises_directive__exception_section_with_two(parse_sphinx: ParserType) -> None:
1151 """Parse multiple raise directives.
1153 Parameters:
1154 parse_sphinx: Fixture parser.
1155 """
1156 docstring = f"""
1157 Function with only return directive
1159 :raise SomeException: {SOME_TEXT}
1160 :raise SomeOtherException: {SOME_TEXT}
1161 """
1163 sections, _ = parse_sphinx(docstring)
1164 assert len(sections) == 2
1165 assert sections[1].kind is DocstringSectionKind.raises
1166 actual = sections[1].value[0]
1167 expected = DocstringRaise(annotation=SOME_EXCEPTION_NAME, description=SOME_TEXT)
1168 assert isinstance(actual, type(expected))
1169 assert actual.as_dict() == expected.as_dict()
1170 actual = sections[1].value[1]
1171 expected = DocstringRaise(annotation=SOME_OTHER_EXCEPTION_NAME, description=SOME_TEXT)
1172 assert isinstance(actual, type(expected))
1173 assert actual.as_dict() == expected.as_dict()
1176@pytest.mark.parametrize(
1177 "raise_directive_name",
1178 [
1179 "raises",
1180 "raise",
1181 "except",
1182 "exception",
1183 ],
1184)
1185def test_parse__all_exception_names__param_section(parse_sphinx: ParserType, raise_directive_name: str) -> None:
1186 """Parse all raise directives.
1188 Parameters:
1189 parse_sphinx: Fixture parser.
1190 raise_directive_name: A parametrized directive name.
1191 """
1192 sections, _ = parse_sphinx(
1193 f"""
1194 Docstring with one line attribute.
1196 :{raise_directive_name} {SOME_EXCEPTION_NAME}: {SOME_TEXT}
1197 """,
1198 )
1199 assert len(sections) == 2
1200 assert sections[1].kind is DocstringSectionKind.raises
1201 actual = sections[1].value[0]
1202 expected = DocstringRaise(annotation=SOME_EXCEPTION_NAME, description=SOME_TEXT)
1203 assert isinstance(actual, type(expected))
1204 assert actual.as_dict() == expected.as_dict()
1207def test_parse__raise_invalid__error(parse_sphinx: ParserType) -> None:
1208 """Warn on invalid raise directives.
1210 Parameters:
1211 parse_sphinx: Fixture parser.
1212 """
1213 docstring = f"""
1214 Function with only return directive
1216 :raise {SOME_TEXT}
1217 """
1219 _, warnings = parse_sphinx(docstring)
1220 assert "Failed to get ':directive: value' pair from " in warnings[0]
1223def test_parse__raise_no_name__error(parse_sphinx: ParserType) -> None:
1224 """Warn on invalid raise directives.
1226 Parameters:
1227 parse_sphinx: Fixture parser.
1228 """
1229 docstring = f"""
1230 Function with only return directive
1232 :raise: {SOME_TEXT}
1233 """
1235 _, warnings = parse_sphinx(docstring)
1236 assert "Failed to parse exception directive from" in warnings[0]
1239def test_parse__module_attributes_section__expected_attributes_section(parse_sphinx: ParserType) -> None:
1240 """Parse attributes section in modules.
1242 Parameters:
1243 parse_sphinx: Fixture parser.
1244 """
1245 docstring = """
1246 Let's describe some attributes.
1248 :var A: Alpha.
1249 :vartype B: bytes
1250 :var B: Beta.
1251 :var C: Gamma.
1252 :var D: Delta.
1253 :var E: Epsilon.
1254 :vartype E: float
1255 """
1257 module = Module("mod", filepath=None)
1258 module["A"] = Attribute("A", annotation="int", value="0")
1259 module["B"] = Attribute("B", annotation="str", value=repr("ŧ"))
1260 module["C"] = Attribute("C", annotation="bool", value="True")
1261 module["D"] = Attribute("D", annotation=None, value="3.0")
1262 module["E"] = Attribute("E", annotation=None, value="None")
1263 sections, warnings = parse_sphinx(docstring, parent=module)
1265 attr_section = sections[1]
1266 assert attr_section.kind is DocstringSectionKind.attributes
1267 assert len(attr_section.value) == 5
1268 expected_data: list[dict[str, Any]] = [
1269 {"name": "A", "annotation": "int", "description": "Alpha."},
1270 {"name": "B", "annotation": "bytes", "description": "Beta."},
1271 {"name": "C", "annotation": "bool", "description": "Gamma."},
1272 {"name": "D", "annotation": None, "description": "Delta."},
1273 {"name": "E", "annotation": "float", "description": "Epsilon."},
1274 ]
1275 for index, expected_kwargs in enumerate(expected_data):
1276 actual = attr_section.value[index]
1277 expected = DocstringAttribute(**expected_kwargs)
1278 assert isinstance(actual, type(expected))
1279 assert actual.name == expected.name
1280 assert actual.as_dict() == expected.as_dict()
1281 assert not warnings
1284def test_parse__properties_return_type(parse_sphinx: ParserType) -> None:
1285 """Parse attributes section in modules.
1287 Parameters:
1288 parse_sphinx: Fixture parser.
1289 """
1290 docstring = """
1291 Property that returns True for explaining the issue.
1293 :return: True
1294 """
1295 prop = Attribute("example", annotation="bool")
1296 sections, warnings = parse_sphinx(docstring, parent=prop)
1297 assert not warnings
1298 assert sections[1].value[0].annotation == "bool"
1301# =============================================================================================
1302# Warnings
1303def test_disabled_warnings(parse_sphinx: ParserType) -> None:
1304 """Assert warnings are disabled.
1306 Parameters:
1307 parse_sphinx: Fixture parser.
1308 """
1309 docstring = ":param x: X value."
1310 _, warnings = parse_sphinx(docstring, warnings=True)
1311 assert warnings
1312 _, warnings = parse_sphinx(docstring, warnings=False)
1313 assert not warnings