assets.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. """Helper functions for adding assets to the app."""
  2. import inspect
  3. from pathlib import Path
  4. from reflex import constants
  5. from reflex.config import EnvironmentVariables
  6. def asset(
  7. path: str,
  8. shared: bool = False,
  9. subfolder: str | None = None,
  10. _stack_level: int = 1,
  11. ) -> str:
  12. """Add an asset to the app, either shared as a symlink or local.
  13. Shared/External/Library assets:
  14. Place the file next to your including python file.
  15. Links the file to the app's external assets directory.
  16. Example:
  17. ```python
  18. # my_custom_javascript.js is a shared asset located next to the including python file.
  19. rx.script(src=rx.asset(path="my_custom_javascript.js", shared=True))
  20. rx.image(src=rx.asset(path="test_image.png", shared=True, subfolder="subfolder"))
  21. ```
  22. Local/Internal assets:
  23. Place the file in the app's assets/ directory.
  24. Example:
  25. ```python
  26. # local_image.png is an asset located in the app's assets/ directory. It cannot be shared when developing a library.
  27. rx.image(src=rx.asset(path="local_image.png"))
  28. ```
  29. Args:
  30. path: The relative path of the asset.
  31. subfolder: The directory to place the shared asset in.
  32. shared: Whether to expose the asset to other apps.
  33. _stack_level: The stack level to determine the calling file, defaults to
  34. the immediate caller 1. When using rx.asset via a helper function,
  35. increase this number for each helper function in the stack.
  36. Raises:
  37. FileNotFoundError: If the file does not exist.
  38. ValueError: If subfolder is provided for local assets.
  39. Returns:
  40. The relative URL to the asset.
  41. """
  42. assets = constants.Dirs.APP_ASSETS
  43. backend_only = EnvironmentVariables.REFLEX_BACKEND_ONLY.get()
  44. # Local asset handling
  45. if not shared:
  46. cwd = Path.cwd()
  47. src_file_local = cwd / assets / path
  48. if subfolder is not None:
  49. raise ValueError("Subfolder is not supported for local assets.")
  50. if not backend_only and not src_file_local.exists():
  51. raise FileNotFoundError(f"File not found: {src_file_local}")
  52. return f"/{path}"
  53. # Shared asset handling
  54. # Determine the file by which the asset is exposed.
  55. frame = inspect.stack()[_stack_level]
  56. calling_file = frame.filename
  57. module = inspect.getmodule(frame[0])
  58. assert module is not None
  59. external = constants.Dirs.EXTERNAL_APP_ASSETS
  60. src_file_shared = Path(calling_file).parent / path
  61. if not src_file_shared.exists():
  62. raise FileNotFoundError(f"File not found: {src_file_shared}")
  63. caller_module_path = module.__name__.replace(".", "/")
  64. subfolder = f"{caller_module_path}/{subfolder}" if subfolder else caller_module_path
  65. # Symlink the asset to the app's external assets directory if running frontend.
  66. if not backend_only:
  67. # Create the asset folder in the currently compiling app.
  68. asset_folder = Path.cwd() / assets / external / subfolder
  69. asset_folder.mkdir(parents=True, exist_ok=True)
  70. dst_file = asset_folder / path
  71. if not dst_file.exists() and (
  72. not dst_file.is_symlink() or dst_file.resolve() != src_file_shared.resolve()
  73. ):
  74. dst_file.symlink_to(src_file_shared)
  75. return f"/{external}/{subfolder}/{path}"