Merge lp://qastaging/~jamesh/storm/bug-349482 into lp://qastaging/storm
- bug-349482
- Merge into trunk
Proposed by
James Henstridge
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp://qastaging/~jamesh/storm/bug-349482 |
Merge into: | lp://qastaging/storm |
Diff against target: | 303 lines |
To merge this branch: | bzr merge lp://qastaging/~jamesh/storm/bug-349482 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Niemeyer | Approve | ||
Thomas Herve (community) | Approve | ||
Review via email: mp+6693@code.qastaging.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
James Henstridge (jamesh) wrote : | # |
Revision history for this message
Thomas Herve (therve) wrote : | # |
+ exec code in namespace
+ return namespace[
Can you add a local dict and do this:
+ local = {}
+ exec code in namespace, local
+ return local['
This is it will be even cleaner.
+1!
review:
Approve
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote : | # |
Nice improvements! Thank you!
James, I recall something about this change being motivated by an actual failing expression. Is it easy to reproduce this case in a test just so that we ensure this case won't ever come back again?
+1!
review:
Approve
- 310. By James Henstridge
-
Add a test to show that get_matcher() works for values whose repr() is
not a valid Python expression. - 311. By James Henstridge
-
Add a NEWS item about changes.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'NEWS' | |||
2 | --- NEWS 2009-06-13 12:18:15 +0000 | |||
3 | +++ NEWS 2009-06-18 15:20:10 +0000 | |||
4 | @@ -28,6 +28,8 @@ | |||
5 | 28 | - The test suite now passes when run with Python 2.6. | 28 | - The test suite now passes when run with Python 2.6. |
6 | 29 | - ListVariable now converts its elements to database representation | 29 | - ListVariable now converts its elements to database representation |
7 | 30 | correctly (bug #136806 reported by Marc Tardif). | 30 | correctly (bug #136806 reported by Marc Tardif). |
8 | 31 | - compile_python now works for values that don't produce valid Python | ||
9 | 32 | expressions with repr(). | ||
10 | 31 | 33 | ||
11 | 32 | 34 | ||
12 | 33 | 0.14 (2009-01-09) | 35 | 0.14 (2009-01-09) |
13 | 34 | 36 | ||
14 | === modified file 'storm/expr.py' | |||
15 | --- storm/expr.py 2008-06-27 16:44:28 +0000 | |||
16 | +++ storm/expr.py 2009-06-18 15:20:10 +0000 | |||
17 | @@ -206,8 +206,18 @@ | |||
18 | 206 | class CompilePython(Compile): | 206 | class CompilePython(Compile): |
19 | 207 | 207 | ||
20 | 208 | def get_matcher(self, expr): | 208 | def get_matcher(self, expr): |
23 | 209 | exec "def match(get_column): return bool(%s)" % self(expr) | 209 | state = State() |
24 | 210 | return match | 210 | source = self(expr, state) |
25 | 211 | namespace = {} | ||
26 | 212 | code = ("def closure(parameters, bool):\n" | ||
27 | 213 | " [%s] = parameters\n" | ||
28 | 214 | " def match(get_column):\n" | ||
29 | 215 | " return bool(%s)\n" | ||
30 | 216 | " return match" % | ||
31 | 217 | (",".join("_%d" % i for i in range(len(state.parameters))), | ||
32 | 218 | source)) | ||
33 | 219 | exec code in namespace | ||
34 | 220 | return namespace['closure'](state.parameters, bool) | ||
35 | 211 | 221 | ||
36 | 212 | 222 | ||
37 | 213 | class State(object): | 223 | class State(object): |
38 | @@ -348,12 +358,18 @@ | |||
39 | 348 | return "NULL" | 358 | return "NULL" |
40 | 349 | 359 | ||
41 | 350 | 360 | ||
44 | 351 | @compile_python.when(str, unicode, bool, int, long, float, | 361 | @compile_python.when(str, unicode, int, long, float, type(None)) |
43 | 352 | datetime, date, time, timedelta, type(None)) | ||
45 | 353 | def compile_python_builtin(compile, expr, state): | 362 | def compile_python_builtin(compile, expr, state): |
46 | 354 | return repr(expr) | 363 | return repr(expr) |
47 | 355 | 364 | ||
48 | 356 | 365 | ||
49 | 366 | @compile_python.when(bool, datetime, date, time, timedelta) | ||
50 | 367 | def compile_python_bool_and_dates(compile, expr, state): | ||
51 | 368 | index = len(state.parameters) | ||
52 | 369 | state.parameters.append(expr) | ||
53 | 370 | return "_%d" % index | ||
54 | 371 | |||
55 | 372 | |||
56 | 357 | @compile.when(Variable) | 373 | @compile.when(Variable) |
57 | 358 | def compile_variable(compile, variable, state): | 374 | def compile_variable(compile, variable, state): |
58 | 359 | state.parameters.append(variable) | 375 | state.parameters.append(variable) |
59 | @@ -361,7 +377,9 @@ | |||
60 | 361 | 377 | ||
61 | 362 | @compile_python.when(Variable) | 378 | @compile_python.when(Variable) |
62 | 363 | def compile_python_variable(compile, variable, state): | 379 | def compile_python_variable(compile, variable, state): |
64 | 364 | return repr(variable.get()) | 380 | index = len(state.parameters) |
65 | 381 | state.parameters.append(variable.get()) | ||
66 | 382 | return "_%d" % index | ||
67 | 365 | 383 | ||
68 | 366 | 384 | ||
69 | 367 | # -------------------------------------------------------------------- | 385 | # -------------------------------------------------------------------- |
70 | @@ -776,7 +794,9 @@ | |||
71 | 776 | 794 | ||
72 | 777 | @compile_python.when(Column) | 795 | @compile_python.when(Column) |
73 | 778 | def compile_python_column(compile, column, state): | 796 | def compile_python_column(compile, column, state): |
75 | 779 | return "get_column(%s)" % repr(column.name) | 797 | index = len(state.parameters) |
76 | 798 | state.parameters.append(column) | ||
77 | 799 | return "get_column(_%d)" % index | ||
78 | 780 | 800 | ||
79 | 781 | 801 | ||
80 | 782 | # -------------------------------------------------------------------- | 802 | # -------------------------------------------------------------------- |
81 | 783 | 803 | ||
82 | === modified file 'storm/store.py' | |||
83 | --- storm/store.py 2009-02-16 10:44:31 +0000 | |||
84 | +++ storm/store.py 2009-06-18 15:20:10 +0000 | |||
85 | @@ -1349,11 +1349,8 @@ | |||
86 | 1349 | match = None | 1349 | match = None |
87 | 1350 | else: | 1350 | else: |
88 | 1351 | match = compile_python.get_matcher(self._where) | 1351 | match = compile_python.get_matcher(self._where) |
94 | 1352 | name_to_column = dict( | 1352 | def get_column(column): |
95 | 1353 | (column.name, column) | 1353 | return obj_info.variables[column].get() |
91 | 1354 | for column in self._find_spec.default_cls_info.columns) | ||
92 | 1355 | def get_column(name, name_to_column=name_to_column): | ||
93 | 1356 | return obj_info.variables[name_to_column[name]].get() | ||
96 | 1357 | objects = [] | 1354 | objects = [] |
97 | 1358 | cls = self._find_spec.default_cls_info.cls | 1355 | cls = self._find_spec.default_cls_info.cls |
98 | 1359 | for obj_info in self._store._iter_alive(): | 1356 | for obj_info in self._store._iter_alive(): |
99 | 1360 | 1357 | ||
100 | === modified file 'tests/expr.py' | |||
101 | --- tests/expr.py 2008-10-02 15:47:18 +0000 | |||
102 | +++ tests/expr.py 2009-06-18 15:20:10 +0000 | |||
103 | @@ -1937,8 +1937,10 @@ | |||
104 | 1937 | 1937 | ||
105 | 1938 | def test_compile_sequence(self): | 1938 | def test_compile_sequence(self): |
106 | 1939 | expr = [elem1, Variable(1), (Variable(2), None)] | 1939 | expr = [elem1, Variable(1), (Variable(2), None)] |
109 | 1940 | py_expr = compile_python(expr) | 1940 | state = State() |
110 | 1941 | self.assertEquals(py_expr, "elem1, 1, 2, None") | 1941 | py_expr = compile_python(expr, state) |
111 | 1942 | self.assertEquals(py_expr, "elem1, _0, _1, None") | ||
112 | 1943 | self.assertEquals(state.parameters, [1, 2]) | ||
113 | 1942 | 1944 | ||
114 | 1943 | def test_compile_invalid(self): | 1945 | def test_compile_invalid(self): |
115 | 1944 | self.assertRaises(CompileError, compile_python, object()) | 1946 | self.assertRaises(CompileError, compile_python, object()) |
116 | @@ -1965,8 +1967,10 @@ | |||
117 | 1965 | self.assertEquals(py_expr, "1L") | 1967 | self.assertEquals(py_expr, "1L") |
118 | 1966 | 1968 | ||
119 | 1967 | def test_bool(self): | 1969 | def test_bool(self): |
122 | 1968 | py_expr = compile_python(True) | 1970 | state = State() |
123 | 1969 | self.assertEquals(py_expr, "True") | 1971 | py_expr = compile_python(True, state) |
124 | 1972 | self.assertEquals(py_expr, "_0") | ||
125 | 1973 | self.assertEquals(state.parameters, [True]) | ||
126 | 1970 | 1974 | ||
127 | 1971 | def test_float(self): | 1975 | def test_float(self): |
128 | 1972 | py_expr = compile_python(1.1) | 1976 | py_expr = compile_python(1.1) |
129 | @@ -1974,23 +1978,31 @@ | |||
130 | 1974 | 1978 | ||
131 | 1975 | def test_datetime(self): | 1979 | def test_datetime(self): |
132 | 1976 | dt = datetime(1977, 5, 4, 12, 34) | 1980 | dt = datetime(1977, 5, 4, 12, 34) |
135 | 1977 | py_expr = compile_python(dt) | 1981 | state = State() |
136 | 1978 | self.assertEquals(py_expr, repr(dt)) | 1982 | py_expr = compile_python(dt, state) |
137 | 1983 | self.assertEquals(py_expr, "_0") | ||
138 | 1984 | self.assertEquals(state.parameters, [dt]) | ||
139 | 1979 | 1985 | ||
140 | 1980 | def test_date(self): | 1986 | def test_date(self): |
141 | 1981 | d = date(1977, 5, 4) | 1987 | d = date(1977, 5, 4) |
144 | 1982 | py_expr = compile_python(d) | 1988 | state = State() |
145 | 1983 | self.assertEquals(py_expr, repr(d)) | 1989 | py_expr = compile_python(d, state) |
146 | 1990 | self.assertEquals(py_expr, "_0") | ||
147 | 1991 | self.assertEquals(state.parameters, [d]) | ||
148 | 1984 | 1992 | ||
149 | 1985 | def test_time(self): | 1993 | def test_time(self): |
150 | 1986 | t = time(12, 34) | 1994 | t = time(12, 34) |
153 | 1987 | py_expr = compile_python(t) | 1995 | state = State() |
154 | 1988 | self.assertEquals(py_expr, repr(t)) | 1996 | py_expr = compile_python(t, state) |
155 | 1997 | self.assertEquals(py_expr, "_0") | ||
156 | 1998 | self.assertEquals(state.parameters, [t]) | ||
157 | 1989 | 1999 | ||
158 | 1990 | def test_timedelta(self): | 2000 | def test_timedelta(self): |
159 | 1991 | td = timedelta(days=1, seconds=2, microseconds=3) | 2001 | td = timedelta(days=1, seconds=2, microseconds=3) |
162 | 1992 | py_expr = compile_python(td) | 2002 | state = State() |
163 | 1993 | self.assertEquals(py_expr, repr(td)) | 2003 | py_expr = compile_python(td, state) |
164 | 2004 | self.assertEquals(py_expr, "_0") | ||
165 | 2005 | self.assertEquals(state.parameters, [td]) | ||
166 | 1994 | 2006 | ||
167 | 1995 | def test_none(self): | 2007 | def test_none(self): |
168 | 1996 | py_expr = compile_python(None) | 2008 | py_expr = compile_python(None) |
169 | @@ -1998,63 +2010,87 @@ | |||
170 | 1998 | 2010 | ||
171 | 1999 | def test_column(self): | 2011 | def test_column(self): |
172 | 2000 | expr = Column(column1) | 2012 | expr = Column(column1) |
175 | 2001 | py_expr = compile_python(expr) | 2013 | state = State() |
176 | 2002 | self.assertEquals(py_expr, "get_column('column1')") | 2014 | py_expr = compile_python(expr, state) |
177 | 2015 | self.assertEquals(py_expr, "get_column(_0)") | ||
178 | 2016 | self.assertEquals(state.parameters, [expr]) | ||
179 | 2003 | 2017 | ||
180 | 2004 | def test_column_table(self): | 2018 | def test_column_table(self): |
181 | 2005 | expr = Column(column1, table1) | 2019 | expr = Column(column1, table1) |
184 | 2006 | py_expr = compile_python(expr) | 2020 | state = State() |
185 | 2007 | self.assertEquals(py_expr, "get_column('column1')") | 2021 | py_expr = compile_python(expr, state) |
186 | 2022 | self.assertEquals(py_expr, "get_column(_0)") | ||
187 | 2023 | self.assertEquals(state.parameters, [expr]) | ||
188 | 2008 | 2024 | ||
189 | 2009 | def test_variable(self): | 2025 | def test_variable(self): |
190 | 2010 | expr = Variable("value") | 2026 | expr = Variable("value") |
193 | 2011 | py_expr = compile_python(expr) | 2027 | state = State() |
194 | 2012 | self.assertEquals(py_expr, "'value'") | 2028 | py_expr = compile_python(expr, state) |
195 | 2029 | self.assertEquals(py_expr, "_0") | ||
196 | 2030 | self.assertEquals(state.parameters, ["value"]) | ||
197 | 2013 | 2031 | ||
198 | 2014 | def test_eq(self): | 2032 | def test_eq(self): |
199 | 2015 | expr = Eq(Variable(1), Variable(2)) | 2033 | expr = Eq(Variable(1), Variable(2)) |
202 | 2016 | py_expr = compile_python(expr) | 2034 | state = State() |
203 | 2017 | self.assertEquals(py_expr, "1 == 2") | 2035 | py_expr = compile_python(expr, state) |
204 | 2036 | self.assertEquals(py_expr, "_0 == _1") | ||
205 | 2037 | self.assertEquals(state.parameters, [1, 2]) | ||
206 | 2018 | 2038 | ||
207 | 2019 | def test_ne(self): | 2039 | def test_ne(self): |
208 | 2020 | expr = Ne(Variable(1), Variable(2)) | 2040 | expr = Ne(Variable(1), Variable(2)) |
211 | 2021 | py_expr = compile_python(expr) | 2041 | state = State() |
212 | 2022 | self.assertEquals(py_expr, "1 != 2") | 2042 | py_expr = compile_python(expr, state) |
213 | 2043 | self.assertEquals(py_expr, "_0 != _1") | ||
214 | 2044 | self.assertEquals(state.parameters, [1, 2]) | ||
215 | 2023 | 2045 | ||
216 | 2024 | def test_gt(self): | 2046 | def test_gt(self): |
217 | 2025 | expr = Gt(Variable(1), Variable(2)) | 2047 | expr = Gt(Variable(1), Variable(2)) |
220 | 2026 | py_expr = compile_python(expr) | 2048 | state = State() |
221 | 2027 | self.assertEquals(py_expr, "1 > 2") | 2049 | py_expr = compile_python(expr, state) |
222 | 2050 | self.assertEquals(py_expr, "_0 > _1") | ||
223 | 2051 | self.assertEquals(state.parameters, [1, 2]) | ||
224 | 2028 | 2052 | ||
225 | 2029 | def test_ge(self): | 2053 | def test_ge(self): |
226 | 2030 | expr = Ge(Variable(1), Variable(2)) | 2054 | expr = Ge(Variable(1), Variable(2)) |
229 | 2031 | py_expr = compile_python(expr) | 2055 | state = State() |
230 | 2032 | self.assertEquals(py_expr, "1 >= 2") | 2056 | py_expr = compile_python(expr, state) |
231 | 2057 | self.assertEquals(py_expr, "_0 >= _1") | ||
232 | 2058 | self.assertEquals(state.parameters, [1, 2]) | ||
233 | 2033 | 2059 | ||
234 | 2034 | def test_lt(self): | 2060 | def test_lt(self): |
235 | 2035 | expr = Lt(Variable(1), Variable(2)) | 2061 | expr = Lt(Variable(1), Variable(2)) |
238 | 2036 | py_expr = compile_python(expr) | 2062 | state = State() |
239 | 2037 | self.assertEquals(py_expr, "1 < 2") | 2063 | py_expr = compile_python(expr, state) |
240 | 2064 | self.assertEquals(py_expr, "_0 < _1") | ||
241 | 2065 | self.assertEquals(state.parameters, [1, 2]) | ||
242 | 2038 | 2066 | ||
243 | 2039 | def test_le(self): | 2067 | def test_le(self): |
244 | 2040 | expr = Le(Variable(1), Variable(2)) | 2068 | expr = Le(Variable(1), Variable(2)) |
247 | 2041 | py_expr = compile_python(expr) | 2069 | state = State() |
248 | 2042 | self.assertEquals(py_expr, "1 <= 2") | 2070 | py_expr = compile_python(expr, state) |
249 | 2071 | self.assertEquals(py_expr, "_0 <= _1") | ||
250 | 2072 | self.assertEquals(state.parameters, [1, 2]) | ||
251 | 2043 | 2073 | ||
252 | 2044 | def test_lshift(self): | 2074 | def test_lshift(self): |
253 | 2045 | expr = LShift(Variable(1), Variable(2)) | 2075 | expr = LShift(Variable(1), Variable(2)) |
256 | 2046 | py_expr = compile_python(expr) | 2076 | state = State() |
257 | 2047 | self.assertEquals(py_expr, "1<<2") | 2077 | py_expr = compile_python(expr, state) |
258 | 2078 | self.assertEquals(py_expr, "_0<<_1") | ||
259 | 2079 | self.assertEquals(state.parameters, [1, 2]) | ||
260 | 2048 | 2080 | ||
261 | 2049 | def test_rshift(self): | 2081 | def test_rshift(self): |
262 | 2050 | expr = RShift(Variable(1), Variable(2)) | 2082 | expr = RShift(Variable(1), Variable(2)) |
265 | 2051 | py_expr = compile_python(expr) | 2083 | state = State() |
266 | 2052 | self.assertEquals(py_expr, "1>>2") | 2084 | py_expr = compile_python(expr, state) |
267 | 2085 | self.assertEquals(py_expr, "_0>>_1") | ||
268 | 2086 | self.assertEquals(state.parameters, [1, 2]) | ||
269 | 2053 | 2087 | ||
270 | 2054 | def test_in(self): | 2088 | def test_in(self): |
271 | 2055 | expr = In(Variable(1), Variable(2)) | 2089 | expr = In(Variable(1), Variable(2)) |
274 | 2056 | py_expr = compile_python(expr) | 2090 | state = State() |
275 | 2057 | self.assertEquals(py_expr, "1 in (2,)") | 2091 | py_expr = compile_python(expr, state) |
276 | 2092 | self.assertEquals(py_expr, "_0 in (_1,)") | ||
277 | 2093 | self.assertEquals(state.parameters, [1, 2]) | ||
278 | 2058 | 2094 | ||
279 | 2059 | def test_and(self): | 2095 | def test_and(self): |
280 | 2060 | expr = And(elem1, elem2, And(elem3, elem4)) | 2096 | expr = And(elem1, elem2, And(elem3, elem4)) |
281 | @@ -2109,8 +2145,20 @@ | |||
282 | 2109 | 2145 | ||
283 | 2110 | match = compile_python.get_matcher((col1 > 10) & (col2 < 10)) | 2146 | match = compile_python.get_matcher((col1 > 10) & (col2 < 10)) |
284 | 2111 | 2147 | ||
287 | 2112 | self.assertTrue(match({"column1": 15, "column2": 5}.get)) | 2148 | self.assertTrue(match({col1: 15, col2: 5}.get)) |
288 | 2113 | self.assertFalse(match({"column1": 5, "column2": 15}.get)) | 2149 | self.assertFalse(match({col1: 5, col2: 15}.get)) |
289 | 2150 | |||
290 | 2151 | def test_match_bad_repr(self): | ||
291 | 2152 | """The get_matcher() works for expressions containing values | ||
292 | 2153 | whose repr is not valid Python syntax.""" | ||
293 | 2154 | class BadRepr(object): | ||
294 | 2155 | def __repr__(self): | ||
295 | 2156 | return "$Not a valid Python expression$" | ||
296 | 2157 | |||
297 | 2158 | value = BadRepr() | ||
298 | 2159 | col1 = Column(column1) | ||
299 | 2160 | match = compile_python.get_matcher(col1 == Variable(value)) | ||
300 | 2161 | self.assertTrue(match({col1: value}.get)) | ||
301 | 2114 | 2162 | ||
302 | 2115 | 2163 | ||
303 | 2116 | class LazyValueExprTest(TestHelper): | 2164 | class LazyValueExprTest(TestHelper): |
Change the way compile_python works to pass constants in directly rather than trying to stringify them. Among other things, this makes it possible to handle variables whose values do not have a repr() that is valid Python code.
I also updated the get_column() callback to take a column object rather than going column -> name -> column conversions when getting column values in the matcher.