Compare commits

..

5 Commits

Author SHA1 Message Date
John Lancaster
3281c7c1ea readability 2026-02-21 23:24:48 -06:00
John Lancaster
071c3bd342 reorg 2026-02-21 23:24:09 -06:00
John Lancaster
7c3e073c54 nested tests 2026-02-21 23:19:43 -06:00
John Lancaster
0980145b10 org 2026-02-21 23:19:32 -06:00
John Lancaster
941e689c19 get_nested 2026-02-21 23:19:23 -06:00
4 changed files with 44 additions and 32 deletions

View File

@@ -44,3 +44,12 @@ class HookedContainer(ABC, Sized, Iterable[T], Container[T]):
@abstractmethod @abstractmethod
def insert(self, index, value): ... def insert(self, index, value): ...
# Custom Methods
def get_nested(self, keys):
"""Recursively call __getitem__ with each key in the iterable."""
result = self
for key in keys:
result = result[key]
return result

View File

@@ -2,6 +2,7 @@ from collections.abc import MutableMapping, MutableSequence, Sequence
from copy import copy from copy import copy
from typing import TypeVar from typing import TypeVar
from . import events as e
from .common import HookedContainer, MutableNesting from .common import HookedContainer, MutableNesting
from .sequence import HookedList from .sequence import HookedList
@@ -23,29 +24,23 @@ class HookedMapping(HookedContainer[T], MutableMapping[T, MutableNesting[T]]):
def __getitem__(self, key: T) -> MutableNesting[T]: def __getitem__(self, key: T) -> MutableNesting[T]:
value = self._data[key] value = self._data[key]
new_path = copy(self._path)
new_path.append(key)
match value: match value:
case MutableMapping() as mapping: case MutableMapping() as mapping:
new_path = copy(self._path) return HookedMapping(hook=self.hook, existing=mapping, path=new_path)
new_path.append(key)
return type(self)(self.hook, existing=mapping, path=new_path)
case MutableSequence() as seq: case MutableSequence() as seq:
new_path = copy(self._path) return HookedList(hook=self.hook, existing=seq, path=new_path)
new_path.append(key)
return HookedList(self.hook, existing=seq, path=new_path)
case _ as item: case _ as item:
return item return item
def __setitem__(self, key: T, value: MutableNesting[T]) -> None: def __setitem__(self, key: T, value: MutableNesting[T]) -> None:
self._data[key] = value self._data[key] = value
if self.hook: if self.hook:
from . import events as e
self.hook(e.SetItemEvent(index=key, item=value)) self.hook(e.SetItemEvent(index=key, item=value))
def __delitem__(self, key: T) -> None: def __delitem__(self, key: T) -> None:
item = self._data[key] item = self._data[key]
del self._data[key] del self._data[key]
if self.hook: if self.hook:
from . import events as e self.hook(e.RemoveItemEvent(index=key, item=item, path=self._path))
self.hook(e.RemoveItemEvent(index=key, item=item))

View File

@@ -12,27 +12,30 @@ class HookedList(HookedContainer[T], MutableSequence[T]):
_data: MutableSequence[T] _data: MutableSequence[T]
path: MutableSequence[int] path: MutableSequence[int]
def __init__(self, hook: HookFunction, existing: MutableSequence[T], path: MutableSequence[int] | None = None): def __init__(
self,
existing: MutableSequence[T],
hook: HookFunction | None = None,
path: MutableSequence[int] | None = None,
) -> None:
self.hook = hook self.hook = hook
match existing: match existing:
case HookedContainer(_data=seq):
self._data = seq
case MutableSequence() as seq: case MutableSequence() as seq:
self._data = seq self._data = seq
case HookedContainer(_data=seq):
self._data = seq
case _ as it: case _ as it:
self._data = list(it) self._data = list(it)
self._path = list(path) if path is not None else [] self._path = list(path) if path is not None else []
def __getitem__(self, s): def __getitem__(self, s):
new_path = copy(self._path)
new_path.append(s)
match self._data[s]: match self._data[s]:
case MutableSequence() as seq: case MutableSequence() as seq:
new_path = copy(self._path) return type(self)(seq, self.hook, path=new_path)
new_path.append(s)
return type(self)(self.hook, existing=seq, path=new_path)
case HookedContainer(_data=seq): case HookedContainer(_data=seq):
new_path = copy(self._path) return type(self)(seq, self.hook, path=new_path)
new_path.append(s)
return type(self)(self.hook, existing=seq, path=new_path)
case _ as item: case _ as item:
return item return item

View File

@@ -12,59 +12,64 @@ def get_id(a: Any):
class TestHookedList: class TestHookedList:
class TestConstruction: class TestConstruction:
def test_empty(self): def test_empty(self):
lst = HookedList(None, existing=[]) lst = HookedList([])
assert list(lst) == [] assert list(lst) == []
def test_with_items(self): def test_with_items(self):
lst = HookedList(None, existing=[1, 2, 3]) lst = HookedList([1, 2, 3])
assert list(lst) == [1, 2, 3] assert list(lst) == [1, 2, 3]
def test_recreation(self): def test_recreation(self):
initial = [1, 2, 3] initial = [1, 2, 3]
initial_id = id(initial) initial_id = id(initial)
lst = HookedList(None, existing=initial) lst = HookedList(initial)
assert id(lst._data) == initial_id assert id(lst._data) == initial_id
lst2 = HookedList(None, existing=lst) lst2 = HookedList(lst)
assert id(lst2._data) == initial_id assert id(lst2._data) == initial_id
class TestSeqOps: class TestSeqOps:
def test_len(self): def test_len(self):
lst = HookedList(None, existing=[1, 2, 3]) lst = HookedList([1, 2, 3])
assert len(lst) == 3 assert len(lst) == 3
def test_getitem(self): def test_getitem(self):
lst = HookedList(None, existing=[1, 2, 3]) lst = HookedList([1, 2, 3])
assert lst[0] == 1 assert lst[0] == 1
assert lst[1] == 2 assert lst[1] == 2
assert lst[-1] == 3 assert lst[-1] == 3
def test_contains(self): def test_contains(self):
lst = HookedList(None, existing=[1, 2, 3]) lst = HookedList([1, 2, 3])
assert 2 in lst assert 2 in lst
assert 4 not in lst assert 4 not in lst
def test_iter(self): def test_iter(self):
lst = HookedList(None, existing=[1, 2, 3]) lst = HookedList([1, 2, 3])
for i, item in enumerate(lst, start=1): for i, item in enumerate(lst, start=1):
assert item == i assert item == i
class TestMutableOps: class TestMutableOps:
def test_setitem(self): def test_setitem(self):
added = [] added = []
lst = HookedList(lambda e: added.append(e.item), existing=[1, 2, 3]) lst = HookedList(
[1, 2, [4, 5, [6, 7]]],
lambda e: added.append(e.item),
)
lst[0] = 10 lst[0] = 10
lst.append(20) lst.append(20)
assert list(lst) == [10, 2, 3, 20]
assert added == [10, 20] assert added == [10, 20]
lst[2][-1].append(8)
assert added == [10, 20, 8]
def test_delitem(self): def test_delitem(self):
lst = HookedList(None, existing=[1, 2, 3]) lst = HookedList([1, 2, 3])
del lst[1] del lst[1]
assert list(lst) == [1, 3] assert list(lst) == [1, 3]
def test_insert(self): def test_insert(self):
lst = HookedList(None, existing=[1, 3]) lst = HookedList([1, 3])
lst.insert(1, 2) lst.insert(1, 2)
assert list(lst) == [1, 2, 3] assert list(lst) == [1, 2, 3]