Note that the variables are safely and correctly interpolated at compile time. And it's type checked across the boundary too, by checking (at compile time) the column types with the live database.
Yes, what you did is strictly more powerful than what the Python people did. And you did it 20 years ago. Well done, have an upvote. And yet, here we are in 2025 with Python popularity growing unstoppably and (approximately) no one caring about OCaml (and all the other languages better than Python). It makes me sad.
Aren't there other benefits to server-side parameter binding besides just SQL-injection safety? For instance, using PG's extended protocol (binary) instead of just raw SQL strings. Caching parameterized prepared statements, etc.
Also:
db.execute(t"QUERY WHERE name = {name}")
Is dangerously close to:
db.execute(f"QUERY WHERE name = {name}")
A single character difference and now you've just made yourself trivially injectible.
I don't think this new format specifier is in any way applicable to SQL queries.
Templates are a very different duck type from strings and intentionally don't support __str__(). The SQL tool can provide a `safe_execute(Template)` that throws if passed a string and not a Template. You can imagine future libraries that only support Template and drop all functions that accept strings as truly safe query libraries.
> Caching parameterized prepared statements, etc.
Templates give you all the data you need to also build things like cacheable parameterized prepared statements. For DB engines that support named parameters you can even get the interpolation expression to auto-name parameters (get the string "name" from your example as the name of the variable filling the slot) for additional debugging/sometimes caching benefits.
You solve that with an execute(stmt) function that requires you to pass in a template.
In Javascript, sql`where id = ${id}` is dangerously close to normal string interpolation `where id = ${id}`, and db libs that offer a sql tag have query(stmt) fns that reject strings.
> A single character difference and now you've just made yourself trivially injectible.
No; a single character difference and now you get a `TypeError`, which hopefully the library has made more informative by predicting this common misuse pattern.
> Aren't there other benefits to server-side parameter binding besides just SQL-injection safety? For instance, using PG's extended protocol (binary) instead of just raw SQL strings. Caching parameterized prepared statements, etc.
All of which can be implemented on top of template strings.
> A single character difference and now you've just made yourself trivially injectible.
It's not just a one character difference, it's a different type. So `db.execute` can reject strings both statically and dynamically.
> I don't think
Definitely true.
> this new format specifier is in any way applicable to SQL queries.
With t-strings you could run proper escaping over the contents of `some_file` before passing it to a shell.
I'd have to take a look at the order things happen in shell, but you might even be able to increase security/foot-gun-potential a little bit here by turning this into something like `stat "$( base64 -d [base64 encoded content of some_file] )"`.
The key point is that t-strings are not strings. Db.execute(t”…”) would throw an exception, because t”…” is not a string and cannot be interpreted as one.
In order for a library to accept t-strings, they need to make a new function. Or else change the behavior and method signature of an old function, which I guess they could do but any sanely designed library doesn’t do.
Handling t-strings will require new functions to be added to libraries.
The execute function can recognize it as a t-string and prevent SQL injection if the name is coming from user input. f-strings immediately evaluate to a string, whereas t-strings evaluate to a template object which requires further processing to turn it into a string.
or other ways of manually interpolating the string - because `db.execute` can flag a `TypeError` if given a string (no matter how it was constructed) rather than a `Template` instance.
Python is not the first one to get this feature. It's been present in JS for some time now, and before that in C# (not sure if that's the origin or they also borrowed it from somewhere). Python adopted it based in part on successful experience in those other languages.
Assuming you also need to format non-values in the SQL (e.g. column names), how does the `execute` function is supposed to make the difference between stuff that should be formatted in the string vs a parametrized value?
I think this would have solved the log4j vulnerability, no?
As I understand it, log4j allowed malicious ${} expansion in any string passed to logging functions. So logging user generated code at all would be a security hole.
But Python's t-strings purposely _do not_ expand user code, they only expand the string literal.
Yes and your example is the hero case because it isn't just sugar. A t-string implementation for SQL will of course escape the values which is a common security issue.
Now instead of being explicit all it takes is someone unfamiliar with t strings (which will be almost everyone - still few know about f strings and their formatting capabilities) to use an f instead and you are in for a bad time.
Any sane library will just error when you pass a string to a function that expects a template though. And that library will have types too so your IDE tells you before you get that far.
That is an issue, but essentially it boils down to the existing risk of unknowledgeable people not escaping untrusted inputs. The solution should be more education and better tooling (linters, SAST), and t-strings are likely to help with both.
I suppose lack of overlap in the "interface surface" (attributes, including callables) between `str` and `Template` should nip the kind of issue in the bud -- being passed a `Template` and needing to actually "instantiate" it -- accessing `strings` and `values` attributes on the passed object, will likely fail at runtime when attempted on a string someone passed instead (e.g. confusing a `t`-string with an `f`-string)?
> In addition, I hope that the tooling ecosystem will adapt to support t-strings. For instance, I’d love to see black and ruff format t-string contents, and vscode color those contents, if they’re a common type like HTML or SQL.
This is such a strange take on t-strings. The only way for anything to infer that the template string is supposed to turn into valid HTML or SQL is to base it of the apparent syntax in the string, which can only be done in an ad-hoc fashion and has nothing to do with the template string feature.
The way the feature has been designed there is no indication in the string itself what type of content it is or what it will eventually be converted to. It’s all handled by the converting function.
As others have added, something like sql”select * from {table}” would have been able to do this, but there’s not even any guarantees that something that is in a template that will be converted into valid sql by a converting function should be any type of valid sql prior to that conversion. For all you know t“give me {table} but only {columns}” might be a converted into valid sql after the template is processed.
As Paul mentioned, we spent quite a lot of time considering these issues as PEP 750 came together. In the end, we concluded (a) the PEP leaves open quite a few potential approaches for tools to adopt (not just the one you suggest, as others here have pointed out), and (b) it's ultimately something that the broader tooling community needs to rally around and should probably be out of scope for the PEP itself.
So, with that background in mind, I am indeed hopeful we'll see the ecosystem adapt! :-)
I get that. But that “ecosystem might adapt” has absolutely nothing to do with t-strings is what I’m saying. Anyone who wants to validate sql will have to just look through strings guess that it’s sql and validate and highlight it. This could be done before and after just the same. The template strings by nature of just being “strings that will turn into something else at some later point” do nothing to further this in any way.
And highlighters and static analyzers will key off of this.
JavaScript's tagged template literals are actually about as flexible as this, since you can dynamically choose the tag function, it's just very rare to do so, so tools assume a lot based on the name of the function. Python tools can basically do the same thing, and just not support t-strings that aren't nested inside a well-named processing function.
> And highlighters and static analyzers will key off of this.
Then the t is redundant and we don’t need t strings involved in any way. This would work just as well. In fact better by using html("<h1>Hello</h1>") and have it return a html object instead of returning a just a string which is what t-strings will do. So literally the only contribution from t-strings in that context is to make things worse.
If they had just standardized some way of putting semantics into the template everything would be better. Let us define a “sql = temlate(….)” and use sql”…”
The original PEP and the original discussion had this in scope. We removed it to let this emerge later. There are different ways to signal the language -- some more friendly to tooling, some more robust.
PyCharm (as well as the other JetBrains IDEs) has for at least 10 years supported language injection in Python strings using a comment [0]. Worst case scenario there's no reason whatsoever that that couldn't be used by formatters to apply auto-formatting in a structured and deterministic way.
But that's far from the only option, either! IntelliJ for Java also supports annotations on arguments [1] that then mean that you get syntax highlighting everywhere you use a string literal with the given function:
public void query(@Language("SQL") String sql)
In Python, typing.Annotated appears to have been specifically designed for purposes like this [2]:
> If a library or tool encounters an annotation Annotated[T, x] and has no special logic for the metadata, it should ignore the metadata and simply treat the annotation as T. As such, Annotated can be useful for code that wants to use annotations for purposes outside Python’s static typing system.
So something like this should be perfectly viable:
SQL = Annotated[Template, "language", "SQL"]
def query(sql_query: SQL):
# do stuff with Template to sanitize
query(t"SELECT * FROM foo WHERE bar={bar}")
Now, where you're right is that we shouldn't actually require template strings to accomplish this! As noted, JetBrains has been doing this since forever. But maybe template strings will be useful enough for purposes like this that the tooling will actually evolve to support it in formatters and other editors (and maybe PyCharm can get some of the better support that Java has from JetBrains).
Couldn’t you do this with a type annotation? e.g. SQLAlchemy could have a SQL type so tools like mypy could see a Template instance and confirm you’re using it safely but Black, Ruff, or SQLFluff could look for the more specialized Annotated[Template, SQL] to realize that the template could be formatted as SQL, and something like Django could even have Annotated[Template, Email], Annotated[Template, HTML], or Annotated[Template, JSX] to indicate what context the same templating syntax is targeting.
This is what we discussed in the first revision of the PEP (the use of `Annotated`.) But we found out: linters don't know anything about the Python type system.
We hope to get a community around all of this, stuff at PyCon US, EuroPython, etc. and work some of this out. The JSX/TSX world really has good tooling. We can provide that for those that want it, perhaps better on some aspects.
> The only way for anything to infer that the template string is supposed to turn into valid HTML or SQL is to base it of the apparent syntax in the string
Not the only thing. You can also look at how it is used. Your editor could know of how some popular libraries use t-strings, track which t-strings get passed into functions from those libraries, and use that to assume what grammar the t-string should follow.
Is that cheating? In some sense, yes, but it also is useful and likely will be worth it for quite a few programmers.
You could already do everything you are proposing t-strings will make easier with just strings or f-strings.
‘query: SQL “Select …”’
“Wait” you’ll say “then the function taking the string has to parse the string and will have to add ugly logic grabbing variables from globals()!” Ah yes. That’s such an ugly solution… let’s instead codify just that as the official best practice and sell people on it by prefixing a t to the string so they don’t notice…
Will this allow neat SQL syntax like the following?
city = 'London'
min_age = 21
# Find all users in London who are 21 or older:
users = db.get(t'
SELECT * FROM users
WHERE city={city} AND age>{min_age}
')
If the db.get() function accepts a template, it should, right?
This would be the nicest way to use SQL I have seen yet.
You would need triple-quotes to span multiple lines, but yes, that is exactly how it's intended to work. `db.get` will receive a Template instance, which stores string parts something like `('\n SELECT * FROM users\n WHERE city=', ' AND age>', '\n')` and interpolated parts like `(Interpolation('London'), Interpolation(21))`. It's then responsible for assembling and executing the query from that.
That’s the sort of thing people have built with the equivalent feature in JavaScript, so it should do. Eg https://github.com/andywer/squid is a nice example.
Isn't the actually proper way to use prepared statements anyway? If we are doing that properly, then what does this t string business buy us for SQL usage from Python?
Thanks, I hate it. While it's nice syntactic sugar, the difference between an SQL injection vulnerability and a properly parametrized query is now a single letter that's easily missed
The t-string produces a Template object without a __str__() method. You can’t mistakenly use an f-string in its place. Either the code expects a string, in which case passing it a Template would blow it up, or the code expects a Template, in which case passing it a string would blow it up.
I guess that is a misunderstanding on your side, about how templates work. Less hate and more love might help to avoid this type of hotheaded misconception ;-)
Why do you think changing a letter would cause a vulnerability? Which letter do you mean?
Also I wonder how easy it will be to shoot oneself in the foot. It may be easy to accidentally make it to a string too soon and not get the proper escapeing.
Personally, this feels like a feature that is too focused on one problem to be a general feature. Python is getting huge. When people ask me if Python is easy and simple to learn I have to say "the basics, yes, but to to learn the whole language... not so much".
I feel like in this sense Go really is interesting by rejecting almost every single feature. Honestly not sure generics were worth it as they add a lot of complexity, and while they are nice, I don't need them very much. The general idea to keep the language at its original focus is the right idea IMO. C++ would be the most extreme case where the language itself barely resembles what it started out as.
Python has always been a batteries-included language, so having a go at templated string interpolation —a feature other languages have had for decades— seems like a strange gripe.
It's far more essential than little utilities like textwrap or goliath packages like Python's bundled tkinter implementation.
This is a pretty simple and useful feature. I wouldn’t say that it bloats the language too much. Descriptors and metaclasses are much more complicated and have a lot more implications and have been in the language for a veeeeery long time. Is it decades already?
This feature is not complicated, but one must keep every feature that can possibly be seen in code in their head. Even if it is familiar now, what happens when you use the feature in the one small section of code where it fits, nowhere else, and then read that code 2 years later? This is the problem with adding useful features that are only used in a few key places. I'm not saying Go is a perfect language, far from it, but limiting # of features as a general goal is something more languages should strive for IMO.
Yeah, Python hasn’t been a simple language for a long time, if ever. That’s probably the biggest misconception about the language - that its friendly syntax implies simple semantics. It’s not true at all.
A lot of Python's complexity like descriptors and metaclasses and the so-called cooperative multiple inheritance come from an era where object orientation is gospel and yet people keep finding limitations to what can be accomplished in an OO paradigm and therefore they patch up the language to support ever more complicated use cases.
I have a feeling that if metaclasses and descriptors were to be proposed today it would be laughed out of the PEP process completely.
I'm truly glad that Go exists for people who like languages that are simple even to the point of frustration and I hope it never changes. But I'm glad that other languages exist for those of us for whom learning some syntax is not a barrier and having convenient ways to do common things is highly valued.
There’s an interesting trade off around maintenance. Python’s stdlib means there’s more consistency across projects and you can assume basic things like error handling which you had to check individually on each Go program, which is the kind of stuff which adds up when you have a lot of small programs.
This is especially noticeable with AWS Lambda where you can have a lot of useful stuff running for years without doing more than bumping the runtime version every year or two, but also that is one highly opinionated architecture so it’s not globally optimal for everyone.
This is exactly how Nim is. The f-string like equivalent uses a macro called "fmt" which has a short alias "&". So you can say:
import std/strformat
let world = "planet"
echo &"hello {world}"
The regular expression module does a similar thing with a `re"regular expression"` syntax or std/pegs with peg"parsing expression grammar" and so on. There are probably numerous other examples.
In general, with user-defined operators and templates and macros, Nim has all kinds of Lisp-like facilities to extend the language, but with a more static focus which helps for both correctness and performance.
This was the original proposed idea in the PEP (750), but it changed overtime. There is a section in the PEP to explain why it changed to t-strings if you are interested.
PEP 638 has always seemed to me like something of a generalization of the idea. But that really feels to me like a 4.0 feature, or rather something that needs to be designed for from the beginning. (Which is why I've done a bit of that in my own mind...)
In my own language design (nothing public yet - need to prioritize more practical things at the moment) this is definitely on the menu. Aside from a minimal set, keywords are implemented as compile-time macros; and it's intended that they can be extended, both "natively" and in the implementation language, by writing AST-manipulation code. But the handling of arithmetic expressions, as well as the broader line/block structure, is hard-coded. (I draw inspiration from both Python and the Lisp family.)
What I really don't get is how it's any different than applying whatever function you would apply to the template, on the f-string variables. So instead of:
> Is it just about not forgetting the sanitization/string manipulation part and forcing you to go through that?
This is a very big deal! It's also about centralizing that work. Now that sanitization can occur in the consumer of the t-string (for example, the API to your HTML renderer), rather than in every f-string.
Pretty much, yeah. The article highlights that people were using f-strings directly, and they wanted to provide an alternative for lightweight template/interpolation.
I feel like I'm still missing something when they're saying this about the example(s):
"Neither of these examples is possible with f-strings. By providing a mechanism to intercept and transform interpolated values, template strings enable a wide range of string processing use cases."
As far as I can see, anything you do with the template, you could do before building the f-string or inline as in my intial example.
1. Allowing library developers to do whatever they want with {} expansions is a good thing, and will probably spawn some good uses.
2. Generalizing template syntax across a language, so that all libraries solve this problem in the same way, is probably a good thing.
https://github.com/darioteixeira/pgocaml
Note that the variables are safely and correctly interpolated at compile time. And it's type checked across the boundary too, by checking (at compile time) the column types with the live database.
Also:
Is dangerously close to: A single character difference and now you've just made yourself trivially injectible.I don't think this new format specifier is in any way applicable to SQL queries.
> Caching parameterized prepared statements, etc.
Templates give you all the data you need to also build things like cacheable parameterized prepared statements. For DB engines that support named parameters you can even get the interpolation expression to auto-name parameters (get the string "name" from your example as the name of the variable filling the slot) for additional debugging/sometimes caching benefits.
In Javascript, sql`where id = ${id}` is dangerously close to normal string interpolation `where id = ${id}`, and db libs that offer a sql tag have query(stmt) fns that reject strings.
No; a single character difference and now you get a `TypeError`, which hopefully the library has made more informative by predicting this common misuse pattern.
All of which can be implemented on top of template strings.
> A single character difference and now you've just made yourself trivially injectible.
It's not just a one character difference, it's a different type. So `db.execute` can reject strings both statically and dynamically.
> I don't think
Definitely true.
> this new format specifier is in any way applicable to SQL queries.
It's literally one of PEP 750's motivations.
Deleted Comment
Deleted Comment
Agree. And the mere presence of such a feature will trigger endless foot-gunning across the Python database ecosystem.
Deleted Comment
I had to look SEVERAL times at your comment before I noticed one is an F and the other is a T.
This won’t end well. Although I like it conceptually, this few pixel difference in a letter is going to cause major problems down the road.
I'd have to take a look at the order things happen in shell, but you might even be able to increase security/foot-gun-potential a little bit here by turning this into something like `stat "$( base64 -d [base64 encoded content of some_file] )"`.
https://github.com/google/zx
In order for a library to accept t-strings, they need to make a new function. Or else change the behavior and method signature of an old function, which I guess they could do but any sanely designed library doesn’t do.
Handling t-strings will require new functions to be added to libraries.
imagine writing a SqL where u put user input into query string directly.
now remember its 2025, lie down try not to cry.
FWIW, format_spec is available in the template structure, so the function writer could at least do a runtime check.
I completely disagree with this. Look what happened to Log4J when it was given similar freedoms.
As I understand it, log4j allowed malicious ${} expansion in any string passed to logging functions. So logging user generated code at all would be a security hole.
But Python's t-strings purposely _do not_ expand user code, they only expand the string literal.
https://xkcd.com/327/
So you would write db.execute(template) to turn template t"... where id = {id}" into a parameterized structure like ("... where id = ?", id).
Deleted Comment
This is such a strange take on t-strings. The only way for anything to infer that the template string is supposed to turn into valid HTML or SQL is to base it of the apparent syntax in the string, which can only be done in an ad-hoc fashion and has nothing to do with the template string feature.
The way the feature has been designed there is no indication in the string itself what type of content it is or what it will eventually be converted to. It’s all handled by the converting function.
As others have added, something like sql”select * from {table}” would have been able to do this, but there’s not even any guarantees that something that is in a template that will be converted into valid sql by a converting function should be any type of valid sql prior to that conversion. For all you know t“give me {table} but only {columns}” might be a converted into valid sql after the template is processed.
I understand why this seems strange at first!
As Paul mentioned, we spent quite a lot of time considering these issues as PEP 750 came together. In the end, we concluded (a) the PEP leaves open quite a few potential approaches for tools to adopt (not just the one you suggest, as others here have pointed out), and (b) it's ultimately something that the broader tooling community needs to rally around and should probably be out of scope for the PEP itself.
So, with that background in mind, I am indeed hopeful we'll see the ecosystem adapt! :-)
JavaScript's tagged template literals are actually about as flexible as this, since you can dynamically choose the tag function, it's just very rare to do so, so tools assume a lot based on the name of the function. Python tools can basically do the same thing, and just not support t-strings that aren't nested inside a well-named processing function.
Then the t is redundant and we don’t need t strings involved in any way. This would work just as well. In fact better by using html("<h1>Hello</h1>") and have it return a html object instead of returning a just a string which is what t-strings will do. So literally the only contribution from t-strings in that context is to make things worse.
If they had just standardized some way of putting semantics into the template everything would be better. Let us define a “sql = temlate(….)” and use sql”…”
But that's far from the only option, either! IntelliJ for Java also supports annotations on arguments [1] that then mean that you get syntax highlighting everywhere you use a string literal with the given function:
In Python, typing.Annotated appears to have been specifically designed for purposes like this [2]:> If a library or tool encounters an annotation Annotated[T, x] and has no special logic for the metadata, it should ignore the metadata and simply treat the annotation as T. As such, Annotated can be useful for code that wants to use annotations for purposes outside Python’s static typing system.
So something like this should be perfectly viable:
Now, where you're right is that we shouldn't actually require template strings to accomplish this! As noted, JetBrains has been doing this since forever. But maybe template strings will be useful enough for purposes like this that the tooling will actually evolve to support it in formatters and other editors (and maybe PyCharm can get some of the better support that Java has from JetBrains).[0] https://www.jetbrains.com/help/pycharm/using-language-inject...
[1] https://www.jetbrains.com/help/idea/using-language-injection...
[2] https://docs.python.org/3/library/typing.html#typing.Annotat...
We hope to get a community around all of this, stuff at PyCon US, EuroPython, etc. and work some of this out. The JSX/TSX world really has good tooling. We can provide that for those that want it, perhaps better on some aspects.
Not the only thing. You can also look at how it is used. Your editor could know of how some popular libraries use t-strings, track which t-strings get passed into functions from those libraries, and use that to assume what grammar the t-string should follow.
Is that cheating? In some sense, yes, but it also is useful and likely will be worth it for quite a few programmers.
Writing `query: SQL = t"SELECT ..."` is a small price to pay for such a DX boost.
You could already do everything you are proposing t-strings will make easier with just strings or f-strings.
‘query: SQL “Select …”’
“Wait” you’ll say “then the function taking the string has to parse the string and will have to add ugly logic grabbing variables from globals()!” Ah yes. That’s such an ugly solution… let’s instead codify just that as the official best practice and sell people on it by prefixing a t to the string so they don’t notice…
This would be the nicest way to use SQL I have seen yet.
Having more control over the interpolation of string values is a win IMO.
EF/EF Core has existed for years :)
https://learn.microsoft.com/en-us/ef/core/querying/sql-queri...
Generally annoying experience if you have to clock in and out every day to watch that UI break your database relations whenever you click save.
Why do you think changing a letter would cause a vulnerability? Which letter do you mean?
Deleted Comment
I feel like in this sense Go really is interesting by rejecting almost every single feature. Honestly not sure generics were worth it as they add a lot of complexity, and while they are nice, I don't need them very much. The general idea to keep the language at its original focus is the right idea IMO. C++ would be the most extreme case where the language itself barely resembles what it started out as.
It's far more essential than little utilities like textwrap or goliath packages like Python's bundled tkinter implementation.
I have a feeling that if metaclasses and descriptors were to be proposed today it would be laughed out of the PEP process completely.
This is especially noticeable with AWS Lambda where you can have a lot of useful stuff running for years without doing more than bumping the runtime version every year or two, but also that is one highly opinionated architecture so it’s not globally optimal for everyone.
;-)
In general, with user-defined operators and templates and macros, Nim has all kinds of Lisp-like facilities to extend the language, but with a more static focus which helps for both correctness and performance.
If you pass a "t-string" to a framework, it can force escaping.
What you suggest is to rely on escaping by the user (dev), who, if he was aware, would already escape.
Unless you'd suggest that it would still return a template, but tagged with a language.
I like F strings a lot, but for the most part I think all of the various X-strings should just be classes that take a string as an argument.
This is a very big deal! It's also about centralizing that work. Now that sanitization can occur in the consumer of the t-string (for example, the API to your HTML renderer), rather than in every f-string.
"Neither of these examples is possible with f-strings. By providing a mechanism to intercept and transform interpolated values, template strings enable a wide range of string processing use cases."
As far as I can see, anything you do with the template, you could do before building the f-string or inline as in my intial example.
I just want this so badly, it's the main reason I drift back to JS: