mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Compare commits
58 Commits
classic
...
fpliger/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20e8c00f79 | ||
|
|
b1aa4d345b | ||
|
|
6f516ed4d1 | ||
|
|
04d117c12d | ||
|
|
579e7ab87a | ||
|
|
10e497c753 | ||
|
|
3b46609614 | ||
|
|
320ca306bd | ||
|
|
e087deef09 | ||
|
|
c3bac976c8 | ||
|
|
4c3e5fabb9 | ||
|
|
e48e6276e1 | ||
|
|
75a57a49f5 | ||
|
|
8a1db288fc | ||
|
|
84dcde188b | ||
|
|
27c91e9703 | ||
|
|
b5a0cd4057 | ||
|
|
77d8fe3562 | ||
|
|
a484aff457 | ||
|
|
c96f5912df | ||
|
|
8a01a56e51 | ||
|
|
2774e49ab9 | ||
|
|
26e7a54f1f | ||
|
|
f0e69cbc36 | ||
|
|
413428f535 | ||
|
|
0c54036466 | ||
|
|
2555833831 | ||
|
|
7e0aceced1 | ||
|
|
77234f6df3 | ||
|
|
45af96aad4 | ||
|
|
184d29055e | ||
|
|
9e73181816 | ||
|
|
0b0e03456c | ||
|
|
c6b5ce7f55 | ||
|
|
a14e701be4 | ||
|
|
7813c3f03f | ||
|
|
3a3cb7b11d | ||
|
|
d7b0731385 | ||
|
|
df8973736f | ||
|
|
9121071ba3 | ||
|
|
bf6470c046 | ||
|
|
3b7099cd3d | ||
|
|
f6dfc5361e | ||
|
|
0a7e1ce0d7 | ||
|
|
d6b1c393f6 | ||
|
|
bccd5e3750 | ||
|
|
6df5905b2b | ||
|
|
6284c02032 | ||
|
|
db27d52352 | ||
|
|
8ba28989fb | ||
|
|
da544929ac | ||
|
|
bb364b0524 | ||
|
|
818614b798 | ||
|
|
50b1a1d7c5 | ||
|
|
7d3b792a79 | ||
|
|
af72e232c3 | ||
|
|
0cdbfbeb30 | ||
|
|
339e40063a |
74
.github/workflows/test-next.yml
vendored
Normal file
74
.github/workflows/test-next.yml
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
name: "[CI] Test Next"
|
||||
|
||||
on:
|
||||
push: # Only run on merges into main that modify files under pyscriptjs/ and examples/
|
||||
branches:
|
||||
- next
|
||||
paths:
|
||||
- pyscript.core/**
|
||||
- .github/workflows/test-next.yml # Test that workflow works when changed
|
||||
|
||||
pull_request: # Run on any PR that modifies files under pyscriptjs/ and examples/
|
||||
branches:
|
||||
- next
|
||||
paths:
|
||||
- pyscript.core/**
|
||||
- .github/workflows/test-next.yml # Test that workflow works when changed
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
TestNext:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: pyscript.core
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
# TODO: this will likely change soon to pyscript.next
|
||||
# - name: install next deps
|
||||
# working-directory: pyscript.core
|
||||
# run: npm i; npx playwright install
|
||||
|
||||
# - name: build next
|
||||
# working-directory: pyscript.core
|
||||
# run: npm run build
|
||||
|
||||
# - name: Run next tests
|
||||
# working-directory: pyscript.core
|
||||
# run: npm run test
|
||||
|
||||
# TODO: DO we want to upload next yet?
|
||||
# - uses: actions/upload-artifact@v3
|
||||
# with:
|
||||
# name: pyscript
|
||||
# path: |
|
||||
# pyscriptjs/build/
|
||||
# if-no-files-found: error
|
||||
# retention-days: 7
|
||||
|
||||
# - uses: actions/upload-artifact@v3
|
||||
# if: success() || failure()
|
||||
# with:
|
||||
# name: test_results
|
||||
# path: pyscriptjs/test_results
|
||||
# if-no-files-found: error
|
||||
@@ -14,19 +14,20 @@ repos:
|
||||
- id: check-docstring-first
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-json
|
||||
exclude: tsconfig.json
|
||||
exclude: tsconfig\.json
|
||||
- id: check-toml
|
||||
- id: check-xml
|
||||
- id: check-yaml
|
||||
- id: detect-private-key
|
||||
- id: end-of-file-fixer
|
||||
exclude: \.min\.js$
|
||||
exclude: pyscript\.core/core.*|\.min\.js$
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.257
|
||||
hooks:
|
||||
- id: ruff
|
||||
exclude: pyscript\.core/test|pyscript\.core/src/display.py
|
||||
args: [--fix]
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
@@ -38,6 +39,7 @@ repos:
|
||||
rev: v2.2.4
|
||||
hooks:
|
||||
- id: codespell # See 'pyproject.toml' for args
|
||||
exclude: \.js\.map$
|
||||
additional_dependencies:
|
||||
- tomli
|
||||
|
||||
@@ -45,6 +47,7 @@ repos:
|
||||
rev: "v3.0.0-alpha.6"
|
||||
hooks:
|
||||
- id: prettier
|
||||
exclude: pyscript\.core/test|pyscript\.core/core.*|pyscript\.core/types/|pyscript\.sw/
|
||||
args: [--tab-width, "4"]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
|
||||
@@ -8,7 +8,15 @@
|
||||
href="https://pyscript.net/latest/pyscript.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<style>
|
||||
py-script {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script
|
||||
type="module"
|
||||
src="https://esm.sh/@pyscript/core@latest/core.js"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar" style="background-color: #000000">
|
||||
@@ -33,6 +41,7 @@
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
from pyscript import display
|
||||
import altair as alt
|
||||
from vega_datasets import data
|
||||
|
||||
|
||||
@@ -32,8 +32,16 @@
|
||||
href="https://pyscript.net/latest/pyscript.css"
|
||||
/>
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<style>
|
||||
py-script {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script
|
||||
type="module"
|
||||
src="https://esm.sh/@pyscript/core@latest/core.js"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar" style="background-color: #000000">
|
||||
|
||||
@@ -7,8 +7,16 @@
|
||||
rel="stylesheet"
|
||||
href="https://pyscript.net/latest/pyscript.css"
|
||||
/>
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<style>
|
||||
py-script {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script
|
||||
type="module"
|
||||
src="https://esm.sh/@pyscript/core@latest/core.js"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar" style="background-color: #000000">
|
||||
@@ -34,6 +42,7 @@
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
from pyscript import display
|
||||
import folium
|
||||
import json
|
||||
import pandas as pd
|
||||
|
||||
@@ -12,7 +12,15 @@
|
||||
href="https://pyscript.net/latest/pyscript.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<style>
|
||||
py-script {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script
|
||||
type="module"
|
||||
src="https://esm.sh/@pyscript/core@latest/core.js"
|
||||
></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -36,6 +44,7 @@
|
||||
Hello world! <br />
|
||||
This is the current date and time, as computed by Python:
|
||||
<py-script>
|
||||
from pyscript import display
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
rel="stylesheet"
|
||||
href="https://pyscript.net/latest/pyscript.css"
|
||||
/>
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<script
|
||||
type="module"
|
||||
src="https://esm.sh/@pyscript/core@latest/core.js"
|
||||
></script>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
<script defer src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js"></script>
|
||||
<script defer src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/@holoviz/panel@0.14.1/dist/panel.min.js"></script>
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<style>py-script{display:none}</style>
|
||||
<script type="module" src="https://esm.sh/@pyscript/core@latest/core.js"></script>
|
||||
<py-config>
|
||||
packages = [
|
||||
"https://cdn.holoviz.org/panel/0.14.3/dist/wheels/bokeh-2.4.3-py3-none-any.whl",
|
||||
|
||||
@@ -12,8 +12,16 @@
|
||||
href="https://pyscript.net/latest/pyscript.css"
|
||||
/>
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<style>
|
||||
py-script {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script
|
||||
type="module"
|
||||
src="https://esm.sh/@pyscript/core@latest/core.js"
|
||||
></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -27,10 +35,10 @@
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div class="font-mono">
|
||||
start time: <label id="outputDiv"></label>
|
||||
start time: <label id="output1"></label>
|
||||
</div>
|
||||
<div id="outputDiv2" class="font-mono"></div>
|
||||
<div id="outputDiv3" class="font-mono"></div>
|
||||
<div id="output2" class="font-mono"></div>
|
||||
<div id="output3" class="font-mono"></div>
|
||||
|
||||
<py-tutor modules="utils.py">
|
||||
<py-config>
|
||||
@@ -40,12 +48,14 @@
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
</py-config>
|
||||
<py-script>
|
||||
<script type="py">
|
||||
import utils
|
||||
display(utils.now())
|
||||
</py-script>
|
||||
from pyscript import display
|
||||
display(utils.now(), target="output1")
|
||||
</script>
|
||||
|
||||
<py-script>
|
||||
from pyscript import display
|
||||
from utils import now
|
||||
import asyncio
|
||||
|
||||
@@ -53,15 +63,14 @@
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
output = now()
|
||||
Element("outputDiv2").write(output)
|
||||
display(output, target="output2")
|
||||
|
||||
out3 = Element("outputDiv3")
|
||||
if output[-1] in ["0", "4", "8"]:
|
||||
out3.write("It's espresso time!")
|
||||
display("It's espresso time!", target="output3")
|
||||
else:
|
||||
out3.clear()
|
||||
display("", target="output3")
|
||||
|
||||
pyscript.run_until_complete(foo())
|
||||
foo()
|
||||
</py-script>
|
||||
</py-tutor>
|
||||
</section>
|
||||
|
||||
@@ -12,8 +12,11 @@
|
||||
href="https://pyscript.net/latest/pyscript.css"
|
||||
/>
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<script
|
||||
type="module"
|
||||
src="https://esm.sh/@pyscript/core@latest/core.js"
|
||||
></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -26,14 +29,14 @@
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<py-tutor modules="./utils.py;./todo.py">
|
||||
<py-tutor modules="./todo.py">
|
||||
<py-config>
|
||||
plugins = [
|
||||
"https://pyscript.net/latest/plugins/python/py_tutor.py",
|
||||
"./py_list.py"
|
||||
]
|
||||
[[fetch]]
|
||||
files = ["./utils.py", "./todo.py"]
|
||||
files = ["./todo.py"]
|
||||
</py-config>
|
||||
|
||||
<py-script src="./todo.py"></py-script>
|
||||
@@ -57,7 +60,7 @@
|
||||
id="new-task-btn"
|
||||
class="py-button"
|
||||
type="submit"
|
||||
py-click="add_task()"
|
||||
py-click="add_task"
|
||||
>
|
||||
Add task
|
||||
</button>
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
from datetime import datetime as dt
|
||||
|
||||
from utils import add_class, remove_class
|
||||
from pyscript import document
|
||||
|
||||
tasks = []
|
||||
|
||||
|
||||
def q(selector, root=document):
|
||||
return root.querySelector(selector)
|
||||
|
||||
|
||||
# define the task template that will be use to render new templates to the page
|
||||
task_template = Element("task-template").select(".task", from_content=True)
|
||||
task_list = Element("list-tasks-container")
|
||||
new_task_content = Element("new-task-content")
|
||||
task_template = q("#task-template").content.querySelector(".task")
|
||||
task_list = q("#list-tasks-container")
|
||||
new_task_content = q("#new-task-content")
|
||||
|
||||
|
||||
def add_task(*args, **kws):
|
||||
def add_task(e):
|
||||
# ignore empty task
|
||||
if not new_task_content.element.value:
|
||||
if not new_task_content.value:
|
||||
return None
|
||||
|
||||
# create task
|
||||
task_id = f"task-{len(tasks)}"
|
||||
task = {
|
||||
"id": task_id,
|
||||
"content": new_task_content.element.value,
|
||||
"content": new_task_content.value,
|
||||
"done": False,
|
||||
"created_at": dt.now(),
|
||||
}
|
||||
@@ -28,26 +33,24 @@ def add_task(*args, **kws):
|
||||
|
||||
# add the task element to the page as new node in the list by cloning from a
|
||||
# template
|
||||
task_html = task_template.clone(task_id)
|
||||
task_html_content = task_html.select("p")
|
||||
task_html_content.element.innerText = task["content"]
|
||||
task_html_check = task_html.select("input")
|
||||
task_list.element.appendChild(task_html.element)
|
||||
task_html = task_template.cloneNode(True)
|
||||
task_html.id = task_id
|
||||
task_html_check = q("input", root=task_html)
|
||||
task_html_content = q("p", root=task_html)
|
||||
task_html_content.textContent = task["content"]
|
||||
task_list.append(task_html)
|
||||
|
||||
def check_task(evt=None):
|
||||
task["done"] = not task["done"]
|
||||
if task["done"]:
|
||||
add_class(task_html_content, "line-through")
|
||||
else:
|
||||
remove_class(task_html_content, "line-through")
|
||||
task_html_content.classList.toggle("line-through", task["done"])
|
||||
|
||||
new_task_content.clear()
|
||||
task_html_check.element.onclick = check_task
|
||||
new_task_content.value = ""
|
||||
task_html_check.onclick = check_task
|
||||
|
||||
|
||||
def add_task_event(e):
|
||||
if e.key == "Enter":
|
||||
add_task()
|
||||
add_task(e)
|
||||
|
||||
|
||||
new_task_content.element.onkeypress = add_task_event
|
||||
new_task_content.onkeypress = add_task_event
|
||||
|
||||
@@ -10,8 +10,8 @@ def now(fmt="%m/%d/%Y, %H:%M:%S"):
|
||||
|
||||
|
||||
def remove_class(element, class_name):
|
||||
element.element.classList.remove(class_name)
|
||||
element.classList.remove(class_name)
|
||||
|
||||
|
||||
def add_class(element, class_name):
|
||||
element.element.classList.add(class_name)
|
||||
element.classList.add(class_name)
|
||||
|
||||
@@ -21,19 +21,26 @@
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.147.0/three.min.js"></script>
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://pyscript.net/latest/pyscript.css"
|
||||
/>
|
||||
<style>
|
||||
py-script {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script
|
||||
type="module"
|
||||
src="https://esm.sh/@pyscript/core@latest/core.js"
|
||||
></script>
|
||||
<py-script>
|
||||
from pyodide.ffi import create_proxy, to_js
|
||||
from js import window
|
||||
from pyscript import window, document
|
||||
from js import Math
|
||||
from js import THREE
|
||||
from js import performance
|
||||
from js import Object
|
||||
from js import document
|
||||
import asyncio
|
||||
|
||||
mouse = THREE.Vector2.new();
|
||||
|
||||
5
pyscript.core/.npmignore
Normal file
5
pyscript.core/.npmignore
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
rollup/
|
||||
test/
|
||||
package-lock.json
|
||||
tsconfig.json
|
||||
203
pyscript.core/LICENSE
Normal file
203
pyscript.core/LICENSE
Normal file
@@ -0,0 +1,203 @@
|
||||
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.
|
||||
3
pyscript.core/README.md
Normal file
3
pyscript.core/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# @pyscript/core
|
||||
|
||||
We have moved and renamed previous _core_ module as [polyscript](https://github.com/pyscript/polyscript/#readme), which is the base module used in here to build up _PyScript Next_, now hosted in this folder.
|
||||
1
pyscript.core/core.css
Normal file
1
pyscript.core/core.css
Normal file
@@ -0,0 +1 @@
|
||||
py-config,py-script{display:none}
|
||||
3
pyscript.core/core.js
Normal file
3
pyscript.core/core.js
Normal file
File diff suppressed because one or more lines are too long
254
pyscript.core/docs/README.md
Normal file
254
pyscript.core/docs/README.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# PyScript Next
|
||||
|
||||
<sup>A summary of <code>@pyscript/core</code> features</sup>
|
||||
|
||||
### Getting started
|
||||
|
||||
Differently from [pyscript classic](https://github.com/pyscript/pyscript), where "*classic*" is the disambiguation name we use to describe the two versions of the project, `@pyscript/core` is an *ECMAScript Module* with the follow benefits:
|
||||
|
||||
* it doesn't block the page like a regular script, without a `deferred` attribute, would
|
||||
* it allows modularity in the future
|
||||
* it bootstraps itself once but it allows exports via the module
|
||||
|
||||
Accordingly, this is the bare minimum required output to bootstrap *PyScript Next* in your page via a CDN:
|
||||
|
||||
```html
|
||||
<!-- Option 1: based on esm.sh which in turns is jsdlvr -->
|
||||
<script type="module" src="https://esm.sh/@pyscript/core@latest/core.js"></script>
|
||||
|
||||
<!-- Option 2: based on unpkg.com -->
|
||||
<script type="module" src="https://unpkg.com/@pyscript/core"></script>
|
||||
|
||||
<!-- Option X: any CDN that uses npmjs registry should work -->
|
||||
```
|
||||
|
||||
Once the module is loaded, any `<script type="py"></script>` on the page, or any `<py-script>` tag, would automatically run its own code or the file defined as `src` attribute, after bootstrapping the *pyodide* interpreter.
|
||||
|
||||
If no `<script type="py">` or `<py-script>` tag is present, it is still possible to use the module to bootstrap via JS a *Worker*, bypassing the need to bootstrap *pyodide* on the main thread, hence without ever blocking the page.
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { PyWorker } from "https://unpkg.com/@pyscript/core";
|
||||
|
||||
const worker = PyWorker("./code.py", { config: "./config.toml" /* optional */ });
|
||||
</script>
|
||||
```
|
||||
|
||||
#### CSS
|
||||
|
||||
If you are planning to use either `<py-config>` or `<py-script>` tags on the page, where latter case is usually better off with `<script type="py">` instead, you can also use CDNs to land our custom CSS:
|
||||
|
||||
```html
|
||||
<!-- Option 1: based on esm.sh which in turns is jsdlvr -->
|
||||
<link rel="stylesheet" href="https://esm.sh/@pyscript/core@latest/core.css">
|
||||
|
||||
<!-- Option 2: based on unpkg.com -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/@pyscript/core/css">
|
||||
|
||||
<!-- Option X: any CDN that uses npmjs registry should work -->
|
||||
```
|
||||
|
||||
The CSS is needed to avoid seeing content on the page before *PyScript* gets a chance to initialize itself. This means both `py-config` and `py-script` tags will have a `display:none` property which is overwritten by *PyScript* once it initialize each `py-script` custom element.
|
||||
|
||||
Once again, if you use `<script type="py">` instead, you won't need CSS unless you also have a `py-config` on the page, instead of using an external `config` file, defined via the `config` attribute:
|
||||
|
||||
```html
|
||||
<script type="py" config="./config.toml">
|
||||
from pyscript import display
|
||||
|
||||
display("Hello PyScript Next")
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
## Tag attributes API
|
||||
|
||||
Either `<script type="py">` or `<py-script>` can have zero, one or more attributes:
|
||||
|
||||
* **src** if defined, the content of the tag is ignored and the *Python* code in the file will be evaluated instead.
|
||||
* **config** if defined, the code will be evaluated after the configuration has been parsed but this can also be directly *JSON* so that both `config='{"packages":["numpy"]}'` and `config="./config.json"`, or `config="./config.toml"`, would be valid options.
|
||||
* **async** if present, it will run the *Python* code asynchronously.
|
||||
* **worker** if present, it will not bootstrap *pyodide* on the main page, only on the worker file it points at, as in `<script type="py" worker="./worker.py"></script>`. Both `async` and `config` attributes are also available and used to bootstrap the worker as desired.
|
||||
|
||||
Please note that other [polyscript's attributes](https://pyscript.github.io/polyscript/#script-attributes) are available too but their usage is more advanced.
|
||||
|
||||
|
||||
## JS Module API
|
||||
|
||||
The module itself is currently exporting the following utilities:
|
||||
|
||||
* **PyWorker**, which allows to bootstrap a *worker* with *pyodide* and the *pyscript* module available within the code. This callback accepts a file as argument, and an additional, and optional, `options` object literal, able to understand a `config`, which could also be directly a *JS* object literal instead of a JSON string or a file to point at, and `async` which if `true` will run the worker code with top level await enabled. Please note that the returned reference is exactly the same as [the polyscript's XWorker](https://pyscript.github.io/polyscript/#the-xworker-reference), exposing exact same utilities but granting on bootstrap all hooks are in place and the type is always *pyodide*.
|
||||
* **hooks**, which allows plugins to define *ASAP* callbacks or strings that should be executed either in the main thread or the worker before, or after, the code has been executed.
|
||||
|
||||
```js
|
||||
import { hooks } from "https://unpkg.com/@pyscript/core";
|
||||
|
||||
// example
|
||||
hooks.onInterpreterReady.add((utils, element) => {
|
||||
console.lot(element, 'found', 'pyscript is ready');
|
||||
});
|
||||
|
||||
// the hooks namespace
|
||||
({
|
||||
// each function is invoked before or after python gets executed
|
||||
// via: callback(pyScriptUtils, currentElement)
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRun: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRunAync: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRun: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRunAsync: new Set(),
|
||||
|
||||
// each function is invoked once when PyScript is ready
|
||||
// and for each element via: callback(pyScriptUtils, currentElement)
|
||||
/** @type {Set<function>} */
|
||||
onInterpreterReady: new Set(),
|
||||
|
||||
// each string is prepended or appended to the worker code
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunWorker: new Set(),
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunWorkerAsync: new Set(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunWorker: new Set(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunWorkerAsync: new Set(),
|
||||
})
|
||||
```
|
||||
|
||||
Please note that a *worker* is a completely different environment and it's not possible, by specifications, to pass a callback to it, which is why worker sets are strings and not functions.
|
||||
|
||||
However, each worker string can use `from pyscript import x, y, z` as that will be available out of the box.
|
||||
|
||||
## PyScript Module API
|
||||
|
||||
The python module offers various utilities in either the main thread or the worker.
|
||||
|
||||
The commonly shared utilities are:
|
||||
|
||||
* **window** in both main and worker, refers to the actual main thread global window context. In classic PyScript that'd be the equivalent of `import js` in the main, which is still available in *PyScript Next*. However, to make code easily portable between main and workers, we decided to offer this named export but please note that in workers, this is still the *main* window, not the worker global context, which would be reachable instead still via `import js`.
|
||||
* **document** in both main and worker, refers to the actual main page `document`. In classic PyScript, this is the equivalent of `from js import document` on the main thread, but this won't ever work in a worker because there is no `document` in there. Fear not though, *PyScript Next* `document` will instead work out of the box, still accessing the main document behind the scene, so that `from pyscript import document` is granted to work in both main and workers seamlessly.
|
||||
* **display** in both main and worker, refers to the good old `display` utility except:
|
||||
* in the *main* it automatically uses the current script `target` to display content
|
||||
* in the *worker* it still needs to know *where* to display content using the `target="dom-id"` named argument, as workers don't get a default target attached
|
||||
* in both main and worker, the `append=Flase` is the *default* behavior, which is a breaking change compared to classic PyScript, but because there is no `Element` with its `write(content)` utility, which would have used that `append=False` behind the scene, we've decided that `false` as append default is more desired, specially after porting most examples in *PyScript Next*, where `append=True` is the exception, not the norm.
|
||||
|
||||
#### Extra main-only features
|
||||
|
||||
* **PyWorker** which allows Python code to create a PyScript worker with the *pyscript* module pre-bundled. Please note that running PyScript on the main requires *pyodide* bootstrap, but also every worker requires *pyodide* bootstrap a part, as each worker is an environment / sandbox a part. This means that using *PyWorker* in the main will take, even if the main interpreter is already up and running, a bit of time to bootstrap the worker, also accordingly to the config files or packages in it.
|
||||
|
||||
|
||||
#### Extra worker-only features
|
||||
|
||||
* **sync** which allows both main and the worker to seamlessly pass any serializable data around, without the need to convert Python dictionaries to JS object literals, as that's done automatically.
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { PyWorker } from "https://unpkg.com/@pyscript/core";
|
||||
|
||||
const worker = PyWorker("./worker.py");
|
||||
|
||||
worker.sync.alert_message = message => {
|
||||
alert(message);
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
```python
|
||||
from pyscript import sync
|
||||
|
||||
sync.alert_message("Hello Main!")
|
||||
```
|
||||
|
||||
### Worker requirements
|
||||
|
||||
To make it possible to use what looks like *synchronous* DOM APIs, or any other API available via the `window` within a *worker*, we are using latest Web features such as [Atomics](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics).
|
||||
|
||||
Without going into too many details, this means that the *SharedArrayBuffer* primitive must be available, and to do so, the server should enable the following headers:
|
||||
|
||||
```
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Resource-Policy: cross-origin
|
||||
```
|
||||
|
||||
These headers allow local files to be secured and yet able to load resources from the Web (i.e. pyodide library or its packages).
|
||||
|
||||
> ℹ️ **Careful**: we are using and testing these headers on both Desktop and Mobile to be sure all major browsers work as expected (Safari, Firefox, Chromium based browsers). If you change the value of these headers please be sure you test your target devices and browsers properly.
|
||||
|
||||
Please note that if you don't have control over your server's headers, it is possible to simply put [mini-coi](https://github.com/WebReflection/mini-coi#readme) script at the root of your *PyScript with Workers* enabled folder (site root, or any subfolder).
|
||||
|
||||
```sh
|
||||
cd project-folder
|
||||
|
||||
# grab mini-coi content and save it locally as mini-coi.js
|
||||
curl -Ls https://unpkg.com/mini-coi -o mini-coi.js
|
||||
```
|
||||
|
||||
With either these two solutions, it should be now possible to bootstrap a *PyScript Worker* without any issue.
|
||||
|
||||
#### mini-coi example
|
||||
```html
|
||||
<!doctype html>
|
||||
<script src="/mini-coi.js"></script>
|
||||
<script type="module">
|
||||
import { PyWorker } from "https://unpkg.com/@pyscript/core";
|
||||
PyWorker("./test.py");
|
||||
</script>
|
||||
<!-- ./test.py -->
|
||||
<!--
|
||||
from pyscript import document
|
||||
document.body.textContent = "Hello PyScript Worker"
|
||||
-->
|
||||
```
|
||||
|
||||
Please note that a local or remote web server is still needed to allow the Service Worker and `python -m http.server` would do locally, *except* we need to reach `http://localhost:8000/`, not `http://0.0.0.0:8000/`, because the browser does not consider safe non localhost sites when the insecure `http://` protocol, instead of `https://`, is reached.
|
||||
|
||||
|
||||
#### local server example
|
||||
If you'd like to test locally these headers, without needing the *mini-coi* Service Worker, you can use various projects or, if you have *NodeJS* available, simply run the following command in the folder containing the site/project:
|
||||
|
||||
```sh
|
||||
# bootstrap a local server with all headers needed
|
||||
npx static-handler --cors --coep --coop --corp .
|
||||
```
|
||||
|
||||
|
||||
### F.A.Q.
|
||||
|
||||
<details>
|
||||
<summary><strong>why config attribute can also contain JSON but not TOML?</strong></summary>
|
||||
<div markdown=1>
|
||||
|
||||
The *JSON* standard doesn't require new lines or indentation so it felt quick and desired to allow inline JSON as attribute content.
|
||||
|
||||
It's true that HTML attributes can be multi-line too, if properly embedded, but that looked too awkward and definitively harder to explain to me.
|
||||
|
||||
We might decide to allow TOML too in the future, but the direct config as attribute, instead of a proper file, or the usage of `<py-config>`, is meant for quick and simple packages or files dependencies and not much else.
|
||||
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>why worker attribute needs an external file?</strong></summary>
|
||||
<div markdown=1>
|
||||
|
||||
It would create confusion to have worker code embedded directly in the page and let *PyScript* forward the content to be executed as worker, but the separation of concerns felt more aligned with the meaning of bootstrapping a worker: it inevitably happens elsewhere and with little caveats or features here and there, so it's OK for now to keep that separation explicit.
|
||||
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>what are the worker's caveats?</strong></summary>
|
||||
<div markdown=1>
|
||||
|
||||
When interacting with `window` or `document` it's important to understand that these use, behind the scene, an orchestrated [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) dance.
|
||||
|
||||
This means that some kind of data that cannot be passed around, specially not compatible with the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
|
||||
|
||||
In short, please try to stick with *JS* references when passing along, or dealing with, *DOM* or other *APIs*.
|
||||
|
||||
</div>
|
||||
</details>
|
||||
1888
pyscript.core/package-lock.json
generated
Normal file
1888
pyscript.core/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
55
pyscript.core/package.json
Normal file
55
pyscript.core/package.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.1.5",
|
||||
"type": "module",
|
||||
"description": "PyScript",
|
||||
"main": "./core.js",
|
||||
"module": "./core.js",
|
||||
"unpkg": "./core.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/esm/core.d.ts",
|
||||
"import": "./esm/core.js"
|
||||
},
|
||||
"./js": {
|
||||
"import": "./core.js"
|
||||
},
|
||||
"./css": {
|
||||
"import": "./core.css"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"server": "npx static-handler --cors --coep --coop --corp .",
|
||||
"build": "rollup --config rollup/core.config.js && rollup --config rollup/core-css.config.js && npm run ts",
|
||||
"ts": "tsc -p ."
|
||||
},
|
||||
"keywords": [
|
||||
"pyscript",
|
||||
"core"
|
||||
],
|
||||
"author": "Anaconda Inc.",
|
||||
"license": "APACHE-2.0",
|
||||
"dependencies": {
|
||||
"@ungap/with-resolvers": "^0.1.0",
|
||||
"basic-devtools": "^0.1.6",
|
||||
"polyscript": "^0.1.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.1.0",
|
||||
"@rollup/plugin-terser": "^0.4.3",
|
||||
"rollup": "^3.28.0",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
"static-handler": "^0.4.2",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
20
pyscript.core/rollup/core-css.config.js
Normal file
20
pyscript.core/rollup/core-css.config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import postcss from "rollup-plugin-postcss";
|
||||
|
||||
export default {
|
||||
input: "./src/core.css",
|
||||
plugins: [
|
||||
postcss({
|
||||
extract: true,
|
||||
sourceMap: false,
|
||||
minimize: !process.env.NO_MIN,
|
||||
plugins: [],
|
||||
}),
|
||||
],
|
||||
output: {
|
||||
file: "./core.css",
|
||||
},
|
||||
onwarn(warning, warn) {
|
||||
if (warning.code === "FILE_NAME_CONFLICT") return;
|
||||
warn(warning);
|
||||
},
|
||||
};
|
||||
25
pyscript.core/rollup/core.config.js
Normal file
25
pyscript.core/rollup/core.config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// This file generates /core.js minified version of the module, which is
|
||||
// the default exported as npm entry.
|
||||
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
import terser from "@rollup/plugin-terser";
|
||||
import { string } from "rollup-plugin-string";
|
||||
|
||||
const plugins = [
|
||||
string({
|
||||
// Required to be specified
|
||||
include: "**/*.py",
|
||||
}),
|
||||
];
|
||||
|
||||
export default {
|
||||
input: "./src/core.js",
|
||||
plugins: plugins.concat(
|
||||
process.env.NO_MIN ? [nodeResolve()] : [nodeResolve(), terser()],
|
||||
),
|
||||
output: {
|
||||
esModule: true,
|
||||
file: "./core.js",
|
||||
sourcemap: true,
|
||||
},
|
||||
};
|
||||
4
pyscript.core/src/core.css
Normal file
4
pyscript.core/src/core.css
Normal file
@@ -0,0 +1,4 @@
|
||||
py-script,
|
||||
py-config {
|
||||
display: none;
|
||||
}
|
||||
273
pyscript.core/src/core.js
Normal file
273
pyscript.core/src/core.js
Normal file
@@ -0,0 +1,273 @@
|
||||
import "@ungap/with-resolvers";
|
||||
import { $ } from "basic-devtools";
|
||||
import { define, XWorker } from "polyscript";
|
||||
|
||||
// TODO: this is not strictly polyscript related but handy ... not sure
|
||||
// we should factor this utility out a part but this works anyway.
|
||||
import { queryTarget } from "../node_modules/polyscript/esm/script-handler.js";
|
||||
import { Hook } from "../node_modules/polyscript/esm/worker/hooks.js";
|
||||
|
||||
import { robustFetch as fetch } from "./fetch.js";
|
||||
|
||||
const { defineProperty } = Object;
|
||||
|
||||
const getText = (body) => body.text();
|
||||
|
||||
// allows lazy element features on code evaluation
|
||||
let currentElement;
|
||||
|
||||
// create a unique identifier when/if needed
|
||||
let id = 0;
|
||||
const getID = (prefix = "py") => `${prefix}-${id++}`;
|
||||
|
||||
// find the shared config for all py-script elements
|
||||
let config;
|
||||
let pyConfig = $("py-config");
|
||||
if (pyConfig) config = pyConfig.getAttribute("src") || pyConfig.textContent;
|
||||
else {
|
||||
pyConfig = $('script[type="py"]');
|
||||
config = pyConfig?.getAttribute("config");
|
||||
}
|
||||
|
||||
if (/^https?:\/\//.test(config)) config = await fetch(config).then(getText);
|
||||
|
||||
// generic helper to disambiguate between custom element and script
|
||||
const isScript = (element) => element.tagName === "SCRIPT";
|
||||
|
||||
// helper for all script[type="py"] out there
|
||||
const before = (script) => {
|
||||
defineProperty(document, "currentScript", {
|
||||
configurable: true,
|
||||
get: () => script,
|
||||
});
|
||||
};
|
||||
|
||||
const after = () => {
|
||||
delete document.currentScript;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a generic DOM Element, tries to fetch the 'src' attribute, if present.
|
||||
* It either throws an error if the 'src' can't be fetched or it returns a fallback
|
||||
* content as source.
|
||||
*/
|
||||
const fetchSource = async (tag, io) => {
|
||||
if (tag.hasAttribute("src")) {
|
||||
try {
|
||||
return await fetch(tag.getAttribute("src")).then(getText);
|
||||
} catch (error) {
|
||||
io.stderr(error);
|
||||
}
|
||||
}
|
||||
return tag.textContent;
|
||||
};
|
||||
|
||||
// common life-cycle handlers for any node
|
||||
const bootstrapNodeAndPlugins = (pyodide, element, callback, hook) => {
|
||||
if (isScript(element)) callback(element);
|
||||
for (const fn of hooks[hook]) fn(pyodide, element);
|
||||
};
|
||||
|
||||
// these are imported as string (via rollup)
|
||||
import init_py from "./stdlib/_pyscript/__init__.py";
|
||||
import display_py from "./stdlib/_pyscript/_display.py";
|
||||
|
||||
const writeStdlib = (pyodide, element) => {
|
||||
console.log("writeStdlib!");
|
||||
const FS = pyodide.interpreter.FS;
|
||||
FS.mkdirTree("/home/pyodide/_pyscript");
|
||||
FS.writeFile("_pyscript/__init__.py", init_py, { encoding: "utf8" });
|
||||
FS.writeFile("_pyscript/_display.py", display_py, { encoding: "utf8" });
|
||||
};
|
||||
|
||||
const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
|
||||
// automatically use the pyscript stderr (when/if defined)
|
||||
// this defaults to console.error
|
||||
function PyWorker(...args) {
|
||||
const worker = $XWorker(...args);
|
||||
worker.onerror = ({ error }) => io.stderr(error);
|
||||
return worker;
|
||||
}
|
||||
// trap once the python `display` utility (borrowed from "classic PyScript")
|
||||
// provide the regular Pyodide globals instead of those from xworker
|
||||
// const pyDisplay = interpreter.runPython(
|
||||
// [
|
||||
// "import js",
|
||||
// "document=js.document",
|
||||
// "window=js",
|
||||
// display,
|
||||
// "display",
|
||||
// ].join("\n"),
|
||||
// );
|
||||
const pyDisplay = interpreter.runPython(`
|
||||
from _pyscript import display
|
||||
display
|
||||
`);
|
||||
|
||||
interpreter.registerJsModule("pyscript", {
|
||||
PyWorker,
|
||||
document,
|
||||
window,
|
||||
// a getter to ensure if multiple scripts with same
|
||||
// env (py) runs, their execution code will have the correct
|
||||
// display reference with automatic target
|
||||
get display() {
|
||||
const id = isScript(currentElement)
|
||||
? currentElement.target.id
|
||||
: currentElement.id;
|
||||
|
||||
return (...args) => {
|
||||
const last = args.at(-1);
|
||||
let kw = { target: id, append: false };
|
||||
if (
|
||||
typeof last === "object" &&
|
||||
last &&
|
||||
("target" in last || "append" in last)
|
||||
)
|
||||
kw = { ...kw, ...args.pop() };
|
||||
pyDisplay.callKwargs(...args, kw);
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const hooks = {
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRun: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRunAync: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRun: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRunAsync: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onInterpreterReady: new Set(),
|
||||
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunWorker: new Set(),
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunWorkerAsync: new Set(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunWorker: new Set(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunWorkerAsync: new Set(),
|
||||
};
|
||||
|
||||
// XXX antocuni: I think this is broken, because now _display.py imports
|
||||
// window and document directly from js
|
||||
const workerPyScriptModule = [
|
||||
"from pyodide_js import FS",
|
||||
`FS.writeFile('./pyscript.py', ${JSON.stringify(
|
||||
[
|
||||
"import polyscript",
|
||||
"document=polyscript.xworker.window.document",
|
||||
"window=polyscript.xworker.window",
|
||||
"sync=polyscript.xworker.sync",
|
||||
display_py,
|
||||
].join("\n"),
|
||||
)})`,
|
||||
].join("\n");
|
||||
|
||||
const workerHooks = {
|
||||
codeBeforeRunWorker: () =>
|
||||
[workerPyScriptModule, ...hooks.codeBeforeRunWorker].join("\n"),
|
||||
codeBeforeRunWorkerAsync: () =>
|
||||
[workerPyScriptModule, ...hooks.codeBeforeRunWorkerAsync].join("\n"),
|
||||
codeAfterRunWorker: () => [...hooks.codeAfterRunWorker].join("\n"),
|
||||
codeAfterRunWorkerAsync: () =>
|
||||
[...hooks.codeAfterRunWorkerAsync].join("\n"),
|
||||
};
|
||||
|
||||
// define the module as both `<script type="py">` and `<py-script>`
|
||||
define("py", {
|
||||
config,
|
||||
env: "py-script",
|
||||
interpreter: "pyodide",
|
||||
...workerHooks,
|
||||
onBeforeRun(pyodide, element) {
|
||||
currentElement = element;
|
||||
bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRun");
|
||||
},
|
||||
onBeforeRunAync(pyodide, element) {
|
||||
currentElement = element;
|
||||
bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRunAync");
|
||||
},
|
||||
onAfterRun(pyodide, element) {
|
||||
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRun");
|
||||
},
|
||||
onAfterRunAsync(pyodide, element) {
|
||||
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRunAsync");
|
||||
},
|
||||
async onInterpreterReady(pyodide, element) {
|
||||
console.log("onInterpreterReady");
|
||||
writeStdlib(pyodide, element);
|
||||
console.log("after writeStdlib");
|
||||
registerModule(pyodide, element);
|
||||
// allows plugins to do whatever they want with the element
|
||||
// before regular stuff happens in here
|
||||
for (const callback of hooks.onInterpreterReady)
|
||||
callback(pyodide, element);
|
||||
if (isScript(element)) {
|
||||
const {
|
||||
attributes: { async: isAsync, target },
|
||||
} = element;
|
||||
const hasTarget = !!target?.value;
|
||||
const show = hasTarget
|
||||
? queryTarget(target.value)
|
||||
: document.createElement("script-py");
|
||||
|
||||
if (!hasTarget) element.after(show);
|
||||
if (!show.id) show.id = getID();
|
||||
|
||||
// allows the code to retrieve the target element via
|
||||
// document.currentScript.target if needed
|
||||
defineProperty(element, "target", { value: show });
|
||||
|
||||
pyodide[`run${isAsync ? "Async" : ""}`](
|
||||
await fetchSource(element, pyodide.io),
|
||||
);
|
||||
} else {
|
||||
// resolve PyScriptElement to allow connectedCallback
|
||||
element._pyodide.resolve(pyodide);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
class PyScriptElement extends HTMLElement {
|
||||
constructor() {
|
||||
if (!super().id) this.id = getID();
|
||||
this._pyodide = Promise.withResolvers();
|
||||
this.srcCode = "";
|
||||
this.executed = false;
|
||||
}
|
||||
async connectedCallback() {
|
||||
if (!this.executed) {
|
||||
this.executed = true;
|
||||
const { io, run } = await this._pyodide.promise;
|
||||
this.srcCode = await fetchSource(this, io);
|
||||
this.textContent = "";
|
||||
run(this.srcCode);
|
||||
this.style.display = "block";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("py-script", PyScriptElement);
|
||||
|
||||
/**
|
||||
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
||||
* @param {string} file the python file to run ina worker.
|
||||
* @param {{config?: string | object, async?: boolean}} [options] optional configuration for the worker.
|
||||
* @returns {Worker & {sync: ProxyHandler<object>}}
|
||||
*/
|
||||
export function PyWorker(file, options) {
|
||||
// this propagates pyscript worker hooks without needing a pyscript
|
||||
// bootstrap + it passes arguments and enforces `pyodide`
|
||||
// as the interpreter to use in the worker, as all hooks assume that
|
||||
// and as `pyodide` is the only default interpreter that can deal with
|
||||
// all the features we need to deliver pyscript out there.
|
||||
return XWorker.call(new Hook(null, workerHooks), file, {
|
||||
...options,
|
||||
type: "pyodide",
|
||||
});
|
||||
}
|
||||
81
pyscript.core/src/exceptions.js
Normal file
81
pyscript.core/src/exceptions.js
Normal file
@@ -0,0 +1,81 @@
|
||||
const CLOSEBUTTON =
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='currentColor' width='12px'><path d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/></svg>";
|
||||
|
||||
/**
|
||||
* These error codes are used to identify the type of error that occurred.
|
||||
* @see https://docs.pyscript.net/latest/reference/exceptions.html?highlight=errors
|
||||
*/
|
||||
export const ErrorCode = {
|
||||
GENERIC: "PY0000", // Use this only for development then change to a more specific error code
|
||||
FETCH_ERROR: "PY0001",
|
||||
FETCH_NAME_ERROR: "PY0002",
|
||||
// Currently these are created depending on error code received from fetching
|
||||
FETCH_UNAUTHORIZED_ERROR: "PY0401",
|
||||
FETCH_FORBIDDEN_ERROR: "PY0403",
|
||||
FETCH_NOT_FOUND_ERROR: "PY0404",
|
||||
FETCH_SERVER_ERROR: "PY0500",
|
||||
FETCH_UNAVAILABLE_ERROR: "PY0503",
|
||||
BAD_CONFIG: "PY1000",
|
||||
MICROPIP_INSTALL_ERROR: "PY1001",
|
||||
BAD_PLUGIN_FILE_EXTENSION: "PY2000",
|
||||
NO_DEFAULT_EXPORT: "PY2001",
|
||||
TOP_LEVEL_AWAIT: "PY9000",
|
||||
};
|
||||
|
||||
export class UserError extends Error {
|
||||
constructor(errorCode, message = "", messageType = "text") {
|
||||
super(`(${errorCode}): ${message}`);
|
||||
this.errorCode = errorCode;
|
||||
this.messageType = messageType;
|
||||
this.name = "UserError";
|
||||
}
|
||||
}
|
||||
|
||||
export class FetchError extends UserError {
|
||||
constructor(errorCode, message) {
|
||||
super(errorCode, message);
|
||||
this.name = "FetchError";
|
||||
}
|
||||
}
|
||||
|
||||
export class InstallError extends UserError {
|
||||
constructor(errorCode, message) {
|
||||
super(errorCode, message);
|
||||
this.name = "InstallError";
|
||||
}
|
||||
}
|
||||
|
||||
export function _createAlertBanner(
|
||||
message,
|
||||
level,
|
||||
messageType = "text",
|
||||
logMessage = true,
|
||||
) {
|
||||
switch (`log-${level}-${logMessage}`) {
|
||||
case "log-error-true":
|
||||
console.error(message);
|
||||
break;
|
||||
case "log-warning-true":
|
||||
console.warn(message);
|
||||
break;
|
||||
}
|
||||
|
||||
const content = messageType === "html" ? "innerHTML" : "textContent";
|
||||
const banner = Object.assign(document.createElement("div"), {
|
||||
className: `alert-banner py-${level}`,
|
||||
[content]: message,
|
||||
});
|
||||
|
||||
if (level === "warning") {
|
||||
const closeButton = Object.assign(document.createElement("button"), {
|
||||
id: "alert-close-button",
|
||||
innerHTML: CLOSEBUTTON,
|
||||
});
|
||||
|
||||
banner.appendChild(closeButton).addEventListener("click", () => {
|
||||
banner.remove();
|
||||
});
|
||||
}
|
||||
|
||||
document.body.prepend(banner);
|
||||
}
|
||||
63
pyscript.core/src/fetch.js
Normal file
63
pyscript.core/src/fetch.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { FetchError, ErrorCode } from "./exceptions.js";
|
||||
|
||||
/**
|
||||
* This is a fetch wrapper that handles any non 200 responses and throws a
|
||||
* FetchError with the right ErrorCode. This is useful because our FetchError
|
||||
* will automatically create an alert banner.
|
||||
*
|
||||
* @param {string} url - URL to fetch
|
||||
* @param {Request} [options] - options to pass to fetch
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export async function robustFetch(url, options) {
|
||||
let response;
|
||||
|
||||
// Note: We need to wrap fetch into a try/catch block because fetch
|
||||
// throws a TypeError if the URL is invalid such as http://blah.blah
|
||||
try {
|
||||
response = await fetch(url, options);
|
||||
} catch (err) {
|
||||
const error = err;
|
||||
let errMsg;
|
||||
if (url.startsWith("http")) {
|
||||
errMsg =
|
||||
`Fetching from URL ${url} failed with error ` +
|
||||
`'${error.message}'. Are your filename and path correct?`;
|
||||
} else {
|
||||
errMsg = `Polyscript: Access to local files
|
||||
(using [[fetch]] configurations in <py-config>)
|
||||
is not available when directly opening a HTML file;
|
||||
you must use a webserver to serve the additional files.
|
||||
See <a style="text-decoration: underline;" href="https://github.com/pyscript/pyscript/issues/257#issuecomment-1119595062">this reference</a>
|
||||
on starting a simple webserver with Python.
|
||||
`;
|
||||
}
|
||||
throw new FetchError(ErrorCode.FETCH_ERROR, errMsg);
|
||||
}
|
||||
|
||||
// Note that response.ok is true for 200-299 responses
|
||||
if (!response.ok) {
|
||||
const errorMsg = `Fetching from URL ${url} failed with error ${response.status} (${response.statusText}). Are your filename and path correct?`;
|
||||
switch (response.status) {
|
||||
case 404:
|
||||
throw new FetchError(ErrorCode.FETCH_NOT_FOUND_ERROR, errorMsg);
|
||||
case 401:
|
||||
throw new FetchError(
|
||||
ErrorCode.FETCH_UNAUTHORIZED_ERROR,
|
||||
errorMsg,
|
||||
);
|
||||
case 403:
|
||||
throw new FetchError(ErrorCode.FETCH_FORBIDDEN_ERROR, errorMsg);
|
||||
case 500:
|
||||
throw new FetchError(ErrorCode.FETCH_SERVER_ERROR, errorMsg);
|
||||
case 503:
|
||||
throw new FetchError(
|
||||
ErrorCode.FETCH_UNAVAILABLE_ERROR,
|
||||
errorMsg,
|
||||
);
|
||||
default:
|
||||
throw new FetchError(ErrorCode.FETCH_ERROR, errorMsg);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
146
pyscript.core/src/python/pystdlib/display.py
Normal file
146
pyscript.core/src/python/pystdlib/display.py
Normal file
@@ -0,0 +1,146 @@
|
||||
# ⚠️ WARNING - both `document` and `window` are added at runtime
|
||||
|
||||
# XXX antocuni: I think this is wrong: it works in the main thread but not in
|
||||
# the worker, because the rest of the code expects window and document to be
|
||||
# proxies (see workerPyScriptModule in core.js)
|
||||
import base64
|
||||
import html
|
||||
import io
|
||||
import re
|
||||
|
||||
from js import document, window
|
||||
|
||||
_MIME_METHODS = {
|
||||
"__repr__": "text/plain",
|
||||
"_repr_html_": "text/html",
|
||||
"_repr_markdown_": "text/markdown",
|
||||
"_repr_svg_": "image/svg+xml",
|
||||
"_repr_png_": "image/png",
|
||||
"_repr_pdf_": "application/pdf",
|
||||
"_repr_jpeg_": "image/jpeg",
|
||||
"_repr_latex": "text/latex",
|
||||
"_repr_json_": "application/json",
|
||||
"_repr_javascript_": "application/javascript",
|
||||
"savefig": "image/png",
|
||||
}
|
||||
|
||||
|
||||
def _render_image(mime, value, meta):
|
||||
# If the image value is using bytes we should convert it to base64
|
||||
# otherwise it will return raw bytes and the browser will not be able to
|
||||
# render it.
|
||||
if isinstance(value, bytes):
|
||||
value = base64.b64encode(value).decode("utf-8")
|
||||
|
||||
# This is the pattern of base64 strings
|
||||
base64_pattern = re.compile(
|
||||
r"^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$"
|
||||
)
|
||||
# If value doesn't match the base64 pattern we should encode it to base64
|
||||
if len(value) > 0 and not base64_pattern.match(value):
|
||||
value = base64.b64encode(value.encode("utf-8")).decode("utf-8")
|
||||
|
||||
data = f"data:{mime};charset=utf-8;base64,{value}"
|
||||
attrs = " ".join(['{k}="{v}"' for k, v in meta.items()])
|
||||
return f'<img src="{data}" {attrs}></img>'
|
||||
|
||||
|
||||
def _identity(value, meta):
|
||||
return value
|
||||
|
||||
|
||||
_MIME_RENDERERS = {
|
||||
"text/plain": html.escape,
|
||||
"text/html": _identity,
|
||||
"image/png": lambda value, meta: _render_image("image/png", value, meta),
|
||||
"image/jpeg": lambda value, meta: _render_image("image/jpeg", value, meta),
|
||||
"image/svg+xml": _identity,
|
||||
"application/json": _identity,
|
||||
"application/javascript": lambda value, meta: f"<script>{value}<\\/script>",
|
||||
}
|
||||
|
||||
|
||||
def _eval_formatter(obj, print_method):
|
||||
"""
|
||||
Evaluates a formatter method.
|
||||
"""
|
||||
if print_method == "__repr__":
|
||||
return repr(obj)
|
||||
elif hasattr(obj, print_method):
|
||||
if print_method == "savefig":
|
||||
buf = io.BytesIO()
|
||||
obj.savefig(buf, format="png")
|
||||
buf.seek(0)
|
||||
return base64.b64encode(buf.read()).decode("utf-8")
|
||||
return getattr(obj, print_method)()
|
||||
elif print_method == "_repr_mimebundle_":
|
||||
return {}, {}
|
||||
return None
|
||||
|
||||
|
||||
def _format_mime(obj):
|
||||
"""
|
||||
Formats object using _repr_x_ methods.
|
||||
"""
|
||||
if isinstance(obj, str):
|
||||
return html.escape(obj), "text/plain"
|
||||
|
||||
mimebundle = _eval_formatter(obj, "_repr_mimebundle_")
|
||||
if isinstance(mimebundle, tuple):
|
||||
format_dict, _ = mimebundle
|
||||
else:
|
||||
format_dict = mimebundle
|
||||
|
||||
output, not_available = None, []
|
||||
for method, mime_type in reversed(_MIME_METHODS.items()):
|
||||
if mime_type in format_dict:
|
||||
output = format_dict[mime_type]
|
||||
else:
|
||||
output = _eval_formatter(obj, method)
|
||||
|
||||
if output is None:
|
||||
continue
|
||||
elif mime_type not in _MIME_RENDERERS:
|
||||
not_available.append(mime_type)
|
||||
continue
|
||||
break
|
||||
if output is None:
|
||||
if not_available:
|
||||
window.console.warn( # noqa: F821
|
||||
f"Rendered object requested unavailable MIME renderers: {not_available}"
|
||||
)
|
||||
output = repr(output)
|
||||
mime_type = "text/plain"
|
||||
elif isinstance(output, tuple):
|
||||
output, meta = output
|
||||
else:
|
||||
meta = {}
|
||||
return _MIME_RENDERERS[mime_type](output, meta), mime_type
|
||||
|
||||
|
||||
def _write(element, value, append=False):
|
||||
html, mime_type = _format_mime(value)
|
||||
if html == "\\n":
|
||||
return
|
||||
|
||||
if append:
|
||||
out_element = document.createElement("div") # noqa: F821
|
||||
element.append(out_element)
|
||||
else:
|
||||
out_element = element.lastElementChild
|
||||
if out_element is None:
|
||||
out_element = element
|
||||
|
||||
if mime_type in ("application/javascript", "text/html"):
|
||||
script_element = document.createRange().createContextualFragment( # noqa: F821
|
||||
html
|
||||
)
|
||||
out_element.append(script_element)
|
||||
else:
|
||||
out_element.innerHTML = html
|
||||
|
||||
|
||||
def display(*values, target=None, append=True):
|
||||
element = document.getElementById(target) # noqa: F821
|
||||
for v in values:
|
||||
_write(element, v, append=append)
|
||||
354
pyscript.core/src/python/pyweb/pydom.py
Normal file
354
pyscript.core/src/python/pyweb/pydom.py
Normal file
@@ -0,0 +1,354 @@
|
||||
import inspect
|
||||
import sys
|
||||
from functools import cached_property
|
||||
from typing import Any
|
||||
|
||||
import js
|
||||
|
||||
# from js import document as js_document
|
||||
from pyodide.ffi import JsProxy
|
||||
from pyodide.ffi.wrappers import add_event_listener
|
||||
from pyscript import display
|
||||
|
||||
alert = js.alert
|
||||
|
||||
|
||||
class BaseElement:
|
||||
def __init__(self, js_element):
|
||||
self._element = js_element
|
||||
self._parent = None
|
||||
self.style = StyleProxy(self)
|
||||
|
||||
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._element == self._element
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self._parent:
|
||||
return self._parent
|
||||
|
||||
if self._element.parentElement:
|
||||
self._parent = self.__class__(self._element.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 = js.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
|
||||
|
||||
|
||||
class Element(BaseElement):
|
||||
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 isinstance(child, JsProxy):
|
||||
return self.append(self.from_js(child))
|
||||
|
||||
elif isinstance(child, Element):
|
||||
self._element.appendChild(child._element)
|
||||
|
||||
return child
|
||||
|
||||
def from_js(self, js_element):
|
||||
return self.__class__(js.tagName, parent=self)
|
||||
|
||||
# TODO: These 2 should be changed to basically do what PyDom.__getitem__ does
|
||||
# but within the scope of the current element children
|
||||
def query(self, selector):
|
||||
"""The querySelector() method of the Element interface returns the first element
|
||||
that is a descendant of the element on which it is invoked that matches the specified
|
||||
group of selectors.
|
||||
"""
|
||||
return self.__class__(self._element.querySelector(selector))
|
||||
|
||||
def query_all(self, selector):
|
||||
"""The querySelectorAll() method of the Element interface returns a static (not live)
|
||||
NodeList representing a list of the document's elements that match the specified group
|
||||
of selectors.
|
||||
"""
|
||||
for element in self._element.querySelectorAll(selector):
|
||||
yield self.__class__(element)
|
||||
|
||||
# -------- Boilerplate Proxy for the Element API -------- #
|
||||
@property
|
||||
def html(self):
|
||||
return self._element.innerHTML
|
||||
|
||||
@html.setter
|
||||
def html(self, value):
|
||||
self._element.innerHTML = value
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
return self._element.innerHTML
|
||||
|
||||
@content.setter
|
||||
def content(self, value):
|
||||
display(value, target=self.id)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._element.id
|
||||
|
||||
@id.setter
|
||||
def id(self, value):
|
||||
self._element.id = value
|
||||
|
||||
@property
|
||||
def checked(self):
|
||||
return self._element.checked
|
||||
|
||||
@checked.setter
|
||||
def checked(self, value):
|
||||
self._element.checked = value
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
tag = self._element.tagName
|
||||
if tag == "INPUT":
|
||||
if self._element.type == "checkbox":
|
||||
return self._element.checked
|
||||
elif self._element.type == "number":
|
||||
return float(self._element.value)
|
||||
else:
|
||||
return self._element.value
|
||||
return self._element.innerHTML
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
# TODO: This needs a bit more thinking. SHould we set .innerHTML or .text for instance?
|
||||
tag = self._element.tagName
|
||||
# print(f"Writing ({tag} )---> {self._selector} ---> {value}")
|
||||
if tag == "INPUT":
|
||||
# print(f"Writing ({tag} | {self._element.type})---> {self._selector} ---> {value}")
|
||||
if self._element.type == "checkbox":
|
||||
self._element.checked = value
|
||||
elif self._element.type == "number":
|
||||
self._element.value = float(value)
|
||||
else:
|
||||
self._element.value = value
|
||||
else:
|
||||
self._element.innerHTML = value
|
||||
|
||||
def clear(self):
|
||||
self.value = ""
|
||||
|
||||
def clone(self, new_id=None):
|
||||
clone = Element(self._element.cloneNode(True))
|
||||
clone.id = new_id
|
||||
|
||||
return clone
|
||||
|
||||
def remove_class(self, classname):
|
||||
classList = self._element.classList
|
||||
if isinstance(classname, list):
|
||||
classList.remove(*classname)
|
||||
else:
|
||||
classList.remove(classname)
|
||||
return self
|
||||
|
||||
def add_class(self, classname):
|
||||
classList = self._element.classList
|
||||
if isinstance(classname, list):
|
||||
classList.add(*classname)
|
||||
else:
|
||||
self._element.classList.add(classname)
|
||||
return self
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
classes = self._element.classList.values()
|
||||
return [x for x in classes]
|
||||
|
||||
def show_me(self):
|
||||
self._element.scrollIntoView()
|
||||
|
||||
def when(self, event, handler):
|
||||
document.when(self, event)(handler)
|
||||
|
||||
|
||||
class StyleProxy(dict):
|
||||
def __init__(self, element: Element) -> None:
|
||||
self._element = element
|
||||
|
||||
@cached_property
|
||||
def _style(self):
|
||||
return self._element._element.style
|
||||
|
||||
def __getitem__(self, key):
|
||||
self._style[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._style.setProperty(key, value)
|
||||
|
||||
def pop(self, key):
|
||||
self._style.removeProperty(key)
|
||||
|
||||
def set(self, **kws):
|
||||
for k, v in kws.items():
|
||||
self._element._element.style.setProperty(k, v)
|
||||
|
||||
# CSS Properties
|
||||
# Reference: https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts#L3799C1-L5005C2
|
||||
@property
|
||||
def visibility(self):
|
||||
return self._element._element.style.visibility
|
||||
|
||||
@visibility.setter
|
||||
def visibility(self, value):
|
||||
self._element._element.style.visibility = value
|
||||
|
||||
@property
|
||||
def background(self):
|
||||
return self._element._element.style.background
|
||||
|
||||
@background.setter
|
||||
def background(self, value):
|
||||
self._element._element.style.background = value
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
return self._element._element.style.color
|
||||
|
||||
@color.setter
|
||||
def color(self, value):
|
||||
self._element._element.style.color = 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 pop(self, key):
|
||||
for element in self._collection._elements:
|
||||
element.style.pop(key)
|
||||
|
||||
|
||||
class ElementCollection:
|
||||
def __init__(self, elements: [Element]) -> None:
|
||||
self._elements = elements
|
||||
self.style = StyleCollection(self)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, int):
|
||||
return self._elements[key]
|
||||
elif isinstance(key, slice):
|
||||
return ElementCollection(self._elements[key])
|
||||
|
||||
# TODO: In this case what do we expect??
|
||||
elements = self._element.querySelectorAll(key)
|
||||
return ElementCollection([Element(el) for el in elements])
|
||||
|
||||
def _get_attribute(self, attr):
|
||||
# As JQuery, when getting an attr, only return it for the first element
|
||||
return getattr(self._elements[0], 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 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) -> Any:
|
||||
element = document[f"#{__name}"]
|
||||
if element:
|
||||
return element[0]
|
||||
|
||||
|
||||
class PyDom(BaseElement):
|
||||
def __init__(self):
|
||||
super().__init__(js.document)
|
||||
self.ids = DomScope()
|
||||
|
||||
def create(self, type_, parent=None, classes=None, html=None):
|
||||
return super().create(type_, is_child=False)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, int):
|
||||
indices = range(*key.indices(len(self.list)))
|
||||
return [self.list[i] for i in indices]
|
||||
|
||||
elements = self._element.querySelectorAll(key)
|
||||
if not elements:
|
||||
return None
|
||||
return ElementCollection([Element(el) for el in elements])
|
||||
|
||||
@staticmethod
|
||||
def when(element, event_type):
|
||||
# TODO: Ideally, we should have that implemented in PyScript not patched here
|
||||
# if isinstance(element, Element):
|
||||
# element = [element]
|
||||
def decorator(func):
|
||||
# elements = js.document.querySelectorAll(selector)
|
||||
sig = inspect.signature(func)
|
||||
|
||||
# Function doesn't receive events
|
||||
if not sig.parameters:
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
func()
|
||||
|
||||
# for el in element:
|
||||
add_event_listener(element._element, event_type, wrapper)
|
||||
else:
|
||||
# for el in element:
|
||||
add_event_listener(element._element, event_type, func)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
document = PyDom()
|
||||
|
||||
|
||||
sys.modules[__name__] = document
|
||||
0
pyscript.core/src/stdlib/_pyscript/__init__.py
Normal file
0
pyscript.core/src/stdlib/_pyscript/__init__.py
Normal file
146
pyscript.core/src/stdlib/_pyscript/_display.py
Normal file
146
pyscript.core/src/stdlib/_pyscript/_display.py
Normal file
@@ -0,0 +1,146 @@
|
||||
# ⚠️ WARNING - both `document` and `window` are added at runtime
|
||||
|
||||
# XXX antocuni: I think this is wrong: it works in the main thread but not in
|
||||
# the worker, because the rest of the code expects window and document to be
|
||||
# proxies (see workerPyScriptModule in core.js)
|
||||
import base64
|
||||
import html
|
||||
import io
|
||||
import re
|
||||
|
||||
from js import document, window
|
||||
|
||||
_MIME_METHODS = {
|
||||
"__repr__": "text/plain",
|
||||
"_repr_html_": "text/html",
|
||||
"_repr_markdown_": "text/markdown",
|
||||
"_repr_svg_": "image/svg+xml",
|
||||
"_repr_png_": "image/png",
|
||||
"_repr_pdf_": "application/pdf",
|
||||
"_repr_jpeg_": "image/jpeg",
|
||||
"_repr_latex": "text/latex",
|
||||
"_repr_json_": "application/json",
|
||||
"_repr_javascript_": "application/javascript",
|
||||
"savefig": "image/png",
|
||||
}
|
||||
|
||||
|
||||
def _render_image(mime, value, meta):
|
||||
# If the image value is using bytes we should convert it to base64
|
||||
# otherwise it will return raw bytes and the browser will not be able to
|
||||
# render it.
|
||||
if isinstance(value, bytes):
|
||||
value = base64.b64encode(value).decode("utf-8")
|
||||
|
||||
# This is the pattern of base64 strings
|
||||
base64_pattern = re.compile(
|
||||
r"^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$"
|
||||
)
|
||||
# If value doesn't match the base64 pattern we should encode it to base64
|
||||
if len(value) > 0 and not base64_pattern.match(value):
|
||||
value = base64.b64encode(value.encode("utf-8")).decode("utf-8")
|
||||
|
||||
data = f"data:{mime};charset=utf-8;base64,{value}"
|
||||
attrs = " ".join(['{k}="{v}"' for k, v in meta.items()])
|
||||
return f'<img src="{data}" {attrs}></img>'
|
||||
|
||||
|
||||
def _identity(value, meta):
|
||||
return value
|
||||
|
||||
|
||||
_MIME_RENDERERS = {
|
||||
"text/plain": html.escape,
|
||||
"text/html": _identity,
|
||||
"image/png": lambda value, meta: _render_image("image/png", value, meta),
|
||||
"image/jpeg": lambda value, meta: _render_image("image/jpeg", value, meta),
|
||||
"image/svg+xml": _identity,
|
||||
"application/json": _identity,
|
||||
"application/javascript": lambda value, meta: f"<script>{value}<\\/script>",
|
||||
}
|
||||
|
||||
|
||||
def _eval_formatter(obj, print_method):
|
||||
"""
|
||||
Evaluates a formatter method.
|
||||
"""
|
||||
if print_method == "__repr__":
|
||||
return repr(obj)
|
||||
elif hasattr(obj, print_method):
|
||||
if print_method == "savefig":
|
||||
buf = io.BytesIO()
|
||||
obj.savefig(buf, format="png")
|
||||
buf.seek(0)
|
||||
return base64.b64encode(buf.read()).decode("utf-8")
|
||||
return getattr(obj, print_method)()
|
||||
elif print_method == "_repr_mimebundle_":
|
||||
return {}, {}
|
||||
return None
|
||||
|
||||
|
||||
def _format_mime(obj):
|
||||
"""
|
||||
Formats object using _repr_x_ methods.
|
||||
"""
|
||||
if isinstance(obj, str):
|
||||
return html.escape(obj), "text/plain"
|
||||
|
||||
mimebundle = _eval_formatter(obj, "_repr_mimebundle_")
|
||||
if isinstance(mimebundle, tuple):
|
||||
format_dict, _ = mimebundle
|
||||
else:
|
||||
format_dict = mimebundle
|
||||
|
||||
output, not_available = None, []
|
||||
for method, mime_type in reversed(_MIME_METHODS.items()):
|
||||
if mime_type in format_dict:
|
||||
output = format_dict[mime_type]
|
||||
else:
|
||||
output = _eval_formatter(obj, method)
|
||||
|
||||
if output is None:
|
||||
continue
|
||||
elif mime_type not in _MIME_RENDERERS:
|
||||
not_available.append(mime_type)
|
||||
continue
|
||||
break
|
||||
if output is None:
|
||||
if not_available:
|
||||
window.console.warn( # noqa: F821
|
||||
f"Rendered object requested unavailable MIME renderers: {not_available}"
|
||||
)
|
||||
output = repr(output)
|
||||
mime_type = "text/plain"
|
||||
elif isinstance(output, tuple):
|
||||
output, meta = output
|
||||
else:
|
||||
meta = {}
|
||||
return _MIME_RENDERERS[mime_type](output, meta), mime_type
|
||||
|
||||
|
||||
def _write(element, value, append=False):
|
||||
html, mime_type = _format_mime(value)
|
||||
if html == "\\n":
|
||||
return
|
||||
|
||||
if append:
|
||||
out_element = document.createElement("div") # noqa: F821
|
||||
element.append(out_element)
|
||||
else:
|
||||
out_element = element.lastElementChild
|
||||
if out_element is None:
|
||||
out_element = element
|
||||
|
||||
if mime_type in ("application/javascript", "text/html"):
|
||||
script_element = document.createRange().createContextualFragment( # noqa: F821
|
||||
html
|
||||
)
|
||||
out_element.append(script_element)
|
||||
else:
|
||||
out_element.innerHTML = html
|
||||
|
||||
|
||||
def display(*values, target=None, append=True):
|
||||
element = document.getElementById(target) # noqa: F821
|
||||
for v in values:
|
||||
_write(element, v, append=append)
|
||||
1
pyscript.core/test/a.py
Normal file
1
pyscript.core/test/a.py
Normal file
@@ -0,0 +1 @@
|
||||
print("a")
|
||||
5
pyscript.core/test/config.json
Normal file
5
pyscript.core/test/config.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"fetch": [{
|
||||
"files": ["./a.py"]
|
||||
}]
|
||||
}
|
||||
16
pyscript.core/test/index.html
Normal file
16
pyscript.core/test/index.html
Normal 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" />
|
||||
<title>PyScript Next</title>
|
||||
<link rel="stylesheet" href="../core.css" />
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py">
|
||||
from pyscript import display
|
||||
display("Hello", "PyScript Next", append=False)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
37
pyscript.core/test/worker.html
Normal file
37
pyscript.core/test/worker.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PyScript Next</title>
|
||||
<link rel="stylesheet" href="../core.css" />
|
||||
|
||||
<!-- the PyWorker approach -->
|
||||
<script type="module">
|
||||
import { PyWorker } from '../core.js';
|
||||
PyWorker('./worker.py', {config: {fetch: [{files: ['./a.py']}]}});
|
||||
// the type is overwritten as "pyodide" in PyScript as the module
|
||||
// lives in that env too
|
||||
</script>
|
||||
|
||||
<!-- the worker attribute -->
|
||||
<script type="py" worker="./worker.py" config="./config.json"></script>
|
||||
|
||||
<!-- this is only to test the non-blocking behavior -->
|
||||
<script>
|
||||
addEventListener('DOMContentLoaded', () => {
|
||||
const div = document.body.appendChild(
|
||||
document.createElement('div')
|
||||
);
|
||||
(function monitor() {
|
||||
const date = new Date;
|
||||
div.textContent = `${date.getSeconds()}.${date.getMilliseconds()}`;
|
||||
requestAnimationFrame(monitor);
|
||||
}());
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="test"></div>
|
||||
</body>
|
||||
</html>
|
||||
5
pyscript.core/test/worker.py
Normal file
5
pyscript.core/test/worker.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from pyscript import display
|
||||
|
||||
import a
|
||||
|
||||
display("Hello World", target="test", append=True)
|
||||
12
pyscript.core/tsconfig.json
Normal file
12
pyscript.core/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ES2022",
|
||||
"target": "ES2022",
|
||||
"moduleResolution": "Classic",
|
||||
"allowJs": true,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"declarationDir": "types"
|
||||
},
|
||||
"include": ["src/core.js"]
|
||||
}
|
||||
10
pyscript.core/types/coincident/window.d.ts
vendored
Normal file
10
pyscript.core/types/coincident/window.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
export { ie as default };
|
||||
declare function ie(e: any, ...r: any[]): any;
|
||||
declare namespace ie {
|
||||
import transfer = m.transfer;
|
||||
export { transfer };
|
||||
}
|
||||
declare function m(t: any, { parse: n, stringify: r, transform: u }?: JSON): any;
|
||||
declare namespace m {
|
||||
function transfer(...e: any[]): any[];
|
||||
}
|
||||
25
pyscript.core/types/core.d.ts
vendored
Normal file
25
pyscript.core/types/core.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
||||
* @param {string} file the python file to run ina worker.
|
||||
* @param {{config?: string | object, async?: boolean}} [options] optional configuration for the worker.
|
||||
* @returns {Worker & {sync: ProxyHandler<object>}}
|
||||
*/
|
||||
export function PyWorker(file: string, options?: {
|
||||
config?: string | object;
|
||||
async?: boolean;
|
||||
}): Worker & {
|
||||
sync: ProxyHandler<object>;
|
||||
};
|
||||
export namespace hooks {
|
||||
let onBeforeRun: Set<Function>;
|
||||
let onBeforeRunAync: Set<Function>;
|
||||
let onAfterRun: Set<Function>;
|
||||
let onAfterRunAsync: Set<Function>;
|
||||
let onInterpreterReady: Set<Function>;
|
||||
let codeBeforeRunWorker: Set<string>;
|
||||
let codeBeforeRunWorkerAsync: Set<string>;
|
||||
let codeAfterRunWorker: Set<string>;
|
||||
let codeAfterRunWorkerAsync: Set<string>;
|
||||
}
|
||||
declare let config: any;
|
||||
export {};
|
||||
27
pyscript.core/types/exceptions.d.ts
vendored
Normal file
27
pyscript.core/types/exceptions.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
export function _createAlertBanner(message: any, level: any, messageType?: string, logMessage?: boolean): void;
|
||||
export namespace ErrorCode {
|
||||
let GENERIC: string;
|
||||
let FETCH_ERROR: string;
|
||||
let FETCH_NAME_ERROR: string;
|
||||
let FETCH_UNAUTHORIZED_ERROR: string;
|
||||
let FETCH_FORBIDDEN_ERROR: string;
|
||||
let FETCH_NOT_FOUND_ERROR: string;
|
||||
let FETCH_SERVER_ERROR: string;
|
||||
let FETCH_UNAVAILABLE_ERROR: string;
|
||||
let BAD_CONFIG: string;
|
||||
let MICROPIP_INSTALL_ERROR: string;
|
||||
let BAD_PLUGIN_FILE_EXTENSION: string;
|
||||
let NO_DEFAULT_EXPORT: string;
|
||||
let TOP_LEVEL_AWAIT: string;
|
||||
}
|
||||
export class UserError extends Error {
|
||||
constructor(errorCode: any, message?: string, messageType?: string);
|
||||
errorCode: any;
|
||||
messageType: string;
|
||||
}
|
||||
export class FetchError extends UserError {
|
||||
constructor(errorCode: any, message: any);
|
||||
}
|
||||
export class InstallError extends UserError {
|
||||
constructor(errorCode: any, message: any);
|
||||
}
|
||||
10
pyscript.core/types/fetch.d.ts
vendored
Normal file
10
pyscript.core/types/fetch.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* This is a fetch wrapper that handles any non 200 responses and throws a
|
||||
* FetchError with the right ErrorCode. This is useful because our FetchError
|
||||
* will automatically create an alert banner.
|
||||
*
|
||||
* @param {string} url - URL to fetch
|
||||
* @param {Request} [options] - options to pass to fetch
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export function robustFetch(url: string, options?: Request): Promise<Response>;
|
||||
54
pyscript.core/types/polyscript/esm/custom.d.ts
vendored
Normal file
54
pyscript.core/types/polyscript/esm/custom.d.ts
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
export const CUSTOM_SELECTORS: any[];
|
||||
export function handleCustomType(node: Element): void;
|
||||
export function define(type: string, options: CustomOptions): void;
|
||||
export function whenDefined(type: string): Promise<object>;
|
||||
/**
|
||||
* custom configuration
|
||||
*/
|
||||
export type Runtime = {
|
||||
/**
|
||||
* the bootstrapped interpreter
|
||||
*/
|
||||
interpreter: object;
|
||||
/**
|
||||
* an XWorker constructor that defaults to same interpreter on the Worker.
|
||||
*/
|
||||
XWorker: (url: string, options?: object) => Worker;
|
||||
/**
|
||||
* a cloned config used to bootstrap the interpreter
|
||||
*/
|
||||
config: object;
|
||||
/**
|
||||
* an utility to run code within the interpreter
|
||||
*/
|
||||
run: (code: string) => any;
|
||||
/**
|
||||
* an utility to run code asynchronously within the interpreter
|
||||
*/
|
||||
runAsync: (code: string) => Promise<any>;
|
||||
/**
|
||||
* an utility to write a file in the virtual FS, if available
|
||||
*/
|
||||
writeFile: (path: string, data: ArrayBuffer) => void;
|
||||
};
|
||||
/**
|
||||
* custom configuration
|
||||
*/
|
||||
export type CustomOptions = {
|
||||
/**
|
||||
* the interpreter to use
|
||||
*/
|
||||
interpreter: 'pyodide' | 'micropython' | 'wasmoon' | 'ruby-wasm-wasi';
|
||||
/**
|
||||
* the optional interpreter version to use
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* the optional config to use within such interpreter
|
||||
*/
|
||||
config?: string;
|
||||
/**
|
||||
* the callback that will be invoked once
|
||||
*/
|
||||
onInterpreterReady?: (environment: object, node: Element) => void;
|
||||
};
|
||||
3
pyscript.core/types/polyscript/esm/fetch-utils.d.ts
vendored
Normal file
3
pyscript.core/types/polyscript/esm/fetch-utils.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export function getBuffer(response: Response): Promise<ArrayBuffer>;
|
||||
export function getJSON(response: Response): Promise<any>;
|
||||
export function getText(response: Response): Promise<string>;
|
||||
3
pyscript.core/types/polyscript/esm/index.d.ts
vendored
Normal file
3
pyscript.core/types/polyscript/esm/index.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export { env } from "./listeners.js";
|
||||
export const XWorker: (url: string, options?: import("./worker/class.js").WorkerOptions) => Worker;
|
||||
export { define, whenDefined } from "./custom.js";
|
||||
4
pyscript.core/types/polyscript/esm/interpreter/_python.d.ts
vendored
Normal file
4
pyscript.core/types/polyscript/esm/interpreter/_python.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export function registerJSModule(interpreter: any, name: any, value: any): void;
|
||||
export function run(interpreter: any, code: any): any;
|
||||
export function runAsync(interpreter: any, code: any): any;
|
||||
export function runEvent(interpreter: any, code: any, event: any): Promise<void>;
|
||||
15
pyscript.core/types/polyscript/esm/interpreter/_utils.d.ts
vendored
Normal file
15
pyscript.core/types/polyscript/esm/interpreter/_utils.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
export function clean(code: string): string;
|
||||
export const io: WeakMap<object, any>;
|
||||
export function stdio(init: any): {
|
||||
stderr: (...args: any[]) => any;
|
||||
stdout: (...args: any[]) => any;
|
||||
get(engine: any): Promise<any>;
|
||||
};
|
||||
export function writeFile({ FS, PATH, PATH_FS }: {
|
||||
FS: any;
|
||||
PATH: any;
|
||||
PATH_FS: any;
|
||||
}, path: any, buffer: any): any;
|
||||
export function writeFileShim(FS: any, path: any, buffer: any): any;
|
||||
export const base: WeakMap<object, any>;
|
||||
export function fetchPaths(module: any, interpreter: any, config_fetch: any): Promise<any[]>;
|
||||
25
pyscript.core/types/polyscript/esm/interpreter/micropython.d.ts
vendored
Normal file
25
pyscript.core/types/polyscript/esm/interpreter/micropython.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
declare namespace _default {
|
||||
export { type };
|
||||
export function module(version?: string): string;
|
||||
export function engine({ loadMicroPython }: {
|
||||
loadMicroPython: any;
|
||||
}, config: any, url: any): Promise<any>;
|
||||
export { registerJSModule };
|
||||
export { run };
|
||||
export { runAsync };
|
||||
export { runEvent };
|
||||
export function transform(_: any, value: any): any;
|
||||
export function writeFile({ FS, _module: { PATH, PATH_FS } }: {
|
||||
FS: any;
|
||||
_module: {
|
||||
PATH: any;
|
||||
PATH_FS: any;
|
||||
};
|
||||
}, path: any, buffer: any): any;
|
||||
}
|
||||
export default _default;
|
||||
declare const type: "micropython";
|
||||
import { registerJSModule } from './_python.js';
|
||||
import { run } from './_python.js';
|
||||
import { runAsync } from './_python.js';
|
||||
import { runEvent } from './_python.js';
|
||||
25
pyscript.core/types/polyscript/esm/interpreter/pyodide.d.ts
vendored
Normal file
25
pyscript.core/types/polyscript/esm/interpreter/pyodide.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
declare namespace _default {
|
||||
export { type };
|
||||
export function module(version?: string): string;
|
||||
export function engine({ loadPyodide }: {
|
||||
loadPyodide: any;
|
||||
}, config: any, url: any): Promise<any>;
|
||||
export { registerJSModule };
|
||||
export { run };
|
||||
export { runAsync };
|
||||
export { runEvent };
|
||||
export function transform(interpreter: any, value: any): any;
|
||||
export function writeFile({ FS, PATH, _module: { PATH_FS } }: {
|
||||
FS: any;
|
||||
PATH: any;
|
||||
_module: {
|
||||
PATH_FS: any;
|
||||
};
|
||||
}, path: any, buffer: any): any;
|
||||
}
|
||||
export default _default;
|
||||
declare const type: "pyodide";
|
||||
import { registerJSModule } from './_python.js';
|
||||
import { run } from './_python.js';
|
||||
import { runAsync } from './_python.js';
|
||||
import { runEvent } from './_python.js';
|
||||
16
pyscript.core/types/polyscript/esm/interpreter/ruby-wasm-wasi.d.ts
vendored
Normal file
16
pyscript.core/types/polyscript/esm/interpreter/ruby-wasm-wasi.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
declare namespace _default {
|
||||
export { type };
|
||||
export let experimental: boolean;
|
||||
export function module(version?: string): string;
|
||||
export function engine({ DefaultRubyVM }: {
|
||||
DefaultRubyVM: any;
|
||||
}, config: any, url: any): Promise<any>;
|
||||
export function registerJSModule(interpreter: any, _: any, value: any): void;
|
||||
export function run(interpreter: any, code: any): any;
|
||||
export function runAsync(interpreter: any, code: any): any;
|
||||
export function runEvent(interpreter: any, code: any, event: any): Promise<void>;
|
||||
export function transform(_: any, value: any): any;
|
||||
export function writeFile(): never;
|
||||
}
|
||||
export default _default;
|
||||
declare const type: "ruby-wasm-wasi";
|
||||
22
pyscript.core/types/polyscript/esm/interpreter/wasmoon.d.ts
vendored
Normal file
22
pyscript.core/types/polyscript/esm/interpreter/wasmoon.d.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
declare namespace _default {
|
||||
export { type };
|
||||
export function module(version?: string): string;
|
||||
export function engine({ LuaFactory, LuaLibraries }: {
|
||||
LuaFactory: any;
|
||||
LuaLibraries: any;
|
||||
}, config: any): Promise<any>;
|
||||
export function registerJSModule(interpreter: any, _: any, value: any): void;
|
||||
export function run(interpreter: any, code: any): any;
|
||||
export function runAsync(interpreter: any, code: any): any;
|
||||
export function runEvent(interpreter: any, code: any, event: any): Promise<void>;
|
||||
export function transform(_: any, value: any): any;
|
||||
export function writeFile({ cmodule: { module: { FS }, }, }: {
|
||||
cmodule: {
|
||||
module: {
|
||||
FS: any;
|
||||
};
|
||||
};
|
||||
}, path: any, buffer: any): any;
|
||||
}
|
||||
export default _default;
|
||||
declare const type: "wasmoon";
|
||||
9
pyscript.core/types/polyscript/esm/interpreters.d.ts
vendored
Normal file
9
pyscript.core/types/polyscript/esm/interpreters.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/** @type {Map<string, object>} */
|
||||
export const registry: Map<string, object>;
|
||||
/** @type {Map<string, object>} */
|
||||
export const configs: Map<string, object>;
|
||||
/** @type {string[]} */
|
||||
export const selectors: string[];
|
||||
/** @type {string[]} */
|
||||
export const prefixes: string[];
|
||||
export const interpreter: Map<any, any>;
|
||||
3
pyscript.core/types/polyscript/esm/listeners.d.ts
vendored
Normal file
3
pyscript.core/types/polyscript/esm/listeners.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export const env: any;
|
||||
export function listener(event: any): Promise<void>;
|
||||
export function addAllListeners(root: Document | Element): void;
|
||||
2
pyscript.core/types/polyscript/esm/loader.d.ts
vendored
Normal file
2
pyscript.core/types/polyscript/esm/loader.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export function getRuntime(id: string, config?: string, options?: object): Promise<any>;
|
||||
export function getRuntimeID(type: string, version?: string): string;
|
||||
4
pyscript.core/types/polyscript/esm/script-handler.d.ts
vendored
Normal file
4
pyscript.core/types/polyscript/esm/script-handler.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export function queryTarget(script: any, idOrSelector: any): any;
|
||||
export const interpreters: Map<any, any>;
|
||||
export function getDetails(type: any, id: any, name: any, version: any, config: any, runtime?: any): any;
|
||||
export function handle(script: HTMLScriptElement): Promise<void>;
|
||||
1
pyscript.core/types/polyscript/esm/toml.d.ts
vendored
Normal file
1
pyscript.core/types/polyscript/esm/toml.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export function parse(text: string): object;
|
||||
29
pyscript.core/types/polyscript/esm/utils.d.ts
vendored
Normal file
29
pyscript.core/types/polyscript/esm/utils.d.ts
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
export const isArray: (arg: any) => arg is any[];
|
||||
export const assign: {
|
||||
<T extends {}, U>(target: T, source: U): T & U;
|
||||
<T_1 extends {}, U_1, V>(target: T_1, source1: U_1, source2: V): T_1 & U_1 & V;
|
||||
<T_2 extends {}, U_2, V_1, W>(target: T_2, source1: U_2, source2: V_1, source3: W): T_2 & U_2 & V_1 & W;
|
||||
(target: object, ...sources: any[]): any;
|
||||
};
|
||||
export const create: {
|
||||
(o: object): any;
|
||||
(o: object, properties: PropertyDescriptorMap & ThisType<any>): any;
|
||||
};
|
||||
export const defineProperties: <T>(o: T, properties: PropertyDescriptorMap & ThisType<any>) => T;
|
||||
export const defineProperty: <T>(o: T, p: PropertyKey, attributes: PropertyDescriptor & ThisType<any>) => T;
|
||||
export const entries: {
|
||||
<T>(o: {
|
||||
[s: string]: T;
|
||||
} | ArrayLike<T>): [string, T][];
|
||||
(o: {}): [string, any][];
|
||||
};
|
||||
export const all: {
|
||||
<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>[]>;
|
||||
<T_1 extends [] | readonly unknown[]>(values: T_1): Promise<{ -readonly [P in keyof T_1]: Awaited<T_1[P]>; }>;
|
||||
};
|
||||
export const resolve: {
|
||||
(): Promise<void>;
|
||||
<T>(value: T): Promise<Awaited<T>>;
|
||||
<T_1>(value: T_1 | PromiseLike<T_1>): Promise<Awaited<T_1>>;
|
||||
};
|
||||
export function absoluteURL(path: any, base?: string): string;
|
||||
19
pyscript.core/types/polyscript/esm/worker/class.d.ts
vendored
Normal file
19
pyscript.core/types/polyscript/esm/worker/class.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
declare function _default(...args: any[]): (url: string, options?: WorkerOptions) => Worker;
|
||||
export default _default;
|
||||
/**
|
||||
* custom configuration
|
||||
*/
|
||||
export type WorkerOptions = {
|
||||
/**
|
||||
* the interpreter type to use
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* the optional interpreter version to use
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* the optional config to use within such interpreter
|
||||
*/
|
||||
config?: string;
|
||||
};
|
||||
6
pyscript.core/types/polyscript/esm/worker/hooks.d.ts
vendored
Normal file
6
pyscript.core/types/polyscript/esm/worker/hooks.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export class Hook {
|
||||
constructor(interpreter: any, options: any);
|
||||
interpreter: any;
|
||||
onWorkerReady: any;
|
||||
get stringHooks(): {};
|
||||
}
|
||||
2
pyscript.core/types/polyscript/esm/worker/xworker.d.ts
vendored
Normal file
2
pyscript.core/types/polyscript/esm/worker/xworker.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare function _default(): Worker;
|
||||
export default _default;
|
||||
25
pyscript.core/types/pyscript/pyscript.core/src/core.d.ts
vendored
Normal file
25
pyscript.core/types/pyscript/pyscript.core/src/core.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
||||
* @param {string} file the python file to run ina worker.
|
||||
* @param {{config?: string | object, async?: boolean}} [options] optional configuration for the worker.
|
||||
* @returns {Worker & {sync: ProxyHandler<object>}}
|
||||
*/
|
||||
export function PyWorker(file: string, options?: {
|
||||
config?: string | object;
|
||||
async?: boolean;
|
||||
}): Worker & {
|
||||
sync: ProxyHandler<object>;
|
||||
};
|
||||
export namespace hooks {
|
||||
let onBeforeRun: Set<Function>;
|
||||
let onBeforeRunAync: Set<Function>;
|
||||
let onAfterRun: Set<Function>;
|
||||
let onAfterRunAsync: Set<Function>;
|
||||
let onInterpreterReady: Set<Function>;
|
||||
let codeBeforeRunWorker: Set<string>;
|
||||
let codeBeforeRunWorkerAsync: Set<string>;
|
||||
let codeAfterRunWorker: Set<string>;
|
||||
let codeAfterRunWorkerAsync: Set<string>;
|
||||
}
|
||||
declare let config: any;
|
||||
export {};
|
||||
27
pyscript.core/types/pyscript/pyscript.core/src/exceptions.d.ts
vendored
Normal file
27
pyscript.core/types/pyscript/pyscript.core/src/exceptions.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
export function _createAlertBanner(message: any, level: any, messageType?: string, logMessage?: boolean): void;
|
||||
export namespace ErrorCode {
|
||||
let GENERIC: string;
|
||||
let FETCH_ERROR: string;
|
||||
let FETCH_NAME_ERROR: string;
|
||||
let FETCH_UNAUTHORIZED_ERROR: string;
|
||||
let FETCH_FORBIDDEN_ERROR: string;
|
||||
let FETCH_NOT_FOUND_ERROR: string;
|
||||
let FETCH_SERVER_ERROR: string;
|
||||
let FETCH_UNAVAILABLE_ERROR: string;
|
||||
let BAD_CONFIG: string;
|
||||
let MICROPIP_INSTALL_ERROR: string;
|
||||
let BAD_PLUGIN_FILE_EXTENSION: string;
|
||||
let NO_DEFAULT_EXPORT: string;
|
||||
let TOP_LEVEL_AWAIT: string;
|
||||
}
|
||||
export class UserError extends Error {
|
||||
constructor(errorCode: any, message?: string, messageType?: string);
|
||||
errorCode: any;
|
||||
messageType: string;
|
||||
}
|
||||
export class FetchError extends UserError {
|
||||
constructor(errorCode: any, message: any);
|
||||
}
|
||||
export class InstallError extends UserError {
|
||||
constructor(errorCode: any, message: any);
|
||||
}
|
||||
10
pyscript.core/types/pyscript/pyscript.core/src/fetch.d.ts
vendored
Normal file
10
pyscript.core/types/pyscript/pyscript.core/src/fetch.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* This is a fetch wrapper that handles any non 200 responses and throws a
|
||||
* FetchError with the right ErrorCode. This is useful because our FetchError
|
||||
* will automatically create an alert banner.
|
||||
*
|
||||
* @param {string} url - URL to fetch
|
||||
* @param {Request} [options] - options to pass to fetch
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export function robustFetch(url: string, options?: Request): Promise<Response>;
|
||||
5
pyscript.sw/.npmignore
Normal file
5
pyscript.sw/.npmignore
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
rollup/
|
||||
src/
|
||||
test/
|
||||
package-lock.json
|
||||
201
pyscript.sw/LICENSE
Normal file
201
pyscript.sw/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
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
|
||||
|
||||
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.
|
||||
47
pyscript.sw/README.md
Normal file
47
pyscript.sw/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# @pyscript/sw
|
||||
|
||||
<sup>**[PyScript](https://github.com/pyscript/pyscript) Service Worker**</sup>
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
This module provides a single, standalone, *script* able to bootstrap a Service Worker which can drive a whole site via Python code.
|
||||
|
||||
Please note the file *must* be available locally and it *must not* be loaded as a module, as a Service Worker is *not a module*.
|
||||
|
||||
### Example
|
||||
|
||||
This is the bare minimal example of an `index.html` file at the root of the site.
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<script src="./pyscript.sw.js"
|
||||
handler="./handler.py"
|
||||
config="./handler_config.toml"
|
||||
scope="."></script>
|
||||
```
|
||||
|
||||
* **src** is where the PyScript Service Worker is located.
|
||||
* **handler** is where Python code is located. This *must* provide a `handle_request` method that will be invoked per each browsing *fetch* operation. Such method should return a `[body, status, headers]` tuple where *body* is the content of the page, *status* is its *HTTP* status and *headers* contain the `content-type` or any other useful header.
|
||||
* **config** is an *optional* attribute that indicates packages to load, files to fetch, and all other usual [py-config goodness](https://docs.pyscript.net/latest/reference/elements/py-config.html).
|
||||
* **scope** (advanced use-case) is an *optional* attribute that indicates [where the Service Worker operates](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope). By default it operates from the same folder, and any subfolder, the `pyscript.sw.js` is.
|
||||
|
||||
#### How to update `handle_request`
|
||||
|
||||
Because the Service Worker, once activated, will persist over any further session, it is pretty hard to change its operating handler.
|
||||
|
||||
To do so, there are two options:
|
||||
|
||||
* unregister the Service Worker, clear all browsing data per that domain and hard-refresh the browser
|
||||
* change and save your `handler.py` file and, once saved, reach the `/pyscript.sw/update_handler` via browser, or run the following code in console:
|
||||
|
||||
```js
|
||||
fetch('/pyscript.sw/update_handler')
|
||||
.then(b => b.text())
|
||||
.then(console.log, console.error);
|
||||
```
|
||||
|
||||
This operation will be intercepted behind the scene and the new file will be parsed.
|
||||
|
||||
The result should be an `OK` response, with status `200`, or an error message with status `500` handled by the `console.error` or visible within the page once reached.
|
||||
467
pyscript.sw/package-lock.json
generated
Normal file
467
pyscript.sw/package-lock.json
generated
Normal file
@@ -0,0 +1,467 @@
|
||||
{
|
||||
"name": "@pyscript/sw",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@pyscript/sw",
|
||||
"version": "0.0.1",
|
||||
"license": "APACHE-2.0",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.1.0",
|
||||
"@rollup/plugin-terser": "^0.4.3",
|
||||
"basic-toml": "^0.3.1",
|
||||
"rollup": "^3.27.0",
|
||||
"static-handler": "^0.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.0.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/set-array": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
|
||||
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
|
||||
"integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "15.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.1.0.tgz",
|
||||
"integrity": "sha512-xeZHCgsiZ9pzYVgAo9580eCGqwh/XCEUM9q6iQfGNocjgkufHAqC3exA+45URvhiYV8sBF9RlBai650eNs7AsA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"@types/resolve": "1.20.2",
|
||||
"deepmerge": "^4.2.2",
|
||||
"is-builtin-module": "^3.2.1",
|
||||
"is-module": "^1.0.0",
|
||||
"resolve": "^1.22.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^2.78.0||^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-terser": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.3.tgz",
|
||||
"integrity": "sha512-EF0oejTMtkyhrkwCdg0HJ0IpkcaVg1MMSf2olHb2Jp+1mnLM04OhjpJWGma4HobiDTF0WCyViWuvadyE9ch2XA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"serialize-javascript": "^6.0.1",
|
||||
"smob": "^1.0.0",
|
||||
"terser": "^5.17.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^2.x || ^3.x"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
|
||||
"integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0||^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
|
||||
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
|
||||
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/basic-toml": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-toml/-/basic-toml-0.3.1.tgz",
|
||||
"integrity": "sha512-O1ywy7Win4Tw215aY/cHyzZXCYlhyzEUbVR+hqkmosBA6Z7ejBcZb3fwDtsirAsd+KADY9A/D3qkbAQ4CqkaHg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/builtin-modules": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
|
||||
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-builtin-module": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
|
||||
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"builtin-modules": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
|
||||
"integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has": "^1.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.2",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
|
||||
"integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.11.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "3.27.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.27.1.tgz",
|
||||
"integrity": "sha512-tXNDFwOkN6C2w5Blj1g6ForKeFw6c1mDu5jxoeDO3/pmYjgt+8yvIFjKzH5FQUq70OKZBkOt0zzv0THXL7vwzQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
|
||||
"integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/smob": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/smob/-/smob-1.4.0.tgz",
|
||||
"integrity": "sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/static-handler": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/static-handler/-/static-handler-0.4.2.tgz",
|
||||
"integrity": "sha512-Szbk521mneb5npxg3SEyoufsHr2osAzxMy71W2zFCzLB8wLhHYvKUDCMkLY6imi+fIqkpfas3rzXGBQf99aeEA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-types": "^2.1.35"
|
||||
},
|
||||
"bin": {
|
||||
"static-handler": "static-handler.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.19.2",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz",
|
||||
"integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.8.2",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
pyscript.sw/package.json
Normal file
20
pyscript.sw/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@pyscript/sw",
|
||||
"version": "0.0.1",
|
||||
"description": "PyScript Service Worker",
|
||||
"main": "pyscript.sw.js",
|
||||
"scripts": {
|
||||
"server": "npx static-handler .",
|
||||
"build": "rollup --config rollup/sw.config.mjs"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Anaconda Inc.",
|
||||
"license": "APACHE-2.0",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.1.0",
|
||||
"@rollup/plugin-terser": "^0.4.3",
|
||||
"basic-toml": "^0.3.1",
|
||||
"rollup": "^3.27.0",
|
||||
"static-handler": "^0.4.2"
|
||||
}
|
||||
}
|
||||
3
pyscript.sw/pyscript.sw.js
Normal file
3
pyscript.sw/pyscript.sw.js
Normal file
@@ -0,0 +1,3 @@
|
||||
!function(){"use strict";(({document:e,navigator:{serviceWorker:t}})=>{if(e){const{href:s}=location,{controller:n}=t,{currentScript:a}=e,{src:r,attributes:{config:i,handler:o,scope:c}}=a;n&&n.postMessage({config:i?.value?new URL(i.value,s).href:"",handler:new URL(o.value,s).href}),t.addEventListener("message",(({data:e})=>{"reload"===e&&location.reload()})),t.register(r,{scope:c?.value||"."}).then((e=>{e.addEventListener("updatefound",(()=>location.reload())),e.active&&!n&&location.reload()}))}else{const e="https://cdn.jsdelivr.net/pyodide/v0.23.4/full";importScripts(`${e}/pyodide.js`);const t=async e=>(await Promise.resolve().then((function(){return r}))).parse(e),s=loadPyodide({indexURL:e});addEventListener("install",(()=>skipWaiting())),addEventListener("activate",(e=>e.waitUntil(clients.claim())));let n,a,i=!1,o=new Promise((e=>{addEventListener("message",(async({data:{config:r,handler:o}})=>{if(r){const e=await s,n=[fetch(r),e.loadPackage("micropip")];n[0]=r.endsWith(".json")?n[0].then((e=>e.json())):n[0].then((e=>e.text())).then(t);const[{packages:a}]=await Promise.all(n),i=await e.pyimport("micropip");await i.install(a)}a=o;const l=await c();i=!0;(await clients.get(n)).postMessage("reload"),e(l)}),{once:!0})}));const c=()=>new Promise((async(e,t)=>{const n=await s,r=await fetch(a).then((e=>e.text())),i=n.globals.get("dict")();await n.runPythonAsync(r,{globals:i}).catch(t),e(i.get("handle_request")),i.destroy()}));addEventListener("fetch",(e=>{if(n||(n=e.clientId),i)if("/pyscript.sw/update_handler"===new URL(e.request.url).pathname){const t=c();e.respondWith(t.then((()=>(o=t,new Response("OK",{headers:{"content-type":"text/plain"},status:200}))),(e=>new Response(e.message,{headers:{"content-type":"text/plain"},status:500}))))}else e.respondWith(o.then((async t=>{const[s,n,a]=await t(e.request);return new Response(s,{headers:a,status:n})})))}))}})(self);
|
||||
/*! (c) Andrea Giammarchi - ISC */
|
||||
const{isArray:e}=Array,{parse:t}=JSON,s=(e,{s:t})=>e.replace(/"s(\d+)"/g,((e,s)=>t[s])),n=(e,s)=>t(e.replace(/(\S+?)\s*=/g,'"$1":'),((e,t)=>"string"==typeof t?s[t[0]][t.slice(1)]:t)),a=(t,n,a,r)=>{for(let i=0,{length:o}=t,c=o-1;i<o;i++){const o=s(t[i],n);a=a[o]||(a[o]=r&&i===c?[]:{}),e(a)&&(i!==c&&a.length||a.push({}),a=a.at(-1))}return a};var r=Object.freeze({__proto__:null,parse:e=>{const[t,r]=((e,t,s)=>[e.replace(/(["'])(?:(?=(\\?))\2.)*?\1/g,(e=>`"s${t.push(e.slice(1,-1))-1}"`)).replace(/\d{2,}([:-]\d{2}){2}([ T:-][\dZ:-]+)?/g,(e=>`"d${s.push(new Date(e))-1}"`)).replace(/,\s*[\r\n]+/g,", ").replace(/\[\s*[\r\n]+/g,"[").replace(/[\r\n]+\s*]/g,"]"),{s:t,d:s}])(e,[],[]),i={};let o=i;for(let e of t.split(/[\r\n]+/))if((e=e.trim())&&!e.startsWith("#"))if(/^(\[+)(.*?)\]+/.test(e))o=a(RegExp.$2.trim().split("."),r,i,"["!==RegExp.$1);else if(/^(\S+?)\s*=([^#]+)/.test(e)){const{$1:e,$2:t}=RegExp;o[s(e,r)]=n(t.trim(),r)}return i}})}();
|
||||
16
pyscript.sw/rollup/sw.config.mjs
Normal file
16
pyscript.sw/rollup/sw.config.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
// This file generates /core.js minified version of the module, which is
|
||||
// the default exported as npm entry.
|
||||
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
import terser from "@rollup/plugin-terser";
|
||||
|
||||
export default {
|
||||
input: "./src/sw.js",
|
||||
plugins: process.env.NO_MIN ? [nodeResolve()] : [nodeResolve(), terser()],
|
||||
output: {
|
||||
esModule: false,
|
||||
inlineDynamicImports: true,
|
||||
file: "./pyscript.sw.js",
|
||||
format: "iife",
|
||||
},
|
||||
};
|
||||
154
pyscript.sw/src/sw.js
Normal file
154
pyscript.sw/src/sw.js
Normal file
@@ -0,0 +1,154 @@
|
||||
// NOTE: this file must be a single JS file so that we can distribute it
|
||||
// as standalone "one-shop" entry for the Service Worker, i.e.:
|
||||
// <script src="../pyscript.sw.js" handler="./file.py"></script>
|
||||
|
||||
// prevent global scope pollution with local references
|
||||
// this callback works in both the main page and in the SW
|
||||
(({ document, navigator: { serviceWorker } }) => {
|
||||
// here we are on the main page
|
||||
if (document) {
|
||||
const { href } = location;
|
||||
const { controller } = serviceWorker;
|
||||
|
||||
const { currentScript } = document;
|
||||
const {
|
||||
src,
|
||||
attributes: { config, handler, scope },
|
||||
} = currentScript;
|
||||
|
||||
// send files to fetch and handle to the Service Worker
|
||||
// after resolving the path through current page location
|
||||
// (not the service worker one as these likely have different relative folders)
|
||||
if (controller) {
|
||||
controller.postMessage({
|
||||
config: config?.value ? new URL(config.value, href).href : "",
|
||||
// handler is mandatory (or it means there's nothing to run as Python)
|
||||
handler: new URL(handler.value, href).href,
|
||||
});
|
||||
}
|
||||
|
||||
// do reload automatically once everything has been bootstrapped
|
||||
serviceWorker.addEventListener("message", ({ data }) => {
|
||||
if (data === "reload") location.reload();
|
||||
});
|
||||
|
||||
// register the Service Worker with an optional scope ...
|
||||
serviceWorker
|
||||
.register(src, { scope: scope?.value || "." })
|
||||
.then((registration) => {
|
||||
// ... once registered, let the SW automatically handle the page reload
|
||||
registration.addEventListener("updatefound", () =>
|
||||
location.reload(),
|
||||
);
|
||||
if (registration.active && !controller) location.reload();
|
||||
});
|
||||
}
|
||||
// here we are on the Service Worker
|
||||
else {
|
||||
const indexURL = "https://cdn.jsdelivr.net/pyodide/v0.23.4/full";
|
||||
|
||||
// because of this issue https://github.com/w3c/ServiceWorker/issues/1356
|
||||
// the pyodide must be loaded sync ASAP or `importScripts` will fail
|
||||
importScripts(`${indexURL}/pyodide.js`);
|
||||
|
||||
// ⚠️ WARNING: this will be inlined by rollup - can't be used AS IS
|
||||
const parse = async (text) => (await import('basic-toml')).parse(text);
|
||||
|
||||
// this is still not blocking so no problems should happen ... we can bring anything
|
||||
// directly from the CDN at this point, as pyodide is flagged as secure script here
|
||||
const interpreter = loadPyodide({ indexURL });
|
||||
|
||||
// skip waiting on installation and ensure activation
|
||||
// this will trigger the automatic reload on the main page
|
||||
// once the Service Worker is ready to operate
|
||||
addEventListener("install", () => skipWaiting());
|
||||
addEventListener("activate", (e) => e.waitUntil(clients.claim()));
|
||||
|
||||
let // used to postMessage a reload when everything is ready
|
||||
clientId,
|
||||
// keeps the handler path known for future updates
|
||||
handlerPath,
|
||||
// let fetch operations through until there is a handler
|
||||
handleReady = false,
|
||||
// wait for the postMessage to communicate where is the python file
|
||||
// and where is the config, if any
|
||||
handleRequest = new Promise(resolve => {
|
||||
addEventListener(
|
||||
"message",
|
||||
async ({ data: { config, handler } }) => {
|
||||
if (config) {
|
||||
const pyodide = await interpreter;
|
||||
const deps = [
|
||||
fetch(config),
|
||||
pyodide.loadPackage("micropip"),
|
||||
];
|
||||
|
||||
// assign the right body retriever accordingly
|
||||
// with the config extension
|
||||
deps[0] = config.endsWith(".json") ?
|
||||
deps[0].then((b) => b.json()) :
|
||||
deps[0].then((b) => b.text()).then(parse);
|
||||
|
||||
const [{ packages }] = await Promise.all(deps);
|
||||
const micropip = await pyodide.pyimport("micropip");
|
||||
await micropip.install(packages);
|
||||
}
|
||||
handlerPath = handler;
|
||||
const result = await getHandler();
|
||||
handleReady = true;
|
||||
const client = await clients.get(clientId);
|
||||
client.postMessage("reload");
|
||||
resolve(result);
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
|
||||
// used to update the handler when '/pyscript.sw/update_handler'
|
||||
// path is reached and the service worker is already initialized
|
||||
const getHandler = () => new Promise(async (resolve, reject) => {
|
||||
const pyodide = await interpreter;
|
||||
const code = await fetch(handlerPath).then(b => b.text());
|
||||
const globals = pyodide.globals.get("dict")();
|
||||
await pyodide.runPythonAsync(code, { globals }).catch(reject);
|
||||
resolve(globals.get("handle_request"));
|
||||
globals.destroy();
|
||||
});
|
||||
|
||||
addEventListener("fetch", (event) => {
|
||||
if (!clientId) clientId = event.clientId;
|
||||
if (!handleReady) return;
|
||||
// this switch is to allow possible future operations too
|
||||
switch (new URL(event.request.url).pathname) {
|
||||
// reserved: this is our namespace to operate internally
|
||||
case '/pyscript.sw/update_handler':
|
||||
const newHandler = getHandler();
|
||||
event.respondWith(newHandler.then(
|
||||
() => {
|
||||
// only if successful the handleRequest is re-assigned
|
||||
handleRequest = newHandler;
|
||||
return new Response('OK', {
|
||||
headers: {'content-type': 'text/plain'},
|
||||
status: 200
|
||||
});
|
||||
},
|
||||
error => new Response(error.message, {
|
||||
headers: {'content-type': 'text/plain'},
|
||||
status: 500
|
||||
})
|
||||
));
|
||||
break;
|
||||
default:
|
||||
event.respondWith(
|
||||
handleRequest.then(async (handler) => {
|
||||
const [text, status, headers] = await handler(
|
||||
event.request,
|
||||
);
|
||||
return new Response(text, { headers, status });
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
})(self);
|
||||
21
pyscript.sw/test/index.html
Normal file
21
pyscript.sw/test/index.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<style>body { font-family: sans-serif; }</style>
|
||||
<script src="../pyscript.sw.js" handler="./test.py" config="./test.toml"></script>
|
||||
<script type="module">
|
||||
// this is just to show some progress
|
||||
const span = document.querySelector('span');
|
||||
let i = 0;
|
||||
(function dot() {
|
||||
span.textContent = '.'.repeat(++i % 6);
|
||||
setTimeout(dot, 300);
|
||||
}());
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Loading PyScript Service Worker <span></span>
|
||||
</body>
|
||||
</html>
|
||||
59
pyscript.sw/test/test.py
Normal file
59
pyscript.sw/test/test.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import random
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import HTMLResponse
|
||||
from httpx import AsyncClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
base = "/test"
|
||||
|
||||
|
||||
def generate_html_response():
|
||||
html_content = """
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>PyScript Service Worker</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>PyScript from a service worker 🦄</h1>
|
||||
<h2>FastAPI demo</h2>
|
||||
<ul>
|
||||
<li>Test some random <a href="./json">json</a></li>
|
||||
<li>Test some random <a href="./emoji">emoji</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
return HTMLResponse(content=html_content, status_code=200)
|
||||
|
||||
|
||||
@app.get(base + "/", response_class=HTMLResponse)
|
||||
async def root():
|
||||
return generate_html_response()
|
||||
|
||||
|
||||
# used to test errors forwarded as 500
|
||||
# shenanigans(1, 2, 3)
|
||||
|
||||
|
||||
@app.get(base + "/json")
|
||||
async def json():
|
||||
# used to test that file changes actually happen when
|
||||
# '/pyscript.sw/update_handler' is reached
|
||||
# print(base + "/json")
|
||||
return {"message": random.choice(["Hello World", "Bonjour le monde", "Hola Mundo"])}
|
||||
|
||||
|
||||
@app.get(base + "/emoji")
|
||||
async def emoji():
|
||||
return {"emoji": random.choice(["👋", "👋🏻", "👋🏼", "👋🏽", "👋🏾", "👋🏿"])}
|
||||
|
||||
|
||||
async def handle_request(request):
|
||||
async with AsyncClient(app=app, base_url="http://testserver") as client:
|
||||
response = await client.get(request.url)
|
||||
|
||||
return response.text, response.status_code, response.headers.items()
|
||||
1
pyscript.sw/test/test.toml
Normal file
1
pyscript.sw/test/test.toml
Normal file
@@ -0,0 +1 @@
|
||||
packages = ["ssl", "httpx", "fastapi"]
|
||||
Reference in New Issue
Block a user