datahold 4.0.0.dev28__tar.gz → 4.0.0.dev30__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.dev28 → datahold-4.0.0.dev30}/PKG-INFO +1 -1
  2. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/pyproject.toml +1 -1
  3. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/src/datahold/__init__.py +101 -75
  4. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/src/datahold.egg-info/PKG-INFO +1 -1
  5. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/src/datahold.egg-info/SOURCES.txt +1 -0
  6. datahold-4.0.0.dev30/tests/test_FrozenDictSlot.py +271 -0
  7. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_FrozenSetSlot.py +243 -0
  8. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_MutableDictSlot.py +1 -2
  9. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/testdata.toml +4 -0
  10. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/LICENSE.txt +0 -0
  11. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/MANIFEST.in +0 -0
  12. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/README.rst +0 -0
  13. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/docs/v1.1.rst +0 -0
  14. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/docs/v1.2.rst +0 -0
  15. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/docs/v2.0.rst +0 -0
  16. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/docs/v2.1.rst +0 -0
  17. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/docs/v2.2.rst +0 -0
  18. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/docs/v2.3.rst +0 -0
  19. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/docs/v3.0.rst +0 -0
  20. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/docs/v4.0.rst +0 -0
  21. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/run_tests.py +0 -0
  22. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/setup.cfg +0 -0
  23. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/src/datahold/py.typed +0 -0
  24. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/src/datahold.egg-info/dependency_links.txt +0 -0
  25. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/src/datahold.egg-info/requires.txt +0 -0
  26. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/src/datahold.egg-info/top_level.txt +0 -0
  27. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_FrozenListSlot.py +0 -0
  28. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_MutableListSlot.py +0 -0
  29. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_MutableSetSlot.py +0 -0
  30. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_abstractmethods.py +0 -0
  31. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_all.py +0 -0
  32. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_attributes.py +0 -0
  33. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_docs.py +0 -0
  34. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_generic.py +0 -0
  35. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_issubclass.py +0 -0
  36. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_metaclass.py +0 -0
  37. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_name.py +0 -0
  38. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_nested.py +0 -0
  39. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_sorted.py +0 -0
  40. {datahold-4.0.0.dev28 → datahold-4.0.0.dev30}/tests/test_variance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datahold
3
- Version: 4.0.0.dev28
3
+ Version: 4.0.0.dev30
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.dev28"
34
+ version = "4.0.0.dev30"
35
35
 
36
36
  [project.urls]
37
37
  Download = "https://pypi.org/project/datahold/#files"
@@ -9,6 +9,7 @@ __all__: list[str] = [
9
9
  "FrozenDictSlot",
10
10
  "FrozenListLike",
11
11
  "FrozenListSlot",
12
+ "FrozenObjectLike",
12
13
  "FrozenObjectSlot",
13
14
  "FrozenSetLike",
14
15
  "FrozenSetSlot",
@@ -36,15 +37,7 @@ from abc import ABCMeta, abstractmethod
36
37
  from collections import abc
37
38
  from contextlib import contextmanager
38
39
  from types import NotImplementedType, TracebackType
39
- from typing import (
40
- Any,
41
- Never,
42
- Optional,
43
- Protocol,
44
- Self,
45
- SupportsIndex,
46
- overload,
47
- )
40
+ from typing import Any, Never, Protocol, Self, SupportsIndex, overload
48
41
 
49
42
  import setdoc
50
43
  from frozendict import frozendict
@@ -86,7 +79,7 @@ class Collection_Frozen[Item](
86
79
  def __contains__(self: Self, other: Never, /) -> bool: ...
87
80
 
88
81
 
89
- class MutableSet_Mutable[Item: abc.Hashable](Protocol):
82
+ class SupportsAddAndDiscard[Item: abc.Hashable](Protocol):
90
83
  @setdoc.basic
91
84
  def add(self: Self, item: Item, /) -> object: ...
92
85
  @setdoc.basic
@@ -95,15 +88,15 @@ class MutableSet_Mutable[Item: abc.Hashable](Protocol):
95
88
 
96
89
  class Mapping_Frozen[Key, Value](Collection_Frozen[Key], Protocol):
97
90
  @setdoc.basic
98
- def __getitem__(self: Self, key: abc.Hashable, /) -> Value: ...
99
- class MutableMapping_Mutable[Key_, Value_](
91
+ def __getitem__(self: Self, key: Never, /) -> Value: ...
92
+ class SupportsDelitemAndSetitem[Key, Value](
100
93
  Protocol,
101
94
  ):
102
95
  @setdoc.basic
103
- def __delitem__(self: Self, key: Key_ | str, /) -> object: ...
96
+ def __delitem__(self: Self, key: Key | str, /) -> object: ...
104
97
  @setdoc.basic
105
98
  def __setitem__(
106
- self: Self, key: Key_ | str, value: Optional[Value_], /
99
+ self: Self, key: Key | str, value: Value | None, /
107
100
  ) -> object: ...
108
101
  class Sequence_Frozen[Item](
109
102
  Collection_Frozen[Item],
@@ -169,16 +162,16 @@ class MutableSequence_Mutable[Item](Protocol):
169
162
 
170
163
  ### TYPE ALIASES ###
171
164
 
172
- type Dict[Key, Value] = dict[Key | str, Optional[Value]]
165
+ type Dict[Key, Value] = dict[Key | str, Value | None]
173
166
 
174
- type DictInit[Key, Value] = (
175
- SupportsKeysAndGetitem[Key | str, Optional[Value]]
176
- | abc.Iterable[tuple[Key | str, Optional[Value]]]
167
+ type Dict_Init[Key, Value] = (
168
+ SupportsKeysAndGetitem[Key | str, Value | None]
169
+ | abc.Iterable[tuple[Key | str, Value | None]]
177
170
  )
178
171
 
179
- type FrozenDict[Key, Value] = frozendict[Key | str, Optional[Value]]
172
+ type FrozenDict[Key, Value] = frozendict[Key | str, Value | None]
180
173
 
181
- type Slice[Index] = slice[Optional[Index], Optional[Index], Optional[Index]]
174
+ type Slice[Index] = slice[Index | None, Index | None, Index | None]
182
175
 
183
176
 
184
177
  ### OBJECT ###
@@ -205,6 +198,19 @@ class MutableObject[Frozen, Mutable](Object[Frozen]):
205
198
  ### OBJECT-LIKE ###
206
199
 
207
200
 
201
+ class FrozenObjectLike[Frozen: abc.Hashable](
202
+ Object[Frozen],
203
+ abc.Hashable,
204
+ ):
205
+ """Provide abc for custom frozen object-like."""
206
+
207
+ __slots__ = ()
208
+
209
+ @setdoc.basic
210
+ def __hash__(self: Self, /) -> int:
211
+ return hash(self.__frozen__())
212
+
213
+
208
214
  class MutableObjectLike[Frozen, Mutable](
209
215
  MutableObject[Frozen, Mutable],
210
216
  ):
@@ -232,7 +238,8 @@ class ObjectSlot[Frozen](
232
238
  _slot: Frozen
233
239
 
234
240
 
235
- class FrozenObjectSlot[Frozen](
241
+ class FrozenObjectSlot[Frozen: abc.Hashable](
242
+ FrozenObjectLike[Frozen],
236
243
  ObjectSlot[Frozen],
237
244
  ):
238
245
  """Provide slotted abc for custom frozen object-like."""
@@ -284,7 +291,7 @@ class Set[Item: abc.Hashable](Collection[Item], abc.Set[Item]):
284
291
  class MutableSet[Item: abc.Hashable](
285
292
  Set[Item],
286
293
  abc.MutableSet[Item],
287
- MutableObject[Collection_Frozen[Item], MutableSet_Mutable[Item]],
294
+ MutableObject[Collection_Frozen[Item], SupportsAddAndDiscard[Item]],
288
295
  ):
289
296
  """Provide abc for custom mutable set."""
290
297
 
@@ -314,7 +321,7 @@ class SetLike[Item: abc.Hashable](Set[Item]):
314
321
  def __frozen__(self: Self, /) -> frozenset[Item]: ...
315
322
  @abstractmethod
316
323
  @setdoc.basic
317
- def __init__(self: Self, data: abc.Iterable[Item] = (), /) -> None: ...
324
+ def __init__(self: Self, other: abc.Iterable[Item] = (), /) -> None: ...
318
325
  @setdoc.basic
319
326
  def difference(self: Self, /, *others: abc.Iterable[abc.Hashable]) -> Self:
320
327
  return type(self)(self.__frozen__().difference(*others))
@@ -342,15 +349,14 @@ class SetLike[Item: abc.Hashable](Set[Item]):
342
349
  return type(self)(self.__frozen__().union(*others))
343
350
 
344
351
 
345
- class FrozenSetLike[Item: abc.Hashable](SetLike[Item], abc.Hashable):
352
+ class FrozenSetLike[Item: abc.Hashable](
353
+ FrozenObjectLike[frozenset[Item]],
354
+ SetLike[Item],
355
+ ):
346
356
  """Provide abc for custom frozen set-like."""
347
357
 
348
358
  __slots__ = ()
349
359
 
350
- @setdoc.basic
351
- def __hash__(self: Self, /) -> int:
352
- return hash(self.__frozen__())
353
-
354
360
 
355
361
  class MutableSetLike[Item: abc.Hashable](
356
362
  SetLike[Item],
@@ -367,9 +373,9 @@ class MutableSetLike[Item: abc.Hashable](
367
373
  return frozenset(mutable)
368
374
 
369
375
  @setdoc.basic
370
- def __init__(self: Self, data: abc.Iterable[Item] = (), /) -> None:
376
+ def __init__(self: Self, other: abc.Iterable[Item] = (), /) -> None:
371
377
  with self.__mutate__() as mutable:
372
- mutable.update(data)
378
+ mutable.update(other)
373
379
 
374
380
  @setdoc.basic
375
381
  def difference_update(
@@ -410,8 +416,8 @@ class FrozenSetSlot[Item](
410
416
  __slots__ = ()
411
417
 
412
418
  @setdoc.basic
413
- def __init__(self: Self, data: abc.Iterable[Item] = (), /) -> None:
414
- self._slot = frozenset(data)
419
+ def __init__(self: Self, other: abc.Iterable[Item] = (), /) -> None:
420
+ self._slot = frozenset(other)
415
421
 
416
422
 
417
423
  class MutableSetSlot[Item](
@@ -428,10 +434,10 @@ class MutableSetSlot[Item](
428
434
  self: Self,
429
435
  /,
430
436
  ) -> abc.Generator[set[Item], None, None]:
431
- slot: set[Item]
432
- slot = set(getattr(self, "_slot", ()))
433
- yield slot
434
- self._slot = frozenset(slot)
437
+ mutable: set[Item]
438
+ mutable = set(getattr(self, "_slot", ()))
439
+ yield mutable
440
+ self._slot = frozenset(mutable)
435
441
 
436
442
 
437
443
  ### MAPPING ###
@@ -449,14 +455,35 @@ class Mapping[Key: abc.Hashable, Value](
449
455
  @setdoc.basic
450
456
  def __frozen__(self: Self, /) -> Mapping_Frozen[Key, Value]: ...
451
457
  @setdoc.basic
452
- def __getitem__(self: Self, key: abc.Hashable, /) -> Value:
453
- return self.__frozen__()[key]
458
+ def __getitem__(self: Self, key: object, /) -> Value:
459
+ try:
460
+ return self.__frozen__()[key] # type: ignore[index]
461
+ except TypeError:
462
+ raise KeyError(key) from None
463
+
464
+ @overload
465
+ def get(self, key: object) -> Value | None: ...
466
+ @overload
467
+ def get[Value_](self, key: object, default: Value_) -> Value | Value_: ...
468
+ @setdoc.basic
469
+ def get[Value_](
470
+ self: Self, key: object, default: Value_ | None = None
471
+ ) -> Value | Value_ | None:
472
+ # this method is just as Mapping.get
473
+ # with the signature corrected
474
+ try:
475
+ return self[key]
476
+ except KeyError:
477
+ return default
454
478
 
455
479
 
456
480
  class MutableMapping[Key: abc.Hashable, Value](
457
481
  Mapping[Key, Value],
458
- abc.MutableMapping[Key | str, Optional[Value]],
459
- MutableObject[FrozenDict[Key, Value], Dict[Key, Value]],
482
+ abc.MutableMapping[Key | str, Value | None],
483
+ MutableObject[
484
+ Mapping_Frozen[Key | str, Value | None],
485
+ SupportsDelitemAndSetitem[Key | str, Value | None],
486
+ ],
460
487
  ):
461
488
  """Provide abc for custom mutable mapping."""
462
489
 
@@ -469,7 +496,7 @@ class MutableMapping[Key: abc.Hashable, Value](
469
496
 
470
497
  @setdoc.basic
471
498
  def __setitem__(
472
- self: Self, key: Key | str, value: Optional[Value], /
499
+ self: Self, key: Key | str, value: Value | None, /
473
500
  ) -> None:
474
501
  with self.__mutate__() as mutable:
475
502
  mutable[key] = value
@@ -479,7 +506,7 @@ class MutableMapping[Key: abc.Hashable, Value](
479
506
 
480
507
 
481
508
  class DictLike[Key: abc.Hashable, Value](
482
- Mapping[Key | str, Optional[Value]],
509
+ Mapping[Key | str, Value | None],
483
510
  ):
484
511
  """Provide abc for custom dict-like."""
485
512
 
@@ -487,21 +514,20 @@ class DictLike[Key: abc.Hashable, Value](
487
514
 
488
515
  @abstractmethod
489
516
  @setdoc.basic
490
- def __frozen__(self: Self, /) -> FrozenDict[Key, Value]: # type: ignore[override]
491
- ...
517
+ def __frozen__(self: Self, /) -> FrozenDict[Key, Value]: ...
492
518
 
493
519
  @abstractmethod
494
520
  @setdoc.basic
495
521
  def __init__(
496
522
  self: Self,
497
- data: DictInit[Key, Value] = (),
523
+ other: Dict_Init[Key, Value] = (),
498
524
  /,
499
525
  **kwargs: Value,
500
526
  ): ...
501
527
  @setdoc.basic
502
528
  def __or__[Key_, Value_](
503
529
  self: Self,
504
- other: DictInit[Key_, Value_],
530
+ other: Dict_Init[Key_, Value_],
505
531
  /,
506
532
  ) -> DictLike[Key | Key_, Value | Value_]:
507
533
  data: FrozenDict[Key, Value]
@@ -518,24 +544,20 @@ class DictLike[Key: abc.Hashable, Value](
518
544
  def fromkeys(
519
545
  cls: type[Self],
520
546
  iterable: abc.Iterable[Key | str],
521
- value: Optional[Value] = None,
547
+ value: Value | None = None,
522
548
  /,
523
549
  ) -> Self:
524
550
  return cls(dict.fromkeys(iterable, value))
525
551
 
526
552
 
527
553
  class FrozenDictLike[Key: abc.Hashable, Value](
554
+ FrozenObjectLike[FrozenDict[Key, Value]],
528
555
  DictLike[Key, Value],
529
- abc.Hashable,
530
556
  ):
531
557
  """Provide abc for custom frozen dict-like."""
532
558
 
533
559
  __slots__ = ()
534
560
 
535
- @setdoc.basic
536
- def __hash__(self: Self, /) -> int:
537
- return hash(self.__frozen__())
538
-
539
561
 
540
562
  class MutableDictLike[Key: abc.Hashable, Value](
541
563
  DictLike[Key, Value],
@@ -547,7 +569,7 @@ class MutableDictLike[Key: abc.Hashable, Value](
547
569
  __slots__ = ()
548
570
 
549
571
  @setdoc.basic
550
- def __frozen__( # type: ignore[override]
572
+ def __frozen__(
551
573
  self: Self,
552
574
  /,
553
575
  ) -> FrozenDict[Key, Value]:
@@ -557,16 +579,16 @@ class MutableDictLike[Key: abc.Hashable, Value](
557
579
  @setdoc.basic
558
580
  def __init__(
559
581
  self: Self,
560
- data: DictInit[Key, Value] = (),
582
+ other: Dict_Init[Key, Value] = (),
561
583
  /,
562
- **kwargs: Optional[Value],
584
+ **kwargs: Value | None,
563
585
  ):
564
- self.update(data, **kwargs)
586
+ self.update(other, **kwargs)
565
587
 
566
588
  @setdoc.basic
567
589
  def __ior__( # type: ignore[override]
568
590
  self: Self,
569
- other: DictInit[Key, Value],
591
+ other: Dict_Init[Key, Value],
570
592
  /,
571
593
  ) -> Self:
572
594
  with self.__mutate__() as mutable:
@@ -574,7 +596,7 @@ class MutableDictLike[Key: abc.Hashable, Value](
574
596
  return self
575
597
 
576
598
  @setdoc.basic
577
- def popitem(self: Self, /) -> tuple[Key | str, Optional[Value]]:
599
+ def popitem(self: Self, /) -> tuple[Key | str, Value | None]:
578
600
  # for most dict d
579
601
  # dict.popitem(d) and collections.abc.MutableMapping.popitem(d)
580
602
  # behave differently
@@ -585,7 +607,7 @@ class MutableDictLike[Key: abc.Hashable, Value](
585
607
  ### DICT-SLOT ###
586
608
 
587
609
 
588
- class FrozenDictSlot[Key: abc.Hashable, Value]( # type: ignore[misc]
610
+ class FrozenDictSlot[Key: abc.Hashable, Value](
589
611
  FrozenObjectSlot[FrozenDict[Key, Value]],
590
612
  FrozenDictLike[Key, Value],
591
613
  ):
@@ -596,11 +618,11 @@ class FrozenDictSlot[Key: abc.Hashable, Value]( # type: ignore[misc]
596
618
  @setdoc.basic
597
619
  def __init__(
598
620
  self: Self,
599
- data: DictInit[Key, Value] = (),
621
+ other: Dict_Init[Key, Value] = (),
600
622
  /,
601
- **kwargs: Optional[Value],
623
+ **kwargs: Value | None,
602
624
  ) -> None:
603
- self._slot = frozendict(data, **kwargs) # type: ignore[arg-type]
625
+ self._slot = frozendict(other, **kwargs) # type: ignore[arg-type]
604
626
 
605
627
 
606
628
  class MutableDictSlot[Key: abc.Hashable, Value](
@@ -617,10 +639,10 @@ class MutableDictSlot[Key: abc.Hashable, Value](
617
639
  self: Self,
618
640
  /,
619
641
  ) -> abc.Generator[Dict[Key, Value], None, None]:
620
- slot: Dict[Key, Value]
621
- slot = dict(getattr(self, "_slot", ()))
622
- yield slot
623
- self._slot = frozendict(slot)
642
+ mutable: Dict[Key, Value]
643
+ mutable = dict(getattr(self, "_slot", ()))
644
+ yield mutable
645
+ self._slot = frozendict(mutable)
624
646
 
625
647
 
626
648
  ### SEQUENCE ###
@@ -769,7 +791,7 @@ class ListLike[Item](Sequence[Item]):
769
791
 
770
792
  @abstractmethod
771
793
  @setdoc.basic
772
- def __init__(self: Self, data: abc.Iterable[Item] = (), /) -> None: ...
794
+ def __init__(self: Self, other: abc.Iterable[Item] = (), /) -> None: ...
773
795
 
774
796
  @setdoc.basic
775
797
  def __le__(self: Self, other: ListLike[Any], /) -> bool:
@@ -799,7 +821,10 @@ class ListLike[Item](Sequence[Item]):
799
821
  return f"{type(self).__name__}({list(self)!r})"
800
822
 
801
823
 
802
- class FrozenListLike[Item](ListLike[Item], abc.Hashable):
824
+ class FrozenListLike[Item](
825
+ FrozenObjectLike[tuple[Item, ...]],
826
+ ListLike[Item],
827
+ ):
803
828
  """Provide abc for custom frozen list-like."""
804
829
 
805
830
  __slots__ = ()
@@ -843,9 +868,9 @@ class MutableListLike[Item](
843
868
  return self
844
869
 
845
870
  @setdoc.basic
846
- def __init__(self: Self, data: abc.Iterable[Item] = (), /) -> None:
871
+ def __init__(self: Self, other: abc.Iterable[Item] = (), /) -> None:
847
872
  with self.__mutate__() as mutable:
848
- mutable.extend(data)
873
+ mutable.extend(other)
849
874
 
850
875
  @overload
851
876
  @setdoc.basic
@@ -894,8 +919,8 @@ class FrozenListSlot[Item](
894
919
  __slots__ = ()
895
920
 
896
921
  @setdoc.basic
897
- def __init__(self: Self, data: abc.Iterable[Item] = (), /) -> None:
898
- self._slot = tuple(data)
922
+ def __init__(self: Self, other: abc.Iterable[Item] = (), /) -> None:
923
+ self._slot = tuple(other)
899
924
 
900
925
 
901
926
  class MutableListSlot[Item](
@@ -909,6 +934,7 @@ class MutableListSlot[Item](
909
934
  @contextmanager
910
935
  @setdoc.basic
911
936
  def __mutate__(self: Self, /) -> abc.Generator[list[Item], None, None]:
912
- slot = list(getattr(self, "_slot", ()))
913
- yield slot
914
- self._slot = tuple(slot)
937
+ mutable: list[Item]
938
+ mutable = list(getattr(self, "_slot", ()))
939
+ yield mutable
940
+ self._slot = tuple(mutable)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datahold
3
- Version: 4.0.0.dev28
3
+ Version: 4.0.0.dev30
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
@@ -19,6 +19,7 @@ src/datahold.egg-info/SOURCES.txt
19
19
  src/datahold.egg-info/dependency_links.txt
20
20
  src/datahold.egg-info/requires.txt
21
21
  src/datahold.egg-info/top_level.txt
22
+ tests/test_FrozenDictSlot.py
22
23
  tests/test_FrozenListSlot.py
23
24
  tests/test_FrozenSetSlot.py
24
25
  tests/test_MutableDictSlot.py
@@ -0,0 +1,271 @@
1
+ import unittest
2
+ from collections import abc
3
+ from typing import Any, Self
4
+
5
+ from datahold import FrozenDictSlot
6
+
7
+
8
+ class TestFromKeysMethod(unittest.TestCase):
9
+ def test_fromkeys_uses_none_by_default(self: Self) -> None:
10
+ result: Any = FrozenDictSlot.fromkeys(["a", "b", "c"])
11
+
12
+ self.assertEqual(result, {"a": None, "b": None, "c": None})
13
+
14
+ def test_fromkeys_uses_provided_value(self: Self) -> None:
15
+ result: Any = FrozenDictSlot.fromkeys(["a", "b"], 10)
16
+
17
+ self.assertEqual(result, {"a": 10, "b": 10})
18
+
19
+ def test_fromkeys_uses_same_value_object_for_every_key(self: Self) -> None:
20
+ value: Any = []
21
+ result: Any = FrozenDictSlot.fromkeys(["a", "b"], value)
22
+
23
+ self.assertIs(result["a"], value)
24
+ self.assertIs(result["b"], value)
25
+
26
+ def test_fromkeys_removes_duplicate_keys(self: Self) -> None:
27
+ result: Any = FrozenDictSlot.fromkeys(
28
+ ["a", "b", "a", "c", "b"],
29
+ 1,
30
+ )
31
+
32
+ self.assertEqual(list(result.keys()), ["a", "b", "c"])
33
+ self.assertEqual(result, {"a": 1, "b": 1, "c": 1})
34
+
35
+ def test_fromkeys_accepts_generator(self: Self) -> None:
36
+ keys: Any = (str(number) for number in range(3))
37
+ result: Any = FrozenDictSlot.fromkeys(keys, "value")
38
+
39
+ self.assertEqual(
40
+ result,
41
+ {"0": "value", "1": "value", "2": "value"},
42
+ )
43
+
44
+ def test_fromkeys_accepts_empty_iterable(self: Self) -> None:
45
+ self.assertEqual(FrozenDictSlot.fromkeys([], 1), {})
46
+
47
+ def test_fromkeys_returns_frozen_dict_slot(self: Self) -> None:
48
+ result: Any = FrozenDictSlot.fromkeys(["a"], 1)
49
+
50
+ self.assertIsInstance(result, FrozenDictSlot)
51
+
52
+ def test_fromkeys_rejects_unhashable_keys(self: Self) -> None:
53
+ with self.assertRaises(TypeError):
54
+ FrozenDictSlot.fromkeys([["unhashable"]], 1) # type: ignore
55
+
56
+
57
+ class TestGetMethod(unittest.TestCase):
58
+ def test_get_returns_existing_value(self: Self) -> None:
59
+ mapping: Any = FrozenDictSlot({"a": 1, "b": 2})
60
+
61
+ self.assertEqual(mapping.get("a"), 1)
62
+
63
+ def test_get_returns_none_for_missing_key(self: Self) -> None:
64
+ mapping: Any = FrozenDictSlot({"a": 1})
65
+
66
+ self.assertIsNone(mapping.get("missing"))
67
+
68
+ def test_get_returns_default_for_missing_key(self: Self) -> None:
69
+ mapping: Any = FrozenDictSlot({"a": 1})
70
+
71
+ self.assertEqual(mapping.get("missing", 20), 20)
72
+
73
+ def test_get_returns_existing_value_instead_of_default(self: Self) -> None:
74
+ mapping: Any = FrozenDictSlot({"a": 1})
75
+
76
+ self.assertEqual(mapping.get("a", 20), 1)
77
+
78
+ def test_get_returns_exact_default_object(self: Self) -> None:
79
+ default: Any = []
80
+ mapping: Any = FrozenDictSlot()
81
+
82
+ self.assertIs(mapping.get("missing", default), default)
83
+
84
+ def test_get_accepts_different_hashable_key_types(self: Self) -> None:
85
+ mapping: FrozenDictSlot[abc.Hashable, str]
86
+ mapping = FrozenDictSlot(
87
+ { # type: ignore
88
+ 1: "integer",
89
+ (2, 3): "tuple",
90
+ None: "none",
91
+ }
92
+ )
93
+
94
+ self.assertEqual(mapping.get(1), "integer")
95
+ self.assertEqual(mapping.get((2, 3)), "tuple")
96
+ self.assertEqual(mapping.get(None), "none")
97
+
98
+ def test_get_rejects_unhashable_key(self: Self) -> None:
99
+ self.assertIsNone(FrozenDictSlot().get([]))
100
+
101
+ def test_get_rejects_too_many_arguments(self: Self) -> None:
102
+ with self.assertRaises(TypeError):
103
+ FrozenDictSlot().get("a", 1, 2) # type: ignore[call-overload]
104
+
105
+
106
+ class TestItemsMethod(unittest.TestCase):
107
+ def test_items_contains_all_key_value_pairs(self: Self) -> None:
108
+ mapping: Any = FrozenDictSlot({"a": 1, "b": 2})
109
+
110
+ self.assertEqual(
111
+ set(mapping.items()),
112
+ {("a", 1), ("b", 2)},
113
+ )
114
+
115
+ def test_items_of_empty_mapping_is_empty(self: Self) -> None:
116
+ self.assertEqual(list(FrozenDictSlot().items()), [])
117
+
118
+ def test_items_preserves_insertion_order(self: Self) -> None:
119
+ mapping: Any = FrozenDictSlot(
120
+ [
121
+ ("third", 3),
122
+ ("first", 1),
123
+ ("second", 2),
124
+ ]
125
+ )
126
+
127
+ self.assertEqual(
128
+ list(mapping.items()),
129
+ [("third", 3), ("first", 1), ("second", 2)],
130
+ )
131
+
132
+ def test_items_supports_membership_testing(self: Self) -> None:
133
+ items: Any = FrozenDictSlot({"a": 1, "b": 2}).items()
134
+
135
+ self.assertIn(("a", 1), items)
136
+ self.assertNotIn(("a", 2), items)
137
+
138
+ def test_items_has_correct_length(self: Self) -> None:
139
+ items: Any = FrozenDictSlot(
140
+ {
141
+ "a": 1,
142
+ "b": 2,
143
+ "c": 3,
144
+ }
145
+ ).items()
146
+
147
+ self.assertEqual(len(items), 3)
148
+
149
+ def test_items_can_be_iterated_multiple_times(self: Self) -> None:
150
+ items: Any = FrozenDictSlot({"a": 1, "b": 2}).items()
151
+
152
+ self.assertEqual(list(items), [("a", 1), ("b", 2)])
153
+ self.assertEqual(list(items), [("a", 1), ("b", 2)])
154
+
155
+ def test_items_rejects_arguments(self: Self) -> None:
156
+ with self.assertRaises(TypeError):
157
+ FrozenDictSlot({"a": 1}).items("unexpected") # type: ignore[call-arg]
158
+
159
+
160
+ class TestKeysMethod(unittest.TestCase):
161
+ def test_keys_contains_all_keys(self: Self) -> None:
162
+ mapping: Any = FrozenDictSlot({"a": 1, "b": 2, "c": 3})
163
+
164
+ self.assertEqual(set(mapping.keys()), {"a", "b", "c"})
165
+
166
+ def test_keys_of_empty_mapping_is_empty(self: Self) -> None:
167
+ self.assertEqual(list(FrozenDictSlot().keys()), [])
168
+
169
+ def test_keys_preserves_insertion_order(self: Self) -> None:
170
+ mapping: Any = FrozenDictSlot(
171
+ [
172
+ ("third", 3),
173
+ ("first", 1),
174
+ ("second", 2),
175
+ ]
176
+ )
177
+
178
+ self.assertEqual(
179
+ list(mapping.keys()),
180
+ ["third", "first", "second"],
181
+ )
182
+
183
+ def test_keys_supports_membership_testing(self: Self) -> None:
184
+ keys: Any = FrozenDictSlot({"a": 1, "b": 2}).keys()
185
+
186
+ self.assertIn("a", keys)
187
+ self.assertNotIn("missing", keys)
188
+
189
+ def test_keys_has_correct_length(self: Self) -> None:
190
+ keys: Any = FrozenDictSlot(
191
+ {
192
+ "a": 1,
193
+ "b": 2,
194
+ "c": 3,
195
+ }
196
+ ).keys()
197
+
198
+ self.assertEqual(len(keys), 3)
199
+
200
+ def test_keys_supports_set_operations(self: Self) -> None:
201
+ keys: Any = FrozenDictSlot({"a": 1, "b": 2}).keys()
202
+
203
+ self.assertEqual(keys & {"b", "c"}, {"b"})
204
+ self.assertEqual(keys | {"c"}, {"a", "b", "c"})
205
+
206
+ def test_keys_rejects_arguments(self: Self) -> None:
207
+ with self.assertRaises(TypeError):
208
+ FrozenDictSlot({"a": 1}).keys("unexpected") # type: ignore[call-arg]
209
+
210
+
211
+ class TestValuesMethod(unittest.TestCase):
212
+ def test_values_contains_all_values(self: Self) -> None:
213
+ mapping: Any = FrozenDictSlot({"a": 1, "b": 2, "c": 3})
214
+
215
+ self.assertEqual(list(mapping.values()), [1, 2, 3])
216
+
217
+ def test_values_of_empty_mapping_is_empty(self: Self) -> None:
218
+ self.assertEqual(list(FrozenDictSlot().values()), [])
219
+
220
+ def test_values_preserves_insertion_order(self: Self) -> None:
221
+ mapping: Any = FrozenDictSlot(
222
+ [
223
+ ("third", 3),
224
+ ("first", 1),
225
+ ("second", 2),
226
+ ]
227
+ )
228
+
229
+ self.assertEqual(list(mapping.values()), [3, 1, 2])
230
+
231
+ def test_values_preserves_duplicates(self: Self) -> None:
232
+ mapping: Any = FrozenDictSlot(
233
+ {
234
+ "a": 1,
235
+ "b": 1,
236
+ "c": 2,
237
+ }
238
+ )
239
+
240
+ self.assertEqual(list(mapping.values()), [1, 1, 2])
241
+
242
+ def test_values_supports_membership_testing(self: Self) -> None:
243
+ values: Any = FrozenDictSlot({"a": 1, "b": 2}).values()
244
+
245
+ self.assertIn(1, values)
246
+ self.assertNotIn(3, values)
247
+
248
+ def test_values_has_correct_length(self: Self) -> None:
249
+ values: Any = FrozenDictSlot(
250
+ {
251
+ "a": 1,
252
+ "b": 2,
253
+ "c": 3,
254
+ }
255
+ ).values()
256
+
257
+ self.assertEqual(len(values), 3)
258
+
259
+ def test_values_can_be_iterated_multiple_times(self: Self) -> None:
260
+ values: Any = FrozenDictSlot({"a": 1, "b": 2}).values()
261
+
262
+ self.assertEqual(list(values), [1, 2])
263
+ self.assertEqual(list(values), [1, 2])
264
+
265
+ def test_values_rejects_arguments(self: Self) -> None:
266
+ with self.assertRaises(TypeError):
267
+ FrozenDictSlot({"a": 1}).values("unexpected") # type: ignore[call-arg]
268
+
269
+
270
+ if __name__ == "__main__":
271
+ unittest.main()
@@ -229,5 +229,248 @@ class TestNonMembershipOperator(unittest.TestCase):
229
229
  self.assertFalse({1, 2} not in FrozenSetSlot({frozenset({1, 2})}))
230
230
 
231
231
 
232
+ class TestDifferenceMethod(unittest.TestCase):
233
+ def test_difference_with_overlapping_values(self: Self) -> None:
234
+ result = FrozenSetSlot({1, 2, 3}).difference({2, 3, 4})
235
+
236
+ self.assertEqual(result, frozenset({1}))
237
+
238
+ def test_difference_with_disjoint_values(self: Self) -> None:
239
+ result = FrozenSetSlot({1, 2}).difference({3, 4})
240
+
241
+ self.assertEqual(result, frozenset({1, 2}))
242
+
243
+ def test_difference_with_multiple_iterables(self: Self) -> None:
244
+ result = FrozenSetSlot({1, 2, 3, 4, 5}).difference(
245
+ {2, 3},
246
+ [4],
247
+ (5, 6),
248
+ )
249
+
250
+ self.assertEqual(result, frozenset({1}))
251
+
252
+ def test_difference_without_arguments(self: Self) -> None:
253
+ result = FrozenSetSlot({1, 2}).difference()
254
+
255
+ self.assertEqual(result, frozenset({1, 2}))
256
+
257
+ def test_difference_returns_frozen_set_slot(self: Self) -> None:
258
+ result = FrozenSetSlot({1, 2, 3}).difference({3})
259
+
260
+ self.assertIsInstance(result, FrozenSetSlot)
261
+
262
+ def test_difference_rejects_non_iterable(self: Self) -> None:
263
+ with self.assertRaises(TypeError):
264
+ FrozenSetSlot({1, 2}).difference(3) # type: ignore[arg-type]
265
+
266
+
267
+ class TestIntersectionMethod(unittest.TestCase):
268
+ def test_intersection_with_overlapping_values(self: Self) -> None:
269
+ result = FrozenSetSlot({1, 2, 3}).intersection({2, 3, 4})
270
+
271
+ self.assertEqual(result, frozenset({2, 3}))
272
+
273
+ def test_intersection_with_disjoint_values(self: Self) -> None:
274
+ result = FrozenSetSlot({1, 2}).intersection({3, 4})
275
+
276
+ self.assertEqual(result, frozenset())
277
+
278
+ def test_intersection_with_multiple_iterables(self: Self) -> None:
279
+ result = FrozenSetSlot({1, 2, 3, 4}).intersection(
280
+ [2, 3, 4, 5],
281
+ (3, 4, 6),
282
+ )
283
+
284
+ self.assertEqual(result, frozenset({3, 4}))
285
+
286
+ def test_intersection_without_arguments(self: Self) -> None:
287
+ result = FrozenSetSlot({1, 2}).intersection()
288
+
289
+ self.assertEqual(result, frozenset({1, 2}))
290
+
291
+ def test_intersection_returns_frozen_set_slot(self: Self) -> None:
292
+ result = FrozenSetSlot({1, 2, 3}).intersection({2, 3})
293
+
294
+ self.assertIsInstance(result, FrozenSetSlot)
295
+
296
+ def test_intersection_accepts_generator(self: Self) -> None:
297
+ values = (value for value in range(2, 5))
298
+ result = FrozenSetSlot({1, 2, 3}).intersection(values)
299
+
300
+ self.assertEqual(result, frozenset({2, 3}))
301
+
302
+
303
+ class TestIsDisjointMethod(unittest.TestCase):
304
+ def test_disjoint_iterables(self: Self) -> None:
305
+ result = FrozenSetSlot({1, 2}).isdisjoint({3, 4})
306
+
307
+ self.assertTrue(result)
308
+
309
+ def test_overlapping_iterables(self: Self) -> None:
310
+ result = FrozenSetSlot({1, 2, 3}).isdisjoint({3, 4})
311
+
312
+ self.assertFalse(result)
313
+
314
+ def test_empty_instance_is_disjoint(self: Self) -> None:
315
+ result = FrozenSetSlot().isdisjoint({1, 2})
316
+
317
+ self.assertTrue(result)
318
+
319
+ def test_is_disjoint_with_empty_iterable(self: Self) -> None:
320
+ result = FrozenSetSlot({1, 2}).isdisjoint([])
321
+
322
+ self.assertTrue(result)
323
+
324
+ def test_is_disjoint_accepts_generator(self: Self) -> None:
325
+ values = (value for value in range(3, 6))
326
+ result = FrozenSetSlot({1, 2, 3}).isdisjoint(values)
327
+
328
+ self.assertFalse(result)
329
+
330
+ def test_is_disjoint_rejects_non_iterable(self: Self) -> None:
331
+ with self.assertRaises(TypeError):
332
+ FrozenSetSlot({1, 2}).isdisjoint(3) # type: ignore[arg-type]
333
+
334
+
335
+ class TestIsSubsetMethod(unittest.TestCase):
336
+ def test_proper_subset(self: Self) -> None:
337
+ result = FrozenSetSlot({1, 2}).issubset({1, 2, 3})
338
+
339
+ self.assertTrue(result)
340
+
341
+ def test_equal_set_is_subset(self: Self) -> None:
342
+ result = FrozenSetSlot({1, 2}).issubset([2, 1])
343
+
344
+ self.assertTrue(result)
345
+
346
+ def test_non_subset(self: Self) -> None:
347
+ result = FrozenSetSlot({1, 2, 4}).issubset({1, 2, 3})
348
+
349
+ self.assertFalse(result)
350
+
351
+ def test_empty_instance_is_subset(self: Self) -> None:
352
+ result = FrozenSetSlot().issubset({1, 2})
353
+
354
+ self.assertTrue(result)
355
+
356
+ def test_nonempty_instance_is_not_subset_of_empty_iterable(
357
+ self: Self,
358
+ ) -> None:
359
+ result = FrozenSetSlot({1}).issubset([])
360
+
361
+ self.assertFalse(result)
362
+
363
+ def test_is_subset_accepts_generator(self: Self) -> None:
364
+ values = (value for value in range(1, 4))
365
+ result = FrozenSetSlot({1, 2}).issubset(values)
366
+
367
+ self.assertTrue(result)
368
+
369
+
370
+ class TestIsSupersetMethod(unittest.TestCase):
371
+ def test_proper_superset(self: Self) -> None:
372
+ result = FrozenSetSlot({1, 2, 3}).issuperset({1, 2})
373
+
374
+ self.assertTrue(result)
375
+
376
+ def test_equal_set_is_superset(self: Self) -> None:
377
+ result = FrozenSetSlot({1, 2}).issuperset([2, 1])
378
+
379
+ self.assertTrue(result)
380
+
381
+ def test_non_superset(self: Self) -> None:
382
+ result = FrozenSetSlot({1, 2}).issuperset({1, 2, 3})
383
+
384
+ self.assertFalse(result)
385
+
386
+ def test_every_instance_is_superset_of_empty_iterable(self: Self) -> None:
387
+ result = FrozenSetSlot({1, 2}).issuperset([])
388
+
389
+ self.assertTrue(result)
390
+
391
+ def test_empty_instance_is_not_superset_of_nonempty_iterable(
392
+ self: Self,
393
+ ) -> None:
394
+ result = FrozenSetSlot().issuperset({1})
395
+
396
+ self.assertFalse(result)
397
+
398
+ def test_is_superset_accepts_generator(self: Self) -> None:
399
+ values = (value for value in range(1, 3))
400
+ result = FrozenSetSlot({1, 2, 3}).issuperset(values)
401
+
402
+ self.assertTrue(result)
403
+
404
+
405
+ class TestSymmetricDifferenceMethod(unittest.TestCase):
406
+ def test_symmetric_difference_with_overlap(self: Self) -> None:
407
+ result = FrozenSetSlot({1, 2, 3}).symmetric_difference({3, 4})
408
+
409
+ self.assertEqual(result, frozenset({1, 2, 4}))
410
+
411
+ def test_symmetric_difference_with_disjoint_values(self: Self) -> None:
412
+ result = FrozenSetSlot({1, 2}).symmetric_difference([3, 4])
413
+
414
+ self.assertEqual(result, frozenset({1, 2, 3, 4}))
415
+
416
+ def test_symmetric_difference_with_equal_values(self: Self) -> None:
417
+ result = FrozenSetSlot({1, 2}).symmetric_difference((2, 1))
418
+
419
+ self.assertEqual(result, frozenset())
420
+
421
+ def test_symmetric_difference_with_empty_iterable(self: Self) -> None:
422
+ result = FrozenSetSlot({1, 2}).symmetric_difference([])
423
+
424
+ self.assertEqual(result, frozenset({1, 2}))
425
+
426
+ def test_symmetric_difference_returns_frozen_set_slot(self: Self) -> None:
427
+ result = FrozenSetSlot({1, 2}).symmetric_difference({2, 3})
428
+
429
+ self.assertIsInstance(result, FrozenSetSlot)
430
+
431
+ def test_symmetric_difference_rejects_multiple_arguments(
432
+ self: Self,
433
+ ) -> None:
434
+ with self.assertRaises(TypeError):
435
+ FrozenSetSlot({1, 2}).symmetric_difference({2}, {3}) # type: ignore[call-arg]
436
+
437
+
438
+ class TestUnionMethod(unittest.TestCase):
439
+ def test_union_with_overlapping_values(self: Self) -> None:
440
+ result = FrozenSetSlot({1, 2, 3}).union({3, 4})
441
+
442
+ self.assertEqual(result, frozenset({1, 2, 3, 4}))
443
+
444
+ def test_union_with_disjoint_values(self: Self) -> None:
445
+ result = FrozenSetSlot({1, 2}).union([3, 4])
446
+
447
+ self.assertEqual(result, frozenset({1, 2, 3, 4}))
448
+
449
+ def test_union_with_multiple_iterables(self: Self) -> None:
450
+ result = FrozenSetSlot({1}).union(
451
+ [2, 3],
452
+ (3, 4),
453
+ frozenset({5}),
454
+ )
455
+
456
+ self.assertEqual(result, frozenset({1, 2, 3, 4, 5}))
457
+
458
+ def test_union_without_arguments(self: Self) -> None:
459
+ result = FrozenSetSlot({1, 2}).union()
460
+
461
+ self.assertEqual(result, frozenset({1, 2}))
462
+
463
+ def test_union_returns_frozen_set_slot(self: Self) -> None:
464
+ result = FrozenSetSlot({1, 2}).union({3})
465
+
466
+ self.assertIsInstance(result, FrozenSetSlot)
467
+
468
+ def test_union_accepts_generator(self: Self) -> None:
469
+ values = (value for value in range(2, 5))
470
+ result = FrozenSetSlot({1, 2}).union(values)
471
+
472
+ self.assertEqual(result, frozenset({1, 2, 3, 4}))
473
+
474
+
232
475
  if __name__ == "__main__":
233
476
  unittest.main()
@@ -136,8 +136,7 @@ class TestGet(unittest.TestCase):
136
136
  def test_get_rejects_unhashable_key(self: Self) -> None:
137
137
  value: MutableDictSlot[Any, Any]
138
138
  value = MutableDictSlot({"a": 1})
139
- with self.assertRaises(TypeError):
140
- value.get([])
139
+ self.assertIsNone(value.get([]))
141
140
  self.assertEqual(value, {"a": 1})
142
141
 
143
142
 
@@ -141,6 +141,10 @@ args = [
141
141
  [datatypes.FrozenListSlot.varia]
142
142
  generic = 1
143
143
 
144
+ [datatypes.FrozenObjectLike.issubclass]
145
+ "collections.abc.Hashable" = true
146
+ "datahold.Object" = true
147
+
144
148
  [datatypes.FrozenObjectSlot.issubclass]
145
149
  "datahold.ObjectSlot" = true
146
150
 
File without changes