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 news/6714.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed `reflex rename` corrupting or failing on UTF-8 source files on non-UTF-8 platform locales (e.g. Windows cp1252) by reading and writing files as UTF-8.
10 changes: 8 additions & 2 deletions reflex/utils/rename.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import re
import sys
import tokenize
from pathlib import Path

from reflex_base import constants
Expand Down Expand Up @@ -97,7 +98,12 @@ def rename_imports_and_app_name(file_path: str | Path, old_name: str, new_name:
new_name: The new name to use.
"""
file_path = Path(file_path)
content = file_path.read_text()
if file_path.suffix == constants.Ext.PY:
with file_path.open("rb") as source:
encoding = tokenize.detect_encoding(source.readline)[0]
else:
encoding = "utf-8"
content = file_path.read_text(encoding=encoding)

# Replace `from old_name.` or `from old_name` with `from new_name`
content = re.sub(
Expand Down Expand Up @@ -127,7 +133,7 @@ def rename_imports_and_app_name(file_path: str | Path, old_name: str, new_name:
content,
)

file_path.write_text(content)
file_path.write_text(content, encoding=encoding)


def process_directory(
Expand Down
64 changes: 64 additions & 0 deletions tests/units/test_prerequisites.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import os
import shutil
import tempfile
import uuid
Expand Down Expand Up @@ -1474,6 +1475,69 @@ def test_regex_edge_cases(temp_directory):
assert updated_content == expected_content


def test_rename_imports_and_app_name_preserves_utf8(
temp_directory, monkeypatch: pytest.MonkeyPatch
):
"""UTF-8 source is preserved even when the platform default encoding is not UTF-8.

Simulates a Western Windows locale (cp1252) where ``Path.read_text`` /
``write_text`` without an explicit ``encoding`` would mis-decode UTF-8 source,
silently corrupting or aborting on non-ASCII characters (curly quotes, accents).

Args:
temp_directory: A temporary directory fixture.
monkeypatch: The pytest monkeypatch fixture.
"""
original_read_text = Path.read_text
original_write_text = Path.write_text

def read_text(self, encoding=None, errors=None):
return original_read_text(
self, encoding=encoding if encoding is not None else "cp1252", errors=errors
)

def write_text(self, data, encoding=None, errors=None, newline=None):
return original_write_text(
self,
data,
encoding=encoding if encoding is not None else "cp1252",
errors=errors,
newline=newline,
)

monkeypatch.setattr(Path, "read_text", read_text)
monkeypatch.setattr(Path, "write_text", write_text)

source = "import old_name # \u201cquoted\u201d caf\u00e9 na\u00efve r\u00e9sum\u00e9\n" # codespell:ignore
file_path = temp_directory / "example.py"
file_path.write_bytes(source.encode("utf-8"))

rename_imports_and_app_name(file_path, "old_name", "new_name")

expected = "import new_name # \u201cquoted\u201d caf\u00e9 na\u00efve r\u00e9sum\u00e9\n" # codespell:ignore
assert file_path.read_bytes() == expected.replace("\n", os.linesep).encode("utf-8")


def test_rename_imports_and_app_name_preserves_declared_encoding(temp_directory):
"""Python source encoding cookies are honored during rename.

Args:
temp_directory: A temporary directory fixture.
"""
source = (
"# -*- coding: cp1252 -*-\nimport old_name # caf\u00e9\n" # codespell:ignore
)
file_path = temp_directory / "example.py"
file_path.write_bytes(source.encode("cp1252"))

rename_imports_and_app_name(file_path, "old_name", "new_name")

expected = (
"# -*- coding: cp1252 -*-\nimport new_name # caf\u00e9\n" # codespell:ignore
)
assert file_path.read_bytes() == expected.replace("\n", os.linesep).encode("cp1252")


def test_cli_rename_command(temp_directory):
foo_dir = temp_directory / "foo"
foo_dir.mkdir()
Expand Down
Loading