Coverage for tests/test_parsers/test_docstrings/test_google.py: 94.06%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

304 statements  

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

2 

3import inspect 

4from textwrap import dedent 

5from typing import Iterator 

6 

7from pytkdocs.loader import Loader 

8from pytkdocs.parsers.docstrings.base import Section 

9from pytkdocs.parsers.docstrings.google import Google 

10from pytkdocs.serializer import serialize_attribute 

11 

12 

13class DummyObject: 

14 path = "o" 

15 

16 

17def parse( 

18 docstring, 

19 signature=None, 

20 return_type=inspect.Signature.empty, 

21 admonitions=True, 

22 trim_doctest=False, 

23): 

24 """Helper to parse a doctring.""" 

25 parser = Google(replace_admonitions=admonitions, trim_doctest_flags=trim_doctest) 

26 

27 return parser.parse( 

28 dedent(docstring).strip(), 

29 context={"obj": DummyObject(), "signature": signature, "type": return_type}, 

30 ) 

31 

32 

33def test_simple_docstring(): 

34 """Parse a simple docstring.""" 

35 sections, errors = parse("A simple docstring.") 

36 assert len(sections) == 1 

37 assert not errors 

38 

39 

40def test_multi_line_docstring(): 

41 """Parse a multi-line docstring.""" 

42 sections, errors = parse( 

43 """ 

44 A somewhat longer docstring. 

45 

46 Blablablabla. 

47 """ 

48 ) 

49 assert len(sections) == 1 

50 assert not errors 

51 

52 

53def test_sections_without_signature(): 

54 """Parse a docstring without a signature.""" 

55 sections, errors = parse( 

56 """ 

57 Sections without signature. 

58 

59 Parameters: 

60 void: SEGFAULT. 

61 niet: SEGFAULT. 

62 nada: SEGFAULT. 

63 rien: SEGFAULT. 

64 

65 Keyword Args: 

66 keywd: SEGFAULT. 

67 

68 Exceptions: 

69 GlobalError: when nothing works as expected. 

70 

71 Returns: 

72 Itself. 

73 """ 

74 ) 

75 

76 assert len(sections) == 5 

77 assert len(errors) == 6 # missing annotations for params and return 

78 for error in errors[:-1]: 

79 assert "param" in error 

80 assert "return" in errors[-1] 

81 

82 

83def test_property_docstring(): 

84 """Parse a property docstring.""" 

85 class_ = Loader().get_object_documentation("tests.fixtures.parsing.docstrings.NotDefinedYet") 

86 prop = class_.attributes[0] 

87 sections, errors = prop.docstring_sections, prop.docstring_errors 

88 assert len(sections) == 2 

89 assert not errors 

90 

91 

92def test_function_without_annotations(): 

93 """Parse a function docstring without signature annotations.""" 

94 

95 def f(x, y, *, z): 

96 """ 

97 This function has no annotations. 

98 

99 Parameters: 

100 x: X value. 

101 y: Y value. 

102 

103 Keyword Args: 

104 z: Z value. 

105 

106 Returns: 

107 Sum X + Y + Z. 

108 """ 

109 return x + y + z 

110 

111 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

112 assert len(sections) == 4 

113 assert len(errors) == 1 

114 assert "No return type/annotation in" in errors[0] 

115 

116 

117def test_function_with_annotations(): 

118 """Parse a function docstring with signature annotations.""" 

119 

120 def f(x: int, y: int, *, z: int) -> int: 

121 """ 

122 This function has annotations. 

123 

124 Parameters: 

125 x: X value. 

126 y: Y value. 

127 

128 Keyword Arguments: 

129 z: Z value. 

130 

131 Returns: 

132 Sum X + Y. 

133 """ 

134 return x + y 

135 

136 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

137 assert len(sections) == 4 

138 assert not errors 

139 

140 

141def test_function_with_examples_trim_doctest(): 

142 """Parse example docstring with trim_doctest_flags option.""" 

143 

144 def f(x: int) -> int: 

145 """Test function. 

146 

147 Example: 

148 

149 We want to skip the following test. 

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

151 True 

152 

153 And then a few more examples here: 

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

155 a 

156 <BLANKLINE> 

157 b 

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

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

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

161 """ 

162 return x 

163 

164 sections, errors = parse( 

165 inspect.getdoc(f), 

166 inspect.signature(f), 

167 trim_doctest=True, 

168 ) 

169 assert len(sections) == 2 

170 assert len(sections[1].value) == 4 

171 assert not errors 

172 

173 # Verify that doctest flags have indeed been trimmed 

174 example_str = sections[1].value[1][1] 

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

176 example_str = sections[1].value[3][1] 

177 assert "<BLANKLINE>" not in example_str 

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

179 

180 

181def test_function_with_examples(): 

182 """Parse a function docstring with examples.""" 

183 

184 def f(x: int, y: int) -> int: 

185 """ 

186 This function has annotations. 

187 

188 Examples: 

189 Some examples that will create an unified code block: 

190 

191 >>> 2 + 2 == 5 

192 False 

193 >>> print("examples") 

194 "examples" 

195 

196 This is just a random comment in the examples section. 

197 

198 These examples will generate two different code blocks. Note the blank line. 

199 

200 >>> print("I'm in the first code block!") 

201 "I'm in the first code block!" 

202 

203 >>> print("I'm in other code block!") 

204 "I'm in other code block!" 

205 

206 We also can write multiline examples: 

207 

208 >>> x = 3 + 2 

209 >>> y = x + 10 

210 >>> y 

211 15 

212 

213 This is just a typical Python code block: 

214 

215 ```python 

216 print("examples") 

217 return 2 + 2 

218 ``` 

219 

220 Even if it contains doctests, the following block is still considered a normal code-block. 

221 

222 ```python 

223 >>> print("examples") 

224 "examples" 

225 >>> 2 + 2 

226 4 

227 ``` 

228 

229 The blank line before an example is optional. 

230 >>> x = 3 

231 >>> y = "apple" 

232 >>> z = False 

233 >>> l = [x, y, z] 

234 >>> my_print_list_function(l) 

235 3 

236 "apple" 

237 False 

238 """ 

239 return x + y 

240 

241 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

242 assert len(sections) == 2 

243 assert len(sections[1].value) == 9 

244 assert not errors 

245 

246 

247def test_types_in_docstring(): 

248 """Parse types in docstring.""" 

249 

250 def f(x, y, *, z): 

251 """ 

252 The types are written in the docstring. 

253 

254 Parameters: 

255 x (int): X value. 

256 y (int): Y value. 

257 

258 Keyword Args: 

259 z (int): Z value. 

260 

261 Returns: 

262 int: Sum X + Y + Z. 

263 """ 

264 return x + y + z 

265 

266 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

267 assert len(sections) == 4 

268 assert not errors 

269 

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

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

272 assert sections[2].type == Section.Type.KEYWORD_ARGS 

273 assert sections[3].type == Section.Type.RETURN 

274 

275 x, y = sections[1].value 

276 (z,) = sections[2].value 

277 r = sections[3].value 

278 

279 assert x.name == "x" 

280 assert x.annotation == "int" 

281 assert x.description == "X value." 

282 assert x.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD 

283 assert x.default is inspect.Signature.empty 

284 

285 assert y.name == "y" 

286 assert y.annotation == "int" 

287 assert y.description == "Y value." 

288 assert y.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD 

289 assert y.default is inspect.Signature.empty 

290 

291 assert z.name == "z" 

292 assert z.annotation == "int" 

293 assert z.description == "Z value." 

294 assert z.kind is inspect.Parameter.KEYWORD_ONLY 

295 assert z.default is inspect.Signature.empty 

296 

297 assert r.annotation == "int" 

298 assert r.description == "Sum X + Y + Z." 

299 

300 

301def test_types_and_optional_in_docstring(): 

302 """Parse optional types in docstring.""" 

303 

304 def f(x=1, y=None, *, z=None): 

305 """ 

306 The types are written in the docstring. 

307 

308 Parameters: 

309 x (int): X value. 

310 y (int, optional): Y value. 

311 

312 Keyword Args: 

313 z (int, optional): Z value. 

314 

315 Returns: 

316 int: Sum X + Y + Z. 

317 """ 

318 return x + (y or 1) + (z or 1) 

319 

320 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

321 assert len(sections) == 4 

322 assert not errors 

323 

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

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

326 assert sections[2].type == Section.Type.KEYWORD_ARGS 

327 

328 x, y = sections[1].value 

329 (z,) = sections[2].value 

330 

331 assert x.name == "x" 

332 assert x.annotation == "int" 

333 assert x.description == "X value." 

334 assert x.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD 

335 assert x.default == 1 

336 

337 assert y.name == "y" 

338 assert y.annotation == "int" 

339 assert y.description == "Y value." 

340 assert y.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD 

341 assert y.default is None 

342 

343 assert z.name == "z" 

344 assert z.annotation == "int" 

345 assert z.description == "Z value." 

346 assert z.kind is inspect.Parameter.KEYWORD_ONLY 

347 assert z.default is None 

348 

349 

350def test_types_in_signature_and_docstring(): 

351 """Parse types in both signature and docstring. Should prefer the docstring type""" 

352 

353 def f(x: int, y: int, *, z: int) -> int: 

354 """ 

355 The types are written both in the signature and in the docstring. 

356 

357 Parameters: 

358 x (str): X value. 

359 y (str): Y value. 

360 

361 Keyword Args: 

362 z (str): Z value. 

363 

364 Returns: 

365 str: Sum X + Y + Z. 

366 """ 

367 return x + y + z 

368 

369 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

370 assert len(sections) == 4 

371 assert not errors 

372 

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

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

375 assert sections[2].type == Section.Type.KEYWORD_ARGS 

376 assert sections[3].type == Section.Type.RETURN 

377 

378 x, y = sections[1].value 

379 (z,) = sections[2].value 

380 r = sections[3].value 

381 

382 assert x.name == "x" 

383 assert x.annotation == "str" 

384 assert x.description == "X value." 

385 assert x.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD 

386 assert x.default is inspect.Signature.empty 

387 

388 assert y.name == "y" 

389 assert y.annotation == "str" 

390 assert y.description == "Y value." 

391 assert y.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD 

392 assert y.default is inspect.Signature.empty 

393 

394 assert z.name == "z" 

395 assert z.annotation == "str" 

396 assert z.description == "Z value." 

397 assert z.kind is inspect.Parameter.KEYWORD_ONLY 

398 assert z.default is inspect.Signature.empty 

399 

400 assert r.annotation == "str" 

401 assert r.description == "Sum X + Y + Z." 

402 

403 

404def test_close_sections(): 

405 """Parse sections without blank lines in between.""" 

406 

407 def f(x, y, z): 

408 """ 

409 Parameters: 

410 x: X. 

411 Parameters: 

412 y: Y. 

413 

414 Parameters: 

415 z: Z. 

416 Exceptions: 

417 Error2: error. 

418 Exceptions: 

419 Error1: error. 

420 Returns: 

421 1. 

422 Returns: 

423 2. 

424 """ 

425 return x + y + z 

426 

427 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

428 assert len(sections) == 7 

429 assert len(errors) == 2 # no return type annotations 

430 

431 

432def test_code_blocks(): 

433 """Parse code blocks.""" 

434 

435 def f(s): # noqa: D300,D301 (escape sequences) 

436 """ 

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

438 

439 ```python 

440 \"\"\" 

441 This docstring is contained in another docstring O_o! 

442 

443 Parameters: 

444 s: A string. 

445 \"\"\" 

446 ``` 

447 """ 

448 return s 

449 

450 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

451 assert len(sections) == 1 

452 assert not errors 

453 

454 

455def test_indented_code_block(): 

456 """Parse indented code blocks.""" 

457 

458 def f(s): # noqa: D300,D301 (escape sequences) 

459 """ 

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

461 

462 \"\"\" 

463 This docstring is contained in another docstring O_o! 

464 

465 Parameters: 

466 s: A string. 

467 \"\"\" 

468 """ 

469 return s 

470 

471 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

472 assert len(sections) == 1 

473 assert not errors 

474 

475 

476def test_extra_parameter(): 

477 """Warn on extra parameter in docstring.""" 

478 

479 def f(x): 

480 """ 

481 Parameters: 

482 x: Integer. 

483 y: Integer. 

484 """ 

485 return x 

486 

487 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

488 assert len(sections) == 1 

489 assert len(errors) == 1 

490 assert "No type" in errors[0] 

491 

492 

493def test_missing_parameter(): 

494 """Don't warn on missing parameter in docstring.""" 

495 # FIXME: could warn 

496 def f(x, y): 

497 """ 

498 Parameters: 

499 x: Integer. 

500 """ 

501 return x + y 

502 

503 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

504 assert len(sections) == 1 

505 assert not errors 

506 

507 

508def test_param_line_without_colon(): 

509 """Warn when missing colon.""" 

510 

511 def f(x: int): 

512 """ 

513 Parameters: 

514 x is an integer. 

515 """ 

516 return x 

517 

518 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

519 assert not sections # getting x fails, so the section is empty and discarded 

520 assert len(errors) == 2 

521 assert "pair" in errors[0] 

522 assert "Empty" in errors[1] 

523 

524 

525def test_param_line_without_colon_keyword_only(): 

526 """Warn when missing colon.""" 

527 

528 def f(*, x: int): 

529 """ 

530 Keyword Args: 

531 x is an integer. 

532 """ 

533 return x 

534 

535 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

536 assert not sections # getting x fails, so the section is empty and discarded 

537 assert len(errors) == 2 

538 assert "pair" in errors[0] 

539 assert "Empty" in errors[1] 

540 

541 

542def test_admonitions(): 

543 """Parse admonitions.""" 

544 

545 def f(): 

546 """ 

547 Note: 

548 Hello. 

549 

550 Note: With title. 

551 Hello again. 

552 

553 Something: 

554 Something. 

555 """ 

556 

557 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

558 assert len(sections) == 1 

559 assert not errors 

560 

561 

562def test_invalid_sections(): 

563 """Warn on invalid (empty) sections.""" 

564 

565 def f(): 

566 """ 

567 Parameters: 

568 Exceptions: 

569 Exceptions: 

570 

571 Returns: 

572 Note: 

573 

574 Important: 

575 """ 

576 

577 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

578 assert len(sections) == 1 

579 for error in errors[:3]: 

580 assert "Empty" in error 

581 assert "Empty return section at line" in errors[3] 

582 assert "Empty" in errors[-1] 

583 

584 

585def test_multiple_lines_in_sections_items(): 

586 """Parse multi-line item description.""" 

587 

588 def f(p: str, q: str): 

589 """ 

590 Hi. 

591 

592 Arguments: 

593 p: This argument 

594 has a description 

595 spawning on multiple lines. 

596 

597 It even has blank lines in it. 

598 Some of these lines 

599 are indented for no reason. 

600 q: 

601 What if the first line is blank? 

602 """ 

603 return p + q 

604 

605 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

606 assert len(sections) == 2 

607 assert len(sections[1].value) == 2 

608 assert errors 

609 for error in errors: 

610 assert "should be 4 * 2 = 8 spaces, not" in error 

611 

612 

613def test_parse_args_kwargs(): 

614 """Parse args and kwargs.""" 

615 

616 def f(a, *args, **kwargs): 

617 """ 

618 Arguments: 

619 a: a parameter. 

620 *args: args parameters. 

621 **kwargs: kwargs parameters. 

622 """ 

623 return 1 

624 

625 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

626 assert len(sections) == 1 

627 expected_parameters = {"a": "a parameter.", "*args": "args parameters.", "**kwargs": "kwargs parameters."} 

628 for param in sections[0].value: 

629 assert param.name in expected_parameters 

630 assert expected_parameters[param.name] == param.description 

631 assert not errors 

632 

633 

634def test_parse_args_kwargs_keyword_only(): 

635 """Parse args and kwargs.""" 

636 

637 def f(a, *args, **kwargs): 

638 """ 

639 Arguments: 

640 a: a parameter. 

641 *args: args parameters. 

642 

643 Keyword Args: 

644 **kwargs: kwargs parameters. 

645 """ 

646 return 1 

647 

648 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

649 assert len(sections) == 2 

650 expected_parameters = {"a": "a parameter.", "*args": "args parameters."} 

651 for param in sections[0].value: 

652 assert param.name in expected_parameters 

653 assert expected_parameters[param.name] == param.description 

654 

655 expected_parameters = {"**kwargs": "kwargs parameters."} 

656 for param in sections[1].value: 

657 assert param.name in expected_parameters 

658 assert expected_parameters[param.name] == param.description 

659 

660 assert not errors 

661 

662 

663def test_different_indentation(): 

664 """Parse different indentations, warn on confusing indentation.""" 

665 

666 def f(): 

667 """ 

668 Hello. 

669 

670 Raises: 

671 StartAt5: this section's items starts with 5 spaces of indentation. 

672 Well indented continuation line. 

673 Badly indented continuation line (will trigger an error). 

674 

675 Empty lines are preserved, as well as extra-indentation (this line is a code block). 

676 AnyOtherLine: ...starting with exactly 5 spaces is a new item. 

677 AnyLine: ...indented with less than 5 spaces signifies the end of the section. 

678 """ 

679 

680 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

681 assert len(sections) == 3 

682 assert len(sections[1].value) == 2 

683 assert sections[1].value[0].description == ( 

684 "this section's items starts with 5 spaces of indentation.\n" 

685 "Well indented continuation line.\n" 

686 "Badly indented continuation line (will trigger an error).\n" 

687 "\n" 

688 " Empty lines are preserved, as well as extra-indentation (this line is a code block)." 

689 ) 

690 assert sections[2].value == " AnyLine: ...indented with less than 5 spaces signifies the end of the section." 

691 assert len(errors) == 1 

692 assert "should be 5 * 2 = 10 spaces, not 6" in errors[0] 

693 

694 

695def test_parse_module_attributes_section(): 

696 """Parse attributes section in modules.""" 

697 loader = Loader() 

698 obj = loader.get_object_documentation("tests.fixtures.docstring_attributes_section") 

699 assert len(obj.docstring_sections) == 2 

700 assert not obj.docstring_errors 

701 attr_section = obj.docstring_sections[1] 

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

703 assert len(attr_section.value) == 5 

704 expected = [ 

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

706 {"name": "B", "annotation": "bytes", "description": "Beta."}, 

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

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

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

710 ] 

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

712 

713 

714def test_docstring_with_yield_section(): 

715 """Parse Yields section.""" 

716 

717 def f(): 

718 """A useless range wrapper. 

719 

720 Yields: 

721 int: Integers. 

722 """ 

723 yield from range(10) 

724 

725 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

726 assert len(sections) == 2 

727 annotated = sections[1].value 

728 assert annotated.annotation == "int" 

729 assert annotated.description == "Integers." 

730 assert not errors 

731 

732 

733def test_docstring_with_yield_section_and_return_annotation(): 

734 """Parse Yields section.""" 

735 

736 def f() -> Iterator[int]: 

737 """A useless range wrapper. 

738 

739 Yields: 

740 Integers. 

741 """ 

742 yield from range(10) 

743 

744 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

745 assert len(sections) == 2 

746 annotated = sections[1].value 

747 assert annotated.annotation is Iterator[int] 

748 assert annotated.description == "Integers." 

749 assert not errors 

750 

751 

752def test_keyword_args_no_type(): 

753 """Parse types for keyword arguments.""" 

754 

755 def f(**kwargs): 

756 """Do nothing. 

757 

758 Keyword arguments: 

759 a: No type. 

760 """ 

761 

762 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

763 assert len(sections) == 2 

764 kwargs = sections[1].value 

765 assert kwargs[0].name == "a" 

766 assert kwargs[0].annotation is inspect.Parameter.empty 

767 assert kwargs[0].description == "No type." 

768 assert kwargs[0].kind is inspect.Parameter.KEYWORD_ONLY 

769 assert kwargs[0].default is inspect.Parameter.empty 

770 assert len(errors) == 1 

771 assert "No type annotation for parameter" in errors[0] 

772 

773 

774def test_keyword_args_type(): 

775 """Parse types for keyword arguments.""" 

776 

777 def f(**kwargs): 

778 """Do nothing. 

779 

780 Keyword arguments: 

781 a (int): Typed. 

782 """ 

783 

784 sections, errors = parse(inspect.getdoc(f), inspect.signature(f)) 

785 assert len(sections) == 2 

786 kwargs = sections[1].value 

787 assert kwargs[0].name == "a" 

788 assert kwargs[0].annotation == "int" 

789 assert kwargs[0].description == "Typed." 

790 assert kwargs[0].kind is inspect.Parameter.KEYWORD_ONLY 

791 assert kwargs[0].default is inspect.Parameter.empty 

792 assert not errors