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.
> or the code expects a Template, in which case passing it a string would blow it up.
That's where the problem is though -- in most cases it probably won't blow up.
Plenty of SQL queries don't have any parameters at all. You're just getting the number of rows in a table or something. A raw string is perfectly fine.
Will sqlite3 really disallow strings? Will it force you to use templates, even when the template doesn't contain any parameters?
You can argue it should, but that's not being very friendly with inputs, and will break backwards compatibility. Maybe if there's a flag you can set in the module to enable that strict behavior though, with the idea that in a decade it will become the default?
It's one extra letter to "force" for an unparameterized query over a "raw string". The t-string itself works just fine without parameters.
There's definitely a backwards compatibility hurdle of switching to a template-only API, but a template-only API doesn't look that much "less friendly" with inputs, when the only difference is a `t` before every string, regardless of number of parameters.
Sure, but it's just I don't have to do that anywhere else.
I never put an f in front of a string if I'm not putting variables within it.
And I'm generally used to Python inputs being liberal. I can usually pass a list if it expects a tuple; I can pass an int if it expects a float; often I can pass an item directly instead of a tuple with a single item. Regex functions take regular strings or regex strings, they don't force regex strings.
Being forced to use a single specific type of string in all cases is just very different from how Python has traditionally operated.
It's safer, I get that. But it's definitely less friendly, so I'll be curious to see how module maintainers decide to handle this.
> Being forced to use a single specific type of string in all cases is just very different from how Python has traditionally operated.
Maybe that's partly the disconnect here? "t-string" is probably a confusing colloquial name because they aren't strings, they are Templates. The runtime type is a Template. It is a very different duck-type from a string. As a duck-typable object it doesn't even implicitly or explicitly act like a string, there's intentionally no `__str__()` method and `str(someTemplate)` doesn't work like you'd expect. It shouldn't be a surprise that there is also no implicit conversion from a string and you have to use its own literal syntax: it isn't a string type, it's a Template type.
Python here is still liberal with respect to Templates (it is still a duck type). If a function expects a Template and you don't want to use the t"" shorthand syntax nor use the Template constructor in string.templatelib, you just need a simple class of object that has an `__iter__()` of the correct shape and/or has `strings` and `values` tuples.
Sure, it may make sense for some types of APIs to support a Union of str and Template as "liberal" options, but it's a different class of liberal support from Union of list and tuple or Union of int and float which are closer "domains" of types. A Template isn't a string and at runtime looks nothing like one (despite how syntactically it looks like one at "compile time"). Given `__iter__()` in Template, it may make more sense/would be more "natural" to Union Template with List or Tuple more than with a single string.
Er… that’s just not correct? Python can be more liberal but it’s not always. It depends entirely on the tooling. Libraries will take time to catch up but I can definitely see people creating libraries that enforce t-strings, even if they’re deconstructing them under the hood for legacy libraries.
For the reasons discussed above, I'm not sure that it will be the case for t-strings. I think it'll take a little while for frameworks/libraries to adapt (while still maintaining backward compatibility) and a while for best practices to find their way into our linting and other tools.
I think it's way more likely that existing libraries will introduce new methods that use t-strings and are type safe, rather than entirely defeat the purpose of having a t-string API.
I'm guessing no existing functions will be extended to allow t-strings for this very reason. Instead, new functions that only accept t-strings will be created.
There's an obvious risk here, same as with strcpy (no, strncpy.. no, strlcpy... no, strcpy_s) that documentation tends to outlive code, and people keep pasting from tutorails and older code so much that the newer alternatives have a hard time cutting through the noise.
I would argue that as bad as some w3schools tutorials were, and copying from bad Stackoverflow answers, going back to MSA and the free cgi archives of the 90s, the tendency of code snippets to live on forever will only be excarbated by AI-style coding agents.
On the other hand, deprecating existing methods is what languages do to die. And for good reason. I don't think there's an easy answer here. But language is also culture, and shared beliefs about code quality can be a middle route between respecting legacy and building new. If static checking is as easy as a directive such as "use strict" and the idea that checking is good spreads, then consesus can slowly evolve while working code keeps working.
Do the python type checkers / linters / whatever have the ability to warn or error on calling certain functions? That would be nice to eventually enforce migration over to the newer functions that only take a t-string template
Yeah. A while back I was poking through some unfamiliar code and noticed that my editor was rendering a use of `datetime.utcnow()` as struck through. When I hovered it with my mouse, I got a message that that function had been deprecated.
Turns out my editor (vscode) and typechecker (pyright) saw that `datetime.utcnow()` was marked as deprecated (I know one can use the `@deprecated` decorator from Python 3.13 or `__future__` to do this; I think it was done another way in this particular case) and therefore rendered it as struck through.
And it taught me A) that `utcnow()` is deprecated and B) how to mark bits of our internal codebase as deprecated and nudge our developers to use the new, better versions if possible.
Can you do it for functions defined by other people, or only for functions that you defined?
I'm thinking in the general case, but motivated by this example of a 3rd party function that accepts a SQL query as a string, and we'd like everywhere in our codebase to stop using that and instead use the 3rd party function that accepts the query as a t-string
If it's not a completely new library written exclusively around templates, such code currently accepts strings and will most likely continue to accept strings for backwards compatibility.
In that case I don’t understand the security regression that t-strings cause (see GP). Before it was all just strings, but you had to make sure to use them in the correct place. Now you can still just use strings for backwards compat. but you can also move on to a distinctly-typed solution for SQL and the like.
That would be a great argument if Python wasn't a language that let you reach into the internals and define __str__() for things you shouldn't be defining it for. And that is something people will definitely do because, you know, they just need something to friggin work so they can get whatever ticket closed and keep some metric happy tracking time-to-close
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?
No sane library is going to do that. If they do let you pass a raw string it should be a different function with the risks clearly documented.
The thing this replaces is every library having their own bespoke API to create a prepared statement on their default/safe path. Now they can just take a template.
How about every library that wants to preserve backwards compatibility?
Or are you suggesting that e.g. every database module needs to implement a new set of query functions with new names that supports templates? Which is probably the correct thing to do, but boy is it going to be ugly...
So now you'll have to remember never to use 'execute()' but always 'execute_t()' or something.
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.
That’s a library author problem, so it’s less likely since library authors tend to be fewer in number and, for popular libraries, get a reasonable number of eyes on this type of change.