Skip to content

Commit 50dd4d1

Browse files
committed
py: Add PEP 750 template strings support.
Implements template strings (t-strings) as specified in PEP 750. Includes Template and Interpolation objects, expression parser, string.templatelib module, tests and documentation. Enabled for Unix, Windows, and WebAssembly ports. Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
1 parent e57aa7e commit 50dd4d1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3685
-30
lines changed

docs/library/btree.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,10 @@ Constants
151151

152152
.. data:: INCL
153153

154-
A flag for `keys()`, `values()`, `items()` methods to specify that
154+
A flag for :meth:`btree.keys`, :meth:`btree.values`, :meth:`btree.items` methods to specify that
155155
scanning should be inclusive of the end key.
156156

157157
.. data:: DESC
158158

159-
A flag for `keys()`, `values()`, `items()` methods to specify that
159+
A flag for :meth:`btree.keys`, :meth:`btree.values`, :meth:`btree.items` methods to specify that
160160
scanning should be in descending direction of keys.

docs/library/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ library.
7878
select.rst
7979
socket.rst
8080
ssl.rst
81+
string.templatelib.rst
8182
struct.rst
8283
sys.rst
8384
time.rst

docs/library/micropython.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ Functions
7979
Return an integer representing the current amount of stack that is being
8080
used. The absolute value of this is not particularly useful, rather it
8181
should be used to compute differences in stack usage at different points.
82+
83+
Note: When building with sanitizers (ASan/UBSan), an 8 KiB stack margin is
84+
automatically reserved unless the port overrides MICROPY_STACK_CHECK_MARGIN.
8285

8386
.. function:: heap_lock()
8487
.. function:: heap_unlock()

docs/library/string.templatelib.rst

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
:mod:`string.templatelib` -- Template String Support
2+
====================================================
3+
4+
.. module:: string.templatelib
5+
:synopsis: PEP 750 template string support
6+
7+
This module provides support for template strings (t-strings) as defined in
8+
`PEP 750 <https://peps.python.org/pep-0750/>`_. Template strings are created
9+
using the ``t`` prefix and provide access to both the literal string parts and
10+
interpolated values before they are combined.
11+
12+
Classes
13+
-------
14+
15+
.. class:: Template(*args)
16+
17+
Represents a template string. Template objects are created by t-string
18+
syntax (``t"..."``).
19+
20+
.. attribute:: strings
21+
22+
A tuple of string literals that appear between interpolations.
23+
24+
.. attribute:: interpolations
25+
26+
A tuple of :class:`Interpolation` objects representing the interpolated
27+
expressions.
28+
29+
.. attribute:: values
30+
31+
A read-only property that returns a tuple containing the ``value``
32+
attribute from each interpolation in the template.
33+
34+
.. method:: __iter__()
35+
36+
Iterate over the template contents, yielding string parts and
37+
:class:`Interpolation` objects in the order they appear. Empty strings
38+
are omitted.
39+
40+
.. class:: Interpolation(value, expression, conversion, format_spec)
41+
42+
Represents an interpolated expression within a template string.
43+
44+
.. attribute:: value
45+
46+
The evaluated value of the interpolated expression.
47+
48+
.. attribute:: expression
49+
50+
The string representation of the expression as it appeared in the
51+
template string.
52+
53+
.. attribute:: conversion
54+
55+
The conversion specifier (``'s'``, ``'r'``, or ``'a'``) if present,
56+
otherwise ``None``.
57+
58+
.. attribute:: format_spec
59+
60+
The format specification string if present, otherwise an empty string.
61+
62+
Template String Syntax
63+
----------------------
64+
65+
Template strings use the same syntax as f-strings but with a ``t`` prefix::
66+
67+
name = "World"
68+
template = t"Hello {name}!"
69+
70+
# Access template components
71+
print(template.strings) # ('Hello ', '!')
72+
print(template.values) # ('World',)
73+
print(template.interpolations[0].expression) # 'name'
74+
75+
Conversion Specifiers
76+
~~~~~~~~~~~~~~~~~~~~~
77+
78+
Template strings store conversion specifiers as metadata. Unlike f-strings,
79+
the conversion is not applied automatically::
80+
81+
value = "test"
82+
t = t"{value!r}"
83+
# t.interpolations[0].value == "test" (not repr(value))
84+
# t.interpolations[0].conversion == "r"
85+
86+
Processing code must explicitly apply conversions when needed.
87+
88+
Format Specifications
89+
~~~~~~~~~~~~~~~~~~~~~
90+
91+
Format specifications are stored as metadata in the ``Interpolation`` object.
92+
Unlike f-strings, formatting is not applied automatically::
93+
94+
pi = 3.14159
95+
t = t"{pi:.2f}"
96+
# t.interpolations[0].value == 3.14159 (not formatted)
97+
# t.interpolations[0].format_spec == ".2f"
98+
99+
Per PEP 750, processing code is not required to use format specifications, but
100+
when present they should be respected and match f-string behavior where possible.
101+
102+
Debug Format
103+
~~~~~~~~~~~~
104+
105+
The debug format ``{expr=}`` is supported::
106+
107+
x = 42
108+
t = t"{x=}"
109+
# t.strings == ("x=", "")
110+
# t.interpolations[0].expression == "x"
111+
# t.interpolations[0].conversion == "r"
112+
113+
.. admonition:: Difference to CPython
114+
:class: attention
115+
116+
Unlike f-strings, template strings do not automatically apply conversions
117+
or format specifications. Processing code must explicitly handle these
118+
attributes.
119+
120+
MicroPython does not provide the ``format()`` built-in function. Use
121+
string formatting methods like ``str.format()`` instead.
122+
123+
Differences from CPython
124+
------------------------
125+
126+
This implementation follows PEP 750 with these MicroPython-specific details:
127+
128+
**Memory Limits**: Template strings are subject to MicroPython's memory
129+
constraints. Very large templates may raise ``MemoryError``.
130+
131+
Availability
132+
------------
133+
134+
Template strings require ``MICROPY_PY_TSTRINGS`` to be enabled at compile time.
135+
They are enabled by default in WebAssembly/PyScript builds.
136+
137+
Example Usage
138+
-------------
139+
140+
Basic processing without format support::
141+
142+
def simple_process(template):
143+
"""Simple template processing"""
144+
parts = []
145+
for item in template:
146+
if isinstance(item, str):
147+
parts.append(item)
148+
else:
149+
parts.append(str(item.value))
150+
return "".join(parts)
151+
152+
Processing template with format support::
153+
154+
from string.templatelib import Template, Interpolation
155+
156+
def convert(value, conversion):
157+
"""Apply conversion specifier to value"""
158+
if conversion == "r":
159+
return repr(value)
160+
elif conversion == "s":
161+
return str(value)
162+
elif conversion == "a":
163+
return ascii(value)
164+
return value
165+
166+
def process_template(template):
167+
"""Process template with conversion and format support"""
168+
result = []
169+
for part in template:
170+
if isinstance(part, str):
171+
result.append(part)
172+
else: # Interpolation
173+
value = convert(part.value, part.conversion)
174+
if part.format_spec:
175+
# Apply format specification using str.format
176+
value = ("{:" + part.format_spec + "}").format(value)
177+
else:
178+
value = str(value)
179+
result.append(value)
180+
return "".join(result)
181+
182+
pi = 3.14159
183+
name = "Alice"
184+
t = t"{name!r}: {pi:.2f}"
185+
print(process_template(t))
186+
# Output: "'Alice': 3.14"
187+
188+
# Other format specifications work too
189+
value = 42
190+
print(process_template(t"{value:>10}")) # " 42"
191+
print(process_template(t"{value:04d}")) # "0042"
192+
193+
HTML escaping example::
194+
195+
def html_escape(value):
196+
"""Escape HTML special characters"""
197+
if not isinstance(value, str):
198+
value = str(value)
199+
return value.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
200+
201+
def safe_html(template):
202+
"""Convert template to HTML-safe string"""
203+
result = []
204+
for part in template:
205+
if isinstance(part, str):
206+
result.append(part)
207+
else:
208+
result.append(html_escape(part.value))
209+
return "".join(result)
210+
211+
user_input = "<script>alert('xss')</script>"
212+
t = t"User said: {user_input}"
213+
print(safe_html(t))
214+
# Output: "User said: &lt;script&gt;alert('xss')&lt;/script&gt;"
215+
216+
See Also
217+
--------
218+
219+
* `PEP 750 <https://peps.python.org/pep-0750/>`_ - Template Strings specification
220+
* :ref:`python:formatstrings` - Format string syntax
221+
* `Formatted string literals <https://docs.python.org/3/reference/lexical_analysis.html#f-strings>`_ - f-strings in Python

mpy-cross/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
#define MICROPY_USE_INTERNAL_PRINTF (0)
7878

7979
#define MICROPY_PY_FSTRINGS (1)
80+
#define MICROPY_PY_TSTRINGS (1)
8081
#define MICROPY_PY_BUILTINS_STR_UNICODE (1)
8182

8283
#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy