Skip to content

Commit 654ff70

Browse files
committed
gh-151099: Better repr output for template strings.
1 parent 11a8bdf commit 654ff70

3 files changed

Lines changed: 85 additions & 4 deletions

File tree

Lib/test/test_string/test_templatelib.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,25 @@ def test_template_values(self):
101101
t = t'Hello, {name}, {age} from {country}'
102102
self.assertEqual(t.values, ("Lys", 0, "GR"))
103103

104+
def test_repr(self):
105+
self.assertEqual(repr(t''), 'Template()')
106+
self.assertEqual(repr(t'foo'), "Template('foo')")
107+
x = 42
108+
self.assertEqual(
109+
repr(t'{x}'),
110+
"Template(Interpolation(42, 'x', None, ''))")
111+
self.assertEqual(
112+
repr(t'a{x!r:02}b'),
113+
"Template('a', Interpolation(42, 'x', 'r', '02'), 'b')")
114+
115+
# Test a "recursive" template
116+
x = []
117+
t = t'a{x}b'
118+
x.append(t)
119+
self.assertEqual(
120+
repr(t),
121+
"Template('a', Interpolation([Template(...)], 'x', None, ''), 'b')")
122+
104123
def test_pickle_template(self):
105124
user = 'test'
106125
for template in (
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Change :func:`repr` output of :class:`string.templatelib.Template` objects
2+
to list the parts of the template in their original source order.

Objects/templateobject.c

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,70 @@ static PyObject *
212212
template_repr(PyObject *op)
213213
{
214214
templateobject *self = templateobject_CAST(op);
215-
return PyUnicode_FromFormat("%s(strings=%R, interpolations=%R)",
216-
_PyType_Name(Py_TYPE(self)),
217-
self->strings,
218-
self->interpolations);
215+
216+
int res = Py_ReprEnter(self);
217+
if (res != 0) {
218+
return (res > 0 ? PyUnicode_FromString("Template(...)") : NULL);
219+
}
220+
221+
Py_ssize_t stringslen = PyTuple_GET_SIZE(self->strings);
222+
Py_ssize_t interpolationslen = PyTuple_GET_SIZE(self->interpolations);
223+
224+
PyUnicodeWriter *writer = PyUnicodeWriter_Create(10);
225+
if (writer == NULL) {
226+
return NULL;
227+
}
228+
229+
if (PyUnicodeWriter_WriteUTF8(writer, _PyType_Name(Py_TYPE(self)), -1) < 0) {
230+
goto error;
231+
}
232+
if (PyUnicodeWriter_WriteChar(writer, '(') < 0) {
233+
goto error;
234+
}
235+
236+
/* Render the strings and interpolations in the order they appeared in the
237+
constructor call, i.e. interleaved and skipping empty strings. This
238+
matches the order produced by templateiter_next. */
239+
int first = 1;
240+
for (Py_ssize_t i = 0; i < stringslen; i++) {
241+
PyObject *string = PyTuple_GET_ITEM(self->strings, i);
242+
if (PyUnicode_GET_LENGTH(string) > 0) {
243+
if (!first) {
244+
if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) {
245+
goto error;
246+
}
247+
}
248+
if (PyUnicodeWriter_WriteRepr(writer, string) < 0) {
249+
goto error;
250+
}
251+
first = 0;
252+
}
253+
if (i < interpolationslen) {
254+
PyObject *interpolation = PyTuple_GET_ITEM(self->interpolations, i);
255+
if (!first) {
256+
if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) {
257+
goto error;
258+
}
259+
}
260+
if (PyUnicodeWriter_WriteRepr(writer, interpolation) < 0) {
261+
goto error;
262+
}
263+
first = 0;
264+
}
265+
}
266+
267+
if (PyUnicodeWriter_WriteChar(writer, ')') < 0) {
268+
goto error;
269+
}
270+
271+
Py_ReprLeave(self);
272+
273+
return PyUnicodeWriter_Finish(writer);
274+
275+
error:
276+
Py_ReprLeave(self);
277+
PyUnicodeWriter_Discard(writer);
278+
return NULL;
219279
}
220280

221281
static PyObject *

0 commit comments

Comments
 (0)