1import unittest 2import string 3from string import Template 4 5 6class ModuleTest(unittest.TestCase): 7 8 def test_attrs(self): 9 # While the exact order of the items in these attributes is not 10 # technically part of the "language spec", in practice there is almost 11 # certainly user code that depends on the order, so de-facto it *is* 12 # part of the spec. 13 self.assertEqual(string.whitespace, ' \t\n\r\x0b\x0c') 14 self.assertEqual(string.ascii_lowercase, 'abcdefghijklmnopqrstuvwxyz') 15 self.assertEqual(string.ascii_uppercase, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') 16 self.assertEqual(string.ascii_letters, string.ascii_lowercase + string.ascii_uppercase) 17 self.assertEqual(string.digits, '0123456789') 18 self.assertEqual(string.hexdigits, string.digits + 'abcdefABCDEF') 19 self.assertEqual(string.octdigits, '01234567') 20 self.assertEqual(string.punctuation, '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~') 21 self.assertEqual(string.printable, string.digits + string.ascii_lowercase + string.ascii_uppercase + string.punctuation + string.whitespace) 22 23 def test_capwords(self): 24 self.assertEqual(string.capwords('abc def ghi'), 'Abc Def Ghi') 25 self.assertEqual(string.capwords('abc\tdef\nghi'), 'Abc Def Ghi') 26 self.assertEqual(string.capwords('abc\t def \nghi'), 'Abc Def Ghi') 27 self.assertEqual(string.capwords('ABC DEF GHI'), 'Abc Def Ghi') 28 self.assertEqual(string.capwords('ABC-DEF-GHI', '-'), 'Abc-Def-Ghi') 29 self.assertEqual(string.capwords('ABC-def DEF-ghi GHI'), 'Abc-def Def-ghi Ghi') 30 self.assertEqual(string.capwords(' aBc DeF '), 'Abc Def') 31 self.assertEqual(string.capwords('\taBc\tDeF\t'), 'Abc Def') 32 self.assertEqual(string.capwords('\taBc\tDeF\t', '\t'), '\tAbc\tDef\t') 33 34 def test_basic_formatter(self): 35 fmt = string.Formatter() 36 self.assertEqual(fmt.format("foo"), "foo") 37 self.assertEqual(fmt.format("foo{0}", "bar"), "foobar") 38 self.assertEqual(fmt.format("foo{1}{0}-{1}", "bar", 6), "foo6bar-6") 39 self.assertRaises(TypeError, fmt.format) 40 self.assertRaises(TypeError, string.Formatter.format) 41 42 def test_format_keyword_arguments(self): 43 fmt = string.Formatter() 44 self.assertEqual(fmt.format("-{arg}-", arg='test'), '-test-') 45 self.assertRaises(KeyError, fmt.format, "-{arg}-") 46 self.assertEqual(fmt.format("-{self}-", self='test'), '-test-') 47 self.assertRaises(KeyError, fmt.format, "-{self}-") 48 self.assertEqual(fmt.format("-{format_string}-", format_string='test'), 49 '-test-') 50 self.assertRaises(KeyError, fmt.format, "-{format_string}-") 51 with self.assertWarnsRegex(DeprecationWarning, "format_string"): 52 self.assertEqual(fmt.format(arg='test', format_string="-{arg}-"), 53 '-test-') 54 55 def test_auto_numbering(self): 56 fmt = string.Formatter() 57 self.assertEqual(fmt.format('foo{}{}', 'bar', 6), 58 'foo{}{}'.format('bar', 6)) 59 self.assertEqual(fmt.format('foo{1}{num}{1}', None, 'bar', num=6), 60 'foo{1}{num}{1}'.format(None, 'bar', num=6)) 61 self.assertEqual(fmt.format('{:^{}}', 'bar', 6), 62 '{:^{}}'.format('bar', 6)) 63 self.assertEqual(fmt.format('{:^{}} {}', 'bar', 6, 'X'), 64 '{:^{}} {}'.format('bar', 6, 'X')) 65 self.assertEqual(fmt.format('{:^{pad}}{}', 'foo', 'bar', pad=6), 66 '{:^{pad}}{}'.format('foo', 'bar', pad=6)) 67 68 with self.assertRaises(ValueError): 69 fmt.format('foo{1}{}', 'bar', 6) 70 71 with self.assertRaises(ValueError): 72 fmt.format('foo{}{1}', 'bar', 6) 73 74 def test_conversion_specifiers(self): 75 fmt = string.Formatter() 76 self.assertEqual(fmt.format("-{arg!r}-", arg='test'), "-'test'-") 77 self.assertEqual(fmt.format("{0!s}", 'test'), 'test') 78 self.assertRaises(ValueError, fmt.format, "{0!h}", 'test') 79 # issue13579 80 self.assertEqual(fmt.format("{0!a}", 42), '42') 81 self.assertEqual(fmt.format("{0!a}", string.ascii_letters), 82 "'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'") 83 self.assertEqual(fmt.format("{0!a}", chr(255)), "'\\xff'") 84 self.assertEqual(fmt.format("{0!a}", chr(256)), "'\\u0100'") 85 86 def test_name_lookup(self): 87 fmt = string.Formatter() 88 class AnyAttr: 89 def __getattr__(self, attr): 90 return attr 91 x = AnyAttr() 92 self.assertEqual(fmt.format("{0.lumber}{0.jack}", x), 'lumberjack') 93 with self.assertRaises(AttributeError): 94 fmt.format("{0.lumber}{0.jack}", '') 95 96 def test_index_lookup(self): 97 fmt = string.Formatter() 98 lookup = ["eggs", "and", "spam"] 99 self.assertEqual(fmt.format("{0[2]}{0[0]}", lookup), 'spameggs') 100 with self.assertRaises(IndexError): 101 fmt.format("{0[2]}{0[0]}", []) 102 with self.assertRaises(KeyError): 103 fmt.format("{0[2]}{0[0]}", {}) 104 105 def test_override_get_value(self): 106 class NamespaceFormatter(string.Formatter): 107 def __init__(self, namespace={}): 108 string.Formatter.__init__(self) 109 self.namespace = namespace 110 111 def get_value(self, key, args, kwds): 112 if isinstance(key, str): 113 try: 114 # Check explicitly passed arguments first 115 return kwds[key] 116 except KeyError: 117 return self.namespace[key] 118 else: 119 string.Formatter.get_value(key, args, kwds) 120 121 fmt = NamespaceFormatter({'greeting':'hello'}) 122 self.assertEqual(fmt.format("{greeting}, world!"), 'hello, world!') 123 124 125 def test_override_format_field(self): 126 class CallFormatter(string.Formatter): 127 def format_field(self, value, format_spec): 128 return format(value(), format_spec) 129 130 fmt = CallFormatter() 131 self.assertEqual(fmt.format('*{0}*', lambda : 'result'), '*result*') 132 133 134 def test_override_convert_field(self): 135 class XFormatter(string.Formatter): 136 def convert_field(self, value, conversion): 137 if conversion == 'x': 138 return None 139 return super().convert_field(value, conversion) 140 141 fmt = XFormatter() 142 self.assertEqual(fmt.format("{0!r}:{0!x}", 'foo', 'foo'), "'foo':None") 143 144 145 def test_override_parse(self): 146 class BarFormatter(string.Formatter): 147 # returns an iterable that contains tuples of the form: 148 # (literal_text, field_name, format_spec, conversion) 149 def parse(self, format_string): 150 for field in format_string.split('|'): 151 if field[0] == '+': 152 # it's markup 153 field_name, _, format_spec = field[1:].partition(':') 154 yield '', field_name, format_spec, None 155 else: 156 yield field, None, None, None 157 158 fmt = BarFormatter() 159 self.assertEqual(fmt.format('*|+0:^10s|*', 'foo'), '* foo *') 160 161 def test_check_unused_args(self): 162 class CheckAllUsedFormatter(string.Formatter): 163 def check_unused_args(self, used_args, args, kwargs): 164 # Track which arguments actually got used 165 unused_args = set(kwargs.keys()) 166 unused_args.update(range(0, len(args))) 167 168 for arg in used_args: 169 unused_args.remove(arg) 170 171 if unused_args: 172 raise ValueError("unused arguments") 173 174 fmt = CheckAllUsedFormatter() 175 self.assertEqual(fmt.format("{0}", 10), "10") 176 self.assertEqual(fmt.format("{0}{i}", 10, i=100), "10100") 177 self.assertEqual(fmt.format("{0}{i}{1}", 10, 20, i=100), "1010020") 178 self.assertRaises(ValueError, fmt.format, "{0}{i}{1}", 10, 20, i=100, j=0) 179 self.assertRaises(ValueError, fmt.format, "{0}", 10, 20) 180 self.assertRaises(ValueError, fmt.format, "{0}", 10, 20, i=100) 181 self.assertRaises(ValueError, fmt.format, "{i}", 10, 20, i=100) 182 183 def test_vformat_recursion_limit(self): 184 fmt = string.Formatter() 185 args = () 186 kwargs = dict(i=100) 187 with self.assertRaises(ValueError) as err: 188 fmt._vformat("{i}", args, kwargs, set(), -1) 189 self.assertIn("recursion", str(err.exception)) 190 191 192# Template tests (formerly housed in test_pep292.py) 193 194class Bag: 195 pass 196 197class Mapping: 198 def __getitem__(self, name): 199 obj = self 200 for part in name.split('.'): 201 try: 202 obj = getattr(obj, part) 203 except AttributeError: 204 raise KeyError(name) 205 return obj 206 207 208class TestTemplate(unittest.TestCase): 209 def test_regular_templates(self): 210 s = Template('$who likes to eat a bag of $what worth $$100') 211 self.assertEqual(s.substitute(dict(who='tim', what='ham')), 212 'tim likes to eat a bag of ham worth $100') 213 self.assertRaises(KeyError, s.substitute, dict(who='tim')) 214 self.assertRaises(TypeError, Template.substitute) 215 216 def test_regular_templates_with_braces(self): 217 s = Template('$who likes ${what} for ${meal}') 218 d = dict(who='tim', what='ham', meal='dinner') 219 self.assertEqual(s.substitute(d), 'tim likes ham for dinner') 220 self.assertRaises(KeyError, s.substitute, 221 dict(who='tim', what='ham')) 222 223 def test_escapes(self): 224 eq = self.assertEqual 225 s = Template('$who likes to eat a bag of $$what worth $$100') 226 eq(s.substitute(dict(who='tim', what='ham')), 227 'tim likes to eat a bag of $what worth $100') 228 s = Template('$who likes $$') 229 eq(s.substitute(dict(who='tim', what='ham')), 'tim likes $') 230 231 def test_percents(self): 232 eq = self.assertEqual 233 s = Template('%(foo)s $foo ${foo}') 234 d = dict(foo='baz') 235 eq(s.substitute(d), '%(foo)s baz baz') 236 eq(s.safe_substitute(d), '%(foo)s baz baz') 237 238 def test_stringification(self): 239 eq = self.assertEqual 240 s = Template('tim has eaten $count bags of ham today') 241 d = dict(count=7) 242 eq(s.substitute(d), 'tim has eaten 7 bags of ham today') 243 eq(s.safe_substitute(d), 'tim has eaten 7 bags of ham today') 244 s = Template('tim has eaten ${count} bags of ham today') 245 eq(s.substitute(d), 'tim has eaten 7 bags of ham today') 246 247 def test_tupleargs(self): 248 eq = self.assertEqual 249 s = Template('$who ate ${meal}') 250 d = dict(who=('tim', 'fred'), meal=('ham', 'kung pao')) 251 eq(s.substitute(d), "('tim', 'fred') ate ('ham', 'kung pao')") 252 eq(s.safe_substitute(d), "('tim', 'fred') ate ('ham', 'kung pao')") 253 254 def test_SafeTemplate(self): 255 eq = self.assertEqual 256 s = Template('$who likes ${what} for ${meal}') 257 eq(s.safe_substitute(dict(who='tim')), 'tim likes ${what} for ${meal}') 258 eq(s.safe_substitute(dict(what='ham')), '$who likes ham for ${meal}') 259 eq(s.safe_substitute(dict(what='ham', meal='dinner')), 260 '$who likes ham for dinner') 261 eq(s.safe_substitute(dict(who='tim', what='ham')), 262 'tim likes ham for ${meal}') 263 eq(s.safe_substitute(dict(who='tim', what='ham', meal='dinner')), 264 'tim likes ham for dinner') 265 266 def test_invalid_placeholders(self): 267 raises = self.assertRaises 268 s = Template('$who likes $') 269 raises(ValueError, s.substitute, dict(who='tim')) 270 s = Template('$who likes ${what)') 271 raises(ValueError, s.substitute, dict(who='tim')) 272 s = Template('$who likes $100') 273 raises(ValueError, s.substitute, dict(who='tim')) 274 275 def test_idpattern_override(self): 276 class PathPattern(Template): 277 idpattern = r'[_a-z][._a-z0-9]*' 278 m = Mapping() 279 m.bag = Bag() 280 m.bag.foo = Bag() 281 m.bag.foo.who = 'tim' 282 m.bag.what = 'ham' 283 s = PathPattern('$bag.foo.who likes to eat a bag of $bag.what') 284 self.assertEqual(s.substitute(m), 'tim likes to eat a bag of ham') 285 286 def test_pattern_override(self): 287 class MyPattern(Template): 288 pattern = r""" 289 (?P<escaped>@{2}) | 290 @(?P<named>[_a-z][._a-z0-9]*) | 291 @{(?P<braced>[_a-z][._a-z0-9]*)} | 292 (?P<invalid>@) 293 """ 294 m = Mapping() 295 m.bag = Bag() 296 m.bag.foo = Bag() 297 m.bag.foo.who = 'tim' 298 m.bag.what = 'ham' 299 s = MyPattern('@bag.foo.who likes to eat a bag of @bag.what') 300 self.assertEqual(s.substitute(m), 'tim likes to eat a bag of ham') 301 302 class BadPattern(Template): 303 pattern = r""" 304 (?P<badname>.*) | 305 (?P<escaped>@{2}) | 306 @(?P<named>[_a-z][._a-z0-9]*) | 307 @{(?P<braced>[_a-z][._a-z0-9]*)} | 308 (?P<invalid>@) | 309 """ 310 s = BadPattern('@bag.foo.who likes to eat a bag of @bag.what') 311 self.assertRaises(ValueError, s.substitute, {}) 312 self.assertRaises(ValueError, s.safe_substitute, {}) 313 314 def test_braced_override(self): 315 class MyTemplate(Template): 316 pattern = r""" 317 \$(?: 318 (?P<escaped>$) | 319 (?P<named>[_a-z][_a-z0-9]*) | 320 @@(?P<braced>[_a-z][_a-z0-9]*)@@ | 321 (?P<invalid>) | 322 ) 323 """ 324 325 tmpl = 'PyCon in $@@location@@' 326 t = MyTemplate(tmpl) 327 self.assertRaises(KeyError, t.substitute, {}) 328 val = t.substitute({'location': 'Cleveland'}) 329 self.assertEqual(val, 'PyCon in Cleveland') 330 331 def test_braced_override_safe(self): 332 class MyTemplate(Template): 333 pattern = r""" 334 \$(?: 335 (?P<escaped>$) | 336 (?P<named>[_a-z][_a-z0-9]*) | 337 @@(?P<braced>[_a-z][_a-z0-9]*)@@ | 338 (?P<invalid>) | 339 ) 340 """ 341 342 tmpl = 'PyCon in $@@location@@' 343 t = MyTemplate(tmpl) 344 self.assertEqual(t.safe_substitute(), tmpl) 345 val = t.safe_substitute({'location': 'Cleveland'}) 346 self.assertEqual(val, 'PyCon in Cleveland') 347 348 def test_invalid_with_no_lines(self): 349 # The error formatting for invalid templates 350 # has a special case for no data that the default 351 # pattern can't trigger (always has at least '$') 352 # So we craft a pattern that is always invalid 353 # with no leading data. 354 class MyTemplate(Template): 355 pattern = r""" 356 (?P<invalid>) | 357 unreachable( 358 (?P<named>) | 359 (?P<braced>) | 360 (?P<escaped>) 361 ) 362 """ 363 s = MyTemplate('') 364 with self.assertRaises(ValueError) as err: 365 s.substitute({}) 366 self.assertIn('line 1, col 1', str(err.exception)) 367 368 def test_unicode_values(self): 369 s = Template('$who likes $what') 370 d = dict(who='t\xffm', what='f\xfe\fed') 371 self.assertEqual(s.substitute(d), 't\xffm likes f\xfe\x0ced') 372 373 def test_keyword_arguments(self): 374 eq = self.assertEqual 375 s = Template('$who likes $what') 376 eq(s.substitute(who='tim', what='ham'), 'tim likes ham') 377 eq(s.substitute(dict(who='tim'), what='ham'), 'tim likes ham') 378 eq(s.substitute(dict(who='fred', what='kung pao'), 379 who='tim', what='ham'), 380 'tim likes ham') 381 s = Template('the mapping is $mapping') 382 eq(s.substitute(dict(foo='none'), mapping='bozo'), 383 'the mapping is bozo') 384 eq(s.substitute(dict(mapping='one'), mapping='two'), 385 'the mapping is two') 386 387 s = Template('the self is $self') 388 eq(s.substitute(self='bozo'), 'the self is bozo') 389 390 def test_keyword_arguments_safe(self): 391 eq = self.assertEqual 392 raises = self.assertRaises 393 s = Template('$who likes $what') 394 eq(s.safe_substitute(who='tim', what='ham'), 'tim likes ham') 395 eq(s.safe_substitute(dict(who='tim'), what='ham'), 'tim likes ham') 396 eq(s.safe_substitute(dict(who='fred', what='kung pao'), 397 who='tim', what='ham'), 398 'tim likes ham') 399 s = Template('the mapping is $mapping') 400 eq(s.safe_substitute(dict(foo='none'), mapping='bozo'), 401 'the mapping is bozo') 402 eq(s.safe_substitute(dict(mapping='one'), mapping='two'), 403 'the mapping is two') 404 d = dict(mapping='one') 405 raises(TypeError, s.substitute, d, {}) 406 raises(TypeError, s.safe_substitute, d, {}) 407 408 s = Template('the self is $self') 409 eq(s.safe_substitute(self='bozo'), 'the self is bozo') 410 411 def test_delimiter_override(self): 412 eq = self.assertEqual 413 raises = self.assertRaises 414 class AmpersandTemplate(Template): 415 delimiter = '&' 416 s = AmpersandTemplate('this &gift is for &{who} &&') 417 eq(s.substitute(gift='bud', who='you'), 'this bud is for you &') 418 raises(KeyError, s.substitute) 419 eq(s.safe_substitute(gift='bud', who='you'), 'this bud is for you &') 420 eq(s.safe_substitute(), 'this &gift is for &{who} &') 421 s = AmpersandTemplate('this &gift is for &{who} &') 422 raises(ValueError, s.substitute, dict(gift='bud', who='you')) 423 eq(s.safe_substitute(), 'this &gift is for &{who} &') 424 425 class PieDelims(Template): 426 delimiter = '@' 427 s = PieDelims('@who likes to eat a bag of @{what} worth $100') 428 self.assertEqual(s.substitute(dict(who='tim', what='ham')), 429 'tim likes to eat a bag of ham worth $100') 430 431 432if __name__ == '__main__': 433 unittest.main() 434