Repository: narwhals-dev/narwhals
Pull request #535 (2024-07-16 07:11:40)

ci: remove [dev] from sklego install
<!--
# 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/dataframe.py b/narwhals/_arrow/dataframe.py
index 10696d6d..94dccd0c 100644
--- a/narwhals/_arrow/dataframe.py
+++ b/narwhals/_arrow/dataframe.py
@@ -343,7 +343,8 @@ class ArrowDataFrame:
         )
 
     def clone(self) -> Self:
-        raise NotImplementedError("clone is not yet supported on PyArrow tables")
+        msg = "clone is not yet supported on PyArrow tables"
+        raise NotImplementedError(msg)
 
     def is_empty(self: Self) -> bool:
         return self.shape[0] == 0
diff --git a/narwhals/_arrow/expr.py b/narwhals/_arrow/expr.py
index 9635b835..bf3cc7de 100644
--- a/narwhals/_arrow/expr.py
+++ b/narwhals/_arrow/expr.py
@@ -125,6 +125,9 @@ class ArrowExpr:
     def __rpow__(self, other: ArrowExpr | Any) -> Self:
         return reuse_series_implementation(self, "__rpow__", other)
 
+    def __invert__(self) -> Self:
+        return reuse_series_implementation(self, "__invert__")
+
     def filter(self, *predicates: Any) -> Self:
         from narwhals._arrow.namespace import ArrowNamespace
 
@@ -183,6 +186,9 @@ class ArrowExpr:
     def null_count(self) -> Self:
         return reuse_series_implementation(self, "null_count", returns_scalar=True)
 
+    def is_null(self) -> Self:
+        return reuse_series_implementation(self, "is_null")
+
     def head(self, n: int) -> Self:
         return reuse_series_implementation(self, "head", n)
 
diff --git a/narwhals/_arrow/series.py b/narwhals/_arrow/series.py
index 5d1ae9a9..b0f2b6ae 100644
--- a/narwhals/_arrow/series.py
+++ b/narwhals/_arrow/series.py
@@ -139,6 +139,10 @@ class ArrowSeries:
         other = validate_column_comparand(other)
         return self._from_native_series(pc.power(other, ser))
 
+    def __invert__(self) -> Self:
+        pc = get_pyarrow_compute()
+        return self._from_native_series(pc.invert(self._native_series))
+
     def filter(self, other: Any) -> Self:
         other = validate_column_comparand(other)
         return self._from_native_series(self._native_series.filter(other))
@@ -229,6 +233,10 @@ class ArrowSeries:
     def is_empty(self) -> bool:
         return len(self) == 0
 
+    def is_null(self) -> Self:
+        ser = self._native_series
+        return self._from_native_series(ser.is_null())
+
     def cast(self, dtype: DType) -> Self:
         pc = get_pyarrow_compute()
         ser = self._native_series
diff --git a/narwhals/_interchange/dataframe.py b/narwhals/_interchange/dataframe.py
index 16c8c54d..c7fc5ea3 100644
--- a/narwhals/_interchange/dataframe.py
+++ b/narwhals/_interchange/dataframe.py
@@ -34,7 +34,8 @@ def map_interchange_dtype_to_narwhals_dtype(
             return dtypes.Int16()
         if interchange_dtype[1] == 8:
             return dtypes.Int8()
-        raise AssertionError("Invalid bit width for INT")
+        msg = "Invalid bit width for INT"  # pragma: no cover
+        raise AssertionError(msg)
     if interchange_dtype[0] == DtypeKind.UINT:
         if interchange_dtype[1] == 64:
             return dtypes.UInt64()
@@ -44,13 +45,15 @@ def map_interchange_dtype_to_narwhals_dtype(
             return dtypes.UInt16()
         if interchange_dtype[1] == 8:
             return dtypes.UInt8()
-        raise AssertionError("Invalid bit width for UINT")
+        msg = "Invalid bit width for UINT"  # pragma: no cover
+        raise AssertionError(msg)
     if interchange_dtype[0] == DtypeKind.FLOAT:
         if interchange_dtype[1] == 64:
             return dtypes.Float64()
         if interchange_dtype[1] == 32:
             return dtypes.Float32()
-        raise AssertionError("Invalid bit width for FLOAT")
+        msg = "Invalid bit width for FLOAT"  # pragma: no cover
+        raise AssertionError(msg)
     if interchange_dtype[0] == DtypeKind.BOOL:
         return dtypes.Boolean()
     if interchange_dtype[0] == DtypeKind.STRING:
diff --git a/narwhals/expression.py b/narwhals/expression.py
index 02f7644d..a6af772a 100644
--- a/narwhals/expression.py
+++ b/narwhals/expression.py
@@ -951,9 +951,8 @@ class Expr:
         if isinstance(other, Iterable) and not isinstance(other, (str, bytes)):
             return self.__class__(lambda plx: self._call(plx).is_in(other))
         else:
-            raise NotImplementedError(
-                "Narwhals `is_in` doesn't accept expressions as an argument, as opposed to Polars. You should provide an iterable instead."
-            )
+            msg = "Narwhals `is_in` doesn't accept expressions as an argument, as opposed to Polars. You should provide an iterable instead."
+            raise NotImplementedError(msg)
 
     def filter(self, *predicates: Any) -> Self:
         """
diff --git a/narwhals/functions.py b/narwhals/functions.py
index 9184a8cf..24d299be 100644
--- a/narwhals/functions.py
+++ b/narwhals/functions.py
@@ -28,10 +28,9 @@ def concat(
     *,
     how: Literal["horizontal", "vertical"] = "vertical",
 ) -> FrameT:
-    if how not in ("horizontal", "vertical"):
-        raise NotImplementedError(
-            "Only horizontal and vertical concatenations are supported"
-        )
+    if how not in ("horizontal", "vertical"):  # pragma: no cover
+        msg = "Only horizontal and vertical concatenations are supported"
+        raise NotImplementedError(msg)
     if not items:
         msg = "No items to concatenate"
         raise ValueError(msg)
diff --git a/narwhals/utils.py b/narwhals/utils.py
index ee19d4b6..1f6a9074 100644
--- a/narwhals/utils.py
+++ b/narwhals/utils.py
@@ -92,7 +92,8 @@ def validate_same_library(items: Iterable[Any]) -> None:
         len({item._compliant_frame._implementation for item in items}) == 1
     ):
         return
-    raise NotImplementedError("Cross-library comparisons aren't supported")
+    msg = "Cross-library comparisons aren't supported"
+    raise NotImplementedError(msg)
 
 
 def validate_laziness(items: Iterable[Any]) -> None:
@@ -103,9 +104,8 @@ def validate_laziness(items: Iterable[Any]) -> None:
         all(isinstance(item, LazyFrame) for item in items)
     ):
         return
-    raise NotImplementedError(
-        "The items to concatenate should either all be eager, or all lazy"
-    )
+    msg = "The items to concatenate should either all be eager, or all lazy"
+    raise NotImplementedError(msg)
 
 
 def maybe_align_index(lhs: T, rhs: Series | BaseFrame[Any]) -> T:
diff --git a/pyproject.toml b/pyproject.toml
index 8976a515..434e74fb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,34 +43,32 @@ lint.select = [
   "ALL",
 ]
 lint.ignore = [
-  'A001',
-  'A003',
-  'ANN101',
-  'ANN401',
-  'C901',
-  'COM812',
-  'D',
-  'DTZ001',
-  'E501',
-  'EM101',
-  'FIX',
-  'ISC001',
-  'PD901',  # This is a auxiliary library so dataframe variables have no concrete business meaning
-  'PLR0911',
-  'PLR0912',
-  'PLR0913',
-  'PLR2004',
-  'PTH',
-  'RET505',
-  'SLF001',
-  'TD003',
-  'TRY003',  # TODO(Unassigned): enable
-  'TRY004'
+  "A001",
+  "ANN101",
+  "ANN401",
+  "C901",
+  "COM812",
+  "D",
+  "DTZ001",
+  "E501",
+  "FIX",
+  "ISC001",
+  "PD901",  # This is a auxiliary library so dataframe variables have no concrete business meaning
+  "PLR0911",
+  "PLR0912",
+  "PLR0913",
+  "PLR2004",
+  "RET505",
+  "SLF001",
+  "TD003",
 ]
 
 [tool.ruff.lint.per-file-ignores]
 "tests/*" = ["S101"]
-"utils/*" = ["S311"]
+"utils/*" = ["S311", "PTH123"]
+
+[tool.ruff.lint.pydocstyle]
+convention = "google"
 
 [tool.ruff.lint.isort]
 force-single-line = true
diff --git a/utils/check_backend_completeness.py b/utils/check_backend_completeness.py
index ab2eec28..f412fc5e 100644
--- a/utils/check_backend_completeness.py
+++ b/utils/check_backend_completeness.py
@@ -25,7 +25,6 @@ MISSING = [
     "Series.is_first_distinct",
     "Series.is_in",
     "Series.is_last_distinct",
-    "Series.is_null",
     "Series.is_sorted",
     "Series.is_unique",
     "Series.len",


Test patch:
diff --git a/.github/workflows/downstream_tests.yml b/.github/workflows/downstream_tests.yml
index 9a587f02..3ae02c65 100644
--- a/.github/workflows/downstream_tests.yml
+++ b/.github/workflows/downstream_tests.yml
@@ -76,7 +76,7 @@ jobs:
       - name: install-scikit-lego-dev
         run: |
             cd scikit-lego
-            pip install -e ".[dev, test]"
+            pip install -e ".[test]"
       - name: install-narwhals-dev
         run: |
             pip uninstall narwhals -y
diff --git a/tests/frame/test_common.py b/tests/frame/test_common.py
index 01eb3495..03efae10 100644
--- a/tests/frame/test_common.py
+++ b/tests/frame/test_common.py
@@ -152,10 +152,7 @@ def test_expr_min_max(request: Any, constructor: Any) -> None:
     compare_dicts(result_max, expected_max)
 
 
-def test_expr_na(request: Any, constructor_with_lazy: Any) -> None:
-    if "pyarrow_table" in str(constructor_with_lazy):
-        request.applymarker(pytest.mark.xfail)
-
+def test_expr_na(constructor_with_lazy: Any) -> None:
     df = nw.from_native(constructor_with_lazy(data_na)).lazy()
     result_nna = df.filter((~nw.col("a").is_null()) & (~df.collect()["z"].is_null()))
     expected = {"a": [2], "b": [6], "z": [9]}
diff --git a/tests/frame/write_parquet_test.py b/tests/frame/write_parquet_test.py
index 88e35b3f..f0735692 100644
--- a/tests/frame/write_parquet_test.py
+++ b/tests/frame/write_parquet_test.py
@@ -1,6 +1,5 @@
 from __future__ import annotations
 
-import os
 from typing import Any
 
 import pandas as pd
@@ -16,6 +15,6 @@ data = {"a": [1, 2, 3]}
     parse_version(pd.__version__) < parse_version("2.0.0"), reason="too old for pyarrow"
 )
 def test_write_parquet(constructor: Any, tmpdir: pytest.TempdirFactory) -> None:
-    path = str(tmpdir / "foo.parquet")  # type: ignore[operator]
-    nw.from_native(constructor(data), eager_only=True).write_parquet(path)
-    assert os.path.exists(path)
+    path = tmpdir / "foo.parquet"  # type: ignore[operator]
+    nw.from_native(constructor(data), eager_only=True).write_parquet(str(path))
+    assert path.exists()