From bc78a7d7fe988f493b3a7581f4606cf4c1116206 Mon Sep 17 00:00:00 2001 From: Henry Harbeck Date: Wed, 14 Aug 2024 21:57:30 +1000 Subject: [PATCH 1/9] allow mapping in `str.replace_many` --- py-polars/polars/api.py | 7 +- py-polars/polars/expr/string.py | 122 ++++++++++++++---- py-polars/polars/series/string.py | 82 ++++++++++-- .../namespaces/string/test_string.py | 77 ++++++++++- 4 files changed, 252 insertions(+), 36 deletions(-) diff --git a/py-polars/polars/api.py b/py-polars/polars/api.py index 44ba02084778..84262d1b7998 100644 --- a/py-polars/polars/api.py +++ b/py-polars/polars/api.py @@ -1,7 +1,5 @@ from __future__ import annotations -from functools import reduce -from operator import or_ from typing import TYPE_CHECKING, Callable, Generic, TypeVar from warnings import warn @@ -20,9 +18,8 @@ ] # do not allow override of polars' own namespaces (as registered by '_accessors') -_reserved_namespaces: set[str] = reduce( - or_, - (cls._accessors for cls in (pl.DataFrame, pl.Expr, pl.LazyFrame, pl.Series)), +_reserved_namespaces: set[str] = set.union( + *(cls._accessors for cls in (pl.DataFrame, pl.Expr, pl.LazyFrame, pl.Series)) ) diff --git a/py-polars/polars/expr/string.py b/py-polars/polars/expr/string.py index d6232f7ff19b..45acf1dc60ed 100644 --- a/py-polars/polars/expr/string.py +++ b/py-polars/polars/expr/string.py @@ -1,7 +1,7 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Mapping import polars._reexport as pl from polars import functions as F @@ -11,7 +11,7 @@ ) from polars._utils.parse import parse_into_expression from polars._utils.unstable import unstable -from polars._utils.various import find_stacklevel +from polars._utils.various import find_stacklevel, no_default from polars._utils.wrap import wrap_expr from polars.datatypes import Date, Datetime, Time, parse_into_dtype from polars.datatypes.constants import N_INFER_DEFAULT @@ -28,6 +28,7 @@ TimeUnit, TransferEncoding, ) + from polars._utils.various import NoDefault class ExprStringNameSpace: @@ -2400,9 +2401,9 @@ def contains_any( self, patterns: IntoExpr, *, ascii_case_insensitive: bool = False ) -> Expr: """ - Use the aho-corasick algorithm to find matches. + Use the Aho-Corasick algorithm to find matches. - This version determines if any of the patterns find a match. + Determines if any of the patterns are contained in the string. Parameters ---------- @@ -2413,6 +2414,12 @@ def contains_any( When this option is enabled, searching will be performed without respect to case for ASCII letters (a-z and A-Z) only. + Notes + ----- + This method supports matching on string literals only, and does not support + regular expression matching. + + Examples -------- >>> _ = pl.Config.set_fmt_str_lengths(100) @@ -2448,29 +2455,75 @@ def contains_any( def replace_many( self, - patterns: IntoExpr, - replace_with: IntoExpr, + patterns: IntoExpr | Mapping[str, str], + replace_with: IntoExpr | NoDefault = no_default, *, ascii_case_insensitive: bool = False, ) -> Expr: """ - - Use the aho-corasick algorithm to replace many matches. + Use the Aho-Corasick algorithm to replace many matches. Parameters ---------- patterns String patterns to search and replace. + Accepts expression input. Strings are parsed as column names, and other + non-expression inputs are parsed as literals. Also accepts a mapping of + patterns to their replacement as syntactic sugar for + `replace_many(pl.Series(mapping.keys()), pl.Series(mapping.values()))`. replace_with Strings to replace where a pattern was a match. - This can be broadcast, so it supports many:one and many:many. + Accepts expression input. Sequences are parsed as Series. Non-expression + inputs are parsed as literals. Length must match the length of `patterns` or + have length 1. This can be broadcasted, so it supports many:one and + many:many. ascii_case_insensitive Enable ASCII-aware case-insensitive matching. When this option is enabled, searching will be performed without respect to case for ASCII letters (a-z and A-Z) only. + Notes + ----- + This method supports matching on string literals only, and does not support + regular expression matching. + Examples -------- + Replace many patterns by passing sequences of equal length to the `patterns` and + `replace_with` parameters. + + >>> _ = pl.Config.set_fmt_str_lengths(100) + >>> df = pl.DataFrame( + ... { + ... "lyrics": [ + ... "Everybody wants to rule the world", + ... "Tell me what you want, what you really really want", + ... "Can you feel the love tonight", + ... ] + ... } + ... ) + >>> df.with_columns( + ... pl.col("lyrics") + ... .str.replace_many( + ... ["me", "you"], + ... ["you", "me"], + ... ) + ... .alias("confusing") + ... ) + shape: (3, 2) + ┌────────────────────────────────────────────────────┬───────────────────────────────────────────────────┐ + │ lyrics ┆ confusing │ + │ --- ┆ --- │ + │ str ┆ str │ + ╞════════════════════════════════════════════════════╪═══════════════════════════════════════════════════╡ + │ Everybody wants to rule the world ┆ Everybody wants to rule the world │ + │ Tell me what you want, what you really really want ┆ Tell you what me want, what me really really want │ + │ Can you feel the love tonight ┆ Can me feel the love tonight │ + └────────────────────────────────────────────────────┴───────────────────────────────────────────────────┘ + + Broadcast a replacement for many patterns by passing a string or a sequence of + length 1 to the `replace_with` parameter. + >>> _ = pl.Config.set_fmt_str_lengths(100) >>> df = pl.DataFrame( ... { @@ -2499,30 +2552,50 @@ def replace_many( │ Tell me what you want, what you really really want ┆ Tell what want, what really really want │ │ Can you feel the love tonight ┆ Can feel the love tonight │ └────────────────────────────────────────────────────┴────────────────────────────────────────────┘ + + Passing a mapping with patterns and replacements is also supported as syntactic + sugar. + + >>> _ = pl.Config.set_fmt_str_lengths(100) + >>> df = pl.DataFrame( + ... { + ... "lyrics": [ + ... "Everybody wants to rule the world", + ... "Tell me what you want, what you really really want", + ... "Can you feel the love tonight", + ... ] + ... } + ... ) + >>> mapping = {"me": "you", "you": "me", "want": "need"} >>> df.with_columns( - ... pl.col("lyrics") - ... .str.replace_many( - ... ["me", "you"], - ... ["you", "me"], - ... ) - ... .alias("confusing") - ... ) # doctest: +IGNORE_RESULT + ... pl.col("lyrics").str.replace_many(mapping).alias("confusing") + ... ) shape: (3, 2) ┌────────────────────────────────────────────────────┬───────────────────────────────────────────────────┐ │ lyrics ┆ confusing │ │ --- ┆ --- │ │ str ┆ str │ ╞════════════════════════════════════════════════════╪═══════════════════════════════════════════════════╡ - │ Everybody wants to rule the world ┆ Everybody wants to rule the world │ - │ Tell me what you want, what you really really want ┆ Tell you what me want, what me really really want │ + │ Everybody wants to rule the world ┆ Everybody needs to rule the world │ + │ Tell me what you want, what you really really want ┆ Tell you what me need, what me really really need │ │ Can you feel the love tonight ┆ Can me feel the love tonight │ └────────────────────────────────────────────────────┴───────────────────────────────────────────────────┘ """ # noqa: W505 + if replace_with is no_default and isinstance(patterns, Mapping): + # Early return in case of an empty mapping. + if not patterns: + return wrap_expr(self._pyexpr) + replace_with = pl.Series(patterns.values()) + patterns = pl.Series(patterns.keys()) patterns = parse_into_expression( - patterns, str_as_lit=False, list_as_series=True + patterns, # type: ignore[arg-type] + str_as_lit=False, + list_as_series=True, ) replace_with = parse_into_expression( - replace_with, str_as_lit=True, list_as_series=True + replace_with, # type: ignore[arg-type] + str_as_lit=True, + list_as_series=True, ) return wrap_expr( self._pyexpr.str_replace_many( @@ -2539,8 +2612,7 @@ def extract_many( overlapping: bool = False, ) -> Expr: """ - - Use the aho-corasick algorithm to extract many matches. + Use the Aho-Corasick algorithm to extract many matches. Parameters ---------- @@ -2553,6 +2625,12 @@ def extract_many( overlapping Whether matches may overlap. + Notes + ----- + This method supports matching on string literals only, and does not support + regular expression matching. + + Examples -------- >>> _ = pl.Config.set_fmt_str_lengths(100) diff --git a/py-polars/polars/series/string.py b/py-polars/polars/series/string.py index 30c5ac90b5c1..0bc9d478480a 100644 --- a/py-polars/polars/series/string.py +++ b/py-polars/polars/series/string.py @@ -1,9 +1,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Mapping from polars._utils.deprecation import deprecate_function from polars._utils.unstable import unstable +from polars._utils.various import no_default from polars.datatypes.constants import N_INFER_DEFAULT from polars.series.utils import expr_dispatch @@ -18,6 +19,7 @@ TimeUnit, TransferEncoding, ) + from polars._utils.various import NoDefault from polars.polars import PySeries @@ -1818,9 +1820,9 @@ def contains_any( self, patterns: Series | list[str], *, ascii_case_insensitive: bool = False ) -> Series: """ - Use the aho-corasick algorithm to find matches. + Use the Aho-Corasick algorithm to find matches. - This version determines if any of the patterns find a match. + Determines if any of the patterns are contained in the string. Parameters ---------- @@ -1831,6 +1833,11 @@ def contains_any( When this option is enabled, searching will be performed without respect to case for ASCII letters (a-z and A-Z) only. + Notes + ----- + This method supports matching on string literals only, and does not support + regular expression matching. + Examples -------- >>> _ = pl.Config.set_fmt_str_lengths(100) @@ -1854,28 +1861,39 @@ def contains_any( def replace_many( self, - patterns: Series | list[str], - replace_with: Series | list[str] | str, + patterns: Series | list[str] | Mapping[str, str], + replace_with: Series | list[str] | str | NoDefault = no_default, *, ascii_case_insensitive: bool = False, ) -> Series: """ - Use the aho-corasick algorithm to replace many matches. + Use the Aho-Corasick algorithm to replace many matches. Parameters ---------- patterns String patterns to search and replace. + Also accepts a mapping of patterns to their replacement as syntactic sugar + for `replace_many(pl.Series(mapping.keys()), pl.Series(mapping.values()))`. replace_with Strings to replace where a pattern was a match. - This can be broadcast, so it supports many:one and many:many. + Length must match the length of `patterns` or have length 1. This can be + broadcasted, so it supports many:one and many:many. ascii_case_insensitive Enable ASCII-aware case-insensitive matching. When this option is enabled, searching will be performed without respect to case for ASCII letters (a-z and A-Z) only. + Notes + ----- + This method supports matching on string literals only, and does not support + regular expression matching. + Examples -------- + Replace many patterns by passing lists of equal length to the `patterns` and + `replace_with` parameters. + >>> _ = pl.Config.set_fmt_str_lengths(100) >>> s = pl.Series( ... "lyrics", @@ -1893,6 +1911,49 @@ def replace_many( "Tell you what me want, what me really really want" "Can me feel the love tonight" ] + + Broadcast a replacement for many patterns by passing a string or a sequence of + length 1 to the `replace_with` parameter. + + >>> _ = pl.Config.set_fmt_str_lengths(100) + >>> s = pl.Series( + ... "lyrics", + ... [ + ... "Everybody wants to rule the world", + ... "Tell me what you want, what you really really want", + ... "Can you feel the love tonight", + ... ], + ... ) + >>> s.str.replace_many(["me", "you", "they"], "") + shape: (3,) + Series: 'lyrics' [str] + [ + "Everybody wants to rule the world" + "Tell what want, what really really want" + "Can feel the love tonight" + ] + + Passing a mapping with patterns and replacements is also supported as syntactic + sugar. + + >>> _ = pl.Config.set_fmt_str_lengths(100) + >>> s = pl.Series( + ... "lyrics", + ... [ + ... "Everybody wants to rule the world", + ... "Tell me what you want, what you really really want", + ... "Can you feel the love tonight", + ... ], + ... ) + >>> mapping = {"me": "you", "you": "me", "want": "need"} + >>> s.str.replace_many(mapping) + shape: (3,) + Series: 'lyrics' [str] + [ + "Everybody needs to rule the world" + "Tell you what me need, what me really really need" + "Can me feel the love tonight" + ] """ @unstable() @@ -1904,7 +1965,7 @@ def extract_many( overlapping: bool = False, ) -> Series: """ - Use the aho-corasick algorithm to extract many matches. + Use the Aho-Corasick algorithm to extract many matches. Parameters ---------- @@ -1917,6 +1978,11 @@ def extract_many( overlapping Whether matches may overlap. + Notes + ----- + This method supports matching on string literals only, and does not support + regular expression matching. + Examples -------- >>> s = pl.Series("values", ["discontent"]) diff --git a/py-polars/tests/unit/operations/namespaces/string/test_string.py b/py-polars/tests/unit/operations/namespaces/string/test_string.py index 7567d1557f36..413d7a260a1b 100644 --- a/py-polars/tests/unit/operations/namespaces/string/test_string.py +++ b/py-polars/tests/unit/operations/namespaces/string/test_string.py @@ -4,7 +4,12 @@ import polars as pl import polars.selectors as cs -from polars.exceptions import ComputeError, InvalidOperationError +from polars.exceptions import ( + ColumnNotFoundError, + ComputeError, + InvalidOperationError, + SchemaError, +) from polars.testing import assert_frame_equal, assert_series_equal @@ -1061,6 +1066,76 @@ def test_replace_many( ) +@pytest.mark.parametrize( + ("mapping", "case_insensitive", "expected"), + [ + ({}, False, "Tell me what you want"), + ({"me": "them"}, False, "Tell them what you want"), + ({"who": "them"}, False, "Tell me what you want"), + ({"me": "it", "you": "it"}, False, "Tell it what it want"), + ({"Me": "it", "you": "it"}, False, "Tell me what it want"), + ({"me": "you", "you": "me"}, False, "Tell you what me want"), + ({}, True, "Tell me what you want"), + ({"Me": "it", "you": "it"}, True, "Tell it what it want"), + ({"me": "you", "YOU": "me"}, True, "Tell you what me want"), + ], +) +def test_replace_many_mapping( + mapping: dict[str, str], + case_insensitive: bool, + expected: str, +) -> None: + df = pl.DataFrame({"text": ["Tell me what you want"]}) + # series + assert ( + expected + == df["text"] + .str.replace_many(mapping, ascii_case_insensitive=case_insensitive) + .item() + ) + # expr + assert ( + expected + == df.select( + pl.col("text").str.replace_many( + mapping, + ascii_case_insensitive=case_insensitive, + ) + ).item() + ) + + +def test_replace_many_invalid_inputs() -> None: + df = pl.DataFrame({"text": ["Tell me what you want"]}) + + # Ensure a string as the first argument is parsed as a column name. + with pytest.raises(ColumnNotFoundError, match="me"): + df.select(pl.col("text").str.replace_many("me", "you")) + + with pytest.raises(SchemaError): + df.select(pl.col("text").str.replace_many(1, 2)) + + with pytest.raises(SchemaError): + df.select(pl.col("text").str.replace_many([1], [2])) + + with pytest.raises( + InvalidOperationError, + match="expected the same amount of patterns as replacement strings", + ): + df.select(pl.col("text").str.replace_many(["a"], ["b", "c"])) + + s = df.to_series() + + with pytest.raises(ColumnNotFoundError, match="me"): + s.str.replace_many("me", "you") # type: ignore[arg-type] + + with pytest.raises( + InvalidOperationError, + match="expected the same amount of patterns as replacement strings", + ): + s.str.replace_many(["a"], ["b", "c"]) + + def test_extract_all_count() -> None: df = pl.DataFrame({"foo": ["123 bla 45 asd", "xaz 678 910t", "boo", None]}) assert ( From 1b383188728607d2d13f84ddff5ed53ab90c7e21 Mon Sep 17 00:00:00 2001 From: Henry Harbeck Date: Thu, 15 Aug 2024 20:25:21 +1000 Subject: [PATCH 2/9] one argument form only supported if argument is a mapping --- py-polars/polars/expr/string.py | 15 +++++++++------ .../operations/namespaces/string/test_string.py | 12 ++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/py-polars/polars/expr/string.py b/py-polars/polars/expr/string.py index 45acf1dc60ed..2f3c8d767ef6 100644 --- a/py-polars/polars/expr/string.py +++ b/py-polars/polars/expr/string.py @@ -2473,10 +2473,9 @@ def replace_many( `replace_many(pl.Series(mapping.keys()), pl.Series(mapping.values()))`. replace_with Strings to replace where a pattern was a match. - Accepts expression input. Sequences are parsed as Series. Non-expression - inputs are parsed as literals. Length must match the length of `patterns` or - have length 1. This can be broadcasted, so it supports many:one and - many:many. + Accepts expression input. Non-expression inputs are parsed as literals. + Length must match the length of `patterns` or have length 1. This can be + broadcasted, so it supports many:one and many:many. ascii_case_insensitive Enable ASCII-aware case-insensitive matching. When this option is enabled, searching will be performed without respect @@ -2581,19 +2580,23 @@ def replace_many( │ Can you feel the love tonight ┆ Can me feel the love tonight │ └────────────────────────────────────────────────────┴───────────────────────────────────────────────────┘ """ # noqa: W505 - if replace_with is no_default and isinstance(patterns, Mapping): + if replace_with is no_default: + if not isinstance(patterns, Mapping): + msg = "`replace_with` is required if `patterns` is not a Mapping type" + raise ValueError(msg) # Early return in case of an empty mapping. if not patterns: return wrap_expr(self._pyexpr) replace_with = pl.Series(patterns.values()) patterns = pl.Series(patterns.keys()) + patterns = parse_into_expression( patterns, # type: ignore[arg-type] str_as_lit=False, list_as_series=True, ) replace_with = parse_into_expression( - replace_with, # type: ignore[arg-type] + replace_with, str_as_lit=True, list_as_series=True, ) diff --git a/py-polars/tests/unit/operations/namespaces/string/test_string.py b/py-polars/tests/unit/operations/namespaces/string/test_string.py index 413d7a260a1b..ceb03ac4a3f3 100644 --- a/py-polars/tests/unit/operations/namespaces/string/test_string.py +++ b/py-polars/tests/unit/operations/namespaces/string/test_string.py @@ -1118,6 +1118,12 @@ def test_replace_many_invalid_inputs() -> None: with pytest.raises(SchemaError): df.select(pl.col("text").str.replace_many([1], [2])) + with pytest.raises(SchemaError): + df.select(pl.col("text").str.replace_many(["me"], None)) + + with pytest.raises(ValueError): + df.select(pl.col("text").str.replace_many(["me"])) + with pytest.raises( InvalidOperationError, match="expected the same amount of patterns as replacement strings", @@ -1129,6 +1135,12 @@ def test_replace_many_invalid_inputs() -> None: with pytest.raises(ColumnNotFoundError, match="me"): s.str.replace_many("me", "you") # type: ignore[arg-type] + with pytest.raises(SchemaError): + df.select(pl.col("text").str.replace_many(["me"], None)) + + with pytest.raises(ValueError): + df.select(pl.col("text").str.replace_many(["me"])) + with pytest.raises( InvalidOperationError, match="expected the same amount of patterns as replacement strings", From 3241cafe97a899d48eeb4f6e74cf3459e6329e5c Mon Sep 17 00:00:00 2001 From: Henry Harbeck Date: Thu, 15 Aug 2024 20:25:28 +1000 Subject: [PATCH 3/9] drive by --- py-polars/polars/dataframe/frame.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/py-polars/polars/dataframe/frame.py b/py-polars/polars/dataframe/frame.py index 444710793f2c..dcd34e4521c4 100644 --- a/py-polars/polars/dataframe/frame.py +++ b/py-polars/polars/dataframe/frame.py @@ -6823,10 +6823,6 @@ def join( Note that joining on any other expressions than `col` will turn off coalescing. - Returns - ------- - DataFrame - See Also -------- join_asof From 6f5a72e38afc72b4ad974cd738f53de2d560ae58 Mon Sep 17 00:00:00 2001 From: Henry Harbeck Date: Fri, 16 Aug 2024 00:12:33 +1000 Subject: [PATCH 4/9] remove unnecessary diff --- py-polars/polars/expr/string.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/py-polars/polars/expr/string.py b/py-polars/polars/expr/string.py index 2f3c8d767ef6..970d3f6668d6 100644 --- a/py-polars/polars/expr/string.py +++ b/py-polars/polars/expr/string.py @@ -2596,9 +2596,7 @@ def replace_many( list_as_series=True, ) replace_with = parse_into_expression( - replace_with, - str_as_lit=True, - list_as_series=True, + replace_with, str_as_lit=True, list_as_series=True ) return wrap_expr( self._pyexpr.str_replace_many( @@ -2633,7 +2631,6 @@ def extract_many( This method supports matching on string literals only, and does not support regular expression matching. - Examples -------- >>> _ = pl.Config.set_fmt_str_lengths(100) From fadafb6f880940ca17c10b597d0fc44e9f3d3fe5 Mon Sep 17 00:00:00 2001 From: Henry Harbeck Date: Fri, 16 Aug 2024 00:22:48 +1000 Subject: [PATCH 5/9] remove extra newline --- py-polars/polars/expr/string.py | 1 - 1 file changed, 1 deletion(-) diff --git a/py-polars/polars/expr/string.py b/py-polars/polars/expr/string.py index 970d3f6668d6..c4879d3841aa 100644 --- a/py-polars/polars/expr/string.py +++ b/py-polars/polars/expr/string.py @@ -2419,7 +2419,6 @@ def contains_any( This method supports matching on string literals only, and does not support regular expression matching. - Examples -------- >>> _ = pl.Config.set_fmt_str_lengths(100) From 91d4b5dede8ddee5fd860fdd51561ee0db9398e9 Mon Sep 17 00:00:00 2001 From: Henry Harbeck Date: Fri, 16 Aug 2024 00:48:05 +1000 Subject: [PATCH 6/9] stop doctests from failing --- py-polars/polars/expr/string.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/py-polars/polars/expr/string.py b/py-polars/polars/expr/string.py index c4879d3841aa..61751cbd8525 100644 --- a/py-polars/polars/expr/string.py +++ b/py-polars/polars/expr/string.py @@ -2491,6 +2491,7 @@ def replace_many( `replace_with` parameters. >>> _ = pl.Config.set_fmt_str_lengths(100) + >>> _ = pl.Config.set_tbl_width_chars(110) >>> df = pl.DataFrame( ... { ... "lyrics": [ @@ -2555,6 +2556,7 @@ def replace_many( sugar. >>> _ = pl.Config.set_fmt_str_lengths(100) + >>> _ = pl.Config.set_tbl_width_chars(110) >>> df = pl.DataFrame( ... { ... "lyrics": [ From a2e9be06e398c3ec42a458d43cad107598f18ff1 Mon Sep 17 00:00:00 2001 From: Henry Harbeck Date: Fri, 16 Aug 2024 01:11:14 +1000 Subject: [PATCH 7/9] Re-trigger CI From 80a7e27a8a70da7b7246f3f66f3941e57c56c063 Mon Sep 17 00:00:00 2001 From: Henry Harbeck Date: Fri, 16 Aug 2024 23:17:40 +1000 Subject: [PATCH 8/9] Re-trigger CI From a46f63f81fb526db69b42d5a8657c84cf11418dc Mon Sep 17 00:00:00 2001 From: Henry Harbeck Date: Mon, 26 Aug 2024 23:52:51 +1000 Subject: [PATCH 9/9] replace `ValueError` with `TypeError` --- py-polars/polars/expr/string.py | 4 ++-- .../tests/unit/operations/namespaces/string/test_string.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/py-polars/polars/expr/string.py b/py-polars/polars/expr/string.py index 61751cbd8525..ff4d4da75f2e 100644 --- a/py-polars/polars/expr/string.py +++ b/py-polars/polars/expr/string.py @@ -2583,8 +2583,8 @@ def replace_many( """ # noqa: W505 if replace_with is no_default: if not isinstance(patterns, Mapping): - msg = "`replace_with` is required if `patterns` is not a Mapping type" - raise ValueError(msg) + msg = "`replace_with` argument is required if `patterns` argument is not a Mapping type" + raise TypeError(msg) # Early return in case of an empty mapping. if not patterns: return wrap_expr(self._pyexpr) diff --git a/py-polars/tests/unit/operations/namespaces/string/test_string.py b/py-polars/tests/unit/operations/namespaces/string/test_string.py index ceb03ac4a3f3..fe47b8d07d2e 100644 --- a/py-polars/tests/unit/operations/namespaces/string/test_string.py +++ b/py-polars/tests/unit/operations/namespaces/string/test_string.py @@ -1121,7 +1121,7 @@ def test_replace_many_invalid_inputs() -> None: with pytest.raises(SchemaError): df.select(pl.col("text").str.replace_many(["me"], None)) - with pytest.raises(ValueError): + with pytest.raises(TypeError): df.select(pl.col("text").str.replace_many(["me"])) with pytest.raises( @@ -1138,7 +1138,7 @@ def test_replace_many_invalid_inputs() -> None: with pytest.raises(SchemaError): df.select(pl.col("text").str.replace_many(["me"], None)) - with pytest.raises(ValueError): + with pytest.raises(TypeError): df.select(pl.col("text").str.replace_many(["me"])) with pytest.raises(