Skip to content

Commit 24d08e9

Browse files
committed
switch to prepend_body
1 parent 283ccdd commit 24d08e9

7 files changed

Lines changed: 63 additions & 247 deletions

File tree

src/build_scripts/install_deps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def find_deps(data):
3030

3131

3232
def install_deps():
33-
pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
33+
pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml"
3434
with open(pyproject_path, "rb") as f:
3535
pyproject_data = toml.load(f)
3636
find_deps(pyproject_data)

src/reactpy/executors/asgi/pyscript.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
pyscript_component_html,
1818
pyscript_setup_html,
1919
)
20-
from reactpy.executors.utils import html_noscript_to_html, vdom_head_to_html
21-
from reactpy.types import Component, ReactPyConfig, RootComponentConstructor, VdomDict
20+
from reactpy.executors.utils import vdom_head_to_html
21+
from reactpy.types import ReactPyConfig, VdomDict
22+
from reactpy.utils import reactpy_to_string
2223

2324

2425
class ReactPyCsr(ReactPy):
@@ -32,11 +33,7 @@ def __init__(
3233
initial: str | VdomDict = "",
3334
http_headers: dict[str, str] | None = None,
3435
html_head: VdomDict | None = None,
35-
html_noscript: str
36-
| Path
37-
| Component
38-
| RootComponentConstructor
39-
| None = "Enable JavaScript to view this site.",
36+
prepend_body: VdomDict | None = ..., # type: ignore[assignment]
4037
html_lang: str = "en",
4138
**settings: Unpack[ReactPyConfig],
4239
) -> None:
@@ -64,9 +61,9 @@ def __init__(
6461
commonly used to render a loading animation.
6562
http_headers: Additional headers to include in the HTTP response for the base HTML document.
6663
html_head: Additional head elements to include in the HTML response.
67-
html_noscript: String, Path to an HTML file, or component rendered to HTML
68-
inside a `<noscript>` tag in the HTML body.
69-
If None, then noscript is not rendered.
64+
prepend_body: Content rendered at the start of the ``<body>`` element.
65+
A ``VdomDict`` constructed via ``html.*`` functions, or ``None`` to omit.
66+
Defaults to ``html.noscript("Enable JavaScript to view this site.")``.
7067
html_lang: The language of the HTML document.
7168
settings:
7269
Global ReactPy configuration settings that affect behavior and performance. Most settings
@@ -86,7 +83,10 @@ def __init__(
8683
self.extra_headers = http_headers or {}
8784
self.dispatcher_pattern = re.compile(f"^{self.dispatcher_path}?")
8885
self.html_head = html_head or html.head()
89-
self.html_noscript = html_noscript
86+
if prepend_body is not ...:
87+
self.prepend_body = prepend_body
88+
else:
89+
self.prepend_body = html.noscript("Enable JavaScript to view this site.")
9090
self.html_lang = html_lang
9191

9292
def match_dispatch_path(self, scope: AsgiWebsocketScope) -> bool: # nocov
@@ -106,7 +106,11 @@ class ReactPyPyscriptApp(ReactPyApp):
106106
def render_index_html(self) -> None:
107107
"""Process the index.html and store the results in this class."""
108108
head_content = vdom_head_to_html(self.parent.html_head)
109-
noscript = html_noscript_to_html(self.parent.html_noscript)
109+
body_content = (
110+
""
111+
if self.parent.prepend_body is None
112+
else reactpy_to_string(self.parent.prepend_body)
113+
)
110114
pyscript_setup = pyscript_setup_html(
111115
extra_py=self.parent.extra_py,
112116
extra_js=self.parent.extra_js,
@@ -124,7 +128,7 @@ def render_index_html(self) -> None:
124128
f'<html lang="{self.parent.html_lang}">'
125129
f"{head_content}"
126130
"<body>"
127-
f"{noscript}"
131+
f"{body_content}"
128132
f"{pyscript_component}"
129133
"</body>"
130134
"</html>"

src/reactpy/executors/asgi/standalone.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from datetime import UTC, datetime
88
from email.utils import formatdate
99
from logging import getLogger
10-
from pathlib import Path
1110
from typing import Literal, Unpack, cast, overload
1211

1312
from asgi_tools import ResponseHTML
@@ -26,18 +25,16 @@
2625
)
2726
from reactpy.executors.pyscript.utils import pyscript_setup_html
2827
from reactpy.executors.utils import (
29-
html_noscript_to_html,
3028
server_side_component_html,
3129
vdom_head_to_html,
3230
)
3331
from reactpy.types import (
34-
Component,
3532
PyScriptOptions,
3633
ReactPyConfig,
3734
RootComponentConstructor,
3835
VdomDict,
3936
)
40-
from reactpy.utils import import_dotted_path, string_to_reactpy
37+
from reactpy.utils import import_dotted_path, reactpy_to_string, string_to_reactpy
4138

4239
_logger = getLogger(__name__)
4340

@@ -51,11 +48,7 @@ def __init__(
5148
*,
5249
http_headers: dict[str, str] | None = None,
5350
html_head: VdomDict | None = None,
54-
html_noscript: str
55-
| Path
56-
| Component
57-
| RootComponentConstructor
58-
| None = "Enable JavaScript to view this site.",
51+
prepend_body: VdomDict | None = ..., # type: ignore[assignment]
5952
html_lang: str = "en",
6053
pyscript_setup: bool = False,
6154
pyscript_options: PyScriptOptions | None = None,
@@ -67,8 +60,9 @@ def __init__(
6760
root_component: The root component to render. This app is typically a single page application.
6861
http_headers: Additional headers to include in the HTTP response for the base HTML document.
6962
html_head: Additional head elements to include in the HTML response.
70-
html_noscript: String, Path to an HTML file, or component rendered to HTML
71-
inside a `<noscript>` tag in the HTML body.
63+
prepend_body: Content rendered at the start of the ``<body>`` element.
64+
A ``VdomDict`` constructed via ``html.*`` functions, or ``None`` to omit.
65+
Defaults to ``html.noscript("Enable JavaScript to view this site.")``.
7266
html_lang: The language of the HTML document.
7367
pyscript_setup: Whether to automatically load PyScript within your HTML head.
7468
pyscript_options: Options to configure PyScript behavior.
@@ -79,7 +73,10 @@ def __init__(
7973
self.extra_headers = http_headers or {}
8074
self.dispatcher_pattern = re.compile(f"^{self.dispatcher_path}?")
8175
self.html_head = html_head or html.head()
82-
self.html_noscript = html_noscript
76+
if prepend_body is not ...:
77+
self.prepend_body = prepend_body
78+
else:
79+
self.prepend_body = html.noscript("Enable JavaScript to view this site.")
8380
self.html_lang = html_lang
8481

8582
if pyscript_setup:
@@ -243,13 +240,17 @@ async def __call__(
243240

244241
def render_index_html(self) -> None:
245242
"""Process the index.html and store the results in this class."""
246-
noscript = html_noscript_to_html(self.parent.html_noscript)
243+
body_content = (
244+
""
245+
if self.parent.prepend_body is None
246+
else reactpy_to_string(self.parent.prepend_body)
247+
)
247248
self._index_html = (
248249
"<!doctype html>"
249250
f'<html lang="{self.parent.html_lang}">'
250251
f"{vdom_head_to_html(self.parent.html_head)}"
251252
"<body>"
252-
f"{noscript}"
253+
f"{body_content}"
253254
f"{server_side_component_html(element_id='app', class_='', component_path='')}"
254255
"</body>"
255256
"</html>"

src/reactpy/executors/utils.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import logging
44
from collections.abc import Iterable
5-
from pathlib import Path
65
from typing import Any
76

87
from reactpy._option import Option
@@ -13,7 +12,7 @@
1312
REACTPY_RECONNECT_MAX_INTERVAL,
1413
REACTPY_RECONNECT_MAX_RETRIES,
1514
)
16-
from reactpy.types import Component, ReactPyConfig, RootComponentConstructor, VdomDict
15+
from reactpy.types import ReactPyConfig, VdomDict
1716
from reactpy.utils import import_dotted_path, reactpy_to_string
1817

1918
logger = logging.getLogger(__name__)
@@ -47,20 +46,6 @@ def vdom_head_to_html(head: VdomDict) -> str:
4746
raise ValueError("Head element must be constructed with `html.head`.")
4847

4948

50-
def html_noscript_to_html(
51-
html_noscript: str | Path | VdomDict | Component | RootComponentConstructor | None,
52-
) -> str:
53-
if html_noscript is None:
54-
return ""
55-
if isinstance(html_noscript, Path):
56-
html_noscript = html_noscript.read_text()
57-
elif callable(html_noscript):
58-
html_noscript = reactpy_to_string(html_noscript())
59-
elif isinstance(html_noscript, dict):
60-
html_noscript = reactpy_to_string(html_noscript)
61-
return f"<noscript>{html_noscript}</noscript>"
62-
63-
6449
def process_settings(settings: ReactPyConfig) -> None:
6550
"""Process the settings and return the final configuration."""
6651
from reactpy import config

tests/test_asgi/test_pyscript.py

Lines changed: 11 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
from starlette.routing import Route
1111
from starlette.templating import Jinja2Templates
1212

13-
import reactpy
13+
from reactpy import config as _config
1414
from reactpy import html
1515
from reactpy.executors.asgi.pyscript import ReactPyCsr
1616
from reactpy.testing import BackendFixture, DisplayFixture
17-
from reactpy.testing.common import REACTPY_TESTS_DEFAULT_TIMEOUT
17+
18+
REACTPY_TESTS_DEFAULT_TIMEOUT = _config.REACTPY_TESTS_DEFAULT_TIMEOUT
1819

1920

2021
@pytest.fixture(scope="module")
@@ -106,77 +107,12 @@ def test_bad_file_path():
106107
ReactPyCsr()
107108

108109

109-
async def test_customized_noscript(tmp_path: Path):
110-
noscript_file = tmp_path / "noscript.html"
111-
noscript_file.write_text(
112-
'<p id="noscript-message">Please enable JavaScript.</p>',
113-
encoding="utf-8",
114-
)
115-
116-
app = ReactPyCsr(
117-
Path(__file__).parent / "pyscript_components" / "root.py",
118-
html_noscript=noscript_file,
119-
)
120-
121-
async with BackendFixture(app) as server:
122-
url = f"http://{server.host}:{server.port}"
123-
response = await asyncio.to_thread(
124-
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
125-
)
126-
assert response.status_code == 200
127-
assert (
128-
'<noscript><p id="noscript-message">Please enable JavaScript.</p></noscript>'
129-
in response.text
130-
)
131-
132-
133-
134-
async def test_customized_noscript_string():
135-
app = ReactPyCsr(
136-
Path(__file__).parent / "pyscript_components" / "root.py",
137-
html_noscript='<p id="noscript-message">Please enable JavaScript.</p>',
138-
)
139-
140-
async with BackendFixture(app) as server:
141-
url = f"http://{server.host}:{server.port}"
142-
response = await asyncio.to_thread(
143-
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
144-
)
145-
assert response.status_code == 200
146-
assert (
147-
'<noscript><p id="noscript-message">Please enable JavaScript.</p></noscript>'
148-
in response.text
149-
)
150-
151-
152-
async def test_customized_noscript_from_file(tmp_path: Path):
153-
noscript_file = tmp_path / "noscript.html"
154-
noscript_file.write_text(
155-
'<p id="noscript-message">Please enable JavaScript.</p>',
156-
encoding="utf-8",
157-
)
158-
159-
app = ReactPyCsr(
160-
Path(__file__).parent / "pyscript_components" / "root.py",
161-
html_noscript=noscript_file,
162-
)
163-
164-
async with BackendFixture(app) as server:
165-
url = f"http://{server.host}:{server.port}"
166-
response = await asyncio.to_thread(
167-
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
168-
)
169-
assert response.status_code == 200
170-
assert (
171-
'<noscript><p id="noscript-message">Please enable JavaScript.</p></noscript>'
172-
in response.text
173-
)
174-
175-
176-
async def test_customized_noscript_from_string():
110+
async def test_customized_noscript_vdom():
177111
app = ReactPyCsr(
178112
Path(__file__).parent / "pyscript_components" / "root.py",
179-
html_noscript='<p id="noscript-message">Please enable JavaScript.</p>',
113+
prepend_body=html.noscript(
114+
html.p({"id": "noscript-message"}, "Please enable JavaScript.")
115+
),
180116
)
181117

182118
async with BackendFixture(app) as server:
@@ -190,17 +126,6 @@ async def test_customized_noscript_from_string():
190126
in response.text
191127
)
192128

193-
194-
async def test_customized_noscript_from_component():
195-
@reactpy.component
196-
def noscript_message():
197-
return html.p({"id": "noscript-message"}, "Please enable JavaScript.")
198-
199-
app = ReactPyCsr(
200-
Path(__file__).parent / "pyscript_components" / "root.py",
201-
html_noscript=noscript_message,
202-
)
203-
204129
async with BackendFixture(app) as server:
205130
url = f"http://{server.host}:{server.port}"
206131
response = await asyncio.to_thread(
@@ -213,20 +138,7 @@ def noscript_message():
213138
)
214139

215140

216-
async def test_default_noscript_rendered():
217-
app = ReactPyCsr(Path(__file__).parent / "pyscript_components" / "root.py")
218-
219-
async with BackendFixture(app) as server:
220-
url = f"http://{server.host}:{server.port}"
221-
response = await asyncio.to_thread(
222-
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
223-
)
224-
assert response.status_code == 200
225-
assert "<noscript>Enable JavaScript to view this site.</noscript>" in response.text
226-
227-
228-
229-
async def test_noscript_omitted():
141+
async def test_prepend_body_default_is_noscript():
230142
app = ReactPyCsr(Path(__file__).parent / "pyscript_components" / "root.py")
231143

232144
async with BackendFixture(app) as server:
@@ -236,15 +148,14 @@ async def test_noscript_omitted():
236148
)
237149
assert response.status_code == 200
238150
assert (
239-
"<noscript>Enable JavaScript to view this site.</noscript>"
240-
in response.text
151+
"<noscript>Enable JavaScript to view this site.</noscript>" in response.text
241152
)
242153

243154

244-
async def test_noscript_disabled():
155+
async def test_prepend_body_disabled():
245156
app = ReactPyCsr(
246157
Path(__file__).parent / "pyscript_components" / "root.py",
247-
html_noscript=None,
158+
prepend_body=None,
248159
)
249160

250161
async with BackendFixture(app) as server:

0 commit comments

Comments
 (0)