datahold 4.0.0.dev31__tar.gz → 4.0.0.dev32__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/PKG-INFO +1 -1
  2. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/pyproject.toml +1 -1
  3. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/src/datahold/__init__.py +42 -24
  4. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/src/datahold.egg-info/PKG-INFO +1 -1
  5. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_FrozenDictSlot.py +0 -41
  6. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_MutableDictSlot.py +321 -12
  7. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_MutableListSlot.py +219 -180
  8. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_MutableSetSlot.py +365 -32
  9. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_abstractmethods.py +1 -1
  10. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_attributes.py +2 -0
  11. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_docs.py +4 -0
  12. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_generic.py +2 -0
  13. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_issubclass.py +8 -1
  14. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_metaclass.py +1 -0
  15. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_name.py +4 -1
  16. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_nested.py +15 -1
  17. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_sorted.py +2 -0
  18. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_variance.py +7 -1
  19. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/testdata.toml +15 -0
  20. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/LICENSE.txt +0 -0
  21. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/MANIFEST.in +0 -0
  22. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/README.rst +0 -0
  23. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/docs/v1.1.rst +0 -0
  24. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/docs/v1.2.rst +0 -0
  25. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/docs/v2.0.rst +0 -0
  26. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/docs/v2.1.rst +0 -0
  27. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/docs/v2.2.rst +0 -0
  28. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/docs/v2.3.rst +0 -0
  29. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/docs/v3.0.rst +0 -0
  30. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/docs/v4.0.rst +0 -0
  31. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/run_tests.py +0 -0
  32. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/setup.cfg +0 -0
  33. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/src/datahold/py.typed +0 -0
  34. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/src/datahold.egg-info/SOURCES.txt +0 -0
  35. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/src/datahold.egg-info/dependency_links.txt +0 -0
  36. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/src/datahold.egg-info/requires.txt +0 -0
  37. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/src/datahold.egg-info/top_level.txt +0 -0
  38. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_FrozenListSlot.py +0 -0
  39. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_FrozenSetSlot.py +0 -0
  40. {datahold-4.0.0.dev31 → datahold-4.0.0.dev32}/tests/test_all.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datahold
3
- Version: 4.0.0.dev31
3
+ Version: 4.0.0.dev32
4
4
  Summary: This project wraps common mutable datastructures for inheritance with modification.
5
5
  Author-email: Johannes <johannes.programming@gmail.com>
6
6
  License-Expression: MIT
@@ -31,7 +31,7 @@ license-files = [
31
31
  name = "datahold"
32
32
  readme = "README.rst"
33
33
  requires-python = ">=3.11"
34
- version = "4.0.0.dev31"
34
+ version = "4.0.0.dev32"
35
35
 
36
36
  [project.urls]
37
37
  Download = "https://pypi.org/project/datahold/#files"
@@ -89,15 +89,17 @@ class SupportsAddAndDiscard[Item: abc.Hashable](Protocol):
89
89
  class Mapping_Frozen[Key, Value](Collection_Frozen[Key], Protocol):
90
90
  @setdoc.basic
91
91
  def __getitem__(self: Self, key: Never, /) -> Value: ...
92
+
93
+
92
94
  class SupportsDelitemAndSetitem[Key, Value](
93
95
  Protocol,
94
96
  ):
95
97
  @setdoc.basic
96
- def __delitem__(self: Self, key: Key | str, /) -> object: ...
98
+ def __delitem__(self: Self, key: Key, /) -> object: ...
97
99
  @setdoc.basic
98
- def __setitem__(
99
- self: Self, key: Key | str, value: Value | None, /
100
- ) -> object: ...
100
+ def __setitem__(self: Self, key: Key, value: Value, /) -> object: ...
101
+
102
+
101
103
  class Sequence_Frozen[Item](
102
104
  Collection_Frozen[Item],
103
105
  Protocol,
@@ -481,7 +483,7 @@ class Mapping[Key: abc.Hashable, Value](
481
483
 
482
484
 
483
485
  class MutableMapping[Key: abc.Hashable, Value](
484
- Mapping[Key, Value],
486
+ Mapping[Key | str, Value | None],
485
487
  abc.MutableMapping[Key | str, Value | None],
486
488
  MutableObject[
487
489
  Mapping_Frozen[Key | str, Value | None],
@@ -509,8 +511,8 @@ class MutableMapping[Key: abc.Hashable, Value](
509
511
 
510
512
 
511
513
  class DictLike[Key: abc.Hashable, Value](
512
- Mapping[Key | str, Value | None],
513
- abc.Reversible[Key | str],
514
+ Mapping[Key, Value],
515
+ abc.Reversible[Key],
514
516
  ):
515
517
  """Provide abc for custom dict-like."""
516
518
 
@@ -518,7 +520,7 @@ class DictLike[Key: abc.Hashable, Value](
518
520
 
519
521
  @abstractmethod
520
522
  @setdoc.basic
521
- def __frozen__(self: Self, /) -> FrozenDict[Key, Value]: ...
523
+ def __frozen__(self: Self, /) -> frozendict[Key, Value]: ...
522
524
 
523
525
  @abstractmethod
524
526
  @setdoc.basic
@@ -528,38 +530,54 @@ class DictLike[Key: abc.Hashable, Value](
528
530
  /,
529
531
  **kwargs: Value,
530
532
  ): ...
533
+
531
534
  @setdoc.basic
532
535
  def __or__[Key_: abc.Hashable, Value_](
533
536
  self: Self,
534
- other: Dict_Init[Key_, Value_],
537
+ other: DictLike[Key_, Value_],
535
538
  /,
536
539
  ) -> DictLike[Key | Key_, Value | Value_]:
537
- other_: FrozenDict[Key, Value]
538
- try:
539
- other_ = frozendict(other) # type: ignore[arg-type]
540
- except TypeError:
540
+ if isinstance(other, DictLike):
541
+ return type(self)( # type: ignore[return-value]
542
+ self.__frozen__() | other # type: ignore[arg-type]
543
+ )
544
+ else:
541
545
  return NotImplemented
542
- return type(self)( # type: ignore[return-value]
543
- self.__frozen__() | other_
544
- )
545
546
 
546
547
  @setdoc.basic
547
- def __reversed__(self: Self, /) -> abc.Iterator[Key | str]:
548
+ def __reversed__(self: Self, /) -> abc.Iterator[Key]:
548
549
  yield from reversed(self.__frozen__())
549
550
 
551
+ @overload
550
552
  @classmethod
551
553
  @setdoc.basic
552
554
  def fromkeys(
553
555
  cls: type[Self],
554
- iterable: abc.Iterable[Key | str],
555
- value: Value | None = None,
556
+ iterable: abc.Iterable[Key],
556
557
  /,
557
- ) -> Self:
558
+ ) -> DictLike[Key, None]: ...
559
+ @overload
560
+ @classmethod
561
+ @setdoc.basic
562
+ def fromkeys(
563
+ cls: type[Self],
564
+ iterable: abc.Iterable[Key],
565
+ value: Value,
566
+ /,
567
+ ) -> Self: ...
568
+ @classmethod
569
+ @setdoc.basic
570
+ def fromkeys(
571
+ cls: type[Self],
572
+ iterable: abc.Iterable[Key],
573
+ value: Any = None,
574
+ /,
575
+ ) -> Any:
558
576
  return cls(dict.fromkeys(iterable, value))
559
577
 
560
578
 
561
579
  class FrozenDictLike[Key: abc.Hashable, Value](
562
- FrozenObjectLike[FrozenDict[Key, Value]],
580
+ FrozenObjectLike[frozendict[Key, Value]],
563
581
  DictLike[Key, Value],
564
582
  ):
565
583
  """Provide abc for custom frozen dict-like."""
@@ -568,7 +586,7 @@ class FrozenDictLike[Key: abc.Hashable, Value](
568
586
 
569
587
 
570
588
  class MutableDictLike[Key: abc.Hashable, Value](
571
- DictLike[Key, Value],
589
+ DictLike[Key | str, Value | None],
572
590
  MutableObjectLike[FrozenDict[Key, Value], Dict[Key, Value]],
573
591
  MutableMapping[Key, Value],
574
592
  ):
@@ -590,7 +608,7 @@ class MutableDictLike[Key: abc.Hashable, Value](
590
608
  other: Dict_Init[Key, Value] = (),
591
609
  /,
592
610
  **kwargs: Value | None,
593
- ):
611
+ ) -> None:
594
612
  self.update(
595
613
  other, # type: ignore[arg-type]
596
614
  **kwargs,
@@ -619,7 +637,7 @@ class MutableDictLike[Key: abc.Hashable, Value](
619
637
 
620
638
 
621
639
  class FrozenDictSlot[Key: abc.Hashable, Value](
622
- FrozenObjectSlot[FrozenDict[Key, Value]],
640
+ FrozenObjectSlot[frozendict[Key, Value]],
623
641
  FrozenDictLike[Key, Value],
624
642
  ):
625
643
  """Provide slotted frozen dict-like."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datahold
3
- Version: 4.0.0.dev31
3
+ Version: 4.0.0.dev32
4
4
  Summary: This project wraps common mutable datastructures for inheritance with modification.
5
5
  Author-email: Johannes <johannes.programming@gmail.com>
6
6
  License-Expression: MIT
@@ -17,7 +17,6 @@ __all__: list[str] = [
17
17
  "TestOperatorReversed",
18
18
  "TestOperatorSubscription",
19
19
  "TestOperatorTruth",
20
- "TestOperatorUnion",
21
20
  ]
22
21
  import unittest
23
22
  from collections import abc
@@ -430,46 +429,6 @@ class TestOperatorInequality(unittest.TestCase):
430
429
  self.assertFalse(value != {"a": 1})
431
430
 
432
431
 
433
- class TestOperatorUnion(unittest.TestCase):
434
- def test_disjoint_mappings(self: Self, /) -> None:
435
- left: FrozenDictSlot[Any, Any]
436
- result: Any
437
- left = FrozenDictSlot({"a": 1})
438
- result = left | {"b": 2}
439
- self.assertEqual(result, {"a": 1, "b": 2})
440
-
441
- def test_right_operand_overrides(self: Self, /) -> None:
442
- left: FrozenDictSlot[Any, Any]
443
- result: Any
444
- left = FrozenDictSlot({"a": 1, "b": 2})
445
- result = left | {"b": 20}
446
- self.assertEqual(result, {"a": 1, "b": 20})
447
-
448
- def test_union_with_empty_dictionary(self: Self, /) -> None:
449
- left: FrozenDictSlot[Any, Any]
450
- result: Any
451
- left = FrozenDictSlot({"a": 1})
452
- result = left | {}
453
- self.assertEqual(result, {"a": 1})
454
-
455
- def test_union_does_not_change_left_operand(self: Self, /) -> None:
456
- answer: FrozenDictSlot[str, int]
457
- left: FrozenDictSlot[str, int]
458
- solution: FrozenDictSlot[str, int]
459
- left = FrozenDictSlot({"a": 1})
460
- answer = left | {"b": 2} # type: ignore[operator]
461
- # this is the only mypy suppression
462
- # that seemingly stems not from a faulty typeshed
463
- # but from a fault in mypy itself
464
- # FrozenDictSlot[str, int].__or__
465
- # annotates SupportsKeyAndGetitem[str, int | None]
466
- # which is covariant in the second parameter
467
- # and therefore should work
468
- solution = FrozenDictSlot({"a": 1, "b": 2})
469
- self.assertEqual(answer, solution)
470
- self.assertEqual(left, {"a": 1})
471
-
472
-
473
432
  class TestOperatorLength(unittest.TestCase):
474
433
  def test_empty_mapping(self: Self, /) -> None:
475
434
  self.assertEqual(len(FrozenDictSlot({})), 0)
@@ -1,3 +1,30 @@
1
+ __all__: list[str] = [
2
+ "TestMethodClear",
3
+ "TestMethodCopy",
4
+ "TestMethodFromKeys",
5
+ "TestMethodGet",
6
+ "TestMethodItems",
7
+ "TestMethodKeys",
8
+ "TestMethodPop",
9
+ "TestMethodPopItem",
10
+ "TestMethodSetDefault",
11
+ "TestMethodUpdate",
12
+ "TestMethodValues",
13
+ "TestOperatorContains",
14
+ "TestOperatorDelItem",
15
+ "TestOperatorEqual",
16
+ "TestOperatorGetItem",
17
+ "TestOperatorInPlaceUnion",
18
+ "TestOperatorIteration",
19
+ "TestOperatorLength",
20
+ "TestOperatorNotEqual",
21
+ "TestOperatorReversed",
22
+ "TestOperatorReverseUnion",
23
+ "TestOperatorSetItem",
24
+ "TestOperatorTruthiness",
25
+ ]
26
+
27
+
1
28
  import unittest
2
29
  from collections import abc
3
30
  from typing import Any, Self
@@ -5,7 +32,7 @@ from typing import Any, Self
5
32
  from datahold import MutableDictSlot
6
33
 
7
34
 
8
- class TestClear(unittest.TestCase):
35
+ class TestMethodClear(unittest.TestCase):
9
36
  def test_clear_removes_all_entries(self: Self, /) -> None:
10
37
  value: MutableDictSlot[Any, Any]
11
38
  value = MutableDictSlot({"a": 1, "b": 2})
@@ -40,7 +67,7 @@ class TestClear(unittest.TestCase):
40
67
  self.assertEqual(list(items_view), [])
41
68
 
42
69
 
43
- class TestCopy(unittest.TestCase):
70
+ class TestMethodCopy(unittest.TestCase):
44
71
  def test_copy_has_equal_contents(self: Self, /) -> None:
45
72
  copied: MutableDictSlot[Any, Any]
46
73
  original: MutableDictSlot[Any, Any]
@@ -79,9 +106,9 @@ class TestCopy(unittest.TestCase):
79
106
  self.assertEqual(copied, {"a": 2, "b": 3})
80
107
 
81
108
 
82
- class TestFromKeys(unittest.TestCase):
109
+ class TestMethodFromKeys(unittest.TestCase):
83
110
  def test_fromkeys_uses_none_by_default(self: Self, /) -> None:
84
- result: MutableDictSlot[Any, Any]
111
+ result: Any
85
112
  result = MutableDictSlot.fromkeys(["a", "b", "c"])
86
113
  self.assertEqual(result, {"a": None, "b": None, "c": None})
87
114
  self.assertEqual(list(result), ["a", "b", "c"])
@@ -109,7 +136,7 @@ class TestFromKeys(unittest.TestCase):
109
136
  self.assertEqual(result["b"], [1])
110
137
 
111
138
 
112
- class TestGet(unittest.TestCase):
139
+ class TestMethodGet(unittest.TestCase):
113
140
  def test_get_returns_existing_value(self: Self, /) -> None:
114
141
  value: MutableDictSlot[Any, Any]
115
142
  value = MutableDictSlot({"a": 1})
@@ -140,7 +167,7 @@ class TestGet(unittest.TestCase):
140
167
  self.assertEqual(value, {"a": 1})
141
168
 
142
169
 
143
- class TestItems(unittest.TestCase):
170
+ class TestMethodItems(unittest.TestCase):
144
171
  def test_items_contains_key_value_pairs(self: Self, /) -> None:
145
172
  result: abc.Collection[Any]
146
173
  value: MutableDictSlot[Any, Any]
@@ -180,7 +207,7 @@ class TestItems(unittest.TestCase):
180
207
  self.assertNotIn(("missing", 1), result)
181
208
 
182
209
 
183
- class TestKeys(unittest.TestCase):
210
+ class TestMethodKeys(unittest.TestCase):
184
211
  def test_keys_contains_all_keys(self: Self, /) -> None:
185
212
  result: abc.Collection[Any]
186
213
  value: MutableDictSlot[Any, Any]
@@ -220,7 +247,7 @@ class TestKeys(unittest.TestCase):
220
247
  self.assertNotIn(1, result)
221
248
 
222
249
 
223
- class TestPop(unittest.TestCase):
250
+ class TestMethodPop(unittest.TestCase):
224
251
  def test_pop_returns_and_removes_existing_value(self: Self, /) -> None:
225
252
  result: Any
226
253
  value: MutableDictSlot[Any, Any]
@@ -256,7 +283,7 @@ class TestPop(unittest.TestCase):
256
283
  self.assertEqual(value, {})
257
284
 
258
285
 
259
- class TestPopItem(unittest.TestCase):
286
+ class TestMethodPopItem(unittest.TestCase):
260
287
  def test_popitem_removes_last_inserted_pair(self: Self, /) -> None:
261
288
  result: tuple[Any, Any]
262
289
  value: MutableDictSlot[Any, Any]
@@ -295,7 +322,7 @@ class TestPopItem(unittest.TestCase):
295
322
  self.assertEqual(value, {})
296
323
 
297
324
 
298
- class TestSetDefault(unittest.TestCase):
325
+ class TestMethodSetDefault(unittest.TestCase):
299
326
  def test_setdefault_returns_existing_value(self: Self, /) -> None:
300
327
  result: Any
301
328
  value: MutableDictSlot[Any, Any]
@@ -335,7 +362,7 @@ class TestSetDefault(unittest.TestCase):
335
362
  self.assertEqual(value["a"], [1])
336
363
 
337
364
 
338
- class TestUpdate(unittest.TestCase):
365
+ class TestMethodUpdate(unittest.TestCase):
339
366
  def test_update_from_mapping(self: Self, /) -> None:
340
367
  value: MutableDictSlot[Any, Any]
341
368
  value = MutableDictSlot({"a": 1})
@@ -363,7 +390,7 @@ class TestUpdate(unittest.TestCase):
363
390
  self.assertEqual(value, {"a": 10, "b": 20, "c": 30})
364
391
 
365
392
 
366
- class TestValues(unittest.TestCase):
393
+ class TestMethodValues(unittest.TestCase):
367
394
  def test_values_contains_all_values(self: Self, /) -> None:
368
395
  result: Any
369
396
  value: MutableDictSlot[Any, Any]
@@ -400,5 +427,287 @@ class TestValues(unittest.TestCase):
400
427
  self.assertEqual(result.count(1), 2)
401
428
 
402
429
 
430
+ class TestOperatorGetItem(unittest.TestCase):
431
+ def test_existing_key(self: Self, /) -> None:
432
+ slot: MutableDictSlot[Any, Any]
433
+ slot = MutableDictSlot({"name": "Alice"})
434
+ self.assertEqual(slot["name"], "Alice")
435
+
436
+ def test_missing_key_raises_key_error(self: Self, /) -> None:
437
+ slot: MutableDictSlot[Any, Any]
438
+ slot = MutableDictSlot({"name": "Alice"})
439
+ with self.assertRaises(KeyError):
440
+ _ = slot["missing"]
441
+
442
+ def test_none_value(self: Self, /) -> None:
443
+ slot: MutableDictSlot[Any, Any]
444
+ slot = MutableDictSlot({"value": None})
445
+ self.assertIsNone(slot["value"])
446
+
447
+ def test_unhashable_key_raises_type_error(self: Self, /) -> None:
448
+ slot: MutableDictSlot[Any, Any]
449
+ slot = MutableDictSlot()
450
+ with self.assertRaises(Exception):
451
+ _ = slot[["unhashable"]]
452
+
453
+
454
+ class TestOperatorSetItem(unittest.TestCase):
455
+ def test_adds_new_key(self: Self, /) -> None:
456
+ slot: MutableDictSlot[Any, Any]
457
+ slot = MutableDictSlot()
458
+ slot["name"] = "Alice"
459
+ self.assertEqual(slot, {"name": "Alice"})
460
+
461
+ def test_replaces_existing_value(self: Self, /) -> None:
462
+ slot: MutableDictSlot[Any, Any]
463
+ slot = MutableDictSlot({"count": 1})
464
+ slot["count"] = 2
465
+ self.assertEqual(slot["count"], 2)
466
+
467
+ def test_accepts_none_value(self: Self, /) -> None:
468
+ slot: MutableDictSlot[Any, Any]
469
+ slot = MutableDictSlot()
470
+ slot["value"] = None
471
+ self.assertIn("value", slot)
472
+ self.assertIsNone(slot["value"])
473
+
474
+ def test_unhashable_key_raises_type_error(self: Self, /) -> None:
475
+ slot: MutableDictSlot[Any, Any]
476
+ slot = MutableDictSlot()
477
+ with self.assertRaises(TypeError):
478
+ slot[["unhashable"]] = "value"
479
+
480
+
481
+ class TestOperatorDelItem(unittest.TestCase):
482
+ def test_deletes_existing_key(self: Self, /) -> None:
483
+ slot: MutableDictSlot[Any, Any]
484
+ slot = MutableDictSlot({"a": 1, "b": 2})
485
+ del slot["a"]
486
+ self.assertEqual(slot, {"b": 2})
487
+
488
+ def test_missing_key_raises_key_error(self: Self, /) -> None:
489
+ slot: MutableDictSlot[Any, Any]
490
+ slot = MutableDictSlot({"a": 1})
491
+ with self.assertRaises(KeyError):
492
+ del slot["missing"]
493
+
494
+ def test_delete_only_item_makes_empty(self: Self, /) -> None:
495
+ slot: MutableDictSlot[Any, Any]
496
+ slot = MutableDictSlot({"a": 1})
497
+ del slot["a"]
498
+ self.assertEqual(slot, {})
499
+
500
+ def test_unhashable_key_raises_type_error(self: Self, /) -> None:
501
+ slot: MutableDictSlot[Any, Any]
502
+ slot = MutableDictSlot({"a": 1})
503
+ with self.assertRaises(TypeError):
504
+ del slot[["unhashable"]]
505
+
506
+
507
+ class TestOperatorContains(unittest.TestCase):
508
+ def test_existing_key_is_contained(self: Self, /) -> None:
509
+ slot: MutableDictSlot[Any, Any]
510
+ slot = MutableDictSlot({"a": 1})
511
+ self.assertIn("a", slot)
512
+
513
+ def test_missing_key_is_not_contained(self: Self, /) -> None:
514
+ slot: MutableDictSlot[Any, Any]
515
+ slot = MutableDictSlot({"a": 1})
516
+ self.assertNotIn("b", slot)
517
+
518
+ def test_values_are_not_checked(self: Self, /) -> None:
519
+ slot: MutableDictSlot[Any, Any]
520
+ slot = MutableDictSlot({"a": "value"})
521
+ self.assertNotIn("value", slot)
522
+
523
+ def test_unhashable_key_raises_type_error(self: Self, /) -> None:
524
+ slot: MutableDictSlot[Any, Any]
525
+ slot = MutableDictSlot({"a": 1})
526
+ self.assertFalse(["unhashable"] in slot)
527
+
528
+
529
+ class TestOperatorEqual(unittest.TestCase):
530
+ def test_equal_contents(self: Self, /) -> None:
531
+ x: MutableDictSlot[str, int]
532
+ y: MutableDictSlot[str, int]
533
+ x = MutableDictSlot({"a": 1, "b": 2})
534
+ y = MutableDictSlot({"a": 1, "b": 2})
535
+ self.assertTrue(x == x)
536
+ self.assertTrue(x == y)
537
+
538
+ def test_order_does_not_affect_equality(self: Self, /) -> None:
539
+ x: MutableDictSlot[str, int]
540
+ y: MutableDictSlot[str, int]
541
+ x = MutableDictSlot({"a": 1, "b": 2})
542
+ y = MutableDictSlot({"b": 2, "a": 1})
543
+ self.assertTrue(x == y)
544
+
545
+ def test_equal_to_plain_dict(self: Self, /) -> None:
546
+ slot: MutableDictSlot[str, int]
547
+ slot = MutableDictSlot[str, int]({"a": 1})
548
+ self.assertTrue(slot == {"a": 1})
549
+
550
+ def test_different_values_are_not_equal(self: Self, /) -> None:
551
+ slot: MutableDictSlot[str, int]
552
+ slot = MutableDictSlot[str, int]({"a": 1})
553
+ self.assertFalse(slot == {"a": 2})
554
+
555
+
556
+ class TestOperatorNotEqual(unittest.TestCase):
557
+ def test_different_values(self: Self, /) -> None:
558
+ slot: MutableDictSlot[str, int]
559
+ slot = MutableDictSlot[str, int]({"a": 1})
560
+ self.assertTrue(slot != {"a": 2})
561
+
562
+ def test_different_keys(self: Self, /) -> None:
563
+ slot: MutableDictSlot[str, int]
564
+ slot = MutableDictSlot[str, int]({"a": 1})
565
+ self.assertTrue(slot != {"b": 1})
566
+
567
+ def test_different_lengths(self: Self, /) -> None:
568
+ slot: MutableDictSlot[str, int]
569
+ slot = MutableDictSlot[str, int]({"a": 1})
570
+ self.assertTrue(slot != {"a": 1, "b": 2})
571
+
572
+ def test_identical_contents_are_not_unequal(self: Self, /) -> None:
573
+ slot: MutableDictSlot[str, int]
574
+ slot = MutableDictSlot[str, int]({"a": 1})
575
+ self.assertFalse(slot != {"a": 1})
576
+
577
+
578
+ class TestOperatorInPlaceUnion(unittest.TestCase):
579
+ def test_adds_distinct_keys(self: Self, /) -> None:
580
+ slot: MutableDictSlot[Any, Any]
581
+ slot = MutableDictSlot({"a": 1})
582
+ slot |= {"b": 2}
583
+ self.assertEqual(slot, {"a": 1, "b": 2})
584
+
585
+ def test_overrides_duplicate_key(self: Self, /) -> None:
586
+ slot: MutableDictSlot[Any, Any]
587
+ slot = MutableDictSlot({"a": 1})
588
+ slot |= {"a": 2}
589
+ self.assertEqual(slot, {"a": 2})
590
+
591
+ def test_preserves_object_identity(self: Self, /) -> None:
592
+ slot: MutableDictSlot[Any, Any]
593
+ slot = MutableDictSlot({"a": 1})
594
+ original_id = id(slot)
595
+ slot |= {"b": 2}
596
+ self.assertEqual(id(slot), original_id)
597
+
598
+ def test_accepts_iterable_of_pairs(self: Self, /) -> None:
599
+ slot: MutableDictSlot[Any, Any]
600
+ slot = MutableDictSlot({"a": 1})
601
+ slot |= [("b", 2), ("c", 3)]
602
+ self.assertEqual(slot, {"a": 1, "b": 2, "c": 3})
603
+
604
+
605
+ class TestOperatorIteration(unittest.TestCase):
606
+ def test_iterates_over_keys(self: Self, /) -> None:
607
+ slot: MutableDictSlot[str, int]
608
+ slot = MutableDictSlot({"a": 1, "b": 2})
609
+ self.assertEqual(list(slot), ["a", "b"])
610
+
611
+ def test_empty_slot_has_no_iterations(self: Self, /) -> None:
612
+ slot: MutableDictSlot[Any, Any]
613
+ slot = MutableDictSlot()
614
+ self.assertEqual(list(slot), [])
615
+
616
+ def test_iteration_preserves_insertion_order(self: Self, /) -> None:
617
+ slot: MutableDictSlot[Any, Any]
618
+ slot = MutableDictSlot()
619
+ slot["third"] = 3
620
+ slot["first"] = 1
621
+ slot["second"] = 2
622
+ self.assertEqual(list(slot), ["third", "first", "second"])
623
+
624
+ def test_size_change_during_iteration_raises_runtime_error(
625
+ self: Self, /
626
+ ) -> None:
627
+ init: list[tuple[str, int]]
628
+ x: dict[str, int]
629
+ x_iter: abc.Iterator[str]
630
+ x_next: str
631
+ y: MutableDictSlot[str, int]
632
+ y_iter: abc.Iterator[str]
633
+ y_next: str
634
+ init = [("a", 1), ("b", 2)]
635
+ x = dict(init)
636
+ y = MutableDictSlot(init)
637
+ x_iter = iter(x)
638
+ y_iter = iter(y)
639
+ x_next = next(x_iter)
640
+ y_next = next(y_iter)
641
+ self.assertEqual(x_next, y_next)
642
+ x["c"] = 3
643
+ y["c"] = 3
644
+ self.assertListEqual(list(x), list(y))
645
+ self.assertListEqual(list("abc"), list(y))
646
+
647
+
648
+ class TestOperatorReversed(unittest.TestCase):
649
+ def test_reverses_key_order(self: Self, /) -> None:
650
+ slot: MutableDictSlot[Any, Any]
651
+ slot = MutableDictSlot({"a": 1, "b": 2, "c": 3})
652
+ self.assertEqual(list(reversed(slot)), ["c", "b", "a"])
653
+
654
+ def test_empty_slot_reverses_to_empty(self: Self, /) -> None:
655
+ slot: MutableDictSlot[Any, Any]
656
+ slot = MutableDictSlot()
657
+ self.assertEqual(list(reversed(slot)), [])
658
+
659
+ def test_single_key(self: Self, /) -> None:
660
+ slot: MutableDictSlot[Any, Any]
661
+ slot = MutableDictSlot({"a": 1})
662
+ self.assertEqual(list(reversed(slot)), ["a"])
663
+
664
+ def test_replacing_value_does_not_change_order(self: Self, /) -> None:
665
+ slot: MutableDictSlot[Any, Any]
666
+ slot = MutableDictSlot({"a": 1, "b": 2})
667
+ slot["a"] = 3
668
+ self.assertEqual(list(reversed(slot)), ["b", "a"])
669
+
670
+
671
+ class TestOperatorLength(unittest.TestCase):
672
+ def test_empty_length(self: Self, /) -> None:
673
+ self.assertEqual(len(MutableDictSlot()), 0)
674
+
675
+ def test_nonempty_length(self: Self, /) -> None:
676
+ x: MutableDictSlot[str, int]
677
+ x = MutableDictSlot({"a": 1, "b": 2})
678
+ self.assertEqual(len(x), 2)
679
+
680
+ def test_replacing_value_does_not_change_length(self: Self, /) -> None:
681
+ slot: MutableDictSlot[Any, Any]
682
+ slot = MutableDictSlot({"a": 1})
683
+ slot["a"] = 2
684
+ self.assertEqual(len(slot), 1)
685
+
686
+ def test_adding_and_deleting_changes_length(self: Self, /) -> None:
687
+ slot: MutableDictSlot[Any, Any]
688
+ slot = MutableDictSlot()
689
+ slot["a"] = 1
690
+ slot["b"] = 2
691
+ del slot["a"]
692
+ self.assertEqual(len(slot), 1)
693
+
694
+
695
+ class TestOperatorTruthiness(unittest.TestCase):
696
+ def test_empty_slot_is_false(self: Self, /) -> None:
697
+ self.assertFalse(MutableDictSlot())
698
+
699
+ def test_nonempty_slot_is_true(self: Self, /) -> None:
700
+ self.assertTrue(MutableDictSlot({"a": 1}))
701
+
702
+ def test_false_value_still_makes_slot_true(self: Self, /) -> None:
703
+ self.assertTrue(MutableDictSlot({"a": False}))
704
+
705
+ def test_deleting_last_key_makes_slot_false(self: Self, /) -> None:
706
+ slot: MutableDictSlot[Any, Any]
707
+ slot = MutableDictSlot({"a": 1})
708
+ del slot["a"]
709
+ self.assertFalse(slot)
710
+
711
+
403
712
  if __name__ == "__main__":
404
713
  unittest.main()