test_string.py 17.7 KB
Newer Older
1
import unittest
2
import string
3
from string import Template
4

5

6 7 8
class ModuleTest(unittest.TestCase):

    def test_attrs(self):
9 10 11 12 13 14 15 16 17 18 19 20 21
        # While the exact order of the items in these attributes is not
        # technically part of the "language spec", in practice there is almost
        # certainly user code that depends on the order, so de-facto it *is*
        # part of the spec.
        self.assertEqual(string.whitespace, ' \t\n\r\x0b\x0c')
        self.assertEqual(string.ascii_lowercase, 'abcdefghijklmnopqrstuvwxyz')
        self.assertEqual(string.ascii_uppercase, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
        self.assertEqual(string.ascii_letters, string.ascii_lowercase + string.ascii_uppercase)
        self.assertEqual(string.digits, '0123456789')
        self.assertEqual(string.hexdigits, string.digits + 'abcdefABCDEF')
        self.assertEqual(string.octdigits, '01234567')
        self.assertEqual(string.punctuation, '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~')
        self.assertEqual(string.printable, string.digits + string.ascii_lowercase + string.ascii_uppercase + string.punctuation + string.whitespace)
22

23 24 25 26 27 28 29
    def test_capwords(self):
        self.assertEqual(string.capwords('abc def ghi'), 'Abc Def Ghi')
        self.assertEqual(string.capwords('abc\tdef\nghi'), 'Abc Def Ghi')
        self.assertEqual(string.capwords('abc\t   def  \nghi'), 'Abc Def Ghi')
        self.assertEqual(string.capwords('ABC DEF GHI'), 'Abc Def Ghi')
        self.assertEqual(string.capwords('ABC-DEF-GHI', '-'), 'Abc-Def-Ghi')
        self.assertEqual(string.capwords('ABC-def DEF-ghi GHI'), 'Abc-def Def-ghi Ghi')
30 31 32
        self.assertEqual(string.capwords('   aBc  DeF   '), 'Abc Def')
        self.assertEqual(string.capwords('\taBc\tDeF\t'), 'Abc Def')
        self.assertEqual(string.capwords('\taBc\tDeF\t', '\t'), '\tAbc\tDef\t')
33

34
    def test_basic_formatter(self):
35 36
        fmt = string.Formatter()
        self.assertEqual(fmt.format("foo"), "foo")
37 38
        self.assertEqual(fmt.format("foo{0}", "bar"), "foobar")
        self.assertEqual(fmt.format("foo{1}{0}-{1}", "bar", 6), "foo6bar-6")
39 40 41 42 43 44 45 46 47 48 49 50
        self.assertRaises(TypeError, fmt.format)
        self.assertRaises(TypeError, string.Formatter.format)

    def test_format_keyword_arguments(self):
        fmt = string.Formatter()
        self.assertEqual(fmt.format("-{arg}-", arg='test'), '-test-')
        self.assertRaises(KeyError, fmt.format, "-{arg}-")
        self.assertEqual(fmt.format("-{self}-", self='test'), '-test-')
        self.assertRaises(KeyError, fmt.format, "-{self}-")
        self.assertEqual(fmt.format("-{format_string}-", format_string='test'),
                         '-test-')
        self.assertRaises(KeyError, fmt.format, "-{format_string}-")
51 52 53
        with self.assertWarnsRegex(DeprecationWarning, "format_string"):
            self.assertEqual(fmt.format(arg='test', format_string="-{arg}-"),
                             '-test-')
54

55 56 57 58 59 60 61 62
    def test_auto_numbering(self):
        fmt = string.Formatter()
        self.assertEqual(fmt.format('foo{}{}', 'bar', 6),
                         'foo{}{}'.format('bar', 6))
        self.assertEqual(fmt.format('foo{1}{num}{1}', None, 'bar', num=6),
                         'foo{1}{num}{1}'.format(None, 'bar', num=6))
        self.assertEqual(fmt.format('{:^{}}', 'bar', 6),
                         '{:^{}}'.format('bar', 6))
63 64
        self.assertEqual(fmt.format('{:^{}} {}', 'bar', 6, 'X'),
                         '{:^{}} {}'.format('bar', 6, 'X'))
65 66 67 68 69 70 71 72 73
        self.assertEqual(fmt.format('{:^{pad}}{}', 'foo', 'bar', pad=6),
                         '{:^{pad}}{}'.format('foo', 'bar', pad=6))

        with self.assertRaises(ValueError):
            fmt.format('foo{1}{}', 'bar', 6)

        with self.assertRaises(ValueError):
            fmt.format('foo{}{1}', 'bar', 6)

74 75
    def test_conversion_specifiers(self):
        fmt = string.Formatter()
76
        self.assertEqual(fmt.format("-{arg!r}-", arg='test'), "-'test'-")
77 78
        self.assertEqual(fmt.format("{0!s}", 'test'), 'test')
        self.assertRaises(ValueError, fmt.format, "{0!h}", 'test')
79 80 81 82 83 84
        # issue13579
        self.assertEqual(fmt.format("{0!a}", 42), '42')
        self.assertEqual(fmt.format("{0!a}",  string.ascii_letters),
            "'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'")
        self.assertEqual(fmt.format("{0!a}",  chr(255)), "'\\xff'")
        self.assertEqual(fmt.format("{0!a}",  chr(256)), "'\\u0100'")
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

    def test_name_lookup(self):
        fmt = string.Formatter()
        class AnyAttr:
            def __getattr__(self, attr):
                return attr
        x = AnyAttr()
        self.assertEqual(fmt.format("{0.lumber}{0.jack}", x), 'lumberjack')
        with self.assertRaises(AttributeError):
            fmt.format("{0.lumber}{0.jack}", '')

    def test_index_lookup(self):
        fmt = string.Formatter()
        lookup = ["eggs", "and", "spam"]
        self.assertEqual(fmt.format("{0[2]}{0[0]}", lookup), 'spameggs')
        with self.assertRaises(IndexError):
            fmt.format("{0[2]}{0[0]}", [])
        with self.assertRaises(KeyError):
            fmt.format("{0[2]}{0[0]}", {})
104

105
    def test_override_get_value(self):
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
        class NamespaceFormatter(string.Formatter):
            def __init__(self, namespace={}):
                string.Formatter.__init__(self)
                self.namespace = namespace

            def get_value(self, key, args, kwds):
                if isinstance(key, str):
                    try:
                        # Check explicitly passed arguments first
                        return kwds[key]
                    except KeyError:
                        return self.namespace[key]
                else:
                    string.Formatter.get_value(key, args, kwds)

        fmt = NamespaceFormatter({'greeting':'hello'})
        self.assertEqual(fmt.format("{greeting}, world!"), 'hello, world!')
123 124


125
    def test_override_format_field(self):
126 127 128 129 130 131 132 133
        class CallFormatter(string.Formatter):
            def format_field(self, value, format_spec):
                return format(value(), format_spec)

        fmt = CallFormatter()
        self.assertEqual(fmt.format('*{0}*', lambda : 'result'), '*result*')


134
    def test_override_convert_field(self):
135 136 137 138
        class XFormatter(string.Formatter):
            def convert_field(self, value, conversion):
                if conversion == 'x':
                    return None
139
                return super().convert_field(value, conversion)
140 141 142 143 144

        fmt = XFormatter()
        self.assertEqual(fmt.format("{0!r}:{0!x}", 'foo', 'foo'), "'foo':None")


145
    def test_override_parse(self):
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
        class BarFormatter(string.Formatter):
            # returns an iterable that contains tuples of the form:
            # (literal_text, field_name, format_spec, conversion)
            def parse(self, format_string):
                for field in format_string.split('|'):
                    if field[0] == '+':
                        # it's markup
                        field_name, _, format_spec = field[1:].partition(':')
                        yield '', field_name, format_spec, None
                    else:
                        yield field, None, None, None

        fmt = BarFormatter()
        self.assertEqual(fmt.format('*|+0:^10s|*', 'foo'), '*   foo    *')

161
    def test_check_unused_args(self):
162 163
        class CheckAllUsedFormatter(string.Formatter):
            def check_unused_args(self, used_args, args, kwargs):
164
                # Track which arguments actually got used
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
                unused_args = set(kwargs.keys())
                unused_args.update(range(0, len(args)))

                for arg in used_args:
                    unused_args.remove(arg)

                if unused_args:
                    raise ValueError("unused arguments")

        fmt = CheckAllUsedFormatter()
        self.assertEqual(fmt.format("{0}", 10), "10")
        self.assertEqual(fmt.format("{0}{i}", 10, i=100), "10100")
        self.assertEqual(fmt.format("{0}{i}{1}", 10, 20, i=100), "1010020")
        self.assertRaises(ValueError, fmt.format, "{0}{i}{1}", 10, 20, i=100, j=0)
        self.assertRaises(ValueError, fmt.format, "{0}", 10, 20)
        self.assertRaises(ValueError, fmt.format, "{0}", 10, 20, i=100)
        self.assertRaises(ValueError, fmt.format, "{i}", 10, 20, i=100)

183 184 185 186 187 188 189
    def test_vformat_recursion_limit(self):
        fmt = string.Formatter()
        args = ()
        kwargs = dict(i=100)
        with self.assertRaises(ValueError) as err:
            fmt._vformat("{i}", args, kwargs, set(), -1)
        self.assertIn("recursion", str(err.exception))
190 191


192 193
# Template tests (formerly housed in test_pep292.py)

194 195 196 197 198 199 200 201 202 203 204 205
class Bag:
    pass

class Mapping:
    def __getitem__(self, name):
        obj = self
        for part in name.split('.'):
            try:
                obj = getattr(obj, part)
            except AttributeError:
                raise KeyError(name)
        return obj
206 207


208
class TestTemplate(unittest.TestCase):
209 210
    def test_regular_templates(self):
        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 217

    def test_regular_templates_with_braces(self):
        s = Template('$who likes ${what} for ${meal}')
218 219 220 221
        d = dict(who='tim', what='ham', meal='dinner')
        self.assertEqual(s.substitute(d), 'tim likes ham for dinner')
        self.assertRaises(KeyError, s.substitute,
                          dict(who='tim', what='ham'))
222 223 224 225

    def test_escapes(self):
        eq = self.assertEqual
        s = Template('$who likes to eat a bag of $$what worth $$100')
226
        eq(s.substitute(dict(who='tim', what='ham')),
227 228
           'tim likes to eat a bag of $what worth $100')
        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 235 236
        d = dict(foo='baz')
        eq(s.substitute(d), '%(foo)s baz baz')
        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 242 243 244 245
        d = dict(count=7)
        eq(s.substitute(d), 'tim has eaten 7 bags of ham today')
        eq(s.safe_substitute(d), 'tim has eaten 7 bags of ham today')
        s = Template('tim has eaten ${count} bags of ham today')
        eq(s.substitute(d), 'tim has eaten 7 bags of ham today')
246

247 248 249 250 251 252 253
    def test_tupleargs(self):
        eq = self.assertEqual
        s = Template('$who ate ${meal}')
        d = dict(who=('tim', 'fred'), meal=('ham', 'kung pao'))
        eq(s.substitute(d), "('tim', 'fred') ate ('ham', 'kung pao')")
        eq(s.safe_substitute(d), "('tim', 'fred') ate ('ham', 'kung pao')")

254 255
    def test_SafeTemplate(self):
        eq = self.assertEqual
256 257 258 259
        s = Template('$who likes ${what} for ${meal}')
        eq(s.safe_substitute(dict(who='tim')), 'tim likes ${what} for ${meal}')
        eq(s.safe_substitute(dict(what='ham')), '$who likes ham for ${meal}')
        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 265 266 267 268
           'tim likes ham for dinner')

    def test_invalid_placeholders(self):
        raises = self.assertRaises
        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 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
        raises(ValueError, s.substitute, dict(who='tim'))

    def test_idpattern_override(self):
        class PathPattern(Template):
            idpattern = r'[_a-z][._a-z0-9]*'
        m = Mapping()
        m.bag = Bag()
        m.bag.foo = Bag()
        m.bag.foo.who = 'tim'
        m.bag.what = 'ham'
        s = PathPattern('$bag.foo.who likes to eat a bag of $bag.what')
        self.assertEqual(s.substitute(m), 'tim likes to eat a bag of ham')

    def test_pattern_override(self):
        class MyPattern(Template):
            pattern = r"""
            (?P<escaped>@{2})                   |
            @(?P<named>[_a-z][._a-z0-9]*)       |
            @{(?P<braced>[_a-z][._a-z0-9]*)}    |
292
            (?P<invalid>@)
293 294 295 296 297 298 299 300 301
            """
        m = Mapping()
        m.bag = Bag()
        m.bag.foo = Bag()
        m.bag.foo.who = 'tim'
        m.bag.what = 'ham'
        s = MyPattern('@bag.foo.who likes to eat a bag of @bag.what')
        self.assertEqual(s.substitute(m), 'tim likes to eat a bag of ham')

302 303 304 305 306 307 308 309 310 311 312 313
        class BadPattern(Template):
            pattern = r"""
            (?P<badname>.*)                     |
            (?P<escaped>@{2})                   |
            @(?P<named>[_a-z][._a-z0-9]*)       |
            @{(?P<braced>[_a-z][._a-z0-9]*)}    |
            (?P<invalid>@)                      |
            """
        s = BadPattern('@bag.foo.who likes to eat a bag of @bag.what')
        self.assertRaises(ValueError, s.substitute, {})
        self.assertRaises(ValueError, s.safe_substitute, {})

314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
    def test_braced_override(self):
        class MyTemplate(Template):
            pattern = r"""
            \$(?:
              (?P<escaped>$)                     |
              (?P<named>[_a-z][_a-z0-9]*)        |
              @@(?P<braced>[_a-z][_a-z0-9]*)@@   |
              (?P<invalid>)                      |
           )
           """

        tmpl = 'PyCon in $@@location@@'
        t = MyTemplate(tmpl)
        self.assertRaises(KeyError, t.substitute, {})
        val = t.substitute({'location': 'Cleveland'})
        self.assertEqual(val, 'PyCon in Cleveland')

    def test_braced_override_safe(self):
        class MyTemplate(Template):
            pattern = r"""
            \$(?:
              (?P<escaped>$)                     |
              (?P<named>[_a-z][_a-z0-9]*)        |
              @@(?P<braced>[_a-z][_a-z0-9]*)@@   |
              (?P<invalid>)                      |
           )
           """

        tmpl = 'PyCon in $@@location@@'
        t = MyTemplate(tmpl)
        self.assertEqual(t.safe_substitute(), tmpl)
        val = t.safe_substitute({'location': 'Cleveland'})
        self.assertEqual(val, 'PyCon in Cleveland')

348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
    def test_invalid_with_no_lines(self):
        # The error formatting for invalid templates
        # has a special case for no data that the default
        # pattern can't trigger (always has at least '$')
        # So we craft a pattern that is always invalid
        # with no leading data.
        class MyTemplate(Template):
            pattern = r"""
              (?P<invalid>) |
              unreachable(
                (?P<named>)   |
                (?P<braced>)  |
                (?P<escaped>)
              )
            """
        s = MyTemplate('')
        with self.assertRaises(ValueError) as err:
            s.substitute({})
        self.assertIn('line 1, col 1', str(err.exception))

368 369
    def test_unicode_values(self):
        s = Template('$who likes $what')
370 371
        d = dict(who='t\xffm', what='f\xfe\fed')
        self.assertEqual(s.substitute(d), 't\xffm likes f\xfe\x0ced')
372

373 374 375 376 377 378 379 380 381 382 383 384 385 386
    def test_keyword_arguments(self):
        eq = self.assertEqual
        s = Template('$who likes $what')
        eq(s.substitute(who='tim', what='ham'), 'tim likes ham')
        eq(s.substitute(dict(who='tim'), what='ham'), 'tim likes ham')
        eq(s.substitute(dict(who='fred', what='kung pao'),
                        who='tim', what='ham'),
           'tim likes ham')
        s = Template('the mapping is $mapping')
        eq(s.substitute(dict(foo='none'), mapping='bozo'),
           'the mapping is bozo')
        eq(s.substitute(dict(mapping='one'), mapping='two'),
           'the mapping is two')

387 388 389
        s = Template('the self is $self')
        eq(s.substitute(self='bozo'), 'the self is bozo')

390 391
    def test_keyword_arguments_safe(self):
        eq = self.assertEqual
392
        raises = self.assertRaises
393 394 395 396 397 398 399 400 401 402 403
        s = Template('$who likes $what')
        eq(s.safe_substitute(who='tim', what='ham'), 'tim likes ham')
        eq(s.safe_substitute(dict(who='tim'), what='ham'), 'tim likes ham')
        eq(s.safe_substitute(dict(who='fred', what='kung pao'),
                        who='tim', what='ham'),
           'tim likes ham')
        s = Template('the mapping is $mapping')
        eq(s.safe_substitute(dict(foo='none'), mapping='bozo'),
           'the mapping is bozo')
        eq(s.safe_substitute(dict(mapping='one'), mapping='two'),
           'the mapping is two')
404 405 406
        d = dict(mapping='one')
        raises(TypeError, s.substitute, d, {})
        raises(TypeError, s.safe_substitute, d, {})
407

408 409 410
        s = Template('the self is $self')
        eq(s.safe_substitute(self='bozo'), 'the self is bozo')

411
    def test_delimiter_override(self):
412 413
        eq = self.assertEqual
        raises = self.assertRaises
414 415 416
        class AmpersandTemplate(Template):
            delimiter = '&'
        s = AmpersandTemplate('this &gift is for &{who} &&')
417 418 419 420
        eq(s.substitute(gift='bud', who='you'), 'this bud is for you &')
        raises(KeyError, s.substitute)
        eq(s.safe_substitute(gift='bud', who='you'), 'this bud is for you &')
        eq(s.safe_substitute(), 'this &gift is for &{who} &')
421
        s = AmpersandTemplate('this &gift is for &{who} &')
422 423 424
        raises(ValueError, s.substitute, dict(gift='bud', who='you'))
        eq(s.safe_substitute(), 'this &gift is for &{who} &')

Georg Brandl's avatar
Georg Brandl committed
425 426 427 428 429 430
        class PieDelims(Template):
            delimiter = '@'
        s = PieDelims('@who likes to eat a bag of @{what} worth $100')
        self.assertEqual(s.substitute(dict(who='tim', what='ham')),
                         'tim likes to eat a bag of ham worth $100')

431 432

if __name__ == '__main__':
433
    unittest.main()