AceMQ for FastAPI
acemq-amqp-fastapi wires acemq-amqp
into a FastAPI application: a lifespan that owns the connection and the
consumers, Depends for publishing from a route, a decorator
for registering handlers, and a health check that knows a blocked broker
is not a reason to restart.
It is the Python analogue of the Spring
Boot starter, and it occupies the same seam. What Spring gives a
starter is SmartLifecycle — something that runs after the
context is built and before it is torn down. What FastAPI gives is
lifespan. They are the same idea and this package lives in
it.
from acemq_amqp import Ack, Message, accept
from acemq_fastapi import AceMQ, publishes
from acemq_amqp import Publisher
from fastapi import Depends, FastAPI
from typing import Annotated
acemq = AceMQ()
@acemq.consumer("orders")
async def handle(message: Message) -> Ack:
print(message.payload)
return accept()
app = FastAPI(lifespan=acemq.lifespan)
app.include_router(acemq.health_router())
Orders = Annotated[Publisher, Depends(publishes("orders"))]
@app.post("/orders")
async def place(orders: Orders) -> dict[str, str]:
result = await orders.send({"sku": "A-1"})
return {"id": result.message_id}Where to go next
| Start here | Getting started |
| Reference | Configuration — every
acemq.* setting |
| Usage | The lifespan · Consumers · Publishing from a route · Testing |
| Patterns | Patterns from FastAPI · Request-reply · Streams · Serialization |
| Operations | Security · Topology · Health · Observability · Shutdown |
| Support | Enterprise support |
Patterns from FastAPI is the page to read
second. It lists every pattern the library carries and says, for each
one, whether there is an acemq.* setting for it or whether
it is four lines in a lifespan — because for most of them it is the
latter, and knowing which is which saves looking for a setting that was
never going to exist.
Versions
This release wires acemq-amqp 0.7.12,
which is the current library release. The declared dependency is a
range, acemq-amqp[rabbitmq]>=0.7.12,<0.8, so pip
resolves the newest release inside it rather than a version pinned here
going stale — and the range is also the constraint your application
inherits.
acemq-amqp-fastapi |
0.1.5 |
acemq-amqp |
>=0.7.12,<0.8, tested against 0.7.12 |
| Python | 3.10, 3.11, 3.12, 3.13 |
| FastAPI | >=0.110 |
pydantic-settings |
>=2.2 |
The floor is 0.7.12. Nothing below 0.7.0 would do, because the health
check calls Connection.blocked, which 0.6.0 does not have;
0.7.9 is where a stream reader resumes where it was after a
reconnection, the publishes the library makes on your behalf are
confirmed and kept when unroutable, and an idempotent handler that
raises gives its claim back; 0.7.10 is where a scheduled message that is
due but reaches no queue is kept and tried again rather than dropped;
0.7.11 is where a reply the requester cannot decode raises the codec's
error at once instead of being lost and waiting out the timeout; 0.7.12
is where a publish into a broker under a memory alarm raises within
acemq.confirm_timeout instead of waiting for the alarm to
clear. The ceiling is one minor ahead, because a pre-1.0 library puts
its breaking changes in minor bumps. This package versions separately
from the library and starts at 0.1.0, as the Spring Boot starter does
from acemq-java-amqp: it tracks FastAPI and Starlette's
release train as much as AceMQ's.
What it is not
This is FastAPI, not Django. Nothing here is
reusable in a WSGI application, and that is not an oversight. The whole
design rests on one event loop that the framework owns and hands to the
lifespan: consumers are asyncio tasks created on it,
handlers await on it, and the drain runs on it while the server is
shutting the sockets. Django has no such seam in its synchronous path —
an AppConfig.ready() runs per worker process with no loop
and no shutdown hook, and a management command that starts consumers is
a second process with a different lifetime. A Django integration is a
different design with a different set of honest limits, and pretending
one package could be both would produce something that was good at
neither.
It is not a second messaging library. Every message,
acknowledgement, codec, retry policy and topology is the library's,
under the library's own names. This package owns the wiring and nothing
else: if you want to know what accept() does or what
happens to a message that fails five times, the answer is in the library's
documentation and not here.
It does not own your /health. It
contributes one check. Whether that becomes the whole readiness probe,
or one part of an aggregate beside your database, is yours to decide —
see health.
It does not survive a restart on your behalf. A publish that must not be lost when the process dies wants the library's outbox, and no lifespan is a substitute for it.