From 7f486ce421584d7b84cdbe14f769a3ccd77d5260 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 17 Apr 2021 12:36:04 -0700 Subject: [PATCH 1/5] TYP: PyTablesExpr --- pandas/core/computation/pytables.py | 4 +++- pandas/core/reshape/pivot.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/core/computation/pytables.py b/pandas/core/computation/pytables.py index 891642bf61d16..0e6a7551ab399 100644 --- a/pandas/core/computation/pytables.py +++ b/pandas/core/computation/pytables.py @@ -546,6 +546,7 @@ class PyTablesExpr(expr.Expr): _visitor: PyTablesExprVisitor | None env: PyTablesScope + expr: str def __init__( self, @@ -570,7 +571,7 @@ def __init__( local_dict = where.env.scope _where = where.expr - elif isinstance(where, (list, tuple)): + elif is_list_like(where): where = list(where) for idx, w in enumerate(where): if isinstance(w, PyTablesExpr): @@ -580,6 +581,7 @@ def __init__( where[idx] = w _where = " & ".join(f"({w})" for w in com.flatten(where)) else: + # _validate_where ensures we otherwise have a string _where = where self.expr = _where diff --git a/pandas/core/reshape/pivot.py b/pandas/core/reshape/pivot.py index ddc6e92b04927..4c73e5d594d2b 100644 --- a/pandas/core/reshape/pivot.py +++ b/pandas/core/reshape/pivot.py @@ -180,7 +180,6 @@ def __internal_pivot_table( # TODO: why does test_pivot_table_doctest_case fail if # we don't do this apparently-unnecessary setitem? agged[v] = agged[v] - pass else: agged[v] = maybe_downcast_to_dtype(agged[v], data[v].dtype) From 2270981a8c6a52d8bbe9e4bf286123d8172c0017 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 17 Apr 2021 13:28:01 -0700 Subject: [PATCH 2/5] update test --- pandas/tests/io/pytables/test_store.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pandas/tests/io/pytables/test_store.py b/pandas/tests/io/pytables/test_store.py index bb6928d2fd95a..c61864bbc0a76 100644 --- a/pandas/tests/io/pytables/test_store.py +++ b/pandas/tests/io/pytables/test_store.py @@ -673,17 +673,20 @@ def test_coordinates(setup_path): tm.assert_frame_equal(result, expected) # invalid - msg = "cannot process expression" - with pytest.raises(ValueError, match=msg): + msg = ( + "where must be passed as a string, PyTablesExpr, " + "or list-like of PyTablesExpr" + ) + with pytest.raises(TypeError, match=msg): store.select("df", where=np.arange(len(df), dtype="float64")) - with pytest.raises(ValueError, match=msg): + with pytest.raises(TypeError, match=msg): store.select("df", where=np.arange(len(df) + 1)) - with pytest.raises(ValueError, match=msg): + with pytest.raises(TypeError, match=msg): store.select("df", where=np.arange(len(df)), start=5) - with pytest.raises(ValueError, match=msg): + with pytest.raises(TypeError, match=msg): store.select("df", where=np.arange(len(df)), start=5, stop=10) # selection with filter From 0c5b1b26964477f57e35df59786cb034fbc1cd72 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 17 Apr 2021 14:46:49 -0700 Subject: [PATCH 3/5] TYP: annotate computation --- pandas/core/computation/engines.py | 5 +++-- pandas/core/computation/eval.py | 11 ++++++----- pandas/core/computation/scope.py | 17 ++++++++++------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/pandas/core/computation/engines.py b/pandas/core/computation/engines.py index 5b2dbed7af6ea..7452cf03d0038 100644 --- a/pandas/core/computation/engines.py +++ b/pandas/core/computation/engines.py @@ -12,6 +12,7 @@ align_terms, reconstruct_object, ) +from pandas.core.computation.expr import Expr from pandas.core.computation.ops import ( MATHOPS, REDUCTIONS, @@ -26,13 +27,13 @@ class NumExprClobberingError(NameError): pass -def _check_ne_builtin_clash(expr): +def _check_ne_builtin_clash(expr: Expr) -> None: """ Attempt to prevent foot-shooting in a helpful way. Parameters ---------- - terms : Term + expr : Expr Terms can contain """ names = expr.names diff --git a/pandas/core/computation/eval.py b/pandas/core/computation/eval.py index 51fcbb02fd926..726232fbbd9ba 100644 --- a/pandas/core/computation/eval.py +++ b/pandas/core/computation/eval.py @@ -1,9 +1,9 @@ """ Top level ``eval`` module. """ +from __future__ import annotations import tokenize -from typing import Optional import warnings from pandas._libs.lib import no_default @@ -20,7 +20,7 @@ from pandas.io.formats.printing import pprint_thing -def _check_engine(engine: Optional[str]) -> str: +def _check_engine(engine: str | None) -> str: """ Make sure a valid engine is passed. @@ -161,9 +161,9 @@ def _check_for_locals(expr: str, stack_level: int, parser: str): def eval( - expr, - parser="pandas", - engine: Optional[str] = None, + expr: str, + parser: str = "pandas", + engine: str | None = None, truediv=no_default, local_dict=None, global_dict=None, @@ -313,6 +313,7 @@ def eval( _check_expression(expr) exprs = [e.strip() for e in expr.splitlines() if e.strip() != ""] else: + # ops.BinOp; for internal compat, not intended to be passed by users exprs = [expr] multi_line = len(exprs) > 1 diff --git a/pandas/core/computation/scope.py b/pandas/core/computation/scope.py index ea92a33f242e9..09067e7eba6e5 100644 --- a/pandas/core/computation/scope.py +++ b/pandas/core/computation/scope.py @@ -106,9 +106,13 @@ class Scope: """ __slots__ = ["level", "scope", "target", "resolvers", "temps"] + level: int + scope: DeepChainMap + resolvers: DeepChainMap + temps: dict def __init__( - self, level, global_dict=None, local_dict=None, resolvers=(), target=None + self, level: int, global_dict=None, local_dict=None, resolvers=(), target=None ): self.level = level + 1 @@ -146,8 +150,7 @@ def __init__( # assumes that resolvers are going from outermost scope to inner if isinstance(local_dict, Scope): - # error: Cannot determine type of 'resolvers' - resolvers += tuple(local_dict.resolvers.maps) # type: ignore[has-type] + resolvers += tuple(local_dict.resolvers.maps) self.resolvers = DeepChainMap(*resolvers) self.temps = {} @@ -212,7 +215,7 @@ def resolve(self, key: str, is_local: bool): raise UndefinedVariableError(key, is_local) from err - def swapkey(self, old_key: str, new_key: str, new_value=None): + def swapkey(self, old_key: str, new_key: str, new_value=None) -> None: """ Replace a variable name, with a potentially new value. @@ -238,7 +241,7 @@ def swapkey(self, old_key: str, new_key: str, new_value=None): mapping[new_key] = new_value # type: ignore[index] return - def _get_vars(self, stack, scopes: list[str]): + def _get_vars(self, stack, scopes: list[str]) -> None: """ Get specifically scoped variables from a list of stack frames. @@ -263,7 +266,7 @@ def _get_vars(self, stack, scopes: list[str]): # scope after the loop del frame - def _update(self, level: int): + def _update(self, level: int) -> None: """ Update the current scope by going back `level` levels. @@ -313,7 +316,7 @@ def ntemps(self) -> int: return len(self.temps) @property - def full_scope(self): + def full_scope(self) -> DeepChainMap: """ Return the full scope for use with passing to engines transparently as a mapping. From 79bb4fc0401dc44ba65df4df6b4b46718b00316c Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 17 Apr 2021 16:47:52 -0700 Subject: [PATCH 4/5] mypy fixup --- pandas/core/computation/eval.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/core/computation/eval.py b/pandas/core/computation/eval.py index 726232fbbd9ba..8ab8b588aea21 100644 --- a/pandas/core/computation/eval.py +++ b/pandas/core/computation/eval.py @@ -14,6 +14,7 @@ PARSERS, Expr, ) +from pandas.core.computation.ops import BinOp from pandas.core.computation.parsing import tokenize_string from pandas.core.computation.scope import ensure_scope @@ -161,7 +162,7 @@ def _check_for_locals(expr: str, stack_level: int, parser: str): def eval( - expr: str, + expr: str | BinOp, # we leave BinOp out of the docstr bc it isn't for users parser: str = "pandas", engine: str | None = None, truediv=no_default, From e0418f6d772a9e743d4da9e97928284e371f586e Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 17 Apr 2021 22:16:01 -0700 Subject: [PATCH 5/5] mypy fixup --- pandas/core/computation/eval.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/core/computation/eval.py b/pandas/core/computation/eval.py index 8ab8b588aea21..57ba478a9157b 100644 --- a/pandas/core/computation/eval.py +++ b/pandas/core/computation/eval.py @@ -310,6 +310,7 @@ def eval( stacklevel=2, ) + exprs: list[str | BinOp] if isinstance(expr, str): _check_expression(expr) exprs = [e.strip() for e in expr.splitlines() if e.strip() != ""]