Compare commits

...

9 Commits

Author SHA1 Message Date
Ted Patrick
848d77b1c2 Same steps as build-unstable (#1550)
* Same steps as build-unstable

* make setup
2023-06-20 13:01:56 -05:00
Ted Patrick
e4e8f2edae Fix build unstable to run on ubuntu-latest-8core (#1549)
* Fix build unstable to run on ubuntu-latest-8core

* ubuntu-latest to ubuntu-latest-8core
2023-06-20 12:17:08 -05:00
Jeff Glass
ea9bdcc961 Fix previous changlog date (#1546)
* Previous version updated from 2023.01.1 to 2023.03.1
2023-06-20 08:08:24 -05:00
Hood Chatham
79ad39260e Fix lifetime for pyExec results (#1540)
Currently if the result from pyExec is a PyProxy, it gets destroyed.
This switches to using `to_js` to handle this (it is better to use
than an explicit `create_proxy` since it automatically decides whether
to create a proxy or not).

I also added `destroyIfProxy` which checks if something is a `PyProxy`
and then destroys it. Each use of `pyExec` needs to call `destroyIfProxy`
on the result after it is done with it.
2023-06-15 11:51:36 -07:00
Jeff Glass
f4936316ab Make getPySrc() a Documented Feature (#1516)
* Add documentation and tests (no real code changes)

* Add Changelog
2023-06-14 12:56:57 -05:00
Madhur Tandon
8879187e6a fix access to interpreter globals without awaits (#1529) 2023-06-14 19:42:38 +05:30
Fábio Rosado
258b80a6a5 Add step to install next deps and run test (#1528) 2023-06-14 14:12:42 +01:00
Fábio Rosado
a108e6e97e Add workflow to run next CI (#1527)
* Add workflow to run next CI

* Don't actually replace the whole unstable

* Unstable name

* Add empty line

* Make CI run on changes to workflow build-unstable-next

* Rename workflow for test-next instead of build

* Update path to use the next test-next

* Add pyscript.core to path
2023-06-13 15:06:19 +01:00
Peter W
dfef7eda3b Use 8-core, 32GB runners for testing and faster builds (#1524)
* swap 3 jobs to 8 core runners

* 16 core test

* test 4 core

* back to 8 core
2023-06-12 15:25:58 -05:00
21 changed files with 287 additions and 41 deletions

View File

@@ -20,7 +20,7 @@ on:
jobs:
BuildAndTest:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-8core
defaults:
run:
working-directory: pyscriptjs
@@ -85,7 +85,7 @@ jobs:
path: pyscriptjs/test_results
if-no-files-found: error
eslint:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-8core
defaults:
run:
working-directory: pyscriptjs
@@ -118,7 +118,7 @@ jobs:
run: npx eslint src -c .eslintrc.js
Deploy:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-8core
needs: BuildAndTest
if: github.ref == 'refs/heads/main' # Only deploy on merge into main
permissions:

View File

@@ -6,7 +6,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-8core
permissions:
contents: read
id-token: write

View File

@@ -19,7 +19,7 @@ concurrency:
jobs:
build:
if: github.repository_owner == 'pyscript'
runs-on: ubuntu-latest
runs-on: ubuntu-latest-8core
permissions:
contents: read
id-token: write

View File

@@ -9,7 +9,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-8core
permissions:
contents: read
id-token: write

View File

@@ -15,7 +15,7 @@ defaults:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-8core
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -44,8 +44,20 @@ jobs:
- name: Setup Environment
run: make setup
- name: Build and Test
run: make test
- name: Build
run: make build
- name: TypeScript Tests
run: make test-ts
- name: Python Tests
run: make test-py
- name: Integration Tests
run: make test-integration-parallel
- name: Examples Tests
run: make test-examples
- name: Zip build folder
run: zip -r -q ./build.zip ./build

View File

@@ -14,7 +14,7 @@ defaults:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-8core
permissions:
contents: read
id-token: write
@@ -46,8 +46,20 @@ jobs:
- name: Setup Environment
run: make setup
- name: Build and Test
run: make test
- name: Build
run: make build
- name: TypeScript Tests
run: make test-ts
- name: Python Tests
run: make test-py
- name: Integration Tests
run: make test-integration-parallel
- name: Examples Tests
run: make test-examples
# Upload to S3
- name: Configure AWS credentials

View File

@@ -11,7 +11,7 @@ on:
jobs:
snapshot:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-8core
permissions:
contents: read
id-token: write

View File

@@ -6,7 +6,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-8core
permissions:
contents: read
id-token: write

128
.github/workflows/test-next.yml vendored Normal file
View File

@@ -0,0 +1,128 @@
name: "[CI] Test Next"
on:
push: # Only run on merges into main that modify files under pyscriptjs/ and examples/
branches:
- next
paths:
- pyscript.core/**
- pyscriptjs/**
- examples/**
- .github/workflows/test-next.yml # Test that workflow works when changed
pull_request: # Run on any PR that modifies files under pyscriptjs/ and examples/
branches:
- next
paths:
- pyscript.core/**
- pyscriptjs/**
- examples/**
- .github/workflows/test-next.yml # Test that workflow works when changed
workflow_dispatch:
jobs:
BuildAndTest:
runs-on: ubuntu-latest-8core
defaults:
run:
working-directory: pyscriptjs
env:
MINICONDA_PYTHON_VERSION: py38
MINICONDA_VERSION: 4.11.0
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install node
uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Cache node modules
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: setup Miniconda
uses: conda-incubator/setup-miniconda@v2
- name: Setup Environment
run: make setup
- name: Build
run: make build
- name: TypeScript Tests (core)
run: make test-ts
- name: Python Tests
run: make test-py
- name: install next deps
working-directory: pyscript.core
run: npm i
- name: Run next tests
working-directory: pyscript.core
run: npm test
- name: Integration Tests
run: make test-integration-parallel
- name: Examples Tests
run: make test-examples
- uses: actions/upload-artifact@v3
with:
name: pyscript
path: |
pyscriptjs/build/
if-no-files-found: error
retention-days: 7
- uses: actions/upload-artifact@v3
if: success() || failure()
with:
name: test_results
path: pyscriptjs/test_results
if-no-files-found: error
eslint:
runs-on: ubuntu-latest-8core
defaults:
run:
working-directory: pyscriptjs
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install node
uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Cache node modules
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: npm install
run: npm install
- name: Eslint
run: npx eslint src -c .eslintrc.js

View File

@@ -6,7 +6,7 @@ on:
- completed
jobs:
report:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-8core
steps:
- uses: dorny/test-reporter@v1.6.0
with:

View File

@@ -32,6 +32,7 @@ Features
- Added a `stderr` attribute of `py-repl` tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
- Resored the `output-mode` attribute of `py-repl` tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results.
- Load code from the attribute src of py-repl and preload it into the corresponding py-repl tag by use the attribute `str` in your `py-repl` tag([#1292](https://github.com/pyscript/pyscript/pull/1292))
- <py-repl> elements now have a `getPySrc()` method, which returns the code inside the REPL as a string.([#1516](https://github.com/pyscript/pyscript/pull/1292))
### Plugins
- Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a `py-repl` tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
@@ -57,7 +58,7 @@ Docs
- Add docs for event handlers
2023.01.1
2023.03.1
=========

View File

@@ -28,6 +28,14 @@ The ID of an element in the DOM that `stderr` will be written to. Defaults to No
### `src`
If a \<py-repl\> tag has the `src` attribute, during page initialization, resource in the `src` will be preloaded into the REPL. Please note that this will not run in advance. If there is content in the \<py-repl\> tag, it will be cleared and replaced with preloaded resource.
## Methods
The following are methods that can be called on the \<py-repl\> element, from within Python or JavaScript
### `getPySrc()`
Returns the current code contents of the REPL as a string.
## Examples
### `<py-repl>` element set to auto-generate

View File

@@ -34,7 +34,7 @@
"prettier": "2.7.1",
"pyodide": "0.23.2",
"synclink": "0.2.4",
"ts-jest": "29.0.3",
"ts-jest": "29.1.0",
"typescript": "5.0.4",
"xterm": "^5.1.0"
}
@@ -5674,15 +5674,15 @@
}
},
"node_modules/ts-jest": {
"version": "29.0.3",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz",
"integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==",
"version": "29.1.0",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.0.tgz",
"integrity": "sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==",
"dev": true,
"dependencies": {
"bs-logger": "0.x",
"fast-json-stable-stringify": "2.x",
"jest-util": "^29.0.0",
"json5": "^2.2.1",
"json5": "^2.2.3",
"lodash.memoize": "4.x",
"make-error": "1.x",
"semver": "7.x",
@@ -5699,7 +5699,7 @@
"@jest/types": "^29.0.0",
"babel-jest": "^29.0.0",
"jest": "^29.0.0",
"typescript": ">=4.3"
"typescript": ">=4.3 <6"
},
"peerDependenciesMeta": {
"@babel/core": {
@@ -10247,15 +10247,15 @@
}
},
"ts-jest": {
"version": "29.0.3",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz",
"integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==",
"version": "29.1.0",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.0.tgz",
"integrity": "sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==",
"dev": true,
"requires": {
"bs-logger": "0.x",
"fast-json-stable-stringify": "2.x",
"jest-util": "^29.0.0",
"json5": "^2.2.1",
"json5": "^2.2.3",
"lodash.memoize": "4.x",
"make-error": "1.x",
"semver": "7.x",

View File

@@ -36,7 +36,7 @@
"prettier": "2.7.1",
"pyodide": "0.23.2",
"synclink": "0.2.4",
"ts-jest": "29.0.3",
"ts-jest": "29.1.0",
"typescript": "5.0.4",
"xterm": "^5.1.0"
},

View File

@@ -191,7 +191,7 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) {
pyReplTag: this,
result,
});
await interpreter._remote.destroyIfProxy(result);
this.autogenerateMaybe();
}

View File

@@ -36,6 +36,7 @@ export function make_PyScript(interpreter: InterpreterClient, app: PyScriptApp)
await app.plugins.beforePyScriptExec({ interpreter, src, pyScriptTag });
const { result } = await pyExec(interpreter, src, pyScriptTag);
await app.plugins.afterPyScriptExec({ interpreter, src, pyScriptTag, result });
await interpreter._remote.destroyIfProxy(result);
} finally {
releaseLock();
app.decrementPendingTags();

View File

@@ -71,6 +71,7 @@ export let runtime;
export class PyScriptApp {
config: AppConfig;
interpreter: InterpreterClient;
unwrapped_remote: RemoteInterpreter;
readyPromise: Promise<void>;
PyScript: ReturnType<typeof make_PyScript>;
plugins: PluginManager;
@@ -139,10 +140,10 @@ export class PyScriptApp {
await this.plugins.configure(this.config);
this.plugins.beforeLaunch(this.config);
await this.loadInterpreter();
interpreter = this.interpreter;
interpreter = this.unwrapped_remote;
// TODO: This is for backwards compatibility, it should be removed
// when we finish the deprecation cycle of `runtime`
runtime = this.interpreter;
runtime = this.unwrapped_remote;
}
// lifecycle (2)
@@ -193,6 +194,7 @@ export class PyScriptApp {
logger.info('Starting the interpreter in the main thread');
// this is basically equivalent to worker_initialize()
const remote_interpreter = new RemoteInterpreter(interpreter_cfg.src);
this.unwrapped_remote = remote_interpreter;
const { port1, port2 } = new Synclink.FakeMessageChannel() as unknown as MessageChannel;
port1.start();
port2.start();

View File

@@ -4,7 +4,7 @@ from contextlib import contextmanager
from js import Object
from pyodide.code import eval_code
from pyodide.ffi import JsProxy
from pyodide.ffi import JsProxy, to_js
from ._event_loop import (
defer_user_asyncio,
@@ -103,7 +103,7 @@ def run_pyscript(code: str, id: str = None) -> JsProxy:
with display_target(id), defer_user_asyncio():
result = eval_code(code, globals=__main__.__dict__)
return Object.new(result=result)
return to_js({"result": result}, depth=1, dict_converter=Object.fromEntries)
__all__ = [

View File

@@ -274,6 +274,20 @@ export class RemoteInterpreter extends Object {
this.FS.writeFile(path, data, { canOwn: true });
}
destroyIfProxy(px: any): void {
if (this.interface.ffi) {
// Pyodide 0.23
if (px instanceof this.interface.ffi.PyProxy) {
px.destroy();
}
} else {
// older Pyodide
if (this.interface.isPyProxy(px)) {
px.destroy();
}
}
}
/**
* delegates clearing importlib's module path
* caches to the underlying interface

View File

@@ -1,9 +1,10 @@
from .support import PyScriptTest
from .support import PyScriptTest, skip_worker
class TestInterpreterAccess(PyScriptTest):
"""Test accessing Python objects from JS via pyscript.interpreter"""
@skip_worker("WONTFIX: used without synclink to avoid awaits")
def test_interpreter_python_access(self):
self.pyscript_run(
"""
@@ -17,9 +18,9 @@ class TestInterpreterAccess(PyScriptTest):
self.run_js(
"""
const x = await pyscript.interpreter.globals.get('x');
const py_func = await pyscript.interpreter.globals.get('py_func');
const py_func_res = await py_func();
const x = pyscript.interpreter.globals.get('x');
const py_func = pyscript.interpreter.globals.get('py_func');
const py_func_res = py_func();
console.log(`x is ${x}`);
console.log(`py_func() returns ${py_func_res}`);
"""
@@ -29,13 +30,14 @@ class TestInterpreterAccess(PyScriptTest):
"py_func() returns 2",
]
@skip_worker("WONTFIX: used without synclink")
def test_interpreter_script_execution(self):
"""Test running Python code from js via pyscript.interpreter"""
self.pyscript_run("")
self.run_js(
"""
const interface = pyscript.interpreter._remote.interface;
const interface = pyscript.interpreter.interface;
await interface.runPython('print("Interpreter Ran This")');
"""
)
@@ -46,13 +48,14 @@ class TestInterpreterAccess(PyScriptTest):
py_terminal = self.page.wait_for_selector("py-terminal")
assert py_terminal.text_content() == expected_message
@skip_worker("WONTFIX: used without synclink")
def test_backward_compatibility_runtime_script_execution(self):
"""Test running Python code from js via pyscript.runtime"""
self.pyscript_run("")
self.run_js(
"""
const interface = pyscript.runtime._remote.interpreter;
const interface = pyscript.runtime.interpreter;
await interface.runPython('print("Interpreter Ran This")');
"""
)
@@ -63,6 +66,7 @@ class TestInterpreterAccess(PyScriptTest):
py_terminal = self.page.wait_for_selector("py-terminal")
assert py_terminal.text_content() == expected_message
@skip_worker("WONTFIX: used without synclink to avoid awaits")
def test_backward_compatibility_runtime_python_access(self):
"""Test accessing Python objects from JS via pyscript.runtime"""
self.pyscript_run(
@@ -77,9 +81,9 @@ class TestInterpreterAccess(PyScriptTest):
self.run_js(
"""
const x = await pyscript.interpreter.globals.get('x');
const py_func = await pyscript.interpreter.globals.get('py_func');
const py_func_res = await py_func();
const x = pyscript.runtime.globals.get('x');
const py_func = pyscript.runtime.globals.get('py_func');
const py_func_res = py_func();
console.log(`x is ${x}`);
console.log(`py_func() returns ${py_func_res}`);
"""

View File

@@ -595,6 +595,26 @@ class TestPyRepl(PyScriptTest):
alert_banner = self.page.wait_for_selector(".alert-banner")
assert expected_alert_banner_msg in alert_banner.inner_text()
def test_getPySrc_Contents(self):
"""Test that an empty REPL returns an empty string as src, and that the typed contents
are returned as the source
"""
self.pyscript_run(
"""
<py-repl>
</py-repl>
"""
)
py_repl = self.page.locator("py-repl")
src = py_repl.evaluate("node => node.getPySrc()")
assert src == ""
assert type(src) == str
py_repl.focus()
py_repl.type("Hello, world!")
src = py_repl.evaluate("node => node.getPySrc()")
assert src == "Hello, world!"
def test_repl_load_content_from_src(self):
self.writefile("loadReplSrc1.py", "print('1')")
self.pyscript_run(
@@ -654,3 +674,47 @@ class TestPyRepl(PyScriptTest):
"Are your filename and path correct?"
)
assert self.console.error.lines[-1] == errorMsg
@skip_worker("dont-care")
def test_repl_results(self):
self.writefile("loadReplSrc2.py", "2")
self.writefile("loadReplSrc3.py", "print('3')")
self.pyscript_run(
"""
<py-repl id="py-repl1" output="out1">
42
</py-repl>
<div id="out1"></div>
<py-repl id="py-repl2" output="out2">
c = [1,2,3]
from sys import getrefcount
# should print 2: 1 from the reference c and 1 since getrefcount
# holds a reference to its argument
print(getrefcount(c))
c
</py-repl>
<div id="out2"></div>
<py-repl id="py-repl3" output="out3">
# should also print 2: if it prints 3 that would mean that c was not properly
# released by py-repl
getrefcount(c)
</py-repl>
<div id="out3"></div>
"""
)
py_repl1 = self.page.locator("py-repl#py-repl1")
py_repl1.locator("button").click()
py_repl2 = self.page.locator("py-repl#py-repl2")
py_repl2.locator("button").click()
py_repl3 = self.page.locator("py-repl#py-repl3")
py_repl3.locator("button").click()
assert self.page.wait_for_selector("#out1").inner_text() == "42"
assert self.page.wait_for_selector("#out2").inner_text() == "2\n\n[1, 2, 3]"
# Check that c was released
assert self.page.wait_for_selector("#out3").inner_text() == "2"