PEP 750: joining template sequences?

Hello,

I have recently started to look into using template strings in psycopg and it seems promising. There is an open conversation in discussion 1044.

Something that would come handy is passing sequence of identifiers to a query. For example, if you have:

fields = ["foo", "bar", "baz-qux"]

and you want to select these fields from a table safely (both thinking about malicious input and thinking about fields names needing escape) you currently need:

>>> fields_list = [sql.Identifier(field) for field in fields]
>>> query = sql.SQL("SELECT {fields} FROM t").format(fields=sql.SQL(", ").join(fields_list))
>>> print(query.as_string())
SELECT "foo", "bar", "baz-qux" FROM t

We are considering to allow nested templates and an i format string to represent an identifier. The above could be simplified as:

>>> fields_list = [t"{field:i}" for field in fields]
>>> query = t"SELECT {", ".join(fields_list)} FROM t"  # I wish

How easy would be to join a sequence of templates? By looking at the PEP 750, Template seems to support +, but "".join() I assume would return a string? Or it wouldn’t and the virality of + would ensure a Template result? Template.join() doesn’t seem implemented but it seems a natural extension to Template.__add__.

1 Like

I think sql.SQL should detect and deal with sequences of templates. So the user can just write t"SELECT {fields_list} FROM t".

No need to use join or +.

1 Like

I agree detecting sequences of templates is likely the best approach.

If you still want to approximate join(), you could try:

>>> fields = ["foo", "bar", "baz-qux"]
>>> fields_list = [t"{field:i}" for field in fields]
>>> joined_fields = [x for f in fields_list for x in (*f, ', ')][:-1]
>>> query = t"SELECT {Template(*joined_fields)} FROM t"
>>> for x in query: print(repr(x))
'SELECT '
Interpolation(Template(strings=('', ', ', ', ', ''), interpolations=(Interpolation('foo', 'field', None, 'i'), Interpolation('bar', 'field', None, 'i'), Interpolation('baz-qux', 'field', None, 'i'))), 'Template(*joined_fields)', None, '')
' FROM t'

I think this is worse than the sequence alternative, though:

>>> query = t"SELECT {fields:i} FROM t"
>>> for x in query: print(repr(x))
'SELECT '
Interpolation(['foo', 'bar', 'baz-qux'], 'fields', None, 'i')
' FROM t'

A

You often will want separators: " AND ".join(filters) or ", ".join(fields), you won’t have just a sequence of templates.

>>> query = t"SELECT {fields:AND} FROM t"
>>> query = t"SELECT {fields:,} FROM t"

There is no rule that the format specifier has to behave like str.format. It can, and should, be used for additional information like this.

(Alternatively, and perhaps better, provide a callable like all_AND(fields) that can be substituted. It really depends on how frequently this is going to be needed.)

(Process note aside: the PEPs category is for posting PEPs, not discussing them after they’re complete. Use Core Development, Help or Ideas once a PEP has been accepted.)

1 Like