Compare commits

...

36 Commits

Author SHA1 Message Date
Nicholas Tollervey
c4e25d879e Update GH actions to node 20 and Python env for PyMinifier. (#2166)
* Update GH actions to node 20 and Python env for PyMinifier.

* Fix spaces.

* Fix test.yml

* [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>
2024-09-13 14:37:17 +01:00
Andrea Giammarchi
c82dbb755e cleanup npm package (#2163) 2024-09-13 15:04:21 +02:00
Andrea Giammarchi
1ed77321a5 Add a storage equivalent for JS (#2165)
* Add a storage equivalent for JS

* [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>
2024-09-13 12:05:40 +02:00
Andrea Giammarchi
e36a57eb06 Fix #2160 - Implement progress events (#2162) 2024-09-12 18:41:36 +02:00
Andrea Giammarchi
ee3cd76022 Follow up - Remove all innerHTML += for consistency sake (#2159) 2024-09-11 15:12:04 +02:00
Andrea Giammarchi
eb31e51a45 Fix #2150 - Avoid trashing previous added elements (#2151) 2024-09-11 11:37:42 +02:00
Andrea Giammarchi
c8c2dd0806 Avoid throwing if Pyodide does not await due missing arguments (#2158)
* Fix #2156 - Avoid requiring args for async functions

* [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>
2024-09-06 15:16:28 +02:00
Andrea Giammarchi
e525d54be0 Fix #2156 - Test @when with async listener (#2157) 2024-09-06 14:56:50 +02:00
Andrea Giammarchi
7b9f7c13f5 Improve by far error reporting around PyEditor bootstrap (#2153)
* Improve by far error reporting around PyEditor bootstrap

* Improve by far error reporting around PyEditor bootstrapiImprove by far error reporting around PyEditor bootstrap

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-09-04 16:52:08 +02:00
Andrea Giammarchi
7582cbef9c Fix #2146 - Workaround Pyodide issue with attributes (#2148)
* Fix #2146 - Workaround Pyodide issue with attributes

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

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

* Fix #2146 - Updated polyscript

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-27 16:47:52 +02:00
Andrea Giammarchi
b395cde49c Forgot to build tests/index.html and push (#2144) 2024-08-26 12:29:16 +02:00
Andrea Giammarchi
9f46234f71 Fix #2114 - Cleanup the test folder + automation (#2143)
* Fix #2114 - Cleanup the test folder + automation
* Merged both test and tests into a single folder
2024-08-08 17:08:59 +02:00
Andrea Giammarchi
f4c4edeb29 Implemented pyminify for our stdlib (#2140)
* Implemented pyminify for our stdlib

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

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

* [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>
2024-08-07 12:33:20 +02:00
Andrea Giammarchi
7166c32384 Fix #2093 - Show setup errors with the editor (#2138)
* Fix #2093 - Show setup errors with the editor

* [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>
2024-08-06 16:31:42 +02:00
pre-commit-ci[bot]
ed126889ae [pre-commit.ci] pre-commit autoupdate (#2137)
updates:
- [github.com/psf/black: 24.4.2 → 24.8.0](https://github.com/psf/black/compare/24.4.2...24.8.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-06 12:36:00 +02:00
Andrea Giammarchi
0d0ea96435 Defaulting to async for top-level await (#2134) 2024-08-05 15:55:53 +02:00
Andrea Giammarchi
fafdf74007 Fixed async methods attached to window (#2136) 2024-08-05 11:58:22 +02:00
Martin
999897df12 The all-new, pyscript.web (ignore the branch name :) ) (#2129)
* Minor cleanups: move all Element classes to bottom of module.

* Commenting.

* Commenting.

* Commenting.

* Group dunder methods.

* Don't cache the element's parent.

* Remove style type check until we decide whether or not to add for classes too.

* Add ability to register/unregister element classes.

* Implement __iter__ for container elements.

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

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

* Minor renaming to make it clear when we have an Element instance vs an actual DOM element.

* remove duplication: added Element.get_tag_name

* Commenting.

* Allow Element.append to 1) use *args, 2) accept iterables

* Remove iterable check - inteferes with js proxies.

* Don't use *args, so it quacks more like a list ;)

* Element.append take 2 :)

* Remove unused code.

* Move to web.py with a page object!

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

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

* Added 'page.title' too :)

* Add __getitem__ as a shortcut for page.find

* Add Element.__getitem__ to be consistent

* Make __getitem__ consistent for Page, Element and ElementCollection.

* Docstringing.

* Docstringing.

* Docstringing/commenting.

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

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

* fix select.add (revert InnerHTML->html)

* Commenting.

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

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

* Hand-edit some of the AI :)

* Rename ElementCollection.children -> ElementCollection.elements

* Remove unnecessary guard.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-01 10:36:57 +01:00
Andrea Giammarchi
d47fb58ede Update Pyodide to its 0.26.2 version (#2133) 2024-07-31 20:34:21 +02:00
Andrea Giammarchi
f316341e73 Updated Polyscript and added Panel worker test (#2130)
* Updated Polyscript and added Panel worker test

* [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>
2024-07-31 14:31:35 +02:00
Andrea Giammarchi
8c46fcabf7 Updating polyscript to its latest (#2128)
* Updating polyscript to its latest
2024-07-29 16:59:31 +02:00
Martin
e4ff4d8fab Controversial version where Element just delegates to the underlying DOM element. (#2127)
* Update elements.py

* remove grid which allows simpler class tag mapping.
2024-07-24 13:27:12 -05:00
Martin
f20a0003ed fix: broken methods video.snap and canvas.download (#2126)
* fix: broken methods video.snap and canvas.download

* Allow canvas.draw to use actual image width/height.
2024-07-23 15:35:07 -05:00
Martin
6c938dfe3b Override __getattr__ and __setattr__ on ElementCollection. (#2116)
* Override __getattr__ and __setattr__ on ElementCollection.

* fix: bug when using a string to query an ElementCollection.

* Use Element.find when indexing ElementCollection with a string.

* For consistency also have a find method on ElementCollection.

* ElementCollection.find now returns a collection of collections :)

* fix tests: for textContent

* Revert to extend for ElementCollection.find :)

* Make element_from_dom a classmethod Element.from_dom

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

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

* rename: Element.from_dom -> Element.from_dom_element

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

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

* PyCharm warning sweep.

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

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

* Workaround for mp not allowing setting via __dict__

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-07-22 10:32:37 -05:00
Andrea Giammarchi
d884586a82 Updated Polyscript to its latest (#2124) 2024-07-19 12:21:04 +02:00
Fabio Pliger
f8f7ba89c1 Cleanup pyscript web elements (#2094)
* change pydom example to use new pyscript.web namespace

* change tests to use new pyscript.web namespace

* create new pyscript.web package and move pydom to pyscript.web.dom

* add __init__ to pyscript.web and expose the dom instance instead of the pyscript.web.dom module

* move elements from pyweb.ui to pyscript.web and temp fix pydom import

* moved of elements file completed

* moved media from pyweb to pyscript.web

* RIP pyweb

* move JSProperty from pyscript.web.dom to pyscript.web.elements

* move element classes from pyscript.web.dom to pyscript.web.elements

* first round of fixes while running tests

* fix test typo

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

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

* restore right type type returned for Element.parent. ALL TESTS PASS LOCALLY NOW

* lint

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

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

* clean up dom.py from dead commented code and osbolete comments

* bugfix: dom shouldn't return None when it can't find any element for a specific selector so it now returns an empty collection

* additional cleanup in tests

* lint

* initial cleaning up of unused modules

* change element.append to not consider types anymore and add tests for appending elements.Element or a JsProxy object

* add Element.append tests for append JS elements directly and appending nodeList as well

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

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

* Tag and create the correct subclass of Element.

* Move: Element.snap -> video.snap

* Move: Element.download and draw to canvas.download and draw.

* Minor cleanups.

* Commenting.

* Allow css classes to be passed to Element constructor.

* Commenting.

* Typo fix.

* Make html, id and text JSProperties.

* Commenting.

* Remove unnecessary selected attribute on BaseElement.

* Extract: BaseElement.from_js -> element_from_js

* Pass *args and **kwargs to element_from_js and remove BaseElement.create

* Move value attribute to specific Element subclasses.

* fix: wrapping of existing js elements.

* Add body and head elements so parent and children work everywhere.

* Revert order of HasOptions mixin for the select element.

* Comment out tests that are no longer relevant (see comment).

* Use correct super args in mixin.

* Have to use element_from_js when returning options from OptionsProxy.

* rename: StyleProxy -> Style, OptionsProxy -> Options and added Classes.

* Remove cached_property.

* Remove list-y methods from Classes collection.

* Allow explicit children or *args for containers.

* controversial: fix tests to use find rather than dom

* Add html element so (say) body.parent does what is expected.

* Collapse Element class hierarchy.

* rename: js_element -> dom_element

* rename: element_from_js -> element_from_dom

* replace: JS with DOM

* rename: _js -> _dom_element

* fix dom tests.

* Complete element list with void elements derived from Element.

* Added attributes to the newly added Element subclasses.

* remove dom module, replace with instance.

Also, remove media :)

* fix: typo in test for 'b' element.

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

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

* Remove dom and media modules.

* fix up ts definitions.

* Added missing import (used in content property).

* Added TODO :)

* wip: Add ClassesCollection

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

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

* Attempt to ask black to leave class list alone.

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

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

* Add classes attribute to ElementCollection

* wip: work on classes collection

* Extract code to get set of all class names in ClassesCollection.

* Update elements.py

* Polishing.

* Make children return an ElementCollection

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

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

* wip: Add the ability to set multiple properties.

* Add __getitem__ back to the dom object.

* Put validation when setting DOM properties back in.

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

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

* All tests green.

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

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

* Remove unnecessary comment.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Martin <martin.chilvers@gmail.com>
2024-07-03 14:21:23 -07:00
Andrea Giammarchi
67d47511d5 Fix MicroPython terminal input when no REPL is used/needed (#2113)
* Fix terminal input when no REPL is used/needed
* Fix input backspace too
2024-07-03 13:03:31 +02:00
Andrea Giammarchi
6f49f18937 Updated Polyscript with its workers feature (#2104)
* Updated Polyscript with its workers feature
* Worked around the inconsistent behavior between Pyodide and MicroPython
* Fixed Pyodide greedy access to undesired Proxy fields
2024-06-26 14:01:22 +02:00
Andrea Giammarchi
7b8ef7ebe2 Fix #2109 - Allow inline JSON config attribute in PyEditor (#2110)
Fix #2109 - Allow inline JSON config attribute in PyEditor
2024-06-24 17:04:28 +02:00
Andrea Giammarchi
461ae38763 Updated reference code to grab latest (#2107) 2024-06-21 16:06:18 +02:00
Andrea Giammarchi
4b90ebdef5 Bring back pyweb as it was (#2105) 2024-06-21 14:49:20 +02:00
Andrea Giammarchi
15c19aa708 Updated Polyscript with latest MicroPython (#2103) 2024-06-19 17:56:22 +02:00
Andrea Giammarchi
d0406be84c A persistent IndexedDB store for PyScript (#2101)
A persistent IndexedDB store for PyScript
2024-06-19 14:11:57 +02:00
Andrea Giammarchi
aab015b9b8 Better py editor indentation (#2098)
Better PyEditor Indentation
2024-06-13 11:34:14 +02:00
Andrea Giammarchi
a1e5a05b49 PyEditor cumulative fixes & improvements (#2095)
* PyEditor fixes

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

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

* PyEditor cumulative fixes & improvements

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-06-12 18:55:36 +02:00
Fabio Pliger
f1a787e031 move pydom and elements from pyweb to pyscript.web (#2092)
* change pydom example to use new pyscript.web namespace

* change tests to use new pyscript.web namespace

* create new pyscript.web package and move pydom to pyscript.web.dom

* add __init__ to pyscript.web and expose the dom instance instead of the pyscript.web.dom module

* move elements from pyweb.ui to pyscript.web and temp fix pydom import

* moved of elements file completed

* moved media from pyweb to pyscript.web

* RIP pyweb

* move JSProperty from pyscript.web.dom to pyscript.web.elements

* move element classes from pyscript.web.dom to pyscript.web.elements

* first round of fixes while running tests

* fix test typo

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

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

* restore right type type returned for Element.parent. ALL TESTS PASS LOCALLY NOW

* lint

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

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

* clean up dom.py from dead commented code and osbolete comments

* bugfix: dom shouldn't return None when it can't find any element for a specific selector so it now returns an empty collection

* additional cleanup in tests

* lint

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-06-06 15:42:14 +02:00
149 changed files with 3568 additions and 2660 deletions

View File

@@ -19,7 +19,22 @@ jobs:
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 18.x
node-version: 20.x
- name: Python venv
run: python -m venv env
- name: Activate Python
run: source env/bin/activate
- name: Update pip
run: pip install --upgrade pip
- name: Install PyMinifier
run: pip install --ignore-requires-python python-minifier
- name: Install Setuptools
run: pip install setuptools
- name: Cache node modules
uses: actions/cache@v4

View File

@@ -21,7 +21,22 @@ jobs:
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 18.x
node-version: 20.x
- name: Python venv
run: python -m venv env
- name: Activate Python
run: source env/bin/activate
- name: Update pip
run: pip install --upgrade pip
- name: Install PyMinifier
run: pip install --ignore-requires-python python-minifier
- name: Install Setuptools
run: pip install setuptools
- name: Cache node modules
uses: actions/cache@v4

View File

@@ -25,7 +25,22 @@ jobs:
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 18.x
node-version: 20.x
- name: Python venv
run: python -m venv env
- name: Activate Python
run: source env/bin/activate
- name: Update pip
run: pip install --upgrade pip
- name: Install PyMinifier
run: pip install --ignore-requires-python python-minifier
- name: Install Setuptools
run: pip install setuptools
- name: Cache node modules
uses: actions/cache@v4

View File

@@ -26,7 +26,22 @@ jobs:
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 18.x
node-version: 20.x
- name: Python venv
run: python -m venv env
- name: Activate Python
run: source env/bin/activate
- name: Update pip
run: pip install --upgrade pip
- name: Install PyMinifier
run: pip install --ignore-requires-python python-minifier
- name: Install Setuptools
run: pip install setuptools
- name: Cache node modules
uses: actions/cache@v4

View File

@@ -25,7 +25,7 @@ repos:
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 24.4.2
rev: 24.8.0
hooks:
- id: black
exclude: pyscript\.core/src/stdlib/pyscript/__init__\.py

View File

@@ -38,11 +38,11 @@ To try PyScript, import the appropriate pyscript files into the `<head>` tag of
<head>
<link
rel="stylesheet"
href="https://pyscript.net/releases/2024.5.2/core.css"
href="https://pyscript.net/releases/2024.6.2/core.css"
/>
<script
type="module"
src="https://pyscript.net/releases/2024.5.2/core.js"
src="https://pyscript.net/releases/2024.6.2/core.js"
></script>
</head>
<body>

View File

@@ -1,12 +0,0 @@
.eslintrc.cjs
eslint.config.mjs
.pytest_cache/
node_modules/
rollup/
test/
tests/
test-results/
src/stdlib/_pyscript
src/stdlib/pyscript.py
package-lock.json
tsconfig.json

View File

@@ -12,7 +12,7 @@ Clone this repository then run `npm install` within its folder.
Use `npm run build` to create all artifacts and _dist_ files.
Use `npm run server` to test locally, via the `http://localhost:8080/test/` url, smoke tests or to test manually anything you'd like to check.
Use `npm run server` to test locally, via the `http://localhost:8080/tests/` url, smoke tests or to test manually anything you'd like to check.
### Artifacts

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@pyscript/core",
"version": "0.4.42",
"version": "0.5.12",
"type": "module",
"description": "PyScript",
"module": "./index.js",
@@ -8,6 +8,15 @@
"jsdelivr": "./jsdelivr.js",
"browser": "./index.js",
"main": "./index.js",
"files": [
"./dist/",
"./src/",
"./types/",
"./index.js",
"./jsdelivr.js",
"LICENSE",
"README.md"
],
"exports": {
".": {
"types": "./types/core.d.ts",
@@ -16,18 +25,23 @@
"./css": {
"import": "./dist/core.css"
},
"./storage": {
"import": "./dist/storage.js"
},
"./package.json": "./package.json"
},
"scripts": {
"server": "npx static-handler --coi .",
"build": "export ESLINT_USE_FLAT_CONFIG=true;npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && eslint src/ && npm run ts && npm run test:mpy",
"server": "echo \"➡️ TESTS @ $(tput bold)http://localhost:8080/tests/$(tput sgr0)\"; npx static-handler --coi .",
"build": "export ESLINT_USE_FLAT_CONFIG=true;npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && npm run build:tests-index && if [ -z \"$NO_MIN\" ]; then eslint src/ && npm run ts && npm run test:integration; fi",
"build:core": "rm -rf dist && rollup --config rollup/core.config.js && cp src/3rd-party/*.css dist/",
"build:flatted": "node rollup/flatted.cjs",
"build:plugins": "node rollup/plugins.cjs",
"build:stdlib": "node rollup/stdlib.cjs",
"build:3rd-party": "node rollup/3rd-party.cjs",
"build:tests-index": "node rollup/build_test_index.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/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",
"test:integration": "static-handler --coi . 2>/dev/null & SH_PID=$!; EXIT_CODE=0; playwright test --fully-parallel tests/integration.spec.js || EXIT_CODE=$?; kill $SH_PID 2>/dev/null; exit $EXIT_CODE",
"test:ws": "bun tests/ws/index.js & playwright test tests/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",
@@ -42,34 +56,37 @@
"license": "APACHE-2.0",
"dependencies": {
"@ungap/with-resolvers": "^0.1.0",
"@webreflection/idb-map": "^0.3.1",
"basic-devtools": "^0.1.6",
"polyscript": "^0.12.14",
"polyscript": "^0.15.6",
"sabayon": "^0.5.2",
"sticky-module": "^0.1.1",
"to-json-callback": "^0.1.1",
"type-checked-collections": "^0.1.7"
},
"devDependencies": {
"@codemirror/commands": "^6.6.0",
"@codemirror/commands": "^6.6.1",
"@codemirror/lang-python": "^6.1.6",
"@codemirror/language": "^6.10.2",
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.27.0",
"@playwright/test": "^1.44.1",
"@rollup/plugin-commonjs": "^25.0.8",
"@codemirror/view": "^6.33.0",
"@playwright/test": "1.45.3",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@webreflection/toml-j0.4": "^1.1.3",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0",
"bun": "^1.1.12",
"chokidar": "^3.6.0",
"bun": "^1.1.27",
"chokidar": "^4.0.0",
"codemirror": "^6.0.1",
"eslint": "^9.4.0",
"rollup": "^4.18.0",
"eslint": "^9.10.0",
"flatted": "^3.3.1",
"rollup": "^4.21.3",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-string": "^3.0.0",
"static-handler": "^0.4.3",
"typescript": "^5.4.5",
"static-handler": "^0.5.3",
"typescript": "^5.6.2",
"xterm": "^5.3.0",
"xterm-readline": "^1.1.1"
},

View File

@@ -0,0 +1,73 @@
const { join } = require("node:path");
const { lstatSync, readdirSync, writeFileSync } = require("node:fs");
// folders to not consider while crawling
const EXCLUDE_DIR = new Set(["ws"]);
const TEST_DIR = join(__dirname, "..", "tests");
const TEST_INDEX = join(TEST_DIR, "index.html");
const crawl = (path, tree = {}) => {
for (const file of readdirSync(path)) {
const current = join(path, file);
if (current === TEST_INDEX) continue;
if (lstatSync(current).isDirectory()) {
if (EXCLUDE_DIR.has(file)) continue;
const sub = {};
tree[file] = sub;
crawl(current, sub);
if (!Reflect.ownKeys(sub).length) {
delete tree[file];
}
} else if (file.endsWith(".html")) {
const name = file === "index.html" ? "." : file.slice(0, -5);
tree[name] = current.replace(TEST_DIR, "");
}
}
return tree;
};
const createList = (tree) => {
const ul = ["<ul>"];
for (const [key, value] of Object.entries(tree)) {
ul.push("<li>");
if (typeof value === "string") {
ul.push(`<a href=".${value}">${key}<small>.html</small></a>`);
} else {
if ("." in value) {
ul.push(`<strong><a href=".${value["."]}">${key}</a></strong>`);
delete value["."];
} else {
ul.push(`<strong><span>${key}</span></strong>`);
}
if (Reflect.ownKeys(value).length) ul.push(createList(value));
}
ul.push("</li>");
}
ul.push("</ul>");
return ul.join("");
};
writeFileSync(
TEST_INDEX,
`<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyScript tests</title>
<style>
body { font-family: sans-serif; }
a {
display: block;
transition: opacity .3s;
}
a, span { opacity: .7; }
a:hover { opacity: 1; }
</style>
</head>
<body>${createList(crawl(TEST_DIR))}</body>
</html>
`,
);

View File

@@ -40,4 +40,17 @@ export default [
warn(warning);
},
},
{
input: "./src/storage.js",
plugins: plugins.concat(
process.env.NO_MIN
? [nodeResolve(), commonjs()]
: [nodeResolve(), commonjs(), terser()],
),
output: {
esModule: true,
dir: "./dist",
sourcemap: true,
},
},
];

View File

@@ -0,0 +1,17 @@
const { writeFileSync, readFileSync } = require("node:fs");
const { join } = require("node:path");
const flatted = "# https://www.npmjs.com/package/flatted\n\n";
const source = join(
__dirname,
"..",
"node_modules",
"flatted",
"python",
"flatted.py",
);
const dest = join(__dirname, "..", "src", "stdlib", "pyscript", "flatted.py");
const clear = (str) => String(str).replace(/^#.*/gm, "").trimStart();
writeFileSync(dest, flatted + clear(readFileSync(source)));

View File

@@ -4,13 +4,27 @@ const {
statSync,
writeFileSync,
} = require("node:fs");
const { spawnSync } = require("node:child_process");
const { join } = require("node:path");
const crawl = (path, json) => {
for (const file of readdirSync(path)) {
const full = join(path, file);
if (/\.py$/.test(file)) json[file] = readFileSync(full).toString();
else if (statSync(full).isDirectory() && !file.endsWith("_"))
if (/\.py$/.test(file)) {
if (process.env.NO_MIN) json[file] = readFileSync(full).toString();
else {
const {
output: [error, result],
} = spawnSync("pyminify", [
"--remove-literal-statements",
full,
]);
if (error) process.exit(1);
json[file] = result.toString();
}
} else if (statSync(full).isDirectory() && !file.endsWith("_"))
crawl(full, (json[file] = {}));
}
};

View File

@@ -45,6 +45,8 @@ const configDetails = async (config, type) => {
const conflictError = (reason) => new Error(`(${CONFLICTING_CODE}): ${reason}`);
const relative_url = (url, base = location.href) => new URL(url, base).href;
const syntaxError = (type, url, { message }) => {
let str = `(${BAD_CONFIG}): Invalid ${type}`;
if (url) str += ` @ ${url}`;
@@ -108,7 +110,7 @@ for (const [TYPE] of TYPES) {
if (!error && config) {
try {
const { json, toml, text, url } = await configDetails(config, type);
if (url) configURL = new URL(url, location.href).href;
if (url) configURL = relative_url(url);
config = text;
if (json || type === "json") {
try {
@@ -153,4 +155,4 @@ for (const [TYPE] of TYPES) {
configs.set(TYPE, { config: parsed, configURL, plugins, error });
}
export default configs;
export { configs, relative_url };

View File

@@ -12,6 +12,7 @@ import {
define,
defineProperty,
dispatch,
isSync,
queryTarget,
unescape,
whenDefined,
@@ -19,7 +20,7 @@ import {
import "./all-done.js";
import TYPES from "./types.js";
import configs from "./config.js";
import { configs, relative_url } from "./config.js";
import sync from "./sync.js";
import bootstrapNodeAndPlugins from "./plugins-helper.js";
import { ErrorCode } from "./exceptions.js";
@@ -84,6 +85,7 @@ const [
export {
TYPES,
relative_url,
exportedPyWorker as PyWorker,
exportedMPWorker as MPWorker,
exportedHooks as hooks,
@@ -92,7 +94,7 @@ export {
};
export const offline_interpreter = (config) =>
config?.interpreter && new URL(config.interpreter, location.href).href;
config?.interpreter && relative_url(config.interpreter);
const hooked = new Map();
@@ -201,15 +203,13 @@ for (const [TYPE, interpreter] of TYPES) {
}
if (isScript(element)) {
const {
attributes: { async: isAsync, target },
} = element;
const hasTarget = !!target?.value;
const show = hasTarget
? queryTarget(element, target.value)
const isAsync = !isSync(element);
const target = element.getAttribute("target");
const show = target
? queryTarget(element, target)
: document.createElement("script-py");
if (!hasTarget) {
if (!target) {
const { head, body } = document;
if (head.contains(element)) body.append(show);
else element.after(show);
@@ -330,7 +330,7 @@ for (const [TYPE, interpreter] of TYPES) {
async connectedCallback() {
if (!this.executed) {
this.executed = true;
const isAsync = this.hasAttribute("async");
const isAsync = !isSync(this);
const { io, run, runAsync } = await this._wrap
.promise;
this.srcCode = await fetchSource(

View File

@@ -1,6 +1,7 @@
// PyScript py-editor plugin
import { Hook, XWorker, dedent, defineProperties } from "polyscript/exports";
import { TYPES, offline_interpreter, stdlib } from "../core.js";
import { TYPES, offline_interpreter, relative_url, stdlib } from "../core.js";
import { notify } from "./error.js";
const RUN_BUTTON = `<svg style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>`;
@@ -23,6 +24,11 @@ const hooks = {
},
};
const validate = (config, result) => {
if (typeof result === "boolean") throw `Invalid source: ${config}`;
return result;
};
async function execute({ currentTarget }) {
const { env, pySrc, outDiv } = this;
const hasRunButton = !!currentTarget;
@@ -34,15 +40,37 @@ async function execute({ currentTarget }) {
if (!envs.has(env)) {
const srcLink = URL.createObjectURL(new Blob([""]));
const details = { type: this.interpreter };
const details = {
type: this.interpreter,
serviceWorker: this.serviceWorker,
};
const { config } = this;
if (config) {
details.configURL = config;
const { parse } = config.endsWith(".toml")
? await import(/* webpackIgnore: true */ "../3rd-party/toml.js")
: JSON;
details.config = parse(await fetch(config).then((r) => r.text()));
details.version = offline_interpreter(details.config);
// verify that config can be parsed and used
try {
details.configURL = relative_url(config);
if (config.endsWith(".toml")) {
const [{ parse }, toml] = await Promise.all([
import(
/* webpackIgnore: true */ "../3rd-party/toml.js"
),
fetch(config).then((r) => r.ok && r.text()),
]);
details.config = parse(validate(config, toml));
} else if (config.endsWith(".json")) {
const json = await fetch(config).then(
(r) => r.ok && r.json(),
);
details.config = validate(config, json);
} else {
details.configURL = relative_url("./config.txt");
details.config = JSON.parse(config);
}
details.version = offline_interpreter(details.config);
} catch (error) {
notify(error);
return;
}
} else {
details.config = {};
}
@@ -63,9 +91,12 @@ async function execute({ currentTarget }) {
return envs.get(env).then((xworker) => {
xworker.onerror = ({ error }) => {
if (hasRunButton) {
outDiv.innerHTML += `<span style='color:red'>${
error.message || error
}</span>\n`;
outDiv.insertAdjacentHTML(
"beforeend",
`<span style='color:red'>${
error.message || error
}</span>\n`,
);
}
console.error(error);
};
@@ -76,31 +107,41 @@ async function execute({ currentTarget }) {
const { sync } = xworker;
sync.write = (str) => {
if (hasRunButton) outDiv.innerText += `${str}\n`;
else console.log(str);
};
sync.writeErr = (str) => {
if (hasRunButton) {
outDiv.innerHTML += `<span style='color:red'>${str}</span>\n`;
outDiv.insertAdjacentHTML(
"beforeend",
`<span style='color:red'>${str}</span>\n`,
);
} else {
notify(str);
console.error(str);
}
};
sync.runAsync(pySrc).then(enable, enable);
});
}
const makeRunButton = (listener, type) => {
const makeRunButton = (handler, type) => {
const runButton = document.createElement("button");
runButton.className = `absolute ${type}-editor-run-button`;
runButton.innerHTML = RUN_BUTTON;
runButton.setAttribute("aria-label", "Python Script Run Button");
runButton.addEventListener("click", listener);
runButton.addEventListener("click", async (event) => {
runButton.blur();
await handler.handleEvent(event);
});
return runButton;
};
const makeEditorDiv = (listener, type) => {
const makeEditorDiv = (handler, type) => {
const editorDiv = document.createElement("div");
editorDiv.className = `${type}-editor-input`;
editorDiv.setAttribute("aria-label", "Python Script Area");
const runButton = makeRunButton(listener, type);
const runButton = makeRunButton(handler, type);
const editorShadowContainer = document.createElement("div");
// avoid outer elements intercepting key events (reveal as example)
@@ -120,15 +161,15 @@ const makeOutDiv = (type) => {
return outDiv;
};
const makeBoxDiv = (listener, type) => {
const makeBoxDiv = (handler, type) => {
const boxDiv = document.createElement("div");
boxDiv.className = `${type}-editor-box`;
const editorDiv = makeEditorDiv(listener, type);
const editorDiv = makeEditorDiv(handler, type);
const outDiv = makeOutDiv(type);
boxDiv.append(editorDiv, outDiv);
return [boxDiv, outDiv];
return [boxDiv, outDiv, editorDiv.querySelector("button")];
};
const init = async (script, type, interpreter) => {
@@ -138,7 +179,7 @@ const init = async (script, type, interpreter) => {
{ python },
{ indentUnit },
{ keymap },
{ defaultKeymap },
{ defaultKeymap, indentWithTab },
] = await Promise.all([
import(/* webpackIgnore: true */ "../3rd-party/codemirror.js"),
import(/* webpackIgnore: true */ "../3rd-party/codemirror_state.js"),
@@ -152,8 +193,17 @@ const init = async (script, type, interpreter) => {
let isSetup = script.hasAttribute("setup");
const hasConfig = script.hasAttribute("config");
const serviceWorker = script.getAttribute("service-worker");
const env = `${interpreter}-${script.getAttribute("env") || getID(type)}`;
// helps preventing too lazy ServiceWorker initialization on button run
if (serviceWorker) {
new XWorker("data:application/javascript,postMessage(0)", {
type: "dummy",
serviceWorker,
}).onmessage = ({ target }) => target.terminate();
}
if (hasConfig && configs.has(env)) {
throw new SyntaxError(
configs.get(env)
@@ -164,15 +214,29 @@ const init = async (script, type, interpreter) => {
configs.set(env, hasConfig);
let source = script.src
? await fetch(script.src).then((b) => b.text())
: script.textContent;
let source = script.textContent;
// verify the src points to a valid file that can be parsed
const { src } = script;
if (src) {
try {
source = validate(
src,
await fetch(src).then((b) => b.ok && b.text()),
);
} catch (error) {
notify(error);
return;
}
}
const context = {
// allow the listener to be overridden at distance
handleEvent: execute,
serviceWorker,
interpreter,
env,
config:
hasConfig &&
new URL(script.getAttribute("config"), location.href).href,
config: hasConfig && script.getAttribute("config"),
get pySrc() {
return isSetup ? source : editor.state.doc.toString();
},
@@ -184,6 +248,29 @@ const init = async (script, type, interpreter) => {
let target;
defineProperties(script, {
target: { get: () => target },
handleEvent: {
get: () => context.handleEvent,
set: (callback) => {
// do not bother with logic if it was set back as its original handler
if (callback === execute) context.handleEvent = execute;
// in every other case be sure that if the listener override returned
// `false` nothing happens, otherwise keep doing what it always did
else {
context.handleEvent = async (event) => {
// trap the currentTarget ASAP (if any)
// otherwise it gets lost asynchronously
const { currentTarget } = event;
// augment a code snapshot before invoking the override
defineProperties(event, {
code: { value: context.pySrc },
});
// avoid executing the default handler if the override returned `false`
if ((await callback(event)) !== false)
await execute.call(context, { currentTarget });
};
}
},
},
code: {
get: () => context.pySrc,
set: (insert) => {
@@ -214,21 +301,21 @@ const init = async (script, type, interpreter) => {
isSetup = wasSetup;
source = wasSource;
};
return execute
.call(context, { currentTarget: null })
return context
.handleEvent({ currentTarget: null })
.then(restore, restore);
},
},
});
const notify = () => {
const notifyEditor = () => {
const event = new Event(`${type}-editor`, { bubbles: true });
script.dispatchEvent(event);
};
if (isSetup) {
await execute.call(context, { currentTarget: null });
notify();
await context.handleEvent({ currentTarget: null });
notifyEditor();
return;
}
@@ -250,8 +337,7 @@ const init = async (script, type, interpreter) => {
if (!target.hasAttribute("root")) target.setAttribute("root", target.id);
// @see https://github.com/JeffersGlass/mkdocs-pyscript/blob/main/mkdocs_pyscript/js/makeblocks.js
const listener = execute.bind(context);
const [boxDiv, outDiv] = makeBoxDiv(listener, type);
const [boxDiv, outDiv, runButton] = makeBoxDiv(context, type);
boxDiv.dataset.env = script.hasAttribute("env") ? env : interpreter;
const inputChild = boxDiv.querySelector(`.${type}-editor-input > div`);
@@ -264,8 +350,9 @@ const init = async (script, type, interpreter) => {
const doc = dedent(script.textContent).trim();
// preserve user indentation, if any
const indentation = /^(\s+)/m.test(doc) ? RegExp.$1 : " ";
const indentation = /^([ \t]+)/m.test(doc) ? RegExp.$1 : " ";
const listener = () => runButton.click();
const editor = new EditorView({
extensions: [
indentUnit.of(indentation),
@@ -275,15 +362,19 @@ const init = async (script, type, interpreter) => {
{ key: "Ctrl-Enter", run: listener, preventDefault: true },
{ key: "Cmd-Enter", run: listener, preventDefault: true },
{ key: "Shift-Enter", run: listener, preventDefault: true },
// @see https://codemirror.net/examples/tab/
indentWithTab,
]),
basicSetup,
],
foldGutter: true,
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
parent,
doc,
});
editor.focus();
notify();
notifyEditor();
};
// avoid too greedy MutationObserver operations at distance

View File

@@ -1,5 +1,5 @@
// PyScript py-terminal plugin
import { TYPES } from "../core.js";
import { TYPES, relative_url } from "../core.js";
import { notify } from "./error.js";
import { customObserver } from "polyscript/exports";
@@ -35,7 +35,7 @@ for (const type of TYPES.keys()) {
document.head.append(
Object.assign(document.createElement("link"), {
rel: "stylesheet",
href: new URL("./xterm.css", import.meta.url),
href: relative_url("./xterm.css", import.meta.url),
}),
);
}

View File

@@ -49,11 +49,12 @@ const workerReady = ({ interpreter, io, run, type }, { sync }) => {
const writer = encoder.writable.getWriter();
sync.pyterminal_stream_write = (buffer) => writer.write(buffer);
pyterminal_ready();
interpreter.replInit();
},
});
pyterminal_ready();
};
export default async (element) => {
@@ -163,13 +164,25 @@ export default async (element) => {
};
terminal.onData((buffer) => {
if (promisedChunks) {
readChunks += buffer;
terminal.write(buffer);
if (readChunks.endsWith("\r")) {
terminal.write("\n");
promisedChunks.resolve(readChunks.slice(0, -1));
promisedChunks = null;
readChunks = "";
// handle backspace on input
if (buffer === "\x7f") {
// avoid over-greedy backspace
if (readChunks.length) {
readChunks = readChunks.slice(0, -1);
// override previous char position
// put an empty space to clear the char
// move back position again
buffer = "\b \b";
} else buffer = "";
} else readChunks += buffer;
if (buffer) {
terminal.write(buffer);
if (readChunks.endsWith("\r")) {
terminal.write("\n");
promisedChunks.resolve(readChunks.slice(0, -1));
promisedChunks = null;
readChunks = "";
}
}
} else {
stream.write(buffer);

View File

@@ -43,8 +43,12 @@ from pyscript.magic_js import (
sync,
window,
)
from pyscript.storage import Storage, storage
from pyscript.websocket import WebSocket
if not RUNNING_IN_WORKER:
from pyscript.workers import create_named_worker, workers
try:
from pyscript.event_handling import when
except:

View File

@@ -19,29 +19,38 @@ def when(event_type=None, selector=None):
"""
def decorator(func):
from pyscript.web import Element, ElementCollection
if isinstance(selector, str):
elements = document.querySelectorAll(selector)
# TODO: This is a hack that will be removed when pyscript becomes a package
# and we can better manage the imports without circular dependencies
elif isinstance(selector, Element):
elements = [selector._dom_element]
elif isinstance(selector, ElementCollection):
elements = [el._dom_element for el in selector]
else:
# TODO: This is a hack that will be removed when pyscript becomes a package
# and we can better manage the imports without circular dependencies
from pyweb import pydom
if isinstance(selector, pydom.Element):
elements = [selector._js]
elif isinstance(selector, pydom.ElementCollection):
elements = [el._js for el in selector]
if isinstance(selector, list):
elements = selector
else:
raise ValueError(
f"Invalid selector: {selector}. Selector must"
" be a string, a pydom.Element or a pydom.ElementCollection."
)
elements = [selector]
try:
sig = inspect.signature(func)
# Function doesn't receive events
if not sig.parameters:
def wrapper(*args, **kwargs):
func()
# Function is async: must be awaited
if inspect.iscoroutinefunction(func):
async def wrapper(*args, **kwargs):
await func()
else:
def wrapper(*args, **kwargs):
func()
else:
wrapper = func

View File

@@ -0,0 +1,148 @@
# https://www.npmjs.com/package/flatted
import json as _json
class _Known:
def __init__(self):
self.key = []
self.value = []
class _String:
def __init__(self, value):
self.value = value
def _array_keys(value):
keys = []
i = 0
for _ in value:
keys.append(i)
i += 1
return keys
def _object_keys(value):
keys = []
for key in value:
keys.append(key)
return keys
def _is_array(value):
return isinstance(value, list) or isinstance(value, tuple)
def _is_object(value):
return isinstance(value, dict)
def _is_string(value):
return isinstance(value, str)
def _index(known, input, value):
input.append(value)
index = str(len(input) - 1)
known.key.append(value)
known.value.append(index)
return index
def _loop(keys, input, known, output):
for key in keys:
value = output[key]
if isinstance(value, _String):
_ref(key, input[int(value.value)], input, known, output)
return output
def _ref(key, value, input, known, output):
if _is_array(value) and not value in known:
known.append(value)
value = _loop(_array_keys(value), input, known, value)
elif _is_object(value) and not value in known:
known.append(value)
value = _loop(_object_keys(value), input, known, value)
output[key] = value
def _relate(known, input, value):
if _is_string(value) or _is_array(value) or _is_object(value):
try:
return known.value[known.key.index(value)]
except:
return _index(known, input, value)
return value
def _transform(known, input, value):
if _is_array(value):
output = []
for val in value:
output.append(_relate(known, input, val))
return output
if _is_object(value):
obj = {}
for key in value:
obj[key] = _relate(known, input, value[key])
return obj
return value
def _wrap(value):
if _is_string(value):
return _String(value)
if _is_array(value):
i = 0
for val in value:
value[i] = _wrap(val)
i += 1
elif _is_object(value):
for key in value:
value[key] = _wrap(value[key])
return value
def parse(value, *args, **kwargs):
json = _json.loads(value, *args, **kwargs)
wrapped = []
for value in json:
wrapped.append(_wrap(value))
input = []
for value in wrapped:
if isinstance(value, _String):
input.append(value.value)
else:
input.append(value)
value = input[0]
if _is_array(value):
return _loop(_array_keys(value), input, [value], value)
if _is_object(value):
return _loop(_object_keys(value), input, [value], value)
return value
def stringify(value, *args, **kwargs):
known = _Known()
input = []
output = []
i = int(_index(known, input, value))
while i < len(input):
output.append(_transform(known, input, input[i]))
i += 1
return _json.dumps(output, *args, **kwargs)

View File

@@ -36,7 +36,6 @@ if RUNNING_IN_WORKER:
)
try:
globalThis.SharedArrayBuffer.new(4)
import js
window = polyscript.xworker.window
@@ -47,17 +46,11 @@ if RUNNING_IN_WORKER:
"return (...urls) => Promise.all(urls.map((url) => import(url)))"
)()
except:
globalThis.console.debug("SharedArrayBuffer is not available")
# in this scenario none of the utilities would work
# as expected so we better export these as NotSupported
window = NotSupported(
"pyscript.window",
"pyscript.window in workers works only via SharedArrayBuffer",
)
document = NotSupported(
"pyscript.document",
"pyscript.document in workers works only via SharedArrayBuffer",
)
message = "Unable to use `window` or `document` -> https://docs.pyscript.net/latest/faq/#sharedarraybuffer"
globalThis.console.warn(message)
window = NotSupported("pyscript.window", message)
document = NotSupported("pyscript.document", message)
js_import = None
sync = polyscript.xworker.sync

View File

@@ -0,0 +1,60 @@
from polyscript import storage as _storage
from pyscript.flatted import parse as _parse
from pyscript.flatted import stringify as _stringify
# convert a Python value into an IndexedDB compatible entry
def _to_idb(value):
if value is None:
return _stringify(["null", 0])
if isinstance(value, (bool, float, int, str, list, dict, tuple)):
return _stringify(["generic", value])
if isinstance(value, bytearray):
return _stringify(["bytearray", [v for v in value]])
if isinstance(value, memoryview):
return _stringify(["memoryview", [v for v in value]])
raise TypeError(f"Unexpected value: {value}")
# convert an IndexedDB compatible entry into a Python value
def _from_idb(value):
(
kind,
result,
) = _parse(value)
if kind == "null":
return None
if kind == "generic":
return result
if kind == "bytearray":
return bytearray(result)
if kind == "memoryview":
return memoryview(bytearray(result))
return value
class Storage(dict):
def __init__(self, store):
super().__init__({k: _from_idb(v) for k, v in store.entries()})
self.__store__ = store
def __delitem__(self, attr):
self.__store__.delete(attr)
super().__delitem__(attr)
def __setitem__(self, attr, value):
self.__store__.set(attr, _to_idb(value))
super().__setitem__(attr, value)
def clear(self):
self.__store__.clear()
super().clear()
async def sync(self):
await self.__store__.sync()
async def storage(name="", storage_class=Storage):
if not name:
raise ValueError("The storage name must be defined")
return storage_class(await _storage(f"@pyscript/{name}"))

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
import js
from pyscript.ffi import create_proxy
from pyscript.util import as_bytearray
code = "code"
@@ -38,7 +39,8 @@ class WebSocket(object):
for t in ["onclose", "onerror", "onmessage", "onopen"]:
if t in kw:
socket[t] = kw[t]
# Pyodide fails at setting socket[t] directly
setattr(socket, t, create_proxy(kw[t]))
def __getattr__(self, attr):
return getattr(self._ws, attr)

View File

@@ -0,0 +1,43 @@
import js as _js
from polyscript import workers as _workers
_get = _js.Reflect.get
def _set(script, name, value=""):
script.setAttribute(name, value)
# this solves an inconsistency between Pyodide and MicroPython
# @see https://github.com/pyscript/pyscript/issues/2106
class _ReadOnlyProxy:
def __getitem__(self, name):
return _get(_workers, name)
def __getattr__(self, name):
return _get(_workers, name)
workers = _ReadOnlyProxy()
async def create_named_worker(src="", name="", config=None, type="py"):
from json import dumps
if not src:
raise ValueError("Named workers require src")
if not name:
raise ValueError("Named workers require a name")
s = _js.document.createElement("script")
s.type = type
s.src = src
_set(s, "worker")
_set(s, "name", name)
if config:
_set(s, "config", isinstance(config, str) and config or dumps(config))
_js.document.body.append(s)
return await workers[name]

View File

@@ -1,2 +0,0 @@
from .pydom import JSProperty
from .pydom import dom as pydom

View File

@@ -1,95 +0,0 @@
from pyodide.ffi import to_js
from pyscript import window
class Device:
"""Device represents a media input or output device, such as a microphone,
camera, or headset.
"""
def __init__(self, device):
self._js = device
@property
def id(self):
return self._js.deviceId
@property
def group(self):
return self._js.groupId
@property
def kind(self):
return self._js.kind
@property
def label(self):
return self._js.label
def __getitem__(self, key):
return getattr(self, key)
@classmethod
async def load(cls, audio=False, video=True):
"""Load the device stream."""
options = window.Object.new()
options.audio = audio
if isinstance(video, bool):
options.video = video
else:
# TODO: Think this can be simplified but need to check it on the pyodide side
# TODO: this is pyodide specific. shouldn't be!
options.video = window.Object.new()
for k in video:
setattr(
options.video,
k,
to_js(video[k], dict_converter=window.Object.fromEntries),
)
stream = await window.navigator.mediaDevices.getUserMedia(options)
return stream
async def get_stream(self):
key = self.kind.replace("input", "").replace("output", "")
options = {key: {"deviceId": {"exact": self.id}}}
return await self.load(**options)
async def list_devices() -> list[dict]:
"""
Return the list of the currently available media input and output devices,
such as microphones, cameras, headsets, and so forth.
Output:
list(dict) - list of dictionaries representing the available media devices.
Each dictionary has the following keys:
* deviceId: a string that is an identifier for the represented device
that is persisted across sessions. It is un-guessable by other
applications and unique to the origin of the calling application.
It is reset when the user clears cookies (for Private Browsing, a
different identifier is used that is not persisted across sessions).
* groupId: a string that is a group identifier. Two devices have the same
group identifier if they belong to the same physical device — for
example a monitor with both a built-in camera and a microphone.
* kind: an enumerated value that is either "videoinput", "audioinput"
or "audiooutput".
* label: a string describing this device (for example "External USB
Webcam").
Note: the returned list will omit any devices that are blocked by the document
Permission Policy: microphone, camera, speaker-selection (for output devices),
and so on. Access to particular non-default devices is also gated by the
Permissions API, and the list will omit devices for which the user has not
granted explicit permission.
"""
# https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices
return [
Device(obj) for obj in await window.navigator.mediaDevices.enumerateDevices()
]

View File

@@ -1,569 +0,0 @@
import inspect
try:
from typing import Any
except ImportError:
Any = "Any"
try:
import warnings
except ImportError:
# TODO: For now it probably means we are in MicroPython. We should figure
# out the "right" way to handle this. For now we just ignore the warning
# and logging to console
class warnings:
@staticmethod
def warn(*args, **kwargs):
print("WARNING: ", *args, **kwargs)
try:
from functools import cached_property
except ImportError:
# TODO: same comment about micropython as above
cached_property = property
try:
from pyodide.ffi import JsProxy
except ImportError:
# TODO: same comment about micropython as above
def JsProxy(obj):
return obj
from pyscript import display, document, window
alert = window.alert
class JSProperty:
"""JS property descriptor that directly maps to the property with the same
name in the underlying JS component."""
def __init__(self, name: str, allow_nones: bool = False):
self.name = name
self.allow_nones = allow_nones
def __get__(self, obj, objtype=None):
return getattr(obj._js, self.name)
def __set__(self, obj, value):
if not self.allow_nones and value is None:
return
setattr(obj._js, self.name, value)
class BaseElement:
def __init__(self, js_element):
self._js = js_element
self._parent = None
self.style = StyleProxy(self)
self._proxies = {}
def __eq__(self, obj):
"""Check if the element is the same as the other element by comparing
the underlying JS element"""
return isinstance(obj, BaseElement) and obj._js == self._js
@property
def parent(self):
if self._parent:
return self._parent
if self._js.parentElement:
self._parent = self.__class__(self._js.parentElement)
return self._parent
@property
def __class(self):
return self.__class__ if self.__class__ != PyDom else Element
def create(self, type_, is_child=True, classes=None, html=None, label=None):
js_el = document.createElement(type_)
element = self.__class(js_el)
if classes:
for class_ in classes:
element.add_class(class_)
if html is not None:
element.html = html
if label is not None:
element.label = label
if is_child:
self.append(element)
return element
def find(self, selector):
"""Return an ElementCollection representing all the child elements that
match the specified selector.
Args:
selector (str): A string containing a selector expression
Returns:
ElementCollection: A collection of elements matching the selector
"""
elements = self._js.querySelectorAll(selector)
if not elements:
return None
return ElementCollection([Element(el) for el in elements])
class Element(BaseElement):
@property
def children(self):
return [self.__class__(el) for el in self._js.children]
def append(self, child):
# TODO: this is Pyodide specific for now!!!!!!
# if we get passed a JSProxy Element directly we just map it to the
# higher level Python element
if inspect.isclass(JsProxy) and isinstance(child, JsProxy):
return self.append(Element(child))
elif isinstance(child, Element):
self._js.appendChild(child._js)
return child
elif isinstance(child, ElementCollection):
for el in child:
self.append(el)
# -------- Pythonic Interface to Element -------- #
@property
def html(self):
return self._js.innerHTML
@html.setter
def html(self, value):
self._js.innerHTML = value
@property
def text(self):
return self._js.textContent
@text.setter
def text(self, value):
self._js.textContent = value
@property
def content(self):
# TODO: This breaks with with standard template elements. Define how to best
# handle this specifica use case. Just not support for now?
if self._js.tagName == "TEMPLATE":
warnings.warn(
"Content attribute not supported for template elements.", stacklevel=2
)
return None
return self._js.innerHTML
@content.setter
def content(self, value):
# TODO: (same comment as above)
if self._js.tagName == "TEMPLATE":
warnings.warn(
"Content attribute not supported for template elements.", stacklevel=2
)
return
display(value, target=self.id)
@property
def id(self):
return self._js.id
@id.setter
def id(self, value):
self._js.id = value
@property
def options(self):
if "options" in self._proxies:
return self._proxies["options"]
if not self._js.tagName.lower() in {"select", "datalist", "optgroup"}:
raise AttributeError(
f"Element {self._js.tagName} has no options attribute."
)
self._proxies["options"] = OptionsProxy(self)
return self._proxies["options"]
@property
def value(self):
return self._js.value
@value.setter
def value(self, value):
# in order to avoid confusion to the user, we don't allow setting the
# value of elements that don't have a value attribute
if not hasattr(self._js, "value"):
raise AttributeError(
f"Element {self._js.tagName} has no value attribute. If you want to "
"force a value attribute, set it directly using the `_js.value = <value>` "
"javascript API attribute instead."
)
self._js.value = value
@property
def selected(self):
return self._js.selected
@selected.setter
def selected(self, value):
# in order to avoid confusion to the user, we don't allow setting the
# value of elements that don't have a value attribute
if not hasattr(self._js, "selected"):
raise AttributeError(
f"Element {self._js.tagName} has no value attribute. If you want to "
"force a value attribute, set it directly using the `_js.value = <value>` "
"javascript API attribute instead."
)
self._js.selected = value
def clone(self, new_id=None):
clone = Element(self._js.cloneNode(True))
clone.id = new_id
return clone
def remove_class(self, classname):
classList = self._js.classList
if isinstance(classname, list):
classList.remove(*classname)
else:
classList.remove(classname)
return self
def add_class(self, classname):
classList = self._js.classList
if isinstance(classname, list):
classList.add(*classname)
else:
self._js.classList.add(classname)
return self
@property
def classes(self):
classes = self._js.classList.values()
return [x for x in classes]
def show_me(self):
self._js.scrollIntoView()
def snap(
self,
to: BaseElement | str = None,
width: int | None = None,
height: int | None = None,
):
"""
Captures a snapshot of a video element. (Only available for video elements)
Inputs:
* to: element where to save the snapshot of the video frame to
* width: width of the image
* height: height of the image
Output:
(Element) canvas element where the video frame snapshot was drawn into
"""
if self._js.tagName != "VIDEO":
raise AttributeError("Snap method is only available for video Elements")
if to is None:
canvas = self.create("canvas")
if width is None:
width = self._js.width
if height is None:
height = self._js.height
canvas._js.width = width
canvas._js.height = height
elif isinstance(to, Element):
if to._js.tagName != "CANVAS":
raise TypeError("Element to snap to must a canvas.")
canvas = to
elif getattr(to, "tagName", "") == "CANVAS":
canvas = Element(to)
elif isinstance(to, str):
canvas = pydom[to][0]
if canvas._js.tagName != "CANVAS":
raise TypeError("Element to snap to must a be canvas.")
canvas.draw(self, width, height)
return canvas
def download(self, filename: str = "snapped.png") -> None:
"""Download the current element (only available for canvas elements) with the filename
provided in input.
Inputs:
* filename (str): name of the file being downloaded
Output:
None
"""
if self._js.tagName != "CANVAS":
raise AttributeError(
"The download method is only available for canvas Elements"
)
link = self.create("a")
link._js.download = filename
link._js.href = self._js.toDataURL()
link._js.click()
def draw(self, what, width, height):
"""Draw `what` on the current element (only available for canvas elements).
Inputs:
* what (canvas image source): An element to draw into the context. The specification permits any canvas
image source, specifically, an HTMLImageElement, an SVGImageElement, an HTMLVideoElement,
an HTMLCanvasElement, an ImageBitmap, an OffscreenCanvas, or a VideoFrame.
"""
if self._js.tagName != "CANVAS":
raise AttributeError(
"The draw method is only available for canvas Elements"
)
if isinstance(what, Element):
what = what._js
# https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
self._js.getContext("2d").drawImage(what, 0, 0, width, height)
class OptionsProxy:
"""This class represents the options of a select element. It
allows to access to add and remove options by using the `add` and `remove` methods.
"""
def __init__(self, element: Element) -> None:
self._element = element
if self._element._js.tagName.lower() != "select":
raise AttributeError(
f"Element {self._element._js.tagName} has no options attribute."
)
def add(
self,
value: Any = None,
html: str = None,
text: str = None,
before: Element | int = None,
**kws,
) -> None:
"""Add a new option to the select element"""
# create the option element and set the attributes
option = document.createElement("option")
if value is not None:
kws["value"] = value
if html is not None:
option.innerHTML = html
if text is not None:
kws["text"] = text
for key, value in kws.items():
option.setAttribute(key, value)
if before:
if isinstance(before, Element):
before = before._js
self._element._js.add(option, before)
def remove(self, item: int) -> None:
"""Remove the option at the specified index"""
self._element._js.remove(item)
def clear(self) -> None:
"""Remove all the options"""
for i in range(len(self)):
self.remove(0)
@property
def options(self):
"""Return the list of options"""
return [Element(opt) for opt in self._element._js.options]
@property
def selected(self):
"""Return the selected option"""
return self.options[self._element._js.selectedIndex]
def __iter__(self):
yield from self.options
def __len__(self):
return len(self.options)
def __repr__(self):
return f"{self.__class__.__name__} (length: {len(self)}) {self.options}"
def __getitem__(self, key):
return self.options[key]
class StyleProxy: # (dict):
def __init__(self, element: Element) -> None:
self._element = element
@cached_property
def _style(self):
return self._element._js.style
def __getitem__(self, key):
return self._style.getPropertyValue(key)
def __setitem__(self, key, value):
self._style.setProperty(key, value)
def remove(self, key):
self._style.removeProperty(key)
def set(self, **kws):
for k, v in kws.items():
self._element._js.style.setProperty(k, v)
# CSS Properties
# Reference: https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts#L3799C1-L5005C2
# Following prperties automatically generated from the above reference using
# tools/codegen_css_proxy.py
@property
def visible(self):
return self._element._js.style.visibility
@visible.setter
def visible(self, value):
self._element._js.style.visibility = value
class StyleCollection:
def __init__(self, collection: "ElementCollection") -> None:
self._collection = collection
def __get__(self, obj, objtype=None):
return obj._get_attribute("style")
def __getitem__(self, key):
return self._collection._get_attribute("style")[key]
def __setitem__(self, key, value):
for element in self._collection._elements:
element.style[key] = value
def remove(self, key):
for element in self._collection._elements:
element.style.remove(key)
class ElementCollection:
def __init__(self, elements: [Element]) -> None:
self._elements = elements
self.style = StyleCollection(self)
def __getitem__(self, key):
# If it's an integer we use it to access the elements in the collection
if isinstance(key, int):
return self._elements[key]
# If it's a slice we use it to support slice operations over the elements
# in the collection
elif isinstance(key, slice):
return ElementCollection(self._elements[key])
# If it's anything else (basically a string) we use it as a selector
# TODO: Write tests!
elements = self._element.querySelectorAll(key)
return ElementCollection([Element(el) for el in elements])
def __len__(self):
return len(self._elements)
def __eq__(self, obj):
"""Check if the element is the same as the other element by comparing
the underlying JS element"""
return isinstance(obj, ElementCollection) and obj._elements == self._elements
def _get_attribute(self, attr, index=None):
if index is None:
return [getattr(el, attr) for el in self._elements]
# As JQuery, when getting an attr, only return it for the first element
return getattr(self._elements[index], attr)
def _set_attribute(self, attr, value):
for el in self._elements:
setattr(el, attr, value)
@property
def html(self):
return self._get_attribute("html")
@html.setter
def html(self, value):
self._set_attribute("html", value)
@property
def value(self):
return self._get_attribute("value")
@value.setter
def value(self, value):
self._set_attribute("value", value)
@property
def children(self):
return self._elements
def __iter__(self):
yield from self._elements
def __repr__(self):
return f"{self.__class__.__name__} (length: {len(self._elements)}) {self._elements}"
class DomScope:
def __getattr__(self, __name: str):
element = document[f"#{__name}"]
if element:
return element[0]
class PyDom(BaseElement):
# Add objects we want to expose to the DOM namespace since this class instance is being
# remapped as "the module" itself
BaseElement = BaseElement
Element = Element
ElementCollection = ElementCollection
def __init__(self):
# PyDom is a special case of BaseElement where we don't want to create a new JS element
# and it really doesn't have a need for styleproxy or parent to to call to __init__
# (which actually fails in MP for some reason)
self._js = document
self._parent = None
self._proxies = {}
self.ids = DomScope()
self.body = Element(document.body)
self.head = Element(document.head)
def create(self, type_, classes=None, html=None):
return super().create(type_, is_child=False, classes=classes, html=html)
def __getitem__(self, key):
elements = self._js.querySelectorAll(key)
if not elements:
return None
return ElementCollection([Element(el) for el in elements])
dom = PyDom()

View File

@@ -1 +0,0 @@
from . import elements

View File

@@ -1,947 +0,0 @@
import inspect
import sys
from pyscript import document, when, window
from pyweb import JSProperty, pydom
#: A flag to show if MicroPython is the current Python interpreter.
is_micropython = "MicroPython" in sys.version
def getmembers_static(cls):
"""Cross-interpreter implementation of inspect.getmembers_static."""
if is_micropython: # pragma: no cover
return [(name, getattr(cls, name)) for name, _ in inspect.getmembers(cls)]
return inspect.getmembers_static(cls)
class ElementBase(pydom.Element):
tag = "div"
# GLOBAL ATTRIBUTES
# These are attribute that all elements have (this list is a subset of the official one)
# We are trying to capture the most used ones
accesskey = JSProperty("accesskey")
autofocus = JSProperty("autofocus")
autocapitalize = JSProperty("autocapitalize")
className = JSProperty("className")
contenteditable = JSProperty("contenteditable")
draggable = JSProperty("draggable")
enterkeyhint = JSProperty("enterkeyhint")
hidden = JSProperty("hidden")
id = JSProperty("id")
lang = JSProperty("lang")
nonce = JSProperty("nonce")
part = JSProperty("part")
popover = JSProperty("popover")
slot = JSProperty("slot")
spellcheck = JSProperty("spellcheck")
tabindex = JSProperty("tabindex")
title = JSProperty("title")
translate = JSProperty("translate")
virtualkeyboardpolicy = JSProperty("virtualkeyboardpolicy")
def __init__(self, style=None, **kwargs):
super().__init__(document.createElement(self.tag))
# set all the style properties provided in input
if isinstance(style, dict):
for key, value in style.items():
self.style[key] = value
elif style is None:
pass
else:
raise ValueError(
f"Style should be a dictionary, received {style} (type {type(style)}) instead."
)
# IMPORTANT!!! This is used to auto-harvest all input arguments and set them as properties
self._init_properties(**kwargs)
def _init_properties(self, **kwargs):
"""Set all the properties (of type JSProperties) provided in input as properties
of the class instance.
Args:
**kwargs: The properties to set
"""
# Look at all the properties of the class and see if they were provided in kwargs
for attr_name, attr in getmembers_static(self.__class__):
# For each one, actually check if it is a property of the class and set it
if isinstance(attr, JSProperty) and attr_name in kwargs:
try:
setattr(self, attr_name, kwargs[attr_name])
except Exception as e:
print(f"Error setting {attr_name} to {kwargs[attr_name]}: {e}")
raise
class TextElementBase(ElementBase):
def __init__(self, content=None, style=None, **kwargs):
super().__init__(style=style, **kwargs)
# If it's an element, append the element
if isinstance(content, pydom.Element):
self.append(content)
# If it's a list of elements
elif isinstance(content, list):
for item in content:
self.append(item)
# If the content wasn't set just ignore
elif content is None:
pass
else:
# Otherwise, set content as the html of the element
self.html = content
# IMPORTANT: For all HTML components defined below, we are not mapping all
# available attributes, just the global and the most common ones.
# If you need to access a specific attribute, you can always use the `_js.<attribute>`
class a(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a"""
tag = "a"
download = JSProperty("download")
href = JSProperty("href")
referrerpolicy = JSProperty("referrerpolicy")
rel = JSProperty("rel")
target = JSProperty("target")
type = JSProperty("type")
class abbr(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr"""
tag = "abbr"
class address(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address"""
tag = "address"
class area(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area"""
tag = "area"
alt = JSProperty("alt")
coords = JSProperty("coords")
download = JSProperty("download")
href = JSProperty("href")
ping = JSProperty("ping")
referrerpolicy = JSProperty("referrerpolicy")
rel = JSProperty("rel")
shape = JSProperty("shape")
target = JSProperty("target")
class article(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article"""
tag = "article"
class aside(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside"""
tag = "aside"
class audio(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio"""
tag = "audio"
autoplay = JSProperty("autoplay")
controls = JSProperty("controls")
controlslist = JSProperty("controlslist")
crossorigin = JSProperty("crossorigin")
disableremoteplayback = JSProperty("disableremoteplayback")
loop = JSProperty("loop")
muted = JSProperty("muted")
preload = JSProperty("preload")
src = JSProperty("src")
class b(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b"""
tag = "b"
class blockquote(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote"""
tag = "blockquote"
cite = JSProperty("cite")
class br(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br"""
tag = "br"
class button(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button"""
tag = "button"
autofocus = JSProperty("autofocus")
disabled = JSProperty("disabled")
form = JSProperty("form")
formaction = JSProperty("formaction")
formenctype = JSProperty("formenctype")
formmethod = JSProperty("formmethod")
formnovalidate = JSProperty("formnovalidate")
formtarget = JSProperty("formtarget")
name = JSProperty("name")
type = JSProperty("type")
value = JSProperty("value")
class canvas(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas"""
tag = "canvas"
height = JSProperty("height")
width = JSProperty("width")
class caption(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption"""
tag = "caption"
class cite(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite"""
tag = "cite"
class code(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code"""
tag = "code"
class data(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data"""
tag = "data"
value = JSProperty("value")
class datalist(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist"""
tag = "datalist"
class dd(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd"""
tag = "dd"
class del_(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del"""
tag = "del"
cite = JSProperty("cite")
datetime = JSProperty("datetime")
class details(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details"""
tag = "details"
open = JSProperty("open")
class dialog(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"""
tag = "dialog"
open = JSProperty("open")
class div(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div"""
tag = "div"
class dl(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl"""
tag = "dl"
value = JSProperty("value")
class dt(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt"""
tag = "dt"
class em(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em"""
tag = "em"
class embed(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed"""
tag = "embed"
height = JSProperty("height")
src = JSProperty("src")
type = JSProperty("type")
width = JSProperty("width")
class fieldset(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset"""
tag = "fieldset"
disabled = JSProperty("disabled")
form = JSProperty("form")
name = JSProperty("name")
class figcaption(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption"""
tag = "figcaption"
class figure(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure"""
tag = "figure"
class footer(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer"""
tag = "footer"
class form(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form"""
tag = "form"
accept_charset = JSProperty("accept-charset")
action = JSProperty("action")
autocapitalize = JSProperty("autocapitalize")
autocomplete = JSProperty("autocomplete")
enctype = JSProperty("enctype")
name = JSProperty("name")
method = JSProperty("method")
nonvalidate = JSProperty("nonvalidate")
rel = JSProperty("rel")
target = JSProperty("target")
class h1(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1"""
tag = "h1"
class h2(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2"""
tag = "h2"
class h3(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3"""
tag = "h3"
class h4(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4"""
tag = "h4"
class h5(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5"""
tag = "h5"
class h6(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6"""
tag = "h6"
class header(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header"""
tag = "header"
class hgroup(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup"""
tag = "hgroup"
class hr(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr"""
tag = "hr"
class i(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i"""
tag = "i"
class iframe(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe"""
tag = "iframe"
allow = JSProperty("allow")
allowfullscreen = JSProperty("allowfullscreen")
height = JSProperty("height")
loading = JSProperty("loading")
name = JSProperty("name")
referrerpolicy = JSProperty("referrerpolicy")
sandbox = JSProperty("sandbox")
src = JSProperty("src")
srcdoc = JSProperty("srcdoc")
width = JSProperty("width")
class img(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img"""
tag = "img"
alt = JSProperty("alt")
crossorigin = JSProperty("crossorigin")
decoding = JSProperty("decoding")
fetchpriority = JSProperty("fetchpriority")
height = JSProperty("height")
ismap = JSProperty("ismap")
loading = JSProperty("loading")
referrerpolicy = JSProperty("referrerpolicy")
sizes = JSProperty("sizes")
src = JSProperty("src")
width = JSProperty("width")
# NOTE: Input is a reserved keyword in Python, so we use input_ instead
class input_(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input"""
tag = "input"
accept = JSProperty("accept")
alt = JSProperty("alt")
autofocus = JSProperty("autofocus")
capture = JSProperty("capture")
checked = JSProperty("checked")
dirname = JSProperty("dirname")
disabled = JSProperty("disabled")
form = JSProperty("form")
formaction = JSProperty("formaction")
formenctype = JSProperty("formenctype")
formmethod = JSProperty("formmethod")
formnovalidate = JSProperty("formnovalidate")
formtarget = JSProperty("formtarget")
height = JSProperty("height")
list = JSProperty("list")
max = JSProperty("max")
maxlength = JSProperty("maxlength")
min = JSProperty("min")
minlength = JSProperty("minlength")
multiple = JSProperty("multiple")
name = JSProperty("name")
pattern = JSProperty("pattern")
placeholder = JSProperty("placeholder")
popovertarget = JSProperty("popovertarget")
popovertargetaction = JSProperty("popovertargetaction")
readonly = JSProperty("readonly")
required = JSProperty("required")
size = JSProperty("size")
src = JSProperty("src")
step = JSProperty("step")
type = JSProperty("type")
value = JSProperty("value")
width = JSProperty("width")
class ins(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins"""
tag = "ins"
cite = JSProperty("cite")
datetime = JSProperty("datetime")
class kbd(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd"""
tag = "kbd"
class label(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label"""
tag = "label"
for_ = JSProperty("for")
class legend(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend"""
tag = "legend"
class li(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li"""
tag = "li"
value = JSProperty("value")
class link(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link"""
tag = "link"
as_ = JSProperty("as")
crossorigin = JSProperty("crossorigin")
disabled = JSProperty("disabled")
fetchpriority = JSProperty("fetchpriority")
href = JSProperty("href")
imagesizes = JSProperty("imagesizes")
imagesrcset = JSProperty("imagesrcset")
integrity = JSProperty("integrity")
media = JSProperty("media")
rel = JSProperty("rel")
referrerpolicy = JSProperty("referrerpolicy")
sizes = JSProperty("sizes")
title = JSProperty("title")
type = JSProperty("type")
class main(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main"""
tag = "main"
class map_(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map"""
tag = "map"
name = JSProperty("name")
class mark(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark"""
tag = "mark"
class menu(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu"""
tag = "menu"
class meter(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter"""
tag = "meter"
form = JSProperty("form")
high = JSProperty("high")
low = JSProperty("low")
max = JSProperty("max")
min = JSProperty("min")
optimum = JSProperty("optimum")
value = JSProperty("value")
class nav(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav"""
tag = "nav"
class object_(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object"""
tag = "object"
data = JSProperty("data")
form = JSProperty("form")
height = JSProperty("height")
name = JSProperty("name")
type = JSProperty("type")
usemap = JSProperty("usemap")
width = JSProperty("width")
class ol(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol"""
tag = "ol"
reversed = JSProperty("reversed")
start = JSProperty("start")
type = JSProperty("type")
class optgroup(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup"""
tag = "optgroup"
disabled = JSProperty("disabled")
label = JSProperty("label")
class option(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option"""
tag = "option"
disabled = JSProperty("value")
label = JSProperty("label")
selected = JSProperty("selected")
value = JSProperty("value")
class output(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output"""
tag = "output"
for_ = JSProperty("for")
form = JSProperty("form")
name = JSProperty("name")
class p(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p"""
tag = "p"
class picture(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture"""
tag = "picture"
class pre(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre"""
tag = "pre"
class progress(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress"""
tag = "progress"
max = JSProperty("max")
value = JSProperty("value")
class q(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q"""
tag = "q"
cite = JSProperty("cite")
class s(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s"""
tag = "s"
class script(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script"""
tag = "script"
# Let's add async manually since it's a reserved keyword in Python
async_ = JSProperty("async")
blocking = JSProperty("blocking")
crossorigin = JSProperty("crossorigin")
defer = JSProperty("defer")
fetchpriority = JSProperty("fetchpriority")
integrity = JSProperty("integrity")
nomodule = JSProperty("nomodule")
nonce = JSProperty("nonce")
referrerpolicy = JSProperty("referrerpolicy")
src = JSProperty("src")
type = JSProperty("type")
class section(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section"""
tag = "section"
class select(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select"""
tag = "select"
class small(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small"""
tag = "small"
class source(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source"""
tag = "source"
media = JSProperty("media")
sizes = JSProperty("sizes")
src = JSProperty("src")
srcset = JSProperty("srcset")
type = JSProperty("type")
class span(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span"""
tag = "span"
class strong(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong"""
tag = "strong"
class style(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style"""
tag = "style"
blocking = JSProperty("blocking")
media = JSProperty("media")
nonce = JSProperty("nonce")
title = JSProperty("title")
class sub(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub"""
tag = "sub"
class summary(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary"""
tag = "summary"
class sup(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup"""
tag = "sup"
class table(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table"""
tag = "table"
class tbody(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody"""
tag = "tbody"
class td(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td"""
tag = "td"
colspan = JSProperty("colspan")
headers = JSProperty("headers")
rowspan = JSProperty("rowspan")
class template(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template"""
tag = "template"
shadowrootmode = JSProperty("shadowrootmode")
class textarea(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea"""
tag = "textarea"
autocapitalize = JSProperty("autocapitalize")
autocomplete = JSProperty("autocomplete")
autofocus = JSProperty("autofocus")
cols = JSProperty("cols")
dirname = JSProperty("dirname")
disabled = JSProperty("disabled")
form = JSProperty("form")
maxlength = JSProperty("maxlength")
minlength = JSProperty("minlength")
name = JSProperty("name")
placeholder = JSProperty("placeholder")
readonly = JSProperty("readonly")
required = JSProperty("required")
rows = JSProperty("rows")
spellcheck = JSProperty("spellcheck")
wrap = JSProperty("wrap")
class tfoot(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot"""
tag = "tfoot"
class th(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th"""
tag = "th"
class thead(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead"""
tag = "thead"
class time(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time"""
tag = "time"
datetime = JSProperty("datetime")
class title(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title"""
tag = "title"
class tr(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr"""
tag = "tr"
abbr = JSProperty("abbr")
colspan = JSProperty("colspan")
headers = JSProperty("headers")
rowspan = JSProperty("rowspan")
scope = JSProperty("scope")
class track(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track"""
tag = "track"
default = JSProperty("default")
kind = JSProperty("kind")
label = JSProperty("label")
src = JSProperty("src")
srclang = JSProperty("srclang")
class u(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u"""
tag = "u"
class ul(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul"""
tag = "ul"
class var(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var"""
tag = "var"
class video(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video"""
tag = "video"
autoplay = JSProperty("autoplay")
controls = JSProperty("controls")
crossorigin = JSProperty("crossorigin")
disablepictureinpicture = JSProperty("disablepictureinpicture")
disableremoteplayback = JSProperty("disableremoteplayback")
height = JSProperty("height")
loop = JSProperty("loop")
muted = JSProperty("muted")
playsinline = JSProperty("playsinline")
poster = JSProperty("poster")
preload = JSProperty("preload")
src = JSProperty("src")
width = JSProperty("width")
# Custom Elements
class grid(TextElementBase):
tag = "div"
def __init__(self, layout, content=None, gap=None, **kwargs):
super().__init__(content, **kwargs)
self.style["display"] = "grid"
self.style["grid-template-columns"] = layout
# TODO: This should be a property
if not gap is None:
self.style["gap"] = gap

View File

@@ -0,0 +1,72 @@
import { ArrayBuffer, TypedArray } from "sabayon/shared";
import IDBMapSync from "@webreflection/idb-map/sync";
import { parse, stringify } from "flatted";
const to_idb = (value) => {
if (value == null) return stringify(["null", 0]);
/* eslint-disable no-fallthrough */
switch (typeof value) {
case "object": {
if (value instanceof TypedArray)
return stringify(["memoryview", [...value]]);
if (value instanceof ArrayBuffer)
return stringify(["bytearray", [...new Uint8Array(value)]]);
}
case "string":
case "number":
case "boolean":
return stringify(["generic", value]);
default:
throw new TypeError(`Unexpected value: ${String(value)}`);
}
};
const from_idb = (value) => {
const [kind, result] = parse(value);
if (kind === "null") return null;
if (kind === "generic") return result;
if (kind === "bytearray") return new Uint8Array(value).buffer;
if (kind === "memoryview") return new Uint8Array(value);
return value;
};
// this export simulate pyscript.storage exposed in the Python world
export const storage = async (name) => {
if (!name) throw new SyntaxError("The storage name must be defined");
const store = new IDBMapSync(`@pyscript/${name}`);
const map = new Map();
await store.sync();
for (const [k, v] of store.entries()) map.set(k, from_idb(v));
const clear = () => {
map.clear();
store.clear();
};
const sync = async () => {
await store.sync();
};
return new Proxy(map, {
ownKeys: (map) => [...map.keys()],
has: (map, name) => map.has(name),
get: (map, name) => {
if (name === "clear") return clear;
if (name === "sync") return sync;
return map.get(name);
},
set: (map, name, value) => {
map.set(name, value);
store.set(name, to_idb(value));
return true;
},
deleteProperty: (map, name) => {
if (map.has(name)) {
map.delete(name);
store.delete(name);
}
return true;
},
});
};

View File

@@ -1,15 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
</head>
<body>
<py-script async>
import asyncio
print('foo')
await asyncio.sleep(1)
print('bar')
</py-script>
</body>
</html>

View File

@@ -1,39 +0,0 @@
// PyScript Error Plugin
import { hooks } from '@pyscript/core';
hooks.onBeforeRun.add(function override(pyScript) {
// be sure this override happens only once
hooks.onBeforeRun.delete(override);
// trap generic `stderr` to propagate to it regardless
const { stderr } = pyScript.io;
// override it with our own logic
pyScript.io.stderr = (...args) => {
// grab the message of the first argument (Error)
const [ { message } ] = args;
// show it
notify(message);
// still let other plugins or PyScript itself do the rest
return stderr(...args);
};
});
// Error hook utilities
// Custom function to show notifications
function notify(message) {
const div = document.createElement('div');
div.textContent = message;
div.style.cssText = `
border: 1px solid red;
background: #ffdddd;
color: black;
font-family: courier, monospace;
white-space: pre;
overflow-x: auto;
padding: 8px;
margin-top: 8px;
`;
document.body.append(div);
}

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyDom Example</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
</head>
<body>
<script type="mpy" src="pydom.py"></script>
<button id="just-a-button">Click For Time</button>
<button id="color-button">Click For Color</button>
<button id="color-reset-button">Reset Color</button>
<div id="result"></div>
</body>
</html>

View File

@@ -1,36 +0,0 @@
import random
import sys
import time
from datetime import datetime as dt
from pyscript import display, when
from pyweb import pydom
display(sys.version, target="system-info")
@when("click", "#just-a-button")
def on_click():
try:
timenow = dt.now()
except NotImplementedError:
# In this case we assume it's not implemented because we are using MycroPython
tnow = time.localtime()
tstr = "{:02d}/{:02d}/{:04d} {:02d}:{:02d}:{:02d}"
timenow = tstr.format(tnow[2], tnow[1], tnow[0], *tnow[2:])
display(f"Hello from PyScript, time is: {timenow}", append=False, target="result")
@when("click", "#color-button")
def on_color_click(event):
btn = pydom["#result"]
btn.style["background-color"] = f"#{random.randrange(0x1000000):06x}"
@when("click", "#color-reset-button")
def reset_color(*args, **kwargs):
pydom["#result"].style["background-color"] = "white"
# btn_reset = pydom["#color-reset-button"][0].when('click', reset_color)

View File

@@ -1,21 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyDom Example (MicroPython)</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
</head>
<body>
<script type="mpy" src="pydom.py"></script>
<div id="system-info"></div>
<button id="just-a-button">Click For Time</button>
<button id="color-button">Click For Color</button>
<button id="color-reset-button">Reset Color</button>
<div id="result"></div>
</body>
</html>

View File

@@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyScript tests</title>
<style>
body { font-family: sans-serif; }
a {
display: block;
transition: opacity .3s;
}
a, span { opacity: .7; }
a:hover { opacity: 1; }
</style>
</head>
<body><ul><li><strong><a href="./config/index.html">config</a></strong><ul><li><a href="./config/ambiguous-config.html">ambiguous-config<small>.html</small></a></li><li><a href="./config/same-config.html">same-config<small>.html</small></a></li><li><a href="./config/too-many-config.html">too-many-config<small>.html</small></a></li><li><a href="./config/too-many-py-config.html">too-many-py-config<small>.html</small></a></li></ul></li><li><strong><a href="./issue-7015/index.html">issue-7015</a></strong></li><li><strong><span>js-integration</span></strong><ul><li><a href="./js-integration/async-listener.html">async-listener<small>.html</small></a></li><li><a href="./js-integration/config-url.html">config-url<small>.html</small></a></li><li><strong><a href="./js-integration/fetch/index.html">fetch</a></strong></li><li><a href="./js-integration/ffi.html">ffi<small>.html</small></a></li><li><a href="./js-integration/hooks.html">hooks<small>.html</small></a></li><li><strong><a href="./js-integration/issue-2093/index.html">issue-2093</a></strong></li><li><a href="./js-integration/js-storage.html">js-storage<small>.html</small></a></li><li><a href="./js-integration/js_modules.html">js_modules<small>.html</small></a></li><li><strong><a href="./js-integration/loader/index.html">loader</a></strong></li><li><a href="./js-integration/mpy.html">mpy<small>.html</small></a></li><li><a href="./js-integration/py-terminal-main.html">py-terminal-main<small>.html</small></a></li><li><a href="./js-integration/py-terminal-worker.html">py-terminal-worker<small>.html</small></a></li><li><a href="./js-integration/py-terminal.html">py-terminal<small>.html</small></a></li><li><a href="./js-integration/py-terminals.html">py-terminals<small>.html</small></a></li><li><a href="./js-integration/storage.html">storage<small>.html</small></a></li><li><strong><a href="./js-integration/workers/index.html">workers</a></strong><ul><li><a href="./js-integration/workers/named.html">named<small>.html</small></a></li></ul></li></ul></li><li><strong><a href="./manual/index.html">manual</a></strong><ul><li><a href="./manual/all-done.html">all-done<small>.html</small></a></li><li><a href="./manual/async.html">async<small>.html</small></a></li><li><a href="./manual/camera.html">camera<small>.html</small></a></li><li><a href="./manual/click.html">click<small>.html</small></a></li><li><a href="./manual/code-a-part.html">code-a-part<small>.html</small></a></li><li><a href="./manual/combo.html">combo<small>.html</small></a></li><li><a href="./manual/config.html">config<small>.html</small></a></li><li><a href="./manual/create-element.html">create-element<small>.html</small></a></li><li><a href="./manual/dialog.html">dialog<small>.html</small></a></li><li><a href="./manual/display.html">display<small>.html</small></a></li><li><a href="./manual/error.html">error<small>.html</small></a></li><li><a href="./manual/html-decode.html">html-decode<small>.html</small></a></li><li><a href="./manual/input.html">input<small>.html</small></a></li><li><a href="./manual/interpreter.html">interpreter<small>.html</small></a></li><li><a href="./manual/multi.html">multi<small>.html</small></a></li><li><a href="./manual/multiple-editors.html">multiple-editors<small>.html</small></a></li><li><a href="./manual/no-error.html">no-error<small>.html</small></a></li><li><a href="./manual/py-editor-failure.html">py-editor-failure<small>.html</small></a></li><li><a href="./manual/py-editor.html">py-editor<small>.html</small></a></li><li><a href="./manual/py_modules.html">py_modules<small>.html</small></a></li><li><a href="./manual/split-config.html">split-config<small>.html</small></a></li><li><a href="./manual/target.html">target<small>.html</small></a></li><li><a href="./manual/test_display_HTML.html">test_display_HTML<small>.html</small></a></li><li><a href="./manual/test_when.html">test_when<small>.html</small></a></li><li><a href="./manual/worker.html">worker<small>.html</small></a></li></ul></li><li><strong><a href="./no_sab/index.html">no_sab</a></strong></li><li><strong><a href="./piratical/index.html">piratical</a></strong></li><li><strong><a href="./py-editor/index.html">py-editor</a></strong><ul><li><a href="./py-editor/issue-2056.html">issue-2056<small>.html</small></a></li><li><a href="./py-editor/service-worker.html">service-worker<small>.html</small></a></li></ul></li><li><strong><a href="./py-terminals/index.html">py-terminals</a></strong><ul><li><a href="./py-terminals/no-repl.html">no-repl<small>.html</small></a></li><li><a href="./py-terminals/repl.html">repl<small>.html</small></a></li></ul></li><li><strong><a href="./pyscript_dom/index.html">pyscript_dom</a></strong></li><li><strong><a href="./service-worker/index.html">service-worker</a></strong></li><li><strong><a href="./ui/index.html">ui</a></strong><ul><li><a href="./ui/gallery.html">gallery<small>.html</small></a></li></ul></li></ul></body>
</html>

View File

@@ -1,7 +1,7 @@
import { test, expect } from '@playwright/test';
test('MicroPython display', async ({ page }) => {
await page.goto('http://localhost:8080/test/mpy.html');
await page.goto('http://localhost:8080/tests/js-integration/mpy.html');
await page.waitForSelector('html.done.worker');
const body = await page.evaluate(() => document.body.innerText);
await expect(body.trim()).toBe([
@@ -18,7 +18,7 @@ test('MicroPython hooks', async ({ page }) => {
if (!text.startsWith('['))
logs.push(text);
});
await page.goto('http://localhost:8080/test/hooks.html');
await page.goto('http://localhost:8080/tests/js-integration/hooks.html');
await page.waitForSelector('html.done.worker');
await expect(logs.join('\n')).toBe([
'main onReady',
@@ -43,7 +43,7 @@ test('MicroPython + Pyodide js_modules', async ({ page }) => {
if (!text.startsWith('['))
logs.push(text);
});
await page.goto('http://localhost:8080/test/js_modules.html');
await page.goto('http://localhost:8080/tests/js-integration/js_modules.html');
await page.waitForSelector('html.done');
await expect(logs.length).toBe(6);
await expect(logs[0]).toBe(logs[1]);
@@ -53,38 +53,64 @@ test('MicroPython + Pyodide js_modules', async ({ page }) => {
});
test('MicroPython + configURL', async ({ page }) => {
const logs = [];
page.on('console', msg => {
const text = msg.text();
if (!text.startsWith('['))
logs.push(text);
});
await page.goto('http://localhost:8080/test/config-url.html');
await page.goto('http://localhost:8080/tests/js-integration/config-url.html');
await page.waitForSelector('html.main.worker');
});
test('Pyodide + terminal on Main', async ({ page }) => {
await page.goto('http://localhost:8080/test/py-terminal-main.html');
await page.goto('http://localhost:8080/tests/js-integration/py-terminal-main.html');
await page.waitForSelector('html.ok');
});
test('Pyodide + terminal on Worker', async ({ page }) => {
await page.goto('http://localhost:8080/test/py-terminal-worker.html');
await page.goto('http://localhost:8080/tests/js-integration/py-terminal-worker.html');
await page.waitForSelector('html.ok');
});
test('Pyodide + multiple terminals via Worker', async ({ page }) => {
await page.goto('http://localhost:8080/test/py-terminals.html');
await page.goto('http://localhost:8080/tests/js-integration/py-terminals.html');
await page.waitForSelector('html.first.second');
});
test('MicroPython + Pyodide fetch', async ({ page }) => {
await page.goto('http://localhost:8080/test/fetch.html');
await page.goto('http://localhost:8080/tests/js-integration/fetch/index.html');
await page.waitForSelector('html.mpy.py');
});
test('MicroPython + Pyodide ffi', async ({ page }) => {
await page.goto('http://localhost:8080/test/ffi.html');
await page.goto('http://localhost:8080/tests/js-integration/ffi.html');
await page.waitForSelector('html.mpy.py');
});
test('MicroPython + Storage', async ({ page }) => {
await page.goto('http://localhost:8080/tests/js-integration/storage.html');
await page.waitForSelector('html.ok');
});
test('MicroPython + JS Storage', async ({ page }) => {
await page.goto('http://localhost:8080/tests/js-integration/js-storage.html');
await page.waitForSelector('html.ok');
});
test('MicroPython + workers', async ({ page }) => {
await page.goto('http://localhost:8080/tests/js-integration/workers/index.html');
await page.waitForSelector('html.mpy.py');
});
test('MicroPython Editor setup error', async ({ page }) => {
await page.goto('http://localhost:8080/tests/js-integration/issue-2093/index.html');
await page.waitForSelector('html.errored');
});
test('MicroPython async @when listener', async ({ page }) => {
await page.goto('http://localhost:8080/tests/js-integration/async-listener.html');
await page.waitForSelector('html.ok');
});
test('Pyodide loader', async ({ page }) => {
await page.goto('http://localhost:8080/tests/js-integration/loader/index.html');
await page.waitForSelector('html.ok');
const body = await page.evaluate(() => document.body.textContent);
await expect(body.includes('Loaded Pyodide')).toBe(true);
});

View File

@@ -17,7 +17,7 @@ from playwright.sync_api import Error as PlaywrightError
ROOT = py.path.local(__file__).dirpath("..", "..", "..")
BUILD = ROOT.join("pyscript.core").join("dist")
TEST = ROOT.join("pyscript.core").join("test")
TEST = ROOT.join("pyscript.core").join("tests")
def params_with_marks(params):
@@ -212,7 +212,7 @@ class PyScriptTest:
tmpdir.join("dist").mksymlinkto(BUILD)
# create a symlink to TEST inside tmpdir so we can run tests in that
# manual test folder
tmpdir.join("test").mksymlinkto(TEST)
tmpdir.join("tests").mksymlinkto(TEST)
# create a symlink to the favicon, so that we can use it in the HTML
self.tmpdir.chdir()

View File

@@ -97,7 +97,7 @@ class TestBasic(PyScriptTest):
def test_input_exception(self):
self.pyscript_run(
"""
<script type="py">
<script type="py" async="false">
input("what's your name?")
</script>
"""

View File

@@ -43,12 +43,12 @@ class TestDisplay(PyScriptTest):
def test_consecutive_display(self):
self.pyscript_run(
"""
<script type="py">
<script type="py" async="false">
from pyscript import display
display('hello 1')
</script>
<p>hello 2</p>
<script type="py">
<script type="py" async="false">
from pyscript import display
display('hello 3')
</script>
@@ -177,16 +177,16 @@ class TestDisplay(PyScriptTest):
def test_consecutive_display_target(self):
self.pyscript_run(
"""
<script type="py" id="first">
<script type="py" id="first" async="false">
from pyscript import display
display('hello 1')
</script>
<p>hello in between 1 and 2</p>
<script type="py" id="second">
<script type="py" id="second" async="false">
from pyscript import display
display('hello 2', target="second")
</script>
<script type="py" id="third">
<script type="py" id="third" async="false">
from pyscript import display
display('hello 3')
</script>

View File

@@ -5,7 +5,6 @@ from .support import PyScriptTest, with_execution_thread
class TestSmokeTests(PyScriptTest):
"""
Each example requires the same three tests:
- Test that the initial markup loads properly (currently done by
testing the <title> tag's content)
- Testing that pyscript is loading properly
@@ -14,7 +13,7 @@ class TestSmokeTests(PyScriptTest):
def test_pydom(self):
# Test the full pydom test suite by running it in the browser
self.goto("test/pyscript_dom/index.html?-v&-s")
self.goto("tests/pyscript_dom/index.html?-v&-s")
assert self.page.title() == "PyDom Test Suite"
# wait for the test suite to finish

View File

@@ -101,10 +101,9 @@ class TestElements(PyScriptTest):
code_ = f"""
from pyscript import when
<script type="{interpreter}">
from pyweb import pydom
from pyweb.ui.elements import {el_type}
from pyscript.web import page, {el_type}
el = {el_type}({attributes})
pydom.body.append(el)
page.body.append(el)
</script>
"""
self.pyscript_run(code_)
@@ -178,7 +177,7 @@ class TestElements(PyScriptTest):
)
def test_b(self, interpreter):
self._create_el_and_basic_asserts("aside", "some text", interpreter)
self._create_el_and_basic_asserts("b", "some text", interpreter)
def test_blockquote(self, interpreter):
self._create_el_and_basic_asserts("blockquote", "some text", interpreter)
@@ -603,3 +602,203 @@ class TestElements(PyScriptTest):
properties=properties,
expected_errors=self.expected_missing_file_errors,
)
def test_append_py_element(self, interpreter):
# Let's make sure the body of the page is clean first
body = self.page.locator("body")
assert body.inner_html() == ""
# Let's make sure the element is not in the page
element = self.page.locator("div")
assert not element.count()
div_text_content = "Luke, I am your father"
p_text_content = "noooooooooo!"
# Let's create the element
code_ = f"""
from pyscript import when
<script type="{interpreter}">
from pyscript.web import page, div, p
el = div("{div_text_content}")
child = p('{p_text_content}')
el.append(child)
page.body.append(el)
</script>
"""
self.pyscript_run(code_)
# Let's keep the tag in 2 variables, one for the selector and another to
# check the return tag from the selector
el = self.page.locator("div")
tag = el.evaluate("node => node.tagName")
assert tag == "DIV"
assert el.text_content() == f"{div_text_content}{p_text_content}"
assert (
el.evaluate("node => node.children.length") == 1
), "There should be only 1 child"
assert el.evaluate("node => node.children[0].tagName") == "P"
assert (
el.evaluate("node => node.children[0].parentNode.textContent")
== f"{div_text_content}{p_text_content}"
)
assert el.evaluate("node => node.children[0].textContent") == p_text_content
def test_append_proxy_element(self, interpreter):
# Let's make sure the body of the page is clean first
body = self.page.locator("body")
assert body.inner_html() == ""
# Let's make sure the element is not in the page
element = self.page.locator("div")
assert not element.count()
div_text_content = "Luke, I am your father"
p_text_content = "noooooooooo!"
# Let's create the element
code_ = f"""
from pyscript import when
<script type="{interpreter}">
from pyscript import document
from pyscript.web import page, div, p
el = div("{div_text_content}")
child = document.createElement('P')
child.textContent = '{p_text_content}'
el.append(child)
page.body.append(el)
</script>
"""
self.pyscript_run(code_)
# Let's keep the tag in 2 variables, one for the selector and another to
# check the return tag from the selector
el = self.page.locator("div")
tag = el.evaluate("node => node.tagName")
assert tag == "DIV"
assert el.text_content() == f"{div_text_content}{p_text_content}"
assert (
el.evaluate("node => node.children.length") == 1
), "There should be only 1 child"
assert el.evaluate("node => node.children[0].tagName") == "P"
assert (
el.evaluate("node => node.children[0].parentNode.textContent")
== f"{div_text_content}{p_text_content}"
)
assert el.evaluate("node => node.children[0].textContent") == p_text_content
def test_append_py_elementcollection(self, interpreter):
# Let's make sure the body of the page is clean first
body = self.page.locator("body")
assert body.inner_html() == ""
# Let's make sure the element is not in the page
element = self.page.locator("div")
assert not element.count()
div_text_content = "Luke, I am your father"
p_text_content = "noooooooooo!"
p2_text_content = "not me!"
# Let's create the element
code_ = f"""
from pyscript import when
<script type="{interpreter}">
from pyscript.web import page, div, p, ElementCollection
el = div("{div_text_content}")
child1 = p('{p_text_content}')
child2 = p('{p2_text_content}', id='child2')
collection = ElementCollection([child1, child2])
el.append(collection)
page.body.append(el)
</script>
"""
self.pyscript_run(code_)
# Let's keep the tag in 2 variables, one for the selector and another to
# check the return tag from the selector
el = self.page.locator("div")
tag = el.evaluate("node => node.tagName")
assert tag == "DIV"
parent_full_content = f"{div_text_content}{p_text_content}{p2_text_content}"
assert el.text_content() == parent_full_content
assert (
el.evaluate("node => node.children.length") == 2
), "There should be only 1 child"
assert el.evaluate("node => node.children[0].tagName") == "P"
assert (
el.evaluate("node => node.children[0].parentNode.textContent")
== parent_full_content
)
assert el.evaluate("node => node.children[0].textContent") == p_text_content
assert el.evaluate("node => node.children[1].tagName") == "P"
assert el.evaluate("node => node.children[1].id") == "child2"
assert (
el.evaluate("node => node.children[1].parentNode.textContent")
== parent_full_content
)
assert el.evaluate("node => node.children[1].textContent") == p2_text_content
def test_append_js_element_nodelist(self, interpreter):
# Let's make sure the body of the page is clean first
body = self.page.locator("body")
assert body.inner_html() == ""
# Let's make sure the element is not in the page
element = self.page.locator("div")
assert not element.count()
div_text_content = "Luke, I am your father"
p_text_content = "noooooooooo!"
p2_text_content = "not me!"
# Let's create the element
code_ = f"""
from pyscript import when
<script type="{interpreter}">
from pyscript import document
from pyscript.web import page, div, p, ElementCollection
el = div("{div_text_content}")
child1 = p('{p_text_content}')
child2 = p('{p2_text_content}', id='child2')
page.body.append(child1)
page.body.append(child2)
nodes = document.querySelectorAll('p')
el.append(nodes)
page.body.append(el)
</script>
"""
self.pyscript_run(code_)
# Let's keep the tag in 2 variables, one for the selector and another to
# check the return tag from the selector
el = self.page.locator("div")
tag = el.evaluate("node => node.tagName")
assert tag == "DIV"
parent_full_content = f"{div_text_content}{p_text_content}{p2_text_content}"
assert el.text_content() == parent_full_content
assert (
el.evaluate("node => node.children.length") == 2
), "There should be only 1 child"
assert el.evaluate("node => node.children[0].tagName") == "P"
assert (
el.evaluate("node => node.children[0].parentNode.textContent")
== parent_full_content
)
assert el.evaluate("node => node.children[0].textContent") == p_text_content
assert el.evaluate("node => node.children[1].tagName") == "P"
assert el.evaluate("node => node.children[1].id") == "child2"
assert (
el.evaluate("node => node.children[1].parentNode.textContent")
== parent_full_content
)
assert el.evaluate("node => node.children[1].textContent") == p2_text_content

View File

@@ -0,0 +1,4 @@
packages = [
"https://cdn.holoviz.org/panel/wheels/bokeh-3.5.0-py3-none-any.whl",
"https://cdn.holoviz.org/panel/1.5.0-b.2/dist/wheels/panel-1.5.0b2-py3-none-any.whl"
]

View File

@@ -0,0 +1,17 @@
<!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 src="https://cdn.bokeh.org/bokeh/release/bokeh-3.5.0.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.5.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.5.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@holoviz/panel@1.5.0-b.2/dist/panel.min.js"></script>
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="py" src="main.py" config="config.toml" worker></script>
<div id="simple_app"></div>
</body>
</html>

View File

@@ -0,0 +1,12 @@
import panel as pn
pn.extension(sizing_mode="stretch_width")
slider = pn.widgets.FloatSlider(start=0, end=10, name="amplitude")
def callback(new):
return f"Amplitude is: {new}"
pn.Row(slider, pn.bind(callback, slider)).servable(target="simple_app")

View File

@@ -0,0 +1,24 @@
<!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">
from pyscript import window, document, fetch, when
@when('click', '#click')
async def click(event):
text = await fetch(window.location.href).text()
document.getElementById('output').append(text)
document.documentElement.classList.add('ok')
document.getElementById('click').click()
</script>
<button id="click">click</button>
<pre id="output"></pre>
</body>
</html>

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyScript Next Plugin</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
<mpy-config src="config-url/config.json"></mpy-config>
<script type="mpy">
from pyscript import config

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../dist/core.css">
<link rel="stylesheet" href="../../../dist/core.css">
</head>
<body>
<script type="module">
@@ -18,7 +18,7 @@
document.createElement('script'),
{
type: 'module',
src: '../dist/core.js'
src: '../../../dist/core.js'
}
)
);

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PyScript FFI</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy">

View File

@@ -4,13 +4,13 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyScript Next Plugin Bug?</title>
<link rel="stylesheet" href="../dist/core.css">
<link rel="stylesheet" href="../../dist/core.css">
<script type="module">
addEventListener('mpy:done', () => {
document.documentElement.classList.add('done');
});
import { hooks } from "../dist/core.js";
import { hooks } from "../../dist/core.js";
// Main
hooks.main.onReady.add((wrap, element) => {
@@ -48,12 +48,12 @@
</script>
</head>
<body>
<script type="mpy" worker>
<script type="mpy" async="false" worker>
from pyscript import document
print("actual code in worker")
document.documentElement.classList.add('worker')
</script>
<script type="mpy">
<script type="mpy" async="false">
print("actual code in main")
</script>
</body>

View File

@@ -0,0 +1,6 @@
const { error } = console;
console.error = (...args) => {
error(...args);
document.documentElement.classList.add('errored');
};

View File

@@ -0,0 +1,16 @@
<!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="./error.js"></script>
<script type="module" src="../../../dist/core.js"></script>
</head>
<body>
<script type="mpy-editor" setup>
print("Hello Editor")
raise Exception("failure")
</script>
</body>
</html>

View File

@@ -0,0 +1,28 @@
<!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">
import { storage } from '../../dist/storage.js';
const store = await storage('js-storage');
store.test = [1, 2, 3].map(String);
await store.sync();
// be sure the store is not empty before bootstrap
import('../../dist/core.js');
</script>
</head>
<body>
<script type="mpy">
from pyscript import storage, document
store = await storage("js-storage")
if ",".join(store["test"]) == "1,2,3":
document.documentElement.classList.add("ok")
</script>
</body>
</html>

View File

@@ -3,8 +3,8 @@
<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>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<mpy-config>

View File

@@ -0,0 +1,29 @@
<!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">
import '../../../dist/core.js';
addEventListener('py:progress', ({ detail }) => {
document.body.append(
detail,
document.createElement('br'),
);
});
</script>
</head>
<body>
<py-config>
packages = ["matplotlib"]
</py-config>
<script type="py" worker>
from pyscript import document
from sys import version
document.body.append(document.createElement('hr'), version)
document.documentElement.classList.add('ok')
</script>
</body>
</html>

View File

@@ -9,8 +9,8 @@
document.documentElement.classList.add('done');
});
</script>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy">

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PyTerminal Main</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
<style>.xterm { padding: .5rem; }</style>
</head>
<body>

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PyTerminal Main</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
<style>.xterm { padding: .5rem; }</style>
</head>
<body>

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PyTerminal</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
<style>.xterm { padding: .5rem; }</style>
</head>
<body>

View File

@@ -4,12 +4,12 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PyTerminal Main</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
<style>.xterm { padding: .5rem; }</style>
</head>
<body>
<script type="py" worker terminal>
<script type="mpy" worker terminal>
from pyscript import document
document.documentElement.classList.add("first")

View File

@@ -0,0 +1,46 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@pyscript/core storage</title>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy" async>
from random import random
from pyscript import storage
store = await storage(name="test")
print("before", len(store))
for k in store:
if isinstance(store[k], memoryview):
print(f" {k}: {store[k].hex()} as hex()")
else:
print(f" {k}: {store[k]}")
store["ba"] = bytearray([0, 1, 2, 3, 4])
store["mv"] = memoryview(bytearray([5, 6, 7, 8, 9]))
store["random"] = ("some", random(), True)
store["key"] = "value"
print("now", len(store))
for k in store:
print(f" {k}: {store[k]}")
del store["key"]
# store.clear()
print("after", len(store))
for k in store:
print(f" {k}: {store[k]}")
await store.sync()
import js
js.document.documentElement.classList.add("ok")
</script>
</body>
</html>

View File

@@ -0,0 +1,2 @@
[files]
"./test.py" = "./test.py"

View File

@@ -0,0 +1,30 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<script type="module" src="../../../dist/core.js"></script>
</head>
<body>
<script type="mpy" async>
from pyscript import create_named_worker
await create_named_worker("./worker.py", name="micropython_version", type="mpy")
</script>
<script type="mpy" config="./config.toml" async>
from test import test
await test("mpy")
</script>
<script type="py" config="./config.toml" async>
from test import test
await test("py")
</script>
<script type="py" name="pyodide_version" worker>
def pyodide_version():
import sys
return sys.version
__export__ = ['pyodide_version']
</script>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>named workers</title>
<script type="module" src="../../../dist/core.js"></script>
</head>
<body>
<script type="mpy" async>
from pyscript import workers
await (await workers["mpy"]).greetings()
await (await workers["py"]).greetings()
</script>
<script type="mpy" worker name="mpy">
def greetings():
print("micropython")
__export__ = ['greetings']
</script>
<script type="py" worker name="py">
def greetings():
print("pyodide")
__export__ = ['greetings']
</script>
</body>
</html>

View File

@@ -0,0 +1,19 @@
from pyscript import document, workers
async def test(interpreter):
# accessed as item
named = await workers.micropython_version
version = await named.micropython_version()
document.body.append(version)
document.body.append(document.createElement("hr"))
# accessed as attribute
named = await workers["pyodide_version"]
version = await named.pyodide_version()
document.body.append(version)
document.body.append(document.createElement("hr"))
document.documentElement.classList.add(interpreter)

View File

@@ -0,0 +1,7 @@
def micropython_version():
import sys
return sys.version
__export__ = ["micropython_version"]

View File

@@ -0,0 +1 @@
print("a")

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../dist/core.css">
<link rel="stylesheet" href="../../dist/core.css">
<script type="module">
import '../dist/core.js';
import '../../dist/core.js';
document.body.append('loading ...', document.createElement('br'));

View File

@@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<py-script>
import asyncio
print('py-script sleep')
await asyncio.sleep(1)
print('py-script done')
</py-script>
<script type="py">
import asyncio
print('script-py sleep')
await asyncio.sleep(1)
print('script-py done')
</script>
</body>
</html>

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyScript Media Example</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="py" src="camera.py" async></script>

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyScript Next Plugin Bug?</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="py">

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../dist/core.css">
<link rel="stylesheet" href="../../dist/core.css">
<script type="module">
import { hooks } from "../dist/core.js";
import { hooks } from "../../dist/core.js";
hooks.main.codeBeforeRun.add('print(0)');
hooks.main.codeAfterRun.add('print(2)');
</script>

View File

@@ -4,11 +4,11 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PyScript Error</title>
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<py-config>
[[fetch]]
files = ["a.py"]
files = ["./a.py"]
</py-config>
<script type="py" worker>
import a

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyScript Next Plugin</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
<py-config>
files = [
</py-config>

View File

@@ -1,8 +1,8 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
<script type="module">
customElements.whenDefined('py-script').then(PyScript => {
const textContent = `

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PyScript Next</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
<script type="module">
const loader = document.querySelector('#loader');
loader.showModal();

View File

@@ -7,8 +7,8 @@
<script>
addEventListener("py:all-done", ({ type }) => console.log(type));
</script>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="py" worker async>

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyScript Next Plugin</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
<script type="py">
print(1, 2, 3)
first()

View File

@@ -1,8 +1,8 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<body>

View File

@@ -7,8 +7,8 @@
<script>
addEventListener("py:ready", console.log);
</script>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="py">

View File

@@ -7,8 +7,8 @@
<script>
addEventListener("py:ready", console.log);
</script>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<py-script>

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Pyodide Worker Version</title>
<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="py" config='{"interpreter":"https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.mjs"}' worker>
import pyodide
print(pyodide.__version__)
</script>
</body>
</html>

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="module" src="../dist/core.js"></script>
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy">

Some files were not shown because too many files have changed in this diff Show More