Skip to content

Instantly share code, notes, and snippets.

@akatrevorjay
Last active October 12, 2017 15:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akatrevorjay/91d81696ba86b2ca990a46d7deaefdc3 to your computer and use it in GitHub Desktop.
Save akatrevorjay/91d81696ba86b2ca990a46d7deaefdc3 to your computer and use it in GitHub Desktop.
Format each string value of dictionary using values contained within itself, keeping track of dependencies as required.
import sys
import re
def format_dict_recursively(mapping, raise_unresolvable=True, strip_unresolvable=False, conversions={'True': True, 'False': False}):
"""Format each string value of dictionary using values contained within
itself, keeping track of dependencies as required.
Also converts any formatted values according to conversions dict.
Example:
>>> c = dict(wat='wat{omg}', omg=True)
>>> format_dict_recursively(c)
{'omg': True, 'wat': 'watTrue'}
Dealing with missing (unresolvable) keys in format strings:
>>> c = dict(wat='wat{omg}', omg=True, fail='no{whale}')
>>> format_dict_recursively(c)
Traceback (most recent call last):
...
ValueError: Impossible to format dict due to missing elements: {'fail': ['whale']}
>>> format_dict_recursively(c, raise_unresolvable=False)
{'fail': 'no{whale}', 'omg': True, 'wat': 'watTrue'}
>>> format_dict_recursively(c, raise_unresolvable=False, strip_unresolvable=True)
{'omg': True, 'wat': 'watTrue'}
:param dict mapping: Dict.
:param bool raise_unresolvable: Upon True, raises ValueError upon an unresolvable key.
:param bool strip_unresolvable: Upon True, strips unresolvable keys.
:param dict conversions: Mapping of {from: to}.
"""
if conversions is None:
conversions = {}
ret = {}
# Create dependency mapping
deps = {}
for k, v in mapping.items():
# Do not include multiline values in this to avoid KeyErrors on actual
# .format below
if isinstance(v, six.string_types) and '\n' not in v:
# Map key -> [*deps]
# This is a bit naive, but it works well.
deps[k] = re.findall(r'\{(\w+)\}', v)
else:
ret[k] = v
while len(ret) != len(mapping):
ret_key_count_at_start = len(ret)
sret = set(ret)
keys = set(mapping) - sret
for k in keys:
needed = (x not in ret for x in deps[k])
if any(needed):
continue
ret[k] = mapping[k].format(**ret)
if ret[k] in conversions:
ret[k] = conversions[ret[k]]
# We have done all that we can here.
if ret_key_count_at_start == len(ret):
if not raise_unresolvable:
if not strip_unresolvable:
# backfill
ret.update({k: mapping[k] for k in keys})
break
missing = {k: [x for x in deps[k] if x not in ret]}
raise ValueError('Impossible to format dict due to missing elements: %r' % missing)
return ret
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment