Compare commits

...

70 Commits

Author SHA1 Message Date
Fabio Pliger
db66bb71e8 lay down the features page sections 2023-09-06 22:03:07 -05:00
Fabio Pliger
fc89d157ce add more sections 2023-09-06 16:07:37 -05:00
Fabio Pliger
4658b6d9a1 improve readability of index 2023-09-06 16:02:46 -05:00
Fabio Pliger
8cb5294f2a add new docs README 2023-09-05 17:29:38 -05:00
Fabio Pliger
f226506856 add complex application example 2023-09-05 17:24:08 -05:00
Fabio Pliger
c739e23cc5 add paragraph about serving application and improve application concepts 2023-09-05 16:55:43 -05:00
Fabio Pliger
5f93eb24bb complete first example 2023-09-05 16:11:30 -05:00
Fabio Pliger
da5569871e better working on first sections of getting started 2023-09-05 15:53:54 -05:00
Fabio Pliger
e33095249c improve working 2023-09-05 15:39:15 -05:00
Fabio Pliger
efcd872ece work on hello world example 2023-09-05 15:32:52 -05:00
Fabio Pliger
829e4f89f1 add basic concepts 2023-09-05 14:36:39 -05:00
Fabio Pliger
0e710461fe improve development setup 2023-09-05 14:18:59 -05:00
Fabio Pliger
bed56df606 add new docs folder 2023-09-05 14:05:01 -05:00
Fabio Pliger
78aa257a21 update getting started 2023-09-05 14:01:56 -05:00
Fabio Pliger
a43dab9850 simplify getting started first application and serving application paragraphs 2023-09-01 16:00:08 -05:00
Fabio Pliger
e63ce9b685 simplify installation section in the getting started section 2023-09-01 14:59:33 -05:00
Fabio Pliger
93e4e485ff add better description and text around environment setup in getting started 2023-09-01 14:55:24 -05:00
Fabio Pliger
df7be28c18 change intro text in the main index page 2023-09-01 14:54:25 -05:00
Andrea Giammarchi
da3b43abdd [next] xworker.sync pollution example (#1659) 2023-08-31 15:37:17 +02:00
Andrea Giammarchi
4cc9647dc6 [next] allow document.createElement(py-script) (#1662) 2023-08-31 14:23:01 +02:00
Andrea Giammarchi
74cd7c840a [next] Sanitize <py-script> content + deprecate html content (#1663) 2023-08-31 10:43:28 +02:00
Andrea Giammarchi
0f2deeb71a [next] Place a target in the body (#1658) 2023-08-30 14:43:48 +02:00
Andrea Giammarchi
93539c9b5a Fix #1651 - Avoid leaks from the registered module (#1655) 2023-08-29 22:32:05 +02:00
Andrea Giammarchi
e48e6276e1 [next] PyScript Next basic documentation (#1634) 2023-08-17 08:12:55 -07:00
Andrea Giammarchi
75a57a49f5 [next] Porting most basic examples (#1631) 2023-08-10 22:42:01 +02:00
Andrea Giammarchi
8a1db288fc [next] Updated polyscript to use PyScript custom stderr when running code (#1629) 2023-08-09 19:30:18 +02:00
Andrea Giammarchi
84dcde188b [next] Bring in the good old PyScript display (#1628) 2023-08-09 16:28:06 +02:00
Andrea Giammarchi
27c91e9703 [next] Updated LICENSE & dependencies (#1626) 2023-08-08 17:16:43 +02:00
Andrea Giammarchi
b5a0cd4057 [sw] Added a first version of the PyScript Service Worker (#1621)
* Added a first version of the PyScript Service Worker

* Fix typo as suggested

Co-authored-by: Fabio Pliger <fpliger@users.noreply.github.com>

---------

Co-authored-by: Fabio Pliger <fpliger@users.noreply.github.com>
2023-08-08 10:57:26 +02:00
Andrea Giammarchi
77d8fe3562 [next] Improved worker attribute DX (#1625) 2023-08-07 18:36:33 +02:00
Andrea Giammarchi
a484aff457 Moved back pyscript.core where it belongs (#1622) 2023-08-07 15:15:05 +02:00
Andrea Giammarchi
c96f5912df Prepared for the first publish (#1620) 2023-08-03 11:22:16 +02:00
Andrea Giammarchi
8a01a56e51 Add pyscript module in both Main and Workers (#1619) 2023-08-03 10:44:17 +02:00
Andrea Giammarchi
2774e49ab9 [next] Brought back home PyScript Next (#1616) 2023-08-01 22:28:57 +02:00
Andrea Giammarchi
26e7a54f1f @pyscript/core deprecation (#1607) 2023-07-21 11:07:42 +02:00
Jack Zhao
f0e69cbc36 fix interpreter absoluteURL (#1603) 2023-07-19 13:21:44 +02:00
Andrea Giammarchi
413428f535 [next] Better Errors - Worker (#1602) 2023-07-19 13:20:03 +02:00
Andrea Giammarchi
0c54036466 [next] Add basic integrations tests (#1576)
* [next] WIP: Add basic integrations tests

* fix typos in  README.md

* Update README.md to specify when files are _ files ignored

---------

Co-authored-by: Fabio Pliger <fpliger@users.noreply.github.com>
2023-07-19 10:18:17 +02:00
Andrea Giammarchi
2555833831 [next] FS create folder before writing in it (#1582) 2023-07-17 13:46:17 +02:00
Andrea Giammarchi
7e0aceced1 [next] Updated to latest coincident + MicroPython (#1595) 2023-07-13 17:00:04 +02:00
Andrea Giammarchi
77234f6df3 [next] Updated coincident to fix a MicroPython bug (#1591) 2023-07-12 17:55:39 +02:00
Andrea Giammarchi
45af96aad4 [next] Updated to latest MicroPython (#1590) 2023-07-12 10:45:46 +02:00
Andrea Giammarchi
184d29055e [next] Await events when invoked (#1589) 2023-07-11 13:27:59 +02:00
Andrea Giammarchi
9e73181816 [next] Use the very same source for all Python interpreters (#1588) 2023-07-10 17:54:13 +02:00
Andrea Giammarchi
0b0e03456c [next] Improve the whole events story (#1584) 2023-07-10 15:36:48 +02:00
Andrea Giammarchi
c6b5ce7f55 [next] Drop web like events (#1578)
* Use registerJSModule when available (#1573)

* Updated version to publish latest
2023-07-06 17:56:06 +02:00
Andrea Giammarchi
a14e701be4 Use registerJSModule when available (#1573) 2023-06-29 22:50:35 +02:00
Andrea Giammarchi
7813c3f03f Added npm run dev (#1572) 2023-06-29 12:45:07 +02:00
Andrea Giammarchi
3a3cb7b11d Align npm versioning (#1568) 2023-06-27 22:34:10 +02:00
Andrea Giammarchi
d7b0731385 Added a section dedicated to XWorker required headers (#1567) 2023-06-27 20:56:16 +02:00
Andrea Giammarchi
df8973736f Update latest package.json version + lock (#1565) 2023-06-27 17:11:53 +02:00
Andrea Giammarchi
9121071ba3 PyScript Core Documentation (#1560) 2023-06-27 16:53:19 +02:00
Andrea Giammarchi
bf6470c046 Cleanup and improve the index.html for tests (#1558) 2023-06-22 21:37:11 +02:00
Andrea Giammarchi
3b7099cd3d Fixed issues around XWorker context (#1556) 2023-06-22 19:04:57 +02:00
Andrea Giammarchi
f6dfc5361e Implement PyScript custom <script type> (#1548)
* updated MicroPython to latest in order to have `globals` API available
  * reduced code around helpers for both MicroPython and Pyodide as now these are more aligned
  * updated all dependencies and brought in latest [coincident/window](https://github.com/WebReflection/coincident#coincidentwindow) goodness to any `xworker`, preserving the `sync` previous behavior
  * using [@ungap/structured-clone/json](https://github.com/ungap/structured-clone#tojson) as *coincident* default `parse` and `stringify` utility to allow recursive and more complex data to travel back from the *Worker* (forward data is still fully [structured clone algorithm compatible](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm))
  * renamed all *plugin/s* references to *custom/s* as plugin as a word was too misleading
  * changed *custom types* helpers logic to allow any single node to have its own version of the interpreter wrapper, and all the extra fields it carries with it, including a way to augment every interpreter execution, among as every worker code execution
  * created a `custom` folder where I've landed the very first `pyscript.js` custom type
  * created an exhaustive test page to demonstrate the current abilities of *PyScript Next* among its ability to expose utilities that can be used to create *PyScript* plugins
2023-06-22 17:29:07 +02:00
Andrea Giammarchi
0a7e1ce0d7 Fix #1531 - Remove overall need for globalThis pollution (#1543) 2023-06-19 18:05:45 +02:00
Andrea Giammarchi
d6b1c393f6 MicroPython as CDN (#1521) 2023-06-19 12:06:15 +02:00
Andrea Giammarchi
bccd5e3750 Fix #1538 - use same customElements Registry utilities (#1542) 2023-06-16 15:34:05 +02:00
Andrea Giammarchi
6df5905b2b [next] Ditch handy shortcuts for good (#1537) 2023-06-15 17:08:28 +02:00
Andrea Giammarchi
6284c02032 [next] Rename all runtime(s) references to interpreter(s) (#1536) 2023-06-15 15:34:07 +02:00
Andrea Giammarchi
db27d52352 [next] Update MicroPython and other dependencies (#1535) 2023-06-15 14:04:47 +02:00
Fábio Rosado
8ba28989fb Add ci for next (#1530)
* Add ci for next

* Install deps from the right folder and build the project

* Rename CI job for test next

* Remove all things pyscriptjs

* Remove ESLint job
2023-06-14 16:01:01 +01:00
Andrea Giammarchi
da544929ac [next] Enabled hooks around plugins (#1522) 2023-06-12 22:18:55 +02:00
Andrea Giammarchi
bb364b0524 Shared array buffer missing error (#1518)
Improve error reporting around SharedArrayBuffer
2023-06-09 20:51:51 +02:00
Andrea Giammarchi
818614b798 Improved Promise polyfill for MicroPython only (#1517) 2023-06-09 12:53:31 +02:00
Andrea Giammarchi
50b1a1d7c5 Fix #1512 - Improve worker tests + update Pyodide (#1513)
* Fix #1512 - Improve worker tests + update Pyodide

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

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

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

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

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-08 17:39:01 +02:00
Andrea Giammarchi
7d3b792a79 Fix #1514 - Provide a better feedback on HTML typos (#1515) 2023-06-08 13:49:49 +02:00
Andrea Giammarchi
af72e232c3 Worker sync utility (#1511)
* patched an issue with wasmoon randomly asking to resolve proxy references
  * simplified pyodide and micropython dance by grouping their common utilities together
  * created an integration test around a worker to main thread input between MicroPython and Lua
  * commented some weird bugs / funny behaviors around both MicroPython and Pyodide
  * other minor clean ups
2023-06-08 11:10:47 +02:00
Andrea Giammarchi
0cdbfbeb30 Updated MicroPython (#1510)
Updated MicroPython
2023-06-06 11:42:23 +02:00
Andrea Giammarchi
339e40063a WIP: Bringing PyScript.next PoC to the main project (#1507)
* kill unwrapped_remote (#1490)

* kill unwrapped_remote

* linting

* don't use callKwargs for python plugins

* fix tests and improve types

* Bringing PyScript.next PoC to the main project

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

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

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

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

---------

Co-authored-by: Madhur Tandon <20173739+madhur-tandon@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-05 21:52:28 +02:00
94 changed files with 5473 additions and 72 deletions

74
.github/workflows/test-next.yml vendored Normal file
View 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

1
.gitignore vendored
View File

@@ -73,6 +73,7 @@ instance/
# Sphinx documentation
docs/_build/
docs/_env/
newdocs/_env/
# PyBuilder
target/

View File

@@ -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

View File

@@ -2,11 +2,24 @@
Welcome to the PyScript documentation!
PyScript provides a way for you to run Python code directly in your browser, giving
anyone the ability to program without infrastructure barriers. Add an interactive
Python REPL directly to your website, share an interactive dashboard with a colleague
as an HTML file, or create a client-side Python-powered web application. This documentation
will show you how.
PyScript is a programming platform that allows you to create web applications that run in the browser, using Python.
That creates some really interesting benefits:
* Using Python directly in the browser allows to create applications with an easier and more user friendly language
* Scalability: since applications run directly in the browser and not on a server somewhere, servers don't need to
scale as much if the number of users of an application grows exponentially
* Shareability: applications can be shared as easily as sharing an URL. Can't get easier than that ;)
* Multi-Platform support: since the browser is the underlying system where PyScript applications run, applications
can run anywhere a modern browser is installed, on windows, linux, mac, mobile, or even a Tesla! :)
* Security: since PyScript runs core in the Browser (via Web Assembly) in a sandbox fashion and the browsers offers
a very strict level of security, code never have access files or part of the underlying system without user permission,
making it a great option in terms of security.
* User Friendly APIs: web APIs are very vast and, sometimes, complicated. PyScript offers smaller and more user friendly
APIs for the most common use cases while also providing an option to access the full Web APIs as well.
We hope you'll enjoy the project and create so many incredible things with it! To learn more, consult our documentation.
::::{grid} 2
:gutter: 3

View File

@@ -1,56 +1,93 @@
# Getting started with PyScript
This page will guide you through getting started with PyScript.
To start developing a PyScript, like with most applications development, you need a **development environment** where you
write your code, a way to install the programming libraries and dependencies your code needs, and way to build and distribute
your application.
Luckily, PyScript makes many of these steps much easier.
## Development setup
PyScript does not require any development environment other
PyScript does not require any specific development environment other
than a web browser (we recommend using [Chrome](https://www.google.com/chrome/)) and a text editor, even though using your [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) of choice might be convenient.
If you're using [VSCode](https://code.visualstudio.com/), the
[Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
can be used to reload the page as you edit the HTML file.
**NOTE:** The easier way to get a development setup for PyScript is to use [pyscript.com](pyscript.com). It is a free service that allows
users to create new projects from pre-created templates that already have all the project structure created and allows users
to edit their apps, preview it and deploy with just a link, all in the same place.
## Installation
There is no installation required. In this document, we'll use
the PyScript assets served on [https://pyscript.net](https://pyscript.net).
There is no PyScript specific installation required in your system to start using PyScript in your browser. All you need to do is to
simply add a reference in your application code to where your application should get PyScript from.
If you want to download the source and build it yourself, follow
the instructions in the [README.md](https://github.com/pyscript/pyscript/blob/main/README.md) file.
If you are not an experienced developer and it all sounds very complicated, don't worry, we'll get you through it in the following steps.
## Your first PyScript HTML file
## Writing your first PyScript application
Here's a "Hello, world!" example using PyScript.
As we hinted earlier, writing a PyScript application means writing a web application that can run code writted in Python (and other languages)
on the web. This means that the way we create PyScript applications starts in a very similar way to how we write web applications: from an
HTML file.
Using your favorite editor, create a new file called `hello.html` in
the same directory as your PyScript, JavaScript, and CSS files with the
following content, and open the file in your web browser. You can typically
open an HTML by double-clicking it in your file explorer.
To demonstrate the above, let's start from the most popular "first application example": let's write a "Hello, world!"
example using PyScript.
Using your favorite editor, create a new file called `hello.html` and paste in the following content and open it in your web browser. (You can typically
open an HTML by double-clicking it in your file explorer.):
```html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>My First PyScript APP: Hello World!</title>
<script
type="module"
src="https://esm.sh/@pyscript/core@latest/core.js"
></script>
</head>
<body>
<py-script>
from pyscript import display
print('Hello, World!')
display('Hello, World!')
</py-script>
</body>
</html>
```
### Using a Local Server
<a href="https://fpliger.pyscriptapps.com/hello-world-minimal-example/latest/" target="_blank">or open this example on pyscript.com</a>
In some situations, your browser may forbid loading remote resources like `pyscript.js` and `pyscript.css` when you open an HTML file directly. When this is the case, you may see your Python code in the text of the webpage, and the [browser developer console](https://balsamiq.com/support/faqs/browserconsole/) may show an error like *"Cross origin requests are only supported for HTTP."* The fix for this is to use a [simple local server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/set_up_a_local_testing_server) to make your html file available to the browser.
You should see "Hello World!" printed in your page and in your Javascript Console (don't worry if
you don't know what it means yet, we'll get into that later).
If you have python installed on your system, you can use it's basic built-in server for this purpose via the command line. Change the current working directory of your terminal or command line to the folder where your HTML file is stored. From this folder, run `python -m http.server 8080 --bind 127.0.0.1` in your terminal or command line. With the server program running, point your browser to `http://localhost:8080` to view the contents of that folder. (If a file in that folder is called `index.html`, it will be displayed by default.)
## Serving your application
Now what we have written our first application, it's important talk about how we can access it.
In the example above, we were able to visualize it by simply opening the local file from our
system directly with the browser. While that's a very simple and fast way to open our application,
it is not very recommended because browsers will forbid many features when accessing files this way,
for security reasons. When this is the case, you may see your Python code in the text of the webpage,
and the [browser developer console](https://balsamiq.com/support/faqs/browserconsole/) may show an
error like *"Cross origin requests are only supported for HTTP."*
In short, when browsers visualize a web page, they expect them to be served by a
web server.
For the rest of this documentation, we'll be presenting examples and snippets and host them on
pyscript.com. Users can reference the [serving your application](serving-your-application.md) at
anytime for other options.
## A more complex example
Now that we know how you can create a simple 'Hello, World!' example, let's see a more complex example. This example will use the Demo created by [Cheuk Ting Ho](https://github.com/Cheukting). In this example, we will use more features from PyScript.
Now that we know how you can create a simple 'Hello, World!' example, let's see a more complex example.
This example will use the Demo created by [Cheuk Ting Ho](https://github.com/Cheukting). In this example, we will use more features from PyScript.
### Setting up the base index file

View File

@@ -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

View File

@@ -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">

View File

@@ -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

View File

@@ -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"))

View File

@@ -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>

View File

@@ -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",

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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)

View File

@@ -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();

46
newdocs/Makefile Normal file
View File

@@ -0,0 +1,46 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
CONDA_ENV ?= _env
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
env := $(CONDA_ENV)
conda_run := conda run -p $(env)
setup:
@if [ -z "$${CONDA_SHLVL:+x}" ]; then echo "Conda is not installed." && exit 1; fi
$(CONDA_EXE) env $(shell [ -d $(env) ] && echo update || echo create) -p $(env) --file environment.yml
clean:
rm -rf $(BUILDDIR)
clean-all: clean
rm -rf $(env) *.egg-info
shell:
@export CONDA_ENV_PROMPT='<{name}>'
@echo 'conda activate $(env)'
htmlserve: html
@echo 'visit docs at http://localhost:8080'
python -m http.server -d "$(BUILDDIR)/html/" 8080
livehtml:
sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile setup clean clean-all shell
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

56
newdocs/README.md Normal file
View File

@@ -0,0 +1,56 @@
# PyScript documentation
Welcome to the PyScript documentation directory, where you can find
and contribute to discussions around PyScript and related topics.
## Getting started
Before you start contributing to the documentation, it's worthwhile to
take a look at the general contributing guidelines for the PyScript project. You can find these guidelines here
[Contributing Guidelines](https://github.com/pyscript/pyscript/blob/main/CONTRIBUTING.md)
## Documentation Principles
The PyScript documentation is based on a documentation framework called [Diátaxis](https://diataxis.fr/). This framework helps to solve the problem of structure in technical documentation and identifies four modes of documentation - **tutorials, how-to guides, technical reference and explanation**. Each one of these modes answers to a different user need, fulfills a different purpose and requires a different approach to its creation.
The picture below gives a good visual representation of that separation of concerns:
![pyodide-pyscript](./img/diataxis.png)
So, please keep that in mind when contributing to the project documentation. For more information on, make sure to check [their website](https://diataxis.fr/).
### Setup
The `docs` directory in the pyscript repository contains a
[Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) documentation project. Material is a system
that takes plaintext files containing documentation written in Markdown, along with
static files like templates and themes, to build the static end result.
To setup the documentation development environment simply run `make setup` from this folder and, once it's done,
activate your environment by running `conda activate ./_env`
### Build
Simply run `mkdocs serve`
## Cross-referencing
You can link to other pages in the documentation by using the `{doc}` role. For example, to link to the `docs/README.md` file, you would use:
```markdown
{doc}`docs/README.md`
```
You can also cross-reference the python glossary by using the `{term}` role. For example, to link to the `iterable` term, you would use:
```markdown
{term}`iterable`
```
You can also cross-reference functions, methods or data attributes by using the `{attr}` for example:
```markdown
{py:func}`repr`
```
This would link to the `repr` function in the python builtins.

View File

@@ -0,0 +1 @@
# Advanced User Guide

View File

@@ -0,0 +1,16 @@
<svg width="100%" viewBox="0 0 2057 974" xmlns="http://www.w3.org/2000/svg">
<g fill="#000000" stroke="none" transform="translate(0 100)">
<path
d="M 1092.534 158.364 C 1095.764 169.589 1102.374 179.795 1107.224 190.364 C 1119.104 216.243 1131.874 241.728 1144.274 267.364 C 1179.204 339.56 1214.064 411.844 1248.314 484.364 C 1260.474 510.112 1273.154 535.617 1285.314 561.364 C 1290.014 571.319 1299.154 583.378 1300.684 594.364 C 1301.444 599.785 1296.944 606.478 1294.984 611.364 C 1289.004 626.289 1282.004 640.557 1273.734 654.364 C 1265.284 668.483 1256.704 683.257 1245.444 695.364 C 1237.304 704.123 1228.664 712.851 1218.534 719.31 C 1176.654 746.023 1130.104 739.811 1084.534 729.364 L 1084.534 796.364 C 1137.744 803.235 1191.744 806.988 1241.534 782.094 C 1291.224 757.25 1321.144 708.125 1345.794 660.364 C 1391.424 571.949 1425.474 477.074 1463.954 385.364 C 1484.774 335.759 1505.144 285.968 1525.954 236.364 C 1532.804 220.048 1539.454 203.643 1546.384 187.364 C 1550.314 178.14 1555.824 168.274 1557.534 158.364 L 1503.534 158.364 C 1498.104 158.364 1487.624 156.363 1482.924 159.392 C 1477.284 163.031 1474.824 176.375 1472.254 182.364 C 1463.294 203.198 1455.174 224.401 1446.524 245.364 C 1422.624 303.289 1398.764 361.248 1375.334 419.364 C 1365.024 444.923 1349.894 471.569 1343.534 498.364 L 1341.534 498.364 L 1326.784 467.364 L 1300.794 414.364 L 1219.784 248.364 L 1188.284 184.364 L 1174.894 159.392 L 1152.534 158.364 L 1092.534 158.364 Z">
</path>
<path
d="M 100.534 391.364 C 109.625 398.897 122.97 403.329 133.534 408.611 L 197.534 440.611 L 405.534 544.611 C 436.606 560.147 467.458 576.073 498.534 591.611 C 511.98 598.334 527.713 609.722 542.534 612.364 L 542.534 563.364 L 541.506 543.754 L 518.534 531.117 L 460.534 502.117 L 307.534 425.117 L 240.534 391.364 L 307.534 358.117 L 459.534 282.611 L 518.534 253.117 L 541.506 240.727 L 542.534 221.364 L 542.534 171.364 C 527.073 174.12 510.565 186.102 496.534 193.117 L 398.534 242.117 L 200.534 341.117 C 167.367 357.701 132.553 372.676 100.534 391.364 Z">
</path>
<path
d="M 1600.534 171.364 L 1600.534 220.364 C 1600.534 225.605 1598.654 235.422 1601.564 239.974 C 1605.194 245.662 1617.614 249.159 1623.534 252.117 L 1680.534 280.611 C 1730.924 305.806 1781.134 331.41 1831.534 356.611 C 1853.974 367.829 1877.404 384.412 1901.534 391.364 L 1901.534 393.364 C 1875.624 400.829 1849.674 418.049 1825.534 430.117 L 1679.534 503.117 C 1661.964 511.903 1644.564 521.567 1626.534 529.364 C 1619.964 532.203 1605.494 536.596 1601.564 542.754 C 1598.654 547.306 1600.534 557.122 1600.534 562.364 L 1600.534 612.364 L 1655.534 585.611 L 1763.534 531.611 L 1947.534 439.611 L 2041.534 392.364 C 2031.474 382.202 2012.324 376.511 1999.534 370.117 L 1907.534 324.117 L 1701.534 221.117 L 1635.534 188.117 C 1624.294 182.495 1612.624 174.847 1600.534 171.364 Z">
</path>
<path
d="M 704.534 384.364 C 704.534 374.13 702.051 360.064 705.503 350.364 C 710.589 336.071 722.183 321.459 731.164 309.364 C 737.516 300.809 743.992 292.429 750.959 284.364 C 786.81 242.863 854.576 189.488 905.519 239.403 C 931.848 265.201 939.204 301.065 941.623 336.364 C 946.631 409.413 926.04 491.22 860.534 532.928 C 811.862 563.917 757.912 556.382 704.534 545.364 Z M 705.534 259.364 L 704.534 259.364 L 704.534 158.364 L 628.534 158.364 L 628.534 789.364 L 704.534 789.364 L 704.534 613.364 C 728.157 613.38 751.915 618.29 775.534 619.325 C 816.206 621.106 857.009 614.508 893.534 596.116 C 989.069 548.011 1025.008 434.77 1024.535 335.364 C 1024.298 285.5 1013.766 232.452 979.364 194.364 C 968.209 182.013 954.851 171.287 940.534 162.816 C 875.388 124.27 794.704 158.21 745.534 207.364 C 730.887 222.007 713.84 240.114 705.534 259.364 Z">
</path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,16 @@
<svg width="100%" viewBox="0 0 2057 974" xmlns="http://www.w3.org/2000/svg">
<g fill="#fda703" stroke="none" transform="translate(0 100)">
<path
d="M 1092.534 158.364 C 1095.764 169.589 1102.374 179.795 1107.224 190.364 C 1119.104 216.243 1131.874 241.728 1144.274 267.364 C 1179.204 339.56 1214.064 411.844 1248.314 484.364 C 1260.474 510.112 1273.154 535.617 1285.314 561.364 C 1290.014 571.319 1299.154 583.378 1300.684 594.364 C 1301.444 599.785 1296.944 606.478 1294.984 611.364 C 1289.004 626.289 1282.004 640.557 1273.734 654.364 C 1265.284 668.483 1256.704 683.257 1245.444 695.364 C 1237.304 704.123 1228.664 712.851 1218.534 719.31 C 1176.654 746.023 1130.104 739.811 1084.534 729.364 L 1084.534 796.364 C 1137.744 803.235 1191.744 806.988 1241.534 782.094 C 1291.224 757.25 1321.144 708.125 1345.794 660.364 C 1391.424 571.949 1425.474 477.074 1463.954 385.364 C 1484.774 335.759 1505.144 285.968 1525.954 236.364 C 1532.804 220.048 1539.454 203.643 1546.384 187.364 C 1550.314 178.14 1555.824 168.274 1557.534 158.364 L 1503.534 158.364 C 1498.104 158.364 1487.624 156.363 1482.924 159.392 C 1477.284 163.031 1474.824 176.375 1472.254 182.364 C 1463.294 203.198 1455.174 224.401 1446.524 245.364 C 1422.624 303.289 1398.764 361.248 1375.334 419.364 C 1365.024 444.923 1349.894 471.569 1343.534 498.364 L 1341.534 498.364 L 1326.784 467.364 L 1300.794 414.364 L 1219.784 248.364 L 1188.284 184.364 L 1174.894 159.392 L 1152.534 158.364 L 1092.534 158.364 Z">
</path>
<path
d="M 100.534 391.364 C 109.625 398.897 122.97 403.329 133.534 408.611 L 197.534 440.611 L 405.534 544.611 C 436.606 560.147 467.458 576.073 498.534 591.611 C 511.98 598.334 527.713 609.722 542.534 612.364 L 542.534 563.364 L 541.506 543.754 L 518.534 531.117 L 460.534 502.117 L 307.534 425.117 L 240.534 391.364 L 307.534 358.117 L 459.534 282.611 L 518.534 253.117 L 541.506 240.727 L 542.534 221.364 L 542.534 171.364 C 527.073 174.12 510.565 186.102 496.534 193.117 L 398.534 242.117 L 200.534 341.117 C 167.367 357.701 132.553 372.676 100.534 391.364 Z">
</path>
<path
d="M 1600.534 171.364 L 1600.534 220.364 C 1600.534 225.605 1598.654 235.422 1601.564 239.974 C 1605.194 245.662 1617.614 249.159 1623.534 252.117 L 1680.534 280.611 C 1730.924 305.806 1781.134 331.41 1831.534 356.611 C 1853.974 367.829 1877.404 384.412 1901.534 391.364 L 1901.534 393.364 C 1875.624 400.829 1849.674 418.049 1825.534 430.117 L 1679.534 503.117 C 1661.964 511.903 1644.564 521.567 1626.534 529.364 C 1619.964 532.203 1605.494 536.596 1601.564 542.754 C 1598.654 547.306 1600.534 557.122 1600.534 562.364 L 1600.534 612.364 L 1655.534 585.611 L 1763.534 531.611 L 1947.534 439.611 L 2041.534 392.364 C 2031.474 382.202 2012.324 376.511 1999.534 370.117 L 1907.534 324.117 L 1701.534 221.117 L 1635.534 188.117 C 1624.294 182.495 1612.624 174.847 1600.534 171.364 Z">
</path>
<path
d="M 704.534 384.364 C 704.534 374.13 702.051 360.064 705.503 350.364 C 710.589 336.071 722.183 321.459 731.164 309.364 C 737.516 300.809 743.992 292.429 750.959 284.364 C 786.81 242.863 854.576 189.488 905.519 239.403 C 931.848 265.201 939.204 301.065 941.623 336.364 C 946.631 409.413 926.04 491.22 860.534 532.928 C 811.862 563.917 757.912 556.382 704.534 545.364 Z M 705.534 259.364 L 704.534 259.364 L 704.534 158.364 L 628.534 158.364 L 628.534 789.364 L 704.534 789.364 L 704.534 613.364 C 728.157 613.38 751.915 618.29 775.534 619.325 C 816.206 621.106 857.009 614.508 893.534 596.116 C 989.069 548.011 1025.008 434.77 1024.535 335.364 C 1024.298 285.5 1013.766 232.452 979.364 194.364 C 968.209 182.013 954.851 171.287 940.534 162.816 C 875.388 124.27 794.704 158.21 745.534 207.364 C 730.887 222.007 713.84 240.114 705.534 259.364 Z">
</path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1 @@
# Contributing

38
newdocs/docs/features.md Normal file
View File

@@ -0,0 +1,38 @@
# What is PyScript - Features
PyScript is a software platform that combines the power of the web with the power and simplicity
of the most popular programming language in the world to provide an ecosystem that let users create
and distribute applications in seconds, while having fun.
## How does it work?
The PyScript library provides HTML tags for embedding and executing Python code in your browser. PyScript is built using Pyodide, the WebAssembly port of CPython, which is compiled using Emscripten.
PyScript turns the browser into a code deployment tool that anyone can learn to use.
Example
In this example, we are using the <py-script> HTML tag to generate a Matplotlib figure and display it as an image. Click Preview to see the rendered HTML.
To try it in your browser, copy the code below into an online HTML editor like W3Schools Tryit Editor, which allows you to modify, run, and even save your code. Watch the video below to see it in action!
## Features
### Run Python code in the browser
#### In the main thread
#### In a web worker
### Environment Setup
#### Install Python Packages
#### Download files and assets
### Error reporting
### Pythonic Web APIs
### Access to Javascript

View File

@@ -0,0 +1,349 @@
# Getting started with PyScript
To start developing a PyScript, like with most applications development, you need a **development environment** where you
write your code, a way to install the programming libraries and dependencies your code needs, and way to build and distribute
your application.
Luckily, PyScript makes many of these steps much easier.
## Requirements
To visualize a PyScript application, users only need a modern web browser.
To distribute a PyScript application, the only requirement is for the application to be hosted somewhere a browser can reach.
To create a PyScript application, users need a Development Environment, often also called [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment)
where they can write their code.
**Note:** The easiest way to get the a full PyScript development environment and application hosting setup in seconds,
is to use [pyscript.com](pyscript.com) on your browser. It is a free service that helps users create new projects from
pre-created templates already structured using best practices allowing user to edit, preview and deploy their apps with
just a link, all in the same place.
### Development Environment
Like most software platforms, PyScript requires a development environment where the user can write their applications. This
means an editor where to edit files, installing all dependencies needed by the application and setting everything up so
that the application can be build and distributed. PyScript simplify these aspects for the user, reducing these needs to
an editor, a browser and ways to serve your application files.
PyScript does not require any specific development environment other than a web browser (we recommend using
[Chrome](https://www.google.com/chrome/)) and a text editor ([IDE](https://en.wikipedia.org/wiki/Integrated_development_environment))
that authors can use to write their applications. Users are free to choose according to their preference. We recommend
picking youR favorite browser and IDE, or using pyscript.com (that includes an editor in the browser itself).
**Note:** If you're using [VSCode](https://code.visualstudio.com/), the
[Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
can be used to reload the page as you edit the HTML file.
## Installation
There is no PyScript specific installation required in your system to start using PyScript in your browser. All you need to do is to
simply add a reference in your application code to where your application should get PyScript from.
If you are not an experienced developer and it all sounds very complicated, don't worry, we'll get you through it in the following steps.
### Application Development Phases
Just like with any Web Application, the development lifecycle of an application in 2 phases:
* **development:** this is where authors write their files and application logic
* **deployment:** in order to open your application in a browser, your application files need to be
"uploaded" to a web server that is able to share your application with the right format when
it's requested by a browser. This is also deferret to as "serving" web page.
Before we get into the `development` topic and have some fine writing our first applications, let's talk about
how to serve a pyscript application.
## Serving your application
While browsers are also capable of opening files from the users local system, it is not recommended because,
for security reasons, browsers will forbid and disable many features when accessing files this way.
(In this is the case, you may see your Python code in the text of the webpage, and the
[browser developer console](https://balsamiq.com/support/faqs/browserconsole/) may show an
error like *"Cross origin requests are only supported for HTTP."*)
In short, when browsers visualize a web page, they expect them to be served by a
web server.
### Serving your application from your computer
There are many ways you can initiate a local web server to serve files. We'll only cover one of them,
using `Python`.
Assuming you have `Python` installed in your system, `cd` in your application folder and run
the following python command:
```python
python3 -m http.server
```
### Serving your application from a web server
If there are many ways to serve a web application from your computer, there are many more
options on how to serve your application from a hosting service on the internet. We will not cover
this in detail and only suggest users to look into:
* pyscript.com as it's a free service and makes the process of authoring and serving an
application almost transparent.
* github pages as it's a free service and Github is a very popular service adopted by developers.
For the rest of this documentation, we'll be presenting examples and snippets and host them on
pyscript.com.
## Basic Application Concepts
While we'll cover PyScript concepts and APIs more thoroughly in the PyScript Concepts and PyScript User Guide sections, it's important
to understand the basics.
PyScript is a Software Platform that enables users to write Python Applications that run in the Browser, with a simple and user
friendly interface. For this reason, it aims to have a small and intuitive interface that triest to enable users while staying out of
the way. In fact, there are 3 main parts of a PyScript application:
1. **Presentation:** Usually this is managed in a `html` file and is also where we specify that `PyScript`` needs
to be loaded into the application.
2. **Configuration:** where users can define their dependencies, assets to be downloaded, etc. PyScript configuration
files in `TOML` or `JSON` formats
3. **Code Logic:** These are typically Python files that host the application code. PyScript allows users to run these
through special `html` tags (such as `<script type="py">` or `<py-script>`) properly placed in their `html` file.
The `html` file acts as the entry point and center of gravity of an application.
## Writing your first PyScript application
As we hinted earlier, writing a PyScript application means writing a web application that
can run code writted in Python (and other languages) on the web. This means that creating
PyScript applications starts in a very similar way to web applications: from an `html` file.
Let's start from the most basic and popular "first application example" possible, a
"Hello, world!" application! In this case we will:
1. Write an `html` file that is the main entry point for our application.
2. Load `pyscript` in our application by using: `<script type="module" src="https://esm.sh/@pyscript/core@latest/core.js"></script>`
3. Skip a configuration file for our projects and use the default since we won't need to install any additional dependencies.
4. Add a `<py-script>` tag to use as entrypoint for our Python code, that will be executed when the page loads.
**NOTE:** We highly recommend users to reproduce and interact with the examples below on their own on pyscript.com or
with their favorite Development Environment setup.
First, create a new file called `hello.html` and paste in the following content and open it in your web browser.
**Hint:** In the example below, click on the icon to read hints about specific sections in the code examples
```html title="hello_world.html"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>My First PyScript APP: Hello World!</title>
<script type="module" src="https://esm.sh/@pyscript/core@latest/core.js"></script> <!-- (1) Load PyScript -->
</head>
<body>
<!-- (2) In this case were using the default config, so don't need to specify a `<py-config>` tag -->
<!-- (3) run the code in the `main.py` file -->
<py-script src="main.py"></py-script>
</body>
</html>
```
1. ⚡️ we use a `<script>` tag to load `PyScript` in the `head` of our `HTML` document so it can
load as soon as possible
2. if needed to install any packages we could load a config in this point, so that any python code
can have their dependencies installed before they run
3. 🐍 the code in `main.py` will run inside the default `Python` interpreter as soon as it's ready
and create a new `main.py` file with the following code:
```python title="main.py"
from pyscript import display # (1)
print('Hello, World!') # print "Hello, World!" to the console
display('Hello, World!') # displays "Hello, World!" in the main page
```
1. pyscript provides the `display` funcition that can be used to display any variable on the page,
while the Python `print` statement will automatically print objects on the browser `console`.
<a href="https://fpliger.pyscriptapps.com/hello-world-minimal-example/latest/" class="md-button" target="_blank">open this example on pyscript.com</a>
When you open application in your browser, you should see `Hello, World!` printed in your page and in your Javascript Console
(if you are new to web development and don't know what it means yet, don't worry,t, we'll get into that later).
Easy, right?
### Using files vs. inline code
In the example above we wrote our Python code for the application logic in a separate file called `main.py`.
While this is a best practive and recommended, `PyScript` also allows users to write their code in the
`html` file, within the `pyscript` tag. In this case, if we rewrote the same example in a single file using
this feature, we'd have the following:
```html title="hello_world.py"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>My First PyScript APP: Hello World!</title>
<script type="module" src="https://esm.sh/@pyscript/core@latest/core.js"></script> <!-- Load PyScript -->
</head>
<body>
<!-- In this case were using the default config, so don't need to specify a `<py-config>` tag -->
<!-- (1) run the code that is defined within the <script type="py"> tag-->
<script type="py" src="main.py">
from pyscript import display # (1)
print('Hello, World!') # print "Hello, World!" to the console
display('Hello, World!') # displays "Hello, World!" in the main page
</script>
</body>
</html>
```
1. 🐍 Noticed anything different? Yes, we are passing the python code within the tag itself instead
of a separate `main.py` file.
<a href="https://fpliger.pyscriptapps.com/hello-world-minimal-example/latest/" class="md-button" target="_blank">open this example on pyscript.com</a>
If you noticed, above we are using `<script type="...">` instead of `<py-script>`. That is another way you
can run code logic in PyScript. The reason we are using `script` in this case is that the `<py-script>`
does not support inline code due to how the browser treats one vs. the other. For all use cases where
the code is defined in a separate file, both tags are equivalent
**⚠️ Important:** While very convenient, we recommend always defining your code in a separate
`.py` file as a best practice for the following reasons:
* editors don't have good support for inline code
* it's really hard to test, lint or QA code define within tags
* code can be easily exported
* both your `html` and `python` code will be easier to read and better organized
## A more complex example
Now that we know how you can create a simple 'Hello, World!' example, let's use everything
we've learned above see a more complex example.
### Setting up the base index file
Just like before, let's create a new `html` file that will contain our application template
and interface. We'll call this file `index.html` and add the following content:
```html title="index.html"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Ice Cream Picker</title>
<script type="module" src="https://esm.sh/@pyscript/core@latest/core.js"></script> <!-- (1) Load PyScript -->
</head>
<body>
<py-config src="pyscript.toml"></py-config>
<!-- (3) run the code in the `main.py` file -->
<py-script src="main.py"></py-script>
<div id="input" style="margin: 20px;">
Select your 🍨 flavour: <br/>
<input type="radio" id="all" name="flavour" value="ALL">
<label for="all"> All 🍧</label>
<input type="radio" id="chocolate" name="flavour" value="COCOA">
<label for="chocolate"> Chocolate 🍫</label>
<input type="radio" id="cherry" name="flavour" value="CHERRIES">
<label for="cherry"> Cherries 🍒</label>
<input type="radio" id="berries" name="flavour" value="BERRY">
<label for="berries"> Berries 🍓</label>
<input type="radio" id="cheese" name="flavour" value="CHEESE">
<label for="cheese"> Cheese 🧀</label>
<input type="radio" id="peanut" name="flavour" value="PEANUT">
<label for="peanut"> Peanut 🥜</label>
</div>
<div id="graph-area"></div>
</body>
</html>
```
This creates a solid base for our application and we are ready to start adding the elements we need for our application.
### Importing the needed libraries
For this example, we will need to install `pandas` and `matplotlib`. We can install libraries using the `<py-config>` tag
we already added to the `index.html` file above. All we have to do now is to create the `pyscript.toml` file with the
right dependencies:
```toml title="pyscript.toml"
packages = ["matplotlib", "pandas"]
```
For more information on the configuration files, please refer to the [`<py-config>` documentation](../reference/elements/py-config.md)
### Importing and plotting the data
Now that we have installed the needed libraries, we can import and explore the data. In this step, we need
to create a `<py-script>` tag to import our dependencies, read the data with pandas and then use `py-repl`
to explore the data.
You may want to read the [`<py-script>`](../reference/elements/py-script.md) documentation for more information about it.
```python title="main.py"
import pandas as pd
import matplotlib.pyplot as plt
from pyodide.http import open_url
from pyodide.ffi import create_proxy
from pyscript import display
import js
url = (
"https://raw.githubusercontent.com/Cheukting/pyscript-ice-cream/main/bj-products.csv"
)
ice_data = pd.read_csv(open_url(url))
current_selected = []
flavour_elements = js.document.getElementsByName("flavour")
def plot(data):
plt.rcParams["figure.figsize"] = (22,20)
fig, ax = plt.subplots()
bars = ax.barh(data["name"], data["rating"], height=0.7)
ax.bar_label(bars)
plt.title("Rating of ice cream flavours of your choice")
display(fig, target="graph-area", append=False)
def select_flavour(event):
for ele in flavour_elements:
if ele.checked:
current_selected = ele.value
break
if current_selected == "ALL":
plot(ice_data)
else:
filter = ice_data.apply(lambda x: ele.value in x["ingredients"], axis=1)
plot(ice_data[filter])
ele_proxy = create_proxy(select_flavour)
for ele in flavour_elements:
if ele.value == "ALL":
ele.checked = True
current_selected = ele.value
ele.addEventListener("change", ele_proxy)
plot(ice_data)
```
<a href="https://fpliger.pyscriptapps.com/hello-world-minimal-example/latest/" class="md-button" target="_blank">open this example on pyscript.com</a>

42
newdocs/docs/index.md Normal file
View File

@@ -0,0 +1,42 @@
# PyScript
![PyScript Logo](assets/images/pyscript.svg)
<h3 style="text-align: center; font-style: italic;">The programming Platform for the 99%</h3>
---------------------------------------------------------------------------------------------
<p style="text-align: center; font-style: italic;">Welcome to the PyScript documentation!</p>
PyScript is a programming platform that allows you to create web applications that run in
the browser, using Python. That gives anyone the ability to program without infrastructure
barriers and creates some really interesting benefits:
* **Easy start**: create and share applications in the browser, not installation required
* **Simplicity**: Code your apps with a friendly and intuitive language like Python
* **Scalability**: since applications run directly in the browser and not on a server somewhere, servers don't need to
scale as much if the number of users of an application grows exponentially
* **Shareability**: applications can be shared as easily as sharing an URL. Can't get easier than that ;)
* **Multi-Platform**: since the browser is the underlying system where PyScript applications run, applications
can run anywhere a modern browser is installed, on windows, linux, mac, mobile, or even a Tesla! :)
* **Security**: since PyScript runs core in the Browser (via Web Assembly) in a sandbox fashion and the browsers offers
a very strict level of security, code never have access files or part of the underlying system without user permission,
making it a great option in terms of security.
* **User Friendly APIs**: web APIs are very vast and, sometimes, complicated. PyScript offers smaller and more user friendly
APIs for the most common use cases while also providing an option to access the full Web APIs as well.
We hope you'll enjoy the project and create so many incredible things with it! To learn more, consult our documentation.
## What next?
Check out our [getting started](getting-started.md) section to learn where to start from or go to our
[user guide](user-guide.md) section for a more in-depth coverage of PyScript features.
<div class="grid cards" markdown>
- :fontawesome-brands-html5: __HTML__ for content and structure
- :fontawesome-brands-js: __JavaScript__ for interactivity
- :fontawesome-brands-css3: __CSS__ for text running out of boxes
- :fontawesome-brands-internet-explorer: __Internet Explorer__ ... huh?
</div>

View File

@@ -0,0 +1,37 @@
# Serving your application
Now what we have written our first application, it's important talk about how we can access it.
In the example above, we were able to visualize it by simply opening the local file from our
system directly with the browser. While that's a very simple and fast way to open our application,
it is not very recommended because browsers will forbid many features when accessing files this way,
for security reasons. When this is the case, you may see your Python code in the text of the webpage,
and the [browser developer console](https://balsamiq.com/support/faqs/browserconsole/) may show an
error like *"Cross origin requests are only supported for HTTP."*
In short, when browsers visualize a web page, they expect them to be served by a
web server. Here are a few options that we can use to fix this issue:
**NOTE:** If you are an experienced developer and already know how to host and serve files on a [static]
web server, feel free to skip to the next section.
TODO: It seems better to not go too deep into "how to serve a PyScript application" but to point to a dedicated
section where we can add more options and actually point to other resources as well.
### Using pyscript.com
If you clicked on <a href="https://fpliger.pyscriptapps.com/hello-world-minimal-example/latest/" target="_blank">the link above</a>
you've already saw how pyscript.com can be used to host PyScript applications.
All you need to do is to create a free account and copy or start creating new projects.
### Using a Local Server
A very common fix for this is to use a [simple local server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/set_up_a_local_testing_server) to make your html file available to the browser.
If you have python installed on your system, you can use it's basic built-in server for this purpose via the command line.
Change the current working directory of your terminal or command line to the folder where your HTML file is stored.
From this folder, run `python -m http.server 8080 --bind 127.0.0.1` in your terminal or command line. With the server
program running, point your browser to `http://localhost:8080` to view the contents of that folder. (If a file in
that folder is called `index.html`, it will be displayed by default.)

View File

@@ -0,0 +1 @@
# User Guide

7
newdocs/environment.yml Normal file
View File

@@ -0,0 +1,7 @@
channels:
- conda-forge
- defaults
dependencies:
- python=3.9
- pip=20.2.2
- mkdocs-material=9.2.6

45
newdocs/mkdocs.yml Normal file
View File

@@ -0,0 +1,45 @@
site_name: PyScript
theme:
name: material
logo: assets/images/pyscript-black.svg
palette:
# Palette toggle for automatic mode
- media: "(prefers-color-scheme)"
primary: orange
toggle:
icon: material/brightness-auto
name: Switch to light mode
# Palette toggle for light mode
- media: "(prefers-color-scheme: light)"
primary: orange
scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
# Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)"
primary: orange
scheme: slate
toggle:
icon: material/brightness-4
name: Switch to system preference
features:
- content.code.copy
- content.code.annotate
markdown_extensions:
- attr_list
- md_in_html
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences

5
newdocs/robots.txt Normal file
View File

@@ -0,0 +1,5 @@
User-agent: *
Disallow: /review/
Sitemap: https://docs.pyscript.net/sitemap.xml
Host: docs.pyscript.net

5
pyscript.core/.npmignore Normal file
View File

@@ -0,0 +1,5 @@
node_modules/
rollup/
test/
package-lock.json
tsconfig.json

203
pyscript.core/LICENSE Normal file
View 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
View 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
View File

@@ -0,0 +1 @@
py-config,py-script{display:none}

2
pyscript.core/core.js Normal file

File diff suppressed because one or more lines are too long

View 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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
{
"name": "@pyscript/core",
"version": "0.1.6",
"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.13"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-terser": "^0.4.3",
"rollup": "^3.28.1",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-string": "^3.0.0",
"static-handler": "^0.4.2",
"typescript": "^5.2.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pyscript/pyscript.git"
},
"bugs": {
"url": "https://github.com/pyscript/pyscript/issues"
},
"homepage": "https://github.com/pyscript/pyscript#readme"
}

View File

@@ -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);
},
};

View File

@@ -0,0 +1,24 @@
// 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",
},
};

View File

@@ -0,0 +1,4 @@
py-script,
py-config {
display: none;
}

282
pyscript.core/src/core.js Normal file
View File

@@ -0,0 +1,282 @@
import "@ungap/with-resolvers";
import { $ } from "basic-devtools";
import { define, XWorker } from "polyscript";
import { htmlDecode } from "./utils.js";
import sync from "./sync.js";
// this is imported as string (via rollup)
import display from "./display.py";
// 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 { assign, 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, asText) => {
if (tag.hasAttribute("src")) {
try {
return await fetch(tag.getAttribute("src")).then(getText);
} catch (error) {
io.stderr(error);
}
}
if (asText) return tag.textContent;
console.warn(
'Deprecated: use <script type="py"> for an always safe content parsing:\n',
tag.innerHTML,
);
return htmlDecode(tag.innerHTML);
};
// 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);
};
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 as window",
"document=window.document",
display,
"display",
].join("\n"),
// avoid leaking on global
{ globals: interpreter.runPython("{}") },
);
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(),
};
const workerPyScriptModule = [
"from pathlib import Path as _Path",
`_Path("./pyscript.py").write_text(${JSON.stringify(
[
"from polyscript import xworker as _xworker",
"window=_xworker.window",
"document=window.document",
"sync=_xworker.sync",
display,
].join("\n"),
)})`,
"del _Path",
].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,
onWorkerReady(_, xworker) {
assign(xworker.sync, sync);
},
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) {
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) {
const { head, body } = document;
if (head.contains(element)) body.append(show);
else 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, true),
);
} else {
// resolve PyScriptElement to allow connectedCallback
element._pyodide.resolve(pyodide);
}
},
});
class PyScriptElement extends HTMLElement {
constructor() {
assign(super(), {
_pyodide: Promise.withResolvers(),
srcCode: "",
executed: false,
});
}
get id() {
return super.id || (super.id = getID());
}
set id(value) {
super.id = value;
}
async connectedCallback() {
if (!this.executed) {
this.executed = true;
const { io, run } = await this._pyodide.promise;
this.srcCode = await fetchSource(this, io, !this.childElementCount);
this.replaceChildren();
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.
const xworker = XWorker.call(new Hook(null, workerHooks), file, {
...options,
type: "pyodide",
});
assign(xworker.sync, sync);
return xworker;
}

View File

@@ -0,0 +1,140 @@
# ⚠️ WARNING - both `document` and `window` are added at runtime
import base64
import html
import io
import re
_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(
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")
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(html)
out_element.append(script_element)
else:
out_element.innerHTML = html
def display(*values, target=None, append=True):
element = document.getElementById(target)
for v in values:
_write(element, v, append=append)

View 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);
}

View 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 &lt;py-config&gt;)
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;
}

View File

@@ -0,0 +1,5 @@
export default {
sleep(seconds) {
return new Promise(($) => setTimeout($, seconds * 1000));
},
};

View File

@@ -0,0 +1,6 @@
const entity = { "<": "&lt;", ">": "&gt;" };
const escape = (str) => str.replace(/[<>]/g, (key) => entity[key]);
export const htmlDecode = (html) =>
new DOMParser().parseFromString(escape(html), "text/html").documentElement
.textContent;

1
pyscript.core/test/a.py Normal file
View File

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

View File

@@ -0,0 +1,5 @@
{
"fetch": [{
"files": ["./a.py"]
}]
}

View File

@@ -0,0 +1,36 @@
<!doctype html>
<html>
<head>
<style>py-script{display:none}</style>
<script type="module" src="../core.js"></script>
<script type="module">
customElements.whenDefined('py-script').then(PyScript => {
const textContent = `
from pyscript import display
display("Hello World")
`;
document.body.append(
// test <script type="py">
Object.assign(
document.createElement('script'),
{ type: "py", textContent }
),
// test <py-script>
Object.assign(
document.createElement('py-script'),
{ textContent }
),
// test PyScript class
Object.assign(
new PyScript(),
{ textContent }
)
);
});
</script>
</head>
</html>

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="../core.css" />
<script type="module" src="../core.js"></script>
</head>
<body>
<body>
<py-script>import js; js.console.log(1<2, 1>2)</py-script>
<py-script>js.console.log("<div></div>")</py-script>
</body>
</body>
</html>

View File

@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<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>

View File

@@ -0,0 +1,11 @@
<!doctype html>
<html>
<head>
<script type="module" src="../core.js"></script>
<script type="py">
from pyscript import display
display('hello\nworld')
</script>
</head>
</html>

View 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>

View File

@@ -0,0 +1,9 @@
from pyscript import display, sync
import a
display("Hello World", target="test", append=True)
print("sleeping")
sync.sleep(1)
print("awake")

View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "ES2022",
"target": "ES2022",
"moduleResolution": "Classic",
"allowJs": true,
"declaration": true,
"emitDeclarationOnly": true,
"declarationDir": "types"
},
"include": ["src/core.js"]
}

View 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[];
}

26
pyscript.core/types/core.d.ts vendored Normal file
View File

@@ -0,0 +1,26 @@
/**
* 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;
import sync from "./sync.js";
export {};

27
pyscript.core/types/exceptions.d.ts vendored Normal file
View 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
View 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>;

View 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;
};

View 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>;

View 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";

View 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>;

View 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[]>;

View 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';

View 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';

View 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";

View 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";

View 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>;

View File

@@ -0,0 +1,3 @@
export const env: any;
export function listener(event: any): Promise<void>;
export function addAllListeners(root: Document | Element): void;

View File

@@ -0,0 +1,2 @@
export function getRuntime(id: string, config?: string, options?: object): Promise<any>;
export function getRuntimeID(type: string, version?: string): string;

View 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>;

View File

@@ -0,0 +1 @@
export function parse(text: string): object;

View 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;

View 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;
};

View File

@@ -0,0 +1,6 @@
export class Hook {
constructor(interpreter: any, options: any);
interpreter: any;
onWorkerReady: any;
get stringHooks(): {};
}

View File

@@ -0,0 +1,2 @@
declare function _default(): Worker;
export default _default;

View 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 {};

View 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);
}

View 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>;

4
pyscript.core/types/sync.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare namespace _default {
function sleep(seconds: any): Promise<any>;
}
export default _default;

1
pyscript.core/types/utils.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export function htmlDecode(html: any): string;

5
pyscript.sw/.npmignore Normal file
View File

@@ -0,0 +1,5 @@
node_modules/
rollup/
src/
test/
package-lock.json

201
pyscript.sw/LICENSE Normal file
View 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
View 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
View 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
View 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"
}
}

View 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}})}();

View 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
View 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);

View 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
View 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()

View File

@@ -0,0 +1 @@
packages = ["ssl", "httpx", "fastapi"]