Skip to content

🪨 firm

Pure-Python ports of the Rails 8 "Solid" stack. Keep background jobs and caching in the SQL database you already run — no Redis, no extra broker, no new infrastructure.

Package Ports Replaces
firm-queue solid_queue Sidekiq / Celery / RQ
firm-cache solid_cache Redis / Memcached cache
firm-channel solid_cable Redis Pub/Sub (Action Cable)
firm-audit (none — original to firm) Hand-rolled audit logging

Inspired by the Rails Solid stack from 37signals.

The four packages are independent — install only what you need — but share a design: your database is the single source of truth, accessed through SQLAlchemy with per-dialect locking.

Why database-backed?

  • One fewer moving part. If you already run PostgreSQL, MySQL, or SQLite, you already have everything you need. Nothing else to deploy, monitor, or secure.
  • Transactional. Enqueue a job in the same transaction as the row it depends on.
  • Inspectable. Jobs and cache entries are just rows — query them, back them up, debug them.

Quick taste

import firm.queue as bq

bq.configure(database_url="postgresql://localhost/myapp")

@bq.job(queue="mailers")
def send_welcome(user_id: int) -> None:
    ...

send_welcome.enqueue(42)            # enqueue from your app
# $ firm-queue start         # run workers + dispatcher
from firm.cache import Cache

cache = Cache(database_url="postgresql://localhost/myapp")
cache.fetch("homepage", lambda: render_homepage())
from firm.channel import Channel

ps = Channel(database_url="postgresql://localhost/myapp")
ps.subscribe("room:42", lambda payload: print(payload))
ps.broadcast("room:42", b'{"msg": "hi"}')
from firm.audit import AuditLog

audit = AuditLog(database_url="postgresql://localhost/myapp")
audit.record("invoice.paid", subject=invoice, actor=user, data={"amount": 4200})

Databases

SQLite (default), PostgreSQL, and MySQL/MariaDB are all supported and tested live. See Database backends for drivers, locking semantics, and when to use which.

Where to next