# PEP 750: Tag Strings For Writing Domain-Specific Languages

**URL:** <https://discuss.python.org/t/pep-750-tag-strings-for-writing-domain-specific-languages/60408>\
**Category:** PEPs\
**Created:** [August 9, 2024, 3:40pm UTC](https://discuss.python.org/t/pep-750-tag-strings-for-writing-domain-specific-languages/60408 "2024-08-09T15:40:07Z")\
**Posts on this page:** 1\
**Showing post:** 224

<div class="post-metadata">

**Author:** ![ncoghlan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ncoghlan/32/14266_2.png) [@ncoghlan](https://discuss.python.org/u/ncoghlan)\
**Post date:** [October 21, 2024, 4:03am UTC](https://discuss.python.org/t/pep-750-tag-strings-for-writing-domain-specific-languages/60408/224 "2024-10-21T04:03:49Z")

</div>

When writing [Feature Proposal: Multi-String Replacement Using a Dictionary in the .replace() Method - #10 by blhsing](https://discuss.python.org/t/feature-proposal-multi-string-replacement-using-a-dictionary-in-the-replace-method/68609/10) it occurred to me that it would be interesting if either `string.Formatter` or the literal `Template` type offered a way to dynamically convert runtime format strings to interpolation template instances.

The code to implement a basic version of that with only name lookups (and without dynamic field format definitions) isn’t overly complicated:

```python
def template_from_format_map(
    fmt: LiteralString, values: Mapping[str, Any]
) -> Template:
    parser = string.Formatter()
    segments = []
    for prefix, name, field_fmt, field_conv in parser.parse(fmt):
        segments.append(prefix)
        if name is not None:
            field = Interpolation(values[name], name, field_conv, field_format)
            segments.append(field)
    return Template(*segments)

```

(The format string would be typed as `LiteralString` instead of `str` as a reminder that any static template content should _always_ come from a trusted source, whether that’s an actual literal string, or something that has been explicitly cast to one)

Allowing the same level of dynamic reference flexibility as `str.format` and `str.format_map` is [substantially more complicated](https://github.com/python/cpython/blob/73bd5bd18ff4fc76d635c970e5c4332349bcb024/Lib/string.py#L211).

Adding an actual `string.Formatter.as_template()` method would likely be confusing (given the ambiguity between the new `Template` literal type and `string.Template`), but I think we could unambiguously offer a `string.Formatter.get_segments` method that worked for both the default formatting and any custom subclasses (since it would only be calling subclass APIs that `string.Formatter.vformat` already calls - presumably `_vformat` would be redesigned to call `self.get_segments` instead of calling `self.parse` directly):

```python
def get_segments(self,
    fmt: LiteralString, args: Sequence[Any], values: Mapping[str, Any]
) -> Sequence[LiteralString|Interpolation]:
    segments = []
    for prefix, value_ref, field_fmt, field_conv in self.parse(fmt):
        segments.append(prefix)
        if value_ref is not None:
            # This branch would do everything `_vformat` does, but
            # writing that out would make this example far too long
            value, _lookup_key = self.get_field(value_ref, args, values)           
            field = Interpolation(value, value_ref, field_conv, field_fmt)
            segments.append(field)
    return Template(*segments)

```

That way, any `str.format_map` call could be turned into an interpolation template instance by replacing:

```python
formatted = pattern.format_map(values)

```

with:

```python
segments = string.Formatter().get_segments(pattern, (), values)
template = Template(*segments)

```

Along similar lines, while it definitely doesn’t need to be in the initial design, we may also eventually want to add a `replace_values` method to template instances to make it easier to use statically defined templates for dynamic formatting tasks:

```python
def replace_values(self, values: Sequence[Any]) -> Self:
    value_iter = iter(values)
    def update_segments():
        for segment in self.args:
            match segment:
                case str() as s:
                    updated = s
                case Interpolation(_, _, conv, fmt_spec):
                    value = next(value_iter)
                    updated = Interpolation(value, repr(value), conv, fmt_spec)
            yield updated
    return type(self)(*update_segments())

```

Tangent: `string.Formatter.convert_field()` may be worth mentioning in the PEP as the current dynamic stdlib implementation of the standard conversion specifiers (it’s an instance method rather than a static method, as it’s designed to allow `Formatter` subclasses to override it)

---

_[View the full topic](https://discuss.python.org/t/pep-750-tag-strings-for-writing-domain-specific-languages/60408)._
