"""Helper functions for adding assets to the app.""" import inspect from pathlib import Path from reflex import constants from reflex.config import EnvironmentVariables def asset( path: str, shared: bool = False, subfolder: str | None = None, _stack_level: int = 1, ) -> str: """Add an asset to the app, either shared as a symlink or local. Shared/External/Library assets: Place the file next to your including python file. Links the file to the app's external assets directory. Example: ```python # my_custom_javascript.js is a shared asset located next to the including python file. rx.script(src=rx.asset(path="my_custom_javascript.js", shared=True)) rx.image(src=rx.asset(path="test_image.png", shared=True, subfolder="subfolder")) ``` Local/Internal assets: Place the file in the app's assets/ directory. Example: ```python # 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.asset(path="local_image.png")) ``` Args: path: The relative path of the asset. subfolder: The directory to place the shared asset in. shared: Whether to expose the asset to other apps. _stack_level: The stack level to determine the calling file, defaults to the immediate caller 1. When using rx.asset via a helper function, increase this number for each helper function in the stack. Raises: FileNotFoundError: If the file does not exist. ValueError: If subfolder is provided for local assets. Returns: The relative URL to the asset. """ assets = constants.Dirs.APP_ASSETS backend_only = EnvironmentVariables.REFLEX_BACKEND_ONLY.get() # 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. frame = inspect.stack()[_stack_level] calling_file = frame.filename module = inspect.getmodule(frame[0]) assert module is not None external = constants.Dirs.EXTERNAL_APP_ASSETS src_file_shared = Path(calling_file).parent / path if not src_file_shared.exists(): raise FileNotFoundError(f"File not found: {src_file_shared}") caller_module_path = module.__name__.replace(".", "/") 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. if not backend_only: # Create the asset folder in the currently compiling app. asset_folder = Path.cwd() / assets / external / subfolder asset_folder.mkdir(parents=True, exist_ok=True) dst_file = asset_folder / path if not dst_file.exists() and ( not dst_file.is_symlink() or dst_file.resolve() != src_file_shared.resolve() ): dst_file.symlink_to(src_file_shared) return f"/{external}/{subfolder}/{path}"