Fix #2031 - Add pyscript.WebSocket to the mix (#2042)

* Fix #2031 - Add pyscript.WebSocket to the mix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Working on a test case anyone can run

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Andrea Giammarchi
2024-05-03 11:35:05 +02:00
committed by GitHub
parent 5b4e8527da
commit 1a05ea5fd2
10 changed files with 292 additions and 14 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@pyscript/core",
"version": "0.4.24",
"version": "0.4.25",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@pyscript/core",
"version": "0.4.24",
"version": "0.4.25",
"license": "APACHE-2.0",
"dependencies": {
"@ungap/with-resolvers": "^0.1.0",
@@ -29,6 +29,7 @@
"@webreflection/toml-j0.4": "^1.1.3",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0",
"bun": "^1.1.6",
"chokidar": "^3.6.0",
"codemirror": "^6.0.1",
"eslint": "^9.1.1",
@@ -376,6 +377,110 @@
"node": ">= 8"
}
},
"node_modules/@oven/bun-darwin-aarch64": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.1.6.tgz",
"integrity": "sha512-1GQb0gugo/qndj5ehktgHRAoyCM5fWisbLEBLq6YorHTc71T5cqFWCQ6uPcfy3k9cxEDd8tUpejekyi8EsCxgw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@oven/bun-darwin-x64": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.1.6.tgz",
"integrity": "sha512-NK8py4eJNkN6iNfvaM8pzmmNNNkbyzRvnc/pmrDLFPPfgJklJ2esm8OBCALDe9RvP1USDo8H1I4IXCtQQIu3SQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@oven/bun-darwin-x64-baseline": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.1.6.tgz",
"integrity": "sha512-amG6o1y1ksjwOUKS97IW5VQE/UPfEa4Edket1+VUa0UGvo/0z3+2gVYF3Bt+pZA45glJKXM3nI9wYlmVeE/AcQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@oven/bun-linux-aarch64": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.1.6.tgz",
"integrity": "sha512-FOlZBeIrk7DlOsktWwahmxJwaKxCG9C+pKPMj8s+u08WhAKtfMBpgHvHsilBKE9W1twta+jB+geW4kU1KZSNZg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oven/bun-linux-x64": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.1.6.tgz",
"integrity": "sha512-PN/5XSPCWCuIgOEhxWfx5ViVEY9ZfpEpJkadDiZi9IY17mb/aZVvt0Lll4doe6B7hjbglb1nI++GXcpqU/fonw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oven/bun-linux-x64-baseline": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.1.6.tgz",
"integrity": "sha512-a/Wx5ZTl0BDq9WH5nJfl8rw0YX5/dWmxmy3pYki6EBbwjcsA8iMzjkRpp5k8NEV2SpyaDMdS+xCb/HiaO4RWoQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oven/bun-windows-x64": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.1.6.tgz",
"integrity": "sha512-knJePhYbqtYv5RNUrnkMFrK4z3dDIPzom402yjSLkESZhsnO16xtLkULocV2yk0KDXKo2C3BqoZKHATCK6LxqQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@oven/bun-windows-x64-baseline": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.1.6.tgz",
"integrity": "sha512-8VVPdKTYEsQPwlCasLWmTsHPcC9om9FaZlH9j7Gs9jBYje3FDXMyLI3DalVQ/VBY+uwQ8qoXhtDrh83nLuawhA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@playwright/test": {
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz",
@@ -938,6 +1043,36 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/bun": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/bun/-/bun-1.1.6.tgz",
"integrity": "sha512-0oKx5dVZ8LpsAvXdW2DTR5Zt3KS81Ifjx9T2ma7RjDPKbvGIlDXOPt++tr1SndxhFNFsMFeE4i6VKvlL7IR0Zw==",
"cpu": [
"arm64",
"x64"
],
"dev": true,
"hasInstallScript": true,
"os": [
"darwin",
"linux",
"win32"
],
"bin": {
"bun": "bin/bun.exe",
"bunx": "bin/bun.exe"
},
"optionalDependencies": {
"@oven/bun-darwin-aarch64": "1.1.6",
"@oven/bun-darwin-x64": "1.1.6",
"@oven/bun-darwin-x64-baseline": "1.1.6",
"@oven/bun-linux-aarch64": "1.1.6",
"@oven/bun-linux-x64": "1.1.6",
"@oven/bun-linux-x64-baseline": "1.1.6",
"@oven/bun-windows-x64": "1.1.6",
"@oven/bun-windows-x64-baseline": "1.1.6"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@pyscript/core",
"version": "0.4.24",
"version": "0.4.25",
"type": "module",
"description": "PyScript",
"module": "./index.js",
@@ -26,7 +26,8 @@
"build:stdlib": "node rollup/stdlib.cjs",
"build:3rd-party": "node rollup/3rd-party.cjs",
"clean:3rd-party": "rm src/3rd-party/*.js && rm src/3rd-party/*.css",
"test:mpy": "static-handler --coi . 2>/dev/null & SH_PID=$!; EXIT_CODE=0; playwright test --fully-parallel test/ || EXIT_CODE=$?; kill $SH_PID 2>/dev/null; exit $EXIT_CODE",
"test:mpy": "static-handler --coi . 2>/dev/null & SH_PID=$!; EXIT_CODE=0; playwright test --fully-parallel test/mpy.spec.js || EXIT_CODE=$?; kill $SH_PID 2>/dev/null; exit $EXIT_CODE",
"test:ws": "bun test/ws/index.js & playwright test test/ws.spec.js",
"dev": "node dev.cjs",
"release": "npm run build && npm run zip",
"size": "echo -e \"\\033[1mdist/*.js file size\\033[0m\"; for js in $(ls dist/*.js); do cat $js | brotli > ._; echo -e \"\\033[2m$js:\\033[0m $(du -h --apparent-size ._ | sed -e 's/[[:space:]]*._//')\"; rm ._; done",
@@ -60,6 +61,7 @@
"@webreflection/toml-j0.4": "^1.1.3",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0",
"bun": "^1.1.6",
"chokidar": "^3.6.0",
"codemirror": "^6.0.1",
"eslint": "^9.1.1",

View File

@@ -41,6 +41,7 @@ from pyscript.magic_js import (
sync,
window,
)
from pyscript.websocket import WebSocket
try:
from pyscript.event_handling import when

View File

@@ -1,6 +1,7 @@
import json
import js
from pyscript.util import as_bytearray
### wrap the response to grant Pythonic results
@@ -12,14 +13,6 @@ class _Response:
def __getattr__(self, attr):
return getattr(self._response, attr)
def _as_bytearray(self, buffer):
ui8a = js.Uint8Array.new(buffer)
size = ui8a.length
ba = bytearray(size)
for i in range(0, size):
ba[i] = ui8a[i]
return ba
# exposed methods with Pythonic results
async def arrayBuffer(self):
buffer = await self._response.arrayBuffer()
@@ -27,14 +20,14 @@ class _Response:
if hasattr(buffer, "to_py"):
return buffer.to_py()
# shims in MicroPython
return memoryview(self._as_bytearray(buffer))
return memoryview(as_bytearray(buffer))
async def blob(self):
return await self._response.blob()
async def bytearray(self):
buffer = await self._response.arrayBuffer()
return self._as_bytearray(buffer)
return as_bytearray(buffer)
async def json(self):
return json.loads(await self.text())

View File

@@ -1,3 +1,15 @@
import js
def as_bytearray(buffer):
ui8a = js.Uint8Array.new(buffer)
size = ui8a.length
ba = bytearray(size)
for i in range(0, size):
ba[i] = ui8a[i]
return ba
class NotSupported:
"""
Small helper that raises exceptions if you try to get/set any attribute on

View File

@@ -0,0 +1,64 @@
import js
from pyscript.util import as_bytearray
code = "code"
protocols = "protocols"
reason = "reason"
class EventMessage:
def __init__(self, event):
self._event = event
def __getattr__(self, attr):
value = getattr(self._event, attr)
if attr == "data" and not isinstance(value, str):
if hasattr(value, "to_py"):
return value.to_py()
# shims in MicroPython
return memoryview(as_bytearray(value))
return value
class WebSocket(object):
CONNECTING = 0
OPEN = 1
CLOSING = 2
CLOSED = 3
def __init__(self, **kw):
url = kw["url"]
socket = None
if protocols in kw:
socket = js.WebSocket.new(url, kw[protocols])
else:
socket = js.WebSocket.new(url)
object.__setattr__(self, "_ws", socket)
def __getattr__(self, attr):
return getattr(self._ws, attr)
def __setattr__(self, attr, value):
if attr == "onmessage":
setattr(self._ws, attr, lambda e: value(EventMessage(e)))
else:
setattr(self._ws, attr, value)
def close(self, **kw):
if code in kw and reason in kw:
self._ws.close(kw[code], kw[reason])
elif code in kw:
self._ws.close(kw[code])
else:
self._ws.close()
def send(self, data):
if isinstance(data, str):
self._ws.send(data)
else:
buffer = js.Uint8Array.new(len(data))
for pos, b in enumerate(data):
buffer[pos] = b
self._ws.send(buffer)

View File

@@ -0,0 +1,6 @@
import { test, expect } from '@playwright/test';
test('MicroPython WebSocket', async ({ page }) => {
await page.goto('http://localhost:5037/');
await page.waitForSelector('html.ok');
});

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy" worker>
from pyscript import WebSocket, document
def onopen(event):
print(event.type)
ws.send("hello")
def onmessage(event):
print(event.type, event.data)
ws.close()
def onclose(event):
print(event.type)
document.documentElement.classList.add("ok")
ws = WebSocket(url="ws://localhost:5037/")
ws.onopen = onopen
ws.onmessage = onmessage
ws.onclose = onclose
</script>
</body>
</html>

View File

@@ -0,0 +1,33 @@
import { serve, file } from 'bun';
import path, { dirname, join } from 'path';
import { fileURLToPath } from 'url';
const dir = dirname(fileURLToPath(import.meta.url));
serve({
port: 5037,
fetch(req, server) {
if (server.upgrade(req)) return;
const url = new URL(req.url);
let { pathname } = url;
if (pathname === '/') pathname = '/index.html';
else if (/^\/dist\//.test(pathname)) pathname = `/../..${pathname}`;
else if (pathname === '/favicon.ico')
return new Response('Not Found', { status: 404 });
const response = new Response(file(`${dir}${pathname}`));
const { headers } = response;
headers.set('Cross-Origin-Opener-Policy', 'same-origin');
headers.set('Cross-Origin-Embedder-Policy', 'require-corp');
headers.set('Cross-Origin-Resource-Policy', 'cross-origin');
return response;
},
websocket: {
message(ws, message) {
ws.send(message);
},
close() {
process.exit(0);
}
},
});

View File

@@ -7,6 +7,7 @@ declare namespace _default {
"ffi.py": string;
"magic_js.py": string;
"util.py": string;
"websocket.py": string;
};
let pyweb: {
"__init__.py": string;