Coverage for tests/test_docstrings/test_numpy.py: 100.00%

407 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-15 16:47 +0200

1"""Tests for the [Numpy-style parser][griffe.docstrings.numpy].""" 

2 

3from __future__ import annotations 

4 

5import logging 

6from typing import TYPE_CHECKING 

7 

8import pytest 

9 

10from griffe import ( 

11 Attribute, 

12 Class, 

13 Docstring, 

14 DocstringSectionKind, 

15 ExprName, 

16 Function, 

17 Module, 

18 Parameter, 

19 Parameters, 

20 parse_docstring_annotation, 

21) 

22 

23if TYPE_CHECKING: 

24 from tests.test_docstrings.helpers import ParserType 

25 

26 

27# ============================================================================================= 

28# Markup flow (multilines, indentation, etc.) 

29def test_simple_docstring(parse_numpy: ParserType) -> None: 

30 """Parse a simple docstring. 

31 

32 Parameters: 

33 parse_numpy: Fixture parser. 

34 """ 

35 sections, warnings = parse_numpy("A simple docstring.") 

36 assert len(sections) == 1 

37 assert not warnings 

38 

39 

40def test_multiline_docstring(parse_numpy: ParserType) -> None: 

41 """Parse a multi-line docstring. 

42 

43 Parameters: 

44 parse_numpy: Fixture parser. 

45 """ 

46 sections, warnings = parse_numpy( 

47 """ 

48 A somewhat longer docstring. 

49 

50 Blablablabla. 

51 """, 

52 ) 

53 assert len(sections) == 1 

54 assert not warnings 

55 

56 

57def test_code_blocks(parse_numpy: ParserType) -> None: 

58 """Parse code blocks. 

59 

60 Parameters: 

61 parse_numpy: Fixture parser. 

62 """ 

63 docstring = """ 

64 This docstring contains a code block! 

65 

66 ```python 

67 print("hello") 

68 ``` 

69 """ 

70 

71 sections, warnings = parse_numpy(docstring) 

72 assert len(sections) == 1 

73 assert not warnings 

74 

75 

76def test_indented_code_block(parse_numpy: ParserType) -> None: 

77 """Parse indented code blocks. 

78 

79 Parameters: 

80 parse_numpy: Fixture parser. 

81 """ 

82 docstring = """ 

83 This docstring contains a docstring in a code block o_O! 

84 

85 \"\"\" 

86 This docstring is contained in another docstring O_o! 

87 

88 Parameters: 

89 s: A string. 

90 \"\"\" 

91 """ 

92 

93 sections, warnings = parse_numpy(docstring) 

94 assert len(sections) == 1 

95 assert not warnings 

96 

97 

98def test_empty_indented_lines_in_section_with_items(parse_numpy: ParserType) -> None: 

99 """In sections with items, don't treat lines with just indentation as items. 

100 

101 Parameters: 

102 parse_numpy: Fixture parser. 

103 """ 

104 docstring = "Returns\n-------\nonly_item : type\n Description.\n \n \n\nSomething." 

105 sections, _ = parse_numpy(docstring) 

106 assert len(sections) == 1 

107 assert len(sections[0].value) == 2 

108 

109 

110def test_doubly_indented_lines_in_section_items(parse_numpy: ParserType) -> None: 

111 """In sections with items, don't remove all spaces on the left of indented lines. 

112 

113 Parameters: 

114 parse_numpy: Fixture parser. 

115 """ 

116 docstring = "Returns\n-------\nonly_item : type\n Description:\n\n - List item.\n - Sublist item." 

117 sections, _ = parse_numpy(docstring) 

118 assert len(sections) == 1 

119 lines = sections[0].value[0].description.split("\n") 

120 assert lines[-1].startswith(4 * " " + "- ") 

121 

122 

123# ============================================================================================= 

124# Admonitions 

125def test_admonition_see_also(parse_numpy: ParserType) -> None: 

126 """Test a "See Also" admonition. 

127 

128 Parameters: 

129 parse_numpy: Fixture parser. 

130 """ 

131 docstring = """ 

132 Summary text. 

133 

134 See Also 

135 -------- 

136 some_function 

137 

138 more text 

139 """ 

140 

141 sections, _ = parse_numpy(docstring) 

142 assert len(sections) == 2 

143 assert sections[0].value == "Summary text." 

144 assert sections[1].title == "See Also" 

145 assert sections[1].value.description == "some_function\n\nmore text" 

146 

147 

148def test_admonition_empty(parse_numpy: ParserType) -> None: 

149 """Test an empty "See Also" admonition. 

150 

151 Parameters: 

152 parse_numpy: Fixture parser. 

153 """ 

154 docstring = """ 

155 Summary text. 

156 

157 See Also 

158 -------- 

159 """ 

160 

161 sections, _ = parse_numpy(docstring) 

162 assert len(sections) == 2 

163 assert sections[0].value == "Summary text." 

164 assert sections[1].title == "See Also" 

165 assert sections[1].value.description == "" 

166 

167 

168def test_isolated_dash_lines_do_not_create_sections(parse_numpy: ParserType) -> None: 

169 """An isolated dash-line (`---`) should not be parsed as a section. 

170 

171 Parameters: 

172 parse_numpy: Fixture parser. 

173 """ 

174 docstring = """ 

175 Summary text. 

176 

177 --- 

178 Text. 

179 

180 Note 

181 ---- 

182 Note contents. 

183 

184 --- 

185 Text. 

186 """ 

187 

188 sections, _ = parse_numpy(docstring) 

189 assert len(sections) == 2 

190 assert sections[0].value == "Summary text.\n\n---\nText." 

191 assert sections[1].title == "Note" 

192 assert sections[1].value.description == "Note contents.\n\n---\nText." 

193 

194 

195def test_admonition_warnings_special_case(parse_numpy: ParserType) -> None: 

196 """Test that the "Warnings" section renders as a warning admonition. 

197 

198 Parameters: 

199 parse_numpy: Fixture parser. 

200 """ 

201 docstring = """ 

202 Summary text. 

203 

204 Warnings 

205 -------- 

206 Be careful!!! 

207 

208 more text 

209 """ 

210 

211 sections, _ = parse_numpy(docstring) 

212 assert len(sections) == 2 

213 assert sections[0].value == "Summary text." 

214 assert sections[1].title == "Warnings" 

215 assert sections[1].value.description == "Be careful!!!\n\nmore text" 

216 assert sections[1].value.kind == "warning" 

217 

218 

219def test_admonition_notes_special_case(parse_numpy: ParserType) -> None: 

220 """Test that the "Warnings" section renders as a warning admonition. 

221 

222 Parameters: 

223 parse_numpy: Fixture parser. 

224 """ 

225 docstring = """ 

226 Summary text. 

227 

228 Notes 

229 ----- 

230 Something noteworthy. 

231 

232 more text 

233 """ 

234 

235 sections, _ = parse_numpy(docstring) 

236 assert len(sections) == 2 

237 assert sections[0].value == "Summary text." 

238 assert sections[1].title == "Notes" 

239 assert sections[1].value.description == "Something noteworthy.\n\nmore text" 

240 assert sections[1].value.kind == "note" 

241 

242 

243# ============================================================================================= 

244# Annotations 

245def test_prefer_docstring_type_over_annotation(parse_numpy: ParserType) -> None: 

246 """Prefer the type written in the docstring over the annotation in the parent. 

247 

248 Parameters: 

249 parse_numpy: Fixture parser. 

250 """ 

251 docstring = """ 

252 Parameters 

253 ---------- 

254 a : int 

255 """ 

256 

257 sections, _ = parse_numpy( 

258 docstring, 

259 parent=Function("func", parameters=Parameters(Parameter("a", annotation="str"))), 

260 ) 

261 assert len(sections) == 1 

262 param = sections[0].value[0] 

263 assert param.name == "a" 

264 assert param.description == "" 

265 assert param.annotation.name == "int" 

266 

267 

268def test_parse_complex_annotations(parse_numpy: ParserType) -> None: 

269 """Check the type regex accepts all the necessary characters. 

270 

271 Parameters: 

272 parse_numpy: Fixture parser. 

273 """ 

274 docstring = """ 

275 Parameters 

276 ---------- 

277 a : typing.Tuple[str, random0123456789] 

278 b : int | float | None 

279 c : Literal['hello'] | Literal["world"] 

280 """ 

281 

282 sections, _ = parse_numpy(docstring) 

283 assert len(sections) == 1 

284 param_a, param_b, param_c = sections[0].value 

285 assert param_a.name == "a" 

286 assert param_a.description == "" 

287 assert param_a.annotation == "typing.Tuple[str, random0123456789]" 

288 assert param_b.name == "b" 

289 assert param_b.description == "" 

290 assert param_b.annotation == "int | float | None" 

291 assert param_c.name == "c" 

292 assert param_c.description == "" 

293 assert param_c.annotation == "Literal['hello'] | Literal[\"world\"]" 

294 

295 

296@pytest.mark.parametrize( 

297 ("docstring", "name"), 

298 [ 

299 ("Attributes\n---\na : {name}\n Description.\n", "int"), 

300 ("Parameters\n---\na : {name}\n Description.\n", "int"), 

301 ("Other Parameters\n---\na : {name}\n Description.\n", "int"), 

302 ("Yields\n---\na : {name}\n Description.\n", "int"), 

303 ("Receives\n---\na : {name}\n Description.\n", "int"), 

304 ("Returns\n---\na : {name}\n Description.\n", "int"), 

305 ("Raises\n---\n{name}\n Description.\n", "RuntimeError"), 

306 ("Warns\n---\n{name}\n Description.\n", "UserWarning"), 

307 ], 

308) 

309def test_parse_annotations_in_all_sections(parse_numpy: ParserType, docstring: str, name: str) -> None: 

310 """Assert annotations are parsed in all relevant sections. 

311 

312 Parameters: 

313 parse_numpy: Fixture parser. 

314 docstring: Parametrized docstring. 

315 name: Parametrized name in annotation. 

316 """ 

317 docstring = docstring.format(name=name) 

318 sections, _ = parse_numpy(docstring, parent=Function("f")) 

319 assert len(sections) == 1 

320 assert sections[0].value[0].annotation.name == name 

321 

322 

323def test_dont_crash_on_text_annotations(parse_numpy: ParserType, caplog: pytest.LogCaptureFixture) -> None: 

324 """Don't crash while parsing annotations containing unhandled nodes. 

325 

326 Parameters: 

327 parse_numpy: Fixture parser. 

328 caplog: Pytest fixture used to capture logs. 

329 """ 

330 docstring = """ 

331 Attributes 

332 ---------- 

333 region : str, list-like, geopandas.GeoSeries, geopandas.GeoDataFrame, geometric 

334 Description. 

335 

336 Parameters 

337 ---------- 

338 region : str, list-like, geopandas.GeoSeries, geopandas.GeoDataFrame, geometric 

339 Description. 

340 

341 Returns 

342 ------- 

343 str or bytes 

344 Description. 

345 

346 Receives 

347 -------- 

348 region : str, list-like, geopandas.GeoSeries, geopandas.GeoDataFrame, geometric 

349 Description. 

350 

351 Yields 

352 ------ 

353 str or bytes 

354 Description. 

355 """ 

356 caplog.set_level(logging.DEBUG) 

357 assert parse_numpy(docstring, parent=Function("f")) 

358 assert all(record.levelname == "DEBUG" for record in caplog.records if "Failed to parse" in record.message) 

359 

360 

361# ============================================================================================= 

362# Sections 

363def test_parameters_section(parse_numpy: ParserType) -> None: 

364 """Parse parameters section. 

365 

366 Parameters: 

367 parse_numpy: Fixture parser. 

368 """ 

369 docstring = """ 

370 Parameters 

371 ---------- 

372 a 

373 b : int 

374 c : str, optional 

375 d : float, default=1.0 

376 e, f 

377 g, h : bytes, optional, default=b'' 

378 i : {0, 1, 2} 

379 j : {"a", 1, None, True} 

380 k 

381 K's description. 

382 """ 

383 

384 sections, _ = parse_numpy(docstring) 

385 assert len(sections) == 1 

386 

387 

388def test_parse_starred_parameters(parse_numpy: ParserType) -> None: 

389 """Parse parameters names with stars in them. 

390 

391 Parameters: 

392 parse_numpy: Fixture parser. 

393 """ 

394 docstring = """ 

395 Parameters 

396 ---------- 

397 *a : str 

398 **b : int 

399 ***c : float 

400 """ 

401 

402 sections, warnings = parse_numpy(docstring) 

403 assert len(sections) == 1 

404 assert len(warnings) == 1 

405 

406 

407def test_other_parameters_section(parse_numpy: ParserType) -> None: 

408 """Parse other parameters section. 

409 

410 Parameters: 

411 parse_numpy: Fixture parser. 

412 """ 

413 docstring = """ 

414 Other Parameters 

415 ---------------- 

416 a 

417 b : int 

418 c : str, optional 

419 d : float, default=1.0 

420 e, f 

421 g, h : bytes, optional, default=b'' 

422 i : {0, 1, 2} 

423 j : {"a", 1, None, True} 

424 k 

425 K's description. 

426 """ 

427 

428 sections, _ = parse_numpy(docstring) 

429 assert len(sections) == 1 

430 

431 

432def test_retrieve_annotation_from_parent(parse_numpy: ParserType) -> None: 

433 """Retrieve parameter annotation from the parent object. 

434 

435 Parameters: 

436 parse_numpy: Fixture parser. 

437 """ 

438 docstring = """ 

439 Parameters 

440 ---------- 

441 a 

442 """ 

443 

444 sections, _ = parse_numpy( 

445 docstring, 

446 parent=Function("func", parameters=Parameters(Parameter("a", annotation="str"))), 

447 ) 

448 assert len(sections) == 1 

449 param = sections[0].value[0] 

450 assert param.name == "a" 

451 assert param.description == "" 

452 assert param.annotation == "str" 

453 

454 

455def test_deprecated_section(parse_numpy: ParserType) -> None: 

456 """Parse deprecated section. 

457 

458 Parameters: 

459 parse_numpy: Fixture parser. 

460 """ 

461 docstring = """ 

462 Deprecated 

463 ---------- 

464 1.23.4 

465 Deprecated. 

466 Sorry. 

467 """ 

468 

469 sections, _ = parse_numpy(docstring) 

470 assert len(sections) == 1 

471 assert sections[0].value.version == "1.23.4" 

472 assert sections[0].value.description == "Deprecated.\nSorry." 

473 

474 

475def test_returns_section(parse_numpy: ParserType) -> None: 

476 """Parse returns section. 

477 

478 Parameters: 

479 parse_numpy: Fixture parser. 

480 """ 

481 docstring = """ 

482 Returns 

483 ------- 

484 list of int 

485 A list of integers. 

486 flag : bool 

487 Some kind 

488 of flag. 

489 x : 

490 Name only 

491 : 

492 No name or annotation 

493 : int 

494 Only annotation 

495 """ 

496 

497 sections, _ = parse_numpy(docstring) 

498 assert len(sections) == 1 

499 

500 param = sections[0].value[0] 

501 assert param.name == "" 

502 assert param.description == "A list of integers." 

503 assert param.annotation == "list of int" 

504 

505 param = sections[0].value[1] 

506 assert param.name == "flag" 

507 assert param.description == "Some kind\nof flag." 

508 assert param.annotation == "bool" 

509 

510 param = sections[0].value[2] 

511 assert param.name == "x" 

512 assert param.description == "Name only" 

513 assert param.annotation is None 

514 

515 param = sections[0].value[3] 

516 assert param.name == "" 

517 assert param.description == "No name or annotation" 

518 assert param.annotation is None 

519 

520 param = sections[0].value[4] 

521 assert param.name == "" 

522 assert param.description == "Only annotation" 

523 assert param.annotation == "int" 

524 

525 

526def test_yields_section(parse_numpy: ParserType) -> None: 

527 """Parse yields section. 

528 

529 Parameters: 

530 parse_numpy: Fixture parser. 

531 """ 

532 docstring = """ 

533 Yields 

534 ------ 

535 list of int 

536 A list of integers. 

537 flag : bool 

538 Some kind 

539 of flag. 

540 """ 

541 

542 sections, _ = parse_numpy(docstring) 

543 assert len(sections) == 1 

544 param = sections[0].value[0] 

545 assert param.name == "" 

546 assert param.description == "A list of integers." 

547 assert param.annotation == "list of int" 

548 

549 param = sections[0].value[1] 

550 assert param.name == "flag" 

551 assert param.description == "Some kind\nof flag." 

552 assert param.annotation == "bool" 

553 

554 

555def test_receives_section(parse_numpy: ParserType) -> None: 

556 """Parse receives section. 

557 

558 Parameters: 

559 parse_numpy: Fixture parser. 

560 """ 

561 docstring = """ 

562 Receives 

563 -------- 

564 list of int 

565 A list of integers. 

566 flag : bool 

567 Some kind 

568 of flag. 

569 """ 

570 

571 sections, _ = parse_numpy(docstring) 

572 assert len(sections) == 1 

573 param = sections[0].value[0] 

574 assert param.name == "" 

575 assert param.description == "A list of integers." 

576 assert param.annotation == "list of int" 

577 param = sections[0].value[1] 

578 assert param.name == "flag" 

579 assert param.description == "Some kind\nof flag." 

580 assert param.annotation == "bool" 

581 

582 

583def test_raises_section(parse_numpy: ParserType) -> None: 

584 """Parse raises section. 

585 

586 Parameters: 

587 parse_numpy: Fixture parser. 

588 """ 

589 docstring = """ 

590 Raises 

591 ------ 

592 RuntimeError 

593 There was an issue. 

594 """ 

595 

596 sections, _ = parse_numpy(docstring) 

597 assert len(sections) == 1 

598 param = sections[0].value[0] 

599 assert param.description == "There was an issue." 

600 assert param.annotation == "RuntimeError" 

601 

602 

603def test_warns_section(parse_numpy: ParserType) -> None: 

604 """Parse warns section. 

605 

606 Parameters: 

607 parse_numpy: Fixture parser. 

608 """ 

609 docstring = """ 

610 Warns 

611 ----- 

612 ResourceWarning 

613 Heads up. 

614 """ 

615 

616 sections, _ = parse_numpy(docstring) 

617 assert len(sections) == 1 

618 param = sections[0].value[0] 

619 assert param.description == "Heads up." 

620 assert param.annotation == "ResourceWarning" 

621 

622 

623def test_attributes_section(parse_numpy: ParserType) -> None: 

624 """Parse attributes section. 

625 

626 Parameters: 

627 parse_numpy: Fixture parser. 

628 """ 

629 docstring = """ 

630 Attributes 

631 ---------- 

632 a 

633 Hello. 

634 m 

635 z : int 

636 Bye. 

637 """ 

638 

639 sections, _ = parse_numpy(docstring) 

640 assert len(sections) == 1 

641 param = sections[0].value[0] 

642 assert param.name == "a" 

643 assert param.description == "Hello." 

644 assert param.annotation is None 

645 

646 param = sections[0].value[1] 

647 assert param.name == "m" 

648 assert param.description == "" 

649 assert param.annotation is None 

650 

651 param = sections[0].value[2] 

652 assert param.name == "z" 

653 assert param.description == "Bye." 

654 assert param.annotation == "int" 

655 

656 

657def test_parse_functions_section(parse_numpy: ParserType) -> None: 

658 """Parse Functions/Methods sections. 

659 

660 Parameters: 

661 parse_numpy: Fixture parser. 

662 """ 

663 docstring = """ 

664 Functions 

665 --------- 

666 f(a, b=2) 

667 Hello. 

668 g 

669 Hi. 

670 

671 Methods 

672 ------- 

673 f(a, b=2) 

674 Hello. 

675 g 

676 Hi. 

677 """ 

678 

679 sections, warnings = parse_numpy(docstring) 

680 assert len(sections) == 2 

681 for section in sections: 

682 assert section.kind is DocstringSectionKind.functions 

683 func_f = section.value[0] 

684 assert func_f.name == "f" 

685 assert func_f.signature == "f(a, b=2)" 

686 assert func_f.description == "Hello." 

687 func_g = section.value[1] 

688 assert func_g.name == "g" 

689 assert func_g.signature is None 

690 assert func_g.description == "Hi." 

691 assert not warnings 

692 

693 

694def test_parse_classes_section(parse_numpy: ParserType) -> None: 

695 """Parse Classes sections. 

696 

697 Parameters: 

698 parse_numpy: Fixture parser. 

699 """ 

700 docstring = """ 

701 Classes 

702 ------- 

703 C(a, b=2) 

704 Hello. 

705 D 

706 Hi. 

707 """ 

708 

709 sections, warnings = parse_numpy(docstring) 

710 assert len(sections) == 1 

711 assert sections[0].kind is DocstringSectionKind.classes 

712 class_c = sections[0].value[0] 

713 assert class_c.name == "C" 

714 assert class_c.signature == "C(a, b=2)" 

715 assert class_c.description == "Hello." 

716 class_d = sections[0].value[1] 

717 assert class_d.name == "D" 

718 assert class_d.signature is None 

719 assert class_d.description == "Hi." 

720 assert not warnings 

721 

722 

723def test_parse_modules_section(parse_numpy: ParserType) -> None: 

724 """Parse Modules sections. 

725 

726 Parameters: 

727 parse_numpy: Fixture parser. 

728 """ 

729 docstring = """ 

730 Modules 

731 ------- 

732 m 

733 Hello. 

734 n 

735 Hi. 

736 """ 

737 

738 sections, warnings = parse_numpy(docstring) 

739 assert len(sections) == 1 

740 assert sections[0].kind is DocstringSectionKind.modules 

741 module_m = sections[0].value[0] 

742 assert module_m.name == "m" 

743 assert module_m.description == "Hello." 

744 module_n = sections[0].value[1] 

745 assert module_n.name == "n" 

746 assert module_n.description == "Hi." 

747 assert not warnings 

748 

749 

750def test_examples_section(parse_numpy: ParserType) -> None: 

751 """Parse examples section. 

752 

753 Parameters: 

754 parse_numpy: Fixture parser. 

755 """ 

756 docstring = """ 

757 Examples 

758 -------- 

759 Hello. 

760 

761 >>> 1 + 2 

762 3 

763 

764 ```pycon 

765 >>> print("Hello again.") 

766 ``` 

767 

768 >>> a = 0 # doctest: +SKIP 

769 >>> b = a + 1 

770 >>> print(b) 

771 1 

772 

773 Bye. 

774 

775 -------- 

776 

777 Not in the section. 

778 """ 

779 

780 sections, _ = parse_numpy(docstring, trim_doctest_flags=False) 

781 assert len(sections) == 2 

782 examples = sections[0] 

783 assert len(examples.value) == 5 

784 assert examples.value[0] == (DocstringSectionKind.text, "Hello.") 

785 assert examples.value[1] == (DocstringSectionKind.examples, ">>> 1 + 2\n3") 

786 assert examples.value[3][1].startswith(">>> a = 0 # doctest: +SKIP") 

787 

788 

789def test_examples_section_when_followed_by_named_section(parse_numpy: ParserType) -> None: 

790 """Parse examples section followed by another section. 

791 

792 Parameters: 

793 parse_numpy: Parse function (fixture). 

794 """ 

795 docstring = """ 

796 Examples 

797 -------- 

798 Hello, hello. 

799 

800 Parameters 

801 ---------- 

802 foo : int 

803 """ 

804 

805 sections, _ = parse_numpy(docstring, trim_doctest_flags=False) 

806 assert len(sections) == 2 

807 assert sections[0].kind is DocstringSectionKind.examples 

808 assert sections[1].kind is DocstringSectionKind.parameters 

809 

810 

811def test_examples_section_as_last(parse_numpy: ParserType) -> None: 

812 """Parse examples section being last in the docstring. 

813 

814 Parameters: 

815 parse_numpy: Parse function (fixture). 

816 """ 

817 docstring = """ 

818 Lorem ipsum dolor sit amet, consectetur adipiscing elit... 

819 

820 Examples 

821 -------- 

822 ```pycon 

823 >>> LoremIpsum.from_string("consectetur") 

824 <foofoo: Ipsum.Lorem> 

825 

826 ``` 

827 """ 

828 

829 sections, _ = parse_numpy(docstring) 

830 assert len(sections) == 2 

831 assert sections[0].kind is DocstringSectionKind.text 

832 assert sections[1].kind is DocstringSectionKind.examples 

833 

834 

835def test_blank_lines_in_section(parse_numpy: ParserType) -> None: 

836 """Support blank lines in the middle of sections. 

837 

838 Parameters: 

839 parse_numpy: Fixture parser. 

840 """ 

841 docstring = """ 

842 Examples 

843 -------- 

844 Line 1. 

845 

846 Line 2. 

847 """ 

848 sections, _ = parse_numpy(docstring) 

849 assert len(sections) == 1 

850 

851 

852# ============================================================================================= 

853# Attributes sections 

854def test_retrieve_attributes_annotation_from_parent(parse_numpy: ParserType) -> None: 

855 """Retrieve the annotations of attributes from the parent object. 

856 

857 Parameters: 

858 parse_numpy: Fixture parser. 

859 """ 

860 docstring = """ 

861 Summary. 

862 

863 Attributes 

864 ---------- 

865 a : 

866 Whatever. 

867 b : 

868 Whatever. 

869 """ 

870 parent = Class("cls") 

871 parent["a"] = Attribute("a", annotation=ExprName("int")) 

872 parent["b"] = Attribute("b", annotation=ExprName("str")) 

873 sections, _ = parse_numpy(docstring, parent=parent) 

874 attributes = sections[1].value 

875 assert attributes[0].name == "a" 

876 assert attributes[0].annotation.name == "int" 

877 assert attributes[1].name == "b" 

878 assert attributes[1].annotation.name == "str" 

879 

880 

881# ============================================================================================= 

882# Parameters sections 

883def test_warn_about_unknown_parameters(parse_numpy: ParserType) -> None: 

884 """Warn about unknown parameters in "Parameters" sections. 

885 

886 Parameters: 

887 parse_numpy: Fixture parser. 

888 """ 

889 docstring = """ 

890 Parameters 

891 ---------- 

892 x : int 

893 Integer. 

894 y : int 

895 Integer. 

896 """ 

897 

898 _, warnings = parse_numpy( 

899 docstring, 

900 parent=Function( 

901 "func", 

902 parameters=Parameters( 

903 Parameter("a"), 

904 Parameter("y"), 

905 ), 

906 ), 

907 ) 

908 assert len(warnings) == 1 

909 assert "'x' does not appear in the function signature" in warnings[0] 

910 

911 

912def test_never_warn_about_unknown_other_parameters(parse_numpy: ParserType) -> None: 

913 """Never warn about unknown parameters in "Other parameters" sections. 

914 

915 Parameters: 

916 parse_numpy: Fixture parser. 

917 """ 

918 docstring = """ 

919 Other Parameters 

920 ---------------- 

921 x : int 

922 Integer. 

923 z : int 

924 Integer. 

925 """ 

926 

927 _, warnings = parse_numpy( 

928 docstring, 

929 parent=Function( 

930 "func", 

931 parameters=Parameters( 

932 Parameter("a"), 

933 Parameter("y"), 

934 ), 

935 ), 

936 ) 

937 assert not warnings 

938 

939 

940def test_unknown_params_scan_doesnt_crash_without_parameters(parse_numpy: ParserType) -> None: 

941 """Assert we don't crash when parsing parameters sections and parent object does not have parameters. 

942 

943 Parameters: 

944 parse_numpy: Fixture parser. 

945 """ 

946 docstring = """ 

947 Parameters 

948 ---------- 

949 this : str 

950 This. 

951 that : str 

952 That. 

953 """ 

954 

955 _, warnings = parse_numpy(docstring, parent=Module("mod")) 

956 assert not warnings 

957 

958 

959def test_class_uses_init_parameters(parse_numpy: ParserType) -> None: 

960 """Assert we use the `__init__` parameters when parsing classes' parameters sections. 

961 

962 Parameters: 

963 parse_numpy: Fixture parser. 

964 """ 

965 docstring = """ 

966 Parameters 

967 ---------- 

968 x : 

969 X value. 

970 """ 

971 

972 parent = Class("c") 

973 parent["__init__"] = Function("__init__", parameters=Parameters(Parameter("x", annotation="int"))) 

974 sections, warnings = parse_numpy(docstring, parent=parent) 

975 assert not warnings 

976 argx = sections[0].value[0] 

977 assert argx.name == "x" 

978 assert argx.annotation == "int" 

979 assert argx.description == "X value." 

980 

981 

982def test_detect_optional_flag(parse_numpy: ParserType) -> None: 

983 """Detect the optional part of a parameter docstring. 

984 

985 Parameters: 

986 parse_numpy: Fixture parser. 

987 """ 

988 docstring = """ 

989 Parameters 

990 ---------- 

991 a : str, optional 

992 g, h : bytes, optional, default=b'' 

993 """ 

994 

995 sections, _ = parse_numpy(docstring) 

996 assert len(sections) == 1 

997 assert sections[0].value[0].annotation == "str" 

998 assert sections[0].value[1].annotation == "bytes" 

999 assert sections[0].value[1].default == "b''" 

1000 assert sections[0].value[2].annotation == "bytes" 

1001 assert sections[0].value[2].default == "b''" 

1002 

1003 

1004@pytest.mark.parametrize("newlines", [1, 2, 3]) 

1005def test_blank_lines_in_item_descriptions(parse_numpy: ParserType, newlines: int) -> None: 

1006 """Support blank lines in the middle of item descriptions. 

1007 

1008 Parameters: 

1009 parse_numpy: Fixture parser. 

1010 newlines: Number of new lines between item summary and its body. 

1011 """ 

1012 nl = "\n" 

1013 nlindent = "\n" + " " * 12 

1014 docstring = f""" 

1015 Parameters 

1016 ---------- 

1017 a : str 

1018 Summary.{nlindent * newlines}Body. 

1019 """ 

1020 sections, _ = parse_numpy(docstring) 

1021 assert len(sections) == 1 

1022 assert sections[0].value[0].annotation == "str" 

1023 assert sections[0].value[0].description == f"Summary.{nl * newlines}Body." 

1024 

1025 

1026# ============================================================================================= 

1027# Yields sections 

1028@pytest.mark.parametrize( 

1029 "return_annotation", 

1030 [ 

1031 "Iterator[tuple[int, float]]", 

1032 "Generator[tuple[int, float], ..., ...]", 

1033 ], 

1034) 

1035def test_parse_yields_tuple_in_iterator_or_generator(parse_numpy: ParserType, return_annotation: str) -> None: 

1036 """Parse Yields annotations in Iterator or Generator types. 

1037 

1038 Parameters: 

1039 parse_numpy: Fixture parser. 

1040 return_annotation: Parametrized return annotation as a string. 

1041 """ 

1042 docstring = """ 

1043 Summary. 

1044 

1045 Yields 

1046 ------ 

1047 a : 

1048 Whatever. 

1049 b : 

1050 Whatever. 

1051 """ 

1052 sections, _ = parse_numpy( 

1053 docstring, 

1054 parent=Function( 

1055 "func", 

1056 returns=parse_docstring_annotation(return_annotation, Docstring("d", parent=Function("f"))), 

1057 ), 

1058 ) 

1059 yields = sections[1].value 

1060 assert yields[0].name == "a" 

1061 assert yields[0].annotation.name == "int" 

1062 assert yields[1].name == "b" 

1063 assert yields[1].annotation.name == "float" 

1064 

1065 

1066@pytest.mark.parametrize( 

1067 "return_annotation", 

1068 [ 

1069 "Iterator[int]", 

1070 "Generator[int, None, None]", 

1071 ], 

1072) 

1073def test_extract_yielded_type_with_single_return_item(parse_numpy: ParserType, return_annotation: str) -> None: 

1074 """Extract main type annotation from Iterator or Generator. 

1075 

1076 Parameters: 

1077 parse_numpy: Fixture parser. 

1078 return_annotation: Parametrized return annotation as a string. 

1079 """ 

1080 docstring = """ 

1081 Summary. 

1082 

1083 Yields 

1084 ------ 

1085 a : 

1086 A number. 

1087 """ 

1088 sections, _ = parse_numpy( 

1089 docstring, 

1090 parent=Function( 

1091 "func", 

1092 returns=parse_docstring_annotation(return_annotation, Docstring("d", parent=Function("f"))), 

1093 ), 

1094 ) 

1095 yields = sections[1].value 

1096 assert yields[0].annotation.name == "int" 

1097 

1098 

1099def test_yield_section_in_property(parse_numpy: ParserType) -> None: 

1100 """No warnings when parsing Yields section in a property. 

1101 

1102 Parameters: 

1103 parse_numpy: Fixture parser. 

1104 """ 

1105 docstring = """ 

1106 Summary. 

1107 

1108 Yields 

1109 ------ 

1110 : 

1111 A number. 

1112 """ 

1113 sections, warnings = parse_numpy( 

1114 docstring, 

1115 parent=Attribute( 

1116 "prop", 

1117 annotation=parse_docstring_annotation("Iterator[int]", Docstring("d", parent=Attribute("a"))), 

1118 ), 

1119 ) 

1120 assert not warnings 

1121 yields = sections[1].value 

1122 assert yields[0].annotation.name == "int" 

1123 

1124 

1125# ============================================================================================= 

1126# Receives sections 

1127def test_parse_receives_tuple_in_generator(parse_numpy: ParserType) -> None: 

1128 """Parse Receives annotations in Generator type. 

1129 

1130 Parameters: 

1131 parse_numpy: Fixture parser. 

1132 """ 

1133 docstring = """ 

1134 Summary. 

1135 

1136 Receives 

1137 -------- 

1138 a : 

1139 Whatever. 

1140 b : 

1141 Whatever. 

1142 """ 

1143 sections, _ = parse_numpy( 

1144 docstring, 

1145 parent=Function( 

1146 "func", 

1147 returns=parse_docstring_annotation( 

1148 "Generator[..., tuple[int, float], ...]", 

1149 Docstring("d", parent=Function("f")), 

1150 ), 

1151 ), 

1152 ) 

1153 receives = sections[1].value 

1154 assert receives[0].name == "a" 

1155 assert receives[0].annotation.name == "int" 

1156 assert receives[1].name == "b" 

1157 assert receives[1].annotation.name == "float" 

1158 

1159 

1160@pytest.mark.parametrize( 

1161 "return_annotation", 

1162 [ 

1163 "Generator[int, float, None]", 

1164 ], 

1165) 

1166def test_extract_received_type_with_single_return_item(parse_numpy: ParserType, return_annotation: str) -> None: 

1167 """Extract main type annotation from Iterator or Generator. 

1168 

1169 Parameters: 

1170 parse_numpy: Fixture parser. 

1171 return_annotation: Parametrized return annotation as a string. 

1172 """ 

1173 docstring = """ 

1174 Summary. 

1175 

1176 Receives 

1177 -------- 

1178 a : 

1179 A floating point number. 

1180 """ 

1181 sections, _ = parse_numpy( 

1182 docstring, 

1183 parent=Function( 

1184 "func", 

1185 returns=parse_docstring_annotation(return_annotation, Docstring("d", parent=Function("f"))), 

1186 ), 

1187 ) 

1188 receives = sections[1].value 

1189 assert receives[0].annotation.name == "float" 

1190 

1191 

1192# ============================================================================================= 

1193# Returns sections 

1194def test_parse_returns_tuple_in_generator(parse_numpy: ParserType) -> None: 

1195 """Parse Returns annotations in Generator type. 

1196 

1197 Parameters: 

1198 parse_numpy: Fixture parser. 

1199 """ 

1200 docstring = """ 

1201 Summary. 

1202 

1203 Returns 

1204 ------- 

1205 a : 

1206 Whatever. 

1207 b : 

1208 Whatever. 

1209 """ 

1210 sections, _ = parse_numpy( 

1211 docstring, 

1212 parent=Function( 

1213 "func", 

1214 returns=parse_docstring_annotation( 

1215 "Generator[..., ..., tuple[int, float]]", 

1216 Docstring("d", parent=Function("f")), 

1217 ), 

1218 ), 

1219 ) 

1220 returns = sections[1].value 

1221 assert returns[0].name == "a" 

1222 assert returns[0].annotation.name == "int" 

1223 assert returns[1].name == "b" 

1224 assert returns[1].annotation.name == "float" 

1225 

1226 

1227# ============================================================================================= 

1228# Parser special features 

1229@pytest.mark.parametrize( 

1230 "docstring", 

1231 [ 

1232 "", 

1233 "\n", 

1234 "\n\n", 

1235 "Summary.", 

1236 "Summary.\n\n\n", 

1237 "Summary.\n\nParagraph.", 

1238 "Summary\non two lines.", 

1239 "Summary\non two lines.\n\nParagraph.", 

1240 ], 

1241) 

1242def test_ignore_init_summary(parse_numpy: ParserType, docstring: str) -> None: 

1243 """Correctly ignore summary in `__init__` methods' docstrings. 

1244 

1245 Parameters: 

1246 parse_numpy: Fixture parser. 

1247 docstring: The docstring to parse (parametrized). 

1248 """ 

1249 sections, _ = parse_numpy(docstring, parent=Function("__init__", parent=Class("C")), ignore_init_summary=True) 

1250 for section in sections: 

1251 assert "Summary" not in section.value 

1252 

1253 if docstring.strip(): 

1254 sections, _ = parse_numpy(docstring, parent=Function("__init__", parent=Module("M")), ignore_init_summary=True) 

1255 assert "Summary" in sections[0].value 

1256 sections, _ = parse_numpy(docstring, parent=Function("f", parent=Class("C")), ignore_init_summary=True) 

1257 assert "Summary" in sections[0].value 

1258 sections, _ = parse_numpy(docstring, ignore_init_summary=True) 

1259 assert "Summary" in sections[0].value 

1260 

1261 

1262@pytest.mark.parametrize( 

1263 "docstring", 

1264 [ 

1265 """ 

1266 Examples 

1267 -------- 

1268 Base case 1. We want to skip the following test. 

1269 >>> 1 + 1 == 3 # doctest: +SKIP 

1270 True 

1271 """, 

1272 r""" 

1273 Examples 

1274 -------- 

1275 

1276 Base case 2. We have a blankline test. 

1277 >>> print("a\n\nb") 

1278 a 

1279 <BLANKLINE> 

1280 b 

1281 """, 

1282 ], 

1283) 

1284def test_trim_doctest_flags_basic_example(parse_numpy: ParserType, docstring: str) -> None: 

1285 """Correctly parse simple example docstrings when `trim_doctest_flags` option is turned on. 

1286 

1287 Parameters: 

1288 parse_numpy: Fixture parser. 

1289 docstring: The docstring to parse_numpy (parametrized). 

1290 """ 

1291 sections, warnings = parse_numpy(docstring, trim_doctest_flags=True) 

1292 assert len(sections) == 1 

1293 assert len(sections[0].value) == 2 

1294 assert not warnings 

1295 

1296 # verify that doctest flags have indeed been trimmed 

1297 example_str = sections[0].value[1][1] 

1298 assert "# doctest: +SKIP" not in example_str 

1299 assert "<BLANKLINE>" not in example_str 

1300 

1301 

1302def test_trim_doctest_flags_multi_example(parse_numpy: ParserType) -> None: 

1303 """Correctly parse multiline example docstrings when `trim_doctest_flags` option is turned on. 

1304 

1305 Parameters: 

1306 parse_numpy: Fixture parser. 

1307 """ 

1308 docstring = r""" 

1309 Examples 

1310 -------- 

1311 

1312 Test multiline example blocks. 

1313 We want to skip the following test. 

1314 >>> 1 + 1 == 3 # doctest: +SKIP 

1315 True 

1316 

1317 And then a few more examples here: 

1318 >>> print("a\n\nb") 

1319 a 

1320 <BLANKLINE> 

1321 b 

1322 >>> 1 + 1 == 2 # doctest: +SKIP 

1323 >>> print(list(range(1, 100))) # doctest: +ELLIPSIS 

1324 [1, 2, ..., 98, 99] 

1325 """ 

1326 sections, warnings = parse_numpy(docstring, trim_doctest_flags=True) 

1327 assert len(sections) == 1 

1328 assert len(sections[0].value) == 4 

1329 assert not warnings 

1330 

1331 # verify that doctest flags have indeed been trimmed 

1332 example_str = sections[0].value[1][1] 

1333 assert "# doctest: +SKIP" not in example_str 

1334 example_str = sections[0].value[3][1] 

1335 assert "<BLANKLINE>" not in example_str 

1336 assert "\n>>> print(list(range(1, 100)))\n" in example_str 

1337 

1338 

1339def test_parsing_choices(parse_numpy: ParserType) -> None: 

1340 """Correctly parse choices. 

1341 

1342 Parameters: 

1343 parse_numpy: Fixture parser. 

1344 """ 

1345 docstring = r""" 

1346 Parameters 

1347 -------- 

1348 order : {'C', 'F', 'A'} 

1349 Description of `order`. 

1350 """ 

1351 sections, warnings = parse_numpy(docstring, trim_doctest_flags=True) 

1352 assert sections[0].value[0].annotation == "'C', 'F', 'A'" 

1353 assert not warnings