50 lines
3.4 KiB
Markdown
50 lines
3.4 KiB
Markdown
# 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.
|