Edgewall Software

Opened 4 years ago

Closed 4 years ago

Last modified 4 years ago

#13244 closed defect (fixed)

Passing Jinja2 context to callable instance as first argument when the callable instance has __getattr__ method since Jinja2 2.11.0 — at Version 6

Reported by: Jun Omae Owned by:
Priority: normal Milestone: 1.4.1
Component: rendering Version:
Severity: normal Keywords: jinja2
Cc: Branch:
Release Notes:

Avoid passing Jinja2 context to Href.__call__ to fix wrong link generated from Jinja2 template since Jinja2 2.11.0.

API Changes:
Internal Changes:

Description (last modified by Jun Omae)

From comment:78:ticket:12130

The callable instance is Href instance.

See also

Jinja2 2.11.0

>>> import jinja2, sys
>>> jinja2.__version__
'2.11.0'
>>> from jinja2 import Template
>>> from trac.web.href import Href
>>> href = Href('/trac.cgi')
>>> Template('{{ href() }}').render(href=href)
"/trac.cgi/%3CContext%20%7B'range'%3A%20%3Cclass%20'range'%3E%2C%20'dict'%3A%20%3Cclass%20'dict'%3E%2C%20'lipsum'%3A%20%3Cfunction%20generate_lorem_ipsum%20at%200x7f0386031310%3E%2C%20'cycler'%3A%20%3Cclass%20'jinja2.utils.Cycler'%3E%2C%20'joiner'%3A%20%3Cclass%20'jinja2.utils.Joiner'%3E%2C%20'namespace'%3A%20%3Cclass%20'jinja2.utils.Namespace'%3E%2C%20'href'%3A%20%3CHref%20'/trac.cgi'%3E%7D%20of%20None%3E"
>>> class Foo(object):
...     def __getattr__(self, name):
...         sys.stderr.write('__getattr__({!r})\n'.format(name))
...         return self
...     def __call__(self, *args, **kwargs):
...         sys.stderr.write('__call__(args={!r}, kwargs={!r})\n'.format(args, kwargs))
...
>>> Template('{{ val() }}').render(val=Foo())
__getattr__('contextfunction')
__call__(args=(<Context {'range': <class 'range'>, 'dict': <class 'dict'>, 'lipsum': <function generate_lorem_ipsum at 0x7f0386031310>, 'cycler': <class 'jinja2.utils.Cycler'>, 'joiner': <class 'jinja2.utils.Joiner'>, 'namespace': <class 'jinja2.utils.Namespace'>, 'val': <__main__.Foo object at 0x7f038a15d160>} of None>,), kwargs={})
'None'

Jinja2 2.10.3

>>> import jinja2, sys
>>> jinja2.__version__
'2.10.3'
>>> from jinja2 import Template
>>> from trac.web.href import Href
>>> href = Href('/trac.cgi')
>>> Template('{{ href() }}').render(href=href)
'/trac.cgi'
>>> class Foo(object):
...     def __getattr__(self, name):
...         sys.stderr.write('__getattr__({!r})\n'.format(name))
...         return self
...     def __call__(self, *args, **kwargs):
...         sys.stderr.write('__call__(args={!r}, kwargs={!r})\n'.format(args, kwargs))
...
>>> Template('{{ val() }}').render(val=Foo())
__call__(args=(), kwargs={})
'None'

Change History (6)

comment:1 by Jun Omae, 4 years ago

Ad hoc patch:

  • trac/web/href.py

    diff --git a/trac/web/href.py b/trac/web/href.py
    index 1d2336952..170e0e3bd 100644
    a b class Href(object):  
    188188        return href
    189189
    190190    def __getattr__(self, name):
     191        if name in ('contextfunction', 'evalcontextfunction',
     192                    'environmentfunction'):
     193            raise AttributeError
    191194        if name not in self._derived:
    192195            self._derived[name] = lambda *args, **kw: self(name, *args, **kw)
    193196        return self._derived[name]

comment:2 by Jun Omae, 4 years ago

Description: modified (diff)

comment:3 by Ryan J Ollos, 4 years ago

Looks similar to Jinja2 issue #1139. Relevant change is 70ea1d3e.

It wouldn't be an issue if Jinja2 tested the value: getattr(__obj, "contextfunction", 0) is True rather than just getattr(__obj, "contextfunction", 0).

Last edited 4 years ago by Ryan J Ollos (previous) (diff)

comment:4 by Jun Omae, 4 years ago

Proposed changes in [332bf1f51/jomae.git].

comment:5 by Ryan J Ollos, 4 years ago

Looks good. Nice solution!

comment:6 by Jun Omae, 4 years ago

Release Notes: modified (diff)
Resolution: fixed
Status: newclosed

Thanks for the review! Committed in [17238] and merged in [17239].

Note: See TracTickets for help on using tickets.