Skip to content

Commit ecbe84f

Browse files
committed
Use microdriver as the basis for the app.
1 parent 6a8688d commit ecbe84f

5 files changed

Lines changed: 17 additions & 145 deletions

File tree

positron/src/positron/pyscript/bootstrap.py

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
from __future__ import annotations
22

3-
import tarfile
43
from pathlib import Path
54
from typing import Any
65

7-
from briefcase.integrations.base import ToolCache
8-
from briefcase.integrations.file import File
9-
106
from ..fastapi.bootstrap import FastAPIPositronBootstrap
117

128
TEMPLATE_PATH = Path(__file__).parent / "templates"
@@ -31,38 +27,15 @@ def extra_context(self, project_overrides: dict[str, str]) -> dict[str, Any] | N
3127

3228
def positron_requires(self):
3329
return super().positron_requires() + [
34-
"flatted-view",
35-
"reflected-ffi",
36-
"next-resolver",
30+
# "microdriver"
31+
# For now, we need a patched version.
32+
"microdriver @ git+https://github.com/freakboy3742/microdriver@pythonic#subdirectory=package"
3733
]
3834

3935
def post_generate(self, base_path: Path):
4036
app_path = base_path / "src" / self.context["module_name"]
4137
resource_path = app_path / "resources"
4238

43-
tools = ToolCache(
44-
console=self.console,
45-
base_path=base_path,
46-
)
47-
File.verify(tools=tools)
48-
49-
# Download and install service worker support code
50-
tag = "v0.2.6"
51-
reflected_path = tools.file.download(
52-
url=f"https://github.com/WebReflection/reflected/raw/refs/tags/{tag}/reflected.tar.gz",
53-
download_path=base_path / "downloads",
54-
role=f"service worker support ({tag})",
55-
)
56-
with tarfile.open(reflected_path, "r:gz") as tar:
57-
for member in [
58-
m for m in tar.getmembers() if m.name.startswith("./reflected/")
59-
]:
60-
member.name = member.name.removeprefix("./reflected/")
61-
tar.extract(member, path=resource_path / "js")
62-
(resource_path / "js/sw.js").rename(resource_path / "sw.js")
63-
64-
# TODO ensure mpy content is also available
65-
6639
# FastAPI server
6740
for template_name in ["server.py"]:
6841
self.templated_file(
@@ -85,7 +58,7 @@ def post_generate(self, base_path: Path):
8558
)
8659

8760
# Then add content that is PyScript specific
88-
for template_name in ["index.html", "main.py", "pyscript.toml"]:
61+
for template_name in ["index.html", "main.py"]:
8962
self.templated_file(
9063
TEMPLATE_PATH / template_name,
9164
resource_path,

positron/src/positron/pyscript/templates/index.html.tmpl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22
<head>
33
<title>{formal_name}</title>
44
<link rel="stylesheet" href="./positron.css" type="text/css">
5-
<link rel="stylesheet" href="https://pyscript.net/releases/2026.1.1/core.css" />
6-
<script type="module" src="https://pyscript.net/releases/2026.1.1/core.js"></script>
75
</head>
86
<body>
97
<h1>{formal_name}</h1>
108
<button id="my-button">Do something</button>
119
<div id="output"></div>
12-
<script type="py" config="./pyscript.toml" src="./main.py"></script>
10+
<script type="module" src="/@microdriver/bootstrap.js" micropython="/main.py"></script>
1311
</body>
1412
</html>
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
from pyscript import web, when
1+
from reflected import server, window
22

3+
button = window.document.getElementById("my-button")
4+
output = window.document.getElementById("output")
35

4-
@when("click", "#my-button")
5-
def handler():
6-
output_div = web.page["output"]
7-
output_div.innerText = "Button clicked!"
6+
7+
async def handler(event):
8+
output.append("Hello World in the app")
9+
server.builtins.print("Hello World on the server")
10+
11+
12+
button.addEventListener("click", handler)

positron/src/positron/pyscript/templates/pyscript.toml

Whitespace-only changes.
Lines changed: 2 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,5 @@
1-
import asyncio
2-
import struct
31
from pathlib import Path
42

5-
from fastapi import FastAPI
6-
from fastapi.middleware.cors import CORSMiddleware
7-
from fastapi.staticfiles import StaticFiles
8-
from fastapi.websockets import WebSocket
9-
from flatted_view import decode, encode
10-
from next_resolver import next_resolver
11-
from reflected_ffi import local
3+
from microdriver import app as micropython_app
124

13-
app = FastAPI()
14-
15-
16-
@app.websocket("/")
17-
async def websocket_endpoint(websocket: WebSocket):
18-
await websocket.accept()
19-
20-
(
21-
next,
22-
resolve,
23-
) = next_resolver()
24-
25-
nmsp = None
26-
27-
while True:
28-
buff = await websocket.receive_bytes()
29-
30-
if len(buff) < 5:
31-
continue
32-
33-
# print("socket frame:", "id", struct.unpack("<i", buff[0:4])[0], "op", buff[4])
34-
35-
# CONNECT
36-
if buff[4] == 0:
37-
38-
async def reflect(id, trap, args, kwargs):
39-
(
40-
uid,
41-
promise,
42-
) = next()
43-
body = bytes(encode([id, trap, args, kwargs]))
44-
frame = struct.pack("<i", uid) + bytes([2]) + body
45-
await websocket.send_bytes(frame)
46-
return promise
47-
48-
nmsp = local(reflect=reflect)
49-
50-
else:
51-
payload = decode(buff[5:]) if len(buff) > 5 else None
52-
53-
# WORKER CALLING SERVER -> return [OK, ERROR] response
54-
if buff[4] == 1:
55-
data = [None, None]
56-
57-
try:
58-
value = nmsp.reflect(*payload)
59-
while asyncio.iscoroutine(value):
60-
value = await value
61-
data[0] = value
62-
63-
except Exception as e:
64-
data[1] = str(e)
65-
66-
body = bytes(encode(data))
67-
await websocket.send_bytes(buff[0:5] + body)
68-
69-
# SERVER CALLING WORKER -> resolve promise
70-
elif buff[4] == 2:
71-
resolve(struct.unpack("<i", buff[0:4])[0], payload)
72-
73-
74-
app.add_middleware(
75-
CORSMiddleware,
76-
allow_credentials=True,
77-
allow_origins=["*"],
78-
allow_methods=["*"],
79-
allow_headers=["*"],
80-
)
81-
82-
83-
@app.middleware("http")
84-
async def add_coi_headers(request, call_next):
85-
if request.url.path == "/sw.js" and request.method == "POST":
86-
return await request.json()
87-
88-
response = await call_next(request)
89-
response.headers.update(
90-
{
91-
"Cross-Origin-Opener-Policy": "same-origin",
92-
"Cross-Origin-Embedder-Policy": "require-corp",
93-
"Cross-Origin-Resource-Policy": "cross-origin",
94-
"Cache-Control": "no-cache",
95-
"Pragma": "no-cache",
96-
"Expires": "0",
97-
"Last-Modified": "0",
98-
"ETag": "0",
99-
}
100-
)
101-
102-
return response
103-
104-
105-
app.mount(
106-
"/",
107-
StaticFiles(directory=Path(__file__).parent / "resources", html=True),
108-
name="positron",
109-
)
5+
app = micropython_app(content=Path(__file__).parent / "resources")

0 commit comments

Comments
 (0)