I wanted a string.Template class that will substitute placeholders with a default value if the mapping is not provided.
For example, say I create a template
A template with $value1, $value2
When I substitute this template with values, say I provide only $value1, I want other $variables to be substituted with a default value. If my default value is HAHA. and I provide this map {'value1': 'first_value'}, the result is
A template with first_value, HAHA
Creating the class was very simple (in retrospect).
I just subclassed string.Template and provided my own version of safe_substitute.
Here's the class, complete with doctest code.
import string
class Template(string.Template):
"""
Template with modified functions
-- doctests ----
>>> t = 'this is a $test with $result'
>>> st = Template(t, 'haha')
>>> d = {'test': 'a big test'}
>>> result = st.safe_substitute(d)
>>> expected_result = 'this is a a big test with haha'
>>> test_result = True if expected_result == result else 'expected %s >>> but got <<< %s' % (expected_result, result)
>>> test_result
True
>>> st = Template(t)
>>> d = {'test': 'a big test'}
>>> result = st.safe_substitute(d)
>>> expected_result = 'this is a a big test with $result'
>>> test_result = True if expected_result == result else 'expected %s >>> but got <<< %s' % (expected_result, result)
>>> test_result
True
"""
def __init__(self, template, default_substitution_value = None):
string.Template(template)
self.default_substitution_value = default_substitution_value
self.template = template
def safe_substitute(self, *args, **kws):
'''
Returns template with placeholders substituted
if no substitution value is specified, the
default_substitution_value
is used
'''
if len(args) > 1:
raise TypeError('Too many positional arguments')
if not args:
mapping = kws
elif kws:
mapping = _multimap(kws, args[0])
else:
mapping = args[0]
# Helper function for .sub()
def convert(mo):
named = mo.group('named')
if named is not None:
try:
# We use this idiom instead of str() because the latter
# will fail if val is a Unicode containing non-ASCII
return '%s' % (mapping[named],)
except KeyError:
if self.default_substitution_value:
return self.default_substitution_value
return self.delimiter + named
braced = mo.group('braced')
if braced is not None:
try:
return '%s' % (mapping[braced],)
except KeyError:
return self.delimiter + '{' + braced + '}'
if mo.group('escaped') is not None:
return self.delimiter
if mo.group('invalid') is not None:
return self.delimiter
raise ValueError('Unrecognized named group in pattern',
self.pattern)
return self.pattern.sub(convert, self.template)
I built the above because I was building an interface to populate P2 Energy's Field Operations equipment readings. The application sends data via xml files and there are many attributes in the file that I did not care to populate. Rather than hand code a default value for each of them, I built the above python class.