py-hamt 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_hamt-3.0.0/.github/workflows/pages-main.yaml +49 -0
- py_hamt-3.0.0/.github/workflows/run-checks.yaml +54 -0
- py_hamt-3.0.0/.gitignore +159 -0
- py_hamt-3.0.0/.pre-commit-config.yaml +32 -0
- py_hamt-3.0.0/.python-version +1 -0
- py_hamt-3.0.0/AGENTS.md +127 -0
- py_hamt-3.0.0/Architecture.md +213 -0
- py_hamt-3.0.0/LICENSE +201 -0
- py_hamt-3.0.0/PKG-INFO +129 -0
- py_hamt-3.0.0/README.md +115 -0
- py_hamt-3.0.0/py_hamt/__init__.py +13 -0
- py_hamt-3.0.0/py_hamt/encryption_hamt_store.py +182 -0
- py_hamt-3.0.0/py_hamt/hamt.py +696 -0
- py_hamt-3.0.0/py_hamt/store.py +269 -0
- py_hamt-3.0.0/py_hamt/zarr_hamt_store.py +252 -0
- py_hamt-3.0.0/pyproject.toml +41 -0
- py_hamt-3.0.0/pytest.ini +4 -0
- py_hamt-3.0.0/run-checks.sh +10 -0
- py_hamt-3.0.0/tests/conftest.py +35 -0
- py_hamt-3.0.0/tests/performance_tests.py +39 -0
- py_hamt-3.0.0/tests/test_branch_anchors.py +51 -0
- py_hamt-3.0.0/tests/test_extract_bits.py +38 -0
- py_hamt-3.0.0/tests/test_hamt.py +216 -0
- py_hamt-3.0.0/tests/test_kubo_cas.py +146 -0
- py_hamt-3.0.0/tests/test_kubocas_auth.py +75 -0
- py_hamt-3.0.0/tests/test_kubocas_session.py +102 -0
- py_hamt-3.0.0/tests/test_zarr_ipfs.py +191 -0
- py_hamt-3.0.0/tests/test_zarr_ipfs_encrypted.py +207 -0
- py_hamt-3.0.0/tests/testing_utils.py +197 -0
- py_hamt-3.0.0/uv.lock +2446 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Deploy static site generated by pdoc to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
pages: write
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
environment:
|
|
15
|
+
name: github-pages
|
|
16
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
pages: write
|
|
21
|
+
id-token: write
|
|
22
|
+
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- name: Checkout
|
|
27
|
+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
|
28
|
+
|
|
29
|
+
- name: Setup Pages
|
|
30
|
+
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
|
|
31
|
+
|
|
32
|
+
- name: Install uv
|
|
33
|
+
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6
|
|
34
|
+
with:
|
|
35
|
+
version: "latest"
|
|
36
|
+
|
|
37
|
+
- name: Create project environment
|
|
38
|
+
run: uv sync
|
|
39
|
+
|
|
40
|
+
- name: Build with pdoc
|
|
41
|
+
run: uv run pdoc py_hamt -o ./_site
|
|
42
|
+
|
|
43
|
+
- name: Upload artifact
|
|
44
|
+
# Automatically uploads an artifact from the './_site' directory by default
|
|
45
|
+
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
|
|
46
|
+
|
|
47
|
+
- name: Deploy to GitHub Pages
|
|
48
|
+
id: deployment
|
|
49
|
+
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Run checks
|
|
2
|
+
run-name: Triggered on push from ${{ github.actor }} to branch/tag ${{ github.ref_name }}
|
|
3
|
+
on: push
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
validate:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@v4
|
|
10
|
+
- uses: actions/setup-python@v5
|
|
11
|
+
with:
|
|
12
|
+
python-version: "3.12"
|
|
13
|
+
- uses: pre-commit/action@v3.0.1
|
|
14
|
+
test:
|
|
15
|
+
name: Create project environment, run all checks
|
|
16
|
+
needs:
|
|
17
|
+
- validate
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
strategy:
|
|
20
|
+
fail-fast: true
|
|
21
|
+
matrix:
|
|
22
|
+
python-version: ["3.12"]
|
|
23
|
+
steps:
|
|
24
|
+
- name: Check out repository
|
|
25
|
+
uses: actions/checkout@v4
|
|
26
|
+
- name: Set up python ${{ matrix.python-version }}
|
|
27
|
+
uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: ${{ matrix.python-version }}
|
|
30
|
+
|
|
31
|
+
- name: Install uv
|
|
32
|
+
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6
|
|
33
|
+
with:
|
|
34
|
+
version: "latest"
|
|
35
|
+
|
|
36
|
+
- name: Create project environment
|
|
37
|
+
run: uv sync
|
|
38
|
+
|
|
39
|
+
- name: Install IPFS
|
|
40
|
+
uses: oduwsdl/setup-ipfs@e92fedca9f61ab9184cb74940254859f4d7af4d9 # v0.6.3
|
|
41
|
+
with:
|
|
42
|
+
ipfs_version: "0.35.0"
|
|
43
|
+
run_daemon: true
|
|
44
|
+
|
|
45
|
+
- name: Run pytest with coverage
|
|
46
|
+
run: uv run pytest --ipfs --cov=py_hamt tests/ --cov-report=xml
|
|
47
|
+
|
|
48
|
+
- name: Upload coverage reports to Codecov
|
|
49
|
+
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5
|
|
50
|
+
with:
|
|
51
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
52
|
+
|
|
53
|
+
- name: Check coverage
|
|
54
|
+
run: uv run coverage report --fail-under=100 --show-missing
|
py_hamt-3.0.0/.gitignore
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
pyrightconfig.json
|
|
3
|
+
|
|
4
|
+
*.prof
|
|
5
|
+
memray*
|
|
6
|
+
|
|
7
|
+
# Byte-compiled / optimized / DLL files
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
*$py.class
|
|
11
|
+
|
|
12
|
+
# C extensions
|
|
13
|
+
*.so
|
|
14
|
+
|
|
15
|
+
# Distribution / packaging
|
|
16
|
+
.Python
|
|
17
|
+
build/
|
|
18
|
+
develop-eggs/
|
|
19
|
+
dist/
|
|
20
|
+
downloads/
|
|
21
|
+
eggs/
|
|
22
|
+
.eggs/
|
|
23
|
+
lib/
|
|
24
|
+
lib64/
|
|
25
|
+
parts/
|
|
26
|
+
sdist/
|
|
27
|
+
var/
|
|
28
|
+
wheels/
|
|
29
|
+
share/python-wheels/
|
|
30
|
+
*.egg-info/
|
|
31
|
+
.installed.cfg
|
|
32
|
+
*.egg
|
|
33
|
+
MANIFEST
|
|
34
|
+
|
|
35
|
+
# PyInstaller
|
|
36
|
+
# Usually these files are written by a python script from a template
|
|
37
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
38
|
+
*.manifest
|
|
39
|
+
*.spec
|
|
40
|
+
|
|
41
|
+
# Installer logs
|
|
42
|
+
pip-log.txt
|
|
43
|
+
pip-delete-this-directory.txt
|
|
44
|
+
|
|
45
|
+
# Unit test / coverage reports
|
|
46
|
+
htmlcov/
|
|
47
|
+
.tox/
|
|
48
|
+
.nox/
|
|
49
|
+
.coverage
|
|
50
|
+
.coverage.*
|
|
51
|
+
.cache
|
|
52
|
+
nosetests.xml
|
|
53
|
+
coverage.xml
|
|
54
|
+
*.cover
|
|
55
|
+
*.py,cover
|
|
56
|
+
.hypothesis/
|
|
57
|
+
.pytest_cache/
|
|
58
|
+
cover/
|
|
59
|
+
|
|
60
|
+
# Translations
|
|
61
|
+
*.mo
|
|
62
|
+
*.pot
|
|
63
|
+
|
|
64
|
+
# Django stuff:
|
|
65
|
+
*.log
|
|
66
|
+
local_settings.py
|
|
67
|
+
db.sqlite3
|
|
68
|
+
db.sqlite3-journal
|
|
69
|
+
|
|
70
|
+
# Flask stuff:
|
|
71
|
+
instance/
|
|
72
|
+
.webassets-cache
|
|
73
|
+
|
|
74
|
+
# Scrapy stuff:
|
|
75
|
+
.scrapy
|
|
76
|
+
|
|
77
|
+
# Sphinx documentation
|
|
78
|
+
docs/_build/
|
|
79
|
+
|
|
80
|
+
# PyBuilder
|
|
81
|
+
.pybuilder/
|
|
82
|
+
target/
|
|
83
|
+
|
|
84
|
+
# Jupyter Notebook
|
|
85
|
+
.ipynb_checkpoints
|
|
86
|
+
|
|
87
|
+
# IPython
|
|
88
|
+
profile_default/
|
|
89
|
+
ipython_config.py
|
|
90
|
+
|
|
91
|
+
# pyenv
|
|
92
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
93
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
94
|
+
# .python-version
|
|
95
|
+
|
|
96
|
+
# pipenv
|
|
97
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
98
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
99
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
100
|
+
# install all needed dependencies.
|
|
101
|
+
#Pipfile.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
#pdm.lock
|
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
114
|
+
# in version control.
|
|
115
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
116
|
+
.pdm.toml
|
|
117
|
+
|
|
118
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
119
|
+
__pypackages__/
|
|
120
|
+
|
|
121
|
+
# Celery stuff
|
|
122
|
+
celerybeat-schedule
|
|
123
|
+
celerybeat.pid
|
|
124
|
+
|
|
125
|
+
# SageMath parsed files
|
|
126
|
+
*.sage.py
|
|
127
|
+
|
|
128
|
+
# Environments
|
|
129
|
+
.env
|
|
130
|
+
.venv
|
|
131
|
+
env/
|
|
132
|
+
venv/
|
|
133
|
+
ENV/
|
|
134
|
+
env.bak/
|
|
135
|
+
venv.bak/
|
|
136
|
+
|
|
137
|
+
# Spyder project settings
|
|
138
|
+
.spyderproject
|
|
139
|
+
.spyproject
|
|
140
|
+
|
|
141
|
+
# Rope project settings
|
|
142
|
+
.ropeproject
|
|
143
|
+
|
|
144
|
+
# mkdocs documentation
|
|
145
|
+
/site
|
|
146
|
+
|
|
147
|
+
# mypy
|
|
148
|
+
.mypy_cache/
|
|
149
|
+
.dmypy.json
|
|
150
|
+
dmypy.json
|
|
151
|
+
|
|
152
|
+
# Pyre type checker
|
|
153
|
+
.pyre/
|
|
154
|
+
|
|
155
|
+
# pytype static type analyzer
|
|
156
|
+
.pytype/
|
|
157
|
+
|
|
158
|
+
# Cython debug symbols
|
|
159
|
+
cython_debug/
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/compilerla/conventional-pre-commit
|
|
3
|
+
rev: v4.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: conventional-pre-commit
|
|
6
|
+
stages: [commit-msg]
|
|
7
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
8
|
+
rev: v5.0.0
|
|
9
|
+
hooks:
|
|
10
|
+
- id: check-ast
|
|
11
|
+
- id: check-case-conflict
|
|
12
|
+
- id: check-merge-conflict
|
|
13
|
+
- id: check-toml
|
|
14
|
+
- id: debug-statements
|
|
15
|
+
- id: end-of-file-fixer
|
|
16
|
+
- id: mixed-line-ending
|
|
17
|
+
- id: trailing-whitespace
|
|
18
|
+
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
|
19
|
+
rev: v0.11.11
|
|
20
|
+
hooks:
|
|
21
|
+
- id: ruff-check
|
|
22
|
+
- id: ruff-format
|
|
23
|
+
- repo: https://github.com/ariebovenberg/slotscheck
|
|
24
|
+
rev: v0.17.1
|
|
25
|
+
hooks:
|
|
26
|
+
- id: slotscheck
|
|
27
|
+
name: slotscheck
|
|
28
|
+
entry: bash -c 'env PYTHONPATH=src slotscheck'
|
|
29
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
30
|
+
rev: "v1.15.0"
|
|
31
|
+
hooks:
|
|
32
|
+
- id: mypy
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
py_hamt-3.0.0/AGENTS.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Project Agents.md Guide for py-hamt
|
|
2
|
+
|
|
3
|
+
This AGENTS.md file provides comprehensive guidance for OpenAI Codex and other AI agents working with this codebase.
|
|
4
|
+
|
|
5
|
+
## Project Description
|
|
6
|
+
|
|
7
|
+
This library is a python implementation of a HAMT, inspired by [rvagg's IAMap project written in JavaScript](https://github.com/rvagg/iamap).
|
|
8
|
+
|
|
9
|
+
py-hamt provides efficient storage and retrieval of large sets of key-value mappings in a content-addressed storage system. The main target is IPFS, and the data model used is IPLD.
|
|
10
|
+
|
|
11
|
+
dClimate primarily created this for storing large [zarrs](https://zarr.dev/) on IPFS. To see this in action, see our [data ETLs](https://github.com/dClimate/etl-scripts).
|
|
12
|
+
|
|
13
|
+
## Project Architecture
|
|
14
|
+
|
|
15
|
+
A user instantiates a ContentAddressedStore such as KuboCAS which is then used to instantiate a HAMT. This HAMT can then be passed to a ZarrHAMTStore to read/write zarr files. An IPFS daemon running locally can be used or a remote one can be accessed via gateway(reads) & rpc endpoints(writes) on their IPFS defaults. For tests, a local docker container is spun up if docker is available.
|
|
16
|
+
|
|
17
|
+
## Project Structure for OpenAI Codex Navigation
|
|
18
|
+
|
|
19
|
+
- `/py_hamt`: Source code of the py-hamt library
|
|
20
|
+
- `encryption_hamt_store.py` - Example using total encryption
|
|
21
|
+
- `hamt.py`- where the HAMT data structure is constructed and accesed
|
|
22
|
+
- `store.py`- Where various stores live that writes the data. Primarily used KuboCAS
|
|
23
|
+
- `zarr_hamt_store.py` - ZarrHAMTStore class used to directly write or read zarrs leveraging the HAMT data structure onto a Store usually a Content Addressed store like KuboCAS located in store.py.
|
|
24
|
+
- `/tests`: Test files that should be maintained and extended where possible.
|
|
25
|
+
|
|
26
|
+
## Coding Conventions
|
|
27
|
+
|
|
28
|
+
### General Conventions for AGENTS.md Implementation
|
|
29
|
+
|
|
30
|
+
- Use the latest version of Python (3.12) for all new code generated by OpenAI Codex
|
|
31
|
+
- The AI Agent should follow the existing code style in each file
|
|
32
|
+
- Agents.md requires meaningful variable and function names
|
|
33
|
+
- The AI Agent should add comments for complex logic as guided by Agents.md
|
|
34
|
+
|
|
35
|
+
### Python Guidelines
|
|
36
|
+
- Use functional easy to understand functions wherever possible.
|
|
37
|
+
- All functions need to be fully typed with mypy.
|
|
38
|
+
- All functions require tests to be written and coverage must be full.
|
|
39
|
+
- Example usage can be found in the `/tests` folder
|
|
40
|
+
|
|
41
|
+
### Development Setup
|
|
42
|
+
|
|
43
|
+
`py-hamt` uses uv for package management. If uv is not already present which can be checked with `uv` then it can be installed via
|
|
44
|
+
|
|
45
|
+
`curl -LsSf https://astral.sh/uv/install.sh | sh`
|
|
46
|
+
|
|
47
|
+
otherwise if uv is already present you can run
|
|
48
|
+
|
|
49
|
+
`uv sync` to install all dependencies
|
|
50
|
+
|
|
51
|
+
and then
|
|
52
|
+
|
|
53
|
+
`source .venv/bin/activate` to activate the venv created by uv.
|
|
54
|
+
|
|
55
|
+
Lastly while still setting up you can run
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
pre-commit install
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
to ensure pre-commit installed.
|
|
62
|
+
|
|
63
|
+
Docker is also used for integration tests to test ipfs. Try to have docker installed to be able to run all tests with` pytest --ipfs`
|
|
64
|
+
|
|
65
|
+
## Testing Requirements for the Agent
|
|
66
|
+
|
|
67
|
+
All tests should be created within the `/tests` directory.
|
|
68
|
+
|
|
69
|
+
The agent should run tests with the following commands:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Run all tests
|
|
73
|
+
pytest --ipfs
|
|
74
|
+
|
|
75
|
+
# Run specific test file
|
|
76
|
+
pytest test tests/<insert_file_here>
|
|
77
|
+
|
|
78
|
+
# Run tests with coverage
|
|
79
|
+
pytest --ipfs --cov
|
|
80
|
+
|
|
81
|
+
# If IPFS is not present use
|
|
82
|
+
pytest --cov
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Tests should be kept as closed to 100% as possible.
|
|
86
|
+
|
|
87
|
+
## Pull Request Guidelines for the Agent
|
|
88
|
+
|
|
89
|
+
When the Agent helps create a PR, please ensure it:
|
|
90
|
+
|
|
91
|
+
1. Includes a clear description of the changes as guided by AGENTS.md
|
|
92
|
+
2. References any related issues that the Agent is addressing
|
|
93
|
+
3. Ensures all tests pass for code generated by the Agent
|
|
94
|
+
4. Keeps PRs focused on a single concern as specified in AGENTS.md
|
|
95
|
+
|
|
96
|
+
## Programmatic Checks for the Agent
|
|
97
|
+
|
|
98
|
+
Before submitting changes generated by the Agent, run:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
bash run-checks.sh
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Note:** This assumes ipfs is running as `run-checks.sh` runs with the ``--ipfs` flag. If kubo ipfs is not running in a local daemon or docker was unable to instantiate simply run
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
pytest --cov
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
and
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
uv run pre-commit run --all-files --show-diff-on-failure
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
instead.
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
If there is an error with formatting for ruff and it can be autofixed you can normally run
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
uv run ruff check --fix
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
to fix it.
|
|
126
|
+
|
|
127
|
+
All checks must pass before the Agent generated code can be merged. AGENTS.md helps ensure the Agent follows these requirements.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# py-hamt Library Architecture Analysis
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The py-hamt library is a Python implementation of a Hash Array Mapped Trie (HAMT) designed for content-addressed storage systems like IPFS. It provides efficient storage and retrieval of large key-value mappings using the IPLD data model.
|
|
6
|
+
|
|
7
|
+
## Core Architecture
|
|
8
|
+
|
|
9
|
+
### 1. HAMT Structure (`hamt.py`)
|
|
10
|
+
|
|
11
|
+
The core HAMT implementation follows a hierarchical trie structure where:
|
|
12
|
+
- **Nodes** contain 256 buckets (indexed 0-255)
|
|
13
|
+
- Each bucket can either be:
|
|
14
|
+
- A dictionary containing key-value mappings (when bucket size ≤ max_bucket_size)
|
|
15
|
+
- A link to a child Node (when bucket overflows)
|
|
16
|
+
- Hash values are consumed 8 bits at a time to determine bucket indices
|
|
17
|
+
|
|
18
|
+
#### Key Components:
|
|
19
|
+
|
|
20
|
+
**Node Class** (`hamt.py:62`)
|
|
21
|
+
- Fixed array of 256 elements representing buckets
|
|
22
|
+
- Uses `list[IPLDKind]` where empty dicts `{}` represent empty buckets
|
|
23
|
+
- Links are stored as single-element lists `[link_id]`
|
|
24
|
+
- Serializes to DAG-CBOR format for content addressing
|
|
25
|
+
|
|
26
|
+
**HAMT Class** (`hamt.py:287`)
|
|
27
|
+
- Main interface for trie operations
|
|
28
|
+
- Supports both read-only and read-write modes
|
|
29
|
+
- Uses asyncio locks for thread safety in write mode
|
|
30
|
+
- Implements two node storage strategies via NodeStore abstraction
|
|
31
|
+
|
|
32
|
+
### 2. Storage Abstraction (`store.py`)
|
|
33
|
+
|
|
34
|
+
**ContentAddressedStore** (`store.py:11`)
|
|
35
|
+
- Abstract base class for content-addressed storage backends
|
|
36
|
+
- Returns immutable IDs (IPLDKind) for stored content
|
|
37
|
+
- Two codec types: "raw" for data, "dag-cbor" for structured content
|
|
38
|
+
|
|
39
|
+
**KuboCAS** (`store.py:74`)
|
|
40
|
+
- IPFS implementation using Kubo daemon
|
|
41
|
+
- Uses RPC API for writes (`/api/v0/add`)
|
|
42
|
+
- Uses HTTP Gateway for reads (`/ipfs/{cid}`)
|
|
43
|
+
- Supports authentication and custom headers
|
|
44
|
+
- Implements connection pooling and concurrency limiting
|
|
45
|
+
|
|
46
|
+
**InMemoryCAS** (`store.py:37`)
|
|
47
|
+
- Testing implementation using in-memory dictionary
|
|
48
|
+
- Content-addressed via Blake3 hashing
|
|
49
|
+
|
|
50
|
+
### 3. Node Storage Strategies
|
|
51
|
+
|
|
52
|
+
**ReadCacheStore** (`hamt.py:150`)
|
|
53
|
+
- Used in read-only mode
|
|
54
|
+
- Implements LRU-style caching of loaded nodes
|
|
55
|
+
- Cannot perform writes (throws exception)
|
|
56
|
+
- Optimized for concurrent read operations
|
|
57
|
+
|
|
58
|
+
**InMemoryTreeStore** (`hamt.py:180`)
|
|
59
|
+
- Used in read-write mode
|
|
60
|
+
- Maintains modified nodes in memory buffer
|
|
61
|
+
- Uses UUID4 integers as temporary node IDs
|
|
62
|
+
- Implements sophisticated flush algorithm during `vacate()`
|
|
63
|
+
|
|
64
|
+
### 4. Zarr Integration (`zarr_hamt_store.py`)
|
|
65
|
+
|
|
66
|
+
**ZarrHAMTStore** (`zarr_hamt_store.py:11`)
|
|
67
|
+
- Implements Zarr v3 Store interface
|
|
68
|
+
- Provides metadata caching for `zarr.json` files
|
|
69
|
+
- Supports directory listing operations with efficient prefix matching
|
|
70
|
+
- Key insight: Zarr keys map directly to HAMT keys without transformation
|
|
71
|
+
|
|
72
|
+
**SimpleEncryptedZarrHAMTStore** (`encryption_hamt_store.py:12`)
|
|
73
|
+
- Extends ZarrHAMTStore with ChaCha20-Poly1305 encryption
|
|
74
|
+
- Encrypts all data including metadata
|
|
75
|
+
- Uses 24-byte nonces and 16-byte authentication tags
|
|
76
|
+
|
|
77
|
+
## Value Setting Mechanism & Data Flow
|
|
78
|
+
|
|
79
|
+
### Setting Values (`_set_pointer` in `hamt.py:505`)
|
|
80
|
+
|
|
81
|
+
1. **Hash and Navigate**: Key is hashed, bits extracted to determine path
|
|
82
|
+
2. **Queue-based Insertion**: Uses FIFO queue to handle bucket overflows
|
|
83
|
+
3. **Bucket Overflow Handling**: When bucket exceeds max_bucket_size:
|
|
84
|
+
- All existing items moved to queue for reinsertion
|
|
85
|
+
- New child node created and linked
|
|
86
|
+
- Continues insertion process at deeper level
|
|
87
|
+
4. **Tree Rebalancing**: After insertion, `_reserialize_and_link` updates all affected nodes
|
|
88
|
+
|
|
89
|
+
### Getting Values (`_get_pointer` in `hamt.py:618`)
|
|
90
|
+
|
|
91
|
+
1. **Hash Traversal**: Follow hash-determined path through trie
|
|
92
|
+
2. **Bucket Search**: Check final bucket for key
|
|
93
|
+
3. **Pointer Resolution**: Return content-addressed pointer
|
|
94
|
+
4. **Value Retrieval**: Load actual value from CAS using pointer
|
|
95
|
+
|
|
96
|
+
### Memory Management
|
|
97
|
+
|
|
98
|
+
**Read-Write Mode**:
|
|
99
|
+
- Uses `InMemoryTreeStore` with UUID-based temporary IDs
|
|
100
|
+
- Modified nodes stay in memory until `make_read_only()` or `cache_vacate()`
|
|
101
|
+
- Flush algorithm uses DFS to preserve parent-child relationships
|
|
102
|
+
|
|
103
|
+
**Read-Only Mode**:
|
|
104
|
+
- Switches to `ReadCacheStore` for better read performance
|
|
105
|
+
- Allows concurrent operations without locks
|
|
106
|
+
- Cache size can be monitored and manually vacated
|
|
107
|
+
|
|
108
|
+
## Architecture Gotchas & Edge Cases
|
|
109
|
+
|
|
110
|
+
### 1. Mode Switching Complexity
|
|
111
|
+
- **Problem**: HAMT can switch between read-only and read-write modes
|
|
112
|
+
- **Gotcha**: In read-write mode, `root_node_id` is invalid until `make_read_only()`
|
|
113
|
+
- **Why**: InMemoryTreeStore uses temporary UUIDs that aren't content-addressed
|
|
114
|
+
- **Solution**: Always call `make_read_only()` before reading `root_node_id`
|
|
115
|
+
|
|
116
|
+
### 2. Thread Safety Limitations
|
|
117
|
+
- **Problem**: Only async-safe, not thread-safe in write mode
|
|
118
|
+
- **Gotcha**: Multiple threads writing simultaneously can corrupt state
|
|
119
|
+
- **Why**: Uses asyncio.Lock, not threading.Lock
|
|
120
|
+
- **Solution**: Use single event loop for all write operations
|
|
121
|
+
|
|
122
|
+
### 3. Hash Function Constraints
|
|
123
|
+
- **Problem**: Hash must be multiple of 8 bits (bytes)
|
|
124
|
+
- **Gotcha**: Custom hash functions with odd bit lengths will fail
|
|
125
|
+
- **Why**: `extract_bits` assumes byte-aligned hash values
|
|
126
|
+
- **Solution**: Ensure hash functions return byte-aligned results
|
|
127
|
+
|
|
128
|
+
### 4. Bucket Size Tuning
|
|
129
|
+
- **Problem**: max_bucket_size affects performance vs memory trade-offs
|
|
130
|
+
- **Gotcha**: Very small bucket sizes (1) force deep trees, large sizes create big nodes
|
|
131
|
+
- **Why**: Small buckets = more CAS operations, large buckets = bigger serialized nodes
|
|
132
|
+
- **Solution**: Test with your specific workload (default 4 is reasonable)
|
|
133
|
+
|
|
134
|
+
### 5. Empty Node Pruning
|
|
135
|
+
- **Problem**: Deletions can leave empty nodes in tree
|
|
136
|
+
- **Gotcha**: Empty nodes are automatically pruned except root
|
|
137
|
+
- **Why**: Content addressing means identical empty nodes have same hash
|
|
138
|
+
- **Solution**: Pruning is automatic, but be aware root can become empty
|
|
139
|
+
|
|
140
|
+
### 6. Serialization Edge Cases
|
|
141
|
+
- **Problem**: InMemoryTreeStore nodes contain non-serializable UUID links
|
|
142
|
+
- **Gotcha**: Attempting to serialize buffer nodes directly will fail
|
|
143
|
+
- **Why**: UUID4.int values exceed DAG-CBOR integer limits
|
|
144
|
+
- **Solution**: Always use `vacate()` or `make_read_only()` before accessing serialized form
|
|
145
|
+
|
|
146
|
+
### 7. CAS ID Immutability Requirement
|
|
147
|
+
- **Problem**: CAS implementations must return immutable IDs
|
|
148
|
+
- **Gotcha**: Using mutable objects (lists, dicts) as IDs breaks assumptions
|
|
149
|
+
- **Why**: HAMT uses ID equality checks and as dictionary keys
|
|
150
|
+
- **Solution**: Ensure CAS returns immutable types (bytes, CID, int, str, etc.)
|
|
151
|
+
|
|
152
|
+
### 8. Zarr Metadata Caching
|
|
153
|
+
- **Problem**: ZarrHAMTStore caches `zarr.json` files
|
|
154
|
+
- **Gotcha**: Cache isn't invalidated on writes, can become stale
|
|
155
|
+
- **Why**: Zarr frequently re-reads metadata, caching improves performance
|
|
156
|
+
- **Solution**: Cache is updated on writes, but be aware of this optimization
|
|
157
|
+
|
|
158
|
+
### 9. Concurrent Operations in Read Mode
|
|
159
|
+
- **Problem**: Read-only mode allows concurrent access
|
|
160
|
+
- **Gotcha**: Switching to write mode while reads are happening is unsafe
|
|
161
|
+
- **Why**: Mode switch changes internal data structures
|
|
162
|
+
- **Solution**: Ensure all operations complete before mode switches
|
|
163
|
+
|
|
164
|
+
### 10. Key Encoding Assumptions
|
|
165
|
+
- **Problem**: Keys are encoded as UTF-8 bytes for hashing
|
|
166
|
+
- **Gotcha**: Non-UTF-8 string keys or binary keys need special handling
|
|
167
|
+
- **Why**: `key.encode()` assumes UTF-8 encoding
|
|
168
|
+
- **Solution**: Ensure keys are valid UTF-8 strings or modify key handling
|
|
169
|
+
|
|
170
|
+
## Performance Characteristics
|
|
171
|
+
|
|
172
|
+
### Time Complexity
|
|
173
|
+
- **Get/Set/Delete**: O(log₂₅₆ n) average case, O(depth) worst case
|
|
174
|
+
- **Iteration**: O(n) for all keys/values
|
|
175
|
+
- **Tree depth**: Typically 1-4 levels for most datasets
|
|
176
|
+
|
|
177
|
+
### Space Complexity
|
|
178
|
+
- **Node overhead**: ~2KB per node (256 × 8-byte pointers)
|
|
179
|
+
- **Memory efficiency**: Improves with higher bucket sizes
|
|
180
|
+
- **CAS efficiency**: Content addressing deduplicates identical subtrees
|
|
181
|
+
|
|
182
|
+
### Concurrency
|
|
183
|
+
- **Read-only mode**: Full concurrency support
|
|
184
|
+
- **Write mode**: Single-writer, async-safe
|
|
185
|
+
- **Mode switching**: Blocking operation requiring exclusive access
|
|
186
|
+
|
|
187
|
+
## Integration Patterns
|
|
188
|
+
|
|
189
|
+
### 1. IPFS Integration
|
|
190
|
+
```python
|
|
191
|
+
kubo_cas = KuboCAS()
|
|
192
|
+
hamt = await HAMT.build(cas=kubo_cas)
|
|
193
|
+
await hamt.set("key", "value")
|
|
194
|
+
await hamt.make_read_only()
|
|
195
|
+
cid = hamt.root_node_id # IPFS CID
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 2. Zarr Storage
|
|
199
|
+
```python
|
|
200
|
+
hamt = await HAMT.build(cas=kubo_cas, values_are_bytes=True)
|
|
201
|
+
zarr_store = ZarrHAMTStore(hamt, read_only=False)
|
|
202
|
+
dataset.to_zarr(store=zarr_store, zarr_format=3)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 3. Encrypted Storage
|
|
206
|
+
```python
|
|
207
|
+
encryption_key = get_random_bytes(32)
|
|
208
|
+
encrypted_store = SimpleEncryptedZarrHAMTStore(
|
|
209
|
+
hamt, read_only=False, encryption_key=encryption_key, header=b"app-name"
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The py-hamt library provides a robust, efficient implementation of HAMTs for content-addressed storage with careful attention to memory management, concurrency, and integration patterns. Understanding these architectural details and gotchas is crucial for successful implementation in production systems.
|