This release is a pre-release and may not be stable for production use.
Covalence for Python
Content identifiers (CIDs), DASL values, and content-addressed and key-value stores, from Python.
>>> from covalence import Cid, Value
>>> cid = Cid.of(b"payload", "sha256")
>>> Cid(str(cid)) == Cid(bytes(cid)) == cid
True
>>> cid.hash, cid.codec
(Hash('sha256'), Codec('raw'))
>>> value = Value({"a": cid})
>>> Value.decode(value.encode()) == value
True
>>> value.links() == [cid]
True
Install
pip install covalence==0.1.0.dev5
- Pre-releases need an exact pin like this one, or
pip install --pre. - Wheels cover CPython 3.11 and later on Linux (x86_64 and aarch64, glibc 2.28 or later) and macOS (arm64).
- Elsewhere pip builds the sdist, which needs Rust 1.98 or later.
- Each wheel and the sdist include
THIRD-PARTY-LICENSES.md, the licenses of the Rust crates compiled into the package.
Terms
- A CID (content identifier) names bytes by their hash. It holds a codec, which says how the bytes are encoded, a hash function, and the digest.
- A multicodec code is a number from the multicodec table that names a codec or a hash function.
- DASL is a small set of specifications for CIDs and
data. DRISL is its deterministic CBOR
encoding, which CIDs label with the
dag-cborcodec. - A CAS (content-addressed store) keeps bytes and finds them by their CIDs.
CIDs, hashes and codecs
Cidis any CIDv1 with a digest of at most 64 bytes.Cid(x)takes text or binary;Cid.from_textandCid.from_bytestake only one.- Text is
band lowercase base32 only. Invalid CIDs raiseValueError. HashandCodecwrap multicodec codes.from_codetakes an int, and the constructors take names:- hashes: the multicodec names
"sha1","sha2-256","sha2-512"and"blake3", or thehashlibnames"sha256"and"sha512"; - codecs:
"raw","dag-cbor"(or"drisl") and"git-raw".
- hashes: the multicodec names
Cid.ofhashes with"blake3"and"raw"by default.Cid.of_file(path)is the raw BLAKE3Cidof a file, asCas.put_filereturns it, without storing it or reading the file into memory.Cid.of_fileobj(file, max_bytes=..., expected_length=...)hashes an already-open, seekable binary file object once from offset zero, without storing it or holding it in memory. It uses the same bounded reader asCas.put_fileobj, and leaves the caller's source open at EOF on success. Keep that source unchanged and exclusively owned until it returns.
Values
-
Valueis a DASL value.Value(...)takes None, bool, int, str, bytes,Cid,Value, lists, tuples and dicts with str keys. It checks the same rules asdecode:- links must be DASL CIDs;
- integers outside i64, floats and nesting deeper than 126 raise
ValueError.
Other types raise
TypeError. -
Value.to_jsonandValue.from_jsonuse the ATProto JSON form ofcovalence-lib-dasl. -
Value.cid()is the DRISL BLAKE3 CID of the value's encoding.
Key-value stores
Kvis a byte key-value store:Kv.memory()makes one in memory;Kv.s3("s3://bucket/prefix/")(orKv.s3("bucket", "prefix")) makes one in S3.
- S3 is configured as the AWS SDKs are:
- the region is
region=, else fromAWS_REGION, the profile or EC2 instance metadata, which can take a moment off EC2; - the endpoint is from
AWS_ENDPOINT_URLor the profile'sendpoint_url, else AWS's for the region, so other S3 services needAWS_ENDPOINT_URL; - credentials come from the SDKs' default chain: environment variables,
profile files, web identity, ECS or EKS Pod Identity, or EC2 instance
roles. They are fetched at the first request; missing or failed
credentials raise
StoreErrorthen. profile=picks the profile instead ofAWS_PROFILEordefault; its credentials win over environment variables.
- the region is
getreturnsbytesorNone.putreplaces any value under the key.existssays whether there is a value without reading it (a HEAD on S3).Kv.s3raisesValueErrorfor an invalid location, a URL with a prefix argument too, or no region.- Without
s3:ListBucket, S3 answers 403 for a missing key, so reading one raisesStoreErrorinstead of returningNoneorFalse, here and throughCas.kv. - S3 keys must be UTF-8 with non-empty
/-separated segments other than.and..; other keys raiseStoreError. - On S3, values over 8 MiB go up as multipart uploads, and values come down in ranged GETs of 8 MiB, 16 parts or ranges at a time. Upload parts grow when needed to stay within 10,000 parts. Covalence checks S3's limits before upload: at most 10,000 parts of 5 GiB each, subject to the platform's allocation limit.
Kv.s3(..., upload_buffer_bytes=n)caps aggregate upload payload reservations across calls sharing that store. An upload reserves the smaller of its length and its part size times its worker count before allocating parts, reading its upload source or making requests. Insufficient capacity raisesStoreErrorimmediately; zero admits only empty uploads. The default is no budget. Reservations last through retries and cleanup.- This budget excludes caller-owned sources, read buffers, allocator overhead, HTTP/TLS buffers and request metadata; it is not an RSS limit. Owned byte values still reserve their planned payload even though their parts share the original allocation. File helpers may hash their source before upload admission. A larger legal object can need a larger budget because its parts grow; rejection does not consume its upload source or create an upload.
Content-addressed stores
>>> from covalence import Cas
>>> cas = Cas.memory()
>>> cid = cas.put(b"hello")
>>> cas.size(cid)
5
>>> cas.get_range(cid, 1, 3), cas.get_range(cid, 4, 10)
(b'ell', b'o')
Casis a CAS. Make one with:Cas.memory();Cas.sqlite(path), in memory without a path;Cas.kv(kv, prefix), keeping blocks in aKvunderprefix/blake3/<digest>/bytes.
putreturns the raw BLAKE3Cid, after checking that the store kept the data under it.Cas.kvnever replaces a block itsKvalready holds: onKv.s3each write is conditional (If-None-Match: *), so concurrent writers of one block race safely and the first one's bytes stay.put_value(value)stores the DRISL encoding of aValue(or whatValue(...)takes) and returns its DRISL CID, which isvalue.cid().getreturns the bytes andget_factaBlobFact, both checked against the CID; a failed check raisesIntegrityError.hasandmissing(cids)say which CIDs the store holds bytes for, without checking their integrity:Cas.sqlitechecks its index, without reading the bytes;Cas.kvasks itsKvwhether the key exists (a HEAD on S3);Cas.memorylooks the bytes up in memory.
infoandstatsreturn native values.size(cid)andget_range(cid, start, length)return a byte count, or up tolengthbytes fromstart, as the store vouches for them.size_uncheckedandget_range_uncheckedskip any checking. No store checks sizes or ranges yet.- Missing values return
None, even for a zerolength. - Ranges are clipped at the end.
- Stores without native ranges fetch the whole value and slice it.
- Missing values return
Files and streams
Cas.put_file(path)stores a file and returns its raw BLAKE3Cid, asputdoes.Cas.put_from(cid, chunks, length)stores a block from an iterable of bytes, such as a request body, and returns its raw BLAKE3Cid. Before a new block is stored, its bytes must matchlengthand the BLAKE3cid, andchunksmust end there. A store may return an existing block without consuming all the input; unread bytes and source errors are not checked. While consuming input, a hash mismatch (IntegrityError), too few or too many bytes (ValueError), or a source exception publish no new block. Source exceptions propagate unchanged unless cleanup also fails, in which caseStoreErrorreports that failure with the source as its__cause__.Cas.get_file(cid, path)writes the block to a file and returns its length, orNoneif missing.Cas.put_fileobj(file, max_bytes=..., expected_length=...)stores a caller-owned, seekable binary file object. It reads from offset zero twice, checks the complete source on both passes even when deduplicating, and leaves it open at EOF on success. The caller must keep the source unchanged and exclusively owned until it returns; the method creates no snapshot.max_bytescaps the accepted source length;expected_length, if given, must match it exactly. A length violation raisesValueErrorbefore a new block is published. A pass may read one extra byte to check the end.Cas.get_to(cid, writer, max_bytes=..., expected_length=...)writes to a caller-owned binary file object and returns the length, orNoneif missing. It checks all bytes against the CID, caps the bytes the writer accepts, and checks the optional exact length. It handles short writes and never seeks, flushes or closes the writer. A failure may leave partial or unchecked bytes there: write to private staging and publish only after a successful return.- These limits govern source length and output bytes, not the memory a backend uses before reading or writing. Python callback transfers are at most 1 MiB.
put_file,put_fileobj,put_from,get_fileandget_tostream onCas.kvoverKv.s3, holding up to 16 part payloads at a time, or as many asKv.s3(..., concurrency=n)says; blocks over 78 GiB go up in larger parts. Upload parts start at 8 MiB; read ranges stay at 8 MiB. Setupload_buffer_bytes=to enforce a shared upload payload budget. Other stores, includingCas.kv(Kv.memory()), hold the block in memory.- A create-only write can reuse a block without checking its stored bytes.
Returning a CID does not freshly verify an existing block: use a checked
read, such as
get_to, when that guarantee is needed. put_filereads the file twice, to hash it and to store it, and stores the bytes it hashed. If they changed in between it raisesIntegrityError, and if the file got shorterOSError, storing nothing either way. Bytes appended in between are not stored.get_filechecks the bytes against the CID in a private temporary file besidepath. That file replacespathonly once the check passes, so a missing block or a failed check (IntegrityError) leavespathas it was. The new file has the permissions of the file it replaces, or those the umask gives a new file.
Use an already-open file without exposing its path:
>>> import io
>>> source = io.BytesIO(b"hello")
>>> cid = cas.put_fileobj(source, max_bytes=5, expected_length=5)
>>> sink = io.BytesIO()
>>> cas.get_to(cid, sink, max_bytes=5, expected_length=5)
5
>>> sink.getvalue()
b'hello'
>>> Cid.of_fileobj(sink, max_bytes=5, expected_length=5) == cid
True
An integer file descriptor can be wrapped with os.fdopen(os.dup(fd), "wb", buffering=0) for downloads, or "rb" for uploads. Close that wrapper when
finished; the original descriptor remains open. Duplicates share a file
position, so the caller must also keep the original descriptor exclusively
owned during the operation.
Blobs and facts
Blobis bytes read whole or by range:Blob(data)holds a snapshot ofdatain memory, andBlob(blob)sharesblob's source;size(),read_range(start, length)andread_all()are not checked, and ranges are clipped at the end;- a
read_rangereturning more thanlengthbytes raisesStoreError; slice(start, length)is a blob over the same bytes;- copies keep their class and share the source.
Blobcan be subclassed, but its methods are not virtual: an override in Python changes only what Python callers see, whilefactandsliceread the Rust blob underneath.Blob.fact(cid)reads the blob once and returns aBlobFactfor exactlycid, checked, orNoneif the bytes do not hash to it:- it checks SHA-1, SHA-256 and BLAKE3 CIDs;
- a digest over 32 bytes, such as SHA-512's, raises
ValueError; - another hash, or a digest length the hash does not give, raises
UnsupportedError.
Factis an interface for bytes and theCidthey claim to hash to:cidandvalue(), which decodes the bytes as the CID's codec says.BlobFactis checked;isinstance(x, Fact)alone proves nothing.BlobFactis aFactand aBlobof its bytes, withdatatoo. It compares and hashes by fact, and cannot be subclassed.BlobFact.of(data)hashesdatawith BLAKE3. A fact asked for its own hash and digest under another codec is relabelled, not hashed again.
Errors
- Bad input raises
ValueErrororTypeError, orOverflowErrorfor an int below 0 or of 2**64 or more where a size, offset or code is expected. - Other failures raise a subclass of
covalence.Error:IntegrityError(also aValueError) when data does not match its CID;StoreError(also anOSError) when a store's storage fails;UnsupportedError(also aNotImplementedError) for an unsupported operation, such as hashing with an unknown function.
- A local file that cannot be read or written raises
OSError.
Developing
glu buildbuilds thecovalencewheel intotarget/glu/wheels/.glu testinstalls it in a fresh venv and runstests/.- Classes are named after the Rust types they wrap.
Documentation
The compiled module is the private covalence._native; users import
everything from covalence. PyO3's typing guide recommends a hand-written
stub; this package writes its stub by hand and checks it against __doc__:
- Rust
///comments are the source. PyO3 makes them__doc__, with signatures from the Rust arguments. Python code incovalence/keeps its own docstrings. _native.pyirepeats each docstring word for word, since editors read the stub and cannot see a compiled module's__doc__.tests/test_docs.pyfails if the two differ, or if the stub lacks a public member.- Classes and exceptions set their module to
covalence(#[pyclass(module = "covalence")]), so they show ascovalence.Name, nevercovalence._native.Name._nativeitself is not documented. - Every class, method, property and exception in
covalence.__all__has a docstring. Examples are>>>doctests.tests/test_docs.pyruns them, and this README's, withdoctest.DocTestSuiteandDocFileSuite. docs/pythonbuilds the docstrings into HTML with Sphinx, for Read the Docs.
Prebuilt wheels
.github/workflows/python-wheels.yml builds abi3 wheels (CPython 3.11 and
later) for manylinux_2_28 x86_64 and aarch64 and macOS arm64, and an sdist,
and runs tests/ against each wheel. Pull requests and pushes keep them as
workflow artifacts. A release tag must match the workspace version, such as
0.1.0-dev.5; the files carry its PEP 440 form, 0.1.0.dev5:
v<version>attaches them to that GitHub release. In the publichiloopai/covalencerepository it also publishes them to PyPI, and.github/workflows/crates.ymlpublishes the crates to crates.io.python-v<version>only attaches them to the GitHub release.
To publish 0.1.0-dev.5:
git tag v0.1.0-dev.5 && git push origin v0.1.0-dev.5
Metadata
Release files for covalence 0.1.0.dev5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| covalence-0.1.0.dev5.tar.gz | 331.0 kB | With provenance | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| covalence-0.1.0.dev5-cp311-abi3-manylinux_2_28_x86_64.whl | CPython 3.11 | abi3 | Linux glibc 2.28+ x86-64 | Details |
| covalence-0.1.0.dev5-cp311-abi3-manylinux_2_28_aarch64.whl | CPython 3.11 | abi3 | Linux glibc 2.28+ ARM64 | Details |
| covalence-0.1.0.dev5-cp311-abi3-macosx_11_0_arm64.whl | CPython 3.11 | abi3 | macOS 11.0+ ARM64 | Details |
Total release size: 19.1 MB
Release files / covalence-0.1.0.dev5.tar.gz
| Download URL | covalence-0.1.0.dev5.tar.gz |
|---|---|
| Size | 331.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3fd9b7c2eff4a799d6c770237c5b5b18d2e1be8c6955111008913b2353c0c988
|
|
BLAKE2b-256 checksum How to use checksums |
bf84603a5eb357639e55841b6474bb03f4153bbc2c85285f9e6fa34f8b916e67
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logRelease files / covalence-0.1.0.dev5-cp311-abi3-manylinux_2_28_x86_64.whl
| Download URL | covalence-0.1.0.dev5-cp311-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 6.4 MB |
| Tags | CPython 3.11 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
cee636e0b9259f49c51938258a15d4005acbb34ab2769f156e16285b8bcc6410
|
|
BLAKE2b-256 checksum How to use checksums |
5df3d64128b484bc292b14e07db3003e9efc926f482454948d63ae0554ec7302
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logRelease files / covalence-0.1.0.dev5-cp311-abi3-manylinux_2_28_aarch64.whl
| Download URL | covalence-0.1.0.dev5-cp311-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 6.6 MB |
| Tags | CPython 3.11 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
9df408dac4040dacd7386669e32e5bea5087d1b0748689208b3491751622b036
|
|
BLAKE2b-256 checksum How to use checksums |
51669c908839c66a89c5b82b5a0320fa9e25fa5e123e7cd38b8fe7a0e1ec5ab2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logRelease files / covalence-0.1.0.dev5-cp311-abi3-macosx_11_0_arm64.whl
| Download URL | covalence-0.1.0.dev5-cp311-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 5.7 MB |
| Tags | CPython 3.11 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
606d734073b178e909d5639db60628839a711345ae524324fd103e1ed3cce1fb
|
|
BLAKE2b-256 checksum How to use checksums |
f818a021af7ce5919ac09be38a3c87d1c6064b6029822faf6ecb8196ebdcb6ec
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logSecurity for covalence 0.1.0.dev5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 4 files in this release have been uploaded with provenance metadata.
Attestation for covalence-0.1.0.dev5.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.