pystache icon indicating copy to clipboard operation
pystache copied to clipboard

Renderer.context won't update when looping through an array

Open crusaderky opened this issue 11 years ago • 4 comments

My target: print the following lines:

Al,John,Jack
Tim,Tom,Todd

without a final comma.

I tried this way:

ctx = {
    'gangs': [
        {'gangsters': [ {'name': 'Al' }, {'name': 'John'}, {'name': 'Jack'}]},
        {'gangsters': [ {'name': 'Tim'}, {'name': 'Tom'} , {'name': 'Todd'}]},
    ]
}

class Lambdas(object):
    def __init__(self, renderer):
        self.renderer = renderer

    def rstrip(self):
        "Remove last character"
        print self.renderer.context
        return lambda s: self.renderer.render(s, self.renderer.context)[:-1]

renderer = pystache.Renderer(missing_tags='strict')

print renderer.render("""
    {{#gangs}}
        {{#rstrip}}{{#gangsters}}{{name}},{{/gangsters}}{{/rstrip}}
    {{/gangs}}
""", ctx, Lambdas(renderer))

The output:

ContextStack({'gangs': [{'gangsters': [{'name': 'Al'}, {'name': 'John'}, {'name': 'Jack'}]}, {'gangsters': [{'name': 'Tim'}, {'name': 'Tom'}, {'name': 'Todd'}]}]}, <__main__.Lambdas object at 0x15cadb10>, {'gangsters': [{'name': 'Al'}, {'name': 'John'}, {'name': 'Jack'}]})
ContextStack({'gangs': [{'gangsters': [{'name': 'Al'}, {'name': 'John'}, {'name': 'Jack'}]}, {'gangsters': [{'name': 'Tim'}, {'name': 'Tom'}, {'name': 'Todd'}]}]}, <__main__.Lambdas object at 0x15cadb10>, {'gangsters': [{'name': 'Al'}, {'name': 'John'}, {'name': 'Jack'}]})

Al,John,Jack
Al,John,Jack

The culprit is the invocation to render() inside rstrip. Notice how, during the second call, the 3d element of the ContextStack is exactly identical to the previous call, instead of changing to {'gangsters': [ {'name': 'Tim'}, {'name': 'Tom'} , {'name': 'Todd'}]}.

Is this a bug, or am I missing something?!?

crusaderky avatar Dec 18 '13 09:12 crusaderky

Rewriting it as dicts won't work either:

ctx = {
    'gang1': { 'gangsters': [ {'name': 'Al' }, {'name': 'John'}, {'name': 'Jack'}] },
    'gang2': { 'gangsters': [ {'name': 'Tim'}, {'name': 'Tom'} , {'name': 'Todd'}] }
}

print renderer.render("""
    {{#gang1}}
        {{#rstrip}}{{#gangsters}}{{name}},{{/gangsters}}{{/rstrip}}
    {{/gang1}}
    {{#gang2}}
        {{#rstrip}}{{#gangsters}}{{name}},{{/gangsters}}{{/rstrip}}
    {{/gang2}}
""", ctx, Lambdas(renderer))

crusaderky avatar Dec 18 '13 09:12 crusaderky

Yes, it looks like there may be an issue. I will try to track this down.

cjerdonek avatar Dec 18 '13 20:12 cjerdonek

It looks like the issue is that calling self.renderer.render() inside the lambda has the side effect of overwriting the renderer's original context property with a new context stack. Later calls to self.renderer.context then no longer reference the original context which has the right gang.

This is related to this TODO.

One work-around is to avoid changing the original renderer's context by creating a new renderer each time:

def rstrip(self):
    "Remove last character"
    print self.renderer.context
    renderer = pystache.Renderer(missing_tags='strict')
    return lambda s: renderer.render(s, self.renderer.context)[:-1]

Another work-around is to make sure you're always referencing the original context:

class Lambdas(object):
    def __init__(self, renderer):
        self.renderer = renderer
        self.context = None

    def rstrip(self):
        "Remove last character"
        if self.context is None:
            self.context = self.renderer.context
        print self.renderer.context
        return lambda s: self.renderer.render(s, self.context)[:-1]

I think issue #157 is related and may have other suggestions.

cjerdonek avatar Dec 18 '13 21:12 cjerdonek

Thanks, this works fine:

def rstrip(self):
    "Remove last character"
    return lambda s: copy.deepcopy(self.renderer).render(s, self.renderer.context)[:-1]

crusaderky avatar Dec 19 '13 10:12 crusaderky