Coverage for tests\unit\validate_type\test_validate_type.py: 78%

206 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-12-15 17:57 -0700

1from __future__ import annotations 

2 

3import typing 

4from typing import Any, Optional, Union 

5 

6import pytest 

7 

8from muutils.validate_type import IncorrectTypeException, validate_type 

9 

10 

11# Tests for basic types and common use cases 

12@pytest.mark.parametrize( 

13 "value, expected_type, expected_result", 

14 [ 

15 (42, int, True), 

16 (3.14, float, True), 

17 (5, int, True), 

18 (5.0, int, False), 

19 ("hello", str, True), 

20 (True, bool, True), 

21 (None, type(None), True), 

22 (None, int, False), 

23 ([1, 2, 3], typing.List, True), 

24 ([1, 2, 3], typing.List, True), 

25 ({"a": 1, "b": 2}, typing.Dict, True), 

26 ({"a": 1, "b": 2}, typing.Dict, True), 

27 ({1, 2, 3}, typing.Set, True), 

28 ({1, 2, 3}, typing.Set, True), 

29 ((1, 2, 3), typing.Tuple, True), 

30 ((1, 2, 3), typing.Tuple, True), 

31 (b"bytes", bytes, True), 

32 (b"bytes", str, False), 

33 ("3.14", float, False), 

34 ("hello", Any, True), 

35 (5, Any, True), 

36 (3.14, Any, True), 

37 # ints 

38 (int(0), int, True), 

39 (int(1), int, True), 

40 (int(-1), int, True), 

41 # bools 

42 (True, bool, True), 

43 (False, bool, True), 

44 ], 

45) 

46def test_validate_type_basic(value, expected_type, expected_result): 

47 try: 

48 assert validate_type(value, expected_type) == expected_result 

49 except Exception as e: 

50 raise Exception( 

51 f"{value = }, {expected_type = }, {expected_result = }, {e}" 

52 ) from e 

53 

54 

55@pytest.mark.parametrize( 

56 "value", 

57 [ 

58 42, 

59 "hello", 

60 3.14, 

61 True, 

62 None, 

63 [1, 2, 3], 

64 {"a": 1, "b": 2}, 

65 {1, 2, 3}, 

66 (1, 2, 3), 

67 b"bytes", 

68 "3.14", 

69 ], 

70) 

71def test_validate_type_any(value): 

72 try: 

73 assert validate_type(value, Any) 

74 except Exception as e: 

75 raise Exception(f"{value = }, expected `Any`, {e}") from e 

76 

77 

78@pytest.mark.parametrize( 

79 "value, expected_type, expected_result", 

80 [ 

81 (42, Union[int, str], True), 

82 ("hello", Union[int, str], True), 

83 (3.14, Union[int, float], True), 

84 (True, Union[int, str], True), 

85 (None, Union[int, type(None)], True), 

86 (None, Union[int, str], False), 

87 (5, Union[int, str], True), 

88 (5.0, Union[int, str], False), 

89 ("hello", Union[int, str], True), 

90 (5, typing.Union[int, str], True), 

91 ("hello", typing.Union[int, str], True), 

92 (5.0, typing.Union[int, str], False), 

93 (5, Union[int, str], True), 

94 ("hello", Union[int, str], True), 

95 (5.0, Union[int, str], False), 

96 ], 

97) 

98def test_validate_type_union(value, expected_type, expected_result): 

99 try: 

100 assert validate_type(value, expected_type) == expected_result 

101 except Exception as e: 

102 raise Exception( 

103 f"{value = }, {expected_type = }, {expected_result = }, {e}" 

104 ) from e 

105 

106 

107@pytest.mark.parametrize( 

108 "value, expected_type, expected_result", 

109 [ 

110 (42, Optional[int], True), 

111 ("hello", Optional[int], False), 

112 (3.14, Optional[int], False), 

113 ([1], Optional[typing.List[int]], True), 

114 (None, Optional[int], True), 

115 (None, Optional[str], True), 

116 (None, Optional[int], True), 

117 (None, Optional[None], True), 

118 (None, Optional[typing.List[typing.Dict[str, int]]], True), 

119 ], 

120) 

121def test_validate_type_optional(value, expected_type, expected_result): 

122 try: 

123 assert validate_type(value, expected_type) == expected_result 

124 except Exception as e: 

125 raise Exception( 

126 f"{value = }, {expected_type = }, {expected_result = }, {e}" 

127 ) from e 

128 

129 

130@pytest.mark.parametrize( 

131 "value, expected_type, expected_result", 

132 [ 

133 (42, typing.List[int], False), 

134 ([1, 2, 3], typing.List[int], True), 

135 ([1, 2, 3], typing.List[str], False), 

136 (["a", "b", "c"], typing.List[str], True), 

137 ([1, "a", 3], typing.List[int], False), 

138 (42, typing.List[int], False), 

139 ([1, 2, 3], typing.List[int], True), 

140 ([1, "2", 3], typing.List[int], False), 

141 ], 

142) 

143def test_validate_type_list(value, expected_type, expected_result): 

144 try: 

145 assert validate_type(value, expected_type) == expected_result 

146 except Exception as e: 

147 raise Exception( 

148 f"{value = }, {expected_type = }, {expected_result = }, {e}" 

149 ) from e 

150 

151 

152@pytest.mark.parametrize( 

153 "value, expected_type, expected_result", 

154 [ 

155 (42, typing.Dict[str, int], False), 

156 ({"a": 1, "b": 2}, typing.Dict[str, int], True), 

157 ({"a": 1, "b": 2}, typing.Dict[int, str], False), 

158 (42, typing.Dict[str, int], False), 

159 ({"a": 1, "b": 2}, typing.Dict[str, int], True), 

160 ({"a": 1, "b": 2}, typing.Dict[int, str], False), 

161 ({1: "a", 2: "b"}, typing.Dict[int, str], True), 

162 ({1: "a", 2: "b"}, typing.Dict[str, int], False), 

163 ({"a": 1, "b": "c"}, typing.Dict[str, int], False), 

164 ([("a", 1), ("b", 2)], typing.Dict[str, int], False), 

165 ({"key": "value"}, typing.Dict[str, str], True), 

166 ({"key": 2}, typing.Dict[str, str], False), 

167 ({"key": 2}, typing.Dict[str, int], True), 

168 ({"key": 2.0}, typing.Dict[str, int], False), 

169 ({"a": 1, "b": 2}, typing.Dict[str, int], True), 

170 ({"a": 1, "b": "2"}, typing.Dict[str, int], False), 

171 ], 

172) 

173def test_validate_type_dict(value, expected_type, expected_result): 

174 try: 

175 assert validate_type(value, expected_type) == expected_result 

176 except Exception as e: 

177 raise Exception( 

178 f"{value = }, {expected_type = }, {expected_result = }, {e}" 

179 ) from e 

180 

181 

182@pytest.mark.parametrize( 

183 "value, expected_type, expected_result", 

184 [ 

185 (42, typing.Set[int], False), 

186 ({1, 2, 3}, typing.Set[int], True), 

187 (42, typing.Set[int], False), 

188 ({1, 2, 3}, typing.Set[int], True), 

189 ({1, 2, 3}, typing.Set[str], False), 

190 ({"a", "b", "c"}, typing.Set[str], True), 

191 ({1, "a", 3}, typing.Set[int], False), 

192 (42, typing.Set[int], False), 

193 ({1, 2, 3}, typing.Set[int], True), 

194 ({1, "2", 3}, typing.Set[int], False), 

195 ([1, 2, 3], typing.Set[int], False), 

196 ("hello", typing.Set[str], False), 

197 ], 

198) 

199def test_validate_type_set(value, expected_type, expected_result): 

200 try: 

201 assert validate_type(value, expected_type) == expected_result 

202 except Exception as e: 

203 raise Exception( 

204 f"{value = }, {expected_type = }, {expected_result = }, {e}" 

205 ) from e 

206 

207 

208@pytest.mark.parametrize( 

209 "value, expected_type, expected_result", 

210 [ 

211 (42, typing.Tuple[int, str], False), 

212 ((1, "a"), typing.Tuple[int, str], True), 

213 (42, typing.Tuple[int, str], False), 

214 ((1, "a"), typing.Tuple[int, str], True), 

215 ((1, 2), typing.Tuple[int, str], False), 

216 ((1, 2), typing.Tuple[int, int], True), 

217 ((1, 2, 3), typing.Tuple[int, int], False), 

218 ((1, "a", 3.14), typing.Tuple[int, str, float], True), 

219 (("a", "b", "c"), typing.Tuple[str, str, str], True), 

220 ((1, "a", 3.14), typing.Tuple[int, str], False), 

221 ((1, "a", 3.14), typing.Tuple[int, str, float], True), 

222 ([1, "a", 3.14], typing.Tuple[int, str, float], False), 

223 ( 

224 (1, "a", 3.14, "b", True, None, (1, 2, 3)), 

225 # no idea why this throws type error, only locally, and only for the generated modern types 

226 typing.Tuple[ # type: ignore[misc] 

227 int, str, float, str, bool, type(None), typing.Tuple[int, int, int] 

228 ], 

229 True, 

230 ), 

231 ], 

232) 

233def test_validate_type_tuple(value, expected_type, expected_result): 

234 try: 

235 assert validate_type(value, expected_type) == expected_result 

236 except Exception as e: 

237 raise Exception( 

238 f"{value = }, {expected_type = }, {expected_result = }, {e}" 

239 ) from e 

240 

241 

242@pytest.mark.parametrize( 

243 "value, expected_type", 

244 [ 

245 (43, typing.Callable), 

246 (lambda x: x, typing.Callable), 

247 (42, typing.Callable[[], None]), 

248 (42, typing.Callable[[int, str], typing.List]), 

249 ], 

250) 

251def test_validate_type_unsupported_type_hint(value, expected_type): 

252 with pytest.raises(NotImplementedError): 

253 validate_type(value, expected_type) 

254 print(f"Failed to except: {value = }, {expected_type = }") 

255 

256 

257@pytest.mark.parametrize( 

258 "value, expected_type, expected_result", 

259 [ 

260 ([1, 2, 3], typing.List[int], True), 

261 (["a", "b", "c"], typing.List[str], True), 

262 ([1, "a", 3], typing.List[int], False), 

263 ([1, 2, [3, 4]], typing.List[Union[int, typing.List[int]]], True), 

264 ([(1, 2), (3, 4)], typing.List[typing.Tuple[int, int]], True), 

265 ([(1, 2), (3, "4")], typing.List[typing.Tuple[int, int]], False), 

266 ({1: [1, 2], 2: [3, 4]}, typing.Dict[int, typing.List[int]], True), 

267 ({1: [1, 2], 2: [3, "4"]}, typing.Dict[int, typing.List[int]], False), 

268 ], 

269) 

270def test_validate_type_collections(value, expected_type, expected_result): 

271 try: 

272 assert validate_type(value, expected_type) == expected_result 

273 except Exception as e: 

274 raise Exception( 

275 f"{value = }, {expected_type = }, {expected_result = }, {e}" 

276 ) from e 

277 

278 

279@pytest.mark.parametrize( 

280 "value, expected_type, expected_result", 

281 [ 

282 # empty lists 

283 ([], typing.List[int], True), 

284 ([], typing.List[typing.Dict], True), 

285 ( 

286 [], 

287 typing.List[typing.Tuple[typing.Dict[typing.Tuple, str], str, None]], 

288 True, 

289 ), 

290 # empty dicts 

291 ({}, typing.Dict[str, int], True), 

292 ({}, typing.Dict[str, typing.Dict], True), 

293 ({}, typing.Dict[str, typing.Dict[str, int]], True), 

294 ({}, typing.Dict[str, typing.Dict[str, int]], True), 

295 # empty sets 

296 (set(), typing.Set[int], True), 

297 (set(), typing.Set[typing.Dict], True), 

298 ( 

299 set(), 

300 typing.Set[typing.Tuple[typing.Dict[typing.Tuple, str], str, None]], 

301 True, 

302 ), 

303 # empty tuple 

304 (tuple(), typing.Tuple, True), 

305 # empty string 

306 ("", str, True), 

307 # empty bytes 

308 (b"", bytes, True), 

309 # None 

310 (None, type(None), True), 

311 # bools are ints, ints are not floats 

312 (True, int, True), 

313 (False, int, True), 

314 (True, float, False), 

315 (False, float, False), 

316 (1, int, True), 

317 (0, int, True), 

318 (1, float, False), 

319 (0, float, False), 

320 (0, bool, False), 

321 (1, bool, False), 

322 # weird floats 

323 (float("nan"), float, True), 

324 (float("inf"), float, True), 

325 (float("-inf"), float, True), 

326 (float(0), float, True), 

327 # list/tuple 

328 ([1], typing.Tuple[int, int], False), 

329 ((1, 2), typing.List[int], False), 

330 ], 

331) 

332def test_validate_type_edge_cases(value, expected_type, expected_result): 

333 try: 

334 assert validate_type(value, expected_type) == expected_result 

335 except Exception as e: 

336 raise Exception( 

337 f"{value = }, {expected_type = }, {expected_result = }, {e}" 

338 ) from e 

339 

340 

341@pytest.mark.parametrize( 

342 "value, expected_type, expected_result", 

343 [ 

344 (42, typing.List[int], False), 

345 ([1, 2, 3], int, False), 

346 (3.14, typing.Tuple[float], False), 

347 (3.14, typing.Tuple[float, float], False), 

348 (3.14, typing.Tuple[bool, str], False), 

349 (False, typing.Tuple[bool, str], False), 

350 (False, typing.Tuple[bool], False), 

351 ((False,), typing.Tuple[bool], True), 

352 (("abc",), typing.Tuple[str], True), 

353 ("test-dict", typing.Dict[str, int], False), 

354 ("test-dict", typing.Dict, False), 

355 ], 

356) 

357def test_validate_type_wrong_type(value, expected_type, expected_result): 

358 try: 

359 assert validate_type(value, expected_type) == expected_result 

360 except Exception as e: 

361 raise Exception( 

362 f"{value = }, {expected_type = }, {expected_result = }, {e}" 

363 ) from e 

364 

365 

366def test_validate_type_complex(): 

367 assert validate_type([1, 2, [3, 4]], typing.List[Union[int, typing.List[int]]]) 

368 assert validate_type( 

369 {"a": 1, "b": {"c": 2}}, typing.Dict[str, Union[int, typing.Dict[str, int]]] 

370 ) 

371 assert validate_type({1, (2, 3)}, typing.Set[Union[int, typing.Tuple[int, int]]]) 

372 assert validate_type((1, ("a", "b")), typing.Tuple[int, typing.Tuple[str, str]]) 

373 assert validate_type([{"key": "value"}], typing.List[typing.Dict[str, str]]) 

374 assert validate_type([{"key": 2}], typing.List[typing.Dict[str, str]]) is False 

375 assert validate_type([[1, 2], [3, 4]], typing.List[typing.List[int]]) 

376 assert validate_type([[1, 2], [3, "4"]], typing.List[typing.List[int]]) is False 

377 assert validate_type([(1, 2), (3, 4)], typing.List[typing.Tuple[int, int]]) 

378 assert ( 

379 validate_type([(1, 2), (3, "4")], typing.List[typing.Tuple[int, int]]) is False 

380 ) 

381 assert validate_type({1: "one", 2: "two"}, typing.Dict[int, str]) 

382 assert validate_type({1: "one", 2: 2}, typing.Dict[int, str]) is False 

383 assert validate_type([(1, "one"), (2, "two")], typing.List[typing.Tuple[int, str]]) 

384 assert ( 

385 validate_type([(1, "one"), (2, 2)], typing.List[typing.Tuple[int, str]]) 

386 is False 

387 ) 

388 assert validate_type({1: [1, 2], 2: [3, 4]}, typing.Dict[int, typing.List[int]]) 

389 assert ( 

390 validate_type({1: [1, 2], 2: [3, "4"]}, typing.Dict[int, typing.List[int]]) 

391 is False 

392 ) 

393 assert validate_type([(1, "a"), (2, "b")], typing.List[typing.Tuple[int, str]]) 

394 assert ( 

395 validate_type([(1, "a"), (2, 2)], typing.List[typing.Tuple[int, str]]) is False 

396 ) 

397 

398 

399@pytest.mark.parametrize( 

400 "value, expected_type, expected_result", 

401 [ 

402 ([[[[1]]]], typing.List[typing.List[typing.List[typing.List[int]]]], True), 

403 ([[[[1]]]], typing.List[typing.List[typing.List[typing.List[str]]]], False), 

404 ( 

405 {"a": {"b": {"c": 1}}}, 

406 typing.Dict[str, typing.Dict[str, typing.Dict[str, int]]], 

407 True, 

408 ), 

409 ( 

410 {"a": {"b": {"c": 1}}}, 

411 typing.Dict[str, typing.Dict[str, typing.Dict[str, str]]], 

412 False, 

413 ), 

414 ({1, 2, 3}, typing.Set[int], True), 

415 ({1, 2, 3}, typing.Set[str], False), 

416 ( 

417 ((1, 2), (3, 4)), 

418 typing.Tuple[typing.Tuple[int, int], typing.Tuple[int, int]], 

419 True, 

420 ), 

421 ( 

422 ((1, 2), (3, 4)), 

423 typing.Tuple[typing.Tuple[int, int], typing.Tuple[int, str]], 

424 False, 

425 ), 

426 ], 

427) 

428def test_validate_type_nested(value, expected_type, expected_result): 

429 try: 

430 assert validate_type(value, expected_type) == expected_result 

431 except Exception as e: 

432 raise Exception( 

433 f"{value = }, {expected_type = }, {expected_result = }, {e}" 

434 ) from e 

435 

436 

437def test_validate_type_inheritance(): 

438 class Parent: 

439 def __init__(self, a: int, b: str): 

440 self.a: int = a 

441 self.b: str = b 

442 

443 class Child(Parent): 

444 def __init__(self, a: int, b: str): 

445 self.a: int = 2 * a 

446 self.b: str = b 

447 

448 assert validate_type(Parent(1, "a"), Parent) 

449 validate_type(Child(1, "a"), Parent, do_except=True) 

450 assert validate_type(Child(1, "a"), Child) 

451 assert not validate_type(Parent(1, "a"), Child) 

452 

453 with pytest.raises(IncorrectTypeException): 

454 validate_type(Parent(1, "a"), Child, do_except=True) 

455 

456 

457def test_validate_type_class(): 

458 class Parent: 

459 def __init__(self, a: int, b: str): 

460 self.a: int = a 

461 self.b: str = b 

462 

463 class Child(Parent): 

464 def __init__(self, a: int, b: str): 

465 self.a: int = 2 * a 

466 self.b: str = b 

467 

468 assert validate_type(Parent, type) 

469 assert validate_type(Child, type) 

470 assert validate_type(Parent, typing.Type[Parent], do_except=True) 

471 assert validate_type(Child, typing.Type[Child]) 

472 assert not validate_type(Parent, typing.Type[Child]) 

473 

474 assert validate_type(Child, typing.Union[typing.Type[Child], typing.Type[Parent]]) 

475 assert validate_type(Child, typing.Union[typing.Type[Child], int]) 

476 

477 

478@pytest.mark.skip(reason="Not implemented") 

479def test_validate_type_class_union(): 

480 class Parent: 

481 def __init__(self, a: int, b: str): 

482 self.a: int = a 

483 self.b: str = b 

484 

485 class Child(Parent): 

486 def __init__(self, a: int, b: str): 

487 self.a: int = 2 * a 

488 self.b: str = b 

489 

490 class Other: 

491 def __init__(self, x: int, y: str): 

492 self.x: int = x 

493 self.y: str = y 

494 

495 assert validate_type(Child, typing.Type[typing.Union[Child, Parent]]) 

496 assert validate_type(Child, typing.Type[typing.Union[Child, Other]]) 

497 assert validate_type(Parent, typing.Type[typing.Union[Child, Other]]) 

498 assert validate_type(Parent, typing.Type[typing.Union[Parent, Other]]) 

499 

500 

501def test_validate_type_aliases(): 

502 AliasInt = int 

503 AliasStr = str 

504 AliasListInt = typing.List[int] 

505 AliasListStr = typing.List[str] 

506 AliasDictIntStr = typing.Dict[int, str] 

507 AliasDictStrInt = typing.Dict[str, int] 

508 AliasTupleIntStr = typing.Tuple[int, str] 

509 AliasTupleStrInt = typing.Tuple[str, int] 

510 AliasSetInt = typing.Set[int] 

511 AliasSetStr = typing.Set[str] 

512 AliasUnionIntStr = typing.Union[int, str] 

513 AliasUnionStrInt = typing.Union[str, int] 

514 AliasOptionalInt = typing.Optional[int] 

515 AliasOptionalStr = typing.Optional[str] 

516 AliasOptionalListInt = typing.Optional[typing.List[int]] 

517 AliasDictStrListInt = typing.Dict[str, typing.List[int]] 

518 

519 assert validate_type(42, AliasInt) 

520 assert not validate_type("42", AliasInt) 

521 assert validate_type(42, AliasInt) 

522 assert not validate_type("42", AliasInt) 

523 assert validate_type("hello", AliasStr) 

524 assert not validate_type(42, AliasStr) 

525 assert validate_type([1, 2, 3], AliasListInt) 

526 assert not validate_type([1, "2", 3], AliasListInt) 

527 assert validate_type(["hello", "world"], AliasListStr) 

528 assert not validate_type(["hello", 42], AliasListStr) 

529 assert validate_type({1: "a", 2: "b"}, AliasDictIntStr) 

530 assert not validate_type({1: 2, 3: 4}, AliasDictIntStr) 

531 assert validate_type({"one": 1, "two": 2}, AliasDictStrInt) 

532 assert not validate_type({1: "one", 2: "two"}, AliasDictStrInt) 

533 assert validate_type((1, "a"), AliasTupleIntStr) 

534 assert not validate_type(("a", 1), AliasTupleIntStr) 

535 assert validate_type(("a", 1), AliasTupleStrInt) 

536 assert not validate_type((1, "a"), AliasTupleStrInt) 

537 assert validate_type({1, 2, 3}, AliasSetInt) 

538 assert not validate_type({1, "two", 3}, AliasSetInt) 

539 assert validate_type({"one", "two"}, AliasSetStr) 

540 assert not validate_type({"one", 2}, AliasSetStr) 

541 assert validate_type(42, AliasUnionIntStr) 

542 assert validate_type("hello", AliasUnionIntStr) 

543 assert not validate_type(3.14, AliasUnionIntStr) 

544 assert validate_type("hello", AliasUnionStrInt) 

545 assert validate_type(42, AliasUnionStrInt) 

546 assert not validate_type(3.14, AliasUnionStrInt) 

547 assert validate_type(42, AliasOptionalInt) 

548 assert validate_type(None, AliasOptionalInt) 

549 assert not validate_type("42", AliasOptionalInt) 

550 assert validate_type("hello", AliasOptionalStr) 

551 assert validate_type(None, AliasOptionalStr) 

552 assert not validate_type(42, AliasOptionalStr) 

553 assert validate_type([1, 2, 3], AliasOptionalListInt) 

554 assert validate_type(None, AliasOptionalListInt) 

555 assert not validate_type(["1", "2", "3"], AliasOptionalListInt) 

556 assert validate_type({"key": [1, 2, 3]}, AliasDictStrListInt) 

557 assert not validate_type({"key": [1, "2", 3]}, AliasDictStrListInt)