From d91d6fc5d67e7101a9025b4069993d9400109f9e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 6 Jun 2025 15:27:44 +0100 Subject: [PATCH 1/4] Add ``string.templatelib.convert()`` --- Doc/library/string.rst | 2 ++ Doc/library/string.templatelib.rst | 46 ++++++++++++++++++++++++ Doc/library/text.rst | 1 + Lib/string/templatelib.py | 17 ++++++--- Lib/test/test_string/test_templatelib.py | 20 ++++++++++- 5 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 Doc/library/string.templatelib.rst diff --git a/Doc/library/string.rst b/Doc/library/string.rst index 23e15780075435..6b4d7571e5fea6 100644 --- a/Doc/library/string.rst +++ b/Doc/library/string.rst @@ -264,6 +264,8 @@ Some simple format string examples:: "Weight in tons {0.weight}" # 'weight' attribute of first positional arg "Units destroyed: {players[0]}" # First element of keyword argument 'players'. +.. _formatstrings-conversion: + The *conversion* field causes a type coercion before formatting. Normally, the job of formatting a value is done by the :meth:`~object.__format__` method of the value itself. However, in some cases it is desirable to force a type to be formatted diff --git a/Doc/library/string.templatelib.rst b/Doc/library/string.templatelib.rst new file mode 100644 index 00000000000000..1e936bc8caa175 --- /dev/null +++ b/Doc/library/string.templatelib.rst @@ -0,0 +1,46 @@ +:mod:`!string.templatelib` --- Support for template string literals +=================================================================== + +.. module:: string.templatelib + :synopsis: Support for template string literals. + +**Source code:** :source:`Lib/string/templatelib.py` + +-------------- + +.. versionadded:: 3.14 + +.. seealso:: + + :pep:`750` -- Template Strings + +Types +----- + +.. class:: Template + +.. class:: Interpolation + +Helper functions +---------------- + +.. function:: convert(obj, /, conversion) + + Applies formatted string literal :ref:`conversion ` + semantics to the given object *obj*. + This is frequently useful for custom template string processing logic. + + Three conversion flags are currently supported: + + * ``'!s'`` which calls :func:`str` on the value, + * ``'!r'`` which calls :func:`repr`, and + * ``'!a'`` which calls :func:`ascii`. + + If the conversion flag is ``None``, *obj* is returned unchanged. + +.. _template-strings: + +Template String Syntax +---------------------- + +.. TODO: Add section similar to :ref:`formatstrings`. diff --git a/Doc/library/text.rst b/Doc/library/text.rst index 47b678434fc899..92e7dd9a53b80d 100644 --- a/Doc/library/text.rst +++ b/Doc/library/text.rst @@ -16,6 +16,7 @@ Python's built-in string type in :ref:`textseq`. .. toctree:: string.rst + string.templatelib.rst re.rst difflib.rst textwrap.rst diff --git a/Lib/string/templatelib.py b/Lib/string/templatelib.py index 14b40e1e36e30b..c77291afff27ca 100644 --- a/Lib/string/templatelib.py +++ b/Lib/string/templatelib.py @@ -1,15 +1,22 @@ """Support for template string literals (t-strings).""" -__all__ = [ - "Interpolation", - "Template", -] - t = t"{0}" Template = type(t) Interpolation = type(t.interpolations[0]) del t +def convert(obj, /, conversion): + """Implements formatted string literals conversion semantics.""" + if conversion is None: + return obj + if conversion == 'r': + return repr(obj) + if conversion == 's': + return str(obj) + if conversion == 'a': + return ascii(obj) + raise ValueError(f'invalid conversion specifier: {conversion!r}') + def _template_unpickle(*args): import itertools diff --git a/Lib/test/test_string/test_templatelib.py b/Lib/test/test_string/test_templatelib.py index 85fcff486d6616..5270e2ce0251a8 100644 --- a/Lib/test/test_string/test_templatelib.py +++ b/Lib/test/test_string/test_templatelib.py @@ -1,7 +1,7 @@ import pickle import unittest from collections.abc import Iterator, Iterable -from string.templatelib import Template, Interpolation +from string.templatelib import Template, Interpolation, convert from test.test_string._support import TStringBaseCase, fstring @@ -156,5 +156,23 @@ def test_exhausted(self): self.assertRaises(StopIteration, next, template_iter) +class TestFunctions(unittest.TestCase): + def test_convert(self): + for obj in ('Café', None, 3.14, b'bytes'): + with self.subTest(f'{obj=}'): + self.assertEqual(convert(obj, None), obj) + self.assertEqual(convert(obj, 's'), str(obj)) + self.assertEqual(convert(obj, 'r'), repr(obj)) + self.assertEqual(convert(obj, 'a'), ascii(obj)) + + # Invalid conversion specifier + with self.assertRaises(ValueError): + convert(obj, 'z') + with self.assertRaises(ValueError): + convert(obj, 1) + with self.assertRaises(ValueError): + convert(obj, object()) + + if __name__ == '__main__': unittest.main() From 321164a01ad9eb563ed789ac9530f80426f8bfbf Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:43:11 +0200 Subject: [PATCH 2/4] Remove documentation --- Doc/library/string.rst | 2 -- Doc/library/string.templatelib.rst | 46 ------------------------------ Doc/library/text.rst | 1 - 3 files changed, 49 deletions(-) delete mode 100644 Doc/library/string.templatelib.rst diff --git a/Doc/library/string.rst b/Doc/library/string.rst index 6b4d7571e5fea6..23e15780075435 100644 --- a/Doc/library/string.rst +++ b/Doc/library/string.rst @@ -264,8 +264,6 @@ Some simple format string examples:: "Weight in tons {0.weight}" # 'weight' attribute of first positional arg "Units destroyed: {players[0]}" # First element of keyword argument 'players'. -.. _formatstrings-conversion: - The *conversion* field causes a type coercion before formatting. Normally, the job of formatting a value is done by the :meth:`~object.__format__` method of the value itself. However, in some cases it is desirable to force a type to be formatted diff --git a/Doc/library/string.templatelib.rst b/Doc/library/string.templatelib.rst deleted file mode 100644 index 1e936bc8caa175..00000000000000 --- a/Doc/library/string.templatelib.rst +++ /dev/null @@ -1,46 +0,0 @@ -:mod:`!string.templatelib` --- Support for template string literals -=================================================================== - -.. module:: string.templatelib - :synopsis: Support for template string literals. - -**Source code:** :source:`Lib/string/templatelib.py` - --------------- - -.. versionadded:: 3.14 - -.. seealso:: - - :pep:`750` -- Template Strings - -Types ------ - -.. class:: Template - -.. class:: Interpolation - -Helper functions ----------------- - -.. function:: convert(obj, /, conversion) - - Applies formatted string literal :ref:`conversion ` - semantics to the given object *obj*. - This is frequently useful for custom template string processing logic. - - Three conversion flags are currently supported: - - * ``'!s'`` which calls :func:`str` on the value, - * ``'!r'`` which calls :func:`repr`, and - * ``'!a'`` which calls :func:`ascii`. - - If the conversion flag is ``None``, *obj* is returned unchanged. - -.. _template-strings: - -Template String Syntax ----------------------- - -.. TODO: Add section similar to :ref:`formatstrings`. diff --git a/Doc/library/text.rst b/Doc/library/text.rst index 92e7dd9a53b80d..47b678434fc899 100644 --- a/Doc/library/text.rst +++ b/Doc/library/text.rst @@ -16,7 +16,6 @@ Python's built-in string type in :ref:`textseq`. .. toctree:: string.rst - string.templatelib.rst re.rst difflib.rst textwrap.rst From 9d59705b73572744090a813a8950f5036bf7dbe4 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:50:08 +0200 Subject: [PATCH 3/4] Avoid BytesWarning --- Lib/test/test_string/test_templatelib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_string/test_templatelib.py b/Lib/test/test_string/test_templatelib.py index 5270e2ce0251a8..81222da8e886d8 100644 --- a/Lib/test/test_string/test_templatelib.py +++ b/Lib/test/test_string/test_templatelib.py @@ -158,7 +158,9 @@ def test_exhausted(self): class TestFunctions(unittest.TestCase): def test_convert(self): - for obj in ('Café', None, 3.14, b'bytes'): + from fractions import Fraction + + for obj in ('Café', None, 3.14, Fraction(1, 2)): with self.subTest(f'{obj=}'): self.assertEqual(convert(obj, None), obj) self.assertEqual(convert(obj, 's'), str(obj)) From 125ce4e885fcef00dc848bff712af8d28ac312fe Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:51:11 +0200 Subject: [PATCH 4/4] Imperative mood --- Lib/string/templatelib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/string/templatelib.py b/Lib/string/templatelib.py index c77291afff27ca..8164872432ad09 100644 --- a/Lib/string/templatelib.py +++ b/Lib/string/templatelib.py @@ -6,7 +6,7 @@ del t def convert(obj, /, conversion): - """Implements formatted string literals conversion semantics.""" + """Convert *obj* using formatted string literal semantics.""" if conversion is None: return obj if conversion == 'r': @@ -15,7 +15,7 @@ def convert(obj, /, conversion): return str(obj) if conversion == 'a': return ascii(obj) - raise ValueError(f'invalid conversion specifier: {conversion!r}') + raise ValueError(f'invalid conversion specifier: {conversion}') def _template_unpickle(*args): import itertools