Skip to main content

fsspec backend for Ethereum Swarm (bzz://) via the Bee HTTP API

Project description

swarmfs

An fsspec backend for Ethereum Swarm, talking to a Bee node (or public gateway) over its HTTP API. Installing it makes Swarm a first-class storage backend for the Python data ecosystem — pandas, dask, zarr, xarray, pyarrow, DuckDB — via URLs like bzz://<reference>/path/to/file.parquet.

Status: v2. Read-only bzz:// access, transactional copy-on-write writes (postage stamps, every commit a snapshot), and mutable feed-backed bzzf:// mounts. See the roadmap.

New to swarmfs? This README is a quick reference — the User Guide walks through a worked example for every library above (pandas, all three Dask collection types, Zarr, xarray, PyArrow, DuckDB) and explains the content-addressing model in plain terms.

Install

pip install swarmfs            # or: pip install "swarmfs[feeds]" for signed feeds

Upload and download a file

You need a running Bee light node (http://localhost:1633 by default) and, for uploads, a usable postage stamp:

import fsspec

fs = fsspec.filesystem("bzz", stamp="auto")

ref = fs.upload("photo.jpg")                    # → "c0ffee…" (64 hex chars)
fs.download(f"bzz://{ref}/photo.jpg", "copy.jpg")

On Swarm the address of new content is the result of a write, not its input — upload returns the new reference, and that reference is permanent: it names this exact content forever. Directories work the same way and come back as a single reference for the whole tree:

ref = fs.upload("dataset/")
fs.ls(f"bzz://{ref}")
fs.download(f"bzz://{ref}", "dataset-copy/", recursive=True)

upload accepts content_type= (otherwise guessed from the filename), encrypt=True (single files; the returned 128-hex reference includes the decryption key), and redundancy=0–4 (erasure coding, default 2). The stamp is validated before any byte moves, so a missing or expired stamp fails immediately with an actionable error.

The data ecosystem

The point of being an fsspec backend: everything that speaks fsspec now speaks Swarm, with zero extra code.

import pandas as pd

df = pd.read_parquet("bzz://<64-hex-reference>/data.parquet")

# local caching via URL chaining
df = pd.read_parquet("simplecache::bzz://<reference>/big.parquet")
import fsspec

fs = fsspec.filesystem("bzz")          # api_url=..., default $BEE_API_URL or localhost:1633
fs.ls("bzz://<reference>/")            # client-side Mantaray trie walk
fs.find("bzz://<reference>/dataset/")  # recursive listing (dask uses this)
fs.cat("bzz://<reference>/hello.txt")

with fs.open("bzz://<reference>/big.parquet", block_size=2**20) as f:
    f.seek(-8, 2)                      # range requests: only the bytes you touch
    f.read(8)

Same story for any other fsspec-based tool — Intake, DVC, Kedro, pyxet, Hugging Face Datasets, petl, and more (see the User Guide).

Transactional writes

For anything beyond a one-shot upload — building a dataset in place, changing one file inside a large collection — writes are copy-on-write commits: each commit patches the manifest trie client-side, re-uploads only what changed, and yields a new root. Old roots are untouched, so every commit is a snapshot.

fs = fsspec.filesystem("bzz", stamp="auto")
with fs.transaction:
    fs.pipe_file("bzz://new/dataset/a.parquet", data_a)
    fs.pipe_file("bzz://new/dataset/b.parquet", data_b)
root = fs.latest("new")          # share this reference; it never changes

Mutable feeds (bzzf://)

A feed gives you a stable URL whose contents you can update — the mutable filesystem on top of immutable commits:

ffs = fsspec.filesystem("bzzf", stamp="auto", signer="<private key hex>")
ffs.pipe_file(f"bzzf://{owner}/my-app/config.json", b'{"v": 2}')
# readers need no keys — and the URL never changes

Addressing content offline, and buying stamps

Two things you can do without uploading anything:

import swarmfs

ref = swarmfs.content_address(open("photo.jpg", "rb").read())   # no node needed
root, chunks = swarmfs.split(data)   # the whole chunk tree, keyed by address

content_address computes the reference Bee would return for those bytes, so you can check whether the network already has content, name blobs by their Swarm address in a local store, or build test fixtures at real addresses. It needs the feeds extra (keccak256).

One caveat that bites in practice: this is the reference for a plain upload. Erasure coding changes every intermediate chunk and therefore the root, and parity is the node's to generate — so a redundant upload's reference cannot be predicted offline. swarmfs writes with redundancy=2 by default and many nodes default to redundancy too, so pass redundancy=0 if you want the uploaded reference to match the one you computed.

Stamps can also be handled programmatically — selection is automatic (stamp="auto" picks the usable batch with the longest life), and purchase is available but never implicit:

from swarmfs.stamps import StampManager

plan = await mgr.plan(size_bytes, ttl_secs)   # depth, amount, cost in xBZZ
batch = await mgr.buy(plan.amount, plan.depth)  # spends the node wallet's xBZZ

plan sizes the batch for your upload (bucket-overflow-aware) and prices it from the chain; buy purchases and waits until the batch is usable. Nothing in swarmfs buys on its own — deciding to spend is the caller's.

Which API should I use?

Three tiers, all backed by the same endpoint resolution (api_url=...$BEE_API_URLhttp://localhost:1633):

  • SwarmFileSystem / fsspec URLs — the default. Filesystem semantics, transactions, verification, and the whole data ecosystem for free.

  • swarmfs.SyncSwarmClient / swarmfs.SwarmClient — direct calls against the Bee API (upload a blob, fetch bytes, post a feed update) without filesystem semantics. SyncSwarmClient is the blocking twin for plain scripts; SwarmClient is the same surface as coroutines for asyncio code:

    from swarmfs import SyncSwarmClient
    
    with SyncSwarmClient() as client:            # async? use SwarmClient + await
        ref = client.bzz_post(open("photo.jpg", "rb"), stamp=batch_id)
        data = client.bzz_get(ref, "photo.jpg")
    
  • Raw HTTP — the Bee API is plain HTTP; no library needed:

    curl -X POST -H "Swarm-Postage-Batch-Id: <batch>" \
         --data-binary @photo.jpg http://localhost:1633/bzz?name=photo.jpg
    

    What the library adds over this: stamp validation up front, chunk verification, gateway policy, better errors — the edge cases.

Nodes, gateways, verification

The recommended setup is a local light node — reads then come straight from the network with nothing to trust in between. Pointing api_url at a public gateway is discouraged and requires an explicit allow_gateway=True — on that path swarmfs verifies every fetched chunk client-side against its BMT address (a Swarm reference is the content hash), so even an untrusted gateway can't tamper with what you read. Verification can also be forced on/off with verify=True/False.

How it works

Swarm has no server-side directory listing today, so swarmfs parses the binary Mantaray manifest trie itself, fetching nodes on demand via /bytes (see swarmfs/mantaray/ — a self-contained pure-Python codec). File reads resolve the path to its data reference once, then use HTTP range requests against /bytes, which is what makes Parquet predicate pushdown and zarr chunk reads viable. When Bee grows a server-side listing endpoint (ethersphere/bee#5535) it will slot in behind the existing capability seam with no API change.

Compared to ipfsspec

ipfsspec, the closest analog in the fsspec ecosystem, is read-only by its own admission. Postage stamps make paid writes tractable on Swarm, so swarmfs adds a transactional write path and, via bzzf:// feeds, a stable URL you can actually mutate — not just read.

Development

pip install -e ".[test]"
pytest                                   # offline unit tests (no node needed)
SWARMFS_TEST_BEE=http://localhost:1633 \
SWARMFS_TEST_STAMP=<batch-id> pytest tests/test_integration.py

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

swarmfs-0.2.0.tar.gz (94.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

swarmfs-0.2.0-py3-none-any.whl (48.9 kB view details)

Uploaded Python 3

File details

Details for the file swarmfs-0.2.0.tar.gz.

File metadata

  • Download URL: swarmfs-0.2.0.tar.gz
  • Upload date:
  • Size: 94.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for swarmfs-0.2.0.tar.gz
Algorithm Hash digest
SHA256 0508c8a7fb79380a9a8cae644d95d39f25dd0e645a7abff4164ed76d6f86dfcc
MD5 7c5c79c5891d0b6cb7c07fdae3240a5d
BLAKE2b-256 a3d51f4e66f623327f2d988a96cbb6462b2760db81e0df249fbd6f94029806db

See more details on using hashes here.

Provenance

The following attestation bundles were made for swarmfs-0.2.0.tar.gz:

Publisher: publish.yml on petfold/swarmfs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file swarmfs-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: swarmfs-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 48.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for swarmfs-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 182cfa6e213fe26874c30c9e0ec274f765ab19c6be5b0d049df472bdecc5ba46
MD5 8f52fb02bda991c4596baac946525ff0
BLAKE2b-256 6a9e3e28e6a3e85870555b3815ae3f38084ecc1c998031b8392edc598ec79d19

See more details on using hashes here.

Provenance

The following attestation bundles were made for swarmfs-0.2.0-py3-none-any.whl:

Publisher: publish.yml on petfold/swarmfs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page