From 657b04cfd5a2a78f34597e7b21bb6f48fadd6c2f Mon Sep 17 00:00:00 2001 From: Anas Khan <83116240+anxkhn@users.noreply.github.com> Date: Tue, 7 Jul 2026 04:11:08 +0530 Subject: [PATCH] fix(rename): read and write source files as UTF-8 reflex rename reads every .py/.md file in the user's app with Path.read_text() and writes it back with Path.write_text(), neither of which passed encoding=. Both default to the platform locale encoding, which on Western Windows is cp1252 rather than UTF-8. UTF-8-authored source containing non-ASCII bytes (curly quotes, emoji, accented identifiers or docstrings) is therefore silently corrupted on a Western Windows locale, or aborts the rename partway with UnicodeDecodeError on a CJK codepage, leaving the tree in an inconsistent state. Pass encoding="utf-8" on both calls, matching path_ops.find_replace which already does this. Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com> --- news/6714.bugfix.md | 1 + reflex/utils/rename.py | 4 +-- tests/units/test_prerequisites.py | 47 +++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 news/6714.bugfix.md diff --git a/news/6714.bugfix.md b/news/6714.bugfix.md new file mode 100644 index 00000000000..d4bf814a8ad --- /dev/null +++ b/news/6714.bugfix.md @@ -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. diff --git a/reflex/utils/rename.py b/reflex/utils/rename.py index 48b904fe142..cfbdebf82bc 100644 --- a/reflex/utils/rename.py +++ b/reflex/utils/rename.py @@ -97,7 +97,7 @@ 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() + content = file_path.read_text(encoding="utf-8") # Replace `from old_name.` or `from old_name` with `from new_name` content = re.sub( @@ -127,7 +127,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="utf-8") def process_directory( diff --git a/tests/units/test_prerequisites.py b/tests/units/test_prerequisites.py index 63a298f78f6..9ac4a4f545e 100644 --- a/tests/units/test_prerequisites.py +++ b/tests/units/test_prerequisites.py @@ -1457,6 +1457,53 @@ 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" + ) + 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" + ) + assert file_path.read_bytes() == expected.encode("utf-8") + + def test_cli_rename_command(temp_directory): foo_dir = temp_directory / "foo" foo_dir.mkdir()