From 747e793af5003c4de61df43b4cc7ab8f28eec249 Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Tue, 14 Jan 2025 15:06:17 +0100 Subject: [PATCH 1/4] feat: pyspark name namespace --- narwhals/_spark_like/expr.py | 5 + narwhals/_spark_like/expr_name.py | 175 ++++++++++++++++++ tests/expr_and_series/name/keep_test.py | 19 +- tests/expr_and_series/name/map_test.py | 19 +- tests/expr_and_series/name/prefix_test.py | 19 +- tests/expr_and_series/name/suffix_test.py | 19 +- .../expr_and_series/name/to_lowercase_test.py | 19 +- .../expr_and_series/name/to_uppercase_test.py | 6 +- 8 files changed, 198 insertions(+), 83 deletions(-) create mode 100644 narwhals/_spark_like/expr_name.py diff --git a/narwhals/_spark_like/expr.py b/narwhals/_spark_like/expr.py index 07b68d26f..4958b2ba2 100644 --- a/narwhals/_spark_like/expr.py +++ b/narwhals/_spark_like/expr.py @@ -7,6 +7,7 @@ from typing import Sequence from narwhals._expression_parsing import infer_new_root_output_names +from narwhals._spark_like.expr_name import SparkLikeExprNameNamespace from narwhals._spark_like.expr_str import SparkLikeExprStringNamespace from narwhals._spark_like.utils import get_column_name from narwhals._spark_like.utils import maybe_evaluate @@ -499,3 +500,7 @@ def is_null(self: Self) -> Self: @property def str(self: Self) -> SparkLikeExprStringNamespace: return SparkLikeExprStringNamespace(self) + + @property + def name(self: Self) -> SparkLikeExprNameNamespace: + return SparkLikeExprNameNamespace(self) diff --git a/narwhals/_spark_like/expr_name.py b/narwhals/_spark_like/expr_name.py new file mode 100644 index 000000000..6c52d2326 --- /dev/null +++ b/narwhals/_spark_like/expr_name.py @@ -0,0 +1,175 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Callable + +if TYPE_CHECKING: + from typing_extensions import Self + + from narwhals._spark_like.expr import SparkLikeExpr + + +class SparkLikeExprNameNamespace: + def __init__(self: Self, expr: SparkLikeExpr) -> None: + self._compliant_expr = expr + + def keep(self: Self) -> SparkLikeExpr: + root_names = self._compliant_expr._root_names + + if root_names is None: + msg = ( + "Anonymous expressions are not supported in `.name.keep`.\n" + "Instead of `nw.all()`, try using a named expression, such as " + "`nw.col('a', 'b')`\n" + ) + raise ValueError(msg) + + return self._compliant_expr.__class__( + lambda df: [ + expr.alias(name) + for expr, name in zip(self._compliant_expr._call(df), root_names) + ], + depth=self._compliant_expr._depth, + function_name=self._compliant_expr._function_name, + root_names=root_names, + output_names=root_names, + returns_scalar=self._compliant_expr._returns_scalar, + backend_version=self._compliant_expr._backend_version, + version=self._compliant_expr._version, + kwargs=self._compliant_expr._kwargs, + ) + + def map(self: Self, function: Callable[[str], str]) -> SparkLikeExpr: + root_names = self._compliant_expr._root_names + + if root_names is None: + msg = ( + "Anonymous expressions are not supported in `.name.map`.\n" + "Instead of `nw.all()`, try using a named expression, such as " + "`nw.col('a', 'b')`\n" + ) + raise ValueError(msg) + + output_names = [function(str(name)) for name in root_names] + + return self._compliant_expr.__class__( + lambda df: [ + expr.alias(name) + for expr, name in zip(self._compliant_expr._call(df), output_names) + ], + depth=self._compliant_expr._depth, + function_name=self._compliant_expr._function_name, + root_names=root_names, + output_names=output_names, + returns_scalar=self._compliant_expr._returns_scalar, + backend_version=self._compliant_expr._backend_version, + version=self._compliant_expr._version, + kwargs={**self._compliant_expr._kwargs, "function": function}, + ) + + def prefix(self: Self, prefix: str) -> SparkLikeExpr: + root_names = self._compliant_expr._root_names + if root_names is None: + msg = ( + "Anonymous expressions are not supported in `.name.prefix`.\n" + "Instead of `nw.all()`, try using a named expression, such as " + "`nw.col('a', 'b')`\n" + ) + raise ValueError(msg) + + output_names = [prefix + str(name) for name in root_names] + return self._compliant_expr.__class__( + lambda df: [ + expr.alias(name) + for expr, name in zip(self._compliant_expr._call(df), output_names) + ], + depth=self._compliant_expr._depth, + function_name=self._compliant_expr._function_name, + root_names=root_names, + output_names=output_names, + returns_scalar=self._compliant_expr._returns_scalar, + backend_version=self._compliant_expr._backend_version, + version=self._compliant_expr._version, + kwargs={**self._compliant_expr._kwargs, "prefix": prefix}, + ) + + def suffix(self: Self, suffix: str) -> SparkLikeExpr: + root_names = self._compliant_expr._root_names + if root_names is None: + msg = ( + "Anonymous expressions are not supported in `.name.suffix`.\n" + "Instead of `nw.all()`, try using a named expression, such as " + "`nw.col('a', 'b')`\n" + ) + raise ValueError(msg) + + output_names = [str(name) + suffix for name in root_names] + + return self._compliant_expr.__class__( + lambda df: [ + expr.alias(name) + for expr, name in zip(self._compliant_expr._call(df), output_names) + ], + depth=self._compliant_expr._depth, + function_name=self._compliant_expr._function_name, + root_names=root_names, + output_names=output_names, + returns_scalar=self._compliant_expr._returns_scalar, + backend_version=self._compliant_expr._backend_version, + version=self._compliant_expr._version, + kwargs={**self._compliant_expr._kwargs, "suffix": suffix}, + ) + + def to_lowercase(self: Self) -> SparkLikeExpr: + root_names = self._compliant_expr._root_names + + if root_names is None: + msg = ( + "Anonymous expressions are not supported in `.name.to_lowercase`.\n" + "Instead of `nw.all()`, try using a named expression, such as " + "`nw.col('a', 'b')`\n" + ) + raise ValueError(msg) + output_names = [str(name).lower() for name in root_names] + + return self._compliant_expr.__class__( + lambda df: [ + expr.alias(name) + for expr, name in zip(self._compliant_expr._call(df), output_names) + ], + depth=self._compliant_expr._depth, + function_name=self._compliant_expr._function_name, + root_names=root_names, + output_names=output_names, + returns_scalar=self._compliant_expr._returns_scalar, + backend_version=self._compliant_expr._backend_version, + version=self._compliant_expr._version, + kwargs=self._compliant_expr._kwargs, + ) + + def to_uppercase(self: Self) -> SparkLikeExpr: + root_names = self._compliant_expr._root_names + + if root_names is None: + msg = ( + "Anonymous expressions are not supported in `.name.to_uppercase`.\n" + "Instead of `nw.all()`, try using a named expression, such as " + "`nw.col('a', 'b')`\n" + ) + raise ValueError(msg) + output_names = [str(name).upper() for name in root_names] + + return self._compliant_expr.__class__( + lambda df: [ + expr.alias(name) + for expr, name in zip(self._compliant_expr._call(df), output_names) + ], + depth=self._compliant_expr._depth, + function_name=self._compliant_expr._function_name, + root_names=root_names, + output_names=output_names, + returns_scalar=self._compliant_expr._returns_scalar, + backend_version=self._compliant_expr._backend_version, + version=self._compliant_expr._version, + kwargs=self._compliant_expr._kwargs, + ) diff --git a/tests/expr_and_series/name/keep_test.py b/tests/expr_and_series/name/keep_test.py index e382db733..6c89d09fc 100644 --- a/tests/expr_and_series/name/keep_test.py +++ b/tests/expr_and_series/name/keep_test.py @@ -12,34 +12,21 @@ data = {"foo": [1, 2, 3], "BAR": [4, 5, 6]} -def test_keep(request: pytest.FixtureRequest, constructor: Constructor) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_keep(constructor: Constructor) -> None: df = nw.from_native(constructor(data)) result = df.select((nw.col("foo", "BAR") * 2).name.keep()) expected = {k: [e * 2 for e in v] for k, v in data.items()} assert_equal_data(result, expected) -def test_keep_after_alias( - request: pytest.FixtureRequest, constructor: Constructor -) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_keep_after_alias(constructor: Constructor) -> None: df = nw.from_native(constructor(data)) result = df.select((nw.col("foo")).alias("alias_for_foo").name.keep()) expected = {"foo": data["foo"]} assert_equal_data(result, expected) -def test_keep_raise_anonymous( - request: pytest.FixtureRequest, constructor: Constructor -) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_keep_raise_anonymous(constructor: Constructor) -> None: df_raw = constructor(data) df = nw.from_native(df_raw) diff --git a/tests/expr_and_series/name/map_test.py b/tests/expr_and_series/name/map_test.py index 276138ef9..5afda2ee8 100644 --- a/tests/expr_and_series/name/map_test.py +++ b/tests/expr_and_series/name/map_test.py @@ -16,34 +16,21 @@ def map_func(s: str | None) -> str: return str(s)[::-1].lower() -def test_map(request: pytest.FixtureRequest, constructor: Constructor) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_map(constructor: Constructor) -> None: df = nw.from_native(constructor(data)) result = df.select((nw.col("foo", "BAR") * 2).name.map(function=map_func)) expected = {map_func(k): [e * 2 for e in v] for k, v in data.items()} assert_equal_data(result, expected) -def test_map_after_alias( - request: pytest.FixtureRequest, constructor: Constructor -) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_map_after_alias(constructor: Constructor) -> None: df = nw.from_native(constructor(data)) result = df.select((nw.col("foo")).alias("alias_for_foo").name.map(function=map_func)) expected = {map_func("foo"): data["foo"]} assert_equal_data(result, expected) -def test_map_raise_anonymous( - request: pytest.FixtureRequest, constructor: Constructor -) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_map_raise_anonymous(constructor: Constructor) -> None: df_raw = constructor(data) df = nw.from_native(df_raw) diff --git a/tests/expr_and_series/name/prefix_test.py b/tests/expr_and_series/name/prefix_test.py index 934d1d664..6f3fb3c9b 100644 --- a/tests/expr_and_series/name/prefix_test.py +++ b/tests/expr_and_series/name/prefix_test.py @@ -13,34 +13,21 @@ prefix = "with_prefix_" -def test_prefix(request: pytest.FixtureRequest, constructor: Constructor) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_prefix(constructor: Constructor) -> None: df = nw.from_native(constructor(data)) result = df.select((nw.col("foo", "BAR") * 2).name.prefix(prefix)) expected = {prefix + str(k): [e * 2 for e in v] for k, v in data.items()} assert_equal_data(result, expected) -def test_suffix_after_alias( - request: pytest.FixtureRequest, constructor: Constructor -) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_suffix_after_alias(constructor: Constructor) -> None: df = nw.from_native(constructor(data)) result = df.select((nw.col("foo")).alias("alias_for_foo").name.prefix(prefix)) expected = {prefix + "foo": data["foo"]} assert_equal_data(result, expected) -def test_prefix_raise_anonymous( - request: pytest.FixtureRequest, constructor: Constructor -) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_prefix_raise_anonymous(constructor: Constructor) -> None: df_raw = constructor(data) df = nw.from_native(df_raw) diff --git a/tests/expr_and_series/name/suffix_test.py b/tests/expr_and_series/name/suffix_test.py index 479546630..1c5816154 100644 --- a/tests/expr_and_series/name/suffix_test.py +++ b/tests/expr_and_series/name/suffix_test.py @@ -13,34 +13,21 @@ suffix = "_with_suffix" -def test_suffix(request: pytest.FixtureRequest, constructor: Constructor) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_suffix(constructor: Constructor) -> None: df = nw.from_native(constructor(data)) result = df.select((nw.col("foo", "BAR") * 2).name.suffix(suffix)) expected = {str(k) + suffix: [e * 2 for e in v] for k, v in data.items()} assert_equal_data(result, expected) -def test_suffix_after_alias( - request: pytest.FixtureRequest, constructor: Constructor -) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_suffix_after_alias(constructor: Constructor) -> None: df = nw.from_native(constructor(data)) result = df.select((nw.col("foo")).alias("alias_for_foo").name.suffix(suffix)) expected = {"foo" + suffix: data["foo"]} assert_equal_data(result, expected) -def test_suffix_raise_anonymous( - request: pytest.FixtureRequest, constructor: Constructor -) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_suffix_raise_anonymous(constructor: Constructor) -> None: df_raw = constructor(data) df = nw.from_native(df_raw) diff --git a/tests/expr_and_series/name/to_lowercase_test.py b/tests/expr_and_series/name/to_lowercase_test.py index 1b39fc726..882663f60 100644 --- a/tests/expr_and_series/name/to_lowercase_test.py +++ b/tests/expr_and_series/name/to_lowercase_test.py @@ -12,34 +12,21 @@ data = {"foo": [1, 2, 3], "BAR": [4, 5, 6]} -def test_to_lowercase(request: pytest.FixtureRequest, constructor: Constructor) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_to_lowercase(constructor: Constructor) -> None: df = nw.from_native(constructor(data)) result = df.select((nw.col("foo", "BAR") * 2).name.to_lowercase()) expected = {k.lower(): [e * 2 for e in v] for k, v in data.items()} assert_equal_data(result, expected) -def test_to_lowercase_after_alias( - request: pytest.FixtureRequest, constructor: Constructor -) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_to_lowercase_after_alias(constructor: Constructor) -> None: df = nw.from_native(constructor(data)) result = df.select((nw.col("BAR")).alias("ALIAS_FOR_BAR").name.to_lowercase()) expected = {"bar": data["BAR"]} assert_equal_data(result, expected) -def test_to_lowercase_raise_anonymous( - request: pytest.FixtureRequest, constructor: Constructor -) -> None: - if "pyspark" in str(constructor): - request.applymarker(pytest.mark.xfail) - +def test_to_lowercase_raise_anonymous(constructor: Constructor) -> None: df_raw = constructor(data) df = nw.from_native(df_raw) diff --git a/tests/expr_and_series/name/to_uppercase_test.py b/tests/expr_and_series/name/to_uppercase_test.py index e6703212d..93c4db8f6 100644 --- a/tests/expr_and_series/name/to_uppercase_test.py +++ b/tests/expr_and_series/name/to_uppercase_test.py @@ -13,7 +13,7 @@ def test_to_uppercase(constructor: Constructor, request: pytest.FixtureRequest) -> None: - if any(x in str(constructor) for x in ("duckdb", "pyspark")): + if any(x in str(constructor) for x in ("duckdb",)): request.applymarker(pytest.mark.xfail) df = nw.from_native(constructor(data)) result = df.select((nw.col("foo", "BAR") * 2).name.to_uppercase()) @@ -24,7 +24,7 @@ def test_to_uppercase(constructor: Constructor, request: pytest.FixtureRequest) def test_to_uppercase_after_alias( constructor: Constructor, request: pytest.FixtureRequest ) -> None: - if any(x in str(constructor) for x in ("duckdb", "pyspark")): + if any(x in str(constructor) for x in ("duckdb",)): request.applymarker(pytest.mark.xfail) df = nw.from_native(constructor(data)) result = df.select((nw.col("foo")).alias("alias_for_foo").name.to_uppercase()) @@ -35,7 +35,7 @@ def test_to_uppercase_after_alias( def test_to_uppercase_raise_anonymous( constructor: Constructor, request: pytest.FixtureRequest ) -> None: - if any(x in str(constructor) for x in ("duckdb", "pyspark")): + if any(x in str(constructor) for x in ("duckdb",)): request.applymarker(pytest.mark.xfail) df_raw = constructor(data) df = nw.from_native(df_raw) From 3241d49198cbbed9e4743d9069dada779c8ce96e Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Tue, 14 Jan 2025 15:11:46 +0100 Subject: [PATCH 2/4] duckdb --- narwhals/_duckdb/expr.py | 5 + narwhals/_duckdb/expr_name.py | 175 ++++++++++++++++++ tests/conftest.py | 2 +- .../expr_and_series/name/to_uppercase_test.py | 16 +- 4 files changed, 184 insertions(+), 14 deletions(-) create mode 100644 narwhals/_duckdb/expr_name.py diff --git a/narwhals/_duckdb/expr.py b/narwhals/_duckdb/expr.py index 0cfddbaa3..c79acdfae 100644 --- a/narwhals/_duckdb/expr.py +++ b/narwhals/_duckdb/expr.py @@ -7,6 +7,7 @@ from typing import Sequence from narwhals._duckdb.expr_dt import DuckDBExprDateTimeNamespace +from narwhals._duckdb.expr_name import DuckDBExprNameNamespace from narwhals._duckdb.expr_str import DuckDBExprStringNamespace from narwhals._duckdb.utils import binary_operation_returns_scalar from narwhals._duckdb.utils import get_column_name @@ -571,3 +572,7 @@ def str(self: Self) -> DuckDBExprStringNamespace: @property def dt(self: Self) -> DuckDBExprDateTimeNamespace: return DuckDBExprDateTimeNamespace(self) + + @property + def name(self: Self) -> DuckDBExprNameNamespace: + return DuckDBExprNameNamespace(self) diff --git a/narwhals/_duckdb/expr_name.py b/narwhals/_duckdb/expr_name.py new file mode 100644 index 000000000..c19b7a8c3 --- /dev/null +++ b/narwhals/_duckdb/expr_name.py @@ -0,0 +1,175 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Callable + +if TYPE_CHECKING: + from typing_extensions import Self + + from narwhals._duckdb.expr import DuckDBExpr + + +class DuckDBExprNameNamespace: + def __init__(self: Self, expr: DuckDBExpr) -> None: + self._compliant_expr = expr + + def keep(self: Self) -> DuckDBExpr: + root_names = self._compliant_expr._root_names + + if root_names is None: + msg = ( + "Anonymous expressions are not supported in `.name.keep`.\n" + "Instead of `nw.all()`, try using a named expression, such as " + "`nw.col('a', 'b')`\n" + ) + raise ValueError(msg) + + return self._compliant_expr.__class__( + lambda df: [ + expr.alias(name) + for expr, name in zip(self._compliant_expr._call(df), root_names) + ], + depth=self._compliant_expr._depth, + function_name=self._compliant_expr._function_name, + root_names=root_names, + output_names=root_names, + returns_scalar=self._compliant_expr._returns_scalar, + backend_version=self._compliant_expr._backend_version, + version=self._compliant_expr._version, + kwargs=self._compliant_expr._kwargs, + ) + + def map(self: Self, function: Callable[[str], str]) -> DuckDBExpr: + root_names = self._compliant_expr._root_names + + if root_names is None: + msg = ( + "Anonymous expressions are not supported in `.name.map`.\n" + "Instead of `nw.all()`, try using a named expression, such as " + "`nw.col('a', 'b')`\n" + ) + raise ValueError(msg) + + output_names = [function(str(name)) for name in root_names] + + return self._compliant_expr.__class__( + lambda df: [ + expr.alias(name) + for expr, name in zip(self._compliant_expr._call(df), output_names) + ], + depth=self._compliant_expr._depth, + function_name=self._compliant_expr._function_name, + root_names=root_names, + output_names=output_names, + returns_scalar=self._compliant_expr._returns_scalar, + backend_version=self._compliant_expr._backend_version, + version=self._compliant_expr._version, + kwargs={**self._compliant_expr._kwargs, "function": function}, + ) + + def prefix(self: Self, prefix: str) -> DuckDBExpr: + root_names = self._compliant_expr._root_names + if root_names is None: + msg = ( + "Anonymous expressions are not supported in `.name.prefix`.\n" + "Instead of `nw.all()`, try using a named expression, such as " + "`nw.col('a', 'b')`\n" + ) + raise ValueError(msg) + + output_names = [prefix + str(name) for name in root_names] + return self._compliant_expr.__class__( + lambda df: [ + expr.alias(name) + for expr, name in zip(self._compliant_expr._call(df), output_names) + ], + depth=self._compliant_expr._depth, + function_name=self._compliant_expr._function_name, + root_names=root_names, + output_names=output_names, + returns_scalar=self._compliant_expr._returns_scalar, + backend_version=self._compliant_expr._backend_version, + version=self._compliant_expr._version, + kwargs={**self._compliant_expr._kwargs, "prefix": prefix}, + ) + + def suffix(self: Self, suffix: str) -> DuckDBExpr: + root_names = self._compliant_expr._root_names + if root_names is None: + msg = ( + "Anonymous expressions are not supported in `.name.suffix`.\n" + "Instead of `nw.all()`, try using a named expression, such as " + "`nw.col('a', 'b')`\n" + ) + raise ValueError(msg) + + output_names = [str(name) + suffix for name in root_names] + + return self._compliant_expr.__class__( + lambda df: [ + expr.alias(name) + for expr, name in zip(self._compliant_expr._call(df), output_names) + ], + depth=self._compliant_expr._depth, + function_name=self._compliant_expr._function_name, + root_names=root_names, + output_names=output_names, + returns_scalar=self._compliant_expr._returns_scalar, + backend_version=self._compliant_expr._backend_version, + version=self._compliant_expr._version, + kwargs={**self._compliant_expr._kwargs, "suffix": suffix}, + ) + + def to_lowercase(self: Self) -> DuckDBExpr: + root_names = self._compliant_expr._root_names + + if root_names is None: + msg = ( + "Anonymous expressions are not supported in `.name.to_lowercase`.\n" + "Instead of `nw.all()`, try using a named expression, such as " + "`nw.col('a', 'b')`\n" + ) + raise ValueError(msg) + output_names = [str(name).lower() for name in root_names] + + return self._compliant_expr.__class__( + lambda df: [ + expr.alias(name) + for expr, name in zip(self._compliant_expr._call(df), output_names) + ], + depth=self._compliant_expr._depth, + function_name=self._compliant_expr._function_name, + root_names=root_names, + output_names=output_names, + returns_scalar=self._compliant_expr._returns_scalar, + backend_version=self._compliant_expr._backend_version, + version=self._compliant_expr._version, + kwargs=self._compliant_expr._kwargs, + ) + + def to_uppercase(self: Self) -> DuckDBExpr: + root_names = self._compliant_expr._root_names + + if root_names is None: + msg = ( + "Anonymous expressions are not supported in `.name.to_uppercase`.\n" + "Instead of `nw.all()`, try using a named expression, such as " + "`nw.col('a', 'b')`\n" + ) + raise ValueError(msg) + output_names = [str(name).upper() for name in root_names] + + return self._compliant_expr.__class__( + lambda df: [ + expr.alias(name) + for expr, name in zip(self._compliant_expr._call(df), output_names) + ], + depth=self._compliant_expr._depth, + function_name=self._compliant_expr._function_name, + root_names=root_names, + output_names=output_names, + returns_scalar=self._compliant_expr._returns_scalar, + backend_version=self._compliant_expr._backend_version, + version=self._compliant_expr._version, + kwargs=self._compliant_expr._kwargs, + ) diff --git a/tests/conftest.py b/tests/conftest.py index 497103d67..60dec8815 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -232,7 +232,7 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: if ( any( x in str(metafunc.module) - for x in ("list", "name", "unpivot", "from_dict", "from_numpy", "tail") + for x in ("list", "unpivot", "from_dict", "from_numpy", "tail") ) and LAZY_CONSTRUCTORS["duckdb"] in constructors ): diff --git a/tests/expr_and_series/name/to_uppercase_test.py b/tests/expr_and_series/name/to_uppercase_test.py index 93c4db8f6..785da4957 100644 --- a/tests/expr_and_series/name/to_uppercase_test.py +++ b/tests/expr_and_series/name/to_uppercase_test.py @@ -12,31 +12,21 @@ data = {"foo": [1, 2, 3], "BAR": [4, 5, 6]} -def test_to_uppercase(constructor: Constructor, request: pytest.FixtureRequest) -> None: - if any(x in str(constructor) for x in ("duckdb",)): - request.applymarker(pytest.mark.xfail) +def test_to_uppercase(constructor: Constructor) -> None: df = nw.from_native(constructor(data)) result = df.select((nw.col("foo", "BAR") * 2).name.to_uppercase()) expected = {k.upper(): [e * 2 for e in v] for k, v in data.items()} assert_equal_data(result, expected) -def test_to_uppercase_after_alias( - constructor: Constructor, request: pytest.FixtureRequest -) -> None: - if any(x in str(constructor) for x in ("duckdb",)): - request.applymarker(pytest.mark.xfail) +def test_to_uppercase_after_alias(constructor: Constructor) -> None: df = nw.from_native(constructor(data)) result = df.select((nw.col("foo")).alias("alias_for_foo").name.to_uppercase()) expected = {"FOO": data["foo"]} assert_equal_data(result, expected) -def test_to_uppercase_raise_anonymous( - constructor: Constructor, request: pytest.FixtureRequest -) -> None: - if any(x in str(constructor) for x in ("duckdb",)): - request.applymarker(pytest.mark.xfail) +def test_to_uppercase_raise_anonymous(constructor: Constructor) -> None: df_raw = constructor(data) df = nw.from_native(df_raw) From fd3ee523907753d435f68d189c128e0df0d0c090 Mon Sep 17 00:00:00 2001 From: Marco Edward Gorelli Date: Fri, 17 Jan 2025 10:06:35 +0000 Subject: [PATCH 3/4] use anonymousexprerror --- narwhals/_duckdb/expr_name.py | 55 ++++++++----------------------- narwhals/_spark_like/expr_name.py | 53 ++++++++--------------------- 2 files changed, 27 insertions(+), 81 deletions(-) diff --git a/narwhals/_duckdb/expr_name.py b/narwhals/_duckdb/expr_name.py index c19b7a8c3..92406b54e 100644 --- a/narwhals/_duckdb/expr_name.py +++ b/narwhals/_duckdb/expr_name.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING from typing import Callable +from narwhals.exceptions import AnonymousExprError if TYPE_CHECKING: from typing_extensions import Self @@ -15,15 +16,9 @@ def __init__(self: Self, expr: DuckDBExpr) -> None: def keep(self: Self) -> DuckDBExpr: root_names = self._compliant_expr._root_names - if root_names is None: - msg = ( - "Anonymous expressions are not supported in `.name.keep`.\n" - "Instead of `nw.all()`, try using a named expression, such as " - "`nw.col('a', 'b')`\n" - ) - raise ValueError(msg) - + msg = ".name.keep" + raise AnonymousExprError.from_expr_name(msg) return self._compliant_expr.__class__( lambda df: [ expr.alias(name) @@ -41,14 +36,9 @@ def keep(self: Self) -> DuckDBExpr: def map(self: Self, function: Callable[[str], str]) -> DuckDBExpr: root_names = self._compliant_expr._root_names - if root_names is None: - msg = ( - "Anonymous expressions are not supported in `.name.map`.\n" - "Instead of `nw.all()`, try using a named expression, such as " - "`nw.col('a', 'b')`\n" - ) - raise ValueError(msg) + msg = ".name.map" + raise AnonymousExprError.from_expr_name(msg) output_names = [function(str(name)) for name in root_names] @@ -70,12 +60,8 @@ def map(self: Self, function: Callable[[str], str]) -> DuckDBExpr: def prefix(self: Self, prefix: str) -> DuckDBExpr: root_names = self._compliant_expr._root_names if root_names is None: - msg = ( - "Anonymous expressions are not supported in `.name.prefix`.\n" - "Instead of `nw.all()`, try using a named expression, such as " - "`nw.col('a', 'b')`\n" - ) - raise ValueError(msg) + msg = ".name.prefix" + raise AnonymousExprError.from_expr_name(msg) output_names = [prefix + str(name) for name in root_names] return self._compliant_expr.__class__( @@ -96,12 +82,8 @@ def prefix(self: Self, prefix: str) -> DuckDBExpr: def suffix(self: Self, suffix: str) -> DuckDBExpr: root_names = self._compliant_expr._root_names if root_names is None: - msg = ( - "Anonymous expressions are not supported in `.name.suffix`.\n" - "Instead of `nw.all()`, try using a named expression, such as " - "`nw.col('a', 'b')`\n" - ) - raise ValueError(msg) + msg = ".name.suffix" + raise AnonymousExprError.from_expr_name(msg) output_names = [str(name) + suffix for name in root_names] @@ -122,14 +104,10 @@ def suffix(self: Self, suffix: str) -> DuckDBExpr: def to_lowercase(self: Self) -> DuckDBExpr: root_names = self._compliant_expr._root_names - if root_names is None: - msg = ( - "Anonymous expressions are not supported in `.name.to_lowercase`.\n" - "Instead of `nw.all()`, try using a named expression, such as " - "`nw.col('a', 'b')`\n" - ) - raise ValueError(msg) + msg = ".name.to_lowercase" + raise AnonymousExprError.from_expr_name(msg) + output_names = [str(name).lower() for name in root_names] return self._compliant_expr.__class__( @@ -149,14 +127,9 @@ def to_lowercase(self: Self) -> DuckDBExpr: def to_uppercase(self: Self) -> DuckDBExpr: root_names = self._compliant_expr._root_names - if root_names is None: - msg = ( - "Anonymous expressions are not supported in `.name.to_uppercase`.\n" - "Instead of `nw.all()`, try using a named expression, such as " - "`nw.col('a', 'b')`\n" - ) - raise ValueError(msg) + msg = ".name.to_uppercase" + raise AnonymousExprError.from_expr_name(msg) output_names = [str(name).upper() for name in root_names] return self._compliant_expr.__class__( diff --git a/narwhals/_spark_like/expr_name.py b/narwhals/_spark_like/expr_name.py index 6c52d2326..cfa876c62 100644 --- a/narwhals/_spark_like/expr_name.py +++ b/narwhals/_spark_like/expr_name.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING from typing import Callable +from narwhals.exceptions import AnonymousExprError if TYPE_CHECKING: from typing_extensions import Self @@ -15,14 +16,9 @@ def __init__(self: Self, expr: SparkLikeExpr) -> None: def keep(self: Self) -> SparkLikeExpr: root_names = self._compliant_expr._root_names - if root_names is None: - msg = ( - "Anonymous expressions are not supported in `.name.keep`.\n" - "Instead of `nw.all()`, try using a named expression, such as " - "`nw.col('a', 'b')`\n" - ) - raise ValueError(msg) + msg = ".name.keep" + raise AnonymousExprError.from_expr_name(msg) return self._compliant_expr.__class__( lambda df: [ @@ -41,14 +37,9 @@ def keep(self: Self) -> SparkLikeExpr: def map(self: Self, function: Callable[[str], str]) -> SparkLikeExpr: root_names = self._compliant_expr._root_names - if root_names is None: - msg = ( - "Anonymous expressions are not supported in `.name.map`.\n" - "Instead of `nw.all()`, try using a named expression, such as " - "`nw.col('a', 'b')`\n" - ) - raise ValueError(msg) + msg = ".name.map" + raise AnonymousExprError.from_expr_name(msg) output_names = [function(str(name)) for name in root_names] @@ -70,12 +61,8 @@ def map(self: Self, function: Callable[[str], str]) -> SparkLikeExpr: def prefix(self: Self, prefix: str) -> SparkLikeExpr: root_names = self._compliant_expr._root_names if root_names is None: - msg = ( - "Anonymous expressions are not supported in `.name.prefix`.\n" - "Instead of `nw.all()`, try using a named expression, such as " - "`nw.col('a', 'b')`\n" - ) - raise ValueError(msg) + msg = ".name.prefix" + raise AnonymousExprError.from_expr_name(msg) output_names = [prefix + str(name) for name in root_names] return self._compliant_expr.__class__( @@ -96,12 +83,8 @@ def prefix(self: Self, prefix: str) -> SparkLikeExpr: def suffix(self: Self, suffix: str) -> SparkLikeExpr: root_names = self._compliant_expr._root_names if root_names is None: - msg = ( - "Anonymous expressions are not supported in `.name.suffix`.\n" - "Instead of `nw.all()`, try using a named expression, such as " - "`nw.col('a', 'b')`\n" - ) - raise ValueError(msg) + msg = ".name.suffix" + raise AnonymousExprError.from_expr_name(msg) output_names = [str(name) + suffix for name in root_names] @@ -122,14 +105,9 @@ def suffix(self: Self, suffix: str) -> SparkLikeExpr: def to_lowercase(self: Self) -> SparkLikeExpr: root_names = self._compliant_expr._root_names - if root_names is None: - msg = ( - "Anonymous expressions are not supported in `.name.to_lowercase`.\n" - "Instead of `nw.all()`, try using a named expression, such as " - "`nw.col('a', 'b')`\n" - ) - raise ValueError(msg) + msg = ".name.to_lowercase" + raise AnonymousExprError.from_expr_name(msg) output_names = [str(name).lower() for name in root_names] return self._compliant_expr.__class__( @@ -149,14 +127,9 @@ def to_lowercase(self: Self) -> SparkLikeExpr: def to_uppercase(self: Self) -> SparkLikeExpr: root_names = self._compliant_expr._root_names - if root_names is None: - msg = ( - "Anonymous expressions are not supported in `.name.to_uppercase`.\n" - "Instead of `nw.all()`, try using a named expression, such as " - "`nw.col('a', 'b')`\n" - ) - raise ValueError(msg) + msg = ".name.to_uppercase" + raise AnonymousExprError.from_expr_name(msg) output_names = [str(name).upper() for name in root_names] return self._compliant_expr.__class__( From 4059af456080f32e8795a2f95b790988849465ec Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:07:16 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- narwhals/_duckdb/expr_name.py | 1 + narwhals/_spark_like/expr_name.py | 1 + 2 files changed, 2 insertions(+) diff --git a/narwhals/_duckdb/expr_name.py b/narwhals/_duckdb/expr_name.py index 92406b54e..2ed2b2ea8 100644 --- a/narwhals/_duckdb/expr_name.py +++ b/narwhals/_duckdb/expr_name.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING from typing import Callable + from narwhals.exceptions import AnonymousExprError if TYPE_CHECKING: diff --git a/narwhals/_spark_like/expr_name.py b/narwhals/_spark_like/expr_name.py index cfa876c62..c32305270 100644 --- a/narwhals/_spark_like/expr_name.py +++ b/narwhals/_spark_like/expr_name.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING from typing import Callable + from narwhals.exceptions import AnonymousExprError if TYPE_CHECKING: