Skip to content

Commit 23ce253

Browse files
authored
Merge pull request #59 from pyscript/pe-cdn-mode
Bring in a "CDN mode"
2 parents 45384c7 + 412bc8c commit 23ce253

26 files changed

+315
-133
lines changed

.github/workflows/tests.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ jobs:
1313
matrix:
1414
include:
1515
- { python: "3.10", os: "ubuntu-latest", session: "pre-commit" }
16-
- { python: "3.10", os: "ubuntu-latest", session: "safety" }
1716
- { python: "3.10", os: "ubuntu-latest", session: "mypy" }
1817
- { python: "3.10", os: "ubuntu-latest", session: "tests" }
1918
- { python: "3.10", os: "windows-latest", session: "tests" }

TODO.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,20 @@
22

33
## Now
44

5+
- CDN mode
6+
7+
## Soon
8+
9+
- Start documentation for example writers
10+
- Explain that we own the `src` in `py-config`
11+
- Remove the hack in noxfile to have tests only run the "fast" ones
12+
- Playright files get the example index.html directly
13+
- And thus, don't have the py-config src re-pointed to cdn
14+
- When run in nox, there are no local files and need to do CDN
15+
516
## Eventually
617

7-
- Get nox to work with downloaded pyodide/pyscript
18+
- Get numpy, pandas, etc. downloaded into local dir
819
- Get rid of Poetry
920

1021
## Done

docs/developers/cdn_mode.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# CDN Mode
2+
3+
Add a way to get PyScript/Pyodide locally sometimes, but from CDN other times.
4+
5+
## Why
6+
7+
When running and testing locally, the developers (and example writers) want fast turnaround.
8+
They don't want to keep going out on a possibly-slow network -- or even no-network, if offline.
9+
In "production", though, we want people browsing the examples to get the CDN version.
10+
11+
Other times are harder to decide.
12+
GitHub Actions would like a nice speedup.
13+
But it will take some investigation to learn how to cache artifacts.
14+
15+
## When
16+
17+
There are several contexts where this decision needs to be made.
18+
19+
## Standalone Example
20+
21+
The Gallery examples are designed to allow people to preview an example's `index.html` without the app.
22+
They have a `<script>` in `<head>` pointed at `pyscript.js`.
23+
They also have a `<py-config>` in the body to point at a Pyodide runtime.
24+
25+
We encourage them to point at the locally-downloaded assets.
26+
The build step then removes those nodes from the generated output, inserting the Gallery's decision on both.
27+
28+
### Local Web App
29+
30+
Some people will pip install the PSC and go through them locally.
31+
Perhaps even hack on them.
32+
Or, contributors writing an example will want a preview.
33+
Both will fire up Starlette locally.
34+
35+
### Local Playwright
36+
37+
When running an "end-to-end" (E2E) test, you want fast turnaround.
38+
You don't want to go out on the network, repeatedly.
39+
40+
### Local/GHA Nox
41+
42+
Nox is used to run our "machinery" in isolation.
43+
We run it locally, as a last step before pushing.
44+
We might also run it locally just for automation, e.g. the reloading Sphinx server.
45+
But it also runs when GitHub Actions workflows call it.
46+
47+
In theory, you want local Nox to be exactly the same as GHA Nox.
48+
Otherwise, you aren't recreating the build environment.
49+
50+
### Production Static Website
51+
52+
The GHA generates a static website.
53+
This should be pointed at the CDN
54+
55+
## Where
56+
57+
The moral of the story: we should point at the CDN *unless* something says to point at local.
58+
There are several places we make point at assets.
59+
60+
### `pyscript.js`
61+
62+
Two locations.
63+
First, in an example's `index.html`, when it is viewed "standalone".
64+
Second, in the `example.jinja2` template's `<head>`.
65+
66+
### `gallery/examples/pyconfig.toml`
67+
68+
This is also pointed to in two locations.
69+
Again, from an example's `index.html`.
70+
Here you'll have a `<py-config src="../pyconfig.toml">` node which might include some instructions in the content.
71+
72+
## Solution
73+
74+
During local (non-CDN) use, we'll change the `<py-config>` to use `src="pyconfig.local.toml`.
75+
76+
How?
77+
We're already using BeautifulSoup to pick apart the `index.html`.
78+
Once we make the local vs. CDN decision, we can easily change the `src` attribute to point either TOML file.
79+
80+
What is the local vs. CDN criteria?
81+
We'll just look for the `src/pyscript` and `src/pyodide` directory.
82+
If they exist, then someone downloaded the assets.
83+
That's a good flag for whether to point at those directories.
84+
85+
What is the action to take?
86+
The `py_config.local.toml` file has a `runtimes.src` entry pointing to the local `pyodide.js`.
87+
The `py_config.cdn.toml` file points at the CDN version.
88+
89+
## Actions
90+
91+
- Remove the timeout
92+
- Test/implementation that calls a `is_local` function to detect the correct mode
93+
- Test/implementation which wires that into the HTML munger

docs/developers/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Developers
2+
3+
Articles and notes for the implementers of the app itself.
4+
5+
```{toctree}
6+
---
7+
hidden:
8+
maxdepth: 1
9+
---
10+
11+
cdn_mode
12+
```

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ maxdepth: 1
1515
1616
contributing
1717
building/index
18+
developers/index
1819
Code of Conduct <codeofconduct>
1920
License <license>
2021
Changelog <https://github.com/pyscript/pyscript-collective/releases>

noxfile.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
nox.needs_version = ">= 2021.6.6"
2929
nox.options.sessions = (
3030
"pre-commit",
31-
"safety",
3231
"mypy",
3332
"tests",
3433
# "typeguard",
@@ -138,18 +137,10 @@ def precommit(session: Session) -> None:
138137
activate_virtualenv_in_precommit_hooks(session)
139138

140139

141-
@session(python=python_versions[0])
142-
def safety(session: Session) -> None:
143-
"""Scan dependencies for insecure packages."""
144-
requirements = session.poetry.export_requirements()
145-
session.install("safety")
146-
session.run("safety", "check", "--full-report", f"--file={requirements}")
147-
148-
149140
@session(python=python_versions)
150141
def mypy(session: Session) -> None:
151142
"""Type-check using mypy."""
152-
args = session.posargs or ["src", "tests", "docs/conf.py"]
143+
args = session.posargs or ["--exclude=examples", "src", "tests", "docs/conf.py"]
153144
session.install(".")
154145
session.install(
155146
"mypy",
@@ -185,7 +176,16 @@ def tests(session: Session) -> None:
185176
"python-frontmatter",
186177
)
187178
try:
188-
session.run("coverage", "run", "--parallel", "-m", "pytest", *session.posargs)
179+
session.run(
180+
"coverage",
181+
"run",
182+
"--parallel",
183+
"-m",
184+
"pytest",
185+
"-m",
186+
"not full",
187+
*session.posargs,
188+
)
189189
finally:
190190
if session.interactive:
191191
session.notify("coverage", posargs=[])

poetry.lock

Lines changed: 1 addition & 44 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ documentation = "https://psc.readthedocs.io"
1111
classifiers = [
1212
"Development Status :: 1 - Planning",
1313
]
14+
exclude = [
15+
"src/psc/pyodide/",
16+
"src/psc/pyscript/",
17+
]
1418

1519
[tool.poetry.urls]
1620
Changelog = "https://github.com/pauleveritt/psc/releases"
@@ -42,7 +46,6 @@ pre-commit = ">=2.16.0"
4246
pre-commit-hooks = ">=4.1.0"
4347
pytest = ">=6.2.5"
4448
pyupgrade = ">=2.29.1"
45-
safety = ">=1.10.3"
4649
sphinx = ">=4.3.2"
4750
sphinx-autobuild = ">=2021.3.14"
4851
sphinx-click = ">=3.0.2"

src/psc/app.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,10 @@ async def content_page(request: Request) -> _TemplateResponse:
107107
Route("/pages/{page_name}.html", content_page),
108108
Mount("/gallery", StaticFiles(directory=HERE / "gallery")),
109109
Mount("/static", StaticFiles(directory=HERE / "static")),
110+
]
111+
if PYODIDE.exists():
110112
Mount("/pyscript", StaticFiles(directory=PYSCRIPT)),
111113
Mount("/pyodide", StaticFiles(directory=PYODIDE)),
112-
]
113114

114115

115116
@contextlib.asynccontextmanager # type: ignore

src/psc/fixtures.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,6 @@ def _route_handler(route: Route) -> None:
179179
# fake server and run through the interceptor instead.
180180
page.route("**", _route_handler)
181181

182-
# Don't spend 30 seconds on timeout
183-
page.set_default_timeout(12000)
184182
return page
185183

186184

@@ -190,6 +188,7 @@ class FakeDocument:
190188

191189
values: dict[str, str] = field(default_factory=dict)
192190
log: list[str] = field(default_factory=list)
191+
nodes: dict[str, FakeElement] = field(default_factory=dict)
193192

194193

195194
@pytest.fixture
@@ -199,39 +198,57 @@ def fake_document() -> Iterable[FakeDocument]:
199198

200199

201200
@dataclass
202-
class ElementNode:
203-
"""An element node."""
201+
class FakeElementNode:
202+
"""Fake for PyScript's ``element`` accessor that gets DOM node."""
203+
204+
log: list[str] = field(default_factory=list)
205+
206+
def removeAttribute(self, name: str) -> None: # noqa
207+
"""Pretend to remove an attribute from this node."""
208+
self.log.append(f"Removed {name}")
209+
210+
211+
@dataclass
212+
class FakeElement:
213+
"""A fake for PyScript's Element global."""
204214

205215
value: str
206216
document: FakeDocument
217+
element: FakeElementNode = FakeElementNode()
207218

208219
def write(self, value: str) -> None:
209220
"""Collect anything that is written to the node."""
210221
self.document.log.append(value)
211222

212-
def removeAttribute(self, name: str) -> None: # noqa
213-
"""Pretend to remove an attribute from this node."""
214-
pass
215-
216223

217224
@dataclass
218225
class ElementCallable:
219-
"""A callable that returns an ElementNode."""
226+
"""A callable that registers and returns an ElementNode."""
220227

221228
document: FakeDocument
222229

223-
def __call__(self, key: str) -> ElementNode:
230+
def __call__(self, key: str) -> FakeElement:
224231
"""Return an ElementNode."""
225232
value = self.document.values[key]
226-
node = ElementNode(value, self.document)
233+
node = FakeElement(value, self.document)
234+
self.document.nodes[key] = node
227235
return node
228236

237+
def removeAttribute(self, attr: str) -> None: # noqa: N802
238+
"""Fake the remove attribute call."""
239+
pass
240+
241+
def write(self, content: str) -> None:
242+
"""Fake the write call."""
243+
pass
244+
229245

230246
@pytest.fixture
231-
def fake_element(fake_document: FakeDocument) -> None: # type: ignore [misc]
247+
def fake_element(fake_document: FakeDocument) -> ElementCallable: # type: ignore [misc]
232248
"""Install the stateful Element into builtins."""
233249
try:
234-
builtins.Element = ElementCallable(fake_document) #type: ignore [attr-defined]
235-
yield
250+
this_element = ElementCallable(fake_document)
251+
builtins.Element = this_element # type: ignore [attr-defined]
252+
yield this_element
236253
finally:
237254
delattr(builtins, "Element")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<script defer src="../../../pyscript/pyscript.js"></script>
77
</head>
88
<body>
9-
<py-config src="../py_config.toml">
9+
<py-config src="../py_config.local.toml">
1010
packages=[
1111
"altair",
1212
"pandas",

0 commit comments

Comments
 (0)