An intentionally tiny IPFS library for Python. Auto-manages a real Kubo node.
Project description
basic-ipfs
An intentionally tiny IPFS library for Python. Zero setup, real Kubo node under the hood.
Description
basic-ipfs does exactly five things:
- Add a file or bytes to IPFS and pin it (returns a CID)
- Announce a file or bytes to IPFS without pinning (returns a CID — content may be GC'd)
- Get content back by CID (as bytes, or written to disk)
- Pin one CID or a list of CIDs (so they never get garbage-collected)
- Unpin one CID or a list of CIDs (idempotent — safe to re-run)
It also runs and manages a real Kubo node for you — auto-downloaded on first use, auto-shutdown on exit. No daemon to install, no config files to edit, no accounts. (See Privacy below — the default daemon does join the public IPFS network.)
I made basic-ipfs because no other Python packages make it this simple. Every existing client assumes you already have an IPFS daemon running. This one just works.
If it does exactly what you need → great, use it. If it doesn't → don't use it. Simple as that.
Install
pip install basic-ipfs
First call downloads the Kubo binary (~115 MB, SHA-512 verified) into your per-user data directory (~/.local/share/basic_ipfs/bin/<platform>/ipfs on Linux; platformdirs picks the right path per OS) and starts it as a background subprocess. The daemon stops automatically when your program exits.
Usage
import basic_ipfs
# Add — pins content permanently so it's never garbage-collected
cid = basic_ipfs.add("photo.jpg")
cid = basic_ipfs.add(b"hello world")
# Pass provide=False to skip the public-DHT announce (content stays
# locally addressable, but its presence is not broadcast)
cid = basic_ipfs.add(b"private data", provide=False)
# Announce — adds to IPFS without pinning; content may be GC'd later
cid = basic_ipfs.announce("photo.jpg")
cid = basic_ipfs.announce(b"temporary data")
# Just compute the CID locally — does NOT store, pin, or announce anything.
# Useful for previewing the CID of content you have not yet decided to publish.
cid = basic_ipfs.compute_cid_locally(b"hello world")
cid = basic_ipfs.compute_cid_locally("photo.jpg")
# Get — returns bytes, or writes to disk if you pass an output path
data = basic_ipfs.get(cid)
basic_ipfs.get(cid, "copy.jpg")
# Pin — one CID, or a whole list (one HTTP round-trip per ~500 CIDs)
basic_ipfs.pin(cid)
basic_ipfs.pin([cid1, cid2, cid3, ...])
# Unpin — same shape, always idempotent
basic_ipfs.unpin(cid)
basic_ipfs.unpin([cid1, cid2, cid3, ...])
# List all pinned CIDs
cids = basic_ipfs.get_all_pins() # → ['Qm...', 'baf...', ...]
# Debug info
basic_ipfs.status() # {peer_id, agent_version, repo_size_bytes, pinned_cids, ...}
That's the core API. There are also helpers for networking, private networks, identity rotation, and lockdown — see Privacy.
Explicit lifecycle, if you want it (you usually don't — the daemon starts lazily on the first call and stops on process exit):
basic_ipfs.start()
basic_ipfs.stop()
with basic_ipfs.node():
basic_ipfs.add(b"hi")
All failures raise basic_ipfs.IPFSError. Subclasses, all with actionable messages:
IPFSBinaryNotFound— Kubo missing and auto-download failed (no network, disk full, unsupported platform).IPFSDaemonTimeout— daemon didn't reach a healthy state in 60 s.IPFSOperationError— an/api/v0/...call returned an error.IPFSPortInUse—API_PORT(default 5001) is bound by something that isn't ours. Setbasic_ipfs.API_PORTto something free.IPFSRepoLocked— another Kubo daemon already owns the repo. One process per repo.IPFSRepoCorrupt— repo on disk failed a sanity check. Investigate; don't auto-delete.
CLI
Installing the package also adds a basic-ipfs command:
basic-ipfs status
basic-ipfs add photo.jpg # prints the CID, pins content
basic-ipfs announce photo.jpg # prints the CID, no pin (may be GC'd)
basic-ipfs add-folder ./mydir # recursive add + pin
basic-ipfs compute-cid photo.jpg # prints the CID, stores nothing, announces nothing
basic-ipfs get <cid> [output-path] # stdout or file
basic-ipfs pin <cid> [<cid> ...]
basic-ipfs unpin <cid> [<cid> ...]
basic-ipfs is-pinned <cid> # exit 0 if pinned, 1 if not
basic-ipfs exists <cid> # exit 0 if locally available
basic-ipfs pins # list all pinned CIDs
basic-ipfs gc # run garbage collection
# Networking
basic-ipfs peers # list connected peers
basic-ipfs my-addr # this node's shareable multiaddrs
basic-ipfs connect <multiaddr> # dial a peer
# Privacy
basic-ipfs create-private-network # generate swarm key (run before first op)
basic-ipfs join-private-network <key> # join an existing private network
basic-ipfs network-key # print this node's swarm key
basic-ipfs rotate-identity # new peer ID; daemon must be stopped
basic-ipfs lockdown # disable DHT, bootstrap, gateway fetch
Config
Override module variables before the first call if you want non-defaults:
import basic_ipfs
basic_ipfs.APP_NAME = "MyApp" # changes default repo location
basic_ipfs.REPO_PATH = "/mnt/data/ipfs" # or pin an exact path
basic_ipfs.KUBO_VERSION = "v0.40.1" # must be in kubo_checksums.py
basic_ipfs.API_PORT = 5099 # if 5001 is taken
basic_ipfs.GATEWAY_PORT = 8099 # if 8080 is taken (or running 2 nodes)
basic_ipfs.SWARM_ADDRESSES = ["/ip4/127.0.0.1/tcp/4001"] # loopback-only swarm
basic_ipfs.STORAGE_MAX = "100GB" # repo size cap ("" = unlimited)
Default repo location is ~/.local/share/basic_ipfs/ipfs_repo (platformdirs picks the right path per OS).
Two-node example
Anything you add() on one machine can be get()-ed on another that knows
your peer address. Smallest possible demo:
# Node A
import basic_ipfs
basic_ipfs.start()
ipv4, _ = basic_ipfs.my_node_multiaddress()
print("share this with node B:", ipv4)
cid = basic_ipfs.add(b"hello from A")
print("share this CID:", cid)
input("press enter to stop…")
# Node B
import basic_ipfs
basic_ipfs.start()
basic_ipfs.connect_to_node("<ipv4 from node A>")
print(basic_ipfs.get("<cid from node A>")) # → b"hello from A"
For an isolated network where only your nodes can see each other, use
create_private_network() on the first node and join_private_network(key)
on the rest, both before start(). See Privacy below.
Troubleshooting
IPFSPortInUse on startup. Something else is on port 5001 (often another
IPFS daemon you forgot about, or some unrelated dev service). Either stop it,
or pick a different port before the first call:
import basic_ipfs
basic_ipfs.API_PORT = 5099
IPFSRepoLocked on startup. Another basic-ipfs-using process owns the
repo. Run only one process per repo — or set basic_ipfs.REPO_PATH to a
different path before starting.
Auto-download fails (corporate proxy / firewall). The downloader honors the standard environment variables:
HTTPS_PROXY=http://corp-proxy:3128 \
REQUESTS_CA_BUNDLE=/etc/ssl/corp-ca.pem \
python your_script.py
If outbound access to dist.ipfs.tech is blocked, pre-place the binary at
<user_data_dir>/basic_ipfs/bin/<platform>/ipfs (or ipfs.exe on Windows)
and basic-ipfs will skip the download entirely. The bundled
<site-packages>/basic_ipfs/bin/<platform>/ipfs path is also honoured
for PyInstaller / Briefcase deploys. Supported platform strings:
linux-amd64, linux-arm64, linux-riscv64, darwin-amd64,
darwin-arm64, windows-amd64.
Alpine / musl Linux. Kubo upstream ships glibc binaries only.
apk add gcompat may be enough; otherwise place a musl-compatible
ipfs binary at the path above.
32-bit ARM (Raspberry Pi 3 and older). Kubo no longer publishes 32-bit ARM builds. Run a 64-bit OS (e.g. Raspberry Pi OS 64-bit) on Pi 4/5.
Slow first run. Auto-download is ~115 MB. Subsequent starts are fast
(daemon comes up in ~1–3 s). The binary lives in platformdirs.user_data_dir("basic_ipfs")
so it survives reinstalls. To wipe it, delete that directory.
Want to inspect the daemon log. It's at
<repo>/basic_ipfs_daemon.log (rotated at 5 MB, one .old kept).
Security
Auto-download is verified against an SHA-512 hash baked into the wheel
(basic_ipfs/kubo_checksums.py) using a constant-time compare. Versions
not in the table are refused — fetching a digest from the same origin as
the archive adds no real protection, so the install is fail-closed. The
download also pins its redirect target to *.ipfs.tech at every hop in
the chain (not just the final URL).
Two trust assumptions worth knowing before deploying on a shared host:
- The local Kubo HTTP API on
127.0.0.1:5001is unauthenticated. Any process running as the same user can drive the full Kubo API, includingkey/*andconfig. Single-user dev boxes are fine; multi-tenant hosts need additional hardening (API.Authorizations). connect_to_node(multiaddr)forwards its argument verbatim. If your code feeds untrusted input into it, the daemon becomes an SSRF probe.
See SECURITY.md for the full threat model and how to
verify the binary manually.
Privacy
By default, the daemon joins the public IPFS network. That has real consequences you should understand before publishing anything sensitive:
- CIDs are advertised.
add()announces the content's CID to the public DHT. Anyone monitoring the DHT (and crawlers do) can record that your node holds that CID. Passprovide=Falseto skip the announce — the content is still addressable, but its presence is not broadcast. get()is observable. Fetching an unknown CID dials peers and may fall back to public gateways operated by third parties (Protocol Labs, Cloudflare). Those parties learn what you asked for.- Peer ID is persistent. The repo is created once and reused, so all your IPFS activity over time is linkable to one stable peer ID.
- Bootstrap nodes are third parties. The daemon dials Kubo's stock bootstrap list on startup (Protocol Labs / Cloudflare hosts).
- Swarm port is exposed. Kubo listens on
0.0.0.0:4001for inbound peers; on a multi-tenant or hostile LAN, treat that port like any other public service.
For non-public data, you have three escape hatches — pick what fits:
import basic_ipfs
# 1. Per-call: skip the DHT announce on a single add()
cid = basic_ipfs.add(b"private", provide=False)
# 2. Private network: only peers with the same 256-bit swarm key can
# connect. Run BEFORE start() / the first IPFS operation.
key = basic_ipfs.create_private_network()
# share key + multiaddr with peers via a trusted channel
# 3. Lockdown: disable the public DHT, bootstrap dialing, gateway
# fetch, and pin the swarm listener to loopback. Right starting
# point for encrypted-content stores or air-gapped pipelines.
basic_ipfs.lockdown_mode()
# Bonus: rotate the libp2p keypair to sever peer-ID linkability with
# prior runs (daemon must be stopped first; pins are preserved).
new_peer_id = basic_ipfs.rotate_identity()
A private network refuses connections from any peer without the matching
swarm key, so the DHT/bootstrap concerns above do not apply. See
SECURITY.md for the full threat model.
How it works
basic-ipfs bundles the reference IPFS implementation — Kubo, the Go daemon maintained by Protocol Labs — and runs it as a subprocess. Your Python calls are translated into HTTP requests to Kubo's local /api/v0 endpoint.
- Auto-download: the Kubo binary fetches from
dist.ipfs.techon first use, checksum-verified, and cached. The Python wheel itself stays tiny (~28 KB,py3-none-any) because it ships no binaries. - Content is immutable: a CID is a hash of the content. The same bytes always produce the same CID, anywhere in the world.
- Pinning is persistence: Kubo garbage-collects unpinned content.
pin()marks content permanent.unpin()undoes that. - PyInstaller / Briefcase friendly: respects
sys._MEIPASS, so bundled apps find the binary correctly.
Contributing
If you want to add functionality, open a PR. I'll merge it if it keeps the library simple and matches the existing patterns.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file basic_ipfs-1.0.0.tar.gz.
File metadata
- Download URL: basic_ipfs-1.0.0.tar.gz
- Upload date:
- Size: 62.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a6b5830e66256b9e404c3b49985b30c1a6e24642d8d93c7a1bf0d38e370e67e
|
|
| MD5 |
ee469e85b63e72a551fe91b1a9b2a109
|
|
| BLAKE2b-256 |
a2d7ca3d1bfbebef573004e9a58d3c9eaf2120c11927b76072f4d369f16e2642
|
Provenance
The following attestation bundles were made for basic_ipfs-1.0.0.tar.gz:
Publisher:
release.yml on lukeprofits/basic-ipfs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
basic_ipfs-1.0.0.tar.gz -
Subject digest:
6a6b5830e66256b9e404c3b49985b30c1a6e24642d8d93c7a1bf0d38e370e67e - Sigstore transparency entry: 1429791757
- Sigstore integration time:
-
Permalink:
lukeprofits/basic-ipfs@d71d00653af25f986dfcd4e64f6a0086208da970 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/lukeprofits
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d71d00653af25f986dfcd4e64f6a0086208da970 -
Trigger Event:
push
-
Statement type:
File details
Details for the file basic_ipfs-1.0.0-py3-none-any.whl.
File metadata
- Download URL: basic_ipfs-1.0.0-py3-none-any.whl
- Upload date:
- Size: 38.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d39af45f074747e4f38c3c634bf1fa4e3d645856d0edee3f7b6e3243bd17c38
|
|
| MD5 |
1af1db2d154929314a7c91b49daf5e3e
|
|
| BLAKE2b-256 |
2c1138f3fe3584283211bc75cbcad8812fc0a112959af25093e72c4d36969584
|
Provenance
The following attestation bundles were made for basic_ipfs-1.0.0-py3-none-any.whl:
Publisher:
release.yml on lukeprofits/basic-ipfs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
basic_ipfs-1.0.0-py3-none-any.whl -
Subject digest:
4d39af45f074747e4f38c3c634bf1fa4e3d645856d0edee3f7b6e3243bd17c38 - Sigstore transparency entry: 1429791760
- Sigstore integration time:
-
Permalink:
lukeprofits/basic-ipfs@d71d00653af25f986dfcd4e64f6a0086208da970 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/lukeprofits
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d71d00653af25f986dfcd4e64f6a0086208da970 -
Trigger Event:
push
-
Statement type: