Skip to content

Commit 9d212bb

Browse files
committed
issue2551026: template variable not defined even though it is.
Fix issue where variables defined in TAL expression are not available in the scope of the definition. (Tom Ekberg (tekberg))
1 parent 73ae5a5 commit 9d212bb

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ Fixed:
109109
found/404. Reported by hannob at
110110
https://github.com/python/bugs.python.org/issues/34, issue opened by
111111
JulienPalard.
112+
- issue2551026: template variable not defined even though it is.
113+
Fix issue where variables defined in TAL expression are not
114+
available in the scope of the definition. (Tom Ekberg (tekberg))
112115

113116
2018-07-13 1.6.0
114117

roundup/cgi/PageTemplates/PythonExpr.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
"""Generic Python Expression Handler
1818
"""
1919

20+
import symtable
21+
2022
from .TALES import CompilerError
2123
from sys import exc_info
2224

@@ -31,7 +33,8 @@ def __init__(self, name, expr, engine):
3133
self.expr = expr = expr.strip().replace('\n', ' ')
3234
try:
3335
d = {}
34-
exec('def f():\n return %s\n' % expr.strip(), d)
36+
self.f_code = 'def f():\n return %s\n' % expr.strip()
37+
exec(self.f_code, d)
3538
self._f = d['f']
3639
except:
3740
raise CompilerError(('Python expression error:\n'
@@ -40,17 +43,38 @@ def __init__(self, name, expr, engine):
4043

4144
def _get_used_names(self):
4245
self._f_varnames = vnames = []
43-
for vname in self._f.__code__.co_names:
44-
if vname[0] not in '$_':
46+
for vname in self._get_from_symtab():
47+
if vname[0] not in '$_.':
4548
vnames.append(vname)
4649

50+
def _get_from_symtab(self):
51+
"""
52+
Get the variables used in the 'f' function.
53+
"""
54+
variables = set()
55+
table = symtable.symtable(self.f_code, "<string>", "exec")
56+
if table.has_children():
57+
variables.update(self._walk_children(table))
58+
return variables
59+
60+
def _walk_children(self, sym):
61+
"""
62+
Get the variables at this level. Recurse to get them all.
63+
"""
64+
variables = set()
65+
for child in sym.get_children():
66+
variables.update(set(child.get_identifiers()))
67+
if child.has_children():
68+
variables.update(self._walk_children(child))
69+
return variables
70+
4771
def _bind_used_names(self, econtext, _marker=[]):
4872
# Bind template variables
4973
names = {'CONTEXTS': econtext.contexts}
50-
vars = econtext.vars
74+
variables = econtext.vars
5175
getType = econtext.getCompiler().getTypes().get
5276
for vname in self._f_varnames:
53-
val = vars.get(vname, _marker)
77+
val = variables.get(vname, _marker)
5478
if val is _marker:
5579
has = val = getType(vname)
5680
if has:

test/test_pythonexpr.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""
2+
In Python 3, sometimes TAL "python:" expressions that refer to
3+
variables but not all variables are recognized. That is in Python 2.7
4+
all variables used in a TAL "python:" expression are recognized as
5+
references. In Python 3.5 (perhaps earlier), some TAL "python:"
6+
expressions refer to variables but the reference generates an error
7+
like this:
8+
9+
<class 'NameError'>: name 'some_tal_variable' is not defined
10+
11+
even when the variable is defined. Output after this message lists the
12+
variable and its value.
13+
"""
14+
15+
import unittest
16+
17+
from roundup.cgi.PageTemplates.PythonExpr import PythonExpr as PythonExprClass
18+
19+
class ExprTest(unittest.TestCase):
20+
def testExpr(self):
21+
expr = '[x for x in context.assignedto ' \
22+
'if x.realname not in user_realnames]'
23+
pe = PythonExprClass('test', expr, None)
24+
# Looking at the expression, only context and user_realnames are
25+
# external variables. The names assignedto and realname are members,
26+
# and x is local.
27+
required_names = ['context', 'user_realnames']
28+
got_names = pe._f_varnames
29+
for required_name in required_names:
30+
self.assertIn(required_name, got_names)

0 commit comments

Comments
 (0)