import textwrap from pathlib import Path import pytest from pyfakefs.fake_filesystem import FakeFilesystem, PatchMode from pyfakefs.fake_filesystem_unittest import Patcher from rio.cli.run_project.app_loading import load_user_app from rio.project_config import RioProjectConfig # Per default, importing from the fake file system doesn't work. So we must # define a custom fixture which enables that. @pytest.fixture def fs(): with Patcher(patch_open_code=PatchMode.AUTO) as p: yield p.fs def create_project( fs: FakeFilesystem, file_hierarchy: str, *, app_file: str, main_module: str, ) -> RioProjectConfig: FILE_CONTENTS_BY_NAME = { "foo_page.py": """ import rio @rio.page(url_segment='foo-was-loaded-correctly') class FooPage(rio.Component): def build(self): return rio.Text('foo') """ } file_hierarchy = textwrap.dedent(file_hierarchy).strip() directories_by_indent = {0: Path()} rio_toml_location: Path | None = None for line in file_hierarchy.splitlines(): file_name = line.lstrip() indent = (len(line) - len(file_name)) // 4 directory = directories_by_indent[indent] # Create the file/folder if file_name.endswith("/"): new_directory = directory / file_name.rstrip("/") directories_by_indent[indent + 1] = new_directory # new_directory.mkdir() fs.create_dir(new_directory) else: new_file = directory / file_name fs.create_file( new_file, contents=FILE_CONTENTS_BY_NAME.get(file_name, "") ) if file_name == "rio.toml": rio_toml_location = new_file (directories_by_indent[0] / app_file).write_text(""" import rio app = rio.App(build=rio.Spacer) """) assert rio_toml_location is not None return RioProjectConfig( file_path=rio_toml_location.absolute(), toml_dict={ "app": { "app-type": "website", "main-module": main_module, } }, dirty_keys=set(), ) def test_project_file(fs: FakeFilesystem): config = create_project( fs, """ my-project/ my_app.py pages/ foo_page.py rio.toml """, app_file="my-project/my_app.py", main_module="my_app", ) app = load_user_app(config).app assert app.name == "My App" assert app.assets_dir == Path("my-project/assets").absolute() assert app.pages[0].url_segment == "foo-was-loaded-correctly" def test_src_folder(fs: FakeFilesystem): config = create_project( fs, """ my-project/ src/ assets/ my_app.py pages/ foo_page.py rio.toml """, app_file="my-project/src/my_app.py", main_module="my_app", ) app = load_user_app(config).app assert app.name == "My App" assert app.assets_dir == Path("my-project/src/assets").absolute() assert app.pages[0].url_segment == "foo-was-loaded-correctly" def test_simple_project_dir(fs: FakeFilesystem): config = create_project( fs, """ my-project/ my_project/ __init__.py assets/ pages/ foo_page.py rio.toml """, app_file="my-project/my_project/__init__.py", main_module="my_project", ) app = load_user_app(config).app assert app.name == "My Project" assert app.assets_dir == Path("my-project/my_project/assets").absolute() assert app.pages[0].url_segment == "foo-was-loaded-correctly" def test_submodule(fs: FakeFilesystem): config = create_project( fs, """ my-project/ my_project/ __init__.py app.py assets/ pages/ foo_page.py rio.toml """, app_file="my-project/my_project/app.py", main_module="my_project.app", ) app = load_user_app(config).app assert app.name == "App" assert app.assets_dir == Path("my-project/my_project/assets").absolute() assert app.pages[0].url_segment == "foo-was-loaded-correctly" @pytest.mark.xfail(reason="Waiting for new pyfakefs version to be published") def test_import_sibling_module(fs: FakeFilesystem): config = create_project( fs, """ my-project/ foo.py bar.py rio.toml """, app_file="my-project/foo.py", main_module="bar", ) Path("my-project/bar.py").write_text(""" # Note: The import statement doesn't work with pyfakefs, so we have to use # importlib. import importlib app = importlib.import_module('foo').app """) app = load_user_app(config).app assert app.name == "Bar" assert app.assets_dir == Path("my-project").absolute() assert not app.pages @pytest.mark.xfail(reason="Waiting for new pyfakefs version to be published") def test_import_from_submodule(fs: FakeFilesystem): config = create_project( fs, """ my-project/ my_project/ __init__.py app.py assets/ pages/ foo_page.py helper.py rio.toml """, app_file="my-project/helper.py", main_module="my_project.app", ) Path("my-project/my_project/app.py").write_text(""" # Note: The import statement doesn't work with pyfakefs, so we have to use # importlib. import importlib app = importlib.import_module('helper').app """) app = load_user_app(config).app assert app.name == "App" assert app.assets_dir == Path("my-project/my_project/assets").absolute() assert app.pages[0].url_segment == "foo-was-loaded-correctly" @pytest.mark.xfail(reason="Waiting for new pyfakefs version to be published") def test_relative_import_from_pages(fs: FakeFilesystem): config = create_project( fs, """ my-project/ my_project/ __init__.py app.py assets/ pages/ fancy_page.py rio.toml """, app_file="my-project/my_project/app.py", main_module="my_project.app", ) Path("my-project/my_project/pages/fancy_page.py").write_text(""" # Note: The import statement doesn't work with pyfakefs, so we have to use # importlib. import importlib.util app_module = importlib.import_module(importlib.util.resolve_name('..app', __package__)) _ = app_module.app # Just to make sure the right file was imported import rio @rio.page(url_segment='fancy-page-was-loaded-correctly') class FancyPage(rio.Component): def build(self): return rio.Text('fancy') """) app = load_user_app(config).app assert app.name == "App" assert app.assets_dir == Path("my-project/my_project/assets").absolute() assert app.pages[0].url_segment == "fancy-page-was-loaded-correctly" def test_relative_paths_with_module(fs: FakeFilesystem): config = create_project( fs, """ my-project/ my_app.py my/ assets/ pages/ foo_page.py rio.toml """, app_file="my-project/my_app.py", main_module="my_app", ) Path("my-project/my_app.py").write_text(""" import rio app = rio.App( build=rio.Spacer, assets_dir='my/assets', pages='my/pages', ) """) app = load_user_app(config).app assert app.name == "My App" assert app.assets_dir == Path("my-project/my/assets").absolute() assert app.pages[0].url_segment == "foo-was-loaded-correctly" def test_relative_paths_with_package(fs: FakeFilesystem): config = create_project( fs, """ my-project/ my_project/ __init__.py my/ pages/ foo_page.py rio.toml """, app_file="my-project/my_project/__init__.py", main_module="my_project", ) Path("my-project/my_project/__init__.py").write_text(""" import rio app = rio.App( build=rio.Spacer, assets_dir='my/assets', pages='my/pages', ) """) app = load_user_app(config).app assert app.name == "My Project" assert app.assets_dir == Path("my-project/my_project/my/assets").absolute() assert app.pages[0].url_segment == "foo-was-loaded-correctly"