Skip to main content

fanferro

Fast, robust SNS + SQS messaging for Python, powered by a Rust core.

fan-out (SNS → SQS) · fanfare (announcing to a topic) · ferro (iron: the Rust core)

import fanferro
from pydantic import BaseModel              # or dataclasses, msgspec, protobuf, ...


class OrderPlaced(BaseModel):               # type name: "OrderPlaced" (see Message types)
    order_id: str
    amount_cents: int


client = fanferro.Client(
    source="urn:service:checkout",
    # create missing resources (default: they must exist)
    provisioning=fanferro.Provisioning.ensure(),
)
orders = client.topic("orders")
billing = client.queue("billing", dead_letter=fanferro.DeadLetterQueue("billing-dlq"))
client.subscription(orders, billing)
consumer = client.consumer(billing, concurrency=20)


@consumer.route(OrderPlaced)
async def charge(order: OrderPlaced) -> None:
    ...                                     # return → ack · raise → retry with backoff → DLQ


async def main() -> None:
    async with client:                      # connect + provision declared resources
        await orders.publish(OrderPlaced(order_id="o-1", amount_cents=1299))
        await consumer.run()                # SIGTERM → graceful drain

Status: 0.x - the API may still change between minor versions.

Install

uv add fanferro                   # pip install fanferro
uv add "fanferro[all]"            # + pydantic, protobuf, msgspec, opentelemetry-api

Message types

Every message carries a type name (the fanferro.type attribute, and type in CloudEvents): consumers route on it, and SNS filter policies can match it. Without a decorator it is the class name (for protobuf messages, their full protobuf name); give classes that cross service boundaries an explicit, versioned name instead:

@fanferro.message_type("checkout.order_placed.v1")
class OrderPlaced(BaseModel):
    order_id: str
    amount_cents: int

The name is then a contract between services rather than a Python class name: renaming the class doesn't change what consumers match, v2 can run next to v1 while consumers migrate, and the prefix tells which service owns the message. Extra arguments are aliases a consumer still accepts (@fanferro.message_type("checkout.order_placed.v1", "order_placed")), for renaming a type without breaking publishers that haven't been redeployed yet. See message types.

AWS Lambda

Consume a queue in a Lambda function with an SQS event source mapping: each record is decoded, routed and retried like a message a consumer received, and failed records come back as batchItemFailures.

# at module level, so it is reused while the function is warm
client = fanferro.Client(source="urn:service:billing")
billing = client.queue("billing", dead_letter=fanferro.DeadLetterQueue("billing-dlq"))
router = fanferro.Router()


@router.route(OrderPlaced)
async def charge(order: OrderPlaced) -> None:
    ...                                     # return → done · raise → retried · Reject → DLQ


# the function Lambda calls: handler(event, context)
handler = fanferro.aws_lambda.sqs_handler(client, router, queue=billing)

Turn on ReportBatchItemFailures on the event source mapping. Permissions, outcomes and testing with fanferro.testing.invoke_sqs_handler: Deployment → Lambda.

Events of AWS services

Notifications and events that AWS services send to SNS and SQS are recognised by their body and decoded into typed classes of fanferro.aws: annotate a handler with one and it is routed there.

import fanferro
from fanferro import aws

client = fanferro.Client()
# In this example, the queue is assumed to be subscribed to the topics S3 and SES send
# to by your infrastructure as code (Terraform, say).
consumer = client.consumer(client.queue("aws-events"))


@consumer.route                             # an S3 event notification
async def on_upload(event: aws.S3Event) -> None:
    for record in event.records:            # none in S3's test event
        if record.created:                  # object keys come URL-decoded
            await index(record.bucket.name, record.object.key, record.object.size)


@consumer.route                             # an SES identity's notification
async def on_bounce(notification: aws.SesBounceNotification) -> None:
    if notification.bounce.permanent:
        await suppress([r.email_address for r in notification.bounce.bounced_recipients])


@consumer.route("aws.ses.open")             # an SES configuration set's event
async def on_open(message: fanferro.Message[aws.SesNotification]) -> None:
    await record_open(message.body.mail.message_id, message.json()["open"]["ipAddress"])


async def main() -> None:
    async with client:
        await consumer.run()

Also typed: SES complaints and deliveries, CloudWatch alarms, Lambda destination records and dead letters, and EventBridge events (decoded into your own class, or typed for scheduled rules, ECS tasks, Step Functions, EC2, Auto Scaling and AWS Health): Notifications and events of AWS services.

Testing

Test routing and handlers without a broker: fanferro.testing.dispatch runs one message through the consumer's routing, decoding, middleware, handler and retry and dead-letter policies, and returns how it was settled. build_message gives a message the transport metadata a handler may depend on (attempt, group_id, the SNS topic, sent_at...).

from fanferro.testing import build_message, dispatch


async def test_charge() -> None:
    message = build_message(OrderPlaced(order_id="o-1", amount_cents=1299), attempt=3)
    result = await dispatch(consumer, message)
    assert result.outcome == "acked"        # or "retried", "requeued", "dead_lettered", ...

Flows run on an in-memory broker (fanferro.testing.memory_client()) or an emulator: Testing.

Highlights

  • Handlers are plain typed functions. Return to ack, raise to retry. No receipt handles.
  • Your own types. Pydantic, dataclasses, msgspec, protobuf, JSON or raw bytes.
  • Routing on anything. Message type, attribute, body field or topic.
  • Safe by default. Heartbeats, retries with backoff, dead-letter queues, graceful shutdown.
  • Infrastructure in code. Declared queues, topics and subscriptions are checked or created.
  • Long-running workers or AWS Lambda. The same handlers and policies for SQS event sources.
  • AWS's events, typed. S3, SES, CloudWatch alarms, Lambda and EventBridge events, decoded.
  • Talks to tomodachi services. Their envelopes, read and written in Rust.
  • Tests without AWS or Docker. An in-memory broker runs the same engine you deploy.
  • Mistakes caught early. Bad names, sizes and signatures fail before any network call.
  • Observable. Typed events, structured logs, hooks and OpenTelemetry.
  • The hard parts included. Large payloads via S3, outbox, idempotency, FIFO ordering.
  • Fast. A Rust core, full batches and little Python work per message.
  • No required dependencies. Fully typed, Python 3.12-3.15, wheels for Linux, macOS, Windows.

Performance

  • Rust does the heavy lifting. Signing, HTTP, the SQS and SNS protocols and batching.
  • Full batches, overlapping. Messages are received ten at a time, the next batch prefetched.
  • Acks don't block handlers. Deletes are batched in the background while the next message runs.
  • Little Python per message. Only your codecs, routing and handlers run on the event loop.
  • Lean defaults. Small messages, no redundant checksums, no tracing work unless enabled.

Benchmarks and how to run them: Performance.

Real AWS, emulators and the in-memory broker

fanferro runs on real AWS, on the AWS emulators MiniStack and LocalStack, and on its in-memory broker. Real AWS is the reference: probes run the same scenarios and raw API calls on each target, and the in-memory broker follows AWS wherever they found a difference, with AWS's error codes and texts.

  • Real AWS, for production: it is the reference.
  • In-memory broker (fanferro.testing.memory_client()), for tests without AWS or Docker: no credentials, and other accounts' queues and topics are just missing; filter policy errors leave out the parser position SNS appends; long polls that a stopping consumer aborts end at once (MemoryBroker(orphaned_polls=True) serves them as SQS does).
  • MiniStack, for end-to-end tests without Docker: accepts many queue and SNS attributes, names and FIFO ids that AWS refuses; differs in receipt handles of earlier receives, Number values, numeric filters on String attributes, and X-Ray headers and Binary values through SNS.
  • LocalStack, for end-to-end tests with Docker: accepts out-of-range queue attributes and a dead-letter queue of the other type; differs in FIFO receipt handles and duplicates, Number values, wildcard filters and presigned URL expiry.

Neither emulator enforces SQS's 60 seconds between deleting a queue and creating it again or between two purges, drops SNS deliveries to queues encrypted with alias/aws/sqs as AWS does, or refuses unknown credentials. Every finding, the recorded runs and how to probe an account of your own: Parity with AWS.

tomodachi services

fanferro services exchange messages with tomodachi services both ways: fanferro.envelopes.compat reads and writes tomodachi's envelopes (TomodachiJSON, TomodachiProtobuf and MetadataProtobuf) in Rust, and the data decodes into and encodes from your own models: tomodachi services.

Learn more

Using an AI coding agent? Point it at fanferro guide (the usage guide shipped with the package) or copy skills/fanferro into your project's skills.

Contributing

See AGENTS.md (for humans and AI agents): uv sync, make check, conventional commits.

License

MIT

Metadata

Release files for fanferro 0.10.0

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

Source distribution (sdist)

Source distribution for fanferro 0.10.0
File Size Uploaded Provenance Actions
fanferro-0.10.0.tar.gz 474.2 kB With provenance Details for fanferro-0.10.0.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for fanferro 0.10.0
File Actions
fanferro-0.10.0-cp312-abi3-win_arm64.whl CPython 3.12 abi3 Windows ARM64 Details for fanferro-0.10.0-cp312-abi3-win_arm64.whl
fanferro-0.10.0-cp312-abi3-win_amd64.whl CPython 3.12 abi3 Windows x86-64 Details for fanferro-0.10.0-cp312-abi3-win_amd64.whl
fanferro-0.10.0-cp312-abi3-musllinux_1_2_x86_64.whl CPython 3.12 abi3 Linux musl 1.2+ x86-64 Details for fanferro-0.10.0-cp312-abi3-musllinux_1_2_x86_64.whl
fanferro-0.10.0-cp312-abi3-musllinux_1_2_aarch64.whl CPython 3.12 abi3 Linux musl 1.2+ ARM64 Details for fanferro-0.10.0-cp312-abi3-musllinux_1_2_aarch64.whl
fanferro-0.10.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.12 abi3 Linux glibc 2.17+ x86-64 Details for fanferro-0.10.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
fanferro-0.10.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.12 abi3 Linux glibc 2.17+ ARM64 Details for fanferro-0.10.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
fanferro-0.10.0-cp312-abi3-macosx_11_0_x86_64.whl CPython 3.12 abi3 macOS 11.0+ x86-64 Details for fanferro-0.10.0-cp312-abi3-macosx_11_0_x86_64.whl
fanferro-0.10.0-cp312-abi3-macosx_11_0_arm64.whl CPython 3.12 abi3 macOS 11.0+ ARM64 Details for fanferro-0.10.0-cp312-abi3-macosx_11_0_arm64.whl

Total release size: 58.3 MB

Release files / fanferro-0.10.0.tar.gz

Download URL fanferro-0.10.0.tar.gz
Size 474.2 kB
Tags Source
SHA-256 checksum
How to use checksums
62029902f73c72f35485e3946d16d75b9cc1dc299a6d8109c10b590780a5573a
BLAKE2b-256 checksum
How to use checksums
f0116dcd16155d3f44e0ef0c4bf93c64ca54b80a541c88bb76895e74856a6d0b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 1, 2026.

Transparency log

Release files / fanferro-0.10.0-cp312-abi3-win_arm64.whl

Download URL fanferro-0.10.0-cp312-abi3-win_arm64.whl
Size 7.2 MB
Tags CPython 3.12 Windows ARM64 abi3
SHA-256 checksum
How to use checksums
e1f5deb261bc912b0d556f6a03d1d44fd6e4fefe9eb49d3efc464f66d4e277dc
BLAKE2b-256 checksum
How to use checksums
7791a975ad6e7965cbfbd7331801b1459099b914e1ee6ab8ec772546a8720398
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 1, 2026.

Transparency log

Release files / fanferro-0.10.0-cp312-abi3-win_amd64.whl

Download URL fanferro-0.10.0-cp312-abi3-win_amd64.whl
Size 7.6 MB
Tags CPython 3.12 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
1a64c39939246f4cb245e420507f8784f2184b28e1530ee967b1ac1f40ac502c
BLAKE2b-256 checksum
How to use checksums
0c9d9188107db4f924404daa6ec1089abb49b1d4b813fad83f47596996d1d396
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 1, 2026.

Transparency log

Release files / fanferro-0.10.0-cp312-abi3-musllinux_1_2_x86_64.whl

Download URL fanferro-0.10.0-cp312-abi3-musllinux_1_2_x86_64.whl
Size 7.5 MB
Tags CPython 3.12 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
6b61e339aee458e03cb715917eda1fc2d1e0b8d97a849fb4236b4d4bb589b28b
BLAKE2b-256 checksum
How to use checksums
e23afd3973c8781b43bbda5eb1cba00eca124b8c2b5b2f77e370a1347be8e333
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 1, 2026.

Transparency log

Release files / fanferro-0.10.0-cp312-abi3-musllinux_1_2_aarch64.whl

Download URL fanferro-0.10.0-cp312-abi3-musllinux_1_2_aarch64.whl
Size 7.1 MB
Tags CPython 3.12 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
b88f7035807731d4fe9e5649ad27c2fb86cd9564c0869b70eaeffe6629acdf5e
BLAKE2b-256 checksum
How to use checksums
b8600d310368ed887e89999a88ed55b704429dbc0cba487dac41195c65977f2d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 1, 2026.

Transparency log

Release files / fanferro-0.10.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL fanferro-0.10.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 7.5 MB
Tags CPython 3.12 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
bd47860037dbe7f85afcdbf970156bedf9e95e476178e9a9495fc0fa7885a303
BLAKE2b-256 checksum
How to use checksums
09b56966dfaebe9d3c8d576110c0e236c4acb6bd2264fb9fcbef557b431d0916
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 1, 2026.

Transparency log

Release files / fanferro-0.10.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL fanferro-0.10.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 7.1 MB
Tags CPython 3.12 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
9ef176fee7b20d870a5acdcf6ebf1a25bb097c768066fc369a0f70c545987d74
BLAKE2b-256 checksum
How to use checksums
7ecfcf47e0cd0482a0a6331c3c24b2b89f9478590f05cb0ec96e249eadd691c4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 1, 2026.

Transparency log

Release files / fanferro-0.10.0-cp312-abi3-macosx_11_0_x86_64.whl

Download URL fanferro-0.10.0-cp312-abi3-macosx_11_0_x86_64.whl
Size 7.3 MB
Tags CPython 3.12 abi3 macOS 11.0+ x86-64
SHA-256 checksum
How to use checksums
99bdd1b446bced93ad1c82655006e8a0d4e0ae04be9b2c59df1f565b9da30a36
BLAKE2b-256 checksum
How to use checksums
80167de714817bcaacdf34c1ec660e56fe127c6f4636c27c64beda349a99ef39
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 1, 2026.

Transparency log

Release files / fanferro-0.10.0-cp312-abi3-macosx_11_0_arm64.whl

Download URL fanferro-0.10.0-cp312-abi3-macosx_11_0_arm64.whl
Size 6.3 MB
Tags CPython 3.12 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
1cf653983a959270a24c02154a767d654de6d1fff0592667f29edd8024a6b080
BLAKE2b-256 checksum
How to use checksums
ce3a1c256189f791e8ab87f6bea69a24228795bc3717dcb3d1e10256ed66aa9c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 1, 2026.

Transparency log

Security for fanferro 0.10.0

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

Select from 9 files

Attestation for fanferro-0.10.0.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 Nov 1, 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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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