pauleveritt / fdom

Template engine based on tag strings and functional ideas.
0 stars 0 forks source link

Cell variables from loop #21

Open jimbaker opened 11 months ago

jimbaker commented 11 months ago

Given that all interpolations are lambda wrapped in tag strings, this common issue seen in working with closed over variables - as implemented with cell vars - might be considered to be a potential issue in using tag strings:

https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/cell-var-from-loop.html

It was also recently discussed in this YouTube segment, which popped up as a suggested video for me: https://www.youtube.com/watch?v=fZE6ZWde-Os

Note that a tag function implementation controls the order of evaluation. In most cases, upon the evaluation of a tag string, all interpolations are evaluated with getvalue (or not). From outside this function call, it's no different than standard evaluation, and the ordering of evaluation inside the function call is not visible (unless we are working with code that is doing monkeypatching or is threaded, but no different again).

However, it is possible for a tag string to defer evaluation, as seen in the fl strings example. But in this particular case, the use of the specific logger method (.info, etc) ensures that the evaluation is fully done by that calling method.

Are there other cases to consider?

This consideration should discussed in the PEP.

pauleveritt commented 11 months ago

I'll file this under "things I would have never thought of." 😉

I don't believe this next point would affect this, but I'll describe anyway, just in case. (And also, I wanted to bring it up.) Other template languages have filters. E.g. {{ items.getBalance() | safe }}. Two operations in one interpolation.

Not a big deal per se for us, as Python 3.12 and PEP 701 have Python expressions. We don't have to wire in any special parsing as the filter is just a normal Python expression. Except...I don't think that's a valid expression, nor do I think Python has a filter-like syntax. OTOH, I think you've thought about this kind of piping.

Coming back to your point about looping, just want to ensure if you do have some kind of multi-stage "fluent" expressions in the loop, that it doesn't have a consequence.

jimbaker commented 11 months ago

First, this does work with f-strings, and by extension tag strings:

from typing import Any
from html import escape

class Safe:
    def __ror__(self, __value: Any) -> str:
        return escape(str(__value))

safe = Safe()

class Account:
    def __init__(self, amount):
        self.amount = amount

    def __str__(self):
        return f'${self.amount}'

    def get_balance(self):
        return self

def test_pipe():
    account = Account('<bad>data</bad>')
    assert f'Your balance is {account.get_balance() | safe}' == \
        'Your balance is $&lt;bad&gt;data&lt;/bad&gt;'

Secondly, fluent expressions like that don't have any special impact here. The only possibility is if the tag doesn't fully evaluate its args before returning; and those args depend on cell vars, which are modified by a loop.

So it takes a special tag function to do this (like the fl example), in a specific context - running in a loop, but not actually evaluating it until later. But in the case of logging this occurs immediately. A sequence diagram would be helpful here. I will put something together.

jimbaker commented 11 months ago

Also if you want safe to work as a function call, simple add __call__ to the Safe class.

jimbaker commented 11 months ago

I added a test for using a pipe for filters: https://github.com/pauleveritt/fdom/blob/main/tests/test_pipe.py