Chang argues that PEP 750 template strings address a two-decade-old gap where SQL drivers, HTML templaters, and shell-command builders have all been hand-rolling structured interpolation. Because t-strings return a Template object exposing literal segments and interpolations separately, the consumer — not the producer — decides how to escape, transforming a community norm enforced by linters into an actual language feature.
By submitting the post and pushing it to 153 points, rbanffy signal-boosts the framing that the quiet 3.15 additions — t-strings in particular — deserve more attention than the marquee free-threaded and JIT work that dominates release coverage.
Chang highlights that compression.zstd graduating to first-class stdlib status, ssl finally shedding wrap_socket and other pre-3.0 TLS surface, and pathlib.Path absorbing copy/move helpers from shutil all matter more for day-to-day code than the headline GIL work. His framing is that these changes remove long-standing rough edges and third-party dependencies that accumulated over a decade of deprecation cycles.
Chang's thesis is that free-threaded builds and JIT progress 'suck up all the oxygen' in Python release cycles, while the actually-useful changes ship in patch notes nobody reads. He argues the asymmetry matters because the quiet items — language-level primitives like t-strings, stdlib promotions, API cleanups — touch ordinary application code far more directly than the runtime experiments that get the headlines.
Python 3.15 is shaping up as one of those releases where the headline features (free-threaded builds, JIT progress) suck up all the oxygen and the actually-useful changes ship in the patch notes nobody reads. A blog post from Tom Chang, currently sitting at 153 points on Hacker News, walks through the under-the-radar items in the 3.15 alpha — and several of them matter more for day-to-day code than the marquee work on the GIL.
The biggest of the quiet wins is PEP 750 template strings, now landing as a real language feature. Where f-strings eagerly interpolate values into a single string, t-strings produce a structured `Template` object that carries the literal parts and the interpolations separately — which is exactly what every SQL driver, HTML templater, and shell-command builder has been hand-rolling for two decades. The syntax is the prefix you'd guess: `t"SELECT * FROM users WHERE id = {user_id}"` returns a `Template`, not a string, and the consumer decides how to render or escape each interpolation.
The other quiet additions: `compression.zstd` graduates from a third-party install to a first-class stdlib module, joining `gzip`, `bz2`, and `lzma` under a unified `compression` namespace. The `ssl` module finally drops `wrap_socket` and the rest of the pre-3.0 TLS surface that's been deprecated since roughly the Obama administration. `os.path` gets a new `isreserved()` check for Windows reserved names. And `pathlib.Path` quietly absorbs more of `shutil`'s job with copy/move helpers that respect symlinks and metadata in ways the old recipes never quite got right.
T-strings are the headline you missed. For a decade, the standard advice for "don't build SQL with f-strings" has been a community norm enforced by linters and code review — the language itself had no way to express "this is a parameterized query, not a string." PEP 750 changes that. The `Template` object exposes `.strings` (the literal segments) and `.interpolations` (each with its value, expression source, conversion, and format spec), so a library can walk the structure and decide what to do — bind as a parameter, HTML-escape, shell-quote, whatever the context demands.
The practical consequence is that ORM and templating maintainers can now offer an API where the *unsafe* version requires explicit work. Imagine `db.execute(t"SELECT * FROM users WHERE id = {user_id}")` doing the right thing by default, with `db.execute(f"...")` either rejected at runtime or flagged by your type checker. Jinja, SQLAlchemy, and the various HTML libraries have already been prototyping integrations against the PEP 750 reference implementation. Expect the next 18 months of Python web framework releases to quietly reorganize their core APIs around t-strings, the way the ecosystem reorganized around type hints after 3.5.
Zstandard in the stdlib is a smaller deal but a real one. Zstd has been the de facto choice for new compression workloads — better ratio than gzip, faster than xz, tunable across a wide range — and shipping it without a `pip install zstandard` removes the last excuse for greenfield code to default to gzip. The new `compression` package namespace also signals an intent to clean up the random scatter of `gzip`/`bz2`/`lzma`/`tarfile` modules over time. It's the kind of small organizational change that pays off in five years when somebody is teaching Python to a new developer.
The `ssl` cleanup is the one that will bite people. `ssl.wrap_socket` has been deprecated since 3.7 and shouting since 3.10, but it's still in production code at every shop that has a script older than the engineer maintaining it. `match_hostname`, `RAND_pseudo_bytes`, and a handful of constants go with it. None of this is a surprise — the deprecation warnings have been screaming for releases — but "deprecated" and "removed" are different events on a maintainer's calendar, and 3.15 turns the latter dial.
If you maintain a library that does any kind of string interpolation with security implications — SQL, HTML, shell, LDAP, anything — start prototyping a t-string API now. The libraries that ship a clean PEP 750 integration in the first six months after 3.15 GA will define the idiomatic patterns the rest of the ecosystem copies. This is the same window of opportunity that pydantic exploited around type hints and that FastAPI exploited around async — early movers get to write the playbook.
If you maintain application code, the t-string migration is gentler than you'd expect. F-strings aren't going anywhere; t-strings are a separate prefix for a separate use case. The realistic upgrade path is: leave existing f-strings alone, adopt t-strings at the boundaries where strings meet an interpreter (database, browser, shell), and let your linter catch new violations. Ruff and the typing ecosystem already have draft rules for "this f-string looks like SQL, did you mean a t-string?"
For the `ssl` removal, the migration is mechanical but tedious. Run `python -W error::DeprecationWarning` against your test suite on 3.14 today and you'll get a free list of every line that needs to change before 3.15. If you have a vendored copy of an HTTP library from before requests dominated the ecosystem, this is the upgrade that finally kills it. For Zstd, there's no migration needed — just stop bundling the third-party package on new code.
Python's release cadence has settled into a rhythm where the headline feature of any given year (pattern matching, the GIL work, the JIT) sucks attention away from the structural improvements that actually shape how code gets written. T-strings are the structural improvement of 3.15, and they're going to land the way `async`/`await` and type hints did — quietly at first, then everywhere, then it's hard to remember what the language looked like without them. Bookmark the release notes, run your test suite against the alpha, and start watching which of your dependencies post t-string integration RFCs first. Those are the maintainers paying attention.
Top 10 dev stories every morning at 8am UTC. AI-curated. Retro terminal HTML email.