A good list came across my feed: 12 libraries that turn a weekend script into
production software — click, python-dotenv, SQLAlchemy, marshmallow, tenacity,
tqdm, schedule, celery, pytest, structlog, the Docker SDK, boto3. Every pick is
defensible. It's the kind of list I'd have loved when I was younger.
I run a production tool — a self-hosted AWS drift detector, live, with real
users. So I checked it against the list, honestly expecting to find gaps.
I use two of the twelve.
Not because I'm hardcore and roll everything by hand. The opposite: because I
picked Django, and Django had already made ten of those decisions before I
wrote a line. That's the part the list doesn't say out loud — choosing a
framework is itself a dependency decision, and it's the one that subsumes most
of the others.
The ten that vanished when I picked the framework
None of these are "I don't need that." They're "I already have that, and adding
the library would give me two of the same thing":
-
click →
manage.pycommands. My CLI is Django management commands. Arg parsing,--help, type coercion, discovery — all free, all consistent with the rest of the app. A second CLI framework would just be a parallel one to keep in sync. -
SQLAlchemy → the Django ORM. I'm already on one ORM (with
psycopg+dj-database-url, so SQLite-to-Postgres is a URL, not a rewrite). Two ORMs in one app isn't flexibility, it's a bug factory — two identity maps, two migration stories, two mental models. -
marshmallow → forms + a boundary
normalize(). Untrusted input goes through Django form/model validation; the AWS scan results get normalized into a fixed shape at the edge. The validation layer exists; it just isn't a separate library. - schedule / celery → django-apscheduler. I already have periodic scans. And celery specifically is the wrong direction for me right now: I wrote a whole post about guarding a single scheduler from running twice. Bolting on Redis + a distributed task queue is scaling I haven't earned and complexity I'd have to defend at 2am.
-
structlog's premise ("graduate from
print") → I already did. The whole codebase is on stdlibloggingwithgetLogger(__name__). The problem that library solves for a script author — I don't have it. -
python-dotenv → compose injects the env.
docker composesets the variables; I read them withos.getenvat the settings boundary. dotenv is a nicety for local non-Docker dev, not a production dependency. - tqdm → server jobs log, they don't draw bars. My scans run on a schedule or a web request, not in an attended terminal. A progress bar has no one to progress to; a structured log line does.
-
Docker SDK → I'm the thing inside the container. SyncVey ships as a
container (
docker compose up). It doesn't need to drive Docker from Python. Different job entirely.
That's ten, gone — not by discipline, by a single upstream choice.
The two that survived the filter
Here's where I keep myself honest, because "I need nothing" is always a lie. Two
of the twelve point at real holes Django didn't fill:
tenacity — the genuine gap. boto3 already retries AWS calls for me
(botocore's built-in backoff). But my two non-AWS HTTP calls — posting to a
Slack webhook, fetching the EOL calendar — go out through raw urllib, single
shot, no retry. A blip on either just fails. That's exactly the "transient
network error" case the list names, and the framework doesn't cover it because
it's not a framework concern. This one I should actually add.
structlog — an upgrade, and it did its job before I installed it. I'm on
plain logging, which is fine, but structured/JSON logs would make scan jobs and
attribution failures queryable instead of greppable. And while auditing against
the list I found a stray print(f"Error processing …") that had slipped past my
own logging convention in one view. The list earned its keep not by adding a
dependency — by making me look.
The actual lesson isn't the count
The quote at the bottom of these lists is usually some version of "good
developers write code, great developers assemble systems." True. But assembling
a system doesn't start with adding twelve pieces. It starts with picking the
one piece that pre-answers ten of the questions — and then knowing:
- which decisions it already made, so you don't bolt on a second copy (the two-ORM trap), and
- which two it left open, so you don't skip them (my un-retried webhook).
The leverage the list is pointing at is real. I'd just locate it one level up:
not in which twelve libraries, but in which one framework makes ten of them
moot — and the discipline to audit the seams it leaves behind.
The honest caveat
If you're writing a genuine standalone script — a CLI tool, a data-munging job,
no web surface, no persistence — the list is dead on and reaching for Django
would be the wrong hammer. click + tqdm + dotenv is exactly right there, and I'd
use all three. The point isn't "framework always wins." It's match the bundle to
the shape of the thing. A weekend script and a hosted service want different
default stacks, and pretending one list fits both is how you end up with two
ORMs.
Takeaways
- Picking a framework is a dependency decision — the biggest one. It bundles answers to CLI, ORM, validation, config, logging, and scheduling before you choose any of them individually.
- Before adding a library, check whether your framework already gives you that capability. A second one isn't more power; it's a synchronization problem (two ORMs, two CLIs, two config layers).
- Then find the seams the framework didn't cover. For me that was retries on non-framework HTTP calls (tenacity) and structured logging (structlog) — the two of twelve that were real.
- celery/Redis is scaling you should have to justify, not a default. Single-node with a guarded scheduler is a legitimate production shape.
- Auditing your stack against someone else's list is worth it even when you adopt
almost none of it — it's how I found a
print()I'd left in a view.
The tool I ran this audit against is an open-source, self-hosted AWS drift
detector — Django, MIT, one docker compose up:
syncvey.com. If you checked your own production service
against a "must-have libraries" list, how many would you actually be missing —
and how many did your framework quietly hand you years ago?
Top comments (0)