Skip to content

Commit b23f6b6

Browse files
committed
refactoring in preparation for functional middleware
1 parent 1304126 commit b23f6b6

File tree

5 files changed

+28
-28
lines changed

5 files changed

+28
-28
lines changed

src/js/packages/@reactpy/client/src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,6 @@ export class ReactPyClient
7878
}
7979

8080
loadModule(moduleName: string): Promise<ReactPyModule> {
81-
return import(`${this.urls.jsModules}/${moduleName}`);
81+
return import(`${this.urls.jsModulesPath}/${moduleName}`);
8282
}
8383
}

src/js/packages/@reactpy/client/src/mount.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,26 @@ export function mountReactPy(props: MountProps) {
77
// WebSocket route for component rendering
88
const wsProtocol = `ws${window.location.protocol === "https:" ? "s" : ""}:`;
99
const wsOrigin = `${wsProtocol}//${window.location.host}`;
10-
const componentUrl = new URL(`${wsOrigin}/${props.pathPrefix}/`);
10+
const componentUrl = new URL(`${wsOrigin}/${props.pathPrefix}/${props.appendComponentPath || ""}`);
1111

1212
// Embed the initial HTTP path into the WebSocket URL
1313
componentUrl.searchParams.append("http_pathname", window.location.pathname);
1414
if (window.location.search) {
15-
componentUrl.searchParams.append("http_search", window.location.search);
15+
componentUrl.searchParams.append("http_query_string", window.location.search);
1616
}
1717

1818
// Configure a new ReactPy client
1919
const client = new ReactPyClient({
2020
urls: {
2121
componentUrl: componentUrl,
22-
query: document.location.search,
23-
jsModules: `${window.location.origin}/${props.pathPrefix}/modules/`,
22+
jsModulesPath: `${window.location.origin}/${props.pathPrefix}/modules/`,
23+
queryString: document.location.search,
2424
},
2525
reconnectOptions: {
2626
interval: props.reconnectInterval || 750,
2727
maxInterval: props.reconnectMaxInterval || 60000,
28-
backoffMultiplier: props.reconnectBackoffMultiplier || 1.25,
2928
maxRetries: props.reconnectMaxRetries || 150,
29+
backoffMultiplier: props.reconnectBackoffMultiplier || 1.25,
3030
},
3131
mountElement: props.mountElement,
3232
});

src/js/packages/@reactpy/client/src/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ export type CreateReconnectingWebSocketProps = {
2323

2424
export type ReactPyUrls = {
2525
componentUrl: URL;
26-
query: string;
27-
jsModules: string;
26+
jsModulesPath: string;
27+
queryString: string;
2828
};
2929

3030
export type GenericReactPyClientProps = {
@@ -36,6 +36,7 @@ export type GenericReactPyClientProps = {
3636
export type MountProps = {
3737
mountElement: HTMLElement;
3838
pathPrefix: string;
39+
appendComponentPath?: string;
3940
reconnectInterval?: number;
4041
reconnectMaxInterval?: number;
4142
reconnectMaxRetries?: number;

src/reactpy/backend/middleware.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@
2626

2727

2828
class ReactPyMiddleware:
29-
_asgi_single_callable = True
30-
single_root_component: bool = False
29+
_asgi_single_callable: bool = True
3130
root_component: RootComponentConstructor | None = None
31+
root_components: dict[str, RootComponentConstructor]
32+
multiple_root_components: bool = True
3233

3334
def __init__(
3435
self,
@@ -53,10 +54,7 @@ def __init__(
5354

5455
# Component attributes
5556
self.user_app = guarantee_single_callable(app)
56-
self.component_dotted_paths = set(root_components)
57-
self.components: dict[str, RootComponentConstructor] = import_components(
58-
self.component_dotted_paths
59-
)
57+
self.root_components = import_components(root_components)
6058

6159
# Directory attributes
6260
self.web_modules_dir = web_modules_dir or REACTPY_WEB_MODULES_DIR.current
@@ -148,12 +146,16 @@ async def run_dispatcher(
148146
) -> None:
149147
# Get the component from the URL.
150148
try:
151-
if not self.parent.single_root_component:
149+
if self.parent.multiple_root_components:
152150
url_match = re.match(self.parent.dispatcher_pattern, scope["path"])
153151
if not url_match:
154152
raise RuntimeError("Could not find component in URL path.")
155-
dotted_path = url_match[1]
156-
component = self.parent.components[dotted_path]
153+
dotted_path = url_match["dotted_path"]
154+
if dotted_path not in self.parent.root_components:
155+
raise RuntimeError(
156+
f"Attempting to use an unregistered root component {dotted_path}."
157+
)
158+
component = self.parent.root_components[dotted_path]
157159
elif self.parent.root_component:
158160
component = self.parent.root_component
159161
else:

src/reactpy/backend/standalone.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class ReactPy(ReactPyMiddleware):
2222
last_modified = ""
2323
templates_dir = Path(__file__).parent.parent / "templates"
2424
index_html_path = templates_dir / "index.html"
25-
single_root_component = True
25+
multiple_root_components = False
2626

2727
def __init__(
2828
self,
@@ -66,7 +66,7 @@ async def reactpy_app(
6666

6767
# Store the HTTP response in memory for performance
6868
if not self.cached_index_html:
69-
await self.process_index_html()
69+
self.process_index_html()
7070

7171
# Return headers for all HTTP responses
7272
request_headers = dict(scope["headers"])
@@ -113,11 +113,11 @@ async def reactpy_app(
113113
)
114114

115115
def match_dispatch_path(self, scope: dict) -> bool:
116-
"""Check if the path matches the dispatcher path."""
117-
return str(scope["path"]).startswith(self.dispatcher_path)
116+
"""Method override to remove `dotted_path` from the dispatcher URL."""
117+
return str(scope["path"]) == self.dispatcher_path
118118

119-
async def process_index_html(self):
120-
"""Process the index.html file."""
119+
def process_index_html(self):
120+
"""Process the index.html and store the results in memory."""
121121
with open(self.index_html_path, encoding="utf-8") as file_handle:
122122
cached_index_html = file_handle.read()
123123

@@ -135,10 +135,7 @@ async def process_index_html(self):
135135
},
136136
)
137137

138-
self.etag = hashlib.md5(
139-
self.cached_index_html.encode(), usedforsecurity=False
140-
).hexdigest()
141-
self.etag = f'"{self.etag}"'
138+
self.etag = f'"{hashlib.md5(self.cached_index_html.encode(), usedforsecurity=False).hexdigest()}"'
142139
self.last_modified = formatdate(
143140
os.stat(self.index_html_path).st_mtime, usegmt=True
144141
)
@@ -152,7 +149,7 @@ async def http_response(
152149
content_type: bytes = b"text/plain",
153150
headers: Sequence = (),
154151
) -> None:
155-
"""Send a simple response."""
152+
"""Sends a HTTP response using the ASGI `send` API."""
156153
# Head requests don't need body content
157154
if method == "HEAD":
158155
await send(

0 commit comments

Comments
 (0)