22
33import inspect
44from pathlib import Path
5+ from typing import TYPE_CHECKING , overload
56
67from reflex_base import constants
78from reflex_base .config import get_config
89from reflex_base .environment import EnvironmentVariables
910
11+ if TYPE_CHECKING :
12+ from typing_extensions import Buffer
13+
14+
15+ class AssetPathStr (str ):
16+ """The relative URL to an asset, with a build-time importable variant.
17+
18+ Returned by :func:`asset`. The string value is the asset URL with the
19+ configured ``frontend_path`` prepended; :attr:`importable_path` is the
20+ same asset prefixed with ``$/public`` so the asset can be referenced by
21+ a component ``library`` or module import at build time.
22+
23+ The constructor signature mirrors :class:`str`: the input is interpreted
24+ as the unprefixed asset path and both forms are derived from it at
25+ construction time.
26+ """
27+
28+ __slots__ = ("importable_path" ,)
29+
30+ importable_path : str
31+
32+ @overload
33+ def __new__ (cls , object : object = "" ) -> "AssetPathStr" : ...
34+ @overload
35+ def __new__ (
36+ cls ,
37+ object : "Buffer" ,
38+ encoding : str = "utf-8" ,
39+ errors : str = "strict" ,
40+ ) -> "AssetPathStr" : ...
41+
42+ def __new__ (
43+ cls ,
44+ object : object = "" ,
45+ encoding : str | None = None ,
46+ errors : str | None = None ,
47+ ) -> "AssetPathStr" :
48+ """Construct from an unprefixed, leading-slash asset path.
49+
50+ Args/semantics mirror :class:`str`. The resulting string is interpreted
51+ as the asset path (e.g. ``"/external/mod/file.js"``); the
52+ frontend-prefixed URL is stored as the ``AssetPathStr`` value and
53+ ``$/public`` + ``relative_path`` as :attr:`importable_path`.
54+
55+ Args:
56+ object: The object to stringify (str, bytes, or any object).
57+ encoding: Encoding to decode ``object`` with when it is bytes-like.
58+ errors: Error handler for decoding.
59+
60+ Returns:
61+ A new ``AssetPathStr`` instance.
62+ """
63+ if encoding is None and errors is None :
64+ relative_path = str .__new__ (str , object )
65+ else :
66+ relative_path = str .__new__ (
67+ str ,
68+ object , # pyright: ignore[reportArgumentType]
69+ "utf-8" if encoding is None else encoding ,
70+ "strict" if errors is None else errors ,
71+ )
72+ instance = super ().__new__ (
73+ cls , get_config ().prepend_frontend_path (relative_path )
74+ )
75+ instance .importable_path = f"$/public{ relative_path } "
76+ return instance
77+
78+ def __getnewargs__ (self ) -> tuple [str ]:
79+ """Return the unprefixed path for pickle/copy reconstruction.
80+
81+ Python's default ``str`` pickle path would feed the frontend-prefixed
82+ value back into :meth:`__new__`, double-applying the prefix and
83+ losing the :attr:`importable_path` slot. Returning the raw path
84+ (recovered by stripping the ``$/public`` prefix) lets ``__new__``
85+ rebuild both forms correctly.
86+
87+ Returns:
88+ A one-tuple containing the unprefixed asset path.
89+ """
90+ return (self .importable_path [len ("$/public" ) :],)
91+
1092
1193def remove_stale_external_asset_symlinks ():
1294 """Remove broken symlinks and empty directories in assets/external/.
@@ -42,7 +124,7 @@ def asset(
42124 shared : bool = False ,
43125 subfolder : str | None = None ,
44126 _stack_level : int = 1 ,
45- ) -> str :
127+ ) -> AssetPathStr :
46128 """Add an asset to the app, either shared as a symlink or local.
47129
48130 Shared/External/Library assets:
@@ -74,7 +156,8 @@ def asset(
74156 increase this number for each helper function in the stack.
75157
76158 Returns:
77- The relative URL to the asset.
159+ The relative URL to the asset, with an ``importable_path`` property
160+ for use as a build-time module reference.
78161
79162 Raises:
80163 FileNotFoundError: If the file does not exist.
@@ -93,7 +176,7 @@ def asset(
93176 if not backend_only and not src_file_local .exists ():
94177 msg = f"File not found: { src_file_local } "
95178 raise FileNotFoundError (msg )
96- return get_config (). prepend_frontend_path (f"/{ path } " )
179+ return AssetPathStr (f"/{ path } " )
97180
98181 # Shared asset handling
99182 # Determine the file by which the asset is exposed.
@@ -129,4 +212,4 @@ def asset(
129212 dst_file .unlink ()
130213 dst_file .symlink_to (src_file_shared )
131214
132- return get_config (). prepend_frontend_path (f"/{ external } /{ subfolder } /{ path } " )
215+ return AssetPathStr (f"/{ external } /{ subfolder } /{ path } " )
0 commit comments