Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Safe string templates for nested dict object

I have a complex nested dict object, e.g:

value = { 
    'a': '100', 
    bits: {
        1: 'alpha', 
        2: 'beta', 
        3: ['31', '32', 901]
    }
}

I need to 'safely' format it using a template. Meaning if the keys are not found, just silently ignore the {} place-holders. The Keys might not exist, and I do not want to raise KeyErrors. The problem is that string.Template cannot handle the same functionality that str.format does. The str.format I used is something like:

"a=${a}, b1={bits[1]}, b31={bits[3]}, b9={bits[9]}".format(**value)

and the output should be:

"a=100, b1=alpha, b31=(31, 32, 901), b9="

I do not need fancy loops or if/else conditions. Just simple formats with sub dicts.

What are the options I have? I prefer to use built-ins as much as possible or a very small library.

This is not a web app, so no if possible I want to avoid loading a lib like jinja2 just for this.

like image 509
Ayman Avatar asked Oct 22 '22 10:10

Ayman


2 Answers

Write you own formatter:

In [1]: from string import Formatter

In [2]: value = { 
   ...:     'a': '100', 
   ...:     'bits': {
   ...:         1: 'alpha', 
   ...:         2: 'beta', 
   ...:         3: ['31', '32', 901]}}

In [3]: class YourFormatter(Formatter):
   ...:     def get_value(self, field_name, args, kwargs):
   ...:         return kwargs.get(field_name, '')
   ...: 
   ...:     def get_field(self, field_name, args, kwargs):
   ...:         first, rest = field_name._formatter_field_name_split() 
   ...:         obj = self.get_value(first, args, kwargs) 
   ...:         
   ...:         for is_attr, i in rest:
   ...:             if is_attr:
   ...:                 obj = getattr(obj, i)
   ...:             else:
   ...:                 obj = obj.get(i, '')
   ...:         return obj, first
   ...:     


In [4]: fmt = YourFormatter()

In [5]: fmt.format("a={a}, b1={bits[1]}, b31={bits[3]}, b9={bits[9]}", **value)
Out[5]: "a=100, b1=alpha, b31=['31', '32', 901], b9="

for Python 3, you need to add

import _string

and replace the line

first, rest = field_name._formatter_field_name_split() 

with

first, rest = _string.formatter_field_name_split(field_name) 
like image 85
root Avatar answered Oct 24 '22 11:10

root


The only way to do this is to write a wrapper class that implements the dict and sequence protocols, wrapping any list or dict return values in the same class, and catch any KeyError or IndexError exceptions.

Then your call becomes "…".format(**DefaultingWrapper(value)).

like image 20
pyroscope Avatar answered Oct 24 '22 09:10

pyroscope