I found myself needing to format part of a string template recently.  So of course I landed on Stack Overflow.  After some digging I found a tiny hack that uses _vformat to partially update a string template.  With a couple of adjustments for recursion depth in cases where curly braces are several layers deep, handling of string conversion and spec (i.e. !r or !s and <30), and finally object attribute handling I ended up with the resulting function.

#!/usr/bin/env python3

import string
from _string import formatter_field_name_split

################################################################################
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(object):
        def __init__(self, key):
            self.key = key
        
        def __format__(self, spec):
            result = self.key
            if spec:
                result += ":" + spec
            return "{" + result + "}"
        
        def __getitem__(self, item):
            return
    
    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 = PartialFormatter()
    try:
        fs, _ = fmttr._vformat(s, ("{}",), FormatDict(**kwargs), set(), recursionlimit)
    except Exception as exc:
        raise exc
    return fs

Putting the code to use, we can see the recursion and replacements are working.

### EXAMPLES
class ColorObj(object):
    """sample of ansi color adjustment"""
    blue = "^BLUE^"
    red = "^RED^"
    yellow = "^YELLOW^"
    reset = "^RESET^"

s = '{"a": {"b": {"c": {"d" : {} {foo:<12} & {foo!r} {arg} {color.blue:<10} {color.pink} {blah.atr} }}}}'
print(s)
print(partialformat(s, foo="Fooolery", arg="ARRrrrrrg!", color=ColorObj))
print(partialformat(s, arg="ARRrrrrrg!", color=ColorObj))
print(partialformat(s, foo="Fooolery", color=ColorObj))
print(partialformat(s, foo="Fooolery", arg="ARRrrrrrg!"))
print(partialformat(s, foo="Fooolery"))
print(partialformat(s, arg="ARRrrrrrg!"))
foo = True

The resulting output:

{"a": {"b": {"c": {"d" : {} {foo:&lt;12} &amp; {foo!r} {arg} {color.blue:&lt;10} {color.pink} {blah.atr} }}}}
{"a": {"b": {"c": {"d" : {} Fooolery &amp; 'Fooolery' ARRrrrrrg! ^BLUE^ {color.pink} {blah.atr} }}}}
{"a": {"b": {"c": {"d" : {} {foo:&lt;12} &amp; &lt;__main__.partialformat.&lt;locals&gt;.FormatPlaceholder object at 0x10cc477f0&gt; ARRrrrrrg! ^BLUE^ {color.pink} {blah.atr} }}}}
{"a": {"b": {"c": {"d" : {} Fooolery &amp; 'Fooolery' {arg} ^BLUE^ {color.pink} {blah.atr} }}}}
{"a": {"b": {"c": {"d" : {} Fooolery &amp; 'Fooolery' ARRrrrrrg! {color.blue} {color.pink} {blah.atr} }}}}
{"a": {"b": {"c": {"d" : {} Fooolery &amp; 'Fooolery' {arg} {color.blue} {color.pink} {blah.atr} }}}}
{"a": {"b": {"c": {"d" : {} {foo:&lt;12} &amp; &lt;__main__.partialformat.&lt;locals&gt;.FormatPlaceholder object at 0x10cc47e10&gt; ARRrrrrrg! {color.blue} {color.pink} {blah.atr} }}}}