https://github.com/betterbuiltfool/weaktree
Provides a tree structure that doesn't keep its data alive, and self prunes.
https://github.com/betterbuiltfool/weaktree
Last synced: 7 months ago
JSON representation
Provides a tree structure that doesn't keep its data alive, and self prunes.
- Host: GitHub
- URL: https://github.com/betterbuiltfool/weaktree
- Owner: BetterBuiltFool
- License: mit
- Created: 2025-07-09T10:31:52.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-07-09T10:46:22.000Z (9 months ago)
- Last Synced: 2025-07-09T11:53:16.148Z (9 months ago)
- Language: Python
- Size: 4.88 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url]
[![Stargazers][stars-shield]][stars-url]
[![Issues][issues-shield]][issues-url]
[![MIT License][license-shield]][license-url]
Weaktree
Model Data Trees Without Worrying About Lifetimes
Explore the docs »
Report Bug
·
Request Feature
Table of Contents
## About The Project
Weaktree provides tree nodes that don't make strong references to their data, and can clean themselves up when their data expires. WeakTreeNodes also provide several handy iteration method for easy traversal.
## Getting Started
Weaktree is written in pure python, with no system dependencies, and should be OS-agnostic.
### Installation
Weaktree can be installed from the [PyPI][pypi-url] using [pip][pip-url]:
```sh
pip install weaktree
```
and can be imported for use with:
```python
import weaktree
```
## Usage
Trees are built out of WeakTreeNodes. WeakTreeNodes possess a `trunk` and `branches`, which are used to define the hierarchy of the tree. Nodes with a trunk of `None` are considered root nodes.
### Creating WeakTrees
Root nodes can be created with the WeakTreeNode constructor.
```python
root = WeakTreeNode(some_object)
```
Branches can be added either by creating new nodes the same way and passing another node as the `trunk`, or by using WeakTreeNode.add_branch().
```python
root.add_branch(another_object)
```
WeakTreeNode.add_branch() returns the new node, so they can be chained.
```python
root.add_branch(third_object).add_branch(fourth_object).add_branch(fifth_object)
```
Trees can also be reorganized by assigning to the `trunk` property.
### Accessing Data
The stored data of a Node is accessed by the `data` property. This will dereference the weakref, and return either the data object, or None if the object has expired.
Alternatively, if iterating over the tree by the `values()` method, the iterand will be the value from `data`.
### Cleanup
WeakTreeNodes possess the `cleanup_mode` property. This is used to how the tree modifies itself when a Node's data reference expires. These values are accessible as constants in the WeakTreeNode class.
#### Prune
When the node expires, all descending nodes are removed from the tree.
Note: If there are strong references to those nodes elsewhere, they will be kept alive and the branches will not be fully unwound. However, they will no longer be reachable from other nodes in the tree.
#### Reparent
When the node expires, shift its branches up to its trunk. All descending nodes will be shifted up in the hierarchy.
#### No cleanup
When a node expires, leave the tree intact. Instead, the node will be "empty" and report a value of `None`.
#### Default
A node will do whatever its trunk node would do when it expires. For root nodes, this will be `prune`.
#### Callbacks
WeakTreeNodes feature an optional callback parameter for each node. The callback must take a weakreference object as its parameter. This will be the reference to the data value, just before it expires.
### Tree Iteration
WeakTreeNodes provide several features to simplify traversal. All branch iteration follows insertion order of the branch nodes.
You can control the type of data provided in the iteration:
#### By Nodes
By using WeakTreeNode.nodes() or by directly iteration over the tree, you can traverse over the nodes themselves.
This is comparable to the `keys()` method of dictionaries.
Example:
```python
root = WeakTreeNode(some_object)
root.add_branch(one_fish).add_branch(two_fish)
root.add_branch(red_fish).add_branch(blue_fish)
for node in root.nodes(): # <- Same as `for node in root:`
print(node)
# Expected order: Node(some_object), Node(one_fish), Node(red_fish), Node(two_fish), Node(blue_fish)
```
#### By Values
By using WeakTreeNode.values(), you can iterate over the values of the nodes. This is comparable to the method of the same name in dictionaries.
Example:
```python
root = WeakTreeNode(some_object)
root.add_branch(one_fish).add_branch(two_fish)
root.add_branch(red_fish).add_branch(blue_fish)
for node in root.values():
print(node)
# Expected output: some_object, one_fish, red_fish, two_fish, blue_fish
```
#### By Items
WeakTreeNode.items() provides an iterable that gives the pairs of nodes and values. This is comparable to the method of the same name in dictionaries.
You can also control _how_ the iteration traverses the tree.
#### By Breadth
By using the `breadth()` method of any iterable, or directly iterating over one, you can specify that the traversal should happen in a breadth-first order, i.e. all nodes of the same level in the hierarchy are processed before moving on to the next level.
Example:
```python
root = WeakTreeNode(some_object)
root.add_branch(one_fish).add_branch(two_fish)
root.add_branch(red_fish).add_branch(blue_fish)
for node in root.nodes().breadth(): # <- Same as `for node in root:`
print(node)
# Expected order: Node(some_object), Node(one_fish), Node(red_fish), Node(two_fish), Node(blue_fish)
```
#### By Depth
By using the `depth()` method of any iterable, you can specify that the traversal should happen in a dpeth-first order, i.e. branches are followed to their furthest reaches before moving on to side branches.
Example:
```python
root = WeakTreeNode(some_object)
root.add_branch(one_fish).add_branch(two_fish)
root.add_branch(red_fish).add_branch(blue_fish)
for node in root.nodes().depth():
print(node)
# Expected order: Node(some_object), Node(one_fish), Node(two_fish), Node(red_fish), Node(blue_fish)
```
#### Towards the Root
By using the `towards_root()` method of any iterable, you can iterate towards the root of the tree.
Example:
```python
root = WeakTreeNode(some_object)
two_fish_node = root.add_branch(one_fish).add_branch(two_fish)
root.add_branch(red_fish).add_branch(blue_fish)
for node in two_fish_node.nodes().towards_root():
print(node)
# Expected order: Node(two_fish), Node(one_fish), Node(some_object)
```
## API Reference
```python
class weaktree.WeakTreeNode(data: Any, trunk: WeakTreeNode | None, cleanup_mode: CleanupMode, callback: Callable | None)
```
The base unit of a weak tree. Stores a weak reference to its data, and cleans itself up per the cleanup mode when that data expires. If a callback is provided, that will be called when the reference expires as well.
```python
method __init__(data: Any, trunk: WeakTreeNode | None, cleanup_mode: CleanupMode, callback: Callable | None) -> None
```
Creates a new WeakTreeNode, storing the passed _data_, and as a branch of _trunk_, if trunk is provided. Optionally, cleanup mode can be specified to determine how the node will behave when the data expires. Additionally, the optional callback can allow further customization of cleanup behavior.
```python
property branches: set[WeakTreeNode]
```
Read-only.
A set representing any branches that descend from the node.
```python
property cleanup_mode: CleanupMode
```
An enum value that determines how the node will clenaup after itself when its data expires.
```python
property data: Any | None
```
The stored data. When called, dereferences and returns either a strong reference to the data, or None if the data has expired.
```python
property trunk: WeakTreeNode | Node
```
The previous node in the tree. If `None`, the node is considered a root.
```python
add_branch(data: Any, cleanup_mode: CleanupMode, callback: Callable | None) -> WeakTreeNode
```
Creates a new node as a branch of the calling node.
```python
breadth() -> Iterator[WeakTreeNode]
```
Returns an iterator that will traverse the tree by nodes, in a breadth-first pattern, starting at the calling node. Branches will be traversed in insertion order.
```python
depth() -> Iterator[WeakTreeNode]
```
Returns an iterator that will traverse the tree by nodes, in a depth-first pattern, starting at the calling node. Branches will be traversed in insertion order.
```python
towards_root() -> Iterator[WeakTreeNode]
```
Returns an iterator that will traverse the tree by nodes, up to the root, starting at the calling node.
```python
nodes() -> NodeIterable
```
Creates an iterable that allows for traversing the tree by nodes. Has the same breadth(), depth(), and towards_root() methods as WeakTreeNode
```python
values() -> ValueIterable
```
Creates an iterable that allows for traversing the tree by data values. Has the same breadth(), depth(), and towards_root() methods as WeakTreeNode
```python
items() -> ItemsIterable
```
Creates an iterable that allows for traversing the tree by node/value pairs. Has the same breadth(), depth(), and towards_root() methods as WeakTreeNode
## License
Distributed under the MIT License. See `LICENSE.txt` for more information.
## Contact
Better Built Fool - betterbuiltfool@gmail.com
Bluesky - [@betterbuiltfool.bsky.social](https://bsky.app/profile/betterbuiltfool.bsky.social)
Project Link: [https://github.com/BetterBuiltFool/weaktree](https://github.com/BetterBuiltFool/weaktree)
[contributors-shield]: https://img.shields.io/github/contributors/BetterBuiltFool/weaktree.svg?style=for-the-badge
[contributors-url]: https://github.com/BetterBuiltFool/weaktree/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/BetterBuiltFool/weaktree.svg?style=for-the-badge
[forks-url]: https://github.com/BetterBuiltFool/weaktree/network/members
[stars-shield]: https://img.shields.io/github/stars/BetterBuiltFool/weaktree.svg?style=for-the-badge
[stars-url]: https://github.com/BetterBuiltFool/weaktree/stargazers
[issues-shield]: https://img.shields.io/github/issues/BetterBuiltFool/weaktree.svg?style=for-the-badge
[issues-url]: https://github.com/BetterBuiltFool/weaktree/issues
[license-shield]: https://img.shields.io/github/license/BetterBuiltFool/weaktree.svg?style=for-the-badge
[license-url]: https://github.com/BetterBuiltFool/weaktree/blob/main/LICENSE
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
[linkedin-url]: https://linkedin.com/in/linkedin_username
[product-screenshot]: images/screenshot.png
[Next.js]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white
[Next-url]: https://nextjs.org/
[python.org]: https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54
[python-url]: https://www.python.org/
[React.js]: https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB
[React-url]: https://reactjs.org/
[Vue.js]: https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs&logoColor=4FC08D
[Vue-url]: https://vuejs.org/
[Angular.io]: https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white
[Angular-url]: https://angular.io/
[Svelte.dev]: https://img.shields.io/badge/Svelte-4A4A55?style=for-the-badge&logo=svelte&logoColor=FF3E00
[Svelte-url]: https://svelte.dev/
[Laravel.com]: https://img.shields.io/badge/Laravel-FF2D20?style=for-the-badge&logo=laravel&logoColor=white
[Laravel-url]: https://laravel.com
[Bootstrap.com]: https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white
[Bootstrap-url]: https://getbootstrap.com
[JQuery.com]: https://img.shields.io/badge/jQuery-0769AD?style=for-the-badge&logo=jquery&logoColor=white
[JQuery-url]: https://jquery.com
[pypi-url]: https://pypi.org/project/weaktree
[pip-url]: https://pip.pypa.io/en/stable/