Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/reflex-base/news/5417.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`StringVar` now includes `lstrip` and `rstrip` methods. The `strip` method now accepts an optional `chars` argument for consistency with Python’s str API.
86 changes: 76 additions & 10 deletions packages/reflex-base/src/reflex_base/vars/sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,20 @@ def lower(self) -> StringVar:
"""
return string_lower_operation(self)

def lstrip(self, chars: StringVar | str | None = None) -> StringVar:
"""Left strip the string.

Args:
chars: Characters to remove from the left side. If None, strip whitespace.

Returns:
The string lstrip operation.
"""
if chars is not None and not isinstance(chars, (StringVar, str)):
raise_unsupported_operand_types("lstrip", (type(self), type(chars)))

return string_lstrip_operation(self, chars)

def upper(self) -> StringVar:
"""Convert the string to uppercase.

Expand All @@ -698,13 +712,19 @@ def capitalize(self) -> StringVar:
"""
return string_capitalize_operation(self)

def strip(self) -> StringVar:
def strip(self, chars: StringVar | str | None = None) -> StringVar:
"""Strip the string.

Args:
chars: Characters to remove from both ends. If None, strip whitespace.

Returns:
The string strip operation.
"""
return string_strip_operation(self)
if chars is not None and not isinstance(chars, (StringVar, str)):
raise_unsupported_operand_types("strip", (type(self), type(chars)))

return string_strip_operation(self, chars)

def reversed(self) -> StringVar:
"""Reverse the string.
Expand All @@ -714,6 +734,20 @@ def reversed(self) -> StringVar:
"""
return self.split().reverse().join()

def rstrip(self, chars: StringVar | str | None = None) -> StringVar:
"""Right strip the string.

Args:
chars: Characters to remove from the right side. If None, strip whitespace.

Returns:
The string rstrip operation.
"""
if chars is not None and not isinstance(chars, (StringVar, str)):
raise_unsupported_operand_types("rstrip", (type(self), type(chars)))

return string_rstrip_operation(self, chars)

def contains(
self, other: StringVar | str, field: StringVar | str | None = None
) -> BooleanVar:
Expand Down Expand Up @@ -972,16 +1006,48 @@ def string_capitalize_operation(string: StringVar[Any]):


@var_operation
def string_strip_operation(string: StringVar[Any]):
"""Strip a string.
def string_strip_operation(
string: StringVar[Any],
chars: StringVar[Any] | str | None = None,
):
"""Strip a string."""
if str(chars) == "null":
return var_operation_return(js_expression=f"{string}.trim()", var_type=str)

Args:
string: The string to strip.
return var_operation_return(
js_expression=f"{string}.replace(/^[{chars}]+|[{chars}]+$/g, '')",
var_type=str,
)
Comment on lines +1017 to +1020
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Quoted string literal injected directly into regex character class

When chars is a plain Python str (e.g., "abc"), the @var_operation wrapper converts it to LiteralVar.create("abc"), whose JavaScript serialization is "abc" (with quotes). Interpolating that into the regex literal produces /^["abc"]+.../ — the double-quote characters become literal members of the character class, so .strip("abc") would also strip " from the string.

For a StringVar state-variable, the JS variable expression (e.g., state.chars) is embedded verbatim inside the literal regex /.../, which is static syntax — it does not reference the variable at runtime and instead treats the characters of the expression name as the set to strip. The same problem exists identically in string_lstrip_operation and string_rstrip_operation.

Comment on lines +1017 to +1020
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Regex special characters in chars are not escaped

Characters such as ], \, ^, and - carry special meaning inside a regex character class. If chars contains any of them (e.g., strip("a-z") would make - a range operator), the generated JavaScript regex will silently behave differently from Python's character-set strip semantics. There is no escaping of these characters before they are placed between [ and ]. The same applies to string_lstrip_operation and string_rstrip_operation.


Returns:
The stripped string.
"""
return var_operation_return(js_expression=f"{string}.trim()", var_type=str)

@var_operation
def string_lstrip_operation(
string: StringVar[Any],
chars: StringVar[Any] | str | None = None,
):
"""Left strip a string."""
if str(chars) == "null":
return var_operation_return(js_expression=f"{string}.trimStart()", var_type=str)

return var_operation_return(
js_expression=f"{string}.replace(/^[{chars}]+/, '')",
var_type=str,
)


@var_operation
def string_rstrip_operation(
string: StringVar[Any],
chars: StringVar[Any] | str | None = None,
):
"""Right strip a string."""
if str(chars) == "null":
return var_operation_return(js_expression=f"{string}.trimEnd()", var_type=str)

return var_operation_return(
js_expression=f"{string}.replace(/[{chars}]+$/, '')",
var_type=str,
)


@var_operation
Expand Down
2 changes: 2 additions & 0 deletions tests/units/test_var.py
Original file line number Diff line number Diff line change
Expand Up @@ -1001,8 +1001,10 @@ def test_string_operations():

assert str(basic_string.length()) == '"Hello, World!".split("").length'
assert str(basic_string.lower()) == '"Hello, World!".toLowerCase()'
assert str(basic_string.lstrip()) == '"Hello, World!".trimStart()'
assert str(basic_string.upper()) == '"Hello, World!".toUpperCase()'
assert str(basic_string.strip()) == '"Hello, World!".trim()'
assert str(basic_string.rstrip()) == '"Hello, World!".trimEnd()'
assert str(basic_string.contains("World")) == '"Hello, World!".includes("World")'
assert (
str(basic_string.split(" ").join(",")) == '"Hello, World!".split(" ").join(",")'
Expand Down
Loading