Based on some of the other answers, I expanded the solutions.
This will handle strings with formatting spec "{a:<10}"
.
I found that some strings from selenium logging were causing vformat (and format_map) to hit a recursion limit. I also wanted to ensure I could handle strings where empty curly braces exist as well.
def partialformat(s: str, recursionlimit: int = 10, **kwargs):
"""
vformat does the acutal work of formatting strings. _vformat is the
internal call to vformat and has the ability to alter the recursion
limit of how many embedded curly braces to handle. But for some reason
vformat does not. vformat also sets the limit to 2!
The 2nd argument of _vformat 'args' allows us to pass in a string which
contains an empty curly brace set and ignore them.
"""
class FormatPlaceholder:
def __init__(self, key):
self.key = key
def __format__(self, spec):
result = self.key
if spec:
result += ":" + spec
return "{" + result + "}"
class FormatDict(dict):
def __missing__(self, key):
return FormatPlaceholder(key)
class PartialFormatter(string.Formatter):
def get_field(self, field_name, args, kwargs):
try:
obj, first = super(PartialFormatter, self).get_field(field_name, args, kwargs)
except (IndexError, KeyError, AttributeError):
first, rest = formatter_field_name_split(field_name)
obj = '{' + field_name + '}'
# loop through the rest of the field_name, doing
# getattr or getitem as needed
for is_attr, i in rest:
if is_attr:
try:
obj = getattr(obj, i)
except AttributeError as exc:
pass
else:
obj = obj[i]
return obj, first
fmttr = string.Formatter()
fs, _ = fmttr._vformat(s, ("{}",), FormatDict(**kwargs), set(), recursionlimit)
return fs
class ColorObj(object):
blue = "^BLUE^"
s = '{"a": {"b": {"c": {"d" : {} {foo:<12} & {foo!r} {arg} {color.blue:<10} {color.pink} {blah.atr} }}}}'
print(partialformat(s, foo="Fooolery", arg="ARRrrrrrg!", color=ColorObj))
output:
{"a": {"b": {"c": {"d" : {} Fooolery & 'Fooolery' Fooolery ARRrrrrrg! ^BLUE^ {color.pink} {blah.atr} }}}}