• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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