Home | History | Annotate | Download | only in test
      1 import unittest
      2 import string
      3 from string import Template
      4 
      5 
      6 class 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.assertRaisesRegex(TypeError, "format_string"):
     52             fmt.format(format_string="-{arg}-", arg='test')
     53 
     54     def test_auto_numbering(self):
     55         fmt = string.Formatter()
     56         self.assertEqual(fmt.format('foo{}{}', 'bar', 6),
     57                          'foo{}{}'.format('bar', 6))
     58         self.assertEqual(fmt.format('foo{1}{num}{1}', None, 'bar', num=6),
     59                          'foo{1}{num}{1}'.format(None, 'bar', num=6))
     60         self.assertEqual(fmt.format('{:^{}}', 'bar', 6),
     61                          '{:^{}}'.format('bar', 6))
     62         self.assertEqual(fmt.format('{:^{}} {}', 'bar', 6, 'X'),
     63                          '{:^{}} {}'.format('bar', 6, 'X'))
     64         self.assertEqual(fmt.format('{:^{pad}}{}', 'foo', 'bar', pad=6),
     65                          '{:^{pad}}{}'.format('foo', 'bar', pad=6))
     66 
     67         with self.assertRaises(ValueError):
     68             fmt.format('foo{1}{}', 'bar', 6)
     69 
     70         with self.assertRaises(ValueError):
     71             fmt.format('foo{}{1}', 'bar', 6)
     72 
     73     def test_conversion_specifiers(self):
     74         fmt = string.Formatter()
     75         self.assertEqual(fmt.format("-{arg!r}-", arg='test'), "-'test'-")
     76         self.assertEqual(fmt.format("{0!s}", 'test'), 'test')
     77         self.assertRaises(ValueError, fmt.format, "{0!h}", 'test')
     78         # issue13579
     79         self.assertEqual(fmt.format("{0!a}", 42), '42')
     80         self.assertEqual(fmt.format("{0!a}",  string.ascii_letters),
     81             "'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'")
     82         self.assertEqual(fmt.format("{0!a}",  chr(255)), "'\\xff'")
     83         self.assertEqual(fmt.format("{0!a}",  chr(256)), "'\\u0100'")
     84 
     85     def test_name_lookup(self):
     86         fmt = string.Formatter()
     87         class AnyAttr:
     88             def __getattr__(self, attr):
     89                 return attr
     90         x = AnyAttr()
     91         self.assertEqual(fmt.format("{0.lumber}{0.jack}", x), 'lumberjack')
     92         with self.assertRaises(AttributeError):
     93             fmt.format("{0.lumber}{0.jack}", '')
     94 
     95     def test_index_lookup(self):
     96         fmt = string.Formatter()
     97         lookup = ["eggs", "and", "spam"]
     98         self.assertEqual(fmt.format("{0[2]}{0[0]}", lookup), 'spameggs')
     99         with self.assertRaises(IndexError):
    100             fmt.format("{0[2]}{0[0]}", [])
    101         with self.assertRaises(KeyError):
    102             fmt.format("{0[2]}{0[0]}", {})
    103 
    104     def test_override_get_value(self):
    105         class NamespaceFormatter(string.Formatter):
    106             def __init__(self, namespace={}):
    107                 string.Formatter.__init__(self)
    108                 self.namespace = namespace
    109 
    110             def get_value(self, key, args, kwds):
    111                 if isinstance(key, str):
    112                     try:
    113                         # Check explicitly passed arguments first
    114                         return kwds[key]
    115                     except KeyError:
    116                         return self.namespace[key]
    117                 else:
    118                     string.Formatter.get_value(key, args, kwds)
    119 
    120         fmt = NamespaceFormatter({'greeting':'hello'})
    121         self.assertEqual(fmt.format("{greeting}, world!"), 'hello, world!')
    122 
    123 
    124     def test_override_format_field(self):
    125         class CallFormatter(string.Formatter):
    126             def format_field(self, value, format_spec):
    127                 return format(value(), format_spec)
    128 
    129         fmt = CallFormatter()
    130         self.assertEqual(fmt.format('*{0}*', lambda : 'result'), '*result*')
    131 
    132 
    133     def test_override_convert_field(self):
    134         class XFormatter(string.Formatter):
    135             def convert_field(self, value, conversion):
    136                 if conversion == 'x':
    137                     return None
    138                 return super().convert_field(value, conversion)
    139 
    140         fmt = XFormatter()
    141         self.assertEqual(fmt.format("{0!r}:{0!x}", 'foo', 'foo'), "'foo':None")
    142 
    143 
    144     def test_override_parse(self):
    145         class BarFormatter(string.Formatter):
    146             # returns an iterable that contains tuples of the form:
    147             # (literal_text, field_name, format_spec, conversion)
    148             def parse(self, format_string):
    149                 for field in format_string.split('|'):
    150                     if field[0] == '+':
    151                         # it's markup
    152                         field_name, _, format_spec = field[1:].partition(':')
    153                         yield '', field_name, format_spec, None
    154                     else:
    155                         yield field, None, None, None
    156 
    157         fmt = BarFormatter()
    158         self.assertEqual(fmt.format('*|+0:^10s|*', 'foo'), '*   foo    *')
    159 
    160     def test_check_unused_args(self):
    161         class CheckAllUsedFormatter(string.Formatter):
    162             def check_unused_args(self, used_args, args, kwargs):
    163                 # Track which arguments actually got used
    164                 unused_args = set(kwargs.keys())
    165                 unused_args.update(range(0, len(args)))
    166 
    167                 for arg in used_args:
    168                     unused_args.remove(arg)
    169 
    170                 if unused_args:
    171                     raise ValueError("unused arguments")
    172 
    173         fmt = CheckAllUsedFormatter()
    174         self.assertEqual(fmt.format("{0}", 10), "10")
    175         self.assertEqual(fmt.format("{0}{i}", 10, i=100), "10100")
    176         self.assertEqual(fmt.format("{0}{i}{1}", 10, 20, i=100), "1010020")
    177         self.assertRaises(ValueError, fmt.format, "{0}{i}{1}", 10, 20, i=100, j=0)
    178         self.assertRaises(ValueError, fmt.format, "{0}", 10, 20)
    179         self.assertRaises(ValueError, fmt.format, "{0}", 10, 20, i=100)
    180         self.assertRaises(ValueError, fmt.format, "{i}", 10, 20, i=100)
    181 
    182     def test_vformat_recursion_limit(self):
    183         fmt = string.Formatter()
    184         args = ()
    185         kwargs = dict(i=100)
    186         with self.assertRaises(ValueError) as err:
    187             fmt._vformat("{i}", args, kwargs, set(), -1)
    188         self.assertIn("recursion", str(err.exception))
    189 
    190 
    191 # Template tests (formerly housed in test_pep292.py)
    192 
    193 class Bag:
    194     pass
    195 
    196 class Mapping:
    197     def __getitem__(self, name):
    198         obj = self
    199         for part in name.split('.'):
    200             try:
    201                 obj = getattr(obj, part)
    202             except AttributeError:
    203                 raise KeyError(name)
    204         return obj
    205 
    206 
    207 class TestTemplate(unittest.TestCase):
    208     def test_regular_templates(self):
    209         s = Template('$who likes to eat a bag of $what worth $$100')
    210         self.assertEqual(s.substitute(dict(who='tim', what='ham')),
    211                          'tim likes to eat a bag of ham worth $100')
    212         self.assertRaises(KeyError, s.substitute, dict(who='tim'))
    213         self.assertRaises(TypeError, Template.substitute)
    214 
    215     def test_regular_templates_with_braces(self):
    216         s = Template('$who likes ${what} for ${meal}')
    217         d = dict(who='tim', what='ham', meal='dinner')
    218         self.assertEqual(s.substitute(d), 'tim likes ham for dinner')
    219         self.assertRaises(KeyError, s.substitute,
    220                           dict(who='tim', what='ham'))
    221 
    222     def test_regular_templates_with_upper_case(self):
    223         s = Template('$WHO likes ${WHAT} for ${MEAL}')
    224         d = dict(WHO='tim', WHAT='ham', MEAL='dinner')
    225         self.assertEqual(s.substitute(d), 'tim likes ham for dinner')
    226 
    227     def test_regular_templates_with_non_letters(self):
    228         s = Template('$_wh0_ likes ${_w_h_a_t_} for ${mea1}')
    229         d = dict(_wh0_='tim', _w_h_a_t_='ham', mea1='dinner')
    230         self.assertEqual(s.substitute(d), 'tim likes ham for dinner')
    231 
    232     def test_escapes(self):
    233         eq = self.assertEqual
    234         s = Template('$who likes to eat a bag of $$what worth $$100')
    235         eq(s.substitute(dict(who='tim', what='ham')),
    236            'tim likes to eat a bag of $what worth $100')
    237         s = Template('$who likes $$')
    238         eq(s.substitute(dict(who='tim', what='ham')), 'tim likes $')
    239 
    240     def test_percents(self):
    241         eq = self.assertEqual
    242         s = Template('%(foo)s $foo ${foo}')
    243         d = dict(foo='baz')
    244         eq(s.substitute(d), '%(foo)s baz baz')
    245         eq(s.safe_substitute(d), '%(foo)s baz baz')
    246 
    247     def test_stringification(self):
    248         eq = self.assertEqual
    249         s = Template('tim has eaten $count bags of ham today')
    250         d = dict(count=7)
    251         eq(s.substitute(d), 'tim has eaten 7 bags of ham today')
    252         eq(s.safe_substitute(d), 'tim has eaten 7 bags of ham today')
    253         s = Template('tim has eaten ${count} bags of ham today')
    254         eq(s.substitute(d), 'tim has eaten 7 bags of ham today')
    255 
    256     def test_tupleargs(self):
    257         eq = self.assertEqual
    258         s = Template('$who ate ${meal}')
    259         d = dict(who=('tim', 'fred'), meal=('ham', 'kung pao'))
    260         eq(s.substitute(d), "('tim', 'fred') ate ('ham', 'kung pao')")
    261         eq(s.safe_substitute(d), "('tim', 'fred') ate ('ham', 'kung pao')")
    262 
    263     def test_SafeTemplate(self):
    264         eq = self.assertEqual
    265         s = Template('$who likes ${what} for ${meal}')
    266         eq(s.safe_substitute(dict(who='tim')), 'tim likes ${what} for ${meal}')
    267         eq(s.safe_substitute(dict(what='ham')), '$who likes ham for ${meal}')
    268         eq(s.safe_substitute(dict(what='ham', meal='dinner')),
    269            '$who likes ham for dinner')
    270         eq(s.safe_substitute(dict(who='tim', what='ham')),
    271            'tim likes ham for ${meal}')
    272         eq(s.safe_substitute(dict(who='tim', what='ham', meal='dinner')),
    273            'tim likes ham for dinner')
    274 
    275     def test_invalid_placeholders(self):
    276         raises = self.assertRaises
    277         s = Template('$who likes $')
    278         raises(ValueError, s.substitute, dict(who='tim'))
    279         s = Template('$who likes ${what)')
    280         raises(ValueError, s.substitute, dict(who='tim'))
    281         s = Template('$who likes $100')
    282         raises(ValueError, s.substitute, dict(who='tim'))
    283         # Template.idpattern should match to only ASCII characters.
    284         # https://bugs.python.org/issue31672
    285         s = Template("$who likes $\u0131")  # (DOTLESS I)
    286         raises(ValueError, s.substitute, dict(who='tim'))
    287         s = Template("$who likes $\u0130")  # (LATIN CAPITAL LETTER I WITH DOT ABOVE)
    288         raises(ValueError, s.substitute, dict(who='tim'))
    289 
    290     def test_idpattern_override(self):
    291         class PathPattern(Template):
    292             idpattern = r'[_a-z][._a-z0-9]*'
    293         m = Mapping()
    294         m.bag = Bag()
    295         m.bag.foo = Bag()
    296         m.bag.foo.who = 'tim'
    297         m.bag.what = 'ham'
    298         s = PathPattern('$bag.foo.who likes to eat a bag of $bag.what')
    299         self.assertEqual(s.substitute(m), 'tim likes to eat a bag of ham')
    300 
    301     def test_flags_override(self):
    302         class MyPattern(Template):
    303             flags = 0
    304         s = MyPattern('$wHO likes ${WHAT} for ${meal}')
    305         d = dict(wHO='tim', WHAT='ham', meal='dinner', w='fred')
    306         self.assertRaises(ValueError, s.substitute, d)
    307         self.assertEqual(s.safe_substitute(d), 'fredHO likes ${WHAT} for dinner')
    308 
    309     def test_idpattern_override_inside_outside(self):
    310         # bpo-1198569: Allow the regexp inside and outside braces to be
    311         # different when deriving from Template.
    312         class MyPattern(Template):
    313             idpattern = r'[a-z]+'
    314             braceidpattern = r'[A-Z]+'
    315             flags = 0
    316         m = dict(foo='foo', BAR='BAR')
    317         s = MyPattern('$foo ${BAR}')
    318         self.assertEqual(s.substitute(m), 'foo BAR')
    319 
    320     def test_idpattern_override_inside_outside_invalid_unbraced(self):
    321         # bpo-1198569: Allow the regexp inside and outside braces to be
    322         # different when deriving from Template.
    323         class MyPattern(Template):
    324             idpattern = r'[a-z]+'
    325             braceidpattern = r'[A-Z]+'
    326             flags = 0
    327         m = dict(foo='foo', BAR='BAR')
    328         s = MyPattern('$FOO')
    329         self.assertRaises(ValueError, s.substitute, m)
    330         s = MyPattern('${bar}')
    331         self.assertRaises(ValueError, s.substitute, m)
    332 
    333     def test_pattern_override(self):
    334         class MyPattern(Template):
    335             pattern = r"""
    336             (?P<escaped>@{2})                   |
    337             @(?P<named>[_a-z][._a-z0-9]*)       |
    338             @{(?P<braced>[_a-z][._a-z0-9]*)}    |
    339             (?P<invalid>@)
    340             """
    341         m = Mapping()
    342         m.bag = Bag()
    343         m.bag.foo = Bag()
    344         m.bag.foo.who = 'tim'
    345         m.bag.what = 'ham'
    346         s = MyPattern('@bag.foo.who likes to eat a bag of @bag.what')
    347         self.assertEqual(s.substitute(m), 'tim likes to eat a bag of ham')
    348 
    349         class BadPattern(Template):
    350             pattern = r"""
    351             (?P<badname>.*)                     |
    352             (?P<escaped>@{2})                   |
    353             @(?P<named>[_a-z][._a-z0-9]*)       |
    354             @{(?P<braced>[_a-z][._a-z0-9]*)}    |
    355             (?P<invalid>@)                      |
    356             """
    357         s = BadPattern('@bag.foo.who likes to eat a bag of @bag.what')
    358         self.assertRaises(ValueError, s.substitute, {})
    359         self.assertRaises(ValueError, s.safe_substitute, {})
    360 
    361     def test_braced_override(self):
    362         class MyTemplate(Template):
    363             pattern = r"""
    364             \$(?:
    365               (?P<escaped>$)                     |
    366               (?P<named>[_a-z][_a-z0-9]*)        |
    367               @@(?P<braced>[_a-z][_a-z0-9]*)@@   |
    368               (?P<invalid>)                      |
    369            )
    370            """
    371 
    372         tmpl = 'PyCon in $@@location@@'
    373         t = MyTemplate(tmpl)
    374         self.assertRaises(KeyError, t.substitute, {})
    375         val = t.substitute({'location': 'Cleveland'})
    376         self.assertEqual(val, 'PyCon in Cleveland')
    377 
    378     def test_braced_override_safe(self):
    379         class MyTemplate(Template):
    380             pattern = r"""
    381             \$(?:
    382               (?P<escaped>$)                     |
    383               (?P<named>[_a-z][_a-z0-9]*)        |
    384               @@(?P<braced>[_a-z][_a-z0-9]*)@@   |
    385               (?P<invalid>)                      |
    386            )
    387            """
    388 
    389         tmpl = 'PyCon in $@@location@@'
    390         t = MyTemplate(tmpl)
    391         self.assertEqual(t.safe_substitute(), tmpl)
    392         val = t.safe_substitute({'location': 'Cleveland'})
    393         self.assertEqual(val, 'PyCon in Cleveland')
    394 
    395     def test_invalid_with_no_lines(self):
    396         # The error formatting for invalid templates
    397         # has a special case for no data that the default
    398         # pattern can't trigger (always has at least '$')
    399         # So we craft a pattern that is always invalid
    400         # with no leading data.
    401         class MyTemplate(Template):
    402             pattern = r"""
    403               (?P<invalid>) |
    404               unreachable(
    405                 (?P<named>)   |
    406                 (?P<braced>)  |
    407                 (?P<escaped>)
    408               )
    409             """
    410         s = MyTemplate('')
    411         with self.assertRaises(ValueError) as err:
    412             s.substitute({})
    413         self.assertIn('line 1, col 1', str(err.exception))
    414 
    415     def test_unicode_values(self):
    416         s = Template('$who likes $what')
    417         d = dict(who='t\xffm', what='f\xfe\fed')
    418         self.assertEqual(s.substitute(d), 't\xffm likes f\xfe\x0ced')
    419 
    420     def test_keyword_arguments(self):
    421         eq = self.assertEqual
    422         s = Template('$who likes $what')
    423         eq(s.substitute(who='tim', what='ham'), 'tim likes ham')
    424         eq(s.substitute(dict(who='tim'), what='ham'), 'tim likes ham')
    425         eq(s.substitute(dict(who='fred', what='kung pao'),
    426                         who='tim', what='ham'),
    427            'tim likes ham')
    428         s = Template('the mapping is $mapping')
    429         eq(s.substitute(dict(foo='none'), mapping='bozo'),
    430            'the mapping is bozo')
    431         eq(s.substitute(dict(mapping='one'), mapping='two'),
    432            'the mapping is two')
    433 
    434         s = Template('the self is $self')
    435         eq(s.substitute(self='bozo'), 'the self is bozo')
    436 
    437     def test_keyword_arguments_safe(self):
    438         eq = self.assertEqual
    439         raises = self.assertRaises
    440         s = Template('$who likes $what')
    441         eq(s.safe_substitute(who='tim', what='ham'), 'tim likes ham')
    442         eq(s.safe_substitute(dict(who='tim'), what='ham'), 'tim likes ham')
    443         eq(s.safe_substitute(dict(who='fred', what='kung pao'),
    444                         who='tim', what='ham'),
    445            'tim likes ham')
    446         s = Template('the mapping is $mapping')
    447         eq(s.safe_substitute(dict(foo='none'), mapping='bozo'),
    448            'the mapping is bozo')
    449         eq(s.safe_substitute(dict(mapping='one'), mapping='two'),
    450            'the mapping is two')
    451         d = dict(mapping='one')
    452         raises(TypeError, s.substitute, d, {})
    453         raises(TypeError, s.safe_substitute, d, {})
    454 
    455         s = Template('the self is $self')
    456         eq(s.safe_substitute(self='bozo'), 'the self is bozo')
    457 
    458     def test_delimiter_override(self):
    459         eq = self.assertEqual
    460         raises = self.assertRaises
    461         class AmpersandTemplate(Template):
    462             delimiter = '&'
    463         s = AmpersandTemplate('this &gift is for &{who} &&')
    464         eq(s.substitute(gift='bud', who='you'), 'this bud is for you &')
    465         raises(KeyError, s.substitute)
    466         eq(s.safe_substitute(gift='bud', who='you'), 'this bud is for you &')
    467         eq(s.safe_substitute(), 'this &gift is for &{who} &')
    468         s = AmpersandTemplate('this &gift is for &{who} &')
    469         raises(ValueError, s.substitute, dict(gift='bud', who='you'))
    470         eq(s.safe_substitute(), 'this &gift is for &{who} &')
    471 
    472         class PieDelims(Template):
    473             delimiter = '@'
    474         s = PieDelims('@who likes to eat a bag of @{what} worth $100')
    475         self.assertEqual(s.substitute(dict(who='tim', what='ham')),
    476                          'tim likes to eat a bag of ham worth $100')
    477 
    478 
    479 if __name__ == '__main__':
    480     unittest.main()
    481