diff --git a/news/6714.bugfix.md b/news/6714.bugfix.md new file mode 100644 index 00000000000..e3a3193a6fd --- /dev/null +++ b/news/6714.bugfix.md @@ -0,0 +1 @@ +Fix `reflex component build` crashing with `AttributeError` on Python 3.10 and 3.11 by replacing `pathlib.Path.walk()` (added in 3.12) with `os.walk()` when generating custom component `.pyi` files. diff --git a/reflex/custom_components/custom_components.py b/reflex/custom_components/custom_components.py index 6b612a0628f..00cc2076a6e 100644 --- a/reflex/custom_components/custom_components.py +++ b/reflex/custom_components/custom_components.py @@ -618,8 +618,8 @@ def _make_pyi_files(): for top_level_dir in Path.cwd().iterdir(): if not top_level_dir.is_dir() or top_level_dir.name.startswith("."): continue - for dir, _, _ in top_level_dir.walk(): - if "__pycache__" in dir.name: + for dir, _, _ in os.walk(top_level_dir): + if "__pycache__" in Path(dir).name: continue PyiGenerator().scan_all([dir]) diff --git a/tests/units/custom_components/__init__.py b/tests/units/custom_components/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/units/custom_components/test_custom_components.py b/tests/units/custom_components/test_custom_components.py new file mode 100644 index 00000000000..ff207b48294 --- /dev/null +++ b/tests/units/custom_components/test_custom_components.py @@ -0,0 +1,47 @@ +"""Unit tests for reflex/custom_components/custom_components.py.""" + +from __future__ import annotations + +from pathlib import Path + +from reflex.custom_components import custom_components + + +def test_make_pyi_files_walks_without_path_walk(monkeypatch, tmp_path: Path): + """``_make_pyi_files`` scans component dirs without relying on ``Path.walk``. + + ``pathlib.Path.walk`` only exists on Python 3.12+, but Reflex supports 3.10 + and 3.11 too, so the build must not depend on it. ``Path.walk`` is removed + here to reproduce the 3.10/3.11 environment on any interpreter; the function + must still recurse (skipping ``__pycache__``) instead of raising + ``AttributeError``. + + Args: + monkeypatch: The pytest monkeypatch fixture. + tmp_path: A temporary directory used as the project root. + """ + package = tmp_path / "my_component" + nested = package / "sub" + nested.mkdir(parents=True) + (package / "__pycache__").mkdir() + (tmp_path / ".hidden").mkdir() + + scanned: list[str] = [] + + class _RecordingGenerator: + def scan_all(self, targets, *_args, **_kwargs): + scanned.extend(str(target) for target in targets) + + monkeypatch.setattr( + "reflex_base.utils.pyi_generator.PyiGenerator", _RecordingGenerator + ) + monkeypatch.delattr(Path, "walk", raising=False) + monkeypatch.chdir(tmp_path) + + custom_components._make_pyi_files() + + scanned_names = {Path(target).name for target in scanned} + assert "my_component" in scanned_names + assert "sub" in scanned_names + assert "__pycache__" not in scanned_names + assert ".hidden" not in scanned_names