Coverage for tests/test_base.py: 97%
70 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-30 23:02 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-30 23:02 +0000
1"""Tests for `bunches.base`."""
3from __future__ import annotations
5import copy
6import dataclasses
7from typing import Any
9import pytest
11from bunches import (
12 Bunch,
13 Catalog,
14 ChainDict,
15 DictList,
16 Dictionary,
17 Listing,
18 Repository,
19 settings,
20)
23@dataclasses.dataclass
24class MinimalBunch(Bunch):
25 """Smallest concrete `Bunch` (a list wrapper) for testing base behavior."""
27 contents: list[Any] = dataclasses.field(default_factory = list)
29 def add(self, item: Any) -> None:
30 """Appends `item`.
32 Args:
33 item: item to append to `contents`.
35 """
36 self.contents.append(item)
38 def delete(self, item: int) -> None:
39 """Deletes the item at index `item`.
41 Args:
42 item: index of the item to delete.
44 """
45 del self.contents[item]
47 def subset(
48 self,
49 include: Any = None,
50 exclude: Any = None,
51 returns: Any = None) -> Any:
52 """Returns a new instance with `include` items.
54 Args:
55 include: items to include.
56 exclude: ignored.
57 returns: ignored.
59 Returns:
60 A new instance with the items in `include`.
62 """
63 return self.__class__([i for i in self.contents if i in include])
65 def __contains__(self, item: object) -> bool:
66 """Returns if `item` is in `contents`.
68 Args:
69 item: item to look for.
71 Returns:
72 Whether `item` is in `contents`.
74 """
75 return item in self.contents
78def test_bunch_is_abstract() -> None:
79 """Tests that `Bunch` cannot be instantiated without required methods."""
80 with pytest.raises(TypeError):
81 Bunch([1, 2]) # type: ignore[abstract]
83 @dataclasses.dataclass
84 class NoSubset(Bunch):
85 """`Bunch` without the required `subset` method."""
87 contents: list[Any] = dataclasses.field(default_factory = list)
89 def add(self, item: Any) -> None:
90 """Does nothing.
92 Args:
93 item: ignored.
95 """
97 def delete(self, item: Any) -> None:
98 """Does nothing.
100 Args:
101 item: ignored.
103 """
105 def __contains__(self, item: object) -> bool:
106 """Returns `False`.
108 Args:
109 item: ignored.
111 Returns:
112 `False`.
114 """
115 return False
117 with pytest.raises(TypeError):
118 NoSubset() # type: ignore[abstract]
121def test_collection_dunders() -> None:
122 """Tests `__iter__`, `__len__`, and `__contains__`."""
123 bunch = MinimalBunch([1, 2, 3])
124 assert list(bunch) == [1, 2, 3]
125 assert len(bunch) == 3
126 assert 2 in bunch
127 assert 4 not in bunch
130def test_add_operators() -> None:
131 """Tests `__add__` (copies) and `__iadd__` (in place)."""
132 bunch = MinimalBunch([1])
133 combined = bunch + 2
134 assert combined.contents == [1, 2]
135 assert bunch.contents == [1]
136 assert combined is not bunch
137 same = bunch
138 bunch += 3
139 assert bunch is same
140 assert same.contents == [1, 3]
143def test_delitem() -> None:
144 """Tests `__delitem__`, which calls `delete`."""
145 bunch = MinimalBunch([1, 2, 3])
146 del bunch[0]
147 assert bunch.contents == [2, 3]
150def test_resolve_returns() -> None:
151 """Tests `_resolve_returns`."""
152 bunch = MinimalBunch()
153 assert bunch._resolve_returns(None) == 'class'
154 assert bunch._resolve_returns('copy') == 'copy'
155 settings.set_subset_return('simple')
156 assert bunch._resolve_returns(None) == 'simple'
157 assert bunch._resolve_returns('class') == 'class'
160def test_concrete_subclasses_are_bunches() -> None:
161 """Tests that package classes are `Bunch` instances."""
162 assert isinstance(Dictionary(), Bunch)
163 assert isinstance(Listing(), Bunch)
166@pytest.mark.parametrize(('instance', 'other'), [
167 (Dictionary({'a': [1]}), {'b': [2]}),
168 (Catalog({'a': [1]}), {'b': [2]}),
169 (ChainDict([Dictionary({'a': [1]})]), {'b': [2]}),
170 (Repository({'a': [1]}), [2]),
171 (Listing([[1]]), [[2]]),
172 (DictList(['a']), ['b'])])
173def test_add_operators_all_classes(instance: Bunch, other: Any) -> None:
174 """Tests that `+` copies and `+=` modifies in place for every class.
176 Args:
177 instance: instance of a `bunches` class.
178 other: item to add to `instance`.
180 """
181 original = copy.deepcopy(instance)
182 combined = instance + other
183 assert type(combined) is type(instance)
184 assert instance == original
185 assert combined != original
186 same = instance
187 instance += other
188 assert instance is same
189 assert instance == combined