Repository: narwhals-dev/narwhals
Pull request #510 (2024-07-13 13:19:50)

feat: add some right-hand operations for Arrow
<!--
# Thanks for contributing a pull request! 
## Please make sure you see our contribution guidelines: https://github.com/narwhals-dev/narwhals/blob/main/CONTRIBUTING.md
-->

## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [ ] ✨ Feature
- [ ] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Related issues 

- Related issue # 
- Closes #

## Checklist

- [ ] Code follows style guide (ruff)
- [ ] Tests added 
- [ ] Documented the changes

## If you have comments or can explain your changes, please do so below.




Patch:
diff --git a/narwhals/_arrow/expr.py b/narwhals/_arrow/expr.py
index 2fd2a490..eea0c6cc 100644
--- a/narwhals/_arrow/expr.py
+++ b/narwhals/_arrow/expr.py
@@ -104,15 +104,27 @@ class ArrowExpr:
     def __add__(self, other: ArrowExpr | Any) -> Self:
         return reuse_series_implementation(self, "__add__", other)
 
+    def __radd__(self, other: ArrowExpr | Any) -> Self:
+        return reuse_series_implementation(self, "__radd__", other)
+
     def __sub__(self, other: ArrowExpr | Any) -> Self:
         return reuse_series_implementation(self, "__sub__", other)
 
+    def __rsub__(self, other: ArrowExpr | Any) -> Self:
+        return reuse_series_implementation(self, "__rsub__", other)
+
     def __mul__(self, other: ArrowExpr | Any) -> Self:
         return reuse_series_implementation(self, "__mul__", other)
 
+    def __rmul__(self, other: ArrowExpr | Any) -> Self:
+        return reuse_series_implementation(self, "__rmul__", other)
+
     def __pow__(self, other: ArrowExpr | Any) -> Self:
         return reuse_series_implementation(self, "__pow__", other)
 
+    def __rpow__(self, other: ArrowExpr | Any) -> Self:
+        return reuse_series_implementation(self, "__rpow__", other)
+
     def filter(self, *predicates: Any) -> Self:
         from narwhals._arrow.namespace import ArrowNamespace
 
diff --git a/narwhals/_arrow/series.py b/narwhals/_arrow/series.py
index 0b732980..8c49bb39 100644
--- a/narwhals/_arrow/series.py
+++ b/narwhals/_arrow/series.py
@@ -108,22 +108,37 @@ class ArrowSeries:
         other = validate_column_comparand(other)
         return self._from_native_series(pc.add(self._native_series, other))
 
+    def __radd__(self, other: Any) -> Self:
+        return self + other  # type: ignore[no-any-return]
+
     def __sub__(self, other: Any) -> Self:
         pc = get_pyarrow_compute()
         other = validate_column_comparand(other)
         return self._from_native_series(pc.subtract(self._native_series, other))
 
+    def __rsub__(self, other: Any) -> Self:
+        return (self - other) * (-1)  # type: ignore[no-any-return]
+
     def __mul__(self, other: Any) -> Self:
         pc = get_pyarrow_compute()
         other = validate_column_comparand(other)
         return self._from_native_series(pc.multiply(self._native_series, other))
 
+    def __rmul__(self, other: Any) -> Self:
+        return self * other  # type: ignore[no-any-return]
+
     def __pow__(self, other: Any) -> Self:
         pc = get_pyarrow_compute()
         ser = self._native_series
         other = validate_column_comparand(other)
         return self._from_native_series(pc.power(ser, other))
 
+    def __rpow__(self, other: Any) -> Self:
+        pc = get_pyarrow_compute()
+        ser = self._native_series
+        other = validate_column_comparand(other)
+        return self._from_native_series(pc.power(other, ser))
+
     def filter(self, other: Any) -> Self:
         other = validate_column_comparand(other)
         return self._from_native_series(self._native_series.filter(other))
diff --git a/narwhals/series.py b/narwhals/series.py
index 5fe00571..6f0632e3 100644
--- a/narwhals/series.py
+++ b/narwhals/series.py
@@ -1259,6 +1259,11 @@ class Series:
             self._compliant_series.__truediv__(self._extract_native(other))
         )
 
+    def __rtruediv__(self, other: object) -> Self:
+        return self._from_compliant_series(
+            self._compliant_series.__rtruediv__(self._extract_native(other))
+        )
+
     def __floordiv__(self, other: object) -> Self:
         return self._from_compliant_series(
             self._compliant_series.__floordiv__(self._extract_native(other))


Test patch:
diff --git a/tests/expr/arithmetic_test.py b/tests/expr/arithmetic_test.py
index d45b5c7b..d91a99b4 100644
--- a/tests/expr/arithmetic_test.py
+++ b/tests/expr/arithmetic_test.py
@@ -38,3 +38,37 @@ def test_arithmetic(
     df = nw.from_native(constructor_with_pyarrow(data))
     result = df.select(getattr(nw.col("a"), attr)(rhs))
     compare_dicts(result, {"a": expected})
+
+
+@pytest.mark.parametrize(
+    ("attr", "rhs", "expected"),
+    [
+        ("__radd__", 1, [2, 3, 4]),
+        ("__rsub__", 1, [0, -1, -2]),
+        ("__rmul__", 2, [2, 4, 6]),
+        ("__rtruediv__", 2.0, [2, 1, 2 / 3]),
+        ("__rfloordiv__", 2, [2, 1, 0]),
+        ("__rmod__", 2, [0, 0, 2]),
+        ("__rpow__", 2, [2, 4, 8]),
+    ],
+)
+def test_right_arithmetic(
+    attr: str, rhs: Any, expected: list[Any], constructor_with_pyarrow: Any, request: Any
+) -> None:
+    if "pandas_pyarrow" in str(constructor_with_pyarrow) and attr in {"__rmod__"}:
+        request.applymarker(pytest.mark.xfail)
+
+    # pyarrow case
+    if "table" in str(constructor_with_pyarrow) and attr in {
+        "__rtruediv__",
+        "__rfloordiv__",
+        "__rmod__",
+    }:
+        request.applymarker(pytest.mark.xfail)
+
+    data = {"a": [1, 2, 3]}
+    df = nw.from_native(constructor_with_pyarrow(data))
+    result = df.select(a=getattr(nw.col("a"), attr)(rhs))
+    compare_dicts(result, {"a": expected})
+    result = df.select(a=getattr(df["a"], attr)(rhs))
+    compare_dicts(result, {"a": expected})
diff --git a/tests/series/arithmetic_test.py b/tests/series/arithmetic_test.py
index 063d4197..5350650f 100644
--- a/tests/series/arithmetic_test.py
+++ b/tests/series/arithmetic_test.py
@@ -43,30 +43,3 @@ def test_arithmetic(
     s = nw.from_native(constructor_series_with_pyarrow(data), series_only=True)
     result = getattr(s, attr)(rhs)
     assert result.to_numpy().tolist() == expected
-
-
-@pytest.mark.parametrize("data", [[1, 2, 3]])
-@pytest.mark.parametrize(
-    ("attr", "rhs", "expected"),
-    [
-        ("__radd__", 1, [2, 3, 4]),
-        ("__rsub__", 1, [0, -1, -2]),
-        ("__rmul__", 2, [2, 4, 6]),
-        ("__rfloordiv__", 2, [2, 1, 0]),
-        ("__rmod__", 2, [0, 0, 2]),
-        ("__rpow__", 2, [2, 4, 8]),
-    ],
-)
-def test_rarithmetic(
-    data: list[int | float],
-    attr: str,
-    rhs: Any,
-    expected: list[Any],
-    constructor_series: Any,
-    request: Any,
-) -> None:
-    if "pyarrow" in str(constructor_series) and attr == "__rmod__":
-        request.applymarker(pytest.mark.xfail)
-    s = nw.from_native(constructor_series(data), series_only=True)
-    result = getattr(s, attr)(rhs)
-    assert result.to_numpy().tolist() == expected