writingartikelen

firm: the Rails Solid stack, in pure Pythonfirm: de Rails Solid-stack, in pure Python

Rails 8 shipped the Solid stack — solid_queue, solid_cache, solid_cable — on a simple bet: most apps don’t need Redis. Your SQL database can hold the job queue, the cache, and the pub/sub channel just fine, and running one fewer piece of infrastructure is worth a lot.

firm is that bet in Python. It’s a pure-Python port of the Solid stack, plus one module of its own.

One package, four independent modules

Install only the part you need:

Module Install Ports Highlights
queue pip install "firm[queue]" solid_queue concurrency controls, recurring tasks, retries, forked/threaded supervisor, crash recovery
cache pip install "firm[cache]" solid_cache FIFO age/size/count eviction, pluggable coders, at-rest encryption
channel pip install "firm[channel]" solid_cable broadcast/subscribe over your database, polling listener, automatic message trimming
audit pip install "firm[audit]" (original to firm) append-only audit log, opt-in retention, history() querying

All four run on SQLite, PostgreSQL, and MySQL/MariaDB — verified live against all three. The top-level package imports nothing heavy, so a queue-only worker process never loads the cache, pub/sub, or audit code.

What using it looks like

Background jobs:

import firm.queue as bq
bq.configure(database_url="postgresql://localhost/myapp")

@bq.job()
def greet(name): print(f"hi {name}")

greet.enqueue("Ada")        # then: firm-queue start --import myapp.jobs

Caching:

from firm.cache import Cache
cache = Cache(database_url="postgresql://localhost/myapp")
cache.fetch("k", lambda: expensive())

Pub/sub:

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"}')

Audit log:

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

Why “no Redis” is the point

If you already run PostgreSQL, adding Redis means another process to deploy, monitor, back up, and reason about failure modes for. For a lot of apps that cost buys very little. firm keeps jobs, cache, channels, and the audit trail in the same transactional database as the rest of your data — which also means a job and the row it writes can commit or roll back together.

How it’s built

firm is a uv workspace of independent packages — firm-core, firm-queue, firm-cache, firm-channel, firm-audit, firm-ui — with a thin top-level firm package that wires them together via extras. You add database drivers and features as extras too: firm[queue,postgres], firm[cache,encryption], and so on.

The docs are built with Zensical (the Material for MkDocs team’s successor to MkDocs), and there’s an llms.txt / llms-full.txt pair for feeding the whole thing to an agent. Tests run on SQLite by default and against live Postgres/MySQL when you point them at real databases.

Inspired by the Rails Solid stack from 37signals (solid_queue, solid_cache, solid_cable).

Rails 8 leverde de Solid-stack — solid_queue, solid_cache, solid_cable — op basis van een simpele weddenschap: de meeste apps hebben geen Redis nodig. Je SQL-database kan de taakwachtrij, de cache en het pub/sub-kanaal prima aan, en één stuk infrastructuur minder draaien is veel waard.

firm is die weddenschap in Python. Het is een pure-Python port van de Solid-stack, plus één eigen module.

Eén package, vier onafhankelijke modules

Installeer alleen wat je nodig hebt:

Module Installatie Port van Kenmerken
queue pip install "firm[queue]" solid_queue concurrency-controls, terugkerende taken, retries, forked/threaded supervisor, crash-recovery
cache pip install "firm[cache]" solid_cache FIFO-eviction op leeftijd/grootte/aantal, plugbare coders, encryptie at-rest
channel pip install "firm[channel]" solid_cable broadcast/subscribe over je database, polling-listener, automatisch opschonen van berichten
audit pip install "firm[audit]" (origineel in firm) append-only auditlog, opt-in retentie, history()-queries

Alle vier draaien op SQLite, PostgreSQL en MySQL/MariaDB — live getest tegen alle drie. Het top-level package importeert niks zwaars, dus een queue-only workerproces laadt nooit de cache-, pub/sub- of auditcode.

Hoe het gebruik eruitziet

Achtergrondtaken:

import firm.queue as bq
bq.configure(database_url="postgresql://localhost/myapp")

@bq.job()
def greet(name): print(f"hi {name}")

greet.enqueue("Ada")        # then: firm-queue start --import myapp.jobs

Caching:

from firm.cache import Cache
cache = Cache(database_url="postgresql://localhost/myapp")
cache.fetch("k", lambda: expensive())

Pub/sub:

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"}')

Auditlog:

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

Waarom “geen Redis” het punt is

Als je al PostgreSQL draait, betekent Redis erbij nog een proces om te deployen, te monitoren, te back-uppen en waarvan je de faalscenario’s moet doorgronden. Voor veel apps levert die kost weinig op. firm houdt taken, cache, channels en het auditspoor in dezelfde transactionele database als de rest van je data — wat ook betekent dat een taak en de rij die hij wegschrijft samen kunnen committen of terugrollen.

Hoe het gebouwd is

firm is een uv-workspace van onafhankelijke packages — firm-core, firm-queue, firm-cache, firm-channel, firm-audit, firm-ui — met een dun top-level firm-package dat ze via extras aan elkaar knoopt. Databasedrivers en features voeg je ook als extras toe: firm[queue,postgres], firm[cache,encryption], enzovoort.

De docs zijn gebouwd met Zensical (de opvolger van MkDocs van het Material for MkDocs-team), en er is een llms.txt / llms-full.txt-paar om het geheel aan een agent te voeren. Tests draaien standaard op SQLite en tegen live Postgres/MySQL als je ze op echte databases richt.

Geïnspireerd door de Rails Solid-stack van 37signals (solid_queue, solid_cache, solid_cable).

pythonbackground-jobscachingpostgresqlsqlite

← all writing← alle artikelen