1
0

assets.py 3.3 KB

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