only allow explicit shared, only validate local assets if not backend_only

This commit is contained in:
Benedikt Bartscher 2024-07-05 13:53:57 +02:00
parent 868fffcf83
commit 0b240dec42
No known key found for this signature in database
2 changed files with 25 additions and 63 deletions

View File

@ -10,8 +10,8 @@ from reflex import constants
def asset( def asset(
path: str, path: str,
shared: bool = False,
subfolder: Optional[str] = None, subfolder: Optional[str] = None,
shared: Optional[bool] = None,
) -> str: ) -> str:
"""Add an asset to the app, either shared as a symlink or local. """Add an asset to the app, either shared as a symlink or local.
@ -22,8 +22,8 @@ def asset(
Example: Example:
```python ```python
# my_custom_javascript.js is a shared asset located next to the including python file. # my_custom_javascript.js is a shared asset located next to the including python file.
rx.script(src=rx._x.asset(path="my_custom_javascript.js")) rx.script(src=rx._x.asset(path="my_custom_javascript.js", shared=True))
rx.image(src=rx._x.asset(path="test_image.png", subfolder="subfolder")) rx.image(src=rx._x.asset(path="test_image.png", shared=True, subfolder="subfolder"))
``` ```
Local/Internal assets: Local/Internal assets:
@ -33,67 +33,48 @@ def asset(
```python ```python
# local_image.png is an asset located in the app's assets/ directory. It cannot be shared when developing a library. # local_image.png is an asset located in the app's assets/ directory. It cannot be shared when developing a library.
rx.image(src=rx._x.asset(path="local_image.png")) rx.image(src=rx._x.asset(path="local_image.png"))
# Explicit shared=False is only needed if you have a local_image.png next to the including python file.
rx.image(src=rx._x.asset(path="local_image.png", shared=False))
``` ```
Args: Args:
path: The relative path of the asset. path: The relative path of the asset.
subfolder: The directory to place the shared asset in. subfolder: The directory to place the shared asset in.
shared: Whether to expose the asset to other apps. None means auto-detect. shared: Whether to expose the asset to other apps.
Raises: Raises:
FileNotFoundError: If the file does not exist. FileNotFoundError: If the file does not exist.
ValueError: If shared is not explicitly set and both shared and local assets exist.
Returns: Returns:
The relative URL to the asset. The relative URL to the asset.
""" """
assets = constants.Dirs.APP_ASSETS
backend_only = os.environ.get(constants.ENV_BACKEND_ONLY)
# Local asset handling
if not shared:
cwd = Path.cwd()
src_file_local = cwd / assets / path
if subfolder is not None:
raise ValueError("Subfolder is not supported for local assets.")
if not backend_only and not src_file_local.exists():
raise FileNotFoundError(f"File not found: {src_file_local}")
return f"/{path}"
# Shared asset handling
# Determine the file by which the asset is exposed. # Determine the file by which the asset is exposed.
calling_file = inspect.stack()[1].filename calling_file = inspect.stack()[1].filename
module = inspect.getmodule(inspect.stack()[1][0]) module = inspect.getmodule(inspect.stack()[1][0])
assert module is not None assert module is not None
cwd = Path.cwd()
assets = constants.Dirs.APP_ASSETS
external = constants.Dirs.EXTERNAL_APP_ASSETS external = constants.Dirs.EXTERNAL_APP_ASSETS
src_file_shared = Path(calling_file).parent / path src_file_shared = Path(calling_file).parent / path
src_file_local = cwd / assets / path if not src_file_shared.exists():
shared_exists = src_file_shared.exists()
local_exists = src_file_local.exists()
# Determine whether the asset is shared or local.
if shared is None:
if shared_exists and local_exists:
raise ValueError(
f"Both shared and local assets exist for {path}. "
+ "Please explicitly set shared=True or shared=False."
)
if not shared_exists and not local_exists:
raise FileNotFoundError(
f"Could not find file, neither at shared location {src_file_shared} nor at local location {src_file_local}"
)
shared = shared_exists
# Local asset handling
if not shared:
if subfolder is not None:
raise ValueError("Subfolder is not supported for local assets.")
if not local_exists:
raise FileNotFoundError(f"File not found: {src_file_local}")
return f"/{path}"
# Shared asset handling
if not shared_exists:
raise FileNotFoundError(f"File not found: {src_file_shared}") raise FileNotFoundError(f"File not found: {src_file_shared}")
caller_module_path = module.__name__.replace(".", "/") caller_module_path = module.__name__.replace(".", "/")
subfolder = f"{caller_module_path}/{subfolder}" if subfolder else caller_module_path subfolder = f"{caller_module_path}/{subfolder}" if subfolder else caller_module_path
# Symlink the asset to the app's external assets directory if running frontend. # Symlink the asset to the app's external assets directory if running frontend.
if not os.environ.get(constants.ENV_BACKEND_ONLY): if not backend_only:
# Create the asset folder in the currently compiling app. # Create the asset folder in the currently compiling app.
asset_folder = Path.cwd() / assets / external / subfolder asset_folder = Path.cwd() / assets / external / subfolder
asset_folder.mkdir(parents=True, exist_ok=True) asset_folder.mkdir(parents=True, exist_ok=True)
@ -105,5 +86,4 @@ def asset(
): ):
dst_file.symlink_to(src_file_shared) dst_file.symlink_to(src_file_shared)
asset_url = f"/{external}/{subfolder}/{path}" return f"/{external}/{subfolder}/{path}"
return asset_url

View File

@ -11,7 +11,7 @@ import reflex.constants as constants
def test_shared_asset() -> None: def test_shared_asset() -> None:
"""Test shared assets.""" """Test shared assets."""
# The asset function copies a file to the app's external assets directory. # The asset function copies a file to the app's external assets directory.
asset = rx._x.asset("custom_script.js", "subfolder") asset = rx._x.asset(path="custom_script.js", shared=True, subfolder="subfolder")
assert asset == "/external/test_assets/subfolder/custom_script.js" assert asset == "/external/test_assets/subfolder/custom_script.js"
result_file = Path( result_file = Path(
Path.cwd(), "assets/external/test_assets/subfolder/custom_script.js" Path.cwd(), "assets/external/test_assets/subfolder/custom_script.js"
@ -19,10 +19,10 @@ def test_shared_asset() -> None:
assert result_file.exists() assert result_file.exists()
# Running a second time should not raise an error. # Running a second time should not raise an error.
asset = rx._x.asset("custom_script.js", "subfolder") asset = rx._x.asset(path="custom_script.js", shared=True, subfolder="subfolder")
# Test the asset function without a subfolder. # Test the asset function without a subfolder.
asset = rx._x.asset("custom_script.js") asset = rx._x.asset(path="custom_script.js", shared=True)
assert asset == "/external/test_assets/custom_script.js" assert asset == "/external/test_assets/custom_script.js"
result_file = Path(Path.cwd(), "assets/external/test_assets/custom_script.js") result_file = Path(Path.cwd(), "assets/external/test_assets/custom_script.js")
assert result_file.exists() assert result_file.exists()
@ -70,30 +70,12 @@ def custom_script_in_asset_dir() -> Generator[Path, None, None]:
path.unlink() path.unlink()
def test_both_existing_implicit(custom_script_in_asset_dir: Path) -> None: def test_local_asset(custom_script_in_asset_dir: Path) -> None:
"""Test that asset raises an error if shared is not set and both files exist.
Args:
custom_script_in_asset_dir: Fixture that creates a custom_script.js file in the app's assets directory.
"""
with pytest.raises(ValueError) as e:
_ = rx._x.asset("custom_script.js")
assert (
str(e.value)
== "Both shared and local assets exist for custom_script.js. Please explicitly set shared=True or shared=False."
)
def test_both_existing_explicit(custom_script_in_asset_dir: Path) -> None:
"""Test that no error is raised if shared is set and both files exist. """Test that no error is raised if shared is set and both files exist.
Args: Args:
custom_script_in_asset_dir: Fixture that creates a custom_script.js file in the app's assets directory. custom_script_in_asset_dir: Fixture that creates a custom_script.js file in the app's assets directory.
""" """
asset = rx._x.asset("custom_script.js", shared=True)
assert asset == "/external/test_assets/custom_script.js"
asset = rx._x.asset("custom_script.js", shared=False) asset = rx._x.asset("custom_script.js", shared=False)
assert asset == "/custom_script.js" assert asset == "/custom_script.js"