Author here. I've been building data-driven apps for many years. I've long thought that ORMs are the right abstraction level for developers (still do). But it turns out Claude and friends are better with native query langages (e.g. SQL) than with relatively niche ORMs. Happy to discuss the tradeoffs. I know this pattern isn't for every project, and I'm genuinely curious where others draw the line.
A bit of feedback I've already gotten since this is a Python-leaning article is:
1. What are you crazy: Why not Pydantic? Pydantic is great for many things. I'm a bit fan. I'm just not convinced it needs to be the access layer for my DB. Most apps do many more reads than writes. You pay the cost of validation on every read, not just writes. Alternatives include using dataclass-wizard (https://github.com/rnag/dataclass-wizard) and cattrs (https://catt.rs) for validation on the way in for dataclasses without revalidating on every read.
2. Data classes don't provide runtime type checking. No they don't. If that is paramount for you, choose Pydantic (see #1for why I chose data classes ;) ). But data classes do provide type-checking time type safety. When paired with tools like cattrs and dataclass-wizard, inbound data can be type safe too.
3. This uses MongoDB examples. Fair. The pattern applies to SQL too (psycopg3 + parameterized queries + dataclasses). The core argument, that AI handles vanilla SQL better than SQLAlchemy ORM code, holds even more strongly there given how universal SQL is.
Benchmarks and runnable code are in the repo if you want to check it out.
It is open source, you could just look. :) But here is a summary for you. It's not just one run and take the number:
Benchmark Iteration Process
Core Approach:
- Warmup Phase: 100 iterations to prepare the operation (default)
- Timing Runs: 5 repeated runs (default), each executing the operation a specified number of times
- Result: Median time per operation across the 5 runs
Iteration Counts by Operation Speed:
- Very fast ops (arithmetic): 100,000 iterations per run
- Fast ops (dict/list access): 10,000 iterations per run
- Medium ops (list membership): 1,000 iterations per run
- Slower ops (database, file I/O): 1,000-5,000 iterations per run
Quality Controls:
- Garbage collection is disabled during timing to prevent interference
- Warmup runs prevent cold-start bias
- Median of 5 runs reduces noise from outliers
- Results are captured to prevent compiler optimization elimination
Total Executions: For a typical benchmark with 1,000 iterations and 5 repeats, each operation runs 5,100 times (100 warmup + 5×1,000 timed) before reporting the median result.
That answers what N is (why not just say in the article). If you are only going to report medians, is there an appendix with further statistics such as confidence intervals or standard deviations. For serious benchmark, it would be essential to show the spread or variability, no?
Thanks for the feedback everyone. I appreciate your posting it @woodenchair and @aurornis for pointing out the intent of the article.
The idea of the article is NOT to suggest you should shave 0.5ns off by choosing some dramatically different algorithm or that you really need to optimize the heck out of everything.
In fact, I think a lot of what the numbers show is that over thinking the optimizations often isn't worth it (e.g. caching len(coll) into a variable rather than calling it over and over is less useful that it might seem conceptually).
Just write clean Python code. So much of it is way faster than you might have thought.
My goal was only to create a reference to what various operations cost to have a mental model.
I didn't tell anyone to optimize anything. I just posted numbers. It's not my fault some people are wired that way. Anytime I suggested some sort of recommendation it was to NOT optimize.
For example, from the post "Maybe we don’t have to optimize it out of the test condition on a while loop looping 100 times after all."
The literal title is "Python Numbers Every Programmer Should Know" which implies the level of detail in the article (down to the values of the numbers) is important. It is not.
It is helpful to know the relative value (costs) of these operations. Everything else can be profiled and optimized for the particular needs of a workflow in a specific architecture.
To use an analogy, turbine designers no longer need to know the values in the "steam tables", but they do need to know efficient geometries and trade-offs among them when designing any Rankine cycle to meet power, torque, and Reynolds regimes.
Why in the world would you create a soundtrack for a course? This Python + AI coding course has a few odd quiet, silent sections. Most of it is standard talking style. But when Claude is working hard, sometimes I want everyone to just see what it's doing and I want to get out of the way. This isn't a lot, but it's enough that'd rather have something interesting while Claude is working.
You can listen to and download the whole track if you're looking for some chill developer music to put on that isn't distracting.
A bit of feedback I've already gotten since this is a Python-leaning article is:
1. What are you crazy: Why not Pydantic? Pydantic is great for many things. I'm a bit fan. I'm just not convinced it needs to be the access layer for my DB. Most apps do many more reads than writes. You pay the cost of validation on every read, not just writes. Alternatives include using dataclass-wizard (https://github.com/rnag/dataclass-wizard) and cattrs (https://catt.rs) for validation on the way in for dataclasses without revalidating on every read.
2. Data classes don't provide runtime type checking. No they don't. If that is paramount for you, choose Pydantic (see #1for why I chose data classes ;) ). But data classes do provide type-checking time type safety. When paired with tools like cattrs and dataclass-wizard, inbound data can be type safe too.
3. This uses MongoDB examples. Fair. The pattern applies to SQL too (psycopg3 + parameterized queries + dataclasses). The core argument, that AI handles vanilla SQL better than SQLAlchemy ORM code, holds even more strongly there given how universal SQL is.
Benchmarks and runnable code are in the repo if you want to check it out.