diff --git a/.hgtags b/.hgtags index 601279ab960001fc2472af1e3fdc9a7769b91d72_LmhndGFncw==..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_LmhndGFncw== 100644 --- a/.hgtags +++ b/.hgtags @@ -71,3 +71,7 @@ 13cd741f8e144265c15bb225fad863234fc8ce16 rql-debian-version-0.30.1-1 bb70a998ced6a839fd1e28686e4ff5254feefde3 rql-version-0.31.0 f4f27e4c588e40ba9c6c9c5d92cb22f74f1207a6 rql-debian-version-0.31.0-1 +6135951b6c7e64cccfc473c69cd025305b4de7e8 rql-version-0.31.1 +543fe6d74b492f198d8bf7dc5e90ff3e3276fa8b rql-debian-version-0.31.1-1 +55af3a14cc29dedab697deabfc7602e140ac3dd8 rql-version-0.31.2 +513a02cce3c22ea14a13f878ccee21c9540a26da rql-debian-version-0.31.2-1 diff --git a/ChangeLog b/ChangeLog index 601279ab960001fc2472af1e3fdc9a7769b91d72_Q2hhbmdlTG9n..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_Q2hhbmdlTG9n 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,20 @@ ChangeLog for RQL ================= --- +2012-03-29 -- 0.31.2 + * #88559: speed up query solutions analysis + + * moved valuable_references from Variable to Referencable, it makes sense for + ColumnAliases as well + + * various cleanups + + + +2012-02-03 -- 0.31.1 + * #87988: fixed bug in simplify with sub-queries + + + +2011-11-09 -- 0.31.0 * #78681: don't crash on column aliases used in outer join @@ -5,2 +20,3 @@ * #78681: don't crash on column aliases used in outer join + * #81394: HAVING support in write queries (INSERT,SET,DELETE) @@ -6,2 +22,3 @@ * #81394: HAVING support in write queries (INSERT,SET,DELETE) + * #80799: fix wrong type analysis with 'NOT identity' @@ -7,3 +24,4 @@ * #80799: fix wrong type analysis with 'NOT identity' + * when possible, use entity type as translation context of relation (break cw < 3.13.10 compat) @@ -8,4 +26,5 @@ * when possible, use entity type as translation context of relation (break cw < 3.13.10 compat) + * #81817: fix add_type_restriction for cases where some types restriction is already in there @@ -10,3 +29,5 @@ * #81817: fix add_type_restriction for cases where some types restriction is already in there + + 2011-09-07 -- 0.30.1 @@ -12,5 +33,4 @@ 2011-09-07 -- 0.30.1 - * #74727: allow entity types to end with a capitalized letter provided they contain a lower-cased letter diff --git a/__init__.py b/__init__.py index 601279ab960001fc2472af1e3fdc9a7769b91d72_X19pbml0X18ucHk=..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_X19pbml0X18ucHk= 100644 --- a/__init__.py +++ b/__init__.py @@ -136,8 +136,8 @@ def _simplify(self, select): # recurse on subqueries first for subquery in select.with_: - for select in subquery.query.children: - self._simplify(select) + for subselect in subquery.query.children: + self._simplify(subselect) rewritten = False for var in select.defined_vars.values(): stinfo = var.stinfo diff --git a/__pkginfo__.py b/__pkginfo__.py index 601279ab960001fc2472af1e3fdc9a7769b91d72_X19wa2dpbmZvX18ucHk=..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_X19wa2dpbmZvX18ucHk= 100644 --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -1,5 +1,5 @@ # pylint: disable-msg=W0622 -# copyright 2004-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2004-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of rql. @@ -20,7 +20,7 @@ __docformat__ = "restructuredtext en" modname = "rql" -numversion = (0, 31, 0) +numversion = (0, 31, 2) version = '.'.join(str(num) for num in numversion) license = 'LGPL' @@ -83,8 +83,8 @@ 'logilab-common >= 0.47.0', 'logilab-database >= 1.6.0', 'yapps == 2.1.1', # XXX to ensure we don't use the broken pypi version - 'constraint', # fallback if the gecode compiled module is missing + 'constraint >= 0.5.0', # fallback if the gecode compiled module is missing ] # links to download yapps2 package that is not (yet) registered in pypi dependency_links = [ @@ -87,6 +87,6 @@ ] # links to download yapps2 package that is not (yet) registered in pypi dependency_links = [ - "http://ftp.logilab.org/pub/yapps/yapps2-2.1.1.zip#egg=yapps-2.1.1", + "http://download.logilab.org/pub/yapps/yapps2-2.1.1.zip#egg=yapps-2.1.1", ] diff --git a/analyze.py b/analyze.py index 601279ab960001fc2472af1e3fdc9a7769b91d72_YW5hbHl6ZS5weQ==..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_YW5hbHl6ZS5weQ== 100644 --- a/analyze.py +++ b/analyze.py @@ -22,6 +22,7 @@ from cStringIO import StringIO +import os, sys from rql import TypeResolverException, nodes from pprint import pprint @@ -29,6 +30,9 @@ from itertools import izip try: + pure = bool(os.environ.get('RQL_USE_PURE_PYTHON_ANALYSE', 0)) + if pure: + raise ImportError import rql_solve except ImportError: rql_solve = None @@ -316,9 +320,9 @@ def set_schema(self, schema): self.schema = schema # default domains for a variable - self._base_domain = [str(etype) for etype in schema.entities()] - self._nonfinal_domain = [str(etype) for etype in schema.entities() - if not etype.final] + self._base_domain = set(str(etype) for etype in schema.entities()) + self._nonfinal_domain = set(str(etype) for etype in schema.entities() + if not etype.final) def solve(self, node, constraints): # debug info @@ -339,7 +343,11 @@ node.set_possible_types(sols, self.kwargs, self.var_solkey) def _visit(self, node, constraints=None): - """Recurse down the tree.""" + """Recurse down the tree. + + * node: rql node to process + * constraints: a XxxCSPProblem object. + """ func = getattr(self, 'visit_%s' % node.__class__.__name__.lower()) if constraints is None: func(node) @@ -343,10 +351,9 @@ func = getattr(self, 'visit_%s' % node.__class__.__name__.lower()) if constraints is None: func(node) - else: - if func(node, constraints) is None: - for c in node.children: - self._visit(c, constraints) + elif func(node, constraints) is None: + for c in node.children: + self._visit(c, constraints) def _uid_node_types(self, valnode): types = set() @@ -505,10 +512,16 @@ # filter according to domain necessary for column aliases rhsdomain = constraints.domains[rhsvar] res = [] - for fromtype, totypes in rschema.associations(): - if not fromtype in lhsdomain: - continue - ptypes = [str(t) for t in totypes if t in rhsdomain] - res.append( [ ([lhsvar], [str(fromtype)]), - ([rhsvar], ptypes) ] ) + var_types = [] + same_var = (rhsvar == lhsvar) + + for frometype, toetypes in rschema.associations(): + fromtype = str(frometype) + if fromtype in lhsdomain: + totypes = set(str(t) for t in toetypes) + ptypes = totypes & rhsdomain + res.append( [ ([lhsvar], [str(fromtype)]), + ([rhsvar], list(ptypes)) ] ) + if same_var and (fromtype in totypes): #ptypes ? + var_types.append(fromtype) constraints.or_and(res) @@ -514,8 +527,6 @@ constraints.or_and(res) - if rhsvar == lhsvar: - res = [str(fromtype) for fromtype, totypes in rschema.associations() - if (fromtype in totypes and fromtype in lhsdomain)] - constraints.var_has_types( lhsvar, res ) + if same_var: + constraints.var_has_types( lhsvar, var_types) else: # XXX consider rhs.get_type? lhsdomain = constraints.domains[lhs.name] diff --git a/base.py b/base.py index 601279ab960001fc2472af1e3fdc9a7769b91d72_YmFzZS5weQ==..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_YmFzZS5weQ== 100644 --- a/base.py +++ b/base.py @@ -22,4 +22,5 @@ __docformat__ = "restructuredtext en" +from rql.utils import VisitableMixIn @@ -25,5 +26,6 @@ -class BaseNode(object): + +class BaseNode(VisitableMixIn): __slots__ = ('parent',) def __str__(self): diff --git a/debian/changelog b/debian/changelog index 601279ab960001fc2472af1e3fdc9a7769b91d72_ZGViaWFuL2NoYW5nZWxvZw==..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_ZGViaWFuL2NoYW5nZWxvZw== 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +rql (0.31.2-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 29 Mar 2012 13:58:45 +0200 + +rql (0.31.1-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 03 Feb 2012 16:10:56 +0100 + rql (0.31.0-1) unstable; urgency=low * new upstream release diff --git a/debian/control b/debian/control index 601279ab960001fc2472af1e3fdc9a7769b91d72_ZGViaWFuL2NvbnRyb2w=..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_ZGViaWFuL2NvbnRyb2w= 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,12 @@ Priority: optional Maintainer: Logilab Packaging Team <contact@logilab.fr> Uploaders: Sylvain Thenault <sylvain.thenault@logilab.fr>, Nicolas Chauvat <nicolas.chauvat@logilab.fr> -Build-Depends: debhelper (>= 5.0.37.1), python-support, python-all-dev (>=2.5), python-all (>=2.5), libgecode-dev, python-sphinx, g++ (>= 4) +Build-Depends: debhelper (>= 5.0.37.1), + python-support, + python-all-dev (>=2.5), + python-all (>=2.5), + libgecode-dev, + python-sphinx, g++ (>= 4) XS-Python-Version: >= 2.5 Standards-Version: 3.9.1 Homepage: http://www.logilab.org/project/rql @@ -11,7 +16,10 @@ Package: python-rql Architecture: any XB-Python-Version: ${python:Versions} -Depends: ${python:Depends}, ${misc:Depends}, ${shlibs:Depends}, python-logilab-common (>= 0.35.3-1), yapps2-runtime, python-logilab-database (>= 1.6.0) +Depends: ${python:Depends}, ${misc:Depends}, ${shlibs:Depends}, + python-logilab-common (>= 0.35.3-1), + yapps2-runtime, + python-logilab-database (>= 1.6.0) Conflicts: cubicweb-common (<= 3.13.9) Provides: ${python:Provides} Description: relationship query language (RQL) utilities diff --git a/nodes.py b/nodes.py index 601279ab960001fc2472af1e3fdc9a7769b91d72_bm9kZXMucHk=..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_bm9kZXMucHk= 100644 --- a/nodes.py +++ b/nodes.py @@ -1,4 +1,4 @@ -# copyright 2004-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2004-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of rql. @@ -32,8 +32,8 @@ from rql import CoercionError, RQLException from rql.base import BaseNode, Node, BinaryNode, LeafNode -from rql.utils import (function_description, quote, uquote, build_visitor_stub, - common_parent) +from rql.utils import (function_description, quote, uquote, common_parent, + VisitableMixIn) CONSTANT_TYPES = frozenset((None, 'Date', 'Datetime', 'Boolean', 'Float', 'Int', 'String', 'Substitute', 'etype')) @@ -901,7 +901,7 @@ ############################################################################### -class Referenceable(object): +class Referenceable(VisitableMixIn): __slots__ = ('name', 'stinfo', 'stmt') def __init__(self, name): @@ -1076,6 +1076,13 @@ return rel return None + def valuable_references(self): + """return the number of "valuable" references : + references is in selection or in a non type (is) relations + """ + stinfo = self.stinfo + return len(stinfo['selected']) + len(stinfo['relations']) + class ColumnAlias(Referenceable): __slots__ = ('colnum', 'query', @@ -1125,10 +1132,4 @@ def __repr__(self): return '%s(%#X)' % (self.name, id(self)) - def valuable_references(self): - """return the number of "valuable" references : - references is in selection or in a non type (is) relations - """ - stinfo = self.stinfo - return len(stinfo['selected']) + len(stinfo['relations']) @@ -1134,5 +1135,1 @@ - -build_visitor_stub((SubQuery, And, Or, Not, Exists, Relation, - Comparison, MathExpression, UnaryExpression, Function, - Constant, VariableRef, SortTerm, ColumnAlias, Variable)) diff --git a/stmts.py b/stmts.py index 601279ab960001fc2472af1e3fdc9a7769b91d72_c3RtdHMucHk=..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_c3RtdHMucHk= 100644 --- a/stmts.py +++ b/stmts.py @@ -31,7 +31,7 @@ from rql import BadRQLQuery, CoercionError, nodes from rql.base import BaseNode, Node -from rql.utils import rqlvar_maker, build_visitor_stub +from rql.utils import rqlvar_maker _MARKER = object() @@ -1113,4 +1113,3 @@ return new -build_visitor_stub((Union, Select, Insert, Delete, Set)) diff --git a/test/unittest_parser.py b/test/unittest_parser.py index 601279ab960001fc2472af1e3fdc9a7769b91d72_dGVzdC91bml0dGVzdF9wYXJzZXIucHk=..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_dGVzdC91bml0dGVzdF9wYXJzZXIucHk= 100644 --- a/test/unittest_parser.py +++ b/test/unittest_parser.py @@ -187,7 +187,7 @@ tree = self.parse(u"Any X WHERE X name 'Ångström';") base = tree.children[0].where comparison = base.children[1] - self.failUnless(isinstance(comparison, nodes.Comparison)) + self.assertTrue(isinstance(comparison, nodes.Comparison)) rhs = comparison.children[0] self.assertEqual(type(rhs.value), unicode) @@ -306,8 +306,8 @@ "Any X WHERE X firstname 'lulu', " "EXISTS(X owned_by U, U in_group G, (G name 'lulufanclub') OR (G name 'managers'))") exists = tree.children[0].where.get_nodes(nodes.Exists)[0] - self.failUnless(exists.children[0].parent is exists) - self.failUnless(exists.parent) + self.assertTrue(exists.children[0].parent is exists) + self.assertTrue(exists.parent) def test_etype(self): tree = self.parse('EmailAddress X;') diff --git a/test/unittest_stcheck.py b/test/unittest_stcheck.py index 601279ab960001fc2472af1e3fdc9a7769b91d72_dGVzdC91bml0dGVzdF9zdGNoZWNrLnB5..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_dGVzdC91bml0dGVzdF9zdGNoZWNrLnB5 100644 --- a/test/unittest_stcheck.py +++ b/test/unittest_stcheck.py @@ -1,4 +1,4 @@ -# copyright 2004-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2004-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of rql. @@ -225,8 +225,8 @@ self.simplify(tree) copy = tree.copy() exists = copy.get_nodes(nodes.Exists)[0] - self.failUnless(exists.children[0].parent is exists) - self.failUnless(exists.parent) + self.assertTrue(exists.children[0].parent is exists) + self.assertTrue(exists.parent) def test_copy_internals(self): root = self.parse('Any X,U WHERE C owned_by U, NOT X owned_by U, X eid 1, C eid 2') @@ -254,7 +254,7 @@ # def test_simplified(self): # rqlst = self.parse('Any L WHERE 5 name L') # self.annotate(rqlst) -# self.failUnless(rqlst.defined_vars['L'].stinfo['attrvar']) +# self.assertTrue(rqlst.defined_vars['L'].stinfo['attrvar']) def test_is_rel_no_scope_1(self): """is relation used as type restriction should not affect variable's @@ -262,9 +262,9 @@ """ rqlst = self.parse('Any X WHERE C is Company, EXISTS(X work_for C)').children[0] C = rqlst.defined_vars['C'] - self.failIf(C.scope is rqlst, C.scope) + self.assertFalse(C.scope is rqlst, C.scope) self.assertEqual(len(C.stinfo['relations']), 1) def test_is_rel_no_scope_2(self): rqlst = self.parse('Any X, ET WHERE C is ET, EXISTS(X work_for C)').children[0] C = rqlst.defined_vars['C'] @@ -266,9 +266,9 @@ self.assertEqual(len(C.stinfo['relations']), 1) def test_is_rel_no_scope_2(self): rqlst = self.parse('Any X, ET WHERE C is ET, EXISTS(X work_for C)').children[0] C = rqlst.defined_vars['C'] - self.failUnless(C.scope is rqlst, C.scope) + self.assertTrue(C.scope is rqlst, C.scope) self.assertEqual(len(C.stinfo['relations']), 2) @@ -276,9 +276,9 @@ rqlst = self.parse('Any X WHERE C is Company, NOT X work_for C').children[0] self.assertEqual(rqlst.as_string(), 'Any X WHERE C is Company, NOT EXISTS(X work_for C)') C = rqlst.defined_vars['C'] - self.failIf(C.scope is rqlst, C.scope) + self.assertFalse(C.scope is rqlst, C.scope) def test_not_rel_normalization_2(self): rqlst = self.parse('Any X, ET WHERE C is ET, NOT X work_for C').children[0] self.assertEqual(rqlst.as_string(), 'Any X,ET WHERE C is ET, NOT EXISTS(X work_for C)') C = rqlst.defined_vars['C'] @@ -280,11 +280,11 @@ def test_not_rel_normalization_2(self): rqlst = self.parse('Any X, ET WHERE C is ET, NOT X work_for C').children[0] self.assertEqual(rqlst.as_string(), 'Any X,ET WHERE C is ET, NOT EXISTS(X work_for C)') C = rqlst.defined_vars['C'] - self.failUnless(C.scope is rqlst, C.scope) + self.assertTrue(C.scope is rqlst, C.scope) def test_not_rel_normalization_3(self): rqlst = self.parse('Any X WHERE C is Company, X work_for C, NOT C name "World Company"').children[0] self.assertEqual(rqlst.as_string(), "Any X WHERE C is Company, X work_for C, NOT C name 'World Company'") C = rqlst.defined_vars['C'] @@ -286,11 +286,11 @@ def test_not_rel_normalization_3(self): rqlst = self.parse('Any X WHERE C is Company, X work_for C, NOT C name "World Company"').children[0] self.assertEqual(rqlst.as_string(), "Any X WHERE C is Company, X work_for C, NOT C name 'World Company'") C = rqlst.defined_vars['C'] - self.failUnless(C.scope is rqlst, C.scope) + self.assertTrue(C.scope is rqlst, C.scope) def test_not_rel_normalization_4(self): rqlst = self.parse('Any X WHERE C is Company, NOT (X work_for C, C name "World Company")').children[0] self.assertEqual(rqlst.as_string(), "Any X WHERE C is Company, NOT EXISTS(X work_for C, C name 'World Company')") C = rqlst.defined_vars['C'] @@ -292,11 +292,11 @@ def test_not_rel_normalization_4(self): rqlst = self.parse('Any X WHERE C is Company, NOT (X work_for C, C name "World Company")').children[0] self.assertEqual(rqlst.as_string(), "Any X WHERE C is Company, NOT EXISTS(X work_for C, C name 'World Company')") C = rqlst.defined_vars['C'] - self.failIf(C.scope is rqlst, C.scope) + self.assertFalse(C.scope is rqlst, C.scope) def test_not_rel_normalization_5(self): rqlst = self.parse('Any X WHERE X work_for C, EXISTS(C identity D, NOT Y work_for D, D name "World Company")').children[0] self.assertEqual(rqlst.as_string(), "Any X WHERE X work_for C, EXISTS(C identity D, NOT EXISTS(Y work_for D), D name 'World Company')") D = rqlst.defined_vars['D'] @@ -298,11 +298,11 @@ def test_not_rel_normalization_5(self): rqlst = self.parse('Any X WHERE X work_for C, EXISTS(C identity D, NOT Y work_for D, D name "World Company")').children[0] self.assertEqual(rqlst.as_string(), "Any X WHERE X work_for C, EXISTS(C identity D, NOT EXISTS(Y work_for D), D name 'World Company')") D = rqlst.defined_vars['D'] - self.failIf(D.scope is rqlst, D.scope) - self.failUnless(D.scope.parent.scope is rqlst, D.scope.parent.scope) + self.assertFalse(D.scope is rqlst, D.scope) + self.assertTrue(D.scope.parent.scope is rqlst, D.scope.parent.scope) def test_subquery_annotation_1(self): rqlst = self.parse('Any X WITH X BEING (Any X WHERE C is Company, EXISTS(X work_for C))').children[0] C = rqlst.with_[0].query.children[0].defined_vars['C'] @@ -305,10 +305,10 @@ def test_subquery_annotation_1(self): rqlst = self.parse('Any X WITH X BEING (Any X WHERE C is Company, EXISTS(X work_for C))').children[0] C = rqlst.with_[0].query.children[0].defined_vars['C'] - self.failIf(C.scope is rqlst, C.scope) + self.assertFalse(C.scope is rqlst, C.scope) self.assertEqual(len(C.stinfo['relations']), 1) def test_subquery_annotation_2(self): rqlst = self.parse('Any X,ET WITH X,ET BEING (Any X, ET WHERE C is ET, EXISTS(X work_for C))').children[0] C = rqlst.with_[0].query.children[0].defined_vars['C'] @@ -310,8 +310,8 @@ self.assertEqual(len(C.stinfo['relations']), 1) def test_subquery_annotation_2(self): rqlst = self.parse('Any X,ET WITH X,ET BEING (Any X, ET WHERE C is ET, EXISTS(X work_for C))').children[0] C = rqlst.with_[0].query.children[0].defined_vars['C'] - self.failUnless(C.scope is rqlst.with_[0].query.children[0], C.scope) + self.assertTrue(C.scope is rqlst.with_[0].query.children[0], C.scope) self.assertEqual(len(C.stinfo['relations']), 2) X = rqlst.get_variable('X') @@ -316,6 +316,6 @@ self.assertEqual(len(C.stinfo['relations']), 2) X = rqlst.get_variable('X') - self.failUnless(X.scope is rqlst, X.scope) + self.assertTrue(X.scope is rqlst, X.scope) def test_no_attr_var_if_uid_rel(self): with self.assertRaises(BadRQLQuery) as cm: diff --git a/utils.py b/utils.py index 601279ab960001fc2472af1e3fdc9a7769b91d72_dXRpbHMucHk=..26b2c3ca5c0edf6f9e60e5ee0f46a196a3b04909_dXRpbHMucHk= 100644 --- a/utils.py +++ b/utils.py @@ -133,7 +133,7 @@ SQL_FUNCTIONS_REGISTRY.register_function(funcdef) def function_description(funcname): - """Return the description (`FunctionDescription`) for a RQL function.""" + """Return the description (:class:`FunctionDescr`) for a RQL function.""" return RQL_FUNCTIONS_REGISTRY.get_function(funcname) def quote(value): @@ -156,5 +156,7 @@ res.append(u'"') return u''.join(res) -# Visitor ##################################################################### + + +class VisitableMixIn(object): @@ -160,10 +162,13 @@ -_accept = 'lambda self, visitor, *args, **kwargs: visitor.visit_%s(self, *args, **kwargs)' -_leave = 'lambda self, visitor, *args, **kwargs: visitor.leave_%s(self, *args, **kwargs)' -def build_visitor_stub(classes): - for cls in classes: - cls.accept = eval(_accept % (cls.__name__.lower())) - cls.leave = eval(_leave % (cls.__name__.lower())) + def accept(self, visitor, *args, **kwargs): + visit_id = self.__class__.__name__.lower() + visit_method = getattr(visitor, 'visit_%s' % visit_id) + return visit_method(self, *args, **kwargs) + + def leave(self, visitor, *args, **kwargs): + visit_id = self.__class__.__name__.lower() + visit_method = getattr(visitor, 'leave_%s' % visit_id) + return visit_method(self, *args, **kwargs) class RQLVisitorHandler(object): """Handler providing a dummy implementation of all callbacks necessary