diff --git a/README.md b/README.md index e69de29..98ddbf4 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,49 @@ +# 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.