-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rx._x.asset improvements #3624
rx._x.asset improvements #3624
Changes from 12 commits
4696f49
1c4c9fd
bec31ff
548094f
868fffc
0b240de
d1f537f
b1a8899
cf98660
5a26dfc
1370204
a5b1f49
47e77f4
45e2852
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,55 +5,86 @@ | |
from typing import Optional | ||
|
||
from reflex import constants | ||
from reflex.utils.exec import is_backend_only | ||
|
||
|
||
def asset(relative_filename: str, subfolder: Optional[str] = None) -> str: | ||
"""Add an asset to the app. | ||
def asset( | ||
path: str, | ||
shared: bool = False, | ||
subfolder: Optional[str] = None, | ||
) -> 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. | ||
Copies the file to the app's external assets directory. | ||
Links the file to the app's external assets directory. | ||
|
||
Example: | ||
```python | ||
rx.script(src=rx._x.asset("my_custom_javascript.js")) | ||
rx.image(src=rx._x.asset("test_image.png","subfolder")) | ||
# 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", shared=True)) | ||
rx.image(src=rx._x.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._x.asset(path="local_image.png")) | ||
``` | ||
|
||
Args: | ||
relative_filename: The relative filename of the asset. | ||
subfolder: The directory to place the asset in. | ||
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. | ||
|
||
Raises: | ||
FileNotFoundError: If the file does not exist. | ||
ValueError: If the module is None. | ||
ValueError: If subfolder is provided for local assets. | ||
|
||
Returns: | ||
The relative URL to the copied asset. | ||
The relative URL to the asset. | ||
""" | ||
assets = constants.Dirs.APP_ASSETS | ||
backend_only = is_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. | ||
calling_file = inspect.stack()[1].filename | ||
module = inspect.getmodule(inspect.stack()[1][0]) | ||
if module is None: | ||
raise ValueError("Module is None") | ||
caller_module_path = module.__name__.replace(".", "/") | ||
|
||
subfolder = f"{caller_module_path}/{subfolder}" if subfolder else caller_module_path | ||
|
||
src_file = Path(calling_file).parent / relative_filename | ||
assert module is not None | ||
|
||
assets = constants.Dirs.APP_ASSETS | ||
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}") | ||
|
||
if not src_file.exists(): | ||
raise FileNotFoundError(f"File not found: {src_file}") | ||
caller_module_path = module.__name__.replace(".", "/") | ||
subfolder = f"{caller_module_path}/{subfolder}" if subfolder else caller_module_path | ||
|
||
# Create the asset folder in the currently compiling app. | ||
asset_folder = Path.cwd() / assets / external / subfolder | ||
asset_folder.mkdir(parents=True, exist_ok=True) | ||
# 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 / relative_filename | ||
dst_file = asset_folder / path | ||
|
||
if not dst_file.exists(): | ||
dst_file.symlink_to(src_file) | ||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this makes me a bit nervous because symlinks are not generally enabled or available to unprivileged users on windows. can we have a fallback path that just copies the files on windows or if this operation fails There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this PR did not introduce the symlinks, they were there before. i can address it in another PR |
||
|
||
asset_url = f"/{external}/{subfolder}/{relative_filename}" | ||
return asset_url | ||
return f"/{external}/{subfolder}/{path}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the
shared
argument is a little bit confusing to me... isshared=False
for referencing files that are already in the app'sassets/
directory?i suppose this is to align the API to work with source-relative assets as well as assets that are already in the correct place? But setting the default to
False
seems to break how existing callers are already using this function to reference source-relative assets.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shared=True
ist for librarys which want to expose the assets to other python modules/reflex apps. Shared mode expects the files to be relative to the python source file whererx._x.asset
is called.shared=False
is for "normal"/"local" assets in reflex. It just returns the serving path and checks if the file really exits.