From 8f22ebc189a03bd80e6cde68261d05e1cd678c06 Mon Sep 17 00:00:00 2001 From: Dave Cole Date: Thu, 21 Mar 2019 10:31:51 +1100 Subject: [PATCH 1/5] TST: Test df.query with "str.contains()" --- pandas/tests/computation/test_eval.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pandas/tests/computation/test_eval.py b/pandas/tests/computation/test_eval.py index 49d263feab664..aba24378b8c85 100644 --- a/pandas/tests/computation/test_eval.py +++ b/pandas/tests/computation/test_eval.py @@ -1903,3 +1903,15 @@ def test_validate_bool_args(self): for value in invalid_values: with pytest.raises(ValueError): pd.eval("2+2", inplace=value) + + +def test_query_str_contains(): + df = pd.DataFrame([["I", "XYZ"], ["IJ", None]], columns=['A', 'B']) + + expected = df[df["A"].str.contains("J")] + result = df.query("A.str.contains('J')", engine="python") + tm.assert_frame_equal(result, expected) + + expected = df[df["B"].str.contains("Z", na=False)] + result = df.query("B.str.contains('Z', na=False)", engine="python") + tm.assert_frame_equal(result, expected) From 64004bc139acf82009d68a508cf0961397b868cb Mon Sep 17 00:00:00 2001 From: Dave Cole Date: Thu, 21 Mar 2019 10:35:30 +1100 Subject: [PATCH 2/5] BUG: Fix bug when using eval to call "str" methods --- pandas/core/computation/expr.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pandas/core/computation/expr.py b/pandas/core/computation/expr.py index 4ab34b7349af5..2f3cd34ba6175 100644 --- a/pandas/core/computation/expr.py +++ b/pandas/core/computation/expr.py @@ -632,11 +632,8 @@ def visit_Call_35(self, node, side=None, **kwargs): if not isinstance(key, ast.keyword): raise ValueError("keyword error in function call " "'{func}'".format(func=node.func.id)) - if key.arg: - # TODO: bug? - kwargs.append(ast.keyword( - keyword.arg, self.visit(keyword.value))) # noqa + kwargs[key.arg] = self.visit(key.value)() return self.const_type(res(*new_args, **kwargs), self.env) From 087157c4b9c56cba9bec457f93535abde824273a Mon Sep 17 00:00:00 2001 From: Dave Cole Date: Thu, 21 Mar 2019 14:27:40 +1100 Subject: [PATCH 3/5] DOC: Update whatsnew v0.25.0 for named kwarg `eval` fix (#25813) --- doc/source/whatsnew/v0.25.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 2ed2c21ba5584..080bd9836ebcc 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -322,7 +322,7 @@ Sparse Other ^^^^^ -- +- Bug in :class:`BaseExprVisitor` for using named kwargs in :func:`eval` expressions. - - From 36b0f4342d23a1821188cccbb9532c1c8000a994 Mon Sep 17 00:00:00 2001 From: Dave Cole Date: Fri, 22 Mar 2019 09:27:42 +1100 Subject: [PATCH 4/5] DOC: Remove trailing whitespace --- doc/source/whatsnew/v0.25.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 080bd9836ebcc..42b92a31a8db8 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -322,7 +322,7 @@ Sparse Other ^^^^^ -- Bug in :class:`BaseExprVisitor` for using named kwargs in :func:`eval` expressions. +- Bug in :class:`BaseExprVisitor` for using named kwargs in :func:`eval` expressions. - - From 223e034555853336d410916f26810f28b6d9dd89 Mon Sep 17 00:00:00 2001 From: dave Date: Sat, 13 Apr 2019 14:30:42 +1000 Subject: [PATCH 5/5] TST: Test explicitly pd.eval with named keyword argument (#25813) --- doc/source/whatsnew/v0.25.0.rst | 2 +- pandas/tests/computation/test_eval.py | 35 ++++++++++++++++++--------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 1d58bdb52ee32..9e2e3faa8386d 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -412,7 +412,7 @@ Other - Improved :class:`Timestamp` type checking in various datetime functions to prevent exceptions when using a subclassed `datetime` (:issue:`25851`) - Bug in :class:`Series` and :class:`DataFrame` repr where ``np.datetime64('NaT')`` and ``np.timedelta64('NaT')`` with ``dtype=object`` would be represented as ``NaN`` (:issue:`25445`) -- Bug in :class:`BaseExprVisitor` which caused :func:`eval` expressions to fail when named keyword arguments were included within the expression string. +- Bug in :class:`BaseExprVisitor` which caused :func:`eval` expressions to fail when named keyword arguments were included within the expression string (:issue:`25813`) - - diff --git a/pandas/tests/computation/test_eval.py b/pandas/tests/computation/test_eval.py index 3ac2896dcfa93..b11a5e975149d 100644 --- a/pandas/tests/computation/test_eval.py +++ b/pandas/tests/computation/test_eval.py @@ -1757,6 +1757,29 @@ def test_no_new_globals(self, engine, parser): assert gbls == gbls2 +class TestEvalNamedKWargs(object): + # xref https://github.com/pandas-dev/pandas/issues/25813 + + def test_eval_with_kwargs(self, engine, parser): + def fn(x): + return 2 * x + result = pd.eval("fn(x=2)", engine=engine, parser=parser) + assert result == 4 + + def test_query_str_contains(self, parser): + df = pd.DataFrame([["I", "XYZ"], ["IJ", None]], columns=['A', 'B']) + + expected = df[df["A"].str.contains("J")] + result = df.query("A.str.contains('J')", engine="python", + parser=parser) + tm.assert_frame_equal(result, expected) + + expected = df[df["B"].str.contains("Z", na=False)] + result = df.query("B.str.contains('Z', na=False)", engine="python", + parser=parser) + tm.assert_frame_equal(result, expected) + + @td.skip_if_no_ne def test_invalid_engine(): msg = 'Invalid engine \'asdf\' passed' @@ -1890,15 +1913,3 @@ def test_validate_bool_args(self): for value in invalid_values: with pytest.raises(ValueError): pd.eval("2+2", inplace=value) - - -def test_query_str_contains(): - df = pd.DataFrame([["I", "XYZ"], ["IJ", None]], columns=['A', 'B']) - - expected = df[df["A"].str.contains("J")] - result = df.query("A.str.contains('J')", engine="python") - tm.assert_frame_equal(result, expected) - - expected = df[df["B"].str.contains("Z", na=False)] - result = df.query("B.str.contains('Z', na=False)", engine="python") - tm.assert_frame_equal(result, expected)