Skip to content

Commit 48da4d9

Browse files
committed
Add an example which loads hello world from a Python file. Centralize the config into a loaded TOML file.
1 parent 3263430 commit 48da4d9

18 files changed

+138
-194
lines changed

src/psc/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def download(
6565
with open(target / fn, "wb") as pyscript_output:
6666
pyscript_output.write(pyscript_request.data)
6767

68-
print("Downloaded PyScript")
68+
print("Downloaded PyScript")
6969

7070

7171
@app.command()

src/psc/app.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ async def example(request: Request) -> _TemplateResponse:
7373
title=this_example.title,
7474
subtitle=this_example.subtitle,
7575
extra_head=this_example.extra_head,
76-
main=this_example.main,
77-
extra_pyscript=this_example.extra_pyscript,
76+
body=this_example.body,
7877
request=request,
7978
),
8079
)
@@ -91,7 +90,7 @@ async def content_page(request: Request) -> _TemplateResponse:
9190
dict(
9291
title=this_page.title,
9392
subtitle=this_page.subtitle,
94-
main=this_page.body,
93+
body=this_page.body,
9594
request=request,
9695
),
9796
)

src/psc/gallery/examples/hello_world/index.html

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,20 @@
1-
<!--
2-
contributor: example@contributor.me
3-
tags: example-tag, another-example
4-
-->
5-
61
<!DOCTYPE html>
72
<html lang="en">
83
<head>
94
<meta charset="utf-8">
105
<meta name="viewport" content="width=device-width,initial-scale=1">
116

12-
<title>Hello World Python</title>
13-
<meta name="subtitle" content="The hello world example, but in a .py file.">
7+
<title>Hello World</title>
148

159
<link rel="icon" type="image/png" href="../../../favicon.png">
1610
<script defer src="../../../pyscript/pyscript.js"></script>
1711
<link rel="stylesheet" href="hello_world.css">
1812
<script defer src="hello_world.js"></script>
1913
</head>
20-
2114
<body>
22-
<py-config>
23-
autoclose_loader = true
24-
25-
[[runtimes]]
26-
src = "../../../pyodide/pyodide.js"
27-
</py-config>
28-
<main>
29-
<h1>Hello ...</h1>
30-
<div id="output"></div>
31-
</main>
15+
<py-config src="../py_config.toml"></py-config>
16+
<h1>Hello ...</h1>
17+
<div id="output"></div>
3218
<h6>More Info</h6>
3319
<py-script>
3420
pyscript.write("output", "...world")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
title: Hello World
3+
subtitle: The classic hello world, but in Python -- in a browser!
4+
---
5+
The *body* description.

src/psc/gallery/examples/hello_world_py/index.html

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
<meta charset="utf-8">
1010
<meta name="viewport" content="width=device-width,initial-scale=1">
1111

12-
<title>Hello World</title>
13-
<meta name="subtitle" content="The classic hello world, but in Python -- in a browser!">
12+
<title>Hello World Python</title>
1413

1514
<link rel="icon" type="image/png" href="../../../favicon.png">
1615
<script defer src="../../../pyscript/pyscript.js"></script>
@@ -19,11 +18,8 @@
1918
</head>
2019

2120
<body>
22-
<py-config>
23-
autoclose_loader = true
24-
25-
[[runtimes]]
26-
src = "../../../pyodide/pyodide.js"
21+
<py-config src="../py_config.toml">
22+
paths = ["hello_world.py"]
2723
</py-config>
2824
<main>
2925
<h1>Hello Python ...</h1>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
title: Hello World Python
3+
subtitle: The hello world example, but in a .py file.
4+
---
5+
The *body* description.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
autoclose_loader = true
2+
3+
[[runtimes]]
4+
src = "../../../pyodide/pyodide.js"

src/psc/resources.py

Lines changed: 15 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
from psc.here import HERE
1717

18-
1918
EXCLUSIONS = ("pyscript.css", "pyscript.js", "favicon.png")
2019

2120

@@ -35,16 +34,6 @@ def tag_filter(
3534
return True
3635

3736

38-
def get_description(index_html_file: Path) -> str:
39-
"""Read an index.md if present and convert to HTML."""
40-
md_file = index_html_file.parent / "index.md"
41-
if not md_file.exists():
42-
return ""
43-
md_content = md_file.read_text()
44-
md = MarkdownIt()
45-
return str(md.render(md_content))
46-
47-
4837
def get_head_nodes(s: BeautifulSoup) -> str:
4938
"""Make post init simpler by putting head node helper here."""
5039
head_nodes = [
@@ -57,24 +46,10 @@ def get_head_nodes(s: BeautifulSoup) -> str:
5746
return ""
5847

5948

60-
def get_main_node_content(s: BeautifulSoup) -> str:
61-
"""Get the main node but raise an exception if not present."""
62-
# Moving to a helper to allow testing ValueError without needing
63-
# to ship a broken (non-main) example.
64-
main_element = s.select_one("main")
65-
if main_element is None: # pragma: no cover
66-
raise ValueError("Example file has no <main> element")
67-
return f"{main_element.decode_contents()}"
68-
69-
70-
def get_pyscript_nodes(s: BeautifulSoup) -> str:
71-
"""Find any pyscript nodes that are NOT ``py-config``."""
72-
pyscript_nodes = [
73-
pyscript.prettify()
74-
for pyscript in s.select("body > *")
75-
if pyscript and pyscript.name.startswith("py-") and pyscript.name != "py-config"
76-
]
77-
return "\n".join(pyscript_nodes)
49+
def get_body_content(s: BeautifulSoup) -> str:
50+
"""Get the body node but raise an exception if not present."""
51+
body_element = s.select_one("body")
52+
return f"{body_element.decode_contents()}"
7853

7954

8055
@dataclass
@@ -83,7 +58,7 @@ class Resource:
8358

8459
path: PurePath
8560
title: str = ""
86-
main: str = ""
61+
body: str = ""
8762
extra_head: str = ""
8863

8964

@@ -98,37 +73,25 @@ class Example(Resource):
9873
"""
9974

10075
description: str = ""
101-
extra_pyscript: str = ""
10276
subtitle: str = ""
10377

10478
def __post_init__(self) -> None:
10579
"""Extract most of the data from the HTML file."""
80+
# Title, subtitle, description come from the example's MD file.
81+
index_md_file = HERE / "gallery/examples" / self.path / "index.md"
82+
md_fm = frontmatter.load(index_md_file)
83+
self.title = md_fm.get("title", "")
84+
self.subtitle = md_fm.get("subtitle", "")
85+
md = MarkdownIt()
86+
self.description = str(md.render(md_fm.content))
87+
88+
# Main, extra head example's HTML file.
10689
index_html_file = HERE / "gallery/examples" / self.path / "index.html"
10790
if not index_html_file.exists():
10891
raise ValueError(f"No example at {self.path}")
10992
soup = BeautifulSoup(index_html_file.read_text(), "html5lib")
110-
111-
# Title
112-
title_node = soup.select_one("title")
113-
self.title = title_node.text if title_node else ""
114-
115-
# Subtitle
116-
subtitle_node = soup.select_one('meta[name="subtitle"]')
117-
assert subtitle_node # noqa
118-
subtitle = cast(str, subtitle_node.get("content", ""))
119-
self.subtitle = subtitle
120-
121-
# Description
122-
self.description = get_description(index_html_file)
123-
124-
# Head
12593
self.extra_head = get_head_nodes(soup)
126-
127-
# Main
128-
self.main = get_main_node_content(soup)
129-
130-
# Extra PyScript
131-
self.extra_pyscript = get_pyscript_nodes(soup)
94+
self.body = get_body_content(soup)
13295

13396

13497
@dataclass

src/psc/templates/example.jinja2

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,10 @@
33
<meta name="subtitle" content="{{ subtitle }}">
44
<script defer src="{{ root_path }}/pyscript/pyscript.js"></script>
55
{{ extra_head | safe }}{% endblock %}
6-
{% block extra_body %}
7-
<py-config>
8-
autoclose_loader = true
9-
10-
[[runtimes]]
11-
src = "{{ root_path }}/pyodide/pyodide.js"
12-
</py-config>
13-
{{ extra_pyscript | safe }}
14-
{% endblock %}
156
{% block main %}
167
<main id="main_container" class="container">
178
<h1 class="title">{{ title }}</h1>
189
<h1 class="subtitle">{{ subtitle }}</h1>
19-
{{ main | safe }}
10+
<div class="content">{{ body | safe }}</div>
2011
</main>
2112
{% endblock %}

src/psc/templates/homepage.jinja2

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
<div class="tile is-ancestor">
7878
<div class="tile is-parent">
7979
<article class="tile is-child box">
80-
<p class="title">Hello World</p>
80+
<p class="title">xxxHello World</p>
8181
<p class="subtitle">What is up?</p>
8282
<div class="content">
8383
This is the <em>description</em> for this example.
@@ -86,7 +86,7 @@
8686
</div>
8787
<div class="tile is-parent">
8888
<article class="tile is-child box">
89-
<p class="title">Hello World</p>
89+
<p class="title">xxxHello World</p>
9090
<p class="subtitle">What is up?</p>
9191
<div class="content">
9292
This is the <em>description</em> for this example.
@@ -95,7 +95,7 @@
9595
</div>
9696
<div class="tile is-parent">
9797
<article class="tile is-child box">
98-
<p class="title">Hello World</p>
98+
<p class="title">xxxHello World</p>
9999
<p class="subtitle">What is up?</p>
100100
<div class="content">
101101
<p>This is the <em>description</em> for this example.</p>

src/psc/templates/layout.jinja2

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
</a>
2222
</div>
2323

24-
<div id="navbar" class="navbar-menu">
2524
<div id="navbar" class="navbar-menu">
2625
<div class="navbar-start">
2726
<a id="navbarGallery" class="navbar-item" href="{{ root_path }}/gallery">
@@ -39,8 +38,6 @@
3938
</div>
4039
</div>
4140
</nav>
42-
{% block extra_body %}
43-
{% endblock %}
4441
{% block main %}
4542
{% endblock %}
4643
<footer class="footer">

src/psc/templates/page.jinja2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<div class="column is-three-quarters">
66
<h1 class="title is-1">{{ title }}</h1>
77
<h1 class="subtitle">{{ subtitle }}</h1>
8-
<div class="content">{{ main | safe }}</div>
8+
<div class="content">{{ body | safe }}</div>
99
</div>
1010
</div>
1111
</main>

tests/examples/test_hello_world.py

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,22 @@
11
"""Test the ``Hello World`` example."""
22
import pytest
33
from playwright.sync_api import Page
4-
from starlette.testclient import TestClient
54

65
from psc.fixtures import PageT
76

87

98
def test_hello_world(client_page: PageT) -> None:
109
"""Test the static HTML for Hello World."""
1110
soup = client_page("/gallery/examples/hello_world/")
12-
13-
# Title and subtitle
1411
title = soup.select_one("title")
15-
assert title
16-
assert title.text == "Hello World | PyScript Collective"
17-
subtitle = soup.select_one('meta[name="subtitle"]')
18-
assert subtitle
19-
assert (
20-
subtitle.get("content")
21-
== "The classic hello world, but in Python -- in a browser!"
22-
)
23-
24-
# See if extra_head got filled, then resolve those
25-
assert soup.find_all("link", href="hello_world.css")
26-
assert soup.find_all("script", src="hello_world.js")
27-
28-
# Ensure the ``<main>`` got filled
29-
assert soup.select_one("main")
30-
31-
# Only one <py-config>, pointing to local runtime
32-
py_configs = soup.select("py-config")
33-
assert len(py_configs) == 1
34-
35-
# The <py-script> is present
36-
py_scripts = soup.select("py-script")
37-
assert len(py_scripts) == 1
38-
39-
# The tracer <h6> is not present
40-
assert not soup.select("h6")
41-
42-
43-
def test_hello_world_js(test_client: TestClient) -> None:
44-
"""Test the static assets for Hello World."""
45-
response = test_client.get("/gallery/examples/hello_world/hello_world.js")
46-
assert response.status_code == 200
12+
assert title and title.text == "Hello World | PyScript Collective"
4713

4814

49-
@pytest.mark.skip()
5015
@pytest.mark.full
5116
def test_hello_world_full(fake_page: Page) -> None:
5217
"""Use Playwright to do a test on Hello World."""
5318
# Use `PWDEBUG=1` to run "head-ful" in Playwright test app
54-
url = "http://xfake/gallery/examples/hello_world/index.html"
19+
url = "http://fake/gallery/examples/hello_world/index.html"
5520
fake_page.goto(url)
5621
element = fake_page.wait_for_selector("text=...world")
5722
if element:

tests/examples/test_hello_world_py.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Test the ``Hello World`` in Python file example."""
2+
import pytest
3+
from playwright.sync_api import Page
4+
5+
from psc.fixtures import PageT
6+
7+
8+
def test_hello_world(client_page: PageT) -> None:
9+
"""Test the static HTML for Hello World."""
10+
soup = client_page("/gallery/examples/hello_world_py/")
11+
title = soup.select_one("title")
12+
assert title and title.text == "Hello World Python | PyScript Collective"
13+
14+
15+
@pytest.mark.full
16+
def test_hello_world_full(fake_page: Page) -> None:
17+
"""Use Playwright to do a test on Hello World."""
18+
# Use `PWDEBUG=1` to run "head-ful" in Playwright test app
19+
url = "http://fake/gallery/examples/hello_world_py/index.html"
20+
fake_page.goto(url)
21+
element = fake_page.wait_for_selector("text=From Python...")
22+
if element:
23+
assert element.text_content() == "From Python..."

tests/test_app.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,6 @@ def test_examples_listing(client_page: PageT) -> None:
5454
assert subtitle
5555
assert "Curated" in subtitle.text
5656

57-
# Example description
58-
description_em = examples_soup.select_one("div.content em")
59-
assert description_em
60-
assert description_em.text == "hello world"
61-
6257
# Get the first example, follow the link, ensure it is Hello World
6358
first_example = examples_soup.select_one("p.title a")
6459
assert first_example

0 commit comments

Comments
 (0)