Coverage for tests/test_parsers/test_docstrings/test_numpy.py: 93.14%

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

163 statements  

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

2 

3import inspect 

4from textwrap import dedent 

5 

6from pytkdocs.loader import Loader 

7from pytkdocs.parsers.docstrings.base import Section 

8from pytkdocs.parsers.docstrings.numpy import Numpy 

9 

10 

11class DummyObject: 

12 path = "o" 

13 

14 

15def parse( 

16 docstring, 

17 signature=None, 

18 return_type=inspect.Signature.empty, 

19 trim_doctest=False, 

20): 

21 """Helper to parse a doctring.""" 

22 parser = Numpy(trim_doctest_flags=trim_doctest) 

23 

24 return parser.parse( 

25 dedent(docstring).strip(), 

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

27 ) 

28 

29 

30def test_simple_docstring(): 

31 """Parse a simple docstring.""" 

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

33 assert len(sections) == 1 

34 assert not errors 

35 

36 

37def test_multi_line_docstring(): 

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

39 sections, errors = parse( 

40 """ 

41 A somewhat longer docstring. 

42 

43 Blablablabla. 

44 """ 

45 ) 

46 assert len(sections) == 1 

47 assert not errors 

48 

49 

50def test_sections_without_signature(): 

51 """Parse a docstring without a signature.""" 

52 # type of return value always required 

53 sections, errors = parse( 

54 """ 

55 Sections without signature. 

56 

57 Parameters 

58 ---------- 

59 void : 

60 SEGFAULT. 

61 niet : 

62 SEGFAULT. 

63 nada : 

64 SEGFAULT. 

65 rien : 

66 SEGFAULT. 

67 

68 Raises 

69 ------ 

70 GlobalError 

71 when nothing works as expected. 

72 

73 Returns 

74 ------- 

75 bool 

76 Itself. 

77 """ 

78 ) 

79 assert len(sections) == 4 

80 assert len(errors) == 4 # missing annotations for params 

81 for error in errors: 

82 assert "param" in error 

83 

84 

85def test_sections_without_description(): 

86 """Parse a docstring without descriptions.""" 

87 # type of return value always required 

88 sections, errors = parse( 

89 """ 

90 Sections without descriptions. 

91 

92 Parameters 

93 ---------- 

94 void : str 

95 niet : str 

96 

97 Raises 

98 ------ 

99 GlobalError 

100 

101 Returns 

102 ------- 

103 bool 

104 """ 

105 ) 

106 

107 # Assert that errors are as expected 

108 assert len(sections) == 4 

109 assert len(errors) == 6 

110 for error in errors[:4]: 

111 assert "param" in error 

112 assert "exception" in errors[4] 

113 assert "return description" in errors[5] 

114 

115 # Assert that no descriptions are ever None (can cause exceptions downstream) 

116 assert sections[1].type is Section.Type.PARAMETERS 

117 for p in sections[1].value: 

118 assert p.description is not None 

119 

120 assert sections[2].type is Section.Type.EXCEPTIONS 

121 for p in sections[2].value: 

122 assert p.description is not None 

123 

124 assert sections[3].type is Section.Type.RETURN 

125 assert sections[3].value.description is not None 

126 

127 

128def test_property_docstring(): 

129 """Parse a property docstring.""" 

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

131 prop = class_.attributes[0] 

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

133 assert len(sections) == 2 

134 assert not errors 

135 

136 

137def test_function_without_annotations(): 

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

139 

140 def f(x, y): 

141 """ 

142 This function has no annotations. 

143 

144 Parameters 

145 ---------- 

146 x: 

147 X value. 

148 y: 

149 Y value. 

150 

151 Returns 

152 ------- 

153 float 

154 Sum X + Y. 

155 """ 

156 return x + y 

157 

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

159 assert len(sections) == 3 

160 assert not errors 

161 

162 

163def test_function_with_annotations(): 

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

165 

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

167 """ 

168 This function has annotations. 

169 

170 Parameters 

171 ---------- 

172 x: 

173 X value. 

174 y: 

175 Y value. 

176 

177 Returns 

178 ------- 

179 int 

180 Sum X + Y. 

181 """ 

182 return x + y 

183 

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

185 assert len(sections) == 3 

186 assert not errors 

187 

188 

189def test_function_with_examples_trim_doctest(): 

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

191 

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

193 """Test function. 

194 

195 Example 

196 ------- 

197 We want to skip the following test. 

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

199 True 

200 

201 And then a few more examples here: 

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

203 a 

204 <BLANKLINE> 

205 b 

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

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

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

209 """ 

210 return x 

211 

212 sections, errors = parse( 

213 inspect.getdoc(f), 

214 inspect.signature(f), 

215 trim_doctest=True, 

216 ) 

217 assert len(sections) == 2 

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

219 assert not errors 

220 

221 # Verify that doctest flags have indeed been trimmed 

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

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

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

225 assert "<BLANKLINE>" not in example_str 

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

227 

228 

229def test_function_with_examples(): 

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

231 

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

233 """ 

234 This function has annotations. 

235 

236 Examples 

237 -------- 

238 Some examples that will create an unified code block: 

239 

240 >>> 2 + 2 == 5 

241 False 

242 >>> print("examples") 

243 "examples" 

244 

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

246 

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

248 

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

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

251 

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

253 "I'm in other code block!" 

254 

255 We also can write multiline examples: 

256 

257 >>> x = 3 + 2 

258 >>> y = x + 10 

259 >>> y 

260 15 

261 

262 This is just a typical Python code block: 

263 

264 ```python 

265 print("examples") 

266 return 2 + 2 

267 ``` 

268 

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

270 

271 ```python 

272 >>> print("examples") 

273 "examples" 

274 >>> 2 + 2 

275 4 

276 ``` 

277 

278 The blank line before an example is optional. 

279 >>> x = 3 

280 >>> y = "apple" 

281 >>> z = False 

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

283 >>> my_print_list_function(l) 

284 3 

285 "apple" 

286 False 

287 """ 

288 return x + y 

289 

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

291 assert len(sections) == 2 

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

293 assert not errors 

294 

295 

296def test_types_in_docstring(): 

297 """Parse types in docstring.""" 

298 

299 def f(x, y): 

300 """ 

301 The types are written in the docstring. 

302 

303 Parameters 

304 ---------- 

305 x : int 

306 X value. 

307 y : int 

308 Y value. 

309 

310 Returns 

311 ------- 

312 int 

313 Sum X + Y. 

314 """ 

315 return x + y 

316 

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

318 assert len(sections) == 3 

319 assert not errors 

320 

321 x, y = sections[1].value 

322 r = sections[2].value 

323 

324 assert x.name == "x" 

325 assert x.annotation == "int" 

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

327 assert x.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD 

328 assert x.default is inspect.Signature.empty 

329 

330 assert y.name == "y" 

331 assert y.annotation == "int" 

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

333 assert y.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD 

334 assert y.default is inspect.Signature.empty 

335 

336 assert r.annotation == "int" 

337 assert r.description == "Sum X + Y." 

338 

339 

340def test_types_and_optional_in_docstring(): 

341 """Parse optional types in docstring.""" 

342 

343 def f(x=1, y=None): 

344 """ 

345 The types are written in the docstring. 

346 

347 Parameters 

348 ---------- 

349 x : int 

350 X value. 

351 y : int, optional 

352 Y value. 

353 

354 Returns 

355 ------- 

356 int 

357 Sum X + Y. 

358 """ 

359 return x + (y or 1) 

360 

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

362 assert len(sections) == 3 

363 assert not errors 

364 

365 x, y = sections[1].value 

366 

367 assert x.name == "x" 

368 assert x.annotation == "int" 

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

370 assert x.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD 

371 assert x.default == 1 

372 

373 assert y.name == "y" 

374 assert y.annotation == "int" 

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

376 assert y.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD 

377 assert y.default is None 

378 

379 

380def test_types_in_signature_and_docstring(): 

381 """Parse types in both signature and docstring.""" 

382 

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

384 """ 

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

386 

387 Parameters 

388 ---------- 

389 x : int 

390 X value. 

391 y : int 

392 Y value. 

393 

394 Returns 

395 ------- 

396 int 

397 Sum X + Y. 

398 """ 

399 return x + y 

400 

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

402 assert len(sections) == 3 

403 assert not errors 

404 

405 

406def test_close_sections(): 

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

408 

409 def f(x, y, z): 

410 """ 

411 Parameters 

412 ---------- 

413 x : 

414 X 

415 y : 

416 Y 

417 z : 

418 Z 

419 Raises 

420 ------ 

421 Error2 

422 error. 

423 Error1 

424 error. 

425 Returns 

426 ------- 

427 str 

428 value 

429 """ 

430 return x + y + z 

431 

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

433 assert len(sections) == 3 

434 assert not errors 

435 

436 

437# test_code_blocks was removed as docstrings within a code block 

438# are not applicable to numpy docstrings 

439 

440 

441def test_extra_parameter(): 

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

443 

444 def f(x): 

445 """ 

446 Parameters 

447 ---------- 

448 x : 

449 Integer. 

450 y : 

451 Integer. 

452 """ 

453 return x 

454 

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

456 assert len(sections) == 1 

457 assert len(errors) == 1 

458 assert "No type" in errors[0] 

459 

460 

461def test_missing_parameter(): 

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

463 # FIXME: could warn 

464 def f(x, y): 

465 """ 

466 Parameters 

467 ---------- 

468 x : 

469 Integer. 

470 """ 

471 return x + y 

472 

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

474 assert len(sections) == 1 

475 assert not errors 

476 

477 

478def test_multiple_lines_in_sections_items(): 

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

480 

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

482 """ 

483 Hi. 

484 

485 Parameters 

486 ---------- 

487 p : 

488 This argument 

489 has a description 

490 spawning on multiple lines. 

491 

492 It even has blank lines in it. 

493 Some of these lines 

494 are indented for no reason. 

495 q : 

496 What if the first line is blank? 

497 """ 

498 return p + q 

499 

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

501 assert len(sections) == 2 

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

503 # numpy docstrings parameter description can be parsed even if misindentated 

504 assert not errors 

505 

506 

507def test_parse_args_kwargs(): 

508 """Parse args and kwargs.""" 

509 

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

511 """ 

512 Parameters 

513 ---------- 

514 a : 

515 a parameter. 

516 *args : 

517 args parameters. 

518 **kwargs : 

519 kwargs parameters. 

520 """ 

521 return 1 

522 

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

524 assert len(sections) == 1 

525 expected_parameters = { 

526 "a": "a parameter.", 

527 "*args": "args parameters.", 

528 "**kwargs": "kwargs parameters.", 

529 } 

530 for param in sections[0].value: 

531 assert param.name in expected_parameters 

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

533 assert not errors 

534 

535 

536def test_different_indentation(): 

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

538 

539 def f(): 

540 """ 

541 Hello. 

542 

543 Raises 

544 ------ 

545 StartAt5 

546 this section's items starts with x spaces of indentation. 

547 Well indented continuation line. 

548 Badly indented continuation line (will not trigger an error). 

549 

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

551 AnyOtherLine 

552 ...starting with exactly 5 spaces is a new item. 

553 """ 

554 

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

556 assert len(sections) == 2 

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

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

559 "this section's items starts with x spaces of indentation.\n" 

560 "Well indented continuation line.\n" 

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

562 "\n" 

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

564 ) 

565 assert not errors