import unittest from test.test_string._support import TStringBaseCase, fstring class TestTString(unittest.TestCase, TStringBaseCase): def test_string_representation(self): # Test __repr__ t = t"Hello" self.assertEqual(repr(t), "Template(strings=('Hello',), interpolations=())") name = "Python" t = t"Hello, {name}" self.assertEqual(repr(t), "Template(strings=('Hello, ', ''), " "interpolations=(Interpolation('Python', 'name', None, ''),))" ) def test_interpolation_basics(self): # Test basic interpolation name = "Python" t = t"Hello, {name}" self.assertTStringEqual(t, ("Hello, ", ""), [(name, "name")]) self.assertEqual(fstring(t), "Hello, Python") # Multiple interpolations first = "Python" last = "Developer" t = t"{first} {last}" self.assertTStringEqual( t, ("", " ", ""), [(first, 'first'), (last, 'last')] ) self.assertEqual(fstring(t), "Python Developer") # Interpolation with expressions a = 10 b = 20 t = t"Sum: {a + b}" self.assertTStringEqual(t, ("Sum: ", ""), [(a + b, "a + b")]) self.assertEqual(fstring(t), "Sum: 30") # Interpolation with function def square(x): return x * x t = t"Square: {square(5)}" self.assertTStringEqual( t, ("Square: ", ""), [(square(5), "square(5)")] ) self.assertEqual(fstring(t), "Square: 25") # Test attribute access in expressions class Person: def __init__(self, name): self.name = name def upper(self): return self.name.upper() person = Person("Alice") t = t"Name: {person.name}" self.assertTStringEqual( t, ("Name: ", ""), [(person.name, "person.name")] ) self.assertEqual(fstring(t), "Name: Alice") # Test method calls t = t"Name: {person.upper()}" self.assertTStringEqual( t, ("Name: ", ""), [(person.upper(), "person.upper()")] ) self.assertEqual(fstring(t), "Name: ALICE") # Test dictionary access data = {"name": "Bob", "age": 30} t = t"Name: {data['name']}, Age: {data['age']}" self.assertTStringEqual( t, ("Name: ", ", Age: ", ""), [(data["name"], "data['name']"), (data["age"], "data['age']")], ) self.assertEqual(fstring(t), "Name: Bob, Age: 30") def test_format_specifiers(self): # Test basic format specifiers value = 3.14159 t = t"Pi: {value:.2f}" self.assertTStringEqual( t, ("Pi: ", ""), [(value, "value", None, ".2f")] ) self.assertEqual(fstring(t), "Pi: 3.14") def test_conversions(self): # Test !s conversion (str) obj = object() t = t"Object: {obj!s}" self.assertTStringEqual(t, ("Object: ", ""), [(obj, "obj", "s")]) self.assertEqual(fstring(t), f"Object: {str(obj)}") # Test !r conversion (repr) t = t"Data: {obj!r}" self.assertTStringEqual(t, ("Data: ", ""), [(obj, "obj", "r")]) self.assertEqual(fstring(t), f"Data: {repr(obj)}") # Test !a conversion (ascii) text = "Café" t = t"ASCII: {text!a}" self.assertTStringEqual(t, ("ASCII: ", ""), [(text, "text", "a")]) self.assertEqual(fstring(t), f"ASCII: {ascii(text)}") # Test !z conversion (error) num = 1 with self.assertRaises(SyntaxError): eval("t'{num!z}'") def test_debug_specifier(self): # Test debug specifier value = 42 t = t"Value: {value=}" self.assertTStringEqual( t, ("Value: value=", ""), [(value, "value", "r")] ) self.assertEqual(fstring(t), "Value: value=42") # Test debug specifier with format (conversion default to !r) t = t"Value: {value=:.2f}" self.assertTStringEqual( t, ("Value: value=", ""), [(value, "value", None, ".2f")] ) self.assertEqual(fstring(t), "Value: value=42.00") # Test debug specifier with conversion t = t"Value: {value=!s}" self.assertTStringEqual( t, ("Value: value=", ""), [(value, "value", "s")] ) # Test white space in debug specifier t = t"Value: {value = }" self.assertTStringEqual( t, ("Value: value = ", ""), [(value, "value", "r")] ) self.assertEqual(fstring(t), "Value: value = 42") def test_raw_tstrings(self): path = r"C:\Users" t = rt"{path}\Documents" self.assertTStringEqual(t, ("", r"\Documents"), [(path, "path")]) self.assertEqual(fstring(t), r"C:\Users\Documents") # Test alternative prefix t = tr"{path}\Documents" self.assertTStringEqual(t, ("", r"\Documents"), [(path, "path")]) def test_template_concatenation(self): # Test template + template t1 = t"Hello, " t2 = t"world" combined = t1 + t2 self.assertTStringEqual(combined, ("Hello, world",), ()) self.assertEqual(fstring(combined), "Hello, world") # Test template + string t1 = t"Hello" combined = t1 + ", world" self.assertTStringEqual(combined, ("Hello, world",), ()) self.assertEqual(fstring(combined), "Hello, world") # Test template + template with interpolation name = "Python" t1 = t"Hello, " t2 = t"{name}" combined = t1 + t2 self.assertTStringEqual(combined, ("Hello, ", ""), [(name, "name")]) self.assertEqual(fstring(combined), "Hello, Python") # Test string + template t = "Hello, " + t"{name}" self.assertTStringEqual(t, ("Hello, ", ""), [(name, "name")]) self.assertEqual(fstring(t), "Hello, Python") def test_nested_templates(self): # Test a template inside another template expression name = "Python" inner = t"{name}" t = t"Language: {inner}" t_interp = t.interpolations[0] self.assertEqual(t.strings, ("Language: ", "")) self.assertEqual(t_interp.value.strings, ("", "")) self.assertEqual(t_interp.value.interpolations[0].value, name) self.assertEqual(t_interp.value.interpolations[0].expression, "name") self.assertEqual(t_interp.value.interpolations[0].conversion, None) self.assertEqual(t_interp.value.interpolations[0].format_spec, "") self.assertEqual(t_interp.expression, "inner") self.assertEqual(t_interp.conversion, None) self.assertEqual(t_interp.format_spec, "") def test_syntax_errors(self): for case, err in ( ("t'", "unterminated t-string literal"), ("t'''", "unterminated triple-quoted t-string literal"), ("t''''", "unterminated triple-quoted t-string literal"), ("t'{", "'{' was never closed"), ("t'{'", "t-string: expecting '}'"), ("t'{a'", "t-string: expecting '}'"), ("t'}'", "t-string: single '}' is not allowed"), ("t'{}'", "t-string: valid expression required before '}'"), ("t'{=x}'", "t-string: valid expression required before '='"), ("t'{!x}'", "t-string: valid expression required before '!'"), ("t'{:x}'", "t-string: valid expression required before ':'"), ("t'{x;y}'", "t-string: expecting '=', or '!', or ':', or '}'"), ("t'{x=y}'", "t-string: expecting '!', or ':', or '}'"), ("t'{x!s!}'", "t-string: expecting ':' or '}'"), ("t'{x!s:'", "t-string: expecting '}', or format specs"), ("t'{x!}'", "t-string: missing conversion character"), ("t'{x=!}'", "t-string: missing conversion character"), ("t'{x!z}'", "t-string: invalid conversion character 'z': " "expected 's', 'r', or 'a'"), ("t'{lambda:1}'", "t-string: lambda expressions are not allowed " "without parentheses"), ("t'{x:{;}}'", "t-string: expecting a valid expression after '{'"), ): with self.subTest(case), self.assertRaisesRegex(SyntaxError, err): eval(case) def test_runtime_errors(self): # Test missing variables with self.assertRaises(NameError): eval("t'Hello, {name}'") def test_literal_concatenation(self): # Test concatenation of t-string literals t = t"Hello, " t"world" self.assertTStringEqual(t, ("Hello, world",), ()) self.assertEqual(fstring(t), "Hello, world") # Test concatenation with interpolation name = "Python" t = t"Hello, " t"{name}" self.assertTStringEqual(t, ("Hello, ", ""), [(name, "name")]) self.assertEqual(fstring(t), "Hello, Python") # Test concatenation with string literal name = "Python" t = t"Hello, {name}" "and welcome!" self.assertTStringEqual( t, ("Hello, ", "and welcome!"), [(name, "name")] ) self.assertEqual(fstring(t), "Hello, Pythonand welcome!") # Test concatenation with Unicode literal name = "Python" t = t"Hello, {name}" u"and welcome!" self.assertTStringEqual( t, ("Hello, ", "and welcome!"), [(name, "name")] ) self.assertEqual(fstring(t), "Hello, Pythonand welcome!") # Test concatenation with f-string literal tab = '\t' t = t"Tab: {tab}. " f"f-tab: {tab}." self.assertTStringEqual(t, ("Tab: ", ". f-tab: \t."), [(tab, "tab")]) self.assertEqual(fstring(t), "Tab: \t. f-tab: \t.") # Test concatenation with raw string literal tab = '\t' t = t"Tab: {tab}. " r"Raw tab: \t." self.assertTStringEqual( t, ("Tab: ", r". Raw tab: \t."), [(tab, "tab")] ) self.assertEqual(fstring(t), "Tab: \t. Raw tab: \\t.") # Test concatenation with raw f-string literal tab = '\t' t = t"Tab: {tab}. " rf"f-tab: {tab}. Raw tab: \t." self.assertTStringEqual( t, ("Tab: ", ". f-tab: \t. Raw tab: \\t."), [(tab, "tab")] ) self.assertEqual(fstring(t), "Tab: \t. f-tab: \t. Raw tab: \\t.") what = 't' expected_msg = 'cannot mix bytes and nonbytes literals' for case in ( "t'{what}-string literal' b'bytes literal'", "t'{what}-string literal' br'raw bytes literal'", ): with self.assertRaisesRegex(SyntaxError, expected_msg): eval(case) def test_triple_quoted(self): # Test triple-quoted t-strings t = t""" Hello, world """ self.assertTStringEqual( t, ("\n Hello,\n world\n ",), () ) self.assertEqual(fstring(t), "\n Hello,\n world\n ") # Test triple-quoted with interpolation name = "Python" t = t""" Hello, {name} """ self.assertTStringEqual( t, ("\n Hello,\n ", "\n "), [(name, "name")] ) self.assertEqual(fstring(t), "\n Hello,\n Python\n ") if __name__ == '__main__': unittest.main()