datahold 4.0.0.dev6__tar.gz → 4.0.0.dev7__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 (34) hide show
  1. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/PKG-INFO +1 -1
  2. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/pyproject.toml +1 -1
  3. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/src/datahold/__init__.py +31 -127
  4. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/src/datahold.egg-info/PKG-INFO +1 -1
  5. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/src/datahold.egg-info/SOURCES.txt +0 -4
  6. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/tests/test_FrozenHoldDict.py +56 -2
  7. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/tests/test_FrozenHoldList.py +55 -2
  8. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/tests/test_FrozenHoldSet.py +60 -3
  9. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/tests/test_HoldDict.py +49 -3
  10. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/tests/test_HoldList.py +48 -2
  11. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/tests/test_HoldSet.py +48 -1
  12. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/tests/test_testdata.py +23 -4
  13. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/tests/testdata.toml +11 -134
  14. datahold-4.0.0.dev6/tests/test_datahold_TestCopy.py +0 -81
  15. datahold-4.0.0.dev6/tests/test_datahold_TestDataAttribute.py +0 -46
  16. datahold-4.0.0.dev6/tests/test_datahold_TestFrozenMutability.py +0 -36
  17. datahold-4.0.0.dev6/tests/test_datahold_TestMutableBehavior.py +0 -33
  18. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/LICENSE.txt +0 -0
  19. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/MANIFEST.in +0 -0
  20. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/README.rst +0 -0
  21. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/docs/v1.1.rst +0 -0
  22. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/docs/v1.2.rst +0 -0
  23. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/docs/v2.0.rst +0 -0
  24. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/docs/v2.1.rst +0 -0
  25. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/docs/v2.2.rst +0 -0
  26. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/docs/v2.3.rst +0 -0
  27. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/docs/v3.0.rst +0 -0
  28. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/docs/v4.0.rst +0 -0
  29. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/run_tests.py +0 -0
  30. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/setup.cfg +0 -0
  31. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/src/datahold/py.typed +0 -0
  32. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/src/datahold.egg-info/dependency_links.txt +0 -0
  33. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/src/datahold.egg-info/requires.txt +0 -0
  34. {datahold-4.0.0.dev6 → datahold-4.0.0.dev7}/src/datahold.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datahold
3
- Version: 4.0.0.dev6
3
+ Version: 4.0.0.dev7
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
@@ -34,7 +34,7 @@ license-files = [
34
34
  name = "datahold"
35
35
  readme = "README.rst"
36
36
  requires-python = ">=3.12"
37
- version = "4.0.0.dev6"
37
+ version = "4.0.0.dev7"
38
38
 
39
39
  [project.urls]
40
40
  Download = "https://pypi.org/project/datahold/#files"
@@ -10,19 +10,13 @@ __all__: list[str] = [
10
10
  "BaseDataMapping",
11
11
  "BaseDataSequence",
12
12
  "BaseDataSet",
13
- "BaseHoldCollection",
13
+ "BaseHoldObject",
14
14
  "DataDict",
15
15
  "DataList",
16
16
  "DataSet",
17
17
  "FrozenDataDict",
18
18
  "FrozenDataList",
19
19
  "FrozenDataSet",
20
- "FrozenHoldDict",
21
- "FrozenHoldList",
22
- "FrozenHoldSet",
23
- "HoldDict",
24
- "HoldList",
25
- "HoldSet",
26
20
  ]
27
21
 
28
22
  import enum
@@ -45,6 +39,11 @@ from frozendict import frozendict
45
39
  ### UTILS ###
46
40
 
47
41
 
42
+ class Copyable(Protocol):
43
+ @setdoc.basic
44
+ def copy(self: Self) -> Self: ...
45
+
46
+
48
47
  class Missing(enum.Enum):
49
48
  missing = None
50
49
 
@@ -61,6 +60,23 @@ class SupportsKeysAndGetitem[Key, Value](Protocol):
61
60
  def keys(self: Self) -> abc.Iterable[Key]: ...
62
61
 
63
62
 
63
+ ### OBJECT ###
64
+
65
+
66
+ class BaseHoldObject[HoldData: Copyable]:
67
+ """Provide abc for usable classes with slots."""
68
+
69
+ __slots__ = ("_data",)
70
+
71
+ @setdoc.basic
72
+ def __fget__(self: Self) -> HoldData:
73
+ return self._data.copy()
74
+
75
+ @setdoc.basic
76
+ def __fset__(self: Self, data: HoldData, /) -> None:
77
+ self._data: HoldData = data
78
+
79
+
64
80
  ### COLLECTION ###
65
81
 
66
82
 
@@ -103,12 +119,6 @@ class BaseDataCollection[Item](
103
119
  def __fget__(self: Self) -> Data[Item]: ...
104
120
 
105
121
 
106
- class BaseHoldCollection[Item](BaseDataCollection[Item]):
107
- """Provide abc for usable classes with slots."""
108
-
109
- __slots__ = ("_data",)
110
-
111
-
112
122
  ### ABSTRACT SET ###
113
123
 
114
124
 
@@ -189,23 +199,6 @@ class FrozenDataSet[Item: abc.Hashable](BaseDataSet[Item], abc.Hashable):
189
199
  return hash(frozenset(self.__fget__()))
190
200
 
191
201
 
192
- class FrozenHoldSet[Item: abc.Hashable](
193
- FrozenDataSet[Item],
194
- BaseHoldCollection[Item],
195
- ):
196
- """Provide usable frozen set-like with slots."""
197
-
198
- __slots__ = ()
199
-
200
- @setdoc.basic
201
- def __fget__(self: Self) -> FrozenHoldSet.Data[Item]:
202
- return set(self._data)
203
-
204
- @setdoc.basic
205
- def __fset__(self: Self, data: FrozenHoldSet.Data[Item], /) -> None:
206
- self._data: FrozenHoldSet.Data[Item] = data
207
-
208
-
209
202
  class DataSet[Item: abc.Hashable](
210
203
  BaseDataSet[Item],
211
204
  abc.MutableSet[Item],
@@ -229,57 +222,40 @@ class DataSet[Item: abc.Hashable](
229
222
  *others: abc.Iterable[abc.Hashable],
230
223
  ) -> None:
231
224
  data: set[Item]
232
- data = set(self.__fget__())
225
+ data = self.__fget__()
233
226
  data.difference_update(*others)
234
- self.__fset__(set(data))
227
+ self.__fset__(data)
235
228
 
236
229
  @setdoc.basic
237
230
  def discard(self: Self, item: abc.Hashable, /) -> None:
238
231
  data: set[Item]
239
- data = set(self.__fget__())
232
+ data = self.__fget__()
240
233
  data.discard(item)
241
- self.__fset__(set(data))
234
+ self.__fset__(data)
242
235
 
243
236
  @setdoc.basic
244
237
  def intersection_update(
245
238
  self: Self, /, *others: abc.Iterable[abc.Hashable]
246
239
  ) -> None:
247
240
  data: set[Item]
248
- data = set(self.__fget__())
241
+ data = self.__fget__()
249
242
  data.intersection_update(*others)
250
- self.__fset__(set(data))
243
+ self.__fset__(data)
251
244
 
252
245
  @setdoc.basic
253
246
  def symmetric_difference_update(
254
247
  self: Self, other: abc.Iterable[Item], /
255
248
  ) -> None:
256
249
  data: set[Item]
257
- data = set(self.__fget__())
250
+ data = self.__fget__()
258
251
  data.symmetric_difference_update(other)
259
- self.__fset__(set(data))
252
+ self.__fset__(data)
260
253
 
261
254
  @setdoc.basic
262
255
  def update(self: Self, /, *others: abc.Iterable[Item]) -> None:
263
256
  self.__fset__(self.__fget__().union(*others))
264
257
 
265
258
 
266
- class HoldSet[Item: abc.Hashable](
267
- DataSet[Item],
268
- BaseHoldCollection[Item],
269
- ):
270
- """Provide usable mutable set-like with slots."""
271
-
272
- __slots__ = ()
273
-
274
- @setdoc.basic
275
- def __fget__(self: Self) -> HoldSet.Data[Item]:
276
- return set(self._data)
277
-
278
- @setdoc.basic
279
- def __fset__(self: Self, value: HoldSet.Data[Item], /) -> None:
280
- self._data: HoldSet.Data[Item] = value
281
-
282
-
283
259
  ### MAPPING ###
284
260
  class BaseDataMapping[Key, Value](
285
261
  BaseDataCollection[Key], abc.Mapping[Key, Value]
@@ -385,23 +361,6 @@ class FrozenDataDict[Key: abc.Hashable, Value](
385
361
  return hash(frozendict(self.__fget__()))
386
362
 
387
363
 
388
- class FrozenHoldDict[Key: abc.Hashable, Value](
389
- FrozenDataDict[Key, Value],
390
- BaseHoldCollection[Key | str],
391
- ):
392
- """Provide usable frozen dict-like with slots."""
393
-
394
- __slots__ = ()
395
-
396
- @setdoc.basic
397
- def __fget__(self: Self) -> FrozenHoldDict.Data[Key, Value]:
398
- return dict(self._data)
399
-
400
- @setdoc.basic
401
- def __fset__(self: Self, data: FrozenHoldDict.Data[Key, Value], /) -> None:
402
- self._data: FrozenHoldDict.Data[Key, Value] = data
403
-
404
-
405
364
  class DataDict[Key: abc.Hashable, Value](
406
365
  BaseDataDict[Key, Value],
407
366
  abc.MutableMapping[Key | str, Optional[Value]],
@@ -444,27 +403,6 @@ class DataDict[Key: abc.Hashable, Value](
444
403
  return type(self)(self)
445
404
 
446
405
 
447
- class HoldDict[Key: abc.Hashable, Value](
448
- DataDict[Key, Value],
449
- BaseHoldCollection[Key | str],
450
- ):
451
- """Provide usable mutable dict-like with slots."""
452
-
453
- __slots__ = ()
454
-
455
- @setdoc.basic
456
- def __fget__(self: Self) -> HoldDict.Data[Key, Value]:
457
- return dict(self._data)
458
-
459
- @setdoc.basic
460
- def __fset__(
461
- self: Self,
462
- value: HoldDict.Data[Key, Value],
463
- /,
464
- ) -> None:
465
- self._data: HoldDict.Data[Key, Value] = value
466
-
467
-
468
406
  ### SEQUENCE ###
469
407
 
470
408
 
@@ -627,23 +565,6 @@ class FrozenDataList[Item](
627
565
  return hash(tuple(self.__fget__()))
628
566
 
629
567
 
630
- class FrozenHoldList[Item](
631
- FrozenDataList[Item],
632
- BaseHoldCollection[Item],
633
- ):
634
- """Provide usable frozen list-like with slots."""
635
-
636
- __slots__ = ()
637
-
638
- @setdoc.basic
639
- def __fget__(self: Self) -> FrozenHoldList.Data[Item]:
640
- return list(self._data)
641
-
642
- @setdoc.basic
643
- def __fset__(self: Self, data: FrozenHoldList.Data[Item], /) -> None:
644
- self._data: FrozenHoldList.Data[Item] = data
645
-
646
-
647
568
  class DataList[Item](
648
569
  BaseDataList[Item],
649
570
  abc.MutableSequence[Item],
@@ -710,20 +631,3 @@ class DataList[Item](
710
631
  data = self.__fget__()
711
632
  data.sort(key=key, reverse=reverse)
712
633
  self.__fset__(data)
713
-
714
-
715
- class HoldList[Item](
716
- DataList[Item],
717
- BaseHoldCollection[Item],
718
- ):
719
- """Provide usable mutable list-like with slots."""
720
-
721
- __slots__ = ()
722
-
723
- @setdoc.basic
724
- def __fget__(self: Self) -> HoldList.Data[Item]:
725
- return list(self._data)
726
-
727
- @setdoc.basic
728
- def __fset__(self: Self, value: HoldList.Data[Item], /) -> None:
729
- self._data: HoldList.Data[Item] = value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datahold
3
- Version: 4.0.0.dev6
3
+ Version: 4.0.0.dev7
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
@@ -25,9 +25,5 @@ tests/test_FrozenHoldSet.py
25
25
  tests/test_HoldDict.py
26
26
  tests/test_HoldList.py
27
27
  tests/test_HoldSet.py
28
- tests/test_datahold_TestCopy.py
29
- tests/test_datahold_TestDataAttribute.py
30
- tests/test_datahold_TestFrozenMutability.py
31
- tests/test_datahold_TestMutableBehavior.py
32
28
  tests/test_testdata.py
33
29
  tests/testdata.toml
@@ -1,11 +1,55 @@
1
- __all__: list[str] = ["TestFrozenHoldDict"]
1
+ __all__: list[str] = [
2
+ "TestCopy",
3
+ "TestDataAttribute",
4
+ "TestFrozenHoldDict",
5
+ "TestFrozenMutability",
6
+ ]
2
7
 
3
8
  import unittest
9
+ from collections import abc
4
10
  from typing import Any, Self
5
11
 
6
12
  from frozendict import frozendict
7
13
 
8
- from datahold import FrozenHoldDict
14
+ from datahold import BaseHoldObject, FrozenDataDict
15
+
16
+
17
+ class FrozenHoldDict[Key: abc.Hashable, Value](
18
+ BaseHoldObject[FrozenDataDict.Data[Key, Value]],
19
+ FrozenDataDict[Key, Value],
20
+ ):
21
+ """Provide usable frozen dict-like with slots."""
22
+
23
+ __slots__ = ()
24
+
25
+
26
+ class TestCopy(unittest.TestCase):
27
+
28
+ def test_frozen_have_no_copy_2(self: Self) -> None:
29
+ """
30
+ Frozen classes should not define their own copy method.
31
+ (If a parent class or wrapped object exposes one, we ignore that.)
32
+ """
33
+ args: Any
34
+ cls: Any
35
+ copy_obj: Any
36
+ obj: Any
37
+ cls, args = (FrozenHoldDict, ({"a": 1},))
38
+ # They must not *define* copy themselves
39
+ self.assertNotIn("copy", cls.__dict__)
40
+ # Optional: if they *do* expose copy on the instance, it should not
41
+ # create a mutable variant; you can drop this if you prefer.
42
+ obj = cls(*args)
43
+ if hasattr(obj, "copy"):
44
+ copy_obj = obj.copy()
45
+ self.assertIsInstance(copy_obj, cls)
46
+
47
+
48
+ class TestDataAttribute(unittest.TestCase):
49
+ def test_dict_data_is_immutable_mapping(self: Self) -> None:
50
+ f: FrozenHoldDict[Any, Any]
51
+ f = FrozenHoldDict({"a": 1})
52
+ self.assertIsInstance(f.__fget__(), dict)
9
53
 
10
54
 
11
55
  class TestFrozenHoldDict(unittest.TestCase):
@@ -127,5 +171,15 @@ class TestFrozenHoldDict(unittest.TestCase):
127
171
  )
128
172
 
129
173
 
174
+ class TestFrozenMutability(unittest.TestCase):
175
+ def test_frozen_dict_cannot_mutate(self: Self) -> None:
176
+ f: Any
177
+ f = FrozenHoldDict({"a": 1})
178
+ with self.assertRaises((TypeError, AttributeError)):
179
+ f["b"] = 2
180
+ with self.assertRaises((TypeError, AttributeError)):
181
+ f.pop("a", None)
182
+
183
+
130
184
  if __name__ == "__main__":
131
185
  unittest.main()
@@ -1,8 +1,51 @@
1
- __all__: list[str] = ["TestFrozenHoldLists"]
1
+ __all__: list[str] = [
2
+ "TestCopy",
3
+ "TestDataAttribute",
4
+ "TestFrozenHoldLists",
5
+ "TestFrozenMutability",
6
+ ]
2
7
  import unittest
3
8
  from typing import Any, Self
4
9
 
5
- from datahold import FrozenHoldList
10
+ from datahold import BaseHoldObject, FrozenDataList
11
+
12
+
13
+ class FrozenHoldList[Item](
14
+ BaseHoldObject[FrozenDataList.Data[Item]],
15
+ FrozenDataList[Item],
16
+ ):
17
+ """Provide usable frozen list-like with slots."""
18
+
19
+ __slots__ = ()
20
+
21
+
22
+ class TestCopy(unittest.TestCase):
23
+
24
+ def test_frozen_have_no_copy_2(self: Self) -> None:
25
+ """
26
+ Frozen classes should not define their own copy method.
27
+ (If a parent class or wrapped object exposes one, we ignore that.)
28
+ """
29
+ args: Any
30
+ cls: Any
31
+ copy_obj: Any
32
+ obj: Any
33
+ cls, args = (FrozenHoldList, ([1, 2],))
34
+ # They must not *define* copy themselves
35
+ self.assertNotIn("copy", cls.__dict__)
36
+ # Optional: if they *do* expose copy on the instance, it should not
37
+ # create a mutable variant; you can drop this if you prefer.
38
+ obj = cls(*args)
39
+ if hasattr(obj, "copy"):
40
+ copy_obj = obj.copy()
41
+ self.assertIsInstance(copy_obj, cls)
42
+
43
+
44
+ class TestDataAttribute(unittest.TestCase):
45
+ def test_list_data_is_tuple(self: Self) -> None:
46
+ f: FrozenHoldList[Any]
47
+ f = FrozenHoldList([1, 2, 3])
48
+ self.assertIsInstance(f.__fget__(), list)
6
49
 
7
50
 
8
51
  class TestFrozenHoldList(unittest.TestCase):
@@ -144,5 +187,15 @@ class TestFrozenHoldList(unittest.TestCase):
144
187
  self.obj.index(99)
145
188
 
146
189
 
190
+ class TestFrozenMutability(unittest.TestCase):
191
+ def test_frozen_list_cannot_mutate(self: Self) -> None:
192
+ f: Any
193
+ f = FrozenHoldList([1, 2, 3])
194
+ with self.assertRaises((TypeError, AttributeError)):
195
+ f.append(4)
196
+ with self.assertRaises((TypeError, AttributeError)):
197
+ f.pop()
198
+
199
+
147
200
  if __name__ == "__main__":
148
201
  unittest.main()
@@ -1,8 +1,54 @@
1
- __all__: list[str] = ["TestFrozenHoldSet"]
1
+ __all__: list[str] = [
2
+ "TestCopy",
3
+ "TestDataAttribute",
4
+ "TestFrozenHoldSet",
5
+ "TestFrozenMutability",
6
+ ]
2
7
  import unittest
3
- from typing import Self
8
+ from collections import abc
9
+ from typing import Any, Self
4
10
 
5
- from datahold import FrozenHoldSet
11
+ from datahold import BaseHoldObject, FrozenDataSet
12
+
13
+
14
+ class FrozenHoldSet[Item: abc.Hashable](
15
+ BaseHoldObject[FrozenDataSet.Data[Item]],
16
+ FrozenDataSet[Item],
17
+ ):
18
+ """Provide usable frozen set-like with slots."""
19
+
20
+ __slots__ = ()
21
+
22
+
23
+ class TestCopy(unittest.TestCase):
24
+
25
+ def test_frozen_have_no_copy_2(self: Self) -> None:
26
+ """
27
+ Frozen classes should not define their own copy method.
28
+ (If a parent class or wrapped object exposes one, we ignore that.)
29
+ """
30
+ args: Any
31
+ cls: Any
32
+ copy_obj: Any
33
+ obj: Any
34
+ cls = FrozenHoldSet
35
+ args = ({1, 2},)
36
+ # They must not *define* copy themselves
37
+ self.assertNotIn("copy", cls.__dict__)
38
+
39
+ # Optional: if they *do* expose copy on the instance, it should not
40
+ # create a mutable variant; you can drop this if you prefer.
41
+ obj = cls(*args)
42
+ if hasattr(obj, "copy"):
43
+ copy_obj = obj.copy()
44
+ self.assertIsInstance(copy_obj, cls)
45
+
46
+
47
+ class TestDataAttribute(unittest.TestCase):
48
+ def test_set_data_is_frozenset(self: Self) -> None:
49
+ f: FrozenHoldSet[Any]
50
+ f = FrozenHoldSet({1, 2, 3})
51
+ self.assertIsInstance(f.__fget__(), set)
6
52
 
7
53
 
8
54
  class TestFrozenHoldSet(unittest.TestCase):
@@ -185,5 +231,16 @@ class TestFrozenHoldSet(unittest.TestCase):
185
231
  )
186
232
 
187
233
 
234
+ class TestFrozenMutability(unittest.TestCase):
235
+
236
+ def test_frozen_set_cannot_mutate(self: Self) -> None:
237
+ f: Any
238
+ f = FrozenHoldSet({1, 2, 3})
239
+ with self.assertRaises((TypeError, AttributeError)):
240
+ f.add(4)
241
+ with self.assertRaises((TypeError, AttributeError)):
242
+ f.remove(1)
243
+
244
+
188
245
  if __name__ == "__main__":
189
246
  unittest.main()
@@ -1,8 +1,45 @@
1
- __all__: list[str] = ["TestHoldDict"]
1
+ __all__: list[str] = [
2
+ "TestDataAttribute",
3
+ "TestHoldDict",
4
+ "TestMutableBehavior",
5
+ "TestCopy",
6
+ ]
2
7
  import unittest
3
- from typing import Any, Self
8
+ from collections import abc
9
+ from typing import Any, Self, cast
4
10
 
5
- from datahold import HoldDict
11
+ from datahold import BaseHoldObject, DataDict
12
+
13
+
14
+ class HoldDict[Key: abc.Hashable, Value](
15
+ BaseHoldObject[DataDict.Data[Key, Value]],
16
+ DataDict[Key, Value],
17
+ ):
18
+ """Provide usable mutable dict-like with slots."""
19
+
20
+ __slots__ = ()
21
+
22
+
23
+ class TestCopy(unittest.TestCase):
24
+ def test_mutable_copy_returns_same_type_and_is_shallow(self: Self) -> None:
25
+ d: HoldDict[str, dict[str, int]]
26
+ d_copy: HoldDict[str, dict[str, int]]
27
+ d = HoldDict({"a": {"x": 1}})
28
+ d_copy = d.copy()
29
+ self.assertIsInstance(d_copy, type(d))
30
+ self.assertIsNot(d_copy, d)
31
+ self.assertEqual(dict(d_copy), dict(d))
32
+
33
+ # shallow: inner object is shared
34
+ cast(dict[str, int], d["a"])["x"] = 2
35
+ self.assertEqual(cast(dict[str, int], d_copy["a"])["x"], 2)
36
+
37
+
38
+ class TestDataAttribute(unittest.TestCase):
39
+ def test_dict_data_is_immutable_mapping(self: Self) -> None:
40
+ m: HoldDict[Any, Any]
41
+ m = HoldDict({"a": 1})
42
+ self.assertIsInstance(m.__fget__(), dict)
6
43
 
7
44
 
8
45
  class TestHoldDict(unittest.TestCase):
@@ -164,5 +201,14 @@ class TestHoldDict(unittest.TestCase):
164
201
  self.assertIsNot(result, self.obj)
165
202
 
166
203
 
204
+ class TestMutableBehavior(unittest.TestCase):
205
+ def test_hold_dict_mutates_and_syncs_data(self: Self) -> None:
206
+ x: HoldDict[Any, Any]
207
+ x = HoldDict({"a": 1})
208
+ x["b"] = 2
209
+ self.assertEqual(x["b"], 2)
210
+ self.assertEqual(x.__fget__()["b"], 2)
211
+
212
+
167
213
  if __name__ == "__main__":
168
214
  unittest.main()
@@ -1,8 +1,44 @@
1
- __all__: list[str] = ["TestHoldList"]
1
+ __all__: list[str] = [
2
+ "TestCopy",
3
+ "TestDataAttribute",
4
+ "TestHoldList",
5
+ "TestMutableBehavior",
6
+ ]
2
7
  import unittest
3
8
  from typing import Any, Self
4
9
 
5
- from datahold import HoldList
10
+ from datahold import BaseHoldObject, DataList
11
+
12
+
13
+ class HoldList[Item](
14
+ BaseHoldObject[DataList.Data[Item]],
15
+ DataList[Item],
16
+ ):
17
+ """Provide usable mutable list-like with slots."""
18
+
19
+ __slots__ = ()
20
+
21
+
22
+ class TestCopy(unittest.TestCase):
23
+
24
+ def test_list_copy(self: Self) -> None:
25
+ lst: HoldList[Any]
26
+ lst_copy: HoldList[Any]
27
+ lst = HoldList([[1], [2]])
28
+ lst_copy = lst.copy()
29
+ self.assertIsInstance(lst_copy, type(lst))
30
+ self.assertIsNot(lst_copy, lst)
31
+ self.assertEqual(list(lst_copy), list(lst))
32
+
33
+ lst[0].append(99)
34
+ self.assertEqual(lst_copy[0], [1, 99])
35
+
36
+
37
+ class TestDataAttribute(unittest.TestCase):
38
+ def test_list_data_is_tuple(self: Self) -> None:
39
+ m: HoldList[Any]
40
+ m = HoldList([1, 2, 3])
41
+ self.assertIsInstance(m.__fget__(), list)
6
42
 
7
43
 
8
44
  class TestHoldList(unittest.TestCase):
@@ -212,5 +248,15 @@ class TestHoldList(unittest.TestCase):
212
248
  )
213
249
 
214
250
 
251
+ class TestMutableBehavior(unittest.TestCase):
252
+
253
+ def test_hold_list_mutates_and_syncs_data(self: Self) -> None:
254
+ x: HoldList[Any]
255
+ x = HoldList([1, 2])
256
+ x.append(3)
257
+ self.assertEqual(list(x), [1, 2, 3])
258
+ self.assertEqual(x.__fget__(), [1, 2, 3])
259
+
260
+
215
261
  if __name__ == "__main__":
216
262
  unittest.main()
@@ -1,7 +1,45 @@
1
+ __all__: list[str] = [
2
+ "TestCopy",
3
+ "TestDataAttribute",
4
+ "TestHoldSet",
5
+ "TestMutableBehavior",
6
+ ]
1
7
  import unittest
8
+ from collections import abc
2
9
  from typing import Any, Self
3
10
 
4
- from datahold import HoldSet
11
+ from datahold import BaseHoldObject, DataSet
12
+
13
+
14
+ class HoldSet[Item: abc.Hashable](
15
+ BaseHoldObject[DataSet.Data[Item]],
16
+ DataSet[Item],
17
+ ):
18
+ """Provide usable mutable set-like with slots."""
19
+
20
+ __slots__ = ()
21
+
22
+
23
+ class TestCopy(unittest.TestCase):
24
+
25
+ def test_set_copy(self: Self) -> None:
26
+ s: HoldSet[Any]
27
+ s_copy: HoldSet[Any]
28
+ s = HoldSet({1, 2, 3})
29
+ s_copy = s.copy()
30
+ self.assertIsInstance(s_copy, type(s))
31
+ self.assertIsNot(s_copy, s)
32
+ self.assertEqual(set(s_copy), set(s))
33
+
34
+ s.add(4)
35
+ self.assertNotIn(4, s_copy)
36
+
37
+
38
+ class TestDataAttribute(unittest.TestCase):
39
+ def test_set_data_is_frozenset(self: Self) -> None:
40
+ m: HoldSet[Any]
41
+ m = HoldSet({1, 2, 3})
42
+ self.assertIsInstance(m.__fget__(), set)
5
43
 
6
44
 
7
45
  class TestHoldSet(unittest.TestCase):
@@ -234,5 +272,14 @@ class TestHoldSet(unittest.TestCase):
234
272
  )
235
273
 
236
274
 
275
+ class TestMutableBehavior(unittest.TestCase):
276
+ def test_hold_set_mutates_and_syncs_data(self: Self) -> None:
277
+ s: HoldSet[Any]
278
+ s = HoldSet({1, 2})
279
+ s.add(3)
280
+ self.assertTrue(3 in s)
281
+ self.assertTrue(3 in s.__fget__())
282
+
283
+
237
284
  if __name__ == "__main__":
238
285
  unittest.main()
@@ -1,5 +1,6 @@
1
1
  __all__: list[str] = [
2
2
  "TestAbstractness",
3
+ "TestAll",
3
4
  "TestCollection",
4
5
  "TestConstructor",
5
6
  "TestData",
@@ -25,6 +26,10 @@ import datahold
25
26
  class Lazy(enum.Enum):
26
27
  lazy = None
27
28
 
29
+ @cached_property
30
+ def HOLD_TYPES(self: Self) -> list[str]:
31
+ return cast(list[str], self.varia["HOLD_TYPES"])
32
+
28
33
  @cached_property
29
34
  def METHODS(self: Self) -> tuple[str, ...]:
30
35
  return cast(tuple[str, ...], self.test_object["METHODS"])
@@ -54,6 +59,10 @@ class Lazy(enum.Enum):
54
59
  def types(self: Self) -> dict[str, dict[str, Any]]:
55
60
  return cast(dict[str, dict[str, Any]], self.data["types"])
56
61
 
62
+ @cached_property
63
+ def varia(self: Self) -> dict[str, Any]:
64
+ return cast(dict[str, Any], self.data["varia"])
65
+
57
66
 
58
67
  class TestAbstractness(unittest.TestCase):
59
68
 
@@ -69,6 +78,18 @@ class TestAbstractness(unittest.TestCase):
69
78
  self.go_types(typename, **kwargs)
70
79
 
71
80
 
81
+ class TestAll(unittest.TestCase):
82
+ def test_all(self: Self) -> None:
83
+ self.assertLessEqual(
84
+ set(datahold.__all__),
85
+ Lazy.lazy.types.keys() | Lazy.lazy.HOLD_TYPES,
86
+ )
87
+ self.assertListEqual(
88
+ datahold.__all__,
89
+ list(sorted(datahold.__all__)),
90
+ )
91
+
92
+
72
93
  class TestCollection(unittest.TestCase):
73
94
  def _test_cls(self: Self, cls: type) -> None:
74
95
  method: Any
@@ -182,10 +203,8 @@ class TestDirData(unittest.TestCase):
182
203
  def go_init(self: Self, x: str, /) -> None:
183
204
  cls: Any
184
205
  cls = getattr(datahold, x)
185
- self.assertEqual(
186
- "__fget__" in cls.__dict__,
187
- "Data" in cls.__dict__ or not ins.isabstract(cls),
188
- )
206
+ if "__fget__" in cls.__dict__:
207
+ self.assertTrue("Data" in cls.__dict__ or not ins.isabstract(cls))
189
208
  if "__fset__" in cls.__dict__:
190
209
  self.assertIn("__fget__", cls.__dict__)
191
210
  self.assertEqual(
@@ -48,7 +48,12 @@ BaseDataCollection = true
48
48
  Hashable = false
49
49
 
50
50
  [types.BaseDataSequence]
51
- dirData = []
51
+ dirData = [
52
+ "__contains__",
53
+ "__getitem__",
54
+ "__iter__",
55
+ "__len__",
56
+ ]
52
57
 
53
58
  [types.BaseDataSequence.parents]
54
59
  BaseDataCollection = true
@@ -58,16 +63,10 @@ Hashable = true
58
63
  dirData = []
59
64
 
60
65
  [types.BaseDataSet.parents]
66
+ BaseDataAbstractSet = true
61
67
  Hashable = false
62
68
  Set = true
63
69
 
64
- [types.BaseHoldCollection]
65
- dirData = []
66
-
67
- [types.BaseHoldCollection.parents]
68
- BaseDataCollection = true
69
- Hashable = true
70
-
71
70
  [types.DataDict]
72
71
  Data = "frozendict"
73
72
  dirData = []
@@ -79,6 +78,7 @@ valid = false
79
78
 
80
79
  [types.DataDict.parents]
81
80
  BaseDataDict = true
81
+ BaseDataMapping = true
82
82
  MutableMapping = true
83
83
 
84
84
  [types.DataList]
@@ -130,130 +130,7 @@ isabstract = true
130
130
 
131
131
  [types.FrozenDataSet.parents]
132
132
 
133
- [types.FrozenHoldDict]
134
- dirData = []
135
-
136
- [types.FrozenHoldDict.constructor.concrete]
137
- args = [
138
- { a = 1 },
139
- ]
140
-
141
- [types.FrozenHoldDict.constructor.concrete-1.kwargs]
142
- a = 1
143
-
144
- [types.FrozenHoldDict.parents]
145
- Mapping = true
146
- MutableMapping = false
147
-
148
- [types.FrozenHoldList]
149
- dirData = []
150
-
151
- [types.FrozenHoldList.constructor.concrete]
152
- args = [
153
- [
154
- 1,
155
- 2,
156
- ],
157
- ]
158
- repr = "FrozenHoldList([1, 2])"
159
-
160
- [types.FrozenHoldList.parents]
161
- FrozenDataList = true
162
- MutableSequence = false
163
- Sequence = true
164
-
165
- [types.FrozenHoldSet]
166
- dirData = []
167
-
168
- [types.FrozenHoldSet.constructor.concrete]
169
- args = [
170
- [
171
- 1,
172
- 2,
173
- ],
174
- ]
175
- valid = true
176
-
177
- [types.FrozenHoldSet.parents]
178
- MutableSet = false
179
- Set = true
180
-
181
- [types.HoldDict]
182
- Data = "frozendict"
183
- dirData = []
184
- n_type_params = 2
185
-
186
- [types.HoldDict.constructor.concrete.kwargs]
187
- a = 1
188
-
189
- [types.HoldDict.constructor.empty]
190
- args = []
191
-
192
- [types.HoldDict.parents]
193
- BaseDataDict = true
194
- DataDict = true
195
- Mapping = true
196
- MutableMapping = true
197
-
198
- [types.HoldList]
199
- Data = "tuple"
200
- dirData = []
201
- n_type_params = 1
202
-
203
- [types.HoldList.constructor.empty]
204
- args = []
205
-
206
- [types.HoldList.constructor.concrete]
207
- args = [
208
- [
209
- 1,
210
- 2,
211
- ],
212
- ]
213
-
214
- [types.HoldList.constructor.protocols]
215
- args = [
216
- [
217
- 1,
218
- 2,
219
- 3,
220
- ],
221
- ]
222
-
223
- [types.HoldList.parents]
224
- DataList = true
225
- MutableSequence = true
226
- Sequence = true
227
-
228
- [types.HoldSet]
229
- Date = "frozenset"
230
- dirData = []
231
- n_type_params = 1
232
-
233
- [types.HoldSet.constructor.empty]
234
- args = []
235
-
236
- [types.HoldSet.constructor.concrete]
237
- args = [
238
- [
239
- 1,
240
- 2,
241
- ],
242
- ]
243
-
244
- [types.HoldSet.constructor.protocols]
245
- args = [
246
- [
247
- 1,
248
- 2,
249
- 3,
250
- ],
251
- ]
252
-
253
- [types.HoldSet.parents]
254
- BaseDataSet = true
255
- DataSet = true
256
- MutableSet = true
257
- Set = true
258
-
259
133
  [varia]
134
+ HOLD_TYPES = [
135
+ "BaseHoldObject",
136
+ ]
@@ -1,81 +0,0 @@
1
- __all__: list[str] = ["TestCopy"]
2
-
3
- import unittest
4
- from typing import Any, Self, cast
5
-
6
- from datahold import (
7
- FrozenHoldDict,
8
- FrozenHoldList,
9
- FrozenHoldSet,
10
- HoldDict,
11
- HoldList,
12
- HoldSet,
13
- )
14
-
15
-
16
- class TestCopy(unittest.TestCase):
17
-
18
- def test_frozen_have_no_copy_2(self: Self) -> None:
19
- """
20
- Frozen classes should not define their own copy method.
21
- (If a parent class or wrapped object exposes one, we ignore that.)
22
- """
23
- args: Any
24
- cls: Any
25
- copy_obj: Any
26
- obj: Any
27
- for cls, args in (
28
- (FrozenHoldDict, ({"a": 1},)),
29
- (FrozenHoldList, ([1, 2],)),
30
- (FrozenHoldSet, ({1, 2},)),
31
- ):
32
- # They must not *define* copy themselves
33
- self.assertNotIn("copy", cls.__dict__)
34
-
35
- # Optional: if they *do* expose copy on the instance, it should not
36
- # create a mutable variant; you can drop this if you prefer.
37
- obj = cls(*args)
38
- if hasattr(obj, "copy"):
39
- copy_obj = obj.copy()
40
- self.assertIsInstance(copy_obj, cls)
41
-
42
- def test_mutable_copy_returns_same_type_and_is_shallow(self: Self) -> None:
43
- d: HoldDict[str, dict[str, int]]
44
- d_copy: HoldDict[str, dict[str, int]]
45
- d = HoldDict({"a": {"x": 1}})
46
- d_copy = d.copy()
47
- self.assertIsInstance(d_copy, type(d))
48
- self.assertIsNot(d_copy, d)
49
- self.assertEqual(dict(d_copy), dict(d))
50
-
51
- # shallow: inner object is shared
52
- cast(dict[str, int], d["a"])["x"] = 2
53
- self.assertEqual(cast(dict[str, int], d_copy["a"])["x"], 2)
54
-
55
- def test_list_copy(self: Self) -> None:
56
- lst: HoldList[Any]
57
- lst_copy: HoldList[Any]
58
- lst = HoldList([[1], [2]])
59
- lst_copy = lst.copy()
60
- self.assertIsInstance(lst_copy, type(lst))
61
- self.assertIsNot(lst_copy, lst)
62
- self.assertEqual(list(lst_copy), list(lst))
63
-
64
- lst[0].append(99)
65
- self.assertEqual(lst_copy[0], [1, 99])
66
-
67
- def test_set_copy(self: Self) -> None:
68
- s: HoldSet[Any]
69
- s_copy: HoldSet[Any]
70
- s = HoldSet({1, 2, 3})
71
- s_copy = s.copy()
72
- self.assertIsInstance(s_copy, type(s))
73
- self.assertIsNot(s_copy, s)
74
- self.assertEqual(set(s_copy), set(s))
75
-
76
- s.add(4)
77
- self.assertNotIn(4, s_copy)
78
-
79
-
80
- if __name__ == "__main__":
81
- unittest.main()
@@ -1,46 +0,0 @@
1
- __all__: list[str] = ["TestDataAttribute"]
2
-
3
- import unittest
4
- from typing import Any, Self
5
-
6
- from datahold import (
7
- FrozenHoldDict,
8
- FrozenHoldList,
9
- FrozenHoldSet,
10
- HoldDict,
11
- HoldList,
12
- HoldSet,
13
- )
14
-
15
-
16
- class TestDataAttribute(unittest.TestCase):
17
- def test_dict_data_is_immutable_mapping(self: Self) -> None:
18
- f: FrozenHoldDict[Any, Any]
19
- m: HoldDict[Any, Any]
20
- obj: Any
21
- f = FrozenHoldDict({"a": 1})
22
- m = HoldDict({"a": 1})
23
- for obj in (f, m):
24
- self.assertIsInstance(obj.__fget__(), dict)
25
-
26
- def test_list_data_is_tuple(self: Self) -> None:
27
- f: FrozenHoldList[Any]
28
- m: HoldList[Any]
29
- o: Any
30
- f = FrozenHoldList([1, 2, 3])
31
- m = HoldList([1, 2, 3])
32
- for o in (f, m):
33
- self.assertIsInstance(o.__fget__(), list)
34
-
35
- def test_set_data_is_frozenset(self: Self) -> None:
36
- f: FrozenHoldSet[Any]
37
- m: HoldSet[Any]
38
- obj: Any
39
- f = FrozenHoldSet({1, 2, 3})
40
- m = HoldSet({1, 2, 3})
41
- for obj in (f, m):
42
- self.assertIsInstance(obj.__fget__(), set)
43
-
44
-
45
- if __name__ == "__main__":
46
- unittest.main()
@@ -1,36 +0,0 @@
1
- __all__: list[str] = ["TestFrozenMutability"]
2
-
3
- import unittest
4
- from typing import Any, Self
5
-
6
- from datahold import FrozenHoldDict, FrozenHoldList, FrozenHoldSet
7
-
8
-
9
- class TestFrozenMutability(unittest.TestCase):
10
- def test_frozen_dict_cannot_mutate(self: Self) -> None:
11
- f: Any
12
- f = FrozenHoldDict({"a": 1})
13
- with self.assertRaises((TypeError, AttributeError)):
14
- f["b"] = 2
15
- with self.assertRaises((TypeError, AttributeError)):
16
- f.pop("a", None)
17
-
18
- def test_frozen_list_cannot_mutate(self: Self) -> None:
19
- f: Any
20
- f = FrozenHoldList([1, 2, 3])
21
- with self.assertRaises((TypeError, AttributeError)):
22
- f.append(4)
23
- with self.assertRaises((TypeError, AttributeError)):
24
- f.pop()
25
-
26
- def test_frozen_set_cannot_mutate(self: Self) -> None:
27
- f: Any
28
- f = FrozenHoldSet({1, 2, 3})
29
- with self.assertRaises((TypeError, AttributeError)):
30
- f.add(4)
31
- with self.assertRaises((TypeError, AttributeError)):
32
- f.remove(1)
33
-
34
-
35
- if __name__ == "__main__":
36
- unittest.main()
@@ -1,33 +0,0 @@
1
- __all__: list[str] = ["TestMutableBehavior"]
2
-
3
- import unittest
4
- from typing import Any, Self
5
-
6
- from datahold import HoldDict, HoldList, HoldSet
7
-
8
-
9
- class TestMutableBehavior(unittest.TestCase):
10
- def test_hold_dict_mutates_and_syncs_data(self: Self) -> None:
11
- x: HoldDict[Any, Any]
12
- x = HoldDict({"a": 1})
13
- x["b"] = 2
14
- self.assertEqual(x["b"], 2)
15
- self.assertEqual(x.__fget__()["b"], 2)
16
-
17
- def test_hold_list_mutates_and_syncs_data(self: Self) -> None:
18
- x: HoldList[Any]
19
- x = HoldList([1, 2])
20
- x.append(3)
21
- self.assertEqual(list(x), [1, 2, 3])
22
- self.assertEqual(x.__fget__(), [1, 2, 3])
23
-
24
- def test_hold_set_mutates_and_syncs_data(self: Self) -> None:
25
- s: HoldSet[Any]
26
- s = HoldSet({1, 2})
27
- s.add(3)
28
- self.assertTrue(3 in s)
29
- self.assertTrue(3 in s.__fget__())
30
-
31
-
32
- if __name__ == "__main__":
33
- unittest.main()
File without changes
File without changes
File without changes
File without changes