jimbaker / tagstr

This repo contains an issue tracker, examples, and early work related to PEP 999: Tag Strings
51 stars 6 forks source link

Using templates defined in another scope #2

Closed jimbaker closed 1 year ago

jimbaker commented 2 years ago

Following up on https://peps.python.org/pep-0501/#proposal, let's demonstrate how we can create an interpolation template that can be used in another scope. We will use a helper function, template, that itself can be used as a tag:

from taglib import template

things_template = template"{num} things"

Like all tag functions, template takes Sequence[str | Thunk], but it's actually just an identity function. Let's also assume tag functions take an optional **kwargs:

def template(items: Sequence[str | Thunk], /, **kwargs) -> Sequence[str | Thunk]:
     return items

where Thunk is defined like so:

class Thunk(Protocol):
    expr: Callable  # wrapped lambda
    expr_text: str  # Python code for the expr
    format_spec: str

With this in place, we can have the following two be equivalent, assuming i18n_tag takes keyword args for variables that are not defined, in this case for num:

from i18nlib import i18n_tag as _

num = 47
print(_"{num} things")

and

from i18nlib import i18n_tag as _
from mytemplates import things_template

print(_(things_template, num=47))

Implementing this simply requires in looping over the sequence of str | Thunk, we simply do

eval(expr.__code__, {'num': 47})

so it gets num available in the namespace.

Lastly, in making the tags just be be callables, we get some very nice syntax out of it, in terms of identity for creating explicit templates, as well as being able to directly sub in the variables with a near equivalent form.

gvanrossum commented 2 years ago

Bikeshed: maybe the signature of template can be (*items: str | Thunk)? (Or if you really want keyword args, (*items: str | Thunk, **kwargs)?)

I'd also like to bikeshed the field names in Thunk, but later.

jimbaker commented 2 years ago

I rather like the bikeshed on the signature - basically with tag strings, we are putting in place a new syntax for applying arguments to a function. So f(*args, **kwargs) is that most general form.

gvanrossum commented 2 years ago

Concretely, tag "foo{bar}baz" would end up calling tag("foo", bar, "baz").

What use do you have for **kwargs?

gvanrossum commented 2 years ago

@jimbaker This example in your initial comment looks wrong:

from i18nlib import i18n_tag as _
from mytemplates import things_template

print(_(things_template, num=47))

The last line (you say) is supposed to be equivalent to

num = 47
print(_"{num} things")

but the expanded example seems to be missing the "{num} things" text.

gvanrossum commented 2 years ago

FWIW I finally get this, things_template is the template created from the "{num} things" string using the template() function. Jim is demonstrating how the template can be in a different module.

jimbaker commented 1 year ago

Example is written.