Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

hyperfly (Python)

Binary compression for typed APIs at the edge of entropy.

Your Pydantic models already fix every field name, type, and bound. Your traffic already reveals what the values usually look like. Hyperfly compiles both into a binary protocol for one exact route — and speaks JSON to anything that hasn't been told.

Pre-release. The wire format is specified and three implementations agree on it byte-for-byte, but nothing is stable yet.

pip install hyperfly[pydantic]

Two lines at the boundary

from enum import Enum

from pydantic import BaseModel, Field

from hyperfly.pydantic import compile


class Status(str, Enum):
    ok = "ok"
    degraded = "degraded"


class Event(BaseModel):
    id: int = Field(ge=0)
    at_ms: int = Field(ge=0)
    status: Status
    message: str | None = None


class EventResponse(BaseModel):
    events: list[Event]
    cursor: str | None = None


codec = compile(EventResponse, validate=True)

response = EventResponse(events=[Event(id=1, at_ms=1_792_454_400_000, status=Status.ok)])
data = codec.encode(response)
value = codec.decode(data)  # an EventResponse; without validate=True, plain dicts

Anything the model already settles — field names, types, enum members, bounds, optionality — never reaches the wire. Models it cannot encode fail loudly at compile time, with the path:

class Model(BaseModel):
    meta: dict[str, int]


compile(Model)  # UnsupportedSchemaError: $.meta: type dict[str, int] has no v0 encoding

Columns and profiles

codec = compile(EventResponse, plan="columnar", validate=True)

The columnar plan lays an array of records out field by field. It turns timestamps into deltas, exact-decimal numbers into integer mantissas, enums into indices and booleans into bitmaps, and deflates text columns together. Row and columnar codecs of the same model have different fingerprints, so a server can register both.

from hyperfly import train
from hyperfly.pydantic import to_ir

samples = [r.model_dump(mode="json", by_alias=True) for r in recent_responses]
profile = train(to_ir(EventResponse), samples)
codec = compile(EventResponse, plan="columnar", profile=profile, validate=True)

A profile adds what only traffic can teach: the values that recur across different responses, which a compressor never sees because it only ever holds one. train returns None when the samples have nothing worth a profile. A new profile produces a new fingerprint, so register the previous codec next to the new one while clients move over.

Serving it

from hyperfly import CodecRegistry
from hyperfly.http import negotiate, encode_for, serve_artifact

registry = CodecRegistry([codec, previous_codec])

decision = negotiate(
    request.headers.get("hyperfly-accept"),
    registry,
    http_accept=request.headers.get("accept"),
)
body, headers = encode_for(decision, payload, on_fallback=lambda reason, error: log.warning("%s: %s", reason, error))

Accept: application/vnd.hyperfly;q=0 opts a client out of binary whatever it advertises. A value the codec refuses under its limits is answered in JSON with the same headers a fallback gets, and on_fallback hears about it. Every decision carries Vary: Hyperfly-Accept, Accept.

Request bodies go through decode_request, which takes a bytes body or an iterable of chunks and stops reading once the body passes max_bytes (1 MiB by default):

from hyperfly.http import RequestError, decode_request

try:
    kind, value = decode_request(
        request.headers.get("content-type"),
        request.headers.get("hyperfly-codec"),
        request.stream(),
        registry,
        content_length=request.headers.get("content-length"),
    )
except RequestError as error:
    return Response(status=error.status, headers=error.headers)

error.status is 415 for a content type that is neither hyperfly nor JSON, 413 past max_bytes, 400 for a malformed envelope, a contradicting Hyperfly-Codec header or a body that fails to decode, and 415 with Hyperfly-Offer for a fingerprint the registry does not hold. error.reason names the rule for logs.

A client that holds nothing gets JSON plus a Hyperfly-Offer naming an artifact it could fetch from serve_artifact; once it has it, the same route answers in binary. There is no failure mode where the response is unreadable.

The JSON fallback writes pydantic models with model_dump(mode="json", by_alias=True), so field aliases and serializers such as ser_json_bytes apply. Raw dicts and lists must already be JSON-ready apart from enums (written as their values) and non-finite floats (written as null, as JSON.stringify does); bytes has no JSON form and raises.

Registering the outgoing codec alongside the incoming one is what makes retraining safe: a new profile is a new fingerprint, so a deployment holding only one codec turns every rollout into a cutover.

Limits

compile_ir(ir, limits=Limits(...)) bounds what one body can make a peer decode (wire v0 §6). A body past a limit fails with code limit (depth for max_depth), and the encoder refuses a value its own decoder would reject. Limits are not negotiated: a route that legitimately ships denser payloads raises them on both peers.

Field Default Bounds
max_depth 64 struct, array and nullable nesting
max_items 2^24 items in one array
max_byte_length 2^28 bytes in one string or bytes value
max_amplification 32 items in one array: at most max_amplification × (remaining bytes + 1), or one item per remaining bit instead for a row-encoded array of non-zero-bit elements
max_total_items 2^24 items across every array in one body, and at most max_amplification × (body length + 1)
max_total_bytes 2^26 decoded bytes of every string and bytes value in one body, counting literals, enum members and dictionary, grammar and derived values
max_byte_amplification 64 those decoded bytes per body byte: at most max_byte_amplification × (body length + 1)

Errors

Every failure the package raises on purpose is a HyperflyError with a stable code, under the same class names as the TypeScript package. A wrapped exception stays reachable through __cause__.

Class Codes
EncodeError type, range, float, utf8, required, depth, limit, validation
DecodeError header, truncated, trailing, varint, bitmap, marker, float, utf8, range, depth, limit, packed, unsupported, json, validation
FingerprintMismatchError (a DecodeError) fingerprint
UnsupportedSchemaError unsupported
HyperflyError ir, options

json is a malformed JSON body read by decode_response or decode_request; its __cause__ is the underlying error, a json.JSONDecodeError, UnicodeDecodeError or RecursionError. validation is a pydantic ValidationError from a codec compiled with validate=True, raised as EncodeError on the encode paths and DecodeError on the decode paths.

options is a malformed compile option, raised by compile_ir and the pydantic compile before the schema is read: a limits that is neither None nor a Limits, or has a field that is not an integer from 0 to 2^53 - 1; a plan other than "row" or "columnar"; a pack other than None, False or a mapping whose deflate and inflate are each a callable, None or absent; a validate other than True or False; or an option that raises when read (the error is its __cause__). Each option is read once. read_body and decode_request raise it for a max_bytes that is not an integer from 0 to 2^53 - 1, before reading the body.

RequestError is separate: it is not a HyperflyError and has no code. It is how read_body and decode_request refuse a request, carrying status (400, 413 or 415), a stable reason and the response headers. Only a 400 wraps a cause: the DecodeError for a body that fails to decode (so a JSON parse error is the __cause__ of that DecodeError, one hop further down), or the stream or buffer error for an unreadable body. A 413 or 415 has no __cause__.

Conformance

The authorities are the specifications, not this implementation. This package runs the same golden vectors as the TypeScript and Rust implementations, and CI runs a TypeScript server against this Python client over real HTTP on every push.

pip install ./python[test] && pytest python/tests -q

License

Apache License 2.0

Metadata

Release files for hyperfly 1.0.0a1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hyperfly 1.0.0a1
File Size Uploaded Provenance
hyperfly-1.0.0a1.tar.gz 57.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for hyperfly 1.0.0a1
File Interpreter ABI Platform
hyperfly-1.0.0a1-py3-none-any.whl Python 3 none any Details

Total release size: 117.9 kB

Release files / hyperfly-1.0.0a1.tar.gz

Download URL hyperfly-1.0.0a1.tar.gz
Size 57.2 kB
Tags Source
SHA-256 checksum
How to use checksums
f979c2a6c37ac0481ab075243692189b5a71e6d4f435f2b5bca3a5c6d43a5b74
BLAKE2b-256 checksum
How to use checksums
f7ec644ea0ad7fd88b1238a206ce11625f06758e10ec107f616128c4eecc8cb2
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 20, 2026.

Transparency log

Release files / hyperfly-1.0.0a1-py3-none-any.whl

Download URL hyperfly-1.0.0a1-py3-none-any.whl
Size 60.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6abf5d22e95127482fbdf93edf50516d985f227bb30586d1add079a9cc8722de
BLAKE2b-256 checksum
How to use checksums
0c376c6c3e7f9f5580500854dd801ee24166c02ccb872b37f19132911d69a72d
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 20, 2026.

Transparency log

Security for hyperfly 1.0.0a1

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 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for hyperfly-1.0.0a1.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 20, 2026.

Transparency log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page