# Hooked Containers Hooked Containers provides an abstract container foundation that calls a function hook whenever its contents are changed through its API. The aim is to retain familiar Python container operations while letting application code react to additions, replacements, and removals. ## Mixin Design The design follows Python's [collections.abc abstract base classes](https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes). Separate mixin classes should build up functionality in layers, so a custom container can compose the behavior it needs: | Layer | Responsibility | | ---------------- | ------------------------------------------------------------------------------------------- | | Container | Membership testing through `__contains__`. | | Collection | Iteration and size through `__iter__` and `__len__`, alongside membership. | | Hook support | A shared callback and change-event reporting for mutation methods. | | Sequence | Indexed access through `__getitem__`, with sequence helpers such as `index` and `count`. | | Mutable sequence | Assignment, deletion, and insertion, supporting helpers such as `append`, `extend`, and `pop`. | | Mapping | Key-based access, iteration over keys, and helpers such as `keys`, `items`, and `values`. | | Mutable mapping | Assignment and deletion, supporting helpers such as `update`, `setdefault`, and `pop`. | Sequences and mappings are separate branches built on the common container foundation, not successive steps in a single inheritance chain. Mutation hooks belong in the primitive operations so inherited ABC mixin methods also report changes. A compound operation may therefore invoke the hook more than once. This layered separation is the intended architecture. Currently, `HookedContainer` combines shared container and hook behavior; `HookedList` and `HookedMapping` extend it with the standard library's `MutableSequence` and `MutableMapping` mixins, respectively. ## Change Hooks A hook receives one change-event object after a mutation. The event identifies the root container (`root`), the index or key path (`path`), and the affected value (`item`). The current event types are `AddItemEvent`, `SetItemEvent`, and `RemoveItemEvent`. ```python from hooked_containers.mapping import HookedMapping from hooked_containers.sequence import HookedList def on_change(event): print(type(event).__name__, event.path, event.item) items = HookedList(["first"], hook=on_change) items.append("second") # AddItemEvent (1,) second items[0] = "updated" # SetItemEvent (0,) updated del items[1] # RemoveItemEvent (1,) second settings = HookedMapping({}, hook=on_change) settings["enabled"] = True # AddItemEvent ('enabled',) True ``` The wrappers can share existing mutable storage. Mutating that storage directly bypasses the hook; make changes through the hooked container to receive events. Hooks are optional, and nested wrappers retain the root and path for supported nested container types. ## Status Requires Python 3.12 or newer. This repository is an early implementation of the container and mixin design; the API and separation of responsibilities may evolve.