Coverage for tests/test_docstrings/test_sphinx.py: 100.00%
435 statements
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-11 13:44 +0200
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-11 13:44 +0200
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} {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]
750@pytest.mark.parametrize(
751 "docstring",
752 [
753 f"""
754 Docstring with param with continuation, no indent.
756 :var {SOME_NAME}: {SOME_TEXT}
757 {SOME_EXTRA_TEXT}
758 """,
759 f"""
760 Docstring with param with continuation, with indent.
762 :var {SOME_NAME}: {SOME_TEXT}
763 {SOME_EXTRA_TEXT}
764 """,
765 ],
766)
767def test_parse__attribute_field_multi_line__param_section(parse_sphinx: ParserType, docstring: str) -> None:
768 """Parse multiline attributes.
770 Parameters:
771 parse_sphinx: Fixture parser.
772 docstring: A parametrized docstring.
773 """
774 sections, warnings = parse_sphinx(docstring)
775 assert len(sections) == 2
776 assert sections[1].kind is DocstringSectionKind.attributes
777 actual = sections[1].value[0]
778 expected = DocstringAttribute(SOME_NAME, description=f"{SOME_TEXT} {SOME_EXTRA_TEXT}")
779 assert isinstance(actual, type(expected))
780 assert actual.as_dict() == expected.as_dict()
781 assert not warnings
784@pytest.mark.parametrize(
785 "attribute_directive_name",
786 [
787 "var",
788 "ivar",
789 "cvar",
790 ],
791)
792def test_parse__all_attribute_names__param_section(parse_sphinx: ParserType, attribute_directive_name: str) -> None:
793 """Parse all attributes directives.
795 Parameters:
796 parse_sphinx: Fixture parser.
797 attribute_directive_name: A parametrized directive name.
798 """
799 sections, warnings = parse_sphinx(
800 f"""
801 Docstring with one line attribute.
803 :{attribute_directive_name} {SOME_NAME}: {SOME_TEXT}
804 """,
805 )
806 assert len(sections) == 2
807 assert sections[1].kind is DocstringSectionKind.attributes
808 actual = sections[1].value[0]
809 expected = DocstringAttribute(SOME_NAME, description=SOME_TEXT)
810 assert isinstance(actual, type(expected))
811 assert actual.as_dict() == expected.as_dict()
812 assert not warnings
815def test_parse__class_attributes__attributes_section(parse_sphinx: ParserType) -> None:
816 """Parse class attributes.
818 Parameters:
819 parse_sphinx: Fixture parser.
820 """
821 docstring = f"""
822 Class docstring with attributes
824 :var foo: {SOME_TEXT}
825 """
827 sections, _ = parse_sphinx(docstring, parent=Class("klass"))
828 assert len(sections) == 2
829 assert sections[1].kind is DocstringSectionKind.attributes
830 actual = sections[1].value[0]
831 expected = DocstringAttribute(SOME_NAME, description=SOME_TEXT)
832 assert isinstance(actual, type(expected))
833 assert actual.as_dict() == expected.as_dict()
836def test_parse__class_attributes_with_type__annotation_in_attributes_section(parse_sphinx: ParserType) -> None:
837 """Parse typed class attributes.
839 Parameters:
840 parse_sphinx: Fixture parser.
841 """
842 docstring = f"""
843 Class docstring with attributes
845 :vartype foo: str
846 :var foo: {SOME_TEXT}
847 """
849 sections, _ = parse_sphinx(docstring, parent=Class("klass"))
850 assert len(sections) == 2
851 assert sections[1].kind is DocstringSectionKind.attributes
852 actual = sections[1].value[0]
853 expected = DocstringAttribute(SOME_NAME, annotation="str", description=SOME_TEXT)
854 assert isinstance(actual, type(expected))
855 assert actual.as_dict() == expected.as_dict()
858def test_parse__attribute_invalid_directive___error(parse_sphinx: ParserType) -> None:
859 """Warn on invalid attribute directive.
861 Parameters:
862 parse_sphinx: Fixture parser.
863 """
864 docstring = f"""
865 Class docstring with attributes
867 :var {SOME_TEXT}
868 """
870 _, warnings = parse_sphinx(docstring)
871 assert "Failed to get ':directive: value' pair from" in warnings[0]
874def test_parse__attribute_no_name__error(parse_sphinx: ParserType) -> None:
875 """Warn on invalid attribute directive.
877 Parameters:
878 parse_sphinx: Fixture parser.
879 """
880 docstring = f"""
881 Class docstring with attributes
883 :var: {SOME_TEXT}
884 """
886 _, warnings = parse_sphinx(docstring)
887 assert "Failed to parse field directive from" in warnings[0]
890def test_parse__attribute_duplicate__error(parse_sphinx: ParserType) -> None:
891 """Warn on duplicate attribute directive.
893 Parameters:
894 parse_sphinx: Fixture parser.
895 """
896 docstring = f"""
897 Class docstring with attributes
899 :var foo: {SOME_TEXT}
900 :var foo: {SOME_TEXT}
901 """
903 _, warnings = parse_sphinx(docstring)
904 assert "Duplicate attribute entry for 'foo'" in warnings[0]
907def test_parse__class_attributes_type_invalid__error(parse_sphinx: ParserType) -> None:
908 """Warn on invalid attribute type directive.
910 Parameters:
911 parse_sphinx: Fixture parser.
912 """
913 docstring = f"""
914 Class docstring with attributes
916 :vartype str
917 :var foo: {SOME_TEXT}
918 """
920 _, warnings = parse_sphinx(docstring)
921 assert "Failed to get ':directive: value' pair from " in warnings[0]
924def test_parse__class_attributes_type_no_name__error(parse_sphinx: ParserType) -> None:
925 """Warn on invalid attribute directive.
927 Parameters:
928 parse_sphinx: Fixture parser.
929 """
930 docstring = f"""
931 Class docstring with attributes
933 :vartype: str
934 :var foo: {SOME_TEXT}
935 """
937 _, warnings = parse_sphinx(docstring)
938 assert "Failed to get attribute name from" in warnings[0]
941def test_parse__return_directive__return_section_no_type(parse_sphinx: ParserType) -> None:
942 """Parse return directives.
944 Parameters:
945 parse_sphinx: Fixture parser.
946 """
947 docstring = f"""
948 Function with only return directive
950 :return: {SOME_TEXT}
951 """
953 sections, _ = parse_sphinx(docstring)
954 assert len(sections) == 2
955 assert sections[1].kind is DocstringSectionKind.returns
956 actual = sections[1].value[0]
957 expected = DocstringReturn(name="", annotation=None, description=SOME_TEXT)
958 assert isinstance(actual, type(expected))
959 assert actual.as_dict() == expected.as_dict()
962def test_parse__return_directive_rtype__return_section_with_type(parse_sphinx: ParserType) -> None:
963 """Parse typed return directives.
965 Parameters:
966 parse_sphinx: Fixture parser.
967 """
968 docstring = f"""
969 Function with only return & rtype directive
971 :return: {SOME_TEXT}
972 :rtype: str
973 """
975 sections, _ = parse_sphinx(docstring)
976 assert len(sections) == 2
977 assert sections[1].kind is DocstringSectionKind.returns
978 actual = sections[1].value[0]
979 expected = DocstringReturn(name="", annotation="str", description=SOME_TEXT)
980 assert isinstance(actual, type(expected))
981 assert actual.as_dict() == expected.as_dict()
984def test_parse__return_directive_rtype_first__return_section_with_type(parse_sphinx: ParserType) -> None:
985 """Parse typed-first return directives.
987 Parameters:
988 parse_sphinx: Fixture parser.
989 """
990 docstring = f"""
991 Function with only return & rtype directive
993 :rtype: str
994 :return: {SOME_TEXT}
995 """
997 sections, _ = parse_sphinx(docstring)
998 assert len(sections) == 2
999 assert sections[1].kind is DocstringSectionKind.returns
1000 actual = sections[1].value[0]
1001 expected = DocstringReturn(name="", annotation="str", description=SOME_TEXT)
1002 assert isinstance(actual, type(expected))
1003 assert actual.as_dict() == expected.as_dict()
1006def test_parse__return_directive_annotation__return_section_with_type(parse_sphinx: ParserType) -> None:
1007 """Parse return directives with return annotation.
1009 Parameters:
1010 parse_sphinx: Fixture parser.
1011 """
1012 docstring = f"""
1013 Function with return directive, rtype directive, & annotation
1015 :return: {SOME_TEXT}
1016 """
1018 sections, _ = parse_sphinx(docstring, parent=Function("func", returns="str"))
1019 assert len(sections) == 2
1020 assert sections[1].kind is DocstringSectionKind.returns
1021 actual = sections[1].value[0]
1022 expected = DocstringReturn(name="", annotation="str", description=SOME_TEXT)
1023 assert isinstance(actual, type(expected))
1024 assert actual.as_dict() == expected.as_dict()
1027def test_parse__return_directive_annotation__prefer_return_directive(parse_sphinx: ParserType) -> None:
1028 """Prefer docstring type over return annotation.
1030 Parameters:
1031 parse_sphinx: Fixture parser.
1032 """
1033 docstring = f"""
1034 Function with return directive, rtype directive, & annotation
1036 :return: {SOME_TEXT}
1037 :rtype: str
1038 """
1040 sections, _ = parse_sphinx(docstring, parent=Function("func", returns="int"))
1041 assert len(sections) == 2
1042 assert sections[1].kind is DocstringSectionKind.returns
1043 actual = sections[1].value[0]
1044 expected = DocstringReturn(name="", annotation="str", description=SOME_TEXT)
1045 assert isinstance(actual, type(expected))
1046 assert actual.as_dict() == expected.as_dict()
1049def test_parse__return_invalid__error(parse_sphinx: ParserType) -> None:
1050 """Warn on invalid return directive.
1052 Parameters:
1053 parse_sphinx: Fixture parser.
1054 """
1055 docstring = f"""
1056 Function with only return directive
1058 :return {SOME_TEXT}
1059 """
1061 _, warnings = parse_sphinx(docstring)
1062 assert "Failed to get ':directive: value' pair from " in warnings[0]
1065def test_parse__rtype_invalid__error(parse_sphinx: ParserType) -> None:
1066 """Warn on invalid typed return directive.
1068 Parameters:
1069 parse_sphinx: Fixture parser.
1070 """
1071 docstring = """
1072 Function with only return directive
1074 :rtype str
1075 """
1077 _, warnings = parse_sphinx(docstring)
1078 assert "Failed to get ':directive: value' pair from " in warnings[0]
1081def test_parse__raises_directive__exception_section(parse_sphinx: ParserType) -> None:
1082 """Parse raise directives.
1084 Parameters:
1085 parse_sphinx: Fixture parser.
1086 """
1087 docstring = f"""
1088 Function with only return directive
1090 :raise SomeException: {SOME_TEXT}
1091 """
1093 sections, _ = parse_sphinx(docstring)
1094 assert len(sections) == 2
1095 assert sections[1].kind is DocstringSectionKind.raises
1096 actual = sections[1].value[0]
1097 expected = DocstringRaise(annotation=SOME_EXCEPTION_NAME, description=SOME_TEXT)
1098 assert isinstance(actual, type(expected))
1099 assert actual.as_dict() == expected.as_dict()
1102def test_parse__multiple_raises_directive__exception_section_with_two(parse_sphinx: ParserType) -> None:
1103 """Parse multiple raise directives.
1105 Parameters:
1106 parse_sphinx: Fixture parser.
1107 """
1108 docstring = f"""
1109 Function with only return directive
1111 :raise SomeException: {SOME_TEXT}
1112 :raise SomeOtherException: {SOME_TEXT}
1113 """
1115 sections, _ = parse_sphinx(docstring)
1116 assert len(sections) == 2
1117 assert sections[1].kind is DocstringSectionKind.raises
1118 actual = sections[1].value[0]
1119 expected = DocstringRaise(annotation=SOME_EXCEPTION_NAME, description=SOME_TEXT)
1120 assert isinstance(actual, type(expected))
1121 assert actual.as_dict() == expected.as_dict()
1122 actual = sections[1].value[1]
1123 expected = DocstringRaise(annotation=SOME_OTHER_EXCEPTION_NAME, description=SOME_TEXT)
1124 assert isinstance(actual, type(expected))
1125 assert actual.as_dict() == expected.as_dict()
1128@pytest.mark.parametrize(
1129 "raise_directive_name",
1130 [
1131 "raises",
1132 "raise",
1133 "except",
1134 "exception",
1135 ],
1136)
1137def test_parse__all_exception_names__param_section(parse_sphinx: ParserType, raise_directive_name: str) -> None:
1138 """Parse all raise directives.
1140 Parameters:
1141 parse_sphinx: Fixture parser.
1142 raise_directive_name: A parametrized directive name.
1143 """
1144 sections, _ = parse_sphinx(
1145 f"""
1146 Docstring with one line attribute.
1148 :{raise_directive_name} {SOME_EXCEPTION_NAME}: {SOME_TEXT}
1149 """,
1150 )
1151 assert len(sections) == 2
1152 assert sections[1].kind is DocstringSectionKind.raises
1153 actual = sections[1].value[0]
1154 expected = DocstringRaise(annotation=SOME_EXCEPTION_NAME, description=SOME_TEXT)
1155 assert isinstance(actual, type(expected))
1156 assert actual.as_dict() == expected.as_dict()
1159def test_parse__raise_invalid__error(parse_sphinx: ParserType) -> None:
1160 """Warn on invalid raise directives.
1162 Parameters:
1163 parse_sphinx: Fixture parser.
1164 """
1165 docstring = f"""
1166 Function with only return directive
1168 :raise {SOME_TEXT}
1169 """
1171 _, warnings = parse_sphinx(docstring)
1172 assert "Failed to get ':directive: value' pair from " in warnings[0]
1175def test_parse__raise_no_name__error(parse_sphinx: ParserType) -> None:
1176 """Warn on invalid raise directives.
1178 Parameters:
1179 parse_sphinx: Fixture parser.
1180 """
1181 docstring = f"""
1182 Function with only return directive
1184 :raise: {SOME_TEXT}
1185 """
1187 _, warnings = parse_sphinx(docstring)
1188 assert "Failed to parse exception directive from" in warnings[0]
1191def test_parse__module_attributes_section__expected_attributes_section(parse_sphinx: ParserType) -> None:
1192 """Parse attributes section in modules.
1194 Parameters:
1195 parse_sphinx: Fixture parser.
1196 """
1197 docstring = """
1198 Let's describe some attributes.
1200 :var A: Alpha.
1201 :vartype B: bytes
1202 :var B: Beta.
1203 :var C: Gamma.
1204 :var D: Delta.
1205 :var E: Epsilon.
1206 :vartype E: float
1207 """
1209 module = Module("mod", filepath=None)
1210 module["A"] = Attribute("A", annotation="int", value="0")
1211 module["B"] = Attribute("B", annotation="str", value=repr("ŧ"))
1212 module["C"] = Attribute("C", annotation="bool", value="True")
1213 module["D"] = Attribute("D", annotation=None, value="3.0")
1214 module["E"] = Attribute("E", annotation=None, value="None")
1215 sections, warnings = parse_sphinx(docstring, parent=module)
1217 attr_section = sections[1]
1218 assert attr_section.kind is DocstringSectionKind.attributes
1219 assert len(attr_section.value) == 5
1220 expected_data: list[dict[str, Any]] = [
1221 {"name": "A", "annotation": "int", "description": "Alpha."},
1222 {"name": "B", "annotation": "bytes", "description": "Beta."},
1223 {"name": "C", "annotation": "bool", "description": "Gamma."},
1224 {"name": "D", "annotation": None, "description": "Delta."},
1225 {"name": "E", "annotation": "float", "description": "Epsilon."},
1226 ]
1227 for index, expected_kwargs in enumerate(expected_data):
1228 actual = attr_section.value[index]
1229 expected = DocstringAttribute(**expected_kwargs)
1230 assert isinstance(actual, type(expected))
1231 assert actual.name == expected.name
1232 assert actual.as_dict() == expected.as_dict()
1233 assert not warnings
1236def test_parse__properties_return_type(parse_sphinx: ParserType) -> None:
1237 """Parse attributes section in modules.
1239 Parameters:
1240 parse_sphinx: Fixture parser.
1241 """
1242 docstring = """
1243 Property that returns True for explaining the issue.
1245 :return: True
1246 """
1247 prop = Attribute("example", annotation="bool")
1248 sections, warnings = parse_sphinx(docstring, parent=prop)
1249 assert not warnings
1250 assert sections[1].value[0].annotation == "bool"
1253# =============================================================================================
1254# Warnings
1255def test_disabled_warnings(parse_sphinx: ParserType) -> None:
1256 """Assert warnings are disabled.
1258 Parameters:
1259 parse_sphinx: Fixture parser.
1260 """
1261 docstring = ":param x: X value."
1262 _, warnings = parse_sphinx(docstring, warnings=True)
1263 assert warnings
1264 _, warnings = parse_sphinx(docstring, warnings=False)
1265 assert not warnings