1 from __future__ import annotations

2

3 import typing

4 from typing import Any, Optional, Union

5

6 import pytest

7

8 from 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)

46 def 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)

71 def 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)

98 def 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)

121 def 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)

143 def 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)

173 def 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)

199 def 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)

233 def 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)

251 def 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)

270 def 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)

332 def 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)

357 def 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

366 def 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)

428 def 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

437 def 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

457 def 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")

479 def 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

501 def 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)