diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8d0aad9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "[python]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, + "editor.defaultFormatter": "charliermarsh.ruff" + }, + "notebook.formatOnSave.enabled": true, + "notebook.codeActionsOnSave": { + "notebook.source.fixAll": "explicit", + "notebook.source.organizeImports": "explicit" + }, +} diff --git a/pyproject.toml b/pyproject.toml index 676d476..e0834d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,3 +19,24 @@ dev = [ "rich>=14.3.3", "ruff>=0.15.2", ] + +[tool.ruff] +line-length = 120 +target-version = "py312" + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "SIM", # flake8-simplify +] +extend-fixable = ["ALL"] +ignore = ["UP046", "UP047"] + +[tool.ruff.lint.isort] +known-first-party = ["hooked_containers"] diff --git a/src/hooked_containers/list.py b/src/hooked_containers/list.py index 542da1a..58b9099 100644 --- a/src/hooked_containers/list.py +++ b/src/hooked_containers/list.py @@ -1,10 +1,72 @@ +from collections.abc import Callable, MutableSequence +from dataclasses import dataclass +from enum import Enum, auto from typing import Generic, TypeVar -T = TypeVar('T') +T = TypeVar("T") -class HookedList(Generic[T], list[T]): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + +class ListChange(Enum): + ADD_ITEM = auto() + REMOVE_ITEM = auto() + SET_ITEM = auto() + + +@dataclass(frozen=True) +class ListChangeEvent(Generic[T]): + index: int + + +@dataclass(frozen=True) +class AddItemEvent(ListChangeEvent[T]): + item: T + + +@dataclass(frozen=True) +class SetItemEvent(ListChangeEvent[T]): + item: T + + +@dataclass(frozen=True) +class RemoveItemEvent(ListChangeEvent[T]): + item: T + + +class HookedList(Generic[T], MutableSequence[T]): + elements: list[T] + hook: Callable[[ListChangeEvent[T]], None] | None + + def __init__(self, *args, hook=None, **kwargs): + self.elements = list(*args, **kwargs) + self.hook = hook def __repr__(self): - return f'{self.__class__.__name__}({super().__repr__()})' + return f"{self.__class__.__name__}({self.elements!r})" + + def __iter__(self): + return iter(self.elements) + + def __contains__(self, value): + return value in self.elements + + def __len__(self): + return len(self.elements) + + def __getitem__(self, s): + print("Getting item:", s) + return self.elements[s] + + def __setitem__(self, s, value): + self.elements[s] = value + if self.hook: + self.hook(SetItemEvent(index=s, item=value)) + + def __delitem__(self, s): + del self.elements[s] + if self.hook: + self.hook(RemoveItemEvent(index=s, item=self.elements[s])) + + def insert(self, index, value): + self.elements.insert(index, value) + if self.hook: + self.hook(AddItemEvent(index=index, item=value))