Skip to content

Commit 57d47da

Browse files
committed
more refactoring
1 parent 5df567e commit 57d47da

File tree

1 file changed

+63
-36
lines changed
  • src/py/reactpy/reactpy/backend

1 file changed

+63
-36
lines changed

src/py/reactpy/reactpy/backend/asgi.py

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
from reactpy.backend.hooks import ConnectionContext
2222
from reactpy.backend.types import Connection, Location
2323
from reactpy.config import REACTPY_WEB_MODULES_DIR
24+
from reactpy.core.component import Component
2425
from reactpy.core.layout import Layout
2526
from reactpy.core.serve import serve_layout
26-
from reactpy.core.types import ComponentConstructor, VdomDict
27+
from reactpy.core.types import ComponentType, VdomDict
2728

2829
_logger = logging.getLogger(__name__)
2930
_backhaul_loop = asyncio.new_event_loop()
@@ -42,20 +43,24 @@ def start_backhaul_loop():
4243
class ReactPy:
4344
def __init__(
4445
self,
45-
app_or_component: ComponentConstructor | Callable[..., Coroutine],
46+
app_or_component: ComponentType | Callable[..., Coroutine],
4647
*,
47-
dispatcher_path: str = "^reactpy/([^/]+)/?",
48-
js_modules_path: str | None = "^reactpy/modules/([^/]+)/?",
49-
static_path: str | None = "^reactpy/static/([^/]+)/?",
48+
dispatcher_path: str = "reactpy/",
49+
web_modules_path: str = "reactpy/modules/",
50+
web_modules_dir: Path | str | None = REACTPY_WEB_MODULES_DIR.current,
51+
static_path: str = "reactpy/static/",
5052
static_dir: Path | str | None = None,
5153
head: Sequence[VdomDict] | VdomDict | str = "",
5254
backhaul_thread: bool = True,
5355
block_size: int = 8192,
5456
) -> None:
57+
"""Anything initialized in this method will be shared across all
58+
requests."""
5559
# Convert kwargs to class attributes
56-
self.dispatch_path = re.compile(dispatcher_path)
57-
self.js_modules_path = re.compile(js_modules_path) if js_modules_path else None
58-
self.static_path = re.compile(static_path) if static_path else None
60+
self.dispatch_path = re.compile(f"^{dispatcher_path}(?P<dotted_path>[^/]+)/?")
61+
self.js_modules_path = re.compile(f"^{web_modules_path}")
62+
self.web_modules_dir = web_modules_dir
63+
self.static_path = re.compile(f"^{static_path}")
5964
self.static_dir = static_dir
6065
self.head = vdom_head_elements_to_html(head)
6166
self.backhaul_thread = backhaul_thread
@@ -67,30 +72,39 @@ def __init__(
6772
if asyncio.iscoroutinefunction(app_or_component)
6873
else None
6974
)
70-
self.component: ComponentConstructor | None = (
71-
None if self.user_app else app_or_component
75+
self.component: ComponentType | None = (
76+
None if self.user_app else app_or_component # type: ignore
7277
)
7378
self.all_paths: re.Pattern = re.compile(
7479
"|".join(
75-
path for path in [dispatcher_path, js_modules_path, static_path] if path
80+
path
81+
for path in [dispatcher_path, web_modules_path, static_path]
82+
if path
7683
)
7784
)
7885
self.dispatcher: Future | asyncio.Task | None = None
7986
self._cached_index_html: str = ""
8087
self._static_file_server: StaticFiles | None = None
81-
self._js_module_server: StaticFiles | None = None
82-
self.connected: bool = False
83-
# TODO: Remove this setting from ReactPy config
84-
self.js_modules_dir: Path | None = REACTPY_WEB_MODULES_DIR.current
88+
self._web_module_server: StaticFiles | None = None
89+
90+
# Startup tasks
91+
if self.backhaul_thread and not _backhaul_thread.is_alive():
92+
_backhaul_thread.start()
93+
if self.web_modules_dir != REACTPY_WEB_MODULES_DIR.current:
94+
REACTPY_WEB_MODULES_DIR.set_current(self.web_modules_dir)
8595

8696
# Validate the arguments
8797
if not self.component and not self.user_app:
8898
raise TypeError(
8999
"The first argument to ReactPy(...) must be a component or an "
90100
"ASGI application."
91101
)
92-
if self.backhaul_thread and not _backhaul_thread.is_alive():
93-
_backhaul_thread.start()
102+
if check_path(dispatcher_path):
103+
raise ValueError("Invalid `dispatcher_path`.")
104+
if check_path(web_modules_path):
105+
raise ValueError("Invalid `web_modules_path`.")
106+
if check_path(static_path):
107+
raise ValueError("Invalid `static_path`.")
94108

95109
async def __call__(
96110
self,
@@ -131,12 +145,12 @@ async def reactpy_app(
131145
return
132146

133147
# JS modules app
134-
if self.js_modules_path and re.match(self.js_modules_path, scope["path"]):
135-
await self.js_module_app(scope, receive, send)
148+
if re.match(self.js_modules_path, scope["path"]):
149+
await self.web_module_app(scope, receive, send)
136150
return
137151

138152
# Static file app
139-
if self.static_path and re.match(self.static_path, scope["path"]):
153+
if re.match(self.static_path, scope["path"]):
140154
await self.static_file_app(scope, receive, send)
141155
return
142156

@@ -181,23 +195,25 @@ async def component_dispatch_app(
181195
else:
182196
await recv_queue_put
183197

184-
async def js_module_app(
198+
async def web_module_app(
185199
self,
186200
scope: dict[str, Any],
187201
receive: Callable[..., Coroutine],
188202
send: Callable[..., Coroutine],
189203
) -> None:
190204
"""ASGI app for ReactPy web modules."""
191-
if not self.js_modules_dir:
192-
raise RuntimeError("No web modules directory configured.")
193-
if not self.js_modules_path:
194-
raise RuntimeError(
195-
"Web modules cannot be served without defining `js_module_path`."
205+
if not self.web_modules_dir:
206+
await asyncio.to_thread(
207+
_logger.info,
208+
"Tried to serve web module without a configured directory.",
196209
)
197-
if not self._js_module_server:
198-
self._js_module_server = StaticFiles(directory=self.js_modules_dir)
210+
if self.user_app:
211+
await self.user_app(scope, receive, send)
212+
return
199213

200-
await self._js_module_server(scope, receive, send)
214+
if not self._web_module_server:
215+
self._web_module_server = StaticFiles(directory=self.web_modules_dir)
216+
await self._web_module_server(scope, receive, send)
201217

202218
async def static_file_app(
203219
self,
@@ -206,17 +222,18 @@ async def static_file_app(
206222
send: Callable[..., Coroutine],
207223
) -> None:
208224
"""ASGI app for ReactPy static files."""
225+
# If no static directory is configured, serve the user's application
209226
if not self.static_dir:
210-
raise RuntimeError(
211-
"Static files cannot be served without defining `static_dir`."
212-
)
213-
if not self.static_path:
214-
raise RuntimeError(
215-
"Static files cannot be served without defining `static_path`."
227+
await asyncio.to_thread(
228+
_logger.info,
229+
"Tried to serve static file without a configured directory.",
216230
)
231+
if self.user_app:
232+
await self.user_app(scope, receive, send)
233+
return
234+
217235
if not self._static_file_server:
218236
self._static_file_server = StaticFiles(directory=self.static_dir)
219-
220237
await self._static_file_server(scope, receive, send)
221238

222239
async def standalone_app(
@@ -317,3 +334,13 @@ async def http_response(
317334
# Head requests don't need a body
318335
if scope["method"] != "HEAD":
319336
await send({"type": "http.response.body", "body": message.encode()})
337+
338+
339+
def check_path(url_path: str) -> bool:
340+
"""Check that a path is valid URL path."""
341+
return (
342+
not url_path
343+
or not isinstance(url_path, str)
344+
or not url_path[0].isalnum()
345+
or not url_path.endswith("/")
346+
)

0 commit comments

Comments
 (0)