We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Pattern Matching, f-Strings and Newer Syntax step 19 of 23
Escape HTML and Build SQL Parameters with t-Strings
This exercise needs Python 3.14, which added template strings.
When you pass an f-string to a function, the function gets one finished
string. It cannot tell your markup apart from what a user typed. A template
string, written t"...", keeps the two apart. It gives you a Template
object instead of a string:
from string.templatelib import Template
name = "<b>ada</b>"
template = t"<li>{name}</li>"
assert isinstance(template, Template)
assert template.strings == ("<li>", "</li>")
assert template.interpolations[0].value == name
The text outside the braces becomes the template's static strings, which
template.strings holds. Each {...} becomes an interpolation, an
object with four fields:
value, the result of the expression. Python evaluates it when it creates the template, as it does for an f-string.expression, the source text, such as"name".conversion, which is"r","s","a"orNone.format_spec, which is""when there is none.
Looping over a template gives its static strings and interpolations in order, and skips empty static strings. A template escapes nothing by itself. The function that receives it decides what to do with each value, and you write two such functions.
What to write
render_html(template) -> str builds HTML:
- Copy each static string unchanged.
- For each interpolation, apply
repr,strorasciiif its conversion asks for one. With no conversion, keep the value as it is. - Call
format(converted, interpolation.format_spec). - Escape the result with
html.escape(text, quote=True), and add it. - Join the pieces with nothing between them.
sql(template) -> tuple[str, list[object]] builds a query for a driver that
uses ? placeholders:
- Copy each static string unchanged.
- Replace each interpolation with
"?", and append its value to the parameter list, in order. - Ignore conversions and format specs. The database gets the original values.
"When Not to Use an f-String", earlier in this track, explains why the driver must get the values separately from the SQL.
solve(name, comment, min_age, score) writes three template literals and
returns a dictionary with four keys:
| Key | Built with |
|---|---|
"html" |
render_html(t"<li><b>{name}</b> said: {comment}</li>") |
"detail" |
render_html(t"{name!r} scored {score:.2f}") |
"sql", "params" |
The query and the list from sql(t"SELECT * FROM users WHERE name = {name} AND age > {min_age}") |
With name "ada", comment "hello", min_age 18 and score 9.5, solve
returns:
{
"html": "<li><b>ada</b> said: hello</li>",
"detail": "'ada' scored 9.50",
"sql": "SELECT * FROM users WHERE name = ? AND age > ?",
"params": ["ada", 18],
}
Edge cases
- An empty string is still a value. It adds nothing to the HTML, and it still adds a parameter.
render_htmlescapes a name that contains<b>, andsqlputs the same name in the parameters unchanged.- In
"detail",reprruns before escaping, so its quote characters are escaped too.
Going further
Template is not a subclass of str, so mypy rejects an f-string passed to
a function that expects a Template. That catches a slip, not an attack, because
code can also build a Template by hand from any strings.
This renderer is safe for text between tags. A value inside a <script>
block, a URL or an attribute name needs different handling, so use an HTML
template library for those.
An older Python rejects the whole file if it contains a t"..." literal,
even inside an if branch that never runs.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.