-2

Consider the following code

from string import Template
s = Template('$a $b')

print(s.safe_substitute(a='hello'))  # $b is not substituted
print(s.substitute(a='hello'))  # raise error

What I want is that I can specified a default value for all the unspecified variables in the template, for example, an empty string ''.

The reason I need this feature is there is a case that both template and variables are provided by users. They may provided some placeholders in the template string as an injection point in case someone else may need to use them, but if the user don't provide value for it those placeholders should be ignore. And I don't need to specify different default values for different variables, but just a common default value for all missing variables.

I don't have to stick to the string template module, but I don't want to use heavy solution like jinja2 either. I just want to have a lightweight template soltuion to get things done.

The method to provide a default dictionary doesn't work in my case as the template is provided by other users, so I don't know the possbile vaiables before hand.

link89
  • 1,064
  • 10
  • 13

2 Answers2

3

As stated in documentation, safe_substitute and substitute accept values in 2 forms - as keyword arguments, as you are doing, or as a mapping dict. It also assures us that "When both mapping and kwds are given and there are duplicates, the placeholders from kwds take precedence". With this in mind, we can use this mapping dict to provide default values for all variables, and ones we pass explicitly as keyword will get overwritten with new values.

from string import Template
s = Template('$a $b')


defaults = {'a': 'a_default', 'b': 'b_default'}

print(s.safe_substitute(defaults, a='hello'))  # hello b_default
print(s.substitute(defaults, a='hello'))  # hello b_default

If all the identifiers should use same default value, you can use a defaultdict with the same method.

from string import Template
from collections import defaultdict
s = Template('$a $b')

defaults = defaultdict(lambda: '')

print(s.safe_substitute(defaults, a='hello'))  # $b is not substituted
print(s.substitute(defaults, a='hello'))  # raise error
matszwecja
  • 6,357
  • 2
  • 10
  • 17
  • Your methods works only when you know all the params before hands. But I my case the template is provided by user, so unless you have another method to get all the params in the template file, or else this method won't work. – link89 Aug 01 '23 at 07:45
  • You don't need to know all the params. Just make a dictionary from whatever user passes in. – matszwecja Aug 01 '23 at 07:46
  • 1
    @matszwecja But you need to know what to put in `defaults`. – Barmar Aug 01 '23 at 07:51
  • @Barmar Yes, but according to question values for those identifiers are also provided with the template, so they need to be parsed out somehow and put into the dict. – matszwecja Aug 01 '23 at 07:58
  • The user supplies the template string and some of the values. They don't supply the list of all the variables. So you need to get the defaults for the rest from the template string. – Barmar Aug 01 '23 at 08:00
  • 1
    Clever solution. – Barmar Aug 01 '23 at 08:09
  • I have never use defaultdict before, that seems to be a useful tech in many places. Thank you @matszwecja – link89 Aug 01 '23 at 08:11
2

Use get_identifiers() to get all the variables in the template. Then if any variables are missing in the user-supplied data, you can fill in the default values from this.

def substitute_with_default(template: Template, values: dict, default: str = '') -> str:
    defaults = dict.fromkeys(template.get_identifiers(), default)
    return template.substitute(defaults | values)

print(substitute_with_default(Template('$a $b'), {'a': 'hello'}))

This method requires Python 3.11.

Barmar
  • 741,623
  • 53
  • 500
  • 612