Skip to content

Commit ef4938a

Browse files
committed
move utils
1 parent ba899d6 commit ef4938a

File tree

2 files changed

+33
-42
lines changed

2 files changed

+33
-42
lines changed

src/reactpy/backend/standalone.py

Lines changed: 12 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Any, Callable
88

99
from reactpy.backend.middleware import ReactPyMiddleware
10+
from reactpy.backend.utils import dict_to_byte_list, find_and_replace
1011
from reactpy.core.types import ComponentType
1112

1213
_logger = getLogger(__name__)
@@ -45,11 +46,13 @@ async def standalone_app(
4546
"""ASGI app for ReactPy standalone mode."""
4647
if scope["type"] != "http":
4748
if scope["type"] != "lifespan":
48-
_logger.warning(
49+
msg = (
4950
"ReactPy app received unsupported request of type '%s' at path '%s'",
5051
scope["type"],
5152
scope["path"],
5253
)
54+
_logger.warning(msg)
55+
raise NotImplementedError(msg)
5356
return
5457

5558
# Store the HTTP response in memory for performance
@@ -59,10 +62,11 @@ async def standalone_app(
5962
# Return headers for all HTTP responses
6063
request_headers = dict(scope["headers"])
6164
response_headers: dict[str, str | int] = {
62-
"etag": f'"{self.etag}"',
65+
"etag": self.etag,
6366
"last-modified": self.last_modified,
6467
"access-control-allow-origin": "*",
6568
"cache-control": "max-age=60, public",
69+
"content-length": len(self.cached_index_html),
6670
**self.extra_headers,
6771
}
6872

@@ -74,18 +78,19 @@ async def standalone_app(
7478
200,
7579
"",
7680
content_type=b"text/html",
77-
headers=self.dict_to_byte_list(response_headers),
81+
headers=dict_to_byte_list(response_headers),
7882
)
7983

8084
# Browser already has the content cached
8185
if request_headers.get(b"if-none-match") == self.etag.encode():
86+
response_headers.pop("content-length")
8287
return await http_response(
8388
scope["method"],
8489
send,
8590
304,
8691
"",
8792
content_type=b"text/html",
88-
headers=self.dict_to_byte_list(response_headers),
93+
headers=dict_to_byte_list(response_headers),
8994
)
9095

9196
# Send the index.html
@@ -95,9 +100,7 @@ async def standalone_app(
95100
200,
96101
self.cached_index_html,
97102
content_type=b"text/html",
98-
headers=self.dict_to_byte_list(
99-
response_headers | {"content-length": len(self.cached_index_html)}
100-
),
103+
headers=dict_to_byte_list(response_headers),
101104
)
102105

103106
def match_dispatch_path(self, scope: dict) -> bool:
@@ -109,7 +112,7 @@ async def process_index_html(self):
109112
with open(self.index_html_path, encoding="utf-8") as file_handle:
110113
cached_index_html = file_handle.read()
111114

112-
self.cached_index_html = self.find_and_replace(
115+
self.cached_index_html = find_and_replace(
113116
cached_index_html,
114117
{
115118
'from "index.ts"': f'from "{self.static_path}index.js"',
@@ -124,44 +127,11 @@ async def process_index_html(self):
124127
self.etag = hashlib.md5(
125128
self.cached_index_html.encode(), usedforsecurity=False
126129
).hexdigest()
130+
self.etag = f'"{self.etag}"'
127131

128132
last_modified = os.stat(self.index_html_path).st_mtime
129133
self.last_modified = formatdate(last_modified, usegmt=True)
130134

131-
# @staticmethod
132-
# def find_js_filename(content: str) -> str:
133-
# """Find the qualified filename of the index.js file."""
134-
# substring = 'src="reactpy/static/index-'
135-
# location = content.find(substring)
136-
# if location == -1:
137-
# raise ValueError(f"Could not find {substring} in content")
138-
# start = content[location + len(substring) :]
139-
# end = start.find('"')
140-
# return f"index-{start[:end]}"
141-
142-
@staticmethod
143-
def dict_to_byte_list(
144-
data: dict[str, str | int],
145-
) -> list[tuple[bytes, bytes]]:
146-
"""Convert a dictionary to a list of byte tuples."""
147-
result: list[tuple[bytes, bytes]] = []
148-
for key, value in data.items():
149-
new_key = key.encode()
150-
new_value = (
151-
value.encode() if isinstance(value, str) else str(value).encode()
152-
)
153-
result.append((new_key, new_value))
154-
return result
155-
156-
@staticmethod
157-
def find_and_replace(content: str, replacements: dict[str, str]) -> str:
158-
"""Find and replace content. Throw and error if the substring is not found."""
159-
for key, value in replacements.items():
160-
if key not in content:
161-
raise ValueError(f"Could not find {key} in content")
162-
content = content.replace(key, value)
163-
return content
164-
165135

166136
async def http_response(
167137
method: str,

src/reactpy/backend/utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,24 @@ def check_path(url_path: str) -> str:
109109
return "URL path must start with an alphanumeric character."
110110

111111
return ""
112+
113+
114+
def find_and_replace(content: str, replacements: dict[str, str]) -> str:
115+
"""Find and replace several key-values, and throw and error if the substring is not found."""
116+
for key, value in replacements.items():
117+
if key not in content:
118+
raise ValueError(f"Could not find {key} in content")
119+
content = content.replace(key, value)
120+
return content
121+
122+
123+
def dict_to_byte_list(
124+
data: dict[str, str | int],
125+
) -> list[tuple[bytes, bytes]]:
126+
"""Convert a dictionary to a list of byte tuples."""
127+
result: list[tuple[bytes, bytes]] = []
128+
for key, value in data.items():
129+
new_key = key.encode()
130+
new_value = value.encode() if isinstance(value, str) else str(value).encode()
131+
result.append((new_key, new_value))
132+
return result

0 commit comments

Comments
 (0)