Coverage for src/bunches/base.py: 100%
34 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"""Base class for extensible, flexible, lightweight collection types.
3Contents:
4 Bunch (Collection, abc.ABC): base class for collections in `bunches`. It
5 requires subclasses to have `add`, `delete`, and `subset` methods.
7"""
9from __future__ import annotations
11import abc
12import copy
13import dataclasses
14from collections.abc import Collection, Iterator
15from typing import TYPE_CHECKING, Any, Self
17from . import settings
19if TYPE_CHECKING:
20 from .kinds import SubsetReturns
23@dataclasses.dataclass
24class Bunch(Collection, abc.ABC):
25 """Base for general `bunches` collections.
27 A Bunch differs from a general python Collection in 5 ways:
28 1) It must include an `add` method which provides the default mechanism
29 for adding new items to the collection. `add` allows a subclass to
30 designate the preferred method of adding to the collection's stored
31 data without replacing other access methods.
32 2) It must include a `delete` method which provides the default
33 mechanism for deleting items in the collection. `delete` is called
34 by the `__delitem__` dunder method to delete stored items.
35 3) A subclass must include a `subset` method with optional `include` and
36 `exclude` parameters for returning a subset of the Bunch subclass.
37 4) It supports the '+' and '+=' operators being used to join a Bunch
38 subclass instance with an item of the same python type (mapping,
39 sequence, etc.). Both operators call the Bunch subclass `add`
40 method to implement how the added item(s) is/are added. '+' leaves
41 the original instance unchanged and returns a modified deep copy,
42 while '+=' modifies the instance in place.
43 5) It offers an accessible `contents` attribute that contains the native
44 Python type for the `Bunch` subclass. This allows easy reversion to
45 the simple type or simple bypassing of the `Bunch` subclass methods.
47 Args:
48 contents: stored collection of items.
50 """
52 contents: Collection[Any]
54 """ Required Methods """
56 @abc.abstractmethod
57 def add(self, item: Any, *args: Any, **kwargs: Any) -> None:
58 """Adds `item` to `contents`.
60 Args:
61 item: item to add to `contents`.
62 *args: positional arguments.
63 **kwargs: keyword arguments.
65 """
67 @abc.abstractmethod
68 def delete(self, item: Any, *args: Any, **kwargs: Any) -> None:
69 """Deletes `item` from `contents`.
71 Args:
72 item: item or key to delete in `contents`.
73 *args: positional arguments.
74 **kwargs: keyword arguments.
76 Raises:
77 KeyError: if `item` is not in `contents`. Subclasses should
78 implement this error.
80 """
82 @abc.abstractmethod
83 def subset(
84 self,
85 include: Collection[Any] | Any | None = None,
86 exclude: Collection[Any] | Any | None = None,
87 returns: SubsetReturns | None = None) -> Any:
88 """Returns a new instance with a subset of `contents`.
90 This method applies `include` before `exclude` if both are passed. If
91 `include` is None, all existing items will be added to the new subset
92 class instance before `exclude` is applied.
94 Args:
95 include: item(s) to include in the new `Bunch`. Defaults to `None`.
96 exclude: item(s) to exclude from the new `Bunch`. Defaults to
97 `None`.
98 returns: whether to return a new instance of the `Bunch` subclass
99 ('class'), a deep copy of the subclass instance ('copy') or the
100 simple native Python type ('simple'). Defaults to `None`, which
101 uses the global setting stored in `settings._SUBSET_RETURN`.
103 Returns:
104 A subset of the stored data in the form dictated by `returns`.
106 """
108 """ Dunder Methods """
110 def __add__(self, other: Any) -> Self:
111 """Returns a deep copy with `other` combined using the `add` method.
113 Args:
114 other: item to add to the copy's `contents` using the `add` method.
116 Returns:
117 A new instance. The original instance is not modified.
119 """
120 new_instance = copy.deepcopy(self)
121 new_instance.add(item = other)
122 return new_instance
124 def __iadd__(self, other: Any) -> Self:
125 """Combines argument with `contents` using the `add` method.
127 Args:
128 other: item to add to `contents` using the `add` method.
130 Returns:
131 The instance, modified in place.
133 """
134 self.add(item = other)
135 return self
137 def __delitem__(self, item: Any) -> None:
138 """Deletes `item` from `contents`.
140 Args:
141 item: item or key to delete in `contents`.
143 Raises:
144 KeyError: if `item` is not in `contents`.
146 """
147 self.delete(item = item)
149 def __iter__(self) -> Iterator[Any]:
150 """Returns iterator of `contents`.
152 Returns:
153 Iterator of `contents`.
155 """
156 return iter(self.contents)
158 def __len__(self) -> int:
159 """Returns length of `contents`.
161 Returns:
162 Length of `contents`.
164 """
165 return len(self.contents)
167 """ Private Methods """
169 def _resolve_returns(self, returns: SubsetReturns | None) -> SubsetReturns:
170 """Returns `returns` or the global default if it is `None`.
172 Args:
173 returns: `returns` argument passed to a `subset` method.
175 Returns:
176 'returns' or the value of `settings._SUBSET_RETURN`.
178 """
179 return settings._SUBSET_RETURN if returns is None else returns