Compare commits

..

7 Commits

Author SHA1 Message Date
Nicholas H.Tollervey
61854bcd14 Fix MicroPython media tests, if no permission is given for a video device. 2025-03-19 15:39:21 +00:00
Nicholas H.Tollervey
f5bd62a8f6 Fix websocket tests, so they just skip. 2025-03-19 10:40:23 +00:00
Nicholas H.Tollervey
042fb93ef4 MicroPython explorations. 2025-03-19 10:34:46 +00:00
Dan Yeaw
11e94f4ae9 Make Python tests more end-to-end 2025-03-19 10:34:45 +00:00
Dan Yeaw
a49f90d67f Remove try except blocks 2025-03-19 10:34:45 +00:00
Dan Yeaw
ecd0451582 Add media js test 2025-03-19 10:34:45 +00:00
Dan Yeaw
2979b8bfcd Add media Python tests 2025-03-19 10:34:42 +00:00
90 changed files with 663 additions and 2036 deletions

View File

@@ -14,10 +14,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Install node
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 20.x

View File

@@ -13,13 +13,13 @@ jobs:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Install node
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 20.x
@@ -57,9 +57,6 @@ jobs:
- name: build
run: npm run build
- name: build offline
run: npm run build:offline
- name: Generate index.html in snapshot
working-directory: .
run: sed -e 's#_PATH_#https://pyscript.net/releases/${{ github.ref_name }}/#' -e 's#_DOC_VERSION_#${{ github.ref_name }}#' -e 's#_TAG_VERSION_#/tag/${{ github.ref_name }}#' -e 's#_VERSION_#${{ github.ref_name }}#' ./public/index.html > ./core/dist/index.html
@@ -68,18 +65,14 @@ jobs:
working-directory: .
run: tar -cvf ../release.tar * && mv ../release.tar .
- name: Upload offline.zip to release
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload ${{ github.ref_name }} ./dist/offline.zip
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
- name: Sync to S3
run:
| # Create an explicitly versioned directory under releases/YYYY.MM.MICRO/
| # Update /latest and create an explicitly versioned directory under releases/YYYY.MM.MICRO/
aws s3 sync --quiet ./dist/ s3://pyscript.net/latest/
aws s3 sync --quiet ./dist/ s3://pyscript.net/releases/${{ github.ref_name }}/

View File

@@ -20,10 +20,10 @@ jobs:
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Install node
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 20.x
@@ -62,7 +62,7 @@ jobs:
run: npm run build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}

View File

@@ -21,10 +21,10 @@ jobs:
working-directory: ./core
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Install node
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 20.x
@@ -67,7 +67,7 @@ jobs:
run: sed -e 's#_PATH_#./#' -e 's#_DOC_VERSION_#latest#' -e 's#_TAG_VERSION_##' -e 's#_VERSION_#latest#' ./public/index.html > ./core/dist/index.html
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}

View File

@@ -24,7 +24,7 @@ jobs:
MINICONDA_VERSION: 4.11.0
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 3
@@ -37,7 +37,7 @@ jobs:
run: git log --graph -3
- name: Install node
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 20.x

View File

@@ -7,7 +7,7 @@ ci:
default_stages: [pre-commit]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
rev: v5.0.0
hooks:
- id: check-builtin-literals
- id: check-case-conflict
@@ -24,8 +24,8 @@ repos:
exclude: core/dist|\.min\.js$
- id: trailing-whitespace
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 25.9.0
- repo: https://github.com/psf/black
rev: 25.1.0
hooks:
- id: black
exclude: core/tests
@@ -40,7 +40,7 @@ repos:
- tomli
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.13.3
rev: v0.9.6
hooks:
- id: ruff
exclude: core/tests

View File

@@ -1,4 +1,3 @@
ISSUE_TEMPLATE
*.min.*
package-lock.json
bridge/

View File

@@ -41,8 +41,8 @@ check-python:
# Check the environment, install the dependencies.
setup: check-node check-npm check-python
cd core && npm ci && cd ..
ifeq (,$(VIRTUAL_ENV)$(CONDA_PREFIX))
echo "\n\n\033[0;31mCannot install Python dependencies. Your virtualenv or conda env is not activated.\033[0m"
ifeq ($(VIRTUAL_ENV),)
echo "\n\n\033[0;31mCannot install Python dependencies. Your virtualenv is not activated.\033[0m"
false
else
python -m pip install -r requirements.txt

View File

@@ -13,15 +13,15 @@ Using PyScript is as simple as:
<title>PyScript!</title>
<link
rel="stylesheet"
href="https://pyscript.net/releases/2025.11.1/core.css"
href="https://pyscript.net/snapshots/2024.9.2/core.css"
/>
<script
type="module"
src="https://pyscript.net/releases/2025.11.1/core.js"
src="https://pyscript.net/snapshots/2024.9.2/core.js"
></script>
</head>
<body>
<!-- type mpy (MicroPython) or py (Pyodide) to run some Python -->
<!-- Use MicroPython to evaluate some Python -->
<script type="mpy" terminal>
print("Hello, world!")
</script>

View File

@@ -1,59 +0,0 @@
# @pyscript/bridge
Import Python utilities directly in JS
```js
// main thread
const { ffi: { func_a, func_b } } = await import('./test.js');
// test.js
import bridge from 'https://esm.run/@pyscript/bridge';
export const ffi = bridge(import.meta.url, { type: 'mpy', worker: false });
// test.py
def func_a(value):
print(f"hello {value}")
def func_b():
import sys
return sys.version
```
### Options
* **pyscript**: the release version to automatically import if not already available on the page. If no version is provided the *developers' channel* version will be used instead (for developers' purposes only).
* **type**: `py` by default to bootstrap *Pyodide*.
* **worker**: `true` by default to bootstrap in a *Web Worker*.
* **config**: either a *string* or a PyScript compatible config *JS literal* to make it possible to bootstrap files and whatnot. If specified, the `worker` becomes implicitly `true` to avoid multiple configs conflicting on the main thread.
* **env**: to share the same environment across multiple modules loaded at different times.
## Tests
Run `npx mini-coi .` within this folder to then reach out `http://localhost:8080/test/` that will show:
```
PyScript Bridge
------------------
no config
```
The [test.js](./test/test.js) files uses the following defaults:
* `pyscript` as `"2025.8.1"`
* `type` as `"mpy"`
* `worker` as `false`
* `config` as `undefined`
* `env` as `undefined`
To test any variant use query string parameters so that `?type=py` will use `py` instead, `worker` will use a worker and `config` will use a basic *config* that brings in another file from the same folder which exposes the version.
To recap: `http://localhost:8080/test/?type=py&worker&config` will show this instead:
```
PyScript Bridge
------------------
3.12.7 (main, May 15 2025, 18:47:24) ...
```
Please note when a *config* is used, the `worker` attribute is always `true`.

View File

@@ -1,163 +0,0 @@
/*! (c) PyScript Development Team */
const { stringify } = JSON;
const { assign, create, entries } = Object;
const el = (name, props) => assign(document.createElement(name), props);
/**
* Transform a list of keys into a Python dictionary.
* `['a', 'b']` => `{ "a": a, "b": b }`
* @param {Iterable<string>} keys
* @returns {string}
*/
const dictionary = keys => {
const fields = [];
for (const key of keys)
fields.push(`${stringify(key)}: ${key}`);
return `{ ${fields.join(',')} }`;
};
/**
* Resolve properly config files relative URLs.
* @param {string|Object} config - The configuration to normalize.
* @param {string} base - The base URL to resolve relative URLs against.
* @returns {string} - The JSON serialized config.
*/
const normalize = async (config, base) => {
if (typeof config === 'string') {
base = config;
config = await fetch(config).then(res => res.json());
}
if (typeof config.files === 'object') {
const files = {};
for (const [key, value] of entries(config.files)) {
files[key.startsWith('{') ? key : new URL(key, base)] = value;
}
config.files = files;
}
return stringify(config);
};
// this logic is based on a 3 levels cache ...
const cache = new Map;
/**
* Return a bridge to a Python module via a `.js` file that has a `.py` alter ego.
* @param {string} url - The URL of the JS module that has a Python counterpart.
* @param {Object} options - The options for the bridge.
* @param {string} [options.type='py'] - The `py` or `mpy` interpreter type, `py` by default.
* @param {boolean} [options.worker=true] - Whether to use a worker, `true` by default.
* @param {string|Object} [options.config=null] - The configuration for the bridge, `null` by default.
* @param {string} [options.env=null] - The optional shared environment to use.
* @param {string} [options.serviceWorker=null] - The optional service worker to use as fallback.
* @returns {Object} - The bridge to the Python module.
*/
export default (url, {
type = 'py',
worker = true,
config = null,
env = null,
serviceWorker = null,
pyscript = null,
} = {}) => {
const { protocol, host, pathname } = new URL(url);
const py = pathname.replace(/\.m?js(?:\/\+\w+)?$/, '.py');
const file = `${protocol}//${host}${py}`;
// the first cache is about the desired file in the wild ...
if (!cache.has(file)) {
// the second cache is about all fields one needs to access out there
const exports = new Map;
let python;
cache.set(file, new Proxy(create(null), {
get(_, field) {
if (!exports.has(field)) {
// create an async callback once and always return the same later on
exports.set(field, async (...args) => {
// the third cache is about reaching lazily the code only once
// augmenting its content with exports once and drop it on done
if (!python) {
// do not await or multiple calls will fetch multiple times
// just assign the fetch `Promise` once and return it
python = fetch(file).then(async response => {
const code = await response.text();
// create a unique identifier for the Python context
const identifier = pathname.replace(/[^a-zA-Z0-9_]/g, '');
const name = `__pyscript_${identifier}${Date.now()}`;
// create a Python dictionary with all accessed fields
const detail = `{"detail":${dictionary(exports.keys())}}`;
// create the arguments for the `dispatchEvent` call
const eventArgs = `${stringify(name)},${name}to_ts(${detail})`;
// bootstrap the script element type and its attributes
const script = el('script', { type, textContent: [
'\n', code, '\n',
// this is to avoid local scope name clashing
`from pyscript import window as ${name}`,
`from pyscript.ffi import to_js as ${name}to_ts`,
`${name}.dispatchEvent(${name}.CustomEvent.new(${eventArgs}))`,
// remove these references even if non-clashing to keep
// the local scope clean from undesired entries
`del ${name}`,
`del ${name}to_ts`,
].join('\n') });
// if config is provided it needs to be a worker to avoid
// conflicting with main config on the main thread (just like always)
script.toggleAttribute('worker', !!config || !!worker);
if (config) {
const attribute = await normalize(config, file);
script.setAttribute('config', attribute);
}
if (env) script.setAttribute('env', env);
if (serviceWorker) script.setAttribute('service-worker', serviceWorker);
// let PyScript resolve and execute this script
document.body.appendChild(script);
// intercept once the unique event identifier with all exports
globalThis.addEventListener(
name,
event => {
resolve(event.detail);
script.remove();
},
{ once: true }
);
// return a promise that will resolve only once the event
// has been emitted and the interpreter evaluated the code
const { promise, resolve } = Promise.withResolvers();
if (!(Symbol.for('@pyscript/core') in globalThis)) {
// bring in PyScript if not available already
const cdn = pyscript ?
`https://pyscript.net/releases/${pyscript}` :
// ⚠️ fallback to developers' channel !!!
'https://cdn.jsdelivr.net/npm/@pyscript/core/dist'
;
document.head.appendChild(
el('link', { rel: 'stylesheet', href: `${cdn}/core.css` }),
);
try { await import(`${cdn}/core.js`) }
catch {}
}
return promise;
});
}
// return the `Promise` that will after invoke the exported field
return python.then(foreign => foreign[field](...args));
});
}
// return the lazily to be resolved once callback to invoke
return exports.get(field);
}
}));
}
return cache.get(file);
};

View File

@@ -1,31 +0,0 @@
{
"name": "@pyscript/bridge",
"version": "0.2.2",
"description": "A JS based way to use PyScript modules",
"type": "module",
"module": "./index.js",
"unpkg": "./index.js",
"jsdelivr": "./jsdelivr.js",
"browser": "./index.js",
"main": "./index.js",
"keywords": [
"PyScript",
"JS",
"Python",
"bridge"
],
"files": [
"index.js",
"README.md"
],
"author": "Anaconda Inc.",
"license": "APACHE-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/pyscript/pyscript.git"
},
"bugs": {
"url": "https://github.com/pyscript/pyscript/issues"
},
"homepage": "https://github.com/pyscript/pyscript#readme"
}

View File

@@ -1,31 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>PyScript Bridge</title>
<style>body { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; }</style>
<!-- for local testing purpose only-->
<script type="importmap">{"imports":{"https://esm.run/@pyscript/bridge":"../index.js"}}</script>
<script type="module">
const { ffi: { test_func, test_other, version } } = await import('./test.js');
console.time("⏱️ first invoke");
const result = await test_func("PyScript Bridge");
console.timeEnd("⏱️ first invoke");
document.body.append(
Object.assign(
document.createElement("h3"),
{ textContent: result },
),
document.createElement("hr"),
await version(),
);
console.time("⏱️ other invokes");
await test_other("🐍");
console.timeEnd("⏱️ other invokes");
</script>
</head>
</html>

View File

@@ -1,40 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>PyScript Bridge</title>
<script type="importmap">
{
"imports": {
"https://esm.run/@pyscript/bridge": "https://esm.run/@pyscript/bridge@latest",
"https://esm.run/@pyscript/bridge/test/test.js": "https://esm.run/@pyscript/bridge@latest/test/test.js"
}
}
</script>
<style>body { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; }</style>
<link rel="stylesheet" href="https://pyscript.net/releases/2025.5.1/core.css" />
<script type="module" src="https://pyscript.net/releases/2025.5.1/core.js"></script>
<script type="module">
const cdn_test = 'https://esm.run/@pyscript/bridge/test/test.js';
const { ffi: { test_func, test_other, version } } = await import(cdn_test);
console.time("⏱️ first invoke");
const result = await test_func("PyScript Bridge");
console.timeEnd("⏱️ first invoke");
document.body.append(
Object.assign(
document.createElement("h3"),
{ textContent: result },
),
document.createElement("hr"),
await version(),
);
console.time("⏱️ other invokes");
await test_other("🐍");
console.timeEnd("⏱️ other invokes");
</script>
</head>
</html>

View File

@@ -1,5 +0,0 @@
import sys
def version():
return sys.version

View File

@@ -1,18 +0,0 @@
import bridge from "https://esm.run/@pyscript/bridge";
// for local testing purpose only
const { searchParams } = new URL(location.href);
// the named (or default) export for test.py
export const ffi = bridge(import.meta.url, {
pyscript: "2025.8.1",
env: searchParams.get("env"),
type: searchParams.get("type") || "mpy",
worker: searchParams.has("worker"),
config: searchParams.has("config") ?
({
files: {
"./sys_version.py": "./sys_version.py",
},
}) : undefined,
});

View File

@@ -1,22 +0,0 @@
from pyscript import config, RUNNING_IN_WORKER
type = config["type"]
print(f"{type}-script", RUNNING_IN_WORKER and "worker" or "main")
def test_func(message):
print("Python", message)
return message
def test_other(message):
print("Python", message)
return message
def version():
try:
from sys_version import version
except ImportError:
version = lambda: "no config"
return version()

View File

@@ -1,205 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) 2022-present, PyScript Development Team
Originated at Anaconda, Inc. in 2022
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

952
core/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@pyscript/core",
"version": "0.7.11",
"version": "0.6.39",
"type": "module",
"description": "PyScript",
"module": "./index.js",
@@ -35,20 +35,16 @@
"./storage": {
"import": "./dist/storage.js"
},
"./service-worker": {
"import": "./dist/service-worker.js"
},
"./package.json": "./package.json"
},
"scripts": {
"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 test:integration; fi",
"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:offline": "node rollup/offline.cjs | bash",
"build:tests-index": "node rollup/build_test_index.cjs",
"clean:3rd-party": "rm src/3rd-party/*.js && rm src/3rd-party/*.css",
"test:integration": "npm run test:ws; static-handler --coi . 2>/dev/null & SH_PID=$!; EXIT_CODE=0; (playwright test tests/js_tests.spec.js && playwright test tests/py_tests.main.spec.js && playwright test tests/py_tests.worker.spec.js) || EXIT_CODE=$?; kill $SH_PID 2>/dev/null; exit $EXIT_CODE",
@@ -68,40 +64,40 @@
"dependencies": {
"@ungap/with-resolvers": "^0.1.0",
"@webreflection/idb-map": "^0.3.2",
"@webreflection/utils": "^0.1.1",
"add-promise-listener": "^0.1.3",
"basic-devtools": "^0.1.6",
"polyscript": "^0.20.0",
"polyscript": "^0.16.21",
"sabayon": "^0.6.6",
"sticky-module": "^0.1.1",
"to-json-callback": "^0.1.1",
"type-checked-collections": "^0.1.7"
},
"devDependencies": {
"@codemirror/commands": "^6.10.0",
"@codemirror/lang-python": "^6.2.1",
"@codemirror/language": "^6.11.3",
"@codemirror/commands": "^6.8.0",
"@codemirror/lang-python": "^6.1.7",
"@codemirror/language": "^6.10.8",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.8",
"@playwright/test": "^1.56.1",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"@codemirror/view": "^6.36.4",
"@playwright/test": "^1.51.0",
"@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-terser": "^0.4.4",
"@webreflection/toml-j0.4": "^1.1.4",
"@webreflection/toml-j0.4": "^1.1.3",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0",
"bun": "^1.3.3",
"bun": "^1.2.4",
"chokidar": "^4.0.3",
"codedent": "^0.1.2",
"codemirror": "^6.0.2",
"eslint": "^9.39.1",
"codemirror": "^6.0.1",
"eslint": "^9.22.0",
"flatted": "^3.3.3",
"rollup": "^4.53.3",
"rollup": "^4.35.0",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-string": "^3.0.0",
"static-handler": "^0.5.3",
"string-width": "^8.1.0",
"typescript": "^5.9.3",
"string-width": "^7.2.0",
"typescript": "^5.8.2",
"xterm-readline": "^1.1.2"
},
"repository": {

View File

@@ -1,48 +0,0 @@
#!/usr/bin/env bash
# This script assumes the following folder structure:
# ./pyscript - it must be a GitHub clone/fork
# ./polyscript - it must be a GitHub clone/fork
#
# Running from ./pyscript/core via:
#
# cd ./pyscript/core
# bash ./pyodide.sh
#
# will print a JSON compatible string like:
#
# {
# "2024.10.1": "0.26.2",
# ...
# "2025.11.1": "0.29.0",
# "": null
# }
#
# Each key represents the PyScript release and each
# value represents the Pyodide version used by that PyScript release.
#
# The last empty key with `null` value is used just to close the JSON object.
# One could remove manually that entry as long as there are no dangling commas.
#
current_pyscript=$(git branch | grep \\* | cut -d ' ' -f2)
echo "{"
for release in $(git tag --list --sort=version:refname); do
git checkout ${release} > /dev/null 2>&1
if test -e "package.json"; then
polyscript=$(cat package.json | jq -r '.dependencies.polyscript')
tag="v${polyscript:1:${#polyscript}-1}"
cd ../../polyscript > /dev/null 2>&1
current_polyscript=$(git branch | grep \\* | cut -d ' ' -f2)
git checkout ${tag} > /dev/null 2>&1
if test -e "versions/pyodide"; then
echo " \"${release}\": \"$(cat versions/pyodide)\","
fi
git checkout ${current_polyscript} > /dev/null 2>&1
cd - > /dev/null 2>&1
fi
git checkout ${current_pyscript} > /dev/null 2>&1
done
echo " \"\": null"
echo "}"

View File

@@ -53,15 +53,4 @@ export default [
sourcemap: true,
},
},
{
input: "./src/service-worker.js",
plugins: plugins.concat(
process.env.NO_MIN
? [nodeResolve(), commonjs()]
: [nodeResolve(), commonjs(), terser()],
),
output: {
file: "./dist/service-worker.js",
},
},
];

View File

@@ -1,79 +0,0 @@
const { readFileSync, writeFileSync } = require("node:fs");
const { join, resolve } = require("node:path");
const versions = resolve(
__dirname,
"..",
"node_modules",
"polyscript",
"versions",
);
let pyodide = String(readFileSync(join(versions, "pyodide"), "utf8")).trim();
let micropython = String(
readFileSync(join(versions, "micropython"), "utf8"),
).trim();
writeFileSync(
join(process.cwd(), "offline.html"),
`<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyScript Offline</title>
<script src="./mini-coi-fd.js"></script>
<script type="module" src="./pyscript/core.js" offline></script>
<link rel="stylesheet" href="./pyscript/core.css">
</head>
<body>
<script type="mpy">
from pyscript import document
document.body.append("MicroPython Offline", document.createElement("hr"))
</script>
<script type="py" worker>
from pyscript import document
document.body.append("Pyodide Offline")
</script>
</body>
</html>
`,
"utf8",
);
let bash = `#!/usr/bin/env bash
rm -rf dist/offline
mkdir -p dist/offline/node_modules
echo '{"dependencies":{"pyodide":"${pyodide}","@micropython/micropython-webassembly-pyscript":"${micropython}"}}' > dist/offline/package.json
cd dist/offline
curl -sLO https://raw.githubusercontent.com/WebReflection/mini-coi/refs/heads/main/mini-coi-fd.js
npm i
cd -
mkdir -p dist/offline/pyscript/pyodide
cd dist/offline/pyscript/pyodide
cp ../../node_modules/pyodide/pyodide* ./
cp ../../node_modules/pyodide/python_stdlib.zip ./
cd -
mkdir -p dist/offline/pyscript/micropython
cd dist/offline/pyscript/micropython
cp ../../node_modules/@micropython/micropython-webassembly-pyscript/micropython.* ./
cd -
rm -rf dist/offline/node_modules
rm -rf dist/offline/*.json
mv offline.html dist/offline/index.html
cp dist/*.* dist/offline/pyscript/
rm -f dist/offline/pyscript/offline.zip
cd dist
zip -r offline.zip offline
rm -rf offline
cd -
`;
console.log(bash);

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (C) 2018-2021 by Marijn Haverbeke <marijnh@gmail.com> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Jak Wings
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,25 +0,0 @@
Copyright 2021 Erik Bremen
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@@ -1,21 +0,0 @@
Copyright (c) 2017-2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com)
Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,19 +0,0 @@
Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,19 +0,0 @@
Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -5,7 +5,3 @@ This folder contains artifacts created via [3rd-party.cjs](../../rollup/3rd-part
As we would like to offer a way to run PyScript offline, and we already offer a `dist` folder with all the necessary scripts, we have created a foreign dependencies resolver that allow to lazy-load CDN dependencies out of the box.
Please **note** these dependencies are **not interpreters**, because interpreters have their own mechanism, folders structure, WASM files, and whatnot, to work locally, but at least XTerm or the TOML parser, among other lazy dependencies, should be available within the dist folder.
## Licenses
All licenses provided by 3rd-party authors can be found in [3rd-party-licenses](../3rd-party-licenses/) folder.

View File

@@ -1,5 +1,5 @@
/**
* Bundled by jsDelivr using Rollup v2.79.2 and Terser v5.39.0.
* Bundled by jsDelivr using Rollup v2.79.1 and Terser v5.19.2.
* Original file: /npm/xterm-readline@1.1.2/lib/readline.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files

View File

@@ -1,5 +1,5 @@
/**
* Minified by jsDelivr using clean-css v5.3.3.
* Minified by jsDelivr using clean-css v5.3.2.
* Original file: /npm/@xterm/xterm@5.5.0/css/xterm.css
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/**
* Bundled by jsDelivr using Rollup v2.79.2 and Terser v5.39.0.
* Bundled by jsDelivr using Rollup v2.79.1 and Terser v5.19.2.
* Original file: /npm/@xterm/addon-fit@0.10.0/lib/addon-fit.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files

View File

@@ -1,5 +1,5 @@
/**
* Bundled by jsDelivr using Rollup v2.79.2 and Terser v5.39.0.
* Bundled by jsDelivr using Rollup v2.79.1 and Terser v5.19.2.
* Original file: /npm/@xterm/addon-web-links@0.11.0/lib/addon-web-links.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files

View File

@@ -1,4 +1,3 @@
import withResolvers from "@webreflection/utils/with-resolvers";
import TYPES from "./types.js";
const waitForIt = [];
@@ -6,7 +5,7 @@ const waitForIt = [];
for (const [TYPE] of TYPES) {
const selectors = [`script[type="${TYPE}"]`, `${TYPE}-script`];
for (const element of document.querySelectorAll(selectors.join(","))) {
const { promise, resolve } = withResolvers();
const { promise, resolve } = Promise.withResolvers();
waitForIt.push(promise);
element.addEventListener(`${TYPE}:done`, resolve, { once: true });
}

View File

@@ -70,7 +70,6 @@ for (const [TYPE] of TYPES) {
let config,
type,
parser,
pyElement,
pyConfigs = $$(`${TYPE}-config`),
attrConfigs = $$(
@@ -93,11 +92,9 @@ for (const [TYPE] of TYPES) {
[pyElement] = pyConfigs;
config = pyElement.getAttribute("src") || pyElement.textContent;
type = pyElement.getAttribute("type");
parser = pyElement.getAttribute("config-parser");
} else if (attrConfigs.length) {
[pyElement, ...attrConfigs] = attrConfigs;
config = pyElement.getAttribute("config");
parser = pyElement.getAttribute("config-parser");
// throw an error if dirrent scripts use different configs
if (
attrConfigs.some((el) => el.getAttribute("config") !== config)
@@ -123,12 +120,9 @@ for (const [TYPE] of TYPES) {
}
} else if (toml || type === "toml") {
try {
const module = parser
? await import(parser)
: await import(
const { parse } = await import(
/* webpackIgnore: true */ "./3rd-party/toml.js"
);
const parse = module.parse || module.default;
parsed = parse(text);
} catch (e) {
error = syntaxError("TOML", url, e);
@@ -160,9 +154,6 @@ for (const [TYPE] of TYPES) {
return await Promise.all(toBeAwaited);
};
if (Number.isSafeInteger(parsed?.experimental_ffi_timeout))
globalThis.reflected_ffi_timeout = parsed?.experimental_ffi_timeout;
configs.set(TYPE, { config: parsed, configURL, plugins, error });
}

View File

@@ -1,8 +1,7 @@
/*! (c) PyScript Development Team */
import "./zero-redirect.js";
import stickyModule from "sticky-module";
import withResolvers from "@webreflection/utils/with-resolvers";
import "@ungap/with-resolvers";
import {
INVALID_CONTENT,
@@ -117,7 +116,7 @@ for (const [TYPE, interpreter] of TYPES) {
else dispatch(element, TYPE, "done");
};
let { config, configURL, plugins, error } = configs.get(TYPE);
const { config, configURL, plugins, error } = configs.get(TYPE);
// create a unique identifier when/if needed
let id = 0;
@@ -311,24 +310,13 @@ for (const [TYPE, interpreter] of TYPES) {
hooked.set(TYPE, hooks);
// allow offline interpreter detection via [offline] attribute
let version = offline_interpreter(config);
if (!version) {
const css = "script[type='module'][offline]";
const s = document.querySelector(css)?.src;
if (s && import.meta.url.startsWith(s.replace(/\.js$/, ""))) {
version = `./pyscript/${interpreter}/${interpreter}.mjs`;
version = offline_interpreter({ interpreter: version });
}
}
define(TYPE, {
config,
configURL,
interpreter,
hooks,
version,
env: `${TYPE}-script`,
version: offline_interpreter(config),
onerror(error, element) {
errors.set(element, error);
},
@@ -339,7 +327,7 @@ for (const [TYPE, interpreter] of TYPES) {
class extends HTMLElement {
constructor() {
assign(super(), {
_wrap: withResolvers(),
_wrap: Promise.withResolvers(),
srcCode: "",
executed: false,
});

View File

@@ -1,5 +1,4 @@
import IDBMap from "@webreflection/idb-map";
import withResolvers from "@webreflection/utils/with-resolvers";
import { assign } from "polyscript/exports";
import { $$ } from "basic-devtools";
@@ -27,7 +26,7 @@ export const getFileSystemDirectoryHandle = async (options) => {
);
}
const { promise, resolve, reject } = withResolvers();
const { promise, resolve, reject } = Promise.withResolvers();
const how = { id: "pyscript", mode: "readwrite", ...options };
if (options.hint) how.startIn = options.hint;

View File

@@ -1,5 +1,4 @@
// PyScript py-editor plugin
import withResolvers from "@webreflection/utils/with-resolvers";
import { Hook, XWorker, dedent, defineProperties } from "polyscript/exports";
import { TYPES, offline_interpreter, relative_url, stdlib } from "../core.js";
import { notify } from "./error.js";
@@ -38,7 +37,7 @@ const getRelatedScript = (target, type) => {
return editor?.parentNode?.previousElementSibling;
};
async function execute({ currentTarget, script }) {
async function execute({ currentTarget }) {
const { env, pySrc, outDiv } = this;
const hasRunButton = !!currentTarget;
@@ -91,15 +90,16 @@ async function execute({ currentTarget, script }) {
// creation and destruction of editors on the fly
if (hasRunButton) {
for (const type of TYPES.keys()) {
script = getRelatedScript(currentTarget, type);
if (script) break;
}
}
const script = getRelatedScript(currentTarget, type);
if (script) {
defineProperties(script, { xworker: { value: xworker } });
break;
}
}
}
const { sync } = xworker;
const { promise, resolve } = withResolvers();
const { promise, resolve } = Promise.withResolvers();
envs.set(env, promise);
sync.revoke = () => {
URL.revokeObjectURL(srcLink);
@@ -126,14 +126,6 @@ async function execute({ currentTarget, script }) {
if (hasRunButton) {
currentTarget.classList.remove("running");
currentTarget.innerHTML = RUN_BUTTON;
const { previousElementSibling } =
currentTarget.closest("[data-env]").parentElement;
previousElementSibling?.dispatchEvent(
new Event("py-editor:done", {
bubbles: true,
cancelable: true,
}),
);
}
};
const { sync } = xworker;
@@ -156,20 +148,6 @@ async function execute({ currentTarget, script }) {
});
}
const replaceScript = (script, type) => {
script.xworker?.terminate();
const clone = script.cloneNode(true);
clone.type = `${type}-editor`;
const editor = editors.get(script);
if (editor) {
const content = editor.state.doc.toString();
clone.textContent = content;
editors.delete(script);
script.nextElementSibling.remove();
}
script.replaceWith(clone);
};
const makeRunButton = (handler, type) => {
const runButton = document.createElement("button");
runButton.className = `absolute ${type}-editor-run-button`;
@@ -182,25 +160,15 @@ const makeRunButton = (handler, type) => {
) {
const script = getRelatedScript(runButton, type);
if (script) {
const env = script.getAttribute("env");
// remove the bootstrapped env which could be one or shared
if (env) {
for (const [key, value] of TYPES) {
if (key === type) {
configs.delete(`${value}-${env}`);
envs.delete(`${value}-${env}`);
break;
}
}
}
// lonley script without setup node should be replaced
if (script.xworker) replaceScript(script, type);
// all scripts sharing the same env should be replaced
else {
const sel = `script[type^="${type}-editor"][env="${env}"]`;
for (const script of document.querySelectorAll(sel))
replaceScript(script, type);
}
const editor = editors.get(script);
const content = editor.state.doc.toString();
const clone = script.cloneNode(true);
clone.type = `${type}-editor`;
clone.textContent = content;
script.xworker.terminate();
script.nextElementSibling.remove();
script.replaceWith(clone);
editors.delete(script);
}
return;
}
@@ -388,7 +356,7 @@ const init = async (script, type, interpreter) => {
};
if (isSetup) {
await context.handleEvent({ currentTarget: null, script });
await context.handleEvent({ currentTarget: null });
notifyEditor();
return;
}
@@ -426,17 +394,16 @@ const init = async (script, type, interpreter) => {
// preserve user indentation, if any
const indentation = /^([ \t]+)/m.test(doc) ? RegExp.$1 : " ";
const listener = () => !runButton.click();
const listener = () => runButton.click();
const editor = new EditorView({
extensions: [
indentUnit.of(indentation),
new Compartment().of(python()),
keymap.of([
...defaultKeymap,
{ key: "Ctrl-Enter", run: listener, preventDefault: true },
{ key: "Cmd-Enter", run: listener, preventDefault: true },
{ key: "Shift-Enter", run: listener, preventDefault: true },
// Consider removing defaultKeymap as likely redundant with basicSetup
...defaultKeymap,
// @see https://codemirror.net/examples/tab/
indentWithTab,
]),

View File

@@ -1,5 +1,4 @@
// PyScript pyodide terminal plugin
import withResolvers from "@webreflection/utils/with-resolvers";
import { defineProperties } from "polyscript/exports";
import { hooks, inputFailure } from "../../core.js";
@@ -147,7 +146,7 @@ export default async (element) => {
// frees the worker on \r
sync.pyterminal_read = (buffer) => {
terminal.write(buffer);
promisedChunks = withResolvers();
promisedChunks = Promise.withResolvers();
return promisedChunks.promise;
};

View File

@@ -1 +0,0 @@
import "polyscript/service-worker";

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,6 @@ import io
import re
from pyscript.magic_js import current_target, document, window
from pyscript.ffi import is_none
_MIME_METHODS = {
"savefig": "image/png",
@@ -106,13 +105,13 @@ def _format_mime(obj):
else:
output = _eval_formatter(obj, method)
if is_none(output):
if output is None:
continue
if mime_type not in _MIME_RENDERERS:
not_available.append(mime_type)
continue
break
if is_none(output):
if output is None:
if not_available:
window.console.warn(
f"Rendered object requested unavailable MIME renderers: {not_available}"
@@ -136,7 +135,7 @@ def _write(element, value, append=False):
element.append(out_element)
else:
out_element = element.lastElementChild
if is_none(out_element):
if out_element is None:
out_element = element
if mime_type in ("application/javascript", "text/html"):
@@ -147,7 +146,7 @@ def _write(element, value, append=False):
def display(*values, target=None, append=True):
if is_none(target):
if target is None:
target = current_target()
elif not isinstance(target, str):
msg = f"target must be str or None, not {target.__class__.__name__}"
@@ -163,7 +162,7 @@ def display(*values, target=None, append=True):
element = document.getElementById(target)
# If target cannot be found on the page, a ValueError is raised
if is_none(element):
if element is None:
msg = f"Invalid selector with id={target}. Cannot be found in the page."
raise ValueError(msg)

View File

@@ -92,6 +92,40 @@ def when(target, *args, **kwargs):
elements = selector if isinstance(selector, list) else [selector]
def decorator(func):
if config["type"] == "mpy": # Is MicroPython?
if is_awaitable(func):
async def wrapper(*args, **kwargs):
"""
This is a very ugly hack to get micropython working because
`inspect.signature` doesn't exist. It may be actually better
to not try any magic for now and raise the error.
"""
try:
return await func(*args, **kwargs)
except TypeError as e:
if "takes" in str(e) and "positional arguments" in str(e):
return await func()
raise
else:
def wrapper(*args, **kwargs):
"""
This is a very ugly hack to get micropython working because
`inspect.signature` doesn't exist. It may be actually better
to not try any magic for now and raise the error.
"""
try:
return func(*args, **kwargs)
except TypeError as e:
if "takes" in str(e) and "positional arguments" in str(e):
return func()
raise
else:
sig = inspect.signature(func)
if sig.parameters:
if is_awaitable(func):

View File

@@ -2,10 +2,8 @@ try:
import js
from pyodide.ffi import create_proxy as _cp
from pyodide.ffi import to_js as _py_tjs
from pyodide.ffi import jsnull
from_entries = js.Object.fromEntries
is_none = lambda value: value is None or value is jsnull
def _tjs(value, **kw):
if not hasattr(kw, "dict_converter"):
@@ -15,34 +13,6 @@ try:
except:
from jsffi import create_proxy as _cp
from jsffi import to_js as _tjs
import js
jsnull = js.Object.getPrototypeOf(js.Object.prototype)
is_none = lambda value: value is None or value is jsnull
create_proxy = _cp
to_js = _tjs
try:
from polyscript import ffi as _ffi
direct = _ffi.direct
gather = _ffi.gather
query = _ffi.query
def assign(source, *args):
for arg in args:
_ffi.assign(source, to_js(arg))
return source
except:
import js
_assign = js.Object.assign
direct = lambda source: source
def assign(source, *args):
for arg in args:
_assign(source, to_js(arg))
return source

View File

@@ -1,13 +1,6 @@
mounted = {}
async def get_handler(details):
handler = details.handler
options = details.options
permission = await handler.queryPermission(options)
return handler if permission == "granted" else None
async def mount(path, mode="readwrite", root="", id="pyscript"):
import js
from _pyscript import fs, interpreter
@@ -19,7 +12,6 @@ async def mount(path, mode="readwrite", root="", id="pyscript"):
js.console.warn("experimental pyscript.fs ⚠️")
details = None
handler = None
uid = f"{path}@{id}"
@@ -39,16 +31,9 @@ async def mount(path, mode="readwrite", root="", id="pyscript"):
if success:
from polyscript import IDBMap
from pyscript import window
idbm = IDBMap.new(fs.NAMESPACE)
details = await idbm.get(uid)
handler = await get_handler(details)
if handler is None:
# force await in either async or sync scenario
await js.Promise.resolve(sync.getFSHandler(details.options))
handler = details.handler
idb = IDBMap.new(fs.NAMESPACE)
handler = await idb.get(uid)
else:
raise RuntimeError(fs.ERROR)
@@ -56,41 +41,14 @@ async def mount(path, mode="readwrite", root="", id="pyscript"):
success = await fs.idb.has(uid)
if success:
details = await fs.idb.get(uid)
handler = await get_handler(details)
if handler is None:
handler = await fs.getFileSystemDirectoryHandle(details.options)
handler = await fs.idb.get(uid)
else:
js_options = to_js(options)
handler = await fs.getFileSystemDirectoryHandle(js_options)
details = {"handler": handler, "options": js_options}
await fs.idb.set(uid, to_js(details))
handler = await fs.getFileSystemDirectoryHandle(to_js(options))
await fs.idb.set(uid, handler)
mounted[path] = await interpreter.mountNativeFS(path, handler)
async def revoke(path, id="pyscript"):
from _pyscript import fs, interpreter
from pyscript.magic_js import (
RUNNING_IN_WORKER,
sync,
)
uid = f"{path}@{id}"
if RUNNING_IN_WORKER:
had = sync.deleteFSHandler(uid)
else:
had = await fs.idb.has(uid)
if had:
had = await fs.idb.delete(uid)
if had:
interpreter._module.FS.unmount(path)
return had
async def sync(path):
await mounted[path].syncfs()

View File

@@ -10,9 +10,6 @@ RUNNING_IN_WORKER = not hasattr(globalThis, "document")
config = json.loads(globalThis.JSON.stringify(_config))
if isinstance(config, str):
config = {}
if "MicroPython" in sys.version:
config["type"] = "mpy"
else:
@@ -70,11 +67,7 @@ if RUNNING_IN_WORKER:
else:
import _pyscript
from _pyscript import PyWorker as _PyWorker, js_import
from pyscript.ffi import to_js
def PyWorker(url, **kw):
return _PyWorker(url, to_js(kw))
from _pyscript import PyWorker, js_import
window = globalThis
document = globalThis.document

View File

@@ -1,12 +1,11 @@
from polyscript import storage as _storage
from pyscript.flatted import parse as _parse
from pyscript.flatted import stringify as _stringify
from pyscript.ffi import is_none
# convert a Python value into an IndexedDB compatible entry
def _to_idb(value):
if is_none(value):
if value is None:
return _stringify(["null", 0])
if isinstance(value, (bool, float, int, str, list, dict, tuple)):
return _stringify(["generic", value])

View File

@@ -6,7 +6,7 @@
# from __future__ import annotations # CAUTION: This is not supported in MicroPython.
from pyscript import document, when, Event # noqa: F401
from pyscript.ffi import create_proxy, is_none
from pyscript.ffi import create_proxy
def wrap_dom_element(dom_element):
@@ -68,10 +68,8 @@ class Element:
If `dom_element` is None we are being called to *create* a new element.
Otherwise, we are being called to *wrap* an existing DOM element.
"""
self._dom_element = (
document.createElement(type(self).get_tag_name())
if is_none(dom_element)
else dom_element
self._dom_element = dom_element or document.createElement(
type(self).get_tag_name()
)
# HTML on_events attached to the element become pyscript.Event instances.
@@ -126,11 +124,6 @@ class Element:
# Element instance via `for_`).
if name.endswith("_"):
name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
if name == "for":
# The `for` attribute is a special case as it is a keyword in both
# Python and JavaScript.
# We need to get it from the underlying DOM element as `htmlFor`.
name = "htmlFor"
return getattr(self._dom_element, name)
def __setattr__(self, name, value):
@@ -149,11 +142,6 @@ class Element:
# Element instance via `for_`).
if name.endswith("_"):
name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
if name == "for":
# The `for` attribute is a special case as it is a keyword in both
# Python and JavaScript.
# We need to set it on the underlying DOM element as `htmlFor`.
name = "htmlFor"
if name.startswith("on_"):
# Ensure on-events are cached in the _on_events dict if the
@@ -197,7 +185,7 @@ class Element:
@property
def parent(self):
"""Return the element's `parent `Element`."""
if is_none(self._dom_element.parentElement):
if self._dom_element.parentElement is None:
return None
return Element.wrap_dom_element(self._dom_element.parentElement)
@@ -1136,7 +1124,7 @@ class video(ContainerElement):
width = width if width is not None else self.videoWidth
height = height if height is not None else self.videoHeight
if is_none(to):
if to is None:
to = canvas(width=width, height=height)
elif isinstance(to, Element):

View File

@@ -1,6 +1,6 @@
import js
from pyscript.ffi import create_proxy
from pyscript.util import as_bytearray, is_awaitable
from pyscript.util import as_bytearray
code = "code"
protocols = "protocols"
@@ -8,23 +8,6 @@ reason = "reason"
methods = ["onclose", "onerror", "onmessage", "onopen"]
def add_listener(socket, onevent, listener):
p = create_proxy(listener)
if is_awaitable(listener):
async def wrapper(e):
await p(EventMessage(e))
m = wrapper
else:
m = lambda e: p(EventMessage(e))
# Pyodide fails at setting socket[onevent] directly
setattr(socket, onevent, m)
class EventMessage:
def __init__(self, event):
self._event = event
@@ -53,20 +36,20 @@ class WebSocket:
socket = js.WebSocket.new(url, kw[protocols])
else:
socket = js.WebSocket.new(url)
socket.binaryType = "arraybuffer"
object.__setattr__(self, "_ws", socket)
for t in methods:
if t in kw:
add_listener(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)
def __setattr__(self, attr, value):
if attr in methods:
add_listener(self._ws, attr, value)
m = lambda e: value(EventMessage(e))
setattr(self._ws, attr, create_proxy(m))
else:
setattr(self._ws, attr, value)

View File

@@ -1,14 +1,14 @@
import { ArrayBuffer, TypedArray } from "sabayon/shared";
import IDBMapSync from "@webreflection/idb-map/sync";
import { parse, stringify } from "flatted";
const { isView } = ArrayBuffer;
const to_idb = (value) => {
if (value == null) return stringify(["null", 0]);
/* eslint-disable no-fallthrough */
switch (typeof value) {
case "object": {
if (isView(value)) return stringify(["memoryview", [...value]]);
if (value instanceof TypedArray)
return stringify(["memoryview", [...value]]);
if (value instanceof ArrayBuffer)
return stringify(["bytearray", [...new Uint8Array(value)]]);
}

View File

@@ -12,33 +12,20 @@ export default {
return new Promise(($) => setTimeout($, seconds * 1000));
},
getFSHandler: (options) => getFileSystemDirectoryHandle(options),
/**
* Ask a user action via dialog and returns the directory handler once granted.
* @param {string} uid
* @param {{id?:string, mode?:"read"|"readwrite", hint?:"desktop"|"documents"|"downloads"|"music"|"pictures"|"videos"}} options
* @returns {Promise<boolean>}
* @returns {boolean}
*/
async storeFSHandler(uid, options = {}) {
if (await idb.has(uid)) return true;
return getFileSystemDirectoryHandle(options).then(
async (handler) => {
await idb.set(uid, { handler, options });
await idb.set(uid, handler);
return true;
},
() => false,
);
},
/**
* Explicitly remove the unique identifier for the FS handler.
* @param {string} uid
* @returns {Promise<boolean>}
*/
async deleteFSHandler(uid) {
const had = await idb.has(uid);
if (had) await idb.delete(uid);
return had;
},
};

View File

@@ -1,7 +0,0 @@
/* eslint no-unused-vars: 0 */
try {
crypto.randomUUID();
} catch (_) {
if (location.href.startsWith("http://0.0.0.0"))
location.href = location.href.replace("0.0.0.0", "localhost");
}

File diff suppressed because one or more lines are too long

View File

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

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">
<link rel="stylesheet" href="../../../dist/core.css">
<script type="module" src="../../../dist/core.js"></script>
</head>
<body>
<py-config config-parser="https://esm.run/basic-toml">
[files]
'file.py' = ""
</py-config>
<script type="py">
import file
from pyscript import document
document.documentElement.classList.add("done")
document.body.append("OK")
</script>
</body>
</html>

View File

@@ -2,7 +2,7 @@ from pyscript import document
classList = document.documentElement.classList
if not __terminal__: # noqa: F821 __terminal__ is defined in core/src/plugins/donkey.js
if not __terminal__:
classList.add("error")
else:
classList.add("ok")

View File

@@ -1,3 +0,0 @@
{
"packages": ["numpy"]
}

View File

@@ -1,16 +0,0 @@
<!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 PyWorker
worker = PyWorker("./worker.py", type="pyodide", config="./config.json")
await worker.ready
</script>
</body>
</html>

View File

@@ -1,5 +0,0 @@
import numpy
from pyscript import document
document.body.append(numpy.__version__)
document.documentElement.classList.add("done")

View File

@@ -1,36 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyScript VS Symbols</title>
<script>
globalThis.hasSymbol = (symbol, ref) => symbol in ref;
globalThis.getSymbol = (symbol, ref) => ref[symbol];
// some 3rd party JS library might use symbols to brand-check
// so it's not about symbols traveling from MicroPython
// it's about MicroPython proxies traps not understanding symbols
globalThis.hasIterator = ref => Symbol.iterator in ref;
</script>
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
<script type="mpy">
import js
symbol = js.Symbol.iterator
if js.getSymbol(symbol, []) and js.hasSymbol(symbol, []) and js.hasIterator([]):
js.document.documentElement.classList.add("main")
</script>
<script type="mpy" worker>
from pyscript import window
import js
symbol = js.Symbol.iterator
if window.getSymbol(symbol, []) and window.hasSymbol(symbol, []) and window.hasIterator([]):
window.document.documentElement.classList.add("worker")
</script>
</head>
</html>

View File

@@ -14,7 +14,7 @@
print(event.type)
ws.send("hello")
async def onmessage(event):
def onmessage(event):
print(event.type, event.data)
ws.close()

View File

@@ -2,18 +2,6 @@ import { test, expect } from '@playwright/test';
test.setTimeout(120 * 1000);
test('Worker config as relative URL', async ({ page }) => {
await page.goto('http://localhost:8080/tests/javascript/worker-config/index.html');
await page.waitForSelector('html.done');
});
test('config-parser custom TOML', async ({ page }) => {
await page.goto('http://localhost:8080/tests/javascript/config-parser/index.html');
await page.waitForSelector('html.done');
const body = await page.evaluate(() => document.body.innerText);
await expect(body.trim()).toBe('OK');
});
test('MicroPython display', async ({ page }) => {
await page.goto('http://localhost:8080/tests/javascript/mpy.html');
await page.waitForSelector('html.done.worker');
@@ -71,11 +59,6 @@ test('MicroPython + configURL', async ({ page }) => {
await page.waitForSelector('html.main.worker');
});
test('MicroPython + Symbols', async ({ page }) => {
await page.goto('http://localhost:8080/tests/javascript/worker-symbols.html');
await page.waitForSelector('html.main.worker');
});
test('Pyodide + terminal on Main', async ({ page }) => {
await page.goto('http://localhost:8080/tests/javascript/py-terminal-main.html');
await page.waitForSelector('html.ok');

View File

@@ -1,21 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../../dist/core.css">
<script>
window.Worker = class extends Worker {
constructor(url, ...rest) {
console.log(rest[0]);
return super(url, ...rest);
}
};
window.start = Date.now();
</script>
<script type="module" src="../../../dist/core.js"></script>
</head>
<body>
<script type="py" config="./index.toml" src="index.py" worker></script>
</body>
</html>

View File

@@ -1,3 +0,0 @@
from pyscript import document, window
document.body.append(window.Date.now() - window.start)

View File

@@ -1,2 +0,0 @@
experimental_ffi_timeout = 0
package_cache = "passthrough"

View File

@@ -19,8 +19,6 @@ if TEST == "implicit":
await fs.sync("/persistent")
# await fs.revoke("/persistent")
elif not RUNNING_IN_WORKER:
from pyscript import document
@@ -41,7 +39,7 @@ elif not RUNNING_IN_WORKER:
js.alert("unable to grant access")
async def unmount(event):
await fs.revoke("/persistent")
await fs.unmount("/persistent")
button.textContent = "mount"
button.onclick = mount

View File

@@ -318,12 +318,16 @@ async def main(winstyle=0):
if not fullscreen:
print("Changing to FULLSCREEN")
screen_backup = screen.copy()
screen = pygame.display.set_mode(SCREENRECT.size, winstyle | pygame.FULLSCREEN)
screen = pygame.display.set_mode(
SCREENRECT.size, winstyle | pygame.FULLSCREEN, bestdepth
)
screen.blit(screen_backup, (0, 0))
else:
print("Changing to windowed mode")
screen_backup = screen.copy()
screen = pygame.display.set_mode(SCREENRECT.size, winstyle)
screen = pygame.display.set_mode(
SCREENRECT.size, winstyle, bestdepth
)
screen.blit(screen_backup, (0, 0))
pygame.display.flip()
fullscreen = not fullscreen

View File

@@ -140,7 +140,7 @@ def get_stats_gl(renderer):
def bg_from_v(*vertices):
geometry = new(THREE.BufferGeometry)
vertices_f32a = new(Float32Array, vertices) # noqa: F821 Float32Array is defined in js
vertices_f32a = new(Float32Array, vertices)
attr = new(THREE.Float32BufferAttribute, vertices_f32a, 3)
return geometry.setAttribute('position', attr)

View File

@@ -10,7 +10,7 @@ from pyscript import document, window, PyWorker
from libthree import THREE, clear, SoundPlayer
from libthree import get_renderer, get_ortho_camera
from libthree import get_loading_manager, get_stats_gl
from libthree import line2, linemat, lsgeo
from libthree import lsgeo, line2, linemat, lsgeo
from libfft import BeatSync
from multipyjs import MICROPYTHON, new, call, to_js, create_proxy

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>
<script type="py-editor">

View File

@@ -1,34 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>PyScript Custom Package</title>
<style>
body {
font-family: system-ui, sans-serif;
}
</style>
<script>
const track = ({ detail }) => {
if (/^(Loading|Loaded)/.test(detail)) {
const state = RegExp.$1;
const what = detail.slice(state.length + 1);
if (state === 'Loading') console.time(what);
else console.timeEnd(what);
}
};
addEventListener("py:progress", track);
addEventListener("mpy:progress", track);
</script>
<!-- PyScript (locally) -->
<link rel="stylesheet" href="../../../dist/core.css">
<script type="module" src="../../../dist/core.js"></script>
</head>
<body>
<div>loading ...</div>
<script type="py" config="./package.toml">
import custom_package
</script>
</body>
</html>

View File

@@ -1,3 +0,0 @@
experimental_remote_packages = true
packages = ['https://webreflection.github.io/examples/pyscript/module/package.toml']

View File

@@ -24,6 +24,5 @@
"./example_js_worker_module.js": "greeting_worker"
}
},
"packages": ["Pillow" ],
"experimental_ffi_timeout": 0
"packages": ["Pillow" ]
}

View File

@@ -358,79 +358,3 @@ def test_when_called_with_an_event_and_handler():
# The function should have been called when the whenable object was
# triggered.
assert counter == 1
def test_when_on_different_callables():
"""
The when function works with:
* Synchronous functions
* Asynchronous functions
* Inner functions
* Async inner functions
* Closure functions
* Async closure functions
"""
def func(x=1):
# A simple function.
return x
async def a_func(x=1):
# A simple async function.
return x
def make_inner_func():
# Creates a simple inner function.
def inner_func(x=1):
return x
return inner_func
def make_inner_a_func():
# Creates a simple async inner function.
async def inner_a_func(x=1):
return x
return inner_a_func
def make_closure():
# Creates a simple closure function.
a = 1
def closure_func(x=1):
return a + x
return closure_func
def make_a_closure():
# Creates a simple async closure function.
a = 1
async def closure_a_func(x=1):
return a + x
return closure_a_func
inner_func = make_inner_func()
inner_a_func = make_inner_a_func()
cl_func = make_closure()
cl_a_func = make_a_closure()
whenable = Event()
# Each of these should work with the when function.
when(whenable, func)
when(whenable, a_func)
when(whenable, inner_func)
when(whenable, inner_a_func)
when(whenable, cl_func)
when(whenable, cl_a_func)
# If we get here, everything worked.
assert True

View File

@@ -32,7 +32,7 @@ def test_js_module_is_available_on_worker():
@upytest.skip("Worker only.", skip_when=not RUNNING_IN_WORKER)
def test_js_module_is_available_on_greeting_worker():
def test_js_module_is_available_on_worker():
"""
The "hello" function in the example_js_worker_module.js file is available
via the js_modules object while running in a worker.

View File

@@ -7,10 +7,6 @@ import upytest
from pyscript import media
@upytest.skip(
"Uses Pyodide-specific to_js function in MicroPython",
skip_when=upytest.is_micropython,
)
async def test_device_enumeration():
"""Test enumerating media devices."""
devices = await media.list_devices()

View File

@@ -11,7 +11,7 @@ def test_as_bytearray():
msg = b"Hello, world!"
buffer = js.ArrayBuffer.new(len(msg))
ui8a = js.Uint8Array.new(buffer)
for i, b in enumerate(msg):
for b in msg:
ui8a[i] = b
ba = util.as_bytearray(buffer)
assert isinstance(ba, bytearray)

View File

@@ -871,17 +871,7 @@ class TestElements:
self._create_el_and_basic_asserts("kbd", "some text")
def test_label(self):
label_text = "Luke, I am your father"
label_for = "some-id"
# Let's create the element
el = web.label(label_text, for_=label_for)
# Let's check the element was configured correctly.
assert isinstance(el, web.label), "The new element should be a label."
assert el.textContent == label_text, "The label text should match."
assert el._dom_element.tagName == "LABEL"
assert el.for_ == label_for, "The label should have the correct for attribute."
# Ensure the label element is rendered with the correct "for" attribute
assert f'for="{label_for}"' in el.outerHTML, "The label should have the correct 'for' attribute in its HTML."
self._create_el_and_basic_asserts("label", "some text")
def test_legend(self):
self._create_el_and_basic_asserts("legend", "some text")

View File

@@ -63,5 +63,5 @@ declare const exportedHooks: {
};
};
declare const exportedConfig: {};
declare const exportedWhenDefined: (type: string) => Promise<object>;
declare const exportedWhenDefined: any;
export { codemirror, stdlib, optional, inputFailure, TYPES, relative_url, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };

10
core/types/sync.d.ts vendored
View File

@@ -9,18 +9,12 @@ declare namespace _default {
* Ask a user action via dialog and returns the directory handler once granted.
* @param {string} uid
* @param {{id?:string, mode?:"read"|"readwrite", hint?:"desktop"|"documents"|"downloads"|"music"|"pictures"|"videos"}} options
* @returns {Promise<boolean>}
* @returns {boolean}
*/
function storeFSHandler(uid: string, options?: {
id?: string;
mode?: "read" | "readwrite";
hint?: "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos";
}): Promise<boolean>;
/**
* Explicitly remove the unique identifier for the FS handler.
* @param {string} uid
* @returns {Promise<boolean>}
*/
function deleteFSHandler(uid: string): Promise<boolean>;
}): boolean;
}
export default _default;

View File

@@ -1 +0,0 @@
export {};

View File

@@ -84,10 +84,6 @@
<li><a href="core.css">core.css</a></li>
<li><a href="core.js">core.js</a></li>
<li><a href="core.js.map">core.js.map</a></li>
<li>
<a href="offline.zip">offline.zip</a> (PyScript _VERSION_
zipped up for offline use)
</li>
</ul>
<div id="out"></div>
<script type="py">
@@ -141,14 +137,12 @@
&lt;body&gt;
&lt;h1&gt;Hello world!&lt;/h1&gt;
&lt;p&gt;These are the Python interpreters in PyScript _VERSION_:&lt;/p&gt;
&lt;script type=&quot;py&quot;&gt;
# Pyodide
&lt;script type=&quot;py&quot;&gt; &lt;!-- Pyodide --&gt;
from pyscript import display
import sys
display(sys.version)
&lt;/script&gt;
&lt;script type=&quot;mpy&quot;&gt;
# MicroPython
&lt;script type=&quot;mpy&quot;&gt; &lt;!-- MicroPython --&gt;
from pyscript import display
import sys
display(sys.version)

View File

@@ -5,5 +5,5 @@ skip = "*.js,*.json"
[tool.ruff]
line-length = 114
lint.select = ["C4", "C90", "E", "EM", "F", "PIE", "PYI", "PLC", "Q", "RET", "W"]
lint.ignore = ["E402", "E722", "E731", "E741", "F401", "F704", "PLC0415"]
lint.ignore = ["E402", "E722", "E731", "E741", "F401", "F704", "F811", "F821"]
lint.mccabe.max-complexity = 27