Skip to content

Commit 5f404d9

Browse files
committed
Merge pull request #6109 from cpcloud/ne-version
TST: add tests for old version of numexpr
2 parents bf477dc + bb601b5 commit 5f404d9

File tree

7 files changed

+106
-64
lines changed

7 files changed

+106
-64
lines changed

ci/requirements-2.6.txt

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ pytz==2013b
55
http://www.crummy.com/software/BeautifulSoup/bs4/download/4.2/beautifulsoup4-4.2.0.tar.gz
66
html5lib==1.0b2
77
bigquery==2.0.17
8+
numexpr==1.4.2

ci/script.sh

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ echo "inside $0"
55
if [ -n "$LOCALE_OVERRIDE" ]; then
66
export LC_ALL="$LOCALE_OVERRIDE";
77
echo "Setting LC_ALL to $LOCALE_OVERRIDE"
8+
curdir="$(pwd)"
9+
cd /tmp
810
pycmd='import pandas; print("pandas detected console encoding: %s" % pandas.get_option("display.encoding"))'
911
python -c "$pycmd"
12+
cd "$curdir"
1013
fi
1114

12-
echo nosetests -v --exe -w /tmp -A "$NOSE_ARGS" pandas --with-xunit --xunit-file=/tmp/nosetests.xml
13-
nosetests -v --exe -w /tmp -A "$NOSE_ARGS" pandas --with-xunit --xunit-file=/tmp/nosetests.xml
15+
echo nosetests --exe -w /tmp -A "$NOSE_ARGS" pandas --with-xunit --xunit-file=/tmp/nosetests.xml
16+
nosetests --exe -w /tmp -A "$NOSE_ARGS" pandas --with-xunit --xunit-file=/tmp/nosetests.xml

pandas/computation/eval.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
"""Top level ``eval`` module.
44
"""
55

6-
import numbers
7-
import numpy as np
86

97
from pandas.core import common as com
10-
from pandas.compat import string_types
118
from pandas.computation.expr import Expr, _parsers, _ensure_scope
129
from pandas.computation.engines import _engines
10+
from distutils.version import LooseVersion
1311

1412

1513
def _check_engine(engine):
@@ -38,7 +36,13 @@ def _check_engine(engine):
3836
import numexpr
3937
except ImportError:
4038
raise ImportError("'numexpr' not found. Cannot use "
41-
"engine='numexpr' if 'numexpr' is not installed")
39+
"engine='numexpr' for query/eval "
40+
"if 'numexpr' is not installed")
41+
else:
42+
ne_version = numexpr.__version__
43+
if ne_version < LooseVersion('2.0'):
44+
raise ImportError("'numexpr' version is %s, "
45+
"must be >= 2.0" % ne_version)
4246

4347

4448
def _check_parser(parser):

pandas/computation/expressions.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88

99
import numpy as np
1010
from pandas.core.common import _values_from_object
11+
from distutils.version import LooseVersion
1112

1213
try:
1314
import numexpr as ne
14-
_NUMEXPR_INSTALLED = True
15+
_NUMEXPR_INSTALLED = ne.__version__ >= LooseVersion('2.0')
1516
except ImportError: # pragma: no cover
1617
_NUMEXPR_INSTALLED = False
1718

pandas/computation/tests/test_eval.py

+56-34
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import functools
44
from itertools import product
5+
from distutils.version import LooseVersion
56

67
import nose
78
from nose.tools import assert_raises, assert_true, assert_false, assert_equal
@@ -17,13 +18,13 @@
1718
from pandas.util.testing import makeCustomDataframe as mkdf
1819

1920
from pandas.computation import pytables
20-
from pandas.computation.expressions import _USE_NUMEXPR
2121
from pandas.computation.engines import _engines
2222
from pandas.computation.expr import PythonExprVisitor, PandasExprVisitor
23-
from pandas.computation.ops import (_binary_ops_dict, _unary_ops_dict,
23+
from pandas.computation.ops import (_binary_ops_dict,
2424
_special_case_arith_ops_syms,
2525
_arith_ops_syms, _bool_ops_syms)
2626
from pandas.computation.common import NameResolutionError
27+
2728
import pandas.computation.expr as expr
2829
import pandas.util.testing as tm
2930
from pandas.util.testing import (assert_frame_equal, randbool,
@@ -35,11 +36,6 @@
3536
_scalar_skip = 'in', 'not in'
3637

3738

38-
def skip_if_no_ne(engine='numexpr'):
39-
if not _USE_NUMEXPR and engine == 'numexpr':
40-
raise nose.SkipTest("numexpr engine not installed or disabled")
41-
42-
4339
def engine_has_neg_frac(engine):
4440
return _engines[engine].has_neg_frac
4541

@@ -108,7 +104,7 @@ class TestEvalNumexprPandas(tm.TestCase):
108104
@classmethod
109105
def setUpClass(cls):
110106
super(TestEvalNumexprPandas, cls).setUpClass()
111-
skip_if_no_ne()
107+
tm.skip_if_no_ne()
112108
import numexpr as ne
113109
cls.ne = ne
114110
cls.engine = 'numexpr'
@@ -426,7 +422,7 @@ def check_single_invert_op(self, lhs, cmp1, rhs):
426422
assert_array_equal(expected, result)
427423

428424
for engine in self.current_engines:
429-
skip_if_no_ne(engine)
425+
tm.skip_if_no_ne(engine)
430426
assert_array_equal(result, pd.eval('~elb', engine=engine,
431427
parser=self.parser))
432428

@@ -457,7 +453,7 @@ def check_compound_invert_op(self, lhs, cmp1, rhs):
457453

458454
# make sure the other engines work the same as this one
459455
for engine in self.current_engines:
460-
skip_if_no_ne(engine)
456+
tm.skip_if_no_ne(engine)
461457
ev = pd.eval(ex, engine=self.engine, parser=self.parser)
462458
assert_array_equal(ev, result)
463459

@@ -709,7 +705,7 @@ class TestEvalNumexprPython(TestEvalNumexprPandas):
709705
@classmethod
710706
def setUpClass(cls):
711707
super(TestEvalNumexprPython, cls).setUpClass()
712-
skip_if_no_ne()
708+
tm.skip_if_no_ne()
713709
import numexpr as ne
714710
cls.ne = ne
715711
cls.engine = 'numexpr'
@@ -788,7 +784,7 @@ class TestAlignment(object):
788784
lhs_index_types = index_types + ('s',) # 'p'
789785

790786
def check_align_nested_unary_op(self, engine, parser):
791-
skip_if_no_ne(engine)
787+
tm.skip_if_no_ne(engine)
792788
s = 'df * ~2'
793789
df = mkdf(5, 3, data_gen_f=f)
794790
res = pd.eval(s, engine=engine, parser=parser)
@@ -799,7 +795,7 @@ def test_align_nested_unary_op(self):
799795
yield self.check_align_nested_unary_op, engine, parser
800796

801797
def check_basic_frame_alignment(self, engine, parser):
802-
skip_if_no_ne(engine)
798+
tm.skip_if_no_ne(engine)
803799
args = product(self.lhs_index_types, self.index_types,
804800
self.index_types)
805801
for lr_idx_type, rr_idx_type, c_idx_type in args:
@@ -815,7 +811,7 @@ def test_basic_frame_alignment(self):
815811
yield self.check_basic_frame_alignment, engine, parser
816812

817813
def check_frame_comparison(self, engine, parser):
818-
skip_if_no_ne(engine)
814+
tm.skip_if_no_ne(engine)
819815
args = product(self.lhs_index_types, repeat=2)
820816
for r_idx_type, c_idx_type in args:
821817
df = mkdf(10, 10, data_gen_f=f, r_idx_type=r_idx_type,
@@ -833,7 +829,7 @@ def test_frame_comparison(self):
833829
yield self.check_frame_comparison, engine, parser
834830

835831
def check_medium_complex_frame_alignment(self, engine, parser):
836-
skip_if_no_ne(engine)
832+
tm.skip_if_no_ne(engine)
837833
args = product(self.lhs_index_types, self.index_types,
838834
self.index_types, self.index_types)
839835

@@ -850,7 +846,7 @@ def test_medium_complex_frame_alignment(self):
850846
yield self.check_medium_complex_frame_alignment, engine, parser
851847

852848
def check_basic_frame_series_alignment(self, engine, parser):
853-
skip_if_no_ne(engine)
849+
tm.skip_if_no_ne(engine)
854850

855851
def testit(r_idx_type, c_idx_type, index_name):
856852
df = mkdf(10, 10, data_gen_f=f, r_idx_type=r_idx_type,
@@ -878,7 +874,7 @@ def test_basic_frame_series_alignment(self):
878874
yield self.check_basic_frame_series_alignment, engine, parser
879875

880876
def check_basic_series_frame_alignment(self, engine, parser):
881-
skip_if_no_ne(engine)
877+
tm.skip_if_no_ne(engine)
882878

883879
def testit(r_idx_type, c_idx_type, index_name):
884880
df = mkdf(10, 7, data_gen_f=f, r_idx_type=r_idx_type,
@@ -911,7 +907,7 @@ def test_basic_series_frame_alignment(self):
911907
yield self.check_basic_series_frame_alignment, engine, parser
912908

913909
def check_series_frame_commutativity(self, engine, parser):
914-
skip_if_no_ne(engine)
910+
tm.skip_if_no_ne(engine)
915911
args = product(self.lhs_index_types, self.index_types, ('+', '*'),
916912
('index', 'columns'))
917913
for r_idx_type, c_idx_type, op, index_name in args:
@@ -934,7 +930,7 @@ def test_series_frame_commutativity(self):
934930
yield self.check_series_frame_commutativity, engine, parser
935931

936932
def check_complex_series_frame_alignment(self, engine, parser):
937-
skip_if_no_ne(engine)
933+
tm.skip_if_no_ne(engine)
938934

939935
import random
940936
args = product(self.lhs_index_types, self.index_types,
@@ -978,7 +974,7 @@ def test_complex_series_frame_alignment(self):
978974
yield self.check_complex_series_frame_alignment, engine, parser
979975

980976
def check_performance_warning_for_poor_alignment(self, engine, parser):
981-
skip_if_no_ne(engine)
977+
tm.skip_if_no_ne(engine)
982978
df = DataFrame(randn(1000, 10))
983979
s = Series(randn(10000))
984980
if engine == 'numexpr':
@@ -1034,7 +1030,7 @@ class TestOperationsNumExprPandas(tm.TestCase):
10341030
@classmethod
10351031
def setUpClass(cls):
10361032
super(TestOperationsNumExprPandas, cls).setUpClass()
1037-
skip_if_no_ne()
1033+
tm.skip_if_no_ne()
10381034
cls.engine = 'numexpr'
10391035
cls.parser = 'pandas'
10401036
cls.arith_ops = expr._arith_ops_syms + expr._cmp_ops_syms
@@ -1194,7 +1190,7 @@ def test_assignment_fails(self):
11941190
local_dict={'df': df, 'df2': df2})
11951191

11961192
def test_assignment_column(self):
1197-
skip_if_no_ne('numexpr')
1193+
tm.skip_if_no_ne('numexpr')
11981194
df = DataFrame(np.random.randn(5, 2), columns=list('ab'))
11991195
orig_df = df.copy()
12001196

@@ -1345,10 +1341,9 @@ class TestOperationsNumExprPython(TestOperationsNumExprPandas):
13451341
@classmethod
13461342
def setUpClass(cls):
13471343
super(TestOperationsNumExprPython, cls).setUpClass()
1348-
if not _USE_NUMEXPR:
1349-
raise nose.SkipTest("numexpr engine not installed")
13501344
cls.engine = 'numexpr'
13511345
cls.parser = 'python'
1346+
tm.skip_if_no_ne(cls.engine)
13521347
cls.arith_ops = expr._arith_ops_syms + expr._cmp_ops_syms
13531348
cls.arith_ops = filter(lambda x: x not in ('in', 'not in'),
13541349
cls.arith_ops)
@@ -1435,7 +1430,7 @@ def setUpClass(cls):
14351430
class TestScope(object):
14361431

14371432
def check_global_scope(self, e, engine, parser):
1438-
skip_if_no_ne(engine)
1433+
tm.skip_if_no_ne(engine)
14391434
assert_array_equal(_var_s * 2, pd.eval(e, engine=engine,
14401435
parser=parser))
14411436

@@ -1445,7 +1440,7 @@ def test_global_scope(self):
14451440
yield self.check_global_scope, e, engine, parser
14461441

14471442
def check_no_new_locals(self, engine, parser):
1448-
skip_if_no_ne(engine)
1443+
tm.skip_if_no_ne(engine)
14491444
x = 1
14501445
lcls = locals().copy()
14511446
pd.eval('x + 1', local_dict=lcls, engine=engine, parser=parser)
@@ -1458,7 +1453,7 @@ def test_no_new_locals(self):
14581453
yield self.check_no_new_locals, engine, parser
14591454

14601455
def check_no_new_globals(self, engine, parser):
1461-
skip_if_no_ne(engine)
1456+
tm.skip_if_no_ne(engine)
14621457
x = 1
14631458
gbls = globals().copy()
14641459
pd.eval('x + 1', engine=engine, parser=parser)
@@ -1471,21 +1466,21 @@ def test_no_new_globals(self):
14711466

14721467

14731468
def test_invalid_engine():
1474-
skip_if_no_ne()
1469+
tm.skip_if_no_ne()
14751470
assertRaisesRegexp(KeyError, 'Invalid engine \'asdf\' passed',
14761471
pd.eval, 'x + y', local_dict={'x': 1, 'y': 2},
14771472
engine='asdf')
14781473

14791474

14801475
def test_invalid_parser():
1481-
skip_if_no_ne()
1476+
tm.skip_if_no_ne()
14821477
assertRaisesRegexp(KeyError, 'Invalid parser \'asdf\' passed',
14831478
pd.eval, 'x + y', local_dict={'x': 1, 'y': 2},
14841479
parser='asdf')
14851480

14861481

14871482
def check_is_expr_syntax(engine):
1488-
skip_if_no_ne(engine)
1483+
tm.skip_if_no_ne(engine)
14891484
s = 1
14901485
valid1 = 's + 1'
14911486
valid2 = '__y + _xx'
@@ -1494,7 +1489,7 @@ def check_is_expr_syntax(engine):
14941489

14951490

14961491
def check_is_expr_names(engine):
1497-
skip_if_no_ne(engine)
1492+
tm.skip_if_no_ne(engine)
14981493
r, s = 1, 2
14991494
valid = 's + r'
15001495
invalid = '__y + __x'
@@ -1517,7 +1512,7 @@ def test_is_expr_names():
15171512

15181513

15191514
def check_disallowed_nodes(engine, parser):
1520-
skip_if_no_ne(engine)
1515+
tm.skip_if_no_ne(engine)
15211516
VisitorClass = _parsers[parser]
15221517
uns_ops = VisitorClass.unsupported_nodes
15231518
inst = VisitorClass('x + 1', engine, parser)
@@ -1532,7 +1527,7 @@ def test_disallowed_nodes():
15321527

15331528

15341529
def check_syntax_error_exprs(engine, parser):
1535-
skip_if_no_ne(engine)
1530+
tm.skip_if_no_ne(engine)
15361531
e = 's +'
15371532
assert_raises(SyntaxError, pd.eval, e, engine=engine, parser=parser)
15381533

@@ -1543,7 +1538,7 @@ def test_syntax_error_exprs():
15431538

15441539

15451540
def check_name_error_exprs(engine, parser):
1546-
skip_if_no_ne(engine)
1541+
tm.skip_if_no_ne(engine)
15471542
e = 's + t'
15481543
assert_raises(NameError, pd.eval, e, engine=engine, parser=parser)
15491544

@@ -1553,6 +1548,33 @@ def test_name_error_exprs():
15531548
yield check_name_error_exprs, engine, parser
15541549

15551550

1551+
def check_invalid_numexpr_version(engine, parser):
1552+
def testit():
1553+
a, b = 1, 2
1554+
res = pd.eval('a + b', engine=engine, parser=parser)
1555+
tm.assert_equal(res, 3)
1556+
1557+
if engine == 'numexpr':
1558+
try:
1559+
import numexpr as ne
1560+
except ImportError:
1561+
raise nose.SkipTest("no numexpr")
1562+
else:
1563+
if ne.__version__ < LooseVersion('2.0'):
1564+
with tm.assertRaisesRegexp(ImportError, "'numexpr' version is "
1565+
".+, must be >= 2.0"):
1566+
testit()
1567+
else:
1568+
testit()
1569+
else:
1570+
testit()
1571+
1572+
1573+
def test_invalid_numexpr_version():
1574+
for engine, parser in ENGINES_PARSERS:
1575+
yield check_invalid_numexpr_version, engine, parser
1576+
1577+
15561578
if __name__ == '__main__':
15571579
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
15581580
exit=False)

0 commit comments

Comments
 (0)