Coverage for tests/test_parsers/test_docstrings/test_restructured_text.py: 97.04%

372 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-09 18:24 +0100

1"""Tests for [the `parsers.docstrings.google` module][pytkdocs.parsers.docstrings.google].""" 

2 

3import inspect 

4from textwrap import dedent 

5from typing import Any, Optional 

6 

7import pytest 

8 

9from pytkdocs.loader import Loader 

10from pytkdocs.objects import Object 

11from pytkdocs.parsers.docstrings.base import AnnotatedObject, Attribute, Parameter, Section, empty 

12from pytkdocs.parsers.docstrings.restructured_text import RestructuredText 

13from pytkdocs.serializer import serialize_attribute 

14 

15 

16class DummyObject: 

17 def __init__(self, signature, return_type): # noqa: D107, ANN001 

18 self.path = "o" 

19 self.signature = signature 

20 self.type = return_type 

21 

22 

23SOME_NAME = "foo" 

24SOME_TEXT = "descriptive test text" 

25SOME_EXTRA_TEXT = "more test text" 

26SOME_EXCEPTION_NAME = "SomeException" 

27SOME_OTHER_EXCEPTION_NAME = "SomeOtherException" 

28 

29 

30def dedent_strip(text: str) -> str: 

31 return dedent(text).strip() 

32 

33 

34def parse(obj: Any, *, strip_docstring: bool = True) -> tuple[list[Section], list[str]]: 

35 """Helper to parse a docstring.""" 

36 return parse_detailed(inspect.getdoc(obj) or "", inspect.signature(obj), strip_docstring=strip_docstring) 

37 

38 

39def parse_detailed( 

40 docstring: str, 

41 signature: Optional[inspect.Signature] = None, 

42 return_type: Any = inspect.Signature.empty, 

43 *, 

44 strip_docstring: bool = True, 

45) -> tuple[list[Section], list[str]]: 

46 """Helper to parse a docstring.""" 

47 docstring = dedent_strip(docstring) if strip_docstring else dedent(docstring) 

48 

49 return RestructuredText().parse(docstring, {"obj": DummyObject(signature, return_type)}) 

50 

51 

52def assert_parameter_equal(actual: Parameter, expected: Parameter) -> None: 

53 assert actual.name == expected.name 

54 assert_annotated_obj_equal(actual, expected) 

55 assert actual.kind == expected.kind 

56 assert actual.default == expected.default 

57 

58 

59def assert_attribute_equal(actual: Attribute, expected: Attribute) -> None: 

60 assert actual.name == expected.name 

61 assert_annotated_obj_equal(actual, expected) 

62 

63 

64def assert_annotated_obj_equal(actual: AnnotatedObject, expected: AnnotatedObject) -> None: 

65 assert actual.annotation == expected.annotation 

66 assert actual.description == expected.description 

67 

68 

69def get_rst_object_documentation(dotted_fixture_subpath: str) -> Object: 

70 return Loader(docstring_style="restructured-text").get_object_documentation( 

71 f"tests.fixtures.parsing.restructured_text.{dotted_fixture_subpath}", 

72 ) 

73 

74 

75@pytest.mark.parametrize( 

76 "docstring", 

77 [ 

78 "One line docstring description", 

79 """ 

80 Multiple line docstring description. 

81 

82 With more text. 

83 """, 

84 ], 

85) 

86def test_parse__description_only_docstring__single_markdown_section(docstring: str) -> None: 

87 sections, errors = parse_detailed(docstring) 

88 

89 assert len(sections) == 1 

90 assert sections[0].type == Section.Type.MARKDOWN 

91 assert sections[0].value == dedent_strip(docstring) 

92 assert not errors 

93 

94 

95def test_parse__no_description__single_markdown_section() -> None: 

96 sections, errors = parse_detailed("") 

97 

98 assert len(sections) == 1 

99 assert sections[0].type == Section.Type.MARKDOWN 

100 assert sections[0].value == "" 

101 assert not errors 

102 

103 

104def test_parse__multiple_blank_lines_before_description__single_markdown_section() -> None: 

105 sections, errors = parse_detailed( 

106 """ 

107  

108  

109 Now text""", # noqa: W293 

110 strip_docstring=False, 

111 ) 

112 

113 assert len(sections) == 1 

114 assert sections[0].type == Section.Type.MARKDOWN 

115 assert sections[0].value == "Now text" 

116 assert not errors 

117 

118 

119def test_parse__description_with_initial_newline__single_markdown_section() -> None: 

120 docstring = """ 

121 With initial newline 

122 """ 

123 sections, errors = parse_detailed(docstring, strip_docstring=False) 

124 

125 assert len(sections) == 1 

126 assert sections[0].type == Section.Type.MARKDOWN 

127 assert sections[0].value == dedent_strip(docstring) 

128 assert not errors 

129 

130 

131def test_parse__param_field__param_section() -> None: 

132 """Parse a simple docstring.""" 

133 sections, errors = parse_detailed( 

134 f""" 

135 Docstring with one line param. 

136 

137 :param {SOME_NAME}: {SOME_TEXT} 

138 """, 

139 ) 

140 assert len(sections) == 2 

141 assert sections[1].type == Section.Type.PARAMETERS 

142 assert_parameter_equal( 

143 sections[1].value[0], 

144 Parameter(SOME_NAME, annotation=empty, description=SOME_TEXT, kind=empty), 

145 ) 

146 

147 

148def test_parse__only_param_field__empty_markdown() -> None: 

149 sections, errors = parse_detailed(":param foo: text") 

150 assert len(sections) == 2 

151 assert sections[0].type == Section.Type.MARKDOWN 

152 assert sections[0].value == "" 

153 

154 

155@pytest.mark.parametrize( 

156 "param_directive_name", 

157 [ 

158 "param", 

159 "parameter", 

160 "arg", 

161 "argument", 

162 "key", 

163 "keyword", 

164 ], 

165) 

166def test_parse__all_param_names__param_section(param_directive_name: str) -> None: 

167 sections, errors = parse_detailed( 

168 f""" 

169 Docstring with one line param. 

170 

171 :{param_directive_name} {SOME_NAME}: {SOME_TEXT} 

172 """, 

173 ) 

174 assert len(sections) == 2 

175 assert sections[1].type == Section.Type.PARAMETERS 

176 assert_parameter_equal( 

177 sections[1].value[0], 

178 Parameter(SOME_NAME, annotation=empty, description=SOME_TEXT, kind=empty), 

179 ) 

180 

181 

182@pytest.mark.parametrize( 

183 "docstring", 

184 [ 

185 f""" 

186 Docstring with param with continuation, no indent. 

187 

188 :param {SOME_NAME}: {SOME_TEXT} 

189 {SOME_EXTRA_TEXT} 

190 """, 

191 f""" 

192 Docstring with param with continuation, with indent. 

193 

194 :param {SOME_NAME}: {SOME_TEXT} 

195 {SOME_EXTRA_TEXT} 

196 """, 

197 ], 

198) 

199def test_parse__param_field_multi_line__param_section(docstring: str) -> None: 

200 """Parse a simple docstring.""" 

201 sections, errors = parse_detailed(docstring) 

202 assert len(sections) == 2 

203 assert sections[1].type == Section.Type.PARAMETERS 

204 assert_parameter_equal( 

205 sections[1].value[0], 

206 Parameter(SOME_NAME, annotation=empty, description=f"{SOME_TEXT} {SOME_EXTRA_TEXT}", kind=empty), 

207 ) 

208 

209 

210def test_parse__param_field_for_function__param_section_with_kind() -> None: 

211 """Parse a simple docstring.""" 

212 

213 def f(foo): # noqa: ANN202, ANN001 

214 """Docstring with line continuation. 

215 

216 :param foo: descriptive test text 

217 """ 

218 

219 sections, errors = parse(f) 

220 assert len(sections) == 2 

221 assert sections[1].type == Section.Type.PARAMETERS 

222 assert_parameter_equal( 

223 sections[1].value[0], 

224 Parameter(SOME_NAME, annotation=empty, description=SOME_TEXT, kind=inspect.Parameter.POSITIONAL_OR_KEYWORD), 

225 ) 

226 

227 

228def test_parse__param_field_docs_type__param_section_with_type() -> None: 

229 """Parse a simple docstring.""" 

230 

231 def f(foo): # noqa: ANN202, ANN001 

232 """Docstring with line continuation. 

233 

234 :param str foo: descriptive test text 

235 """ 

236 

237 sections, errors = parse(f) 

238 assert len(sections) == 2 

239 assert sections[1].type == Section.Type.PARAMETERS 

240 assert_parameter_equal( 

241 sections[1].value[0], 

242 Parameter(SOME_NAME, annotation="str", description=SOME_TEXT, kind=inspect.Parameter.POSITIONAL_OR_KEYWORD), 

243 ) 

244 

245 

246def test_parse__param_field_type_field__param_section_with_type() -> None: 

247 """Parse a simple docstring.""" 

248 

249 def f(foo): # noqa: ANN202, ANN001 

250 """Docstring with line continuation. 

251 

252 :param foo: descriptive test text 

253 :type foo: str 

254 """ 

255 

256 sections, errors = parse(f) 

257 assert len(sections) == 2 

258 assert sections[1].type == Section.Type.PARAMETERS 

259 assert_parameter_equal( 

260 sections[1].value[0], 

261 Parameter(SOME_NAME, annotation="str", description=SOME_TEXT, kind=inspect.Parameter.POSITIONAL_OR_KEYWORD), 

262 ) 

263 

264 

265def test_parse__param_field_type_field_first__param_section_with_type() -> None: 

266 """Parse a simple docstring.""" 

267 

268 def f(foo): # noqa: ANN202, ANN001 

269 """Docstring with line continuation. 

270 

271 :type foo: str 

272 :param foo: descriptive test text 

273 """ 

274 

275 sections, errors = parse(f) 

276 assert len(sections) == 2 

277 assert sections[1].type == Section.Type.PARAMETERS 

278 assert_parameter_equal( 

279 sections[1].value[0], 

280 Parameter(SOME_NAME, annotation="str", description=SOME_TEXT, kind=inspect.Parameter.POSITIONAL_OR_KEYWORD), 

281 ) 

282 

283 

284def test_parse__param_field_type_field_or_none__param_section_with_optional() -> None: 

285 """Parse a simple docstring.""" 

286 

287 def f(foo): # noqa: ANN202, ANN001 

288 """Docstring with line continuation. 

289 

290 :param foo: descriptive test text 

291 :type foo: str or None 

292 """ 

293 

294 sections, errors = parse(f) 

295 assert len(sections) == 2 

296 assert sections[1].type == Section.Type.PARAMETERS 

297 assert_parameter_equal( 

298 sections[1].value[0], 

299 Parameter( 

300 SOME_NAME, 

301 annotation="Optional[str]", 

302 description=SOME_TEXT, 

303 kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, 

304 ), 

305 ) 

306 

307 

308def test_parse__param_field_type_none_or_field__param_section_with_optional() -> None: 

309 """Parse a simple docstring.""" 

310 

311 def f(foo): # noqa: ANN202, ANN001 

312 """Docstring with line continuation. 

313 

314 :param foo: descriptive test text 

315 :type foo: None or str 

316 """ 

317 

318 sections, errors = parse(f) 

319 assert len(sections) == 2 

320 assert sections[1].type == Section.Type.PARAMETERS 

321 assert_parameter_equal( 

322 sections[1].value[0], 

323 Parameter( 

324 SOME_NAME, 

325 annotation="Optional[str]", 

326 description=SOME_TEXT, 

327 kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, 

328 ), 

329 ) 

330 

331 

332def test_parse__param_field_type_field_or_int__param_section_with_union() -> None: 

333 """Parse a simple docstring.""" 

334 

335 def f(foo): # noqa: ANN202, ANN001 

336 """Docstring with line continuation. 

337 

338 :param foo: descriptive test text 

339 :type foo: str or int 

340 """ 

341 

342 sections, errors = parse(f) 

343 assert len(sections) == 2 

344 assert sections[1].type == Section.Type.PARAMETERS 

345 assert_parameter_equal( 

346 sections[1].value[0], 

347 Parameter( 

348 SOME_NAME, 

349 annotation="Union[str,int]", 

350 description=SOME_TEXT, 

351 kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, 

352 ), 

353 ) 

354 

355 

356def test_parse__param_field_type_multiple__param_section_with_union() -> None: 

357 """Parse a simple docstring.""" 

358 

359 def f(foo): # noqa: ANN202, ANN001 

360 """Docstring with line continuation. 

361 

362 :param foo: descriptive test text 

363 :type foo: str or int or float 

364 """ 

365 

366 sections, errors = parse(f) 

367 assert len(sections) == 2 

368 assert sections[1].type == Section.Type.PARAMETERS 

369 assert_parameter_equal( 

370 sections[1].value[0], 

371 Parameter( 

372 SOME_NAME, 

373 annotation="Union[str,int,float]", 

374 description=SOME_TEXT, 

375 kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, 

376 ), 

377 ) 

378 

379 

380def test_parse__param_field_annotate_type__param_section_with_type() -> None: 

381 """Parse a simple docstring.""" 

382 

383 def f(foo: str): # noqa: ANN202 

384 """Docstring with line continuation. 

385 

386 :param foo: descriptive test text 

387 """ 

388 

389 sections, errors = parse(f) 

390 assert len(sections) == 2 

391 assert sections[1].type == Section.Type.PARAMETERS 

392 assert_parameter_equal( 

393 sections[1].value[0], 

394 Parameter(SOME_NAME, annotation=str, description=SOME_TEXT, kind=inspect.Parameter.POSITIONAL_OR_KEYWORD), 

395 ) 

396 

397 

398def test_parse__param_field_no_matching_param__result_from_docstring() -> None: 

399 """Parse a simple docstring.""" 

400 

401 def f(foo: str): # noqa: ANN202 

402 """Docstring with line continuation. 

403 

404 :param other: descriptive test text 

405 """ 

406 

407 sections, errors = parse(f) 

408 assert len(sections) == 2 

409 assert sections[1].type == Section.Type.PARAMETERS 

410 assert_parameter_equal( 

411 sections[1].value[0], 

412 Parameter("other", annotation=empty, description=SOME_TEXT, kind=empty), 

413 ) 

414 

415 

416def test_parse__param_field_with_default__result_from_docstring() -> None: 

417 """Parse a simple docstring.""" 

418 

419 def f(foo=""): # noqa: ANN202, ANN001 

420 """Docstring with line continuation. 

421 

422 :param foo: descriptive test text 

423 """ 

424 

425 sections, errors = parse(f) 

426 assert len(sections) == 2 

427 assert sections[1].type == Section.Type.PARAMETERS 

428 assert_parameter_equal( 

429 sections[1].value[0], 

430 Parameter( 

431 "foo", 

432 annotation=empty, 

433 description=SOME_TEXT, 

434 default="", 

435 kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, 

436 ), 

437 ) 

438 

439 

440def test_parse__param_field_no_matching_param__error_message() -> None: 

441 """Parse a simple docstring.""" 

442 

443 def f(foo: str): # noqa: ANN202 

444 """Docstring with line continuation. 

445 

446 :param other: descriptive test text 

447 """ 

448 

449 sections, errors = parse(f) 

450 assert "No matching parameter for 'other'" in errors[0] 

451 

452 

453def test_parse__invalid_param_field_only_initial_marker__error_message() -> None: 

454 """Parse a simple docstring.""" 

455 

456 def f(foo: str): # noqa: ANN202 

457 """Docstring with line continuation. 

458 

459 :param foo descriptive test text 

460 """ 

461 

462 sections, errors = parse(f) 

463 assert "Failed to get ':directive: value' pair" in errors[0] 

464 

465 

466def test_parse__invalid_param_field_wrong_part_count__error_message() -> None: 

467 """Parse a simple docstring.""" 

468 

469 def f(foo: str): # noqa: ANN202 

470 """Docstring with line continuation. 

471 

472 :param: descriptive test text 

473 """ 

474 

475 sections, errors = parse(f) 

476 assert "Failed to parse field directive" in errors[0] 

477 

478 

479def test_parse__param_twice__error_message() -> None: 

480 """Parse a simple docstring.""" 

481 

482 def f(foo: str): # noqa: ANN202 

483 """Docstring with line continuation. 

484 

485 :param foo: descriptive test text 

486 :param foo: descriptive test text again 

487 """ 

488 

489 sections, errors = parse(f) 

490 assert "Duplicate parameter entry for 'foo'" in errors[0] 

491 

492 

493def test_parse__param_type_twice_doc__error_message() -> None: 

494 """Parse a simple docstring.""" 

495 

496 def f(foo): # noqa: ANN202, ANN001 

497 """Docstring with line continuation. 

498 

499 :param str foo: descriptive test text 

500 :type foo: str 

501 """ 

502 

503 sections, errors = parse(f) 

504 assert "Duplicate parameter information for 'foo'" in errors[0] 

505 

506 

507def test_parse__param_type_twice_type_directive_first__error_message() -> None: 

508 """Parse a simple docstring.""" 

509 

510 def f(foo): # noqa: ANN202, ANN001 

511 """Docstring with line continuation. 

512 

513 :type foo: str 

514 :param str foo: descriptive test text 

515 """ 

516 

517 sections, errors = parse(f) 

518 assert "Duplicate parameter information for 'foo'" in errors[0] 

519 

520 

521def test_parse__param_type_twice_annotated__error_message() -> None: 

522 """Parse a simple docstring.""" 

523 

524 def f(foo: str): # noqa: ANN202 

525 """Docstring with line continuation. 

526 

527 :param str foo: descriptive test text 

528 :type foo: str 

529 """ 

530 

531 sections, errors = parse(f) 

532 assert "Duplicate parameter information for 'foo'" in errors[0] 

533 

534 

535def test_parse__param_type_no_type__error_message() -> None: 

536 """Parse a simple docstring.""" 

537 

538 def f(foo: str): # noqa: ANN202 

539 """Docstring with line continuation. 

540 

541 :param str foo: descriptive test text 

542 :type str 

543 """ 

544 

545 sections, errors = parse(f) 

546 assert "Failed to get ':directive: value' pair from" in errors[0] 

547 

548 

549def test_parse__param_type_no_name__error_message() -> None: 

550 """Parse a simple docstring.""" 

551 

552 def f(foo: str): # noqa: ANN202 

553 """Docstring with line continuation. 

554 

555 :param str foo: descriptive test text 

556 :type: str 

557 """ 

558 

559 sections, errors = parse(f) 

560 assert "Failed to get parameter name from" in errors[0] 

561 

562 

563@pytest.mark.parametrize( 

564 "docstring", 

565 [ 

566 f""" 

567 Docstring with param with continuation, no indent. 

568 

569 :var {SOME_NAME}: {SOME_TEXT} 

570 {SOME_EXTRA_TEXT} 

571 """, 

572 f""" 

573 Docstring with param with continuation, with indent. 

574 

575 :var {SOME_NAME}: {SOME_TEXT} 

576 {SOME_EXTRA_TEXT} 

577 """, 

578 ], 

579) 

580def test_parse__attribute_field_multi_line__param_section(docstring: str) -> None: 

581 """Parse a simple docstring.""" 

582 sections, errors = parse_detailed(docstring) 

583 assert len(sections) == 2 

584 assert sections[1].type == Section.Type.ATTRIBUTES 

585 assert_attribute_equal( 

586 sections[1].value[0], 

587 Attribute(SOME_NAME, annotation=empty, description=f"{SOME_TEXT} {SOME_EXTRA_TEXT}"), 

588 ) 

589 

590 

591@pytest.mark.parametrize( 

592 "attribute_directive_name", 

593 [ 

594 "var", 

595 "ivar", 

596 "cvar", 

597 ], 

598) 

599def test_parse__all_attribute_names__param_section(attribute_directive_name: str) -> None: 

600 sections, errors = parse_detailed( 

601 f""" 

602 Docstring with one line attribute. 

603 

604 :{attribute_directive_name} {SOME_NAME}: {SOME_TEXT} 

605 """, 

606 ) 

607 assert len(sections) == 2 

608 assert sections[1].type == Section.Type.ATTRIBUTES 

609 assert_attribute_equal( 

610 sections[1].value[0], 

611 Attribute(SOME_NAME, annotation=empty, description=SOME_TEXT), 

612 ) 

613 

614 

615def test_parse__class_attributes__attributes_section() -> None: 

616 class Foo: 

617 """Class docstring with attributes. 

618 

619 :var foo: descriptive test text 

620 """ 

621 

622 sections, errors = parse(Foo) 

623 assert len(sections) == 2 

624 assert sections[1].type == Section.Type.ATTRIBUTES 

625 assert_attribute_equal( 

626 sections[1].value[0], 

627 Attribute(SOME_NAME, annotation=empty, description=SOME_TEXT), 

628 ) 

629 

630 

631def test_parse__class_attributes_with_type__annotation_in_attributes_section() -> None: 

632 class Foo: 

633 """Class docstring with attributes. 

634 

635 :vartype foo: str 

636 :var foo: descriptive test text 

637 """ 

638 

639 sections, errors = parse(Foo) 

640 assert len(sections) == 2 

641 assert sections[1].type == Section.Type.ATTRIBUTES 

642 assert_attribute_equal( 

643 sections[1].value[0], 

644 Attribute(SOME_NAME, annotation="str", description=SOME_TEXT), 

645 ) 

646 

647 

648def test_parse__attribute_invalid_directive___error() -> None: 

649 class Foo: 

650 """Class docstring with attributes. 

651 

652 :var descriptive test text 

653 """ 

654 

655 sections, errors = parse(Foo) 

656 assert "Failed to get ':directive: value' pair from" in errors[0] 

657 

658 

659def test_parse__attribute_no_name__error() -> None: 

660 class Foo: 

661 """Class docstring with attributes. 

662 

663 :var: descriptive test text 

664 """ 

665 

666 sections, errors = parse(Foo) 

667 assert "Failed to parse field directive from" in errors[0] 

668 

669 

670def test_parse__attribute_duplicate__error() -> None: 

671 class Foo: 

672 """Class docstring with attributes. 

673 

674 :var foo: descriptive test text 

675 :var foo: descriptive test text 

676 """ 

677 

678 sections, errors = parse(Foo) 

679 assert "Duplicate attribute entry for 'foo'" in errors[0] 

680 

681 

682def test_parse__class_attributes_type_invalid__error() -> None: 

683 class Foo: 

684 """Class docstring with attributes. 

685 

686 :vartype str 

687 :var foo: descriptive test text 

688 """ 

689 

690 sections, errors = parse(Foo) 

691 assert "Failed to get ':directive: value' pair from " in errors[0] 

692 

693 

694def test_parse__class_attributes_type_no_name__error() -> None: 

695 class Foo: 

696 """Class docstring with attributes. 

697 

698 :vartype: str 

699 :var foo: descriptive test text 

700 """ 

701 

702 sections, errors = parse(Foo) 

703 assert "Failed to get attribute name from" in errors[0] 

704 

705 

706def test_parse__return_directive__return_section_no_type() -> None: 

707 def f(foo: str): # noqa: ANN202 

708 """Function with only return directive. 

709 

710 :return: descriptive test text 

711 """ 

712 return foo 

713 

714 sections, errors = parse(f) 

715 assert len(sections) == 2 

716 assert sections[1].type == Section.Type.RETURN 

717 assert_annotated_obj_equal( 

718 sections[1].value, 

719 AnnotatedObject(annotation=empty, description=SOME_TEXT), 

720 ) 

721 

722 

723def test_parse__return_directive_rtype__return_section_with_type() -> None: 

724 def f(foo: str): # noqa: ANN202 

725 """Function with only return & rtype directive. 

726 

727 :return: descriptive test text 

728 :rtype: str 

729 """ 

730 return foo 

731 

732 sections, errors = parse(f) 

733 assert len(sections) == 2 

734 assert sections[1].type == Section.Type.RETURN 

735 assert_annotated_obj_equal( 

736 sections[1].value, 

737 AnnotatedObject(annotation="str", description=SOME_TEXT), 

738 ) 

739 

740 

741def test_parse__return_directive_rtype_first__return_section_with_type() -> None: 

742 def f(foo: str): # noqa: ANN202 

743 """Function with only return & rtype directive. 

744 

745 :rtype: str 

746 :return: descriptive test text 

747 """ 

748 return foo 

749 

750 sections, errors = parse(f) 

751 assert len(sections) == 2 

752 assert sections[1].type == Section.Type.RETURN 

753 assert_annotated_obj_equal( 

754 sections[1].value, 

755 AnnotatedObject(annotation="str", description=SOME_TEXT), 

756 ) 

757 

758 

759def test_parse__return_directive_annotation__return_section_with_type() -> None: 

760 def f(foo: str) -> str: 

761 """Function with return directive, rtype directive, & annotation. 

762 

763 :return: descriptive test text 

764 """ 

765 return foo 

766 

767 sections, errors = parse(f) 

768 assert len(sections) == 2 

769 assert sections[1].type == Section.Type.RETURN 

770 assert_annotated_obj_equal( 

771 sections[1].value, 

772 AnnotatedObject(annotation=str, description=SOME_TEXT), 

773 ) 

774 

775 

776def test_parse__return_directive_annotation__return_section_with_type_error() -> None: 

777 def f(foo: str) -> str: 

778 """Function with return directive, rtype directive, & annotation. 

779 

780 :return: descriptive test text 

781 :rtype: str 

782 """ 

783 return foo 

784 

785 sections, errors = parse(f) 

786 assert len(sections) == 2 

787 assert sections[1].type == Section.Type.RETURN 

788 assert_annotated_obj_equal( 

789 sections[1].value, 

790 AnnotatedObject(annotation=str, description=SOME_TEXT), 

791 ) 

792 assert "Duplicate type information for return" in errors[0] 

793 

794 

795def test_parse__return_invalid__error() -> None: 

796 def f(foo: str): # noqa: ANN202 

797 """Function with only return directive. 

798 

799 :return descriptive test text 

800 """ 

801 return foo 

802 

803 sections, errors = parse(f) 

804 assert "Failed to get ':directive: value' pair from " in errors[0] 

805 

806 

807def test_parse__rtype_invalid__error() -> None: 

808 def f(foo: str): # noqa: ANN202 

809 """Function with only return directive. 

810 

811 :rtype str 

812 """ 

813 return foo 

814 

815 sections, errors = parse(f) 

816 assert "Failed to get ':directive: value' pair from " in errors[0] 

817 

818 

819def test_parse__raises_directive__exception_section() -> None: 

820 def f(foo: str): # noqa: ANN202 

821 """Function with only return directive. 

822 

823 :raise SomeException: descriptive test text 

824 """ 

825 return foo 

826 

827 sections, errors = parse(f) 

828 assert len(sections) == 2 

829 assert sections[1].type == Section.Type.EXCEPTIONS 

830 assert_annotated_obj_equal( 

831 sections[1].value[0], 

832 AnnotatedObject(annotation=SOME_EXCEPTION_NAME, description=SOME_TEXT), 

833 ) 

834 

835 

836def test_parse__multiple_raises_directive__exception_section_with_two() -> None: 

837 def f(foo: str): # noqa: ANN202 

838 """Function with only return directive. 

839 

840 :raise SomeException: descriptive test text 

841 :raise SomeOtherException: descriptive test text 

842 """ 

843 return foo 

844 

845 sections, errors = parse(f) 

846 assert len(sections) == 2 

847 assert sections[1].type == Section.Type.EXCEPTIONS 

848 assert_annotated_obj_equal( 

849 sections[1].value[0], 

850 AnnotatedObject(annotation=SOME_EXCEPTION_NAME, description=SOME_TEXT), 

851 ) 

852 assert_annotated_obj_equal( 

853 sections[1].value[1], 

854 AnnotatedObject(annotation=SOME_OTHER_EXCEPTION_NAME, description=SOME_TEXT), 

855 ) 

856 

857 

858@pytest.mark.parametrize( 

859 "attribute_directive_name", 

860 [ 

861 "raises", 

862 "raise", 

863 "except", 

864 "exception", 

865 ], 

866) 

867def test_parse__all_exception_names__param_section(attribute_directive_name: str) -> None: 

868 sections, errors = parse_detailed( 

869 f""" 

870 Docstring with one line attribute. 

871 

872 :{attribute_directive_name} {SOME_EXCEPTION_NAME}: {SOME_TEXT} 

873 """, 

874 ) 

875 assert len(sections) == 2 

876 assert sections[1].type == Section.Type.EXCEPTIONS 

877 assert_annotated_obj_equal( 

878 sections[1].value[0], 

879 AnnotatedObject(annotation=SOME_EXCEPTION_NAME, description=SOME_TEXT), 

880 ) 

881 

882 

883def test_parse__raise_invalid__error() -> None: 

884 def f(foo: str): # noqa: ANN202 

885 """Function with only return directive. 

886 

887 :raise descriptive test text 

888 """ 

889 return foo 

890 

891 sections, errors = parse(f) 

892 assert "Failed to get ':directive: value' pair from " in errors[0] 

893 

894 

895def test_parse__raise_no_name__error() -> None: 

896 def f(foo: str): # noqa: ANN202 

897 """Function with only return directive. 

898 

899 :raise: descriptive test text 

900 """ 

901 return foo 

902 

903 sections, errors = parse(f) 

904 assert "Failed to parse exception directive from" in errors[0] 

905 

906 

907# ------------------------------- 

908# Fixture tests 

909# ------------------------------- 

910 

911 

912def test_parse_module_attributes_section__expected_attributes_section() -> None: 

913 """Parse attributes section in modules.""" 

914 obj = get_rst_object_documentation("docstring_attributes_section") 

915 assert len(obj.docstring_sections) == 2 

916 attr_section = obj.docstring_sections[1] 

917 assert attr_section.type == Section.Type.ATTRIBUTES 

918 assert len(attr_section.value) == 5 

919 expected = [ 

920 {"name": "A", "annotation": "int", "description": "Alpha."}, 

921 # type annotation takes preference over docstring 

922 {"name": "B", "annotation": "str", "description": "Beta."}, 

923 {"name": "C", "annotation": "bool", "description": "Gamma."}, 

924 {"name": "D", "annotation": "", "description": "Delta."}, 

925 {"name": "E", "annotation": "float", "description": "Epsilon."}, 

926 ] 

927 assert [serialize_attribute(attr) for attr in attr_section.value] == expected 

928 

929 

930def test_parse_module_attributes_section__expected_docstring_errors() -> None: 

931 """Parse attributes section in modules.""" 

932 obj = get_rst_object_documentation("docstring_attributes_section") 

933 assert len(obj.docstring_errors) == 1 

934 assert "Duplicate attribute information for 'B'" in obj.docstring_errors[0] 

935 

936 

937def test_property_docstring__expected_description() -> None: 

938 """Parse a property docstring.""" 

939 class_ = get_rst_object_documentation("class_docstrings:NotDefinedYet") 

940 prop = class_.attributes[0] 

941 sections = prop.docstring_sections 

942 assert len(sections) == 2 

943 assert sections[0].type == Section.Type.MARKDOWN 

944 assert ( 

945 sections[0].value 

946 == "This property returns `self`.\n\nIt's fun because you can call it like `obj.ha.ha.ha.ha.ha.ha...`." 

947 ) 

948 

949 

950def test_property_docstring__expected_return() -> None: 

951 """Parse a property docstring.""" 

952 class_ = get_rst_object_documentation("class_docstrings:NotDefinedYet") 

953 prop = class_.attributes[0] 

954 sections = prop.docstring_sections 

955 assert len(sections) == 2 

956 assert sections[1].type == Section.Type.RETURN 

957 assert_annotated_obj_equal(sections[1].value, AnnotatedObject("NotDefinedYet", "self!")) 

958 

959 

960def test_property_class_init__expected_description() -> None: 

961 class_ = get_rst_object_documentation("class_docstrings:ClassInitFunction") 

962 init = class_.methods[0] 

963 sections = init.docstring_sections 

964 assert len(sections) == 2 

965 assert sections[0].type == Section.Type.MARKDOWN 

966 assert sections[0].value == "Initialize instance." 

967 

968 

969def test_class_init__expected_param() -> None: 

970 class_ = get_rst_object_documentation("class_docstrings:ClassInitFunction") 

971 init = class_.methods[0] 

972 sections = init.docstring_sections 

973 assert len(sections) == 2 

974 assert sections[1].type == Section.Type.PARAMETERS 

975 param_section = sections[1] 

976 assert_parameter_equal( 

977 param_section.value[0], 

978 Parameter("value", str, "Value to store", kind=inspect.Parameter.POSITIONAL_OR_KEYWORD), 

979 ) 

980 assert_parameter_equal( 

981 param_section.value[1], 

982 Parameter("other", "int", "Other value with default", kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, default=1), 

983 ) 

984 

985 

986def test_member_function___expected_param() -> None: 

987 class_ = get_rst_object_documentation("class_docstrings:ClassWithFunction") 

988 init = class_.methods[0] 

989 sections = init.docstring_sections 

990 assert len(sections) == 3 

991 param_section = sections[1] 

992 assert param_section.type == Section.Type.PARAMETERS 

993 assert_parameter_equal( 

994 param_section.value[0], 

995 Parameter("value", str, "Value to store", kind=inspect.Parameter.POSITIONAL_OR_KEYWORD), 

996 ) 

997 assert_parameter_equal( 

998 param_section.value[1], 

999 Parameter("other", "int", "Other value with default", kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, default=1), 

1000 ) 

1001 

1002 

1003def test_member_function___expected_return() -> None: 

1004 class_ = get_rst_object_documentation("class_docstrings:ClassWithFunction") 

1005 init = class_.methods[0] 

1006 sections = init.docstring_sections 

1007 assert len(sections) == 3 

1008 assert sections[2].type == Section.Type.RETURN 

1009 assert_annotated_obj_equal(sections[2].value, AnnotatedObject(str, "Concatenated result")) 

1010 

1011 

1012def test_property_docstring__no_errors() -> None: 

1013 """Parse a property docstring.""" 

1014 class_ = get_rst_object_documentation("class_docstrings:NotDefinedYet") 

1015 prop = class_.attributes[0] 

1016 assert not prop.docstring_errors