Evaluate an expression string using xarray's native operations.
(self, expr: str)
| 9588 | ) |
| 9589 | |
| 9590 | def _eval_expression(self, expr: str) -> DataArray: |
| 9591 | """Evaluate an expression string using xarray's native operations.""" |
| 9592 | try: |
| 9593 | tree = ast.parse(expr, mode="eval") |
| 9594 | except SyntaxError as e: |
| 9595 | raise ValueError(f"Invalid expression syntax: {expr}") from e |
| 9596 | |
| 9597 | # Transform logical operators for consistency with query(). |
| 9598 | # See LogicalOperatorTransformer docstring for details. |
| 9599 | tree = LogicalOperatorTransformer().visit(tree) |
| 9600 | ast.fix_missing_locations(tree) |
| 9601 | |
| 9602 | validate_expression(tree) |
| 9603 | |
| 9604 | # Build namespace: data variables, coordinates, modules, and safe builtins. |
| 9605 | # Empty __builtins__ blocks dangerous functions like __import__, exec, open. |
| 9606 | # Priority order (highest to lowest): data variables > coordinates > modules > builtins |
| 9607 | # This ensures user data always wins when names collide with builtins. |
| 9608 | import xarray as xr # Lazy import to avoid circular dependency |
| 9609 | |
| 9610 | namespace: dict[str, Any] = dict(EVAL_BUILTINS) |
| 9611 | namespace.update({"np": np, "pd": pd, "xr": xr}) |
| 9612 | namespace.update({str(name): self.coords[name] for name in self.coords}) |
| 9613 | namespace.update({str(name): self[name] for name in self.data_vars}) |
| 9614 | |
| 9615 | code = compile(tree, "<xarray.eval>", "eval") |
| 9616 | return builtins.eval(code, {"__builtins__": {}}, namespace) |
| 9617 | |
| 9618 | def eval( |
| 9619 | self, |
no test coverage detected