The string module has a Template class, that lets you make substitutions in a string using a mapping object, for instance:
>>> string.Template('var is $var').substitute({'var': 1})
'var is 1'
The substitute method may raise a KeyError exception, if an attempt is made to substitute an element that is missing from the mapping, for instance
>>> string.Template('var is $var and foo is $foo').substitute({'var': 1})
KeyError: 'foo'
or may raise a ValueError, if the template string is invalid, e.g. it contains a $
character followed by a space:
>>> string.Template('$ var is $var').substitute({'var': 1})
ValueError: Invalid placeholder in string: line 1, col 1
Given a template string and a mapping, I want to determine whether all place-holders in the template would be substituted. For this, I would try to make the substitution and catch any KeyError exception:
def check_substitution(template, mapping):
try:
string.Template(template).substitute(mapping)
except KeyError:
return False
except ValueError:
pass
return True
But this doesn't work, because if the template is invalid and a ValueError is raised, subsequent KeyErrors aren't caught:
>>> check_substitution('var is $var and foo is $foo', {'var': 1})
False
>>> check_substitution('$ var is $var and foo is $foo', {'var': 1})
True
but I don't care about ValueErrors. So, what would be the right approach to this problem?
The docs say that you can replace the pattern as long as it contains all necessary named groups:
import re
from string import Template
class TemplateIgnoreInvalid(Template):
# override pattern to make sure `invalid` never matches
pattern = r"""
%(delim)s(?:
(?P<escaped>%(delim)s) | # Escape sequence of two delimiters
(?P<named>%(id)s) | # delimiter and a Python identifier
{(?P<braced>%(id)s)} | # delimiter and a braced identifier
(?P<invalid>^$) # never matches (the regex is not multilined)
)
""" % dict(delim=re.escape(Template.delimiter), id=Template.idpattern)
def check_substitution(template, **mapping):
try:
TemplateIgnoreInvalid(template).substitute(mapping)
except KeyError:
return False
else:
return True
f = check_substitution
assert f('var is $var', var=1)
assert f('$ var is $var', var=1)
assert f('var is $var and foo is $foo', var=1, foo=2)
assert not f('var is $var and foo is $foo', var=1)
assert f('$ var is $var and foo is $foo', var=1, foo=2)
assert not f('$ var is $var and foo is $foo', var=1)
# support all invalid patterns
assert f('var is $var and foo is ${foo', var=1)
assert f('var is $var and foo is ${foo', var=1, foo=2) #NOTE: problematic API
assert f('var is $var and foo is ${foo and ${baz}', var=1, baz=3)
assert not f('var is $var and foo is ${foo and ${baz}', var=1)
It works for all invalid occurences of the delimiter ($
).
The examples show that ignoring invalid patterns conceals simple typos in the template so it is not a good API.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With