Skip to content
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

External assets #3220

Merged
merged 21 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ce72c31
feat: wip rx.asset
abulvenz May 3, 2024
40bd7ab
fix: Removed test exception. Using contemporary cwd.
abulvenz May 3, 2024
3cb68c3
fix: Adding much logic to provide a nice API for the caller.
abulvenz May 3, 2024
38c127d
fix: cleaning up comments.
abulvenz May 3, 2024
c4929ec
fix: The pipeline made me do this.
abulvenz May 3, 2024
73e5a04
Merge remote-tracking branch 'upstream/main' into external-assets
abulvenz May 6, 2024
3806aec
feat: Review comments. Built in symlinks on non-Windows.
abulvenz May 6, 2024
4b80aa4
fix: Using contemporary python libraries.
abulvenz May 6, 2024
76cf9d7
Merge remote-tracking branch 'upstream/main' into external-assets
abulvenz May 7, 2024
592ef9a
Merge remote-tracking branch 'upstream/main' into external-assets
abulvenz May 15, 2024
8cbd3af
feat: Added test. Moving function to _x namespace.
abulvenz May 15, 2024
2bc36f9
Merge remote-tracking branch 'upstream/main' into external-assets
abulvenz May 15, 2024
381b732
Merge remote-tracking branch 'upstream/main' into external-assets
abulvenz May 16, 2024
69b8037
Merge remote-tracking branch 'upstream/main' into external-assets
abulvenz May 17, 2024
f46c3c8
fix: Fixing typing for python 3.8 in hooks.
abulvenz May 17, 2024
635e701
fix: Fixing typing for python 3.8 in hooks.
abulvenz May 17, 2024
a758e33
fix: Fixing typing for python 3.8 in client_state.
abulvenz May 17, 2024
627c91b
fix: Just saw it was alpha-sorted before.
abulvenz May 17, 2024
c5cc4c1
Merge remote-tracking branch 'upstream/main' into external-assets
abulvenz May 21, 2024
ca09e38
fix: Removing superficial Path constructor and inlining variable.
abulvenz May 21, 2024
9972a35
Merge remote-tracking branch 'upstream/main' into external-assets
abulvenz May 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
**/.DS_Store
**/*.pyc
assets/external/*
dist/*
examples/
.idea
Expand Down
2 changes: 2 additions & 0 deletions reflex/constants/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class Dirs(SimpleNamespace):
WEB = ".web"
# The name of the assets directory.
APP_ASSETS = "assets"
# The name of the assets directory for external ressource (a subfolder of APP_ASSETS).
EXTERNAL_APP_ASSETS = "external"
# The name of the utils file.
UTILS = "utils"
# The name of the output static directory.
Expand Down
5 changes: 5 additions & 0 deletions reflex/constants/base.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ from types import SimpleNamespace
from platformdirs import PlatformDirs

IS_WINDOWS = platform.system() == "Windows"
IS_WINDOWS_BUN_SUPPORTED_MACHINE = IS_WINDOWS and platform.machine() in [
"AMD64",
"x86_64",
]

class Dirs(SimpleNamespace):
WEB = ".web"
APP_ASSETS = "assets"
EXTERNAL_APP_ASSETS = "external"
UTILS = "utils"
STATIC = "_static"
STATE_PATH = "/".join([UTILS, "state"])
Expand Down
2 changes: 2 additions & 0 deletions reflex/experimental/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from ..utils.console import warn
from . import hooks as hooks
from .assets import asset as asset
from .client_state import ClientStateVar as ClientStateVar
from .layout import layout as layout
from .misc import run_in_thread as run_in_thread
Expand All @@ -17,6 +18,7 @@
)

_x = SimpleNamespace(
asset=asset,
client_state=ClientStateVar.create,
hooks=hooks,
layout=layout,
Expand Down
57 changes: 57 additions & 0 deletions reflex/experimental/assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Helper functions for adding assets to the app."""
import inspect
from pathlib import Path
from typing import Optional

from reflex import constants


def asset(relative_filename: str, subfolder: Optional[str] = None) -> str:
"""Add an asset to the app.
Place the file next to your including python file.
Copies 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"))
```

Args:
relative_filename: The relative filename of the asset.
subfolder: The directory to place the asset in.

Raises:
FileNotFoundError: If the file does not exist.

Returns:
The relative URL to the copied asset.
"""
# Determine the file by which the asset is exposed.
calling_file = inspect.stack()[1].filename
module = inspect.getmodule(inspect.stack()[1][0])
assert module is not 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

assets = constants.Dirs.APP_ASSETS
external = constants.Dirs.EXTERNAL_APP_ASSETS

if not src_file.exists():
raise FileNotFoundError(f"File not found: {src_file}")

# Create the asset folder in the currently compiling app.
cwd = Path.cwd()
asset_folder = Path(Path(cwd) / assets / external / subfolder)
abulvenz marked this conversation as resolved.
Show resolved Hide resolved
asset_folder.mkdir(parents=True, exist_ok=True)

dst_file = asset_folder / relative_filename

if not dst_file.exists():
dst_file.symlink_to(src_file)

asset_url = f"/{external}/{subfolder}/{relative_filename}"
return asset_url
6 changes: 4 additions & 2 deletions reflex/experimental/client_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import dataclasses
import sys
from typing import Any, Callable, Optional, Type
from typing import Any, Callable, Optional, Type, Union

from reflex import constants
from reflex.event import EventChain, EventHandler, EventSpec, call_script
Expand Down Expand Up @@ -171,7 +171,9 @@ def set(self) -> Var:
)
)

def retrieve(self, callback: EventHandler | Callable | None = None) -> EventSpec:
def retrieve(
self, callback: Union[EventHandler, Callable, None] = None
) -> EventSpec:
"""Pass the value of the client state variable to a backend EventHandler.

The event handler must `yield` or `return` the EventSpec to trigger the event.
Expand Down
14 changes: 8 additions & 6 deletions reflex/experimental/hooks.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Add standard Hooks wrapper for React."""

from typing import Optional, Union

from reflex.utils.imports import ImportVar
from reflex.vars import Var, VarData


def _add_react_import(v: Var | None, tags: str | list):
def _add_react_import(v: Optional[Var], tags: Union[str, list]):
if v is None:
return

Expand All @@ -16,7 +18,7 @@ def _add_react_import(v: Var | None, tags: str | list):
)


def const(name, value) -> Var | None:
def const(name, value) -> Optional[Var]:
"""Create a constant Var.

Args:
Expand All @@ -31,7 +33,7 @@ def const(name, value) -> Var | None:
return Var.create(f"const {name} = {value}")


def useCallback(func, deps) -> Var | None:
def useCallback(func, deps) -> Optional[Var]:
"""Create a useCallback hook with a function and dependencies.

Args:
Expand All @@ -49,7 +51,7 @@ def useCallback(func, deps) -> Var | None:
return v


def useContext(context) -> Var | None:
def useContext(context) -> Optional[Var]:
"""Create a useContext hook with a context.

Args:
Expand All @@ -63,7 +65,7 @@ def useContext(context) -> Var | None:
return v


def useRef(default) -> Var | None:
def useRef(default) -> Optional[Var]:
"""Create a useRef hook with a default value.

Args:
Expand All @@ -77,7 +79,7 @@ def useRef(default) -> Var | None:
return v


def useState(var_name, default=None) -> Var | None:
def useState(var_name, default=None) -> Optional[Var]:
"""Create a useState hook with a variable name and setter name.

Args:
Expand Down
1 change: 1 addition & 0 deletions tests/experimental/custom_script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const test = "inside custom_script.js";
36 changes: 36 additions & 0 deletions tests/experimental/test_assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import shutil
from pathlib import Path

import pytest

import reflex as rx


def test_asset():
# Test the asset function.

# The asset function copies a file to the app's external assets directory.
asset = rx._x.asset("custom_script.js", "subfolder")
assert asset == "/external/test_assets/subfolder/custom_script.js"
result_file = Path(
Path.cwd(), "assets/external/test_assets/subfolder/custom_script.js"
)
assert result_file.exists()

# Running a second time should not raise an error.
asset = rx._x.asset("custom_script.js", "subfolder")

# Test the asset function without a subfolder.
asset = rx._x.asset("custom_script.js")
assert asset == "/external/test_assets/custom_script.js"
result_file = Path(Path.cwd(), "assets/external/test_assets/custom_script.js")
assert result_file.exists()

# clean up
shutil.rmtree(Path.cwd() / "assets/external")

with pytest.raises(FileNotFoundError):
asset = rx._x.asset("non_existent_file.js")

# Nothing is done to assets when file does not exist.
assert not Path(Path.cwd() / "assets/external").exists()
Loading