From b1b92309114f2d469a0b51a4f8b9c62083668d71 Mon Sep 17 00:00:00 2001 From: raisadz <34237447+raisadz@users.noreply.github.com> Date: Sun, 29 Dec 2024 14:10:19 +0000 Subject: [PATCH 1/3] add replace_all --- narwhals/_duckdb/expr.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/narwhals/_duckdb/expr.py b/narwhals/_duckdb/expr.py index 902da2c9b..b4ad59deb 100644 --- a/narwhals/_duckdb/expr.py +++ b/narwhals/_duckdb/expr.py @@ -591,6 +591,23 @@ def to_uppercase(self) -> DuckDBExpr: returns_scalar=False, ) + def replace_all( + self, pattern: str, value: str, *, literal: bool = False + ) -> DuckDBExpr: + from duckdb import ConstantExpression + from duckdb import FunctionExpression + + return self._compliant_expr._from_call( + lambda _input: FunctionExpression( + "replace" if literal else "regexp_replace", + _input, + ConstantExpression(pattern), + ConstantExpression(value), + ), + "replace_all", + returns_scalar=False, + ) + class DuckDBExprDateTimeNamespace: def __init__(self, expr: DuckDBExpr) -> None: From 07a52329c47f1db58d79bc5db1f2eef504c2d519 Mon Sep 17 00:00:00 2001 From: raisadz <34237447+raisadz@users.noreply.github.com> Date: Sun, 29 Dec 2024 14:39:56 +0000 Subject: [PATCH 2/3] add notimplemented for `replace` and literal is only True for `replace_all` --- narwhals/_duckdb/expr.py | 10 +++++++++- tests/expr_and_series/str/replace_test.py | 8 ++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/narwhals/_duckdb/expr.py b/narwhals/_duckdb/expr.py index b4ad59deb..e2edf74fb 100644 --- a/narwhals/_duckdb/expr.py +++ b/narwhals/_duckdb/expr.py @@ -6,6 +6,7 @@ from typing import Any from typing import Callable from typing import Literal +from typing import NoReturn from typing import Sequence from narwhals._duckdb.utils import get_column_name @@ -597,9 +598,12 @@ def replace_all( from duckdb import ConstantExpression from duckdb import FunctionExpression + if literal is False: + msg = "Only `literal=True` is currently supported." + raise NotImplementedError(msg) return self._compliant_expr._from_call( lambda _input: FunctionExpression( - "replace" if literal else "regexp_replace", + "replace", _input, ConstantExpression(pattern), ConstantExpression(value), @@ -608,6 +612,10 @@ def replace_all( returns_scalar=False, ) + def replace(self, pattern: str, value: str, *, literal: bool, n: int) -> NoReturn: + msg = "`replace` is currently not supported for DuckDB" + raise TypeError(msg) + class DuckDBExprDateTimeNamespace: def __init__(self, expr: DuckDBExpr) -> None: diff --git a/tests/expr_and_series/str/replace_test.py b/tests/expr_and_series/str/replace_test.py index ffd8fce2e..7d57eeb7d 100644 --- a/tests/expr_and_series/str/replace_test.py +++ b/tests/expr_and_series/str/replace_test.py @@ -93,6 +93,7 @@ def test_str_replace_all_series( ) def test_str_replace_expr( constructor: Constructor, + request: pytest.FixtureRequest, data: dict[str, list[str]], pattern: str, value: str, @@ -100,8 +101,9 @@ def test_str_replace_expr( literal: bool, # noqa: FBT001 expected: dict[str, list[str]], ) -> None: + if "duckdb" in str(constructor): + request.applymarker(pytest.mark.xfail) df = nw.from_native(constructor(data)) - result_df = df.select( nw.col("a").str.replace(pattern=pattern, value=value, n=n, literal=literal) ) @@ -114,14 +116,16 @@ def test_str_replace_expr( ) def test_str_replace_all_expr( constructor: Constructor, + request: pytest.FixtureRequest, data: dict[str, list[str]], pattern: str, value: str, literal: bool, # noqa: FBT001 expected: dict[str, list[str]], ) -> None: + if "duckdb" in str(constructor) and literal is False: + request.applymarker(pytest.mark.xfail) df = nw.from_native(constructor(data)) - result = df.select( nw.col("a").str.replace_all(pattern=pattern, value=value, literal=literal) ) From 364ae0d528dcb3eb564947ccd5d4a87b5295b122 Mon Sep 17 00:00:00 2001 From: raisadz <34237447+raisadz@users.noreply.github.com> Date: Sun, 29 Dec 2024 14:47:14 +0000 Subject: [PATCH 3/3] raise notimplemetederror instead of typeerror --- narwhals/_duckdb/expr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/narwhals/_duckdb/expr.py b/narwhals/_duckdb/expr.py index 2d3a7b1ed..e251a90a3 100644 --- a/narwhals/_duckdb/expr.py +++ b/narwhals/_duckdb/expr.py @@ -617,7 +617,7 @@ def replace_all( from duckdb import FunctionExpression if literal is False: - msg = "Only `literal=True` is currently supported." + msg = "`replace_all` for DuckDB currently only supports `literal=True`." raise NotImplementedError(msg) return self._compliant_expr._from_call( lambda _input: FunctionExpression( @@ -632,7 +632,7 @@ def replace_all( def replace(self, pattern: str, value: str, *, literal: bool, n: int) -> NoReturn: msg = "`replace` is currently not supported for DuckDB" - raise TypeError(msg) + raise NotImplementedError(msg) class DuckDBExprDateTimeNamespace: