mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-20 02:37:41 -05:00
Compare commits
33 Commits
antocuni/p
...
fix-404-ge
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39725c1384 | ||
|
|
48c5158b46 | ||
|
|
4b89c84692 | ||
|
|
df68449b82 | ||
|
|
48e3383f66 | ||
|
|
e750fa7393 | ||
|
|
5a15199a3a | ||
|
|
1801472fc4 | ||
|
|
ab15ac37ff | ||
|
|
0955a6be49 | ||
|
|
d58237ea15 | ||
|
|
2d50ca86a6 | ||
|
|
f1a46be738 | ||
|
|
3e2a67d434 | ||
|
|
aef028be6e | ||
|
|
c8ec29a3d8 | ||
|
|
e81830a2ea | ||
|
|
54df7171a2 | ||
|
|
b31af823d1 | ||
|
|
72f266532b | ||
|
|
d9bf5cae12 | ||
|
|
cd95a42e5e | ||
|
|
e67eb06d8b | ||
|
|
28d37cdead | ||
|
|
13604e0a47 | ||
|
|
aeb6f1a755 | ||
|
|
92e6f711b7 | ||
|
|
a24113f42b | ||
|
|
7a6f8ab3ad | ||
|
|
6dd242f3ce | ||
|
|
88fa82c61a | ||
|
|
2299ba5f61 | ||
|
|
117df6ca38 |
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -11,5 +11,5 @@
|
||||
<!-- Note: Only user-facing changes require a changelog entry. Internal-only API changes do not require a changelog entry. Changes in documentation do not require a changelog entry. -->
|
||||
|
||||
- [ ] All tests pass locally
|
||||
- [ ] I have updated `docs/changelog.md`
|
||||
- [ ] I have updated `CHANGELOG.md`
|
||||
- [ ] I have created documentation for this(if applicable)
|
||||
|
||||
2
.github/workflows/prepare-release.yml
vendored
2
.github/workflows/prepare-release.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: NPM Install
|
||||
run: npm install
|
||||
run: npm install && npx playwright install
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
2
.github/workflows/publish-release.yml
vendored
2
.github/workflows/publish-release.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: npm install
|
||||
run: npm install
|
||||
run: npm install && npx playwright install
|
||||
|
||||
- name: build
|
||||
run: npm run build
|
||||
|
||||
2
.github/workflows/publish-snapshot.yml
vendored
2
.github/workflows/publish-snapshot.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
run: npm install && npx playwright install
|
||||
|
||||
- name: Build Pyscript.core
|
||||
run: npm run build
|
||||
|
||||
2
.github/workflows/publish-unstable.yml
vendored
2
.github/workflows/publish-unstable.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: NPM Install
|
||||
run: npm install
|
||||
run: npm install && npx playwright install
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
15
.github/workflows/test.yml
vendored
15
.github/workflows/test.yml
vendored
@@ -57,15 +57,24 @@ jobs:
|
||||
- name: setup Miniconda
|
||||
uses: conda-incubator/setup-miniconda@v2
|
||||
|
||||
- name: Setup Environment
|
||||
run: make setup
|
||||
- name: Create and activate virtual environment
|
||||
run: |
|
||||
python3 -m venv test_venv
|
||||
source test_venv/bin/activate
|
||||
echo PATH=$PATH >> $GITHUB_ENV
|
||||
echo VIRTUAL_ENV=$VIRTUAL_ENV >> $GITHUB_ENV
|
||||
|
||||
- name: Setup dependencies in virtual environment
|
||||
run: |
|
||||
make setup
|
||||
|
||||
- name: Build
|
||||
run: make build
|
||||
|
||||
- name: Integration Tests
|
||||
#run: make test-integration-parallel
|
||||
run: make test-integration
|
||||
run: |
|
||||
make test-integration
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -144,6 +144,8 @@ test_results
|
||||
# @pyscript/core npm artifacts
|
||||
pyscript.core/core.*
|
||||
pyscript.core/dist
|
||||
pyscript.core/dist
|
||||
pyscript.core/dist.zip
|
||||
pyscript.core/src/plugins.js
|
||||
pyscript.core/src/stdlib/pyscript.js
|
||||
pyscript.core/src/3rd-party/*
|
||||
!pyscript.core/src/3rd-party/READMEmd
|
||||
|
||||
@@ -42,7 +42,7 @@ repos:
|
||||
rev: "v3.0.0-alpha.6"
|
||||
hooks:
|
||||
- id: prettier
|
||||
exclude: pyscript\.core/test|pyscript\.core/dist|pyscript\.core/types|pyscript.core/src/stdlib/pyscript.js|pyscript\.sw/
|
||||
exclude: pyscript\.core/test|pyscript\.core/dist|pyscript\.core/types|pyscript.core/src/stdlib/pyscript.js|pyscript\.sw/|pyscript.core/src/3rd-party
|
||||
args: [--tab-width, "4"]
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
|
||||
87
CHANGELOG.md
Normal file
87
CHANGELOG.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Release Notes
|
||||
|
||||
## 2023.05.01
|
||||
|
||||
### Features
|
||||
|
||||
- Added the `xterm` attribute to `py-config`. When set to `True` or `xterm`, an (output-only) [xterm.js](http://xtermjs.org/) terminal will be used in place of the default py-terminal.
|
||||
- The default version of Pyodide is now `0.23.2`. See the [Pyodide Changelog](https://pyodide.org/en/stable/project/changelog.html#version-0-23-2) for a detailed list of changes.
|
||||
- Added the `@when` decorator for attaching Python functions as event handlers
|
||||
- The `py-mount` attribute on HTML elements has been deprecated, and will be removed in a future release.
|
||||
|
||||
#### Runtime py- attributes
|
||||
|
||||
- Added logic to react to `py-*` attributes changes, removal, `py-*` attributes added to already live nodes but also `py-*` attributes added or defined via injected nodes (either appended or via `innerHTML` operations). ([#1435](https://github.com/pyscript/pyscript/pull/1435))
|
||||
|
||||
#### <script type="py">
|
||||
|
||||
- Added the ability to optionally use `<script type="py">`, `<script type="pyscript">` or `<script type="py-script">` instead of a `<py-script>` custom element, in order to tackle cases where the content of the `<py-script>` tag, inevitably parsed by browsers, could accidentally contain _HTML_ able to break the surrounding page layout. ([#1396](https://github.com/pyscript/pyscript/pull/1396))
|
||||
|
||||
#### <py-terminal>
|
||||
|
||||
- Added a `docked` field and attribute for the `<py-terminal>` custom element, enabled by default when the terminal is in `auto` mode, and able to dock the terminal at the bottom of the page with auto scroll on new code execution.
|
||||
|
||||
#### <py-script>
|
||||
|
||||
- Restored the `output` attribute of `py-script` tags to route `sys.stdout` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063))
|
||||
- Added a `stderr` attribute of `py-script` tags to route `sys.stderr` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063))
|
||||
|
||||
#### <py-repl>
|
||||
|
||||
- The `output` attribute of `py-repl` tags now specifies the id of the DOM element that `sys.stdout`, `sys.stderr`, and the results of a REPL execution are written to. It no longer affects the location of calls to `display()`
|
||||
- Added a `stderr` attribute of `py-repl` tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
|
||||
- Resored the `output-mode` attribute of `py-repl` tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results.
|
||||
- Load code from the attribute src of py-repl and preload it into the corresponding py-repl tag by use the attribute `str` in your `py-repl` tag([#1292](https://github.com/pyscript/pyscript/pull/1292))
|
||||
- <py-repl> elements now have a `getPySrc()` method, which returns the code inside the REPL as a string.([#1516](https://github.com/pyscript/pyscript/pull/1292))
|
||||
|
||||
#### Plugins
|
||||
|
||||
- Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a `py-repl` tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
|
||||
|
||||
#### Web worker support
|
||||
|
||||
- introduced the new experimental `execution_thread` config option: if you set `execution_thread = "worker"`, the python interpreter runs inside a web worker
|
||||
- worker support is still **very** experimental: not everything works, use it at your own risk
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fixes [#1280](https://github.com/pyscript/pyscript/issues/1280), which describes the errors on the PyRepl tests related to having auto-gen tags that shouldn't be there.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Py-REPL tests now run on both osx and non osx OSs
|
||||
- migrated from _rollup_ to _esbuild_ to create artifacts
|
||||
- updated `@codemirror` dependency to its latest
|
||||
|
||||
### Docs
|
||||
|
||||
- Add docs for event handlers
|
||||
|
||||
## 2023.03.1
|
||||
|
||||
### Features
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fixed an issue where `pyscript` would not be available when using the minified version of PyScript. ([#1054](https://github.com/pyscript/pyscript/pull/1054))
|
||||
- Fixed missing closing tag when rendering an image with `display`. ([#1058](https://github.com/pyscript/pyscript/pull/1058))
|
||||
- Fixed a bug where Python plugins methods were being executed twice. ([#1064](https://github.com/pyscript/pyscript/pull/1064))
|
||||
|
||||
### Enhancements
|
||||
|
||||
- When adding a `py-` attribute to an element but didn't added an `id` attribute, PyScript will now generate a random ID for the element instead of throwing an error which caused the splash screen to not shutdown. ([#1122](https://github.com/pyscript/pyscript/pull/1122))
|
||||
- You can now disable the splashscreen by setting `enabled = false` in your `py-config` under the `[splashscreen]` configuration section. ([#1138](https://github.com/pyscript/pyscript/pull/1138))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fixed 'Direct usage of document is deprecated' warning in the getting started guide. ([#1052](https://github.com/pyscript/pyscript/pull/1052))
|
||||
- Added reference documentation for the `py-splashscreen` plugin ([#1138](https://github.com/pyscript/pyscript/pull/1138))
|
||||
- Adds doc for installing tests ([#1156](https://github.com/pyscript/pyscript/pull/1156))
|
||||
- Adds docs for custom Pyscript attributes (`py-*`) that allow you to add event listeners to an element ([#1125](https://github.com/pyscript/pyscript/pull/1125))
|
||||
|
||||
### Deprecations and Removals
|
||||
|
||||
- The py-config `runtimes` to specify an interpreter has been deprecated. The `interpreters` config should be used instead. ([#1082](https://github.com/pyscript/pyscript/pull/1082))
|
||||
- The attributes `pys-onClick` and `pys-onKeyDown` have been deprecated, but the warning was only shown in the console. An alert banner will now be shown on the page if the attributes are used. They will be removed in the next release. ([#1084](https://github.com/pyscript/pyscript/pull/1084))
|
||||
- The pyscript elements `py-button`, `py-inputbox`, `py-box` and `py-title` have now completed their deprecation cycle and have been removed. ([#1084](https://github.com/pyscript/pyscript/pull/1084))
|
||||
- The attributes `pys-onClick` and `pys-onKeyDown` have been removed. Use `py-click` and `py-keydown` instead ([#1361](https://github.com/pyscript/pyscript/pull/1361))
|
||||
130
Makefile
130
Makefile
@@ -1,122 +1,92 @@
|
||||
tag := latest
|
||||
git_hash ?= $(shell git log -1 --pretty=format:%h)
|
||||
|
||||
base_dir ?= $(shell git rev-parse --show-toplevel)
|
||||
examples ?= ../$(base_dir)/examples
|
||||
app_dir ?= $(shell git rev-parse --show-prefix)
|
||||
|
||||
CONDA_EXE := conda
|
||||
CONDA_ENV ?= $(base_dir)/env
|
||||
env := $(CONDA_ENV)
|
||||
conda_run := $(CONDA_EXE) run -p $(env)
|
||||
PYTEST_EXE := $(CONDA_ENV)/bin/pytest
|
||||
|
||||
MIN_NODE_VER := 14
|
||||
MIN_NODE_VER := 20
|
||||
MIN_NPM_VER := 6
|
||||
MIN_PY3_VER := 8
|
||||
NODE_VER := $(shell node -v | cut -d. -f1 | sed 's/^v\(.*\)/\1/')
|
||||
NPM_VER := $(shell npm -v | cut -d. -f1)
|
||||
PY3_VER := $(shell python3 -c "import sys;t='{v[1]}'.format(v=list(sys.version_info[:2]));print(t)")
|
||||
PY_OK := $(shell python3 -c "print(int($(PY3_VER) >= $(MIN_PY3_VER)))")
|
||||
|
||||
ifeq ($(shell uname -s), Darwin)
|
||||
SED_I_ARG := -i ''
|
||||
else
|
||||
SED_I_ARG := -i
|
||||
endif
|
||||
all:
|
||||
@echo "\nThere is no default Makefile target right now. Try:\n"
|
||||
@echo "make setup - check your environment and install the dependencies."
|
||||
@echo "make clean - clean up auto-generated assets."
|
||||
@echo "make build - build PyScript."
|
||||
@echo "make precommit-check - run the precommit checks (run eslint)."
|
||||
@echo "make test-integration - run all integration tests sequentially."
|
||||
@echo "make fmt - format the code."
|
||||
@echo "make fmt-check - check the code formatting.\n"
|
||||
|
||||
.PHONY: check-node
|
||||
check-node:
|
||||
@if [ $(NODE_VER) -lt $(MIN_NODE_VER) ]; then \
|
||||
echo "Build requires Node $(MIN_NODE_VER).x or higher: $(NODE_VER) detected"; \
|
||||
echo "\033[0;31mBuild requires Node $(MIN_NODE_VER).x or higher: $(NODE_VER) detected.\033[0m"; \
|
||||
false; \
|
||||
fi
|
||||
|
||||
.PHONY: check-npm
|
||||
check-npm:
|
||||
@if [ $(NPM_VER) -lt $(MIN_NPM_VER) ]; then \
|
||||
echo "Build requires Node $(MIN_NPM_VER).x or higher: $(NPM_VER) detected"; \
|
||||
echo "\033[0;31mBuild requires Node $(MIN_NPM_VER).x or higher: $(NPM_VER) detected.\033[0m"; \
|
||||
false; \
|
||||
fi
|
||||
|
||||
setup: check-node check-npm
|
||||
cd pyscript.core && npm install && cd ..
|
||||
$(CONDA_EXE) env $(shell [ -d $(env) ] && echo update || echo create) -p $(env) --file environment.yml
|
||||
$(conda_run) playwright install
|
||||
$(CONDA_EXE) install -c anaconda pytest -y
|
||||
.PHONY: check-python
|
||||
check-python:
|
||||
@if [ $(PY_OK) -eq 0 ]; then \
|
||||
echo "\033[0;31mRequires Python 3.$(MIN_PY3_VER).x or higher: 3.$(PY3_VER) detected.\033[0m"; \
|
||||
false; \
|
||||
fi
|
||||
|
||||
# Check the environment, install the dependencies.
|
||||
setup: check-node check-npm check-python
|
||||
cd pyscript.core && npm install && cd ..
|
||||
ifeq ($(VIRTUAL_ENV),)
|
||||
echo "\n\n\033[0;31mCannot install Python dependencies. Your virtualenv is not activated.\033[0m"
|
||||
false
|
||||
else
|
||||
python -m pip install -r requirements.txt
|
||||
playwright install
|
||||
endif
|
||||
|
||||
# Clean up generated assets.
|
||||
clean:
|
||||
find . -name \*.py[cod] -delete
|
||||
rm -rf $(env) *.egg-info
|
||||
rm -rf .pytest_cache .coverage coverage.xml
|
||||
|
||||
clean-all: clean
|
||||
rm -rf $(env) *.egg-info
|
||||
|
||||
shell:
|
||||
@export CONDA_ENV_PROMPT='<{name}>'
|
||||
@echo 'conda activate $(env)'
|
||||
|
||||
dev:
|
||||
cd pyscript.core && npm run dev
|
||||
|
||||
# Build PyScript.
|
||||
build:
|
||||
cd pyscript.core && npm run build
|
||||
cd pyscript.core && npx playwright install && npm run build
|
||||
|
||||
# use the following rule to do all the checks done by precommit: in
|
||||
# particular, use this if you want to run eslint.
|
||||
# Run the precommit checks (run eslint).
|
||||
precommit-check:
|
||||
pre-commit run --all-files
|
||||
|
||||
examples:
|
||||
mkdir -p ./examples
|
||||
cp -r ../examples/* ./examples
|
||||
chmod -R 755 examples
|
||||
find ./examples/toga -type f -name '*.html' -exec sed $(SED_I_ARG) s+https://pyscript.net/latest/+../../build/+g {} \;
|
||||
find ./examples/webgl -type f -name '*.html' -exec sed $(SED_I_ARG) s+https://pyscript.net/latest/+../../../build/+g {} \;
|
||||
find ./examples -type f -name '*.html' -exec sed $(SED_I_ARG) s+https://pyscript.net/latest/+../build/+g {} \;
|
||||
npm run build
|
||||
rm -rf ./examples/build
|
||||
mkdir -p ./examples/build
|
||||
cp -R ./build/* ./examples/build
|
||||
@echo "To serve examples run: $(conda_run) python -m http.server 8080 --directory examples"
|
||||
|
||||
# run prerequisites and serve pyscript examples at http://localhost:8000/examples/
|
||||
run-examples: setup build examples
|
||||
make examples
|
||||
npm install
|
||||
make dev
|
||||
|
||||
# run all integration tests *including examples* sequentially
|
||||
# TODO: (fpliger) The cd pyscript.core before running the tests shouldn't be needed but for
|
||||
# but for some reason it seems to bother pytest tmppaths (or test cache?). Unclear.
|
||||
# Run all integration tests sequentially.
|
||||
test-integration:
|
||||
mkdir -p test_results
|
||||
$(PYTEST_EXE) -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml
|
||||
pytest -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml
|
||||
|
||||
# run all integration tests *except examples* in parallel (examples use too much memory)
|
||||
# Run all integration tests in parallel.
|
||||
test-integration-parallel:
|
||||
mkdir -p test_results
|
||||
$(PYTEST_EXE) --numprocesses auto -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml
|
||||
pytest --numprocesses auto -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml
|
||||
|
||||
# run integration tests on only examples sequentially (to avoid running out of memory)
|
||||
test-examples:
|
||||
mkdir -p test_results
|
||||
$(PYTEST_EXE) -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml -k 'zz_examples'
|
||||
|
||||
fmt: fmt-py fmt-ts
|
||||
# Format the code.
|
||||
fmt: fmt-py
|
||||
@echo "Format completed"
|
||||
|
||||
fmt-check: fmt-ts-check fmt-py-check
|
||||
# Check the code formatting.
|
||||
fmt-check: fmt-py-check
|
||||
@echo "Format check completed"
|
||||
|
||||
fmt-ts:
|
||||
npm run format
|
||||
|
||||
fmt-ts-check:
|
||||
npm run format:check
|
||||
|
||||
# Format Python code.
|
||||
fmt-py:
|
||||
$(conda_run) black --skip-string-normalization .
|
||||
$(conda_run) isort --profile black .
|
||||
black -l 88 --skip-string-normalization .
|
||||
isort --profile black .
|
||||
|
||||
# Check the format of Python code.
|
||||
fmt-py-check:
|
||||
$(conda_run) black -l 88 --check .
|
||||
black -l 88 --check .
|
||||
|
||||
.PHONY: $(MAKECMDGOALS)
|
||||
|
||||
48
README.md
48
README.md
@@ -4,9 +4,9 @@
|
||||
|
||||
### Summary
|
||||
|
||||
PyScript is a framework that allows users to create rich Python applications in the browser using HTML's interface and the power of [Pyodide](https://pyodide.org/en/stable/), [WASM](https://webassembly.org/), and modern web technologies.
|
||||
PyScript is a framework that allows users to create rich Python applications in the browser using HTML's interface and the power of [Pyodide](https://pyodide.org/en/stable/), [MicroPython](https://micropython.org/) and [WASM](https://webassembly.org/), and modern web technologies.
|
||||
|
||||
To get started see the [getting started tutorial](docs/tutorials/getting-started.md).
|
||||
To get started see the [getting started tutorial](https://pyscript.github.io/docs/latest/beginning-pyscript/).
|
||||
|
||||
For examples see [here](examples).
|
||||
|
||||
@@ -16,27 +16,55 @@ PyScript is a meta project that aims to combine multiple open technologies into
|
||||
|
||||
## Try PyScript
|
||||
|
||||
To try PyScript, import the appropriate pyscript files into the `<head>` tag of your html page with:
|
||||
To try PyScript, import the appropriate pyscript files into the `<head>` tag of your html page:
|
||||
|
||||
```html
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://pyscript.net/releases/2023.11.1/core.css"
|
||||
/>
|
||||
<script
|
||||
type="module"
|
||||
src="https://pyscript.net/releases/2023.11.1/core.js"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py" terminal>
|
||||
from pyscript import display
|
||||
display("Hello World!") # this goes to the DOM
|
||||
print("Hello terminal") # this goes to the terminal
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
You can then use PyScript components in your html page. PyScript currently implements the following elements:
|
||||
You can then use PyScript components in your html page. PyScript currently offers various ways of running Python code:
|
||||
|
||||
- `<py-script>`: can be used to define python code that is executable within the web page. The element itself is not rendered to the page and is only used to add logic
|
||||
- `<py-repl>`: creates a REPL component that is rendered to the page as a code editor and allows users to write executable code
|
||||
- `<script type="py">`: can be used to define python code that is executable within the web page.
|
||||
- `<script type="py" src="hello.py">`: same as above, but the python source is fetched from the given URL.
|
||||
- `<script type="py" terminal>`: same as above, but also creates a terminal where to display stdout and stderr (e.g., the output of `print()`); `input()` does not work.
|
||||
- `<script type="py" terminal worker>`: run Python inside a web worker: the terminal if fully functional and `input()` works.
|
||||
- `<py-script>`: same as `<script type="py">`, but it is not recommended because if the code contains HTML tags, they could be parsed wrongly.
|
||||
- `<script type="mpy">`: same as above but use MicroPython instead of Python.
|
||||
|
||||
Check out the [the examples directory](examples) folder for more examples on how to use it, all you need to do is open them in Chrome.
|
||||
Check out the [official docs](https://docs.pyscript.net) for more detailed documentation.
|
||||
|
||||
## How to Contribute
|
||||
|
||||
Read the [contributing guide](CONTRIBUTING.md) to learn about our development process, reporting bugs and improvements, creating issues and asking questions.
|
||||
|
||||
Check out the [developing process](https://docs.pyscript.net/latest/development/developing.html) documentation for more information on how to setup your development environment.
|
||||
Check out the [developing process](https://docs.pyscript.net/latest/contributing) documentation for more information on how to setup your development environment.
|
||||
|
||||
## Community calls and events
|
||||
|
||||
Every Tuesday at 15:30 UTC there is the _PyScript Community Call_ on zoom, where we can talk about PyScript development in the open. Most of the maintainers regularly participate in the call, and everybody is welcome to join.
|
||||
|
||||
Every other Thursday at 16:00 UTC there is the _PyScript FUN_ call: this is a call in which everybody is encouraged to show what they did with PyScript.
|
||||
|
||||
For more details on how to join the calls and up to date schedule, consult the official calendar:
|
||||
|
||||
- [Google calendar](https://calendar.google.com/calendar/u/0/embed?src=d3afdd81f9c132a8c8f3290f5cc5966adebdf61017fca784eef0f6be9fd519e0@group.calendar.google.com&ctz=UTC) in UTC time;
|
||||
- [iCal format](https://calendar.google.com/calendar/ical/d3afdd81f9c132a8c8f3290f5cc5966adebdf61017fca784eef0f6be9fd519e0%40group.calendar.google.com/public/basic.ics).
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
channels:
|
||||
- defaults
|
||||
- conda-forge
|
||||
- microsoft
|
||||
dependencies:
|
||||
- python=3.11.3
|
||||
- pip
|
||||
- pytest=7.1.2
|
||||
- nodejs=16
|
||||
- black
|
||||
- isort
|
||||
- codespell
|
||||
- pre-commit
|
||||
- pillow
|
||||
- numpy
|
||||
- markdown
|
||||
- toml
|
||||
- pip:
|
||||
- playwright==1.33.0
|
||||
- pytest-playwright==0.3.3
|
||||
- pytest-xdist==3.3.0
|
||||
- pexpect
|
||||
# We need Pyodide and micropip so we can import them in our Python
|
||||
# unit tests
|
||||
- pyodide_py==0.23.2
|
||||
- micropip==0.2.2
|
||||
@@ -19,6 +19,7 @@ module.exports = {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
ignorePatterns: ["3rd-party"],
|
||||
rules: {
|
||||
"no-implicit-globals": ["error"],
|
||||
},
|
||||
|
||||
31
pyscript.core/dev.cjs
Normal file
31
pyscript.core/dev.cjs
Normal file
@@ -0,0 +1,31 @@
|
||||
let queue = Promise.resolve();
|
||||
|
||||
const { exec } = require("node:child_process");
|
||||
|
||||
const build = (fileName) => {
|
||||
if (fileName) console.log(fileName, "changed");
|
||||
else console.log("building without optimizations");
|
||||
queue = queue.then(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
exec(
|
||||
"npm run build:stdlib && npm run build:plugins && npm run build:core",
|
||||
{ cwd: __dirname, env: { ...process.env, NO_MIN: true } },
|
||||
(error) => {
|
||||
if (error) console.error(error);
|
||||
else console.log(fileName || "", "build completed");
|
||||
resolve();
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const options = {
|
||||
ignored: /\/(?:toml|plugins|pyscript)\.[mc]?js$/,
|
||||
persistent: true,
|
||||
};
|
||||
|
||||
require("chokidar").watch("./src", options).on("change", build);
|
||||
|
||||
build();
|
||||
@@ -159,7 +159,7 @@ The commonly shared utilities are:
|
||||
* **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.
|
||||
* in both main and worker, the `append=True` is the *default* behavior, which is inherited from the classic PyScript.
|
||||
|
||||
#### Extra main-only features
|
||||
|
||||
|
||||
734
pyscript.core/package-lock.json
generated
734
pyscript.core/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.2.7",
|
||||
"version": "0.3.4",
|
||||
"type": "module",
|
||||
"description": "PyScript",
|
||||
"module": "./index.js",
|
||||
@@ -19,10 +19,18 @@
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"server": "npx static-handler --cors --coep --coop --corp .",
|
||||
"build": "node rollup/stdlib.cjs && node rollup/plugins.cjs && rm -rf dist && rollup --config rollup/core.config.js && eslint src/ && npm run ts",
|
||||
"server": "npx static-handler --coi .",
|
||||
"build": "npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && eslint src/ && npm run ts && npm run test:mpy",
|
||||
"build:core": "rm -rf dist && rollup --config rollup/core.config.js && cp src/3rd-party/*.css dist/",
|
||||
"build:plugins": "node rollup/plugins.cjs",
|
||||
"build:stdlib": "node rollup/stdlib.cjs",
|
||||
"build:3rd-party": "node rollup/3rd-party.cjs",
|
||||
"test:mpy": "static-handler --coi . 2>/dev/null & SH_PID=$!; EXIT_CODE=0; playwright test --fully-parallel test/ || EXIT_CODE=$?; kill $SH_PID 2>/dev/null; exit $EXIT_CODE",
|
||||
"dev": "node dev.cjs",
|
||||
"release": "npm run build && npm run zip",
|
||||
"size": "echo -e \"\\033[1mdist/*.js file size\\033[0m\"; for js in $(ls dist/*.js); do echo -e \"\\033[2m$js:\\033[0m $(cat $js | brotli | wc -c) bytes\"; done",
|
||||
"ts": "tsc -p ."
|
||||
"ts": "tsc -p .",
|
||||
"zip": "zip -r dist.zip ./dist"
|
||||
},
|
||||
"keywords": [
|
||||
"pyscript",
|
||||
@@ -33,18 +41,26 @@
|
||||
"dependencies": {
|
||||
"@ungap/with-resolvers": "^0.1.0",
|
||||
"basic-devtools": "^0.1.6",
|
||||
"polyscript": "^0.4.11",
|
||||
"polyscript": "^0.5.11",
|
||||
"sticky-module": "^0.1.1",
|
||||
"to-json-callback": "^0.1.1",
|
||||
"type-checked-collections": "^0.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.2.1",
|
||||
"@rollup/plugin-terser": "^0.4.3",
|
||||
"eslint": "^8.50.0",
|
||||
"rollup": "^3.29.4",
|
||||
"@playwright/test": "^1.39.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@webreflection/toml-j0.4": "^1.1.3",
|
||||
"chokidar": "^3.5.3",
|
||||
"eslint": "^8.53.0",
|
||||
"rollup": "^4.3.0",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
"static-handler": "^0.4.2",
|
||||
"typescript": "^5.2.2"
|
||||
"static-handler": "^0.4.3",
|
||||
"typescript": "^5.2.2",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-readline": "^1.1.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
52
pyscript.core/rollup/3rd-party.cjs
Normal file
52
pyscript.core/rollup/3rd-party.cjs
Normal file
@@ -0,0 +1,52 @@
|
||||
const { copyFileSync, writeFileSync } = require("node:fs");
|
||||
const { join } = require("node:path");
|
||||
|
||||
const CDN = "https://cdn.jsdelivr.net/npm";
|
||||
|
||||
const targets = join(__dirname, "..", "src", "3rd-party");
|
||||
const node_modules = join(__dirname, "..", "node_modules");
|
||||
|
||||
const { devDependencies } = require(join(__dirname, "..", "package.json"));
|
||||
|
||||
const v = (name) => devDependencies[name].replace(/[^\d.]/g, "");
|
||||
|
||||
// Fetch a module via jsdelivr CDN `/+esm` orchestration
|
||||
// then sanitize the resulting outcome to avoid importing
|
||||
// anything via `/npm/...` through Rollup
|
||||
const resolve = (name) => {
|
||||
const cdn = `${CDN}/${name}@${v(name)}/+esm`;
|
||||
console.debug("fetching", cdn);
|
||||
return fetch(cdn)
|
||||
.then((b) => b.text())
|
||||
.then((text) =>
|
||||
text.replace(
|
||||
/("|')\/npm\/(.+)?\+esm\1/g,
|
||||
// normalize `/npm/module@version/+esm` as
|
||||
// just `module` so that rollup can do the rest
|
||||
(_, quote, module) => {
|
||||
const i = module.lastIndexOf("@");
|
||||
return `${quote}${module.slice(0, i)}${quote}`;
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
// key/value pairs as:
|
||||
// "3rd-party/file-name.js"
|
||||
// string as content or
|
||||
// Promise<string> as resolved content
|
||||
const modules = {
|
||||
"toml.js": join(node_modules, "@webreflection", "toml-j0.4", "toml.js"),
|
||||
"xterm.js": resolve("xterm"),
|
||||
"xterm.css": fetch(`${CDN}/xterm@${v("xterm")}/css/xterm.min.css`).then(
|
||||
(b) => b.text(),
|
||||
),
|
||||
"xterm-readline.js": resolve("xterm-readline"),
|
||||
};
|
||||
|
||||
for (const [target, source] of Object.entries(modules)) {
|
||||
if (typeof source === "string") copyFileSync(source, join(targets, target));
|
||||
else {
|
||||
source.then((text) => writeFileSync(join(targets, target), text));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// the default exported as npm entry.
|
||||
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import terser from "@rollup/plugin-terser";
|
||||
import postcss from "rollup-plugin-postcss";
|
||||
|
||||
@@ -11,7 +12,9 @@ export default [
|
||||
{
|
||||
input: "./src/core.js",
|
||||
plugins: plugins.concat(
|
||||
process.env.NO_MIN ? [nodeResolve()] : [nodeResolve(), terser()],
|
||||
process.env.NO_MIN
|
||||
? [nodeResolve(), commonjs()]
|
||||
: [nodeResolve(), commonjs(), terser()],
|
||||
),
|
||||
output: {
|
||||
esModule: true,
|
||||
|
||||
7
pyscript.core/src/3rd-party/README.md
vendored
Normal file
7
pyscript.core/src/3rd-party/README.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# PyScript 3rd Party
|
||||
|
||||
This folder contains artifacts created via [3rd-party.cjs](../../rollup/3rd-party.cjs).
|
||||
|
||||
As we would like to offer a way to run PyScript offline, and we already offer a `dist` folder with all the necessary scripts, we have created a foreign dependencies resolver that allow to lazy-load CDN dependencies out of the box.
|
||||
|
||||
Please **note** these dependencies are **not interpreters**, because interpreters have their own mechanism, folders structure, WASM files, and whatnot, to work locally, but at least XTerm or the TOML parser, among other lazy dependencies, should be available within the dist folder.
|
||||
@@ -89,8 +89,7 @@ for (const [TYPE] of TYPES) {
|
||||
} else if (toml || type === "toml") {
|
||||
try {
|
||||
const { parse } = await import(
|
||||
/* webpackIgnore: true */
|
||||
"https://cdn.jsdelivr.net/npm/@webreflection/toml-j0.4/toml.js"
|
||||
/* webpackIgnore: true */ "./3rd-party/toml.js"
|
||||
);
|
||||
parsed = parse(text);
|
||||
} catch (e) {
|
||||
@@ -114,7 +113,7 @@ for (const [TYPE] of TYPES) {
|
||||
value().then(({ notify }) => notify(error.message));
|
||||
}
|
||||
} else if (!parsed?.plugins?.includes(`!${key}`)) {
|
||||
toBeAwaited.push(value());
|
||||
toBeAwaited.push(value().then(({ default: p }) => p));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
/*! (c) PyScript Development Team */
|
||||
|
||||
import stickyModule from "sticky-module";
|
||||
import "@ungap/with-resolvers";
|
||||
|
||||
// These imports can hook more than usual and help debugging possible polyscript issues
|
||||
import {
|
||||
INVALID_CONTENT,
|
||||
define,
|
||||
Hook,
|
||||
XWorker,
|
||||
} from "../node_modules/polyscript/esm/index.js";
|
||||
import { queryTarget } from "../node_modules/polyscript/esm/script-handler.js";
|
||||
import {
|
||||
assign,
|
||||
dedent,
|
||||
define,
|
||||
defineProperty,
|
||||
dispatch,
|
||||
queryTarget,
|
||||
unescape,
|
||||
} from "../node_modules/polyscript/esm/utils.js";
|
||||
import { Hook } from "../node_modules/polyscript/esm/worker/hooks.js";
|
||||
whenDefined,
|
||||
} from "polyscript/exports";
|
||||
|
||||
import "./all-done.js";
|
||||
import TYPES from "./types.js";
|
||||
import configs from "./config.js";
|
||||
import hooks from "./hooks.js";
|
||||
import sync from "./sync.js";
|
||||
import stdlib from "./stdlib.js";
|
||||
import bootstrapNodeAndPlugins from "./plugins-helper.js";
|
||||
import { ErrorCode } from "./exceptions.js";
|
||||
import { robustFetch as fetch, getText } from "./fetch.js";
|
||||
|
||||
const { assign, defineProperty } = Object;
|
||||
import { hooks, main, worker, codeFor, createFunction } from "./hooks.js";
|
||||
|
||||
// allows lazy element features on code evaluation
|
||||
let currentElement;
|
||||
@@ -33,25 +32,6 @@ let currentElement;
|
||||
// generic helper to disambiguate between custom element and script
|
||||
const isScript = ({ tagName }) => 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;
|
||||
};
|
||||
|
||||
// common life-cycle handlers for any node
|
||||
const bootstrapNodeAndPlugins = (wrap, element, callback, hook) => {
|
||||
// make it possible to reach the current target node via Python
|
||||
callback(element);
|
||||
for (const fn of hooks[hook]) fn(wrap, element);
|
||||
};
|
||||
|
||||
let shouldRegister = true;
|
||||
const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
|
||||
// automatically use the pyscript stderr (when/if defined)
|
||||
@@ -71,25 +51,38 @@ const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
|
||||
: currentElement.id;
|
||||
},
|
||||
});
|
||||
|
||||
interpreter.runPython(stdlib, { globals: interpreter.runPython("{}") });
|
||||
};
|
||||
|
||||
const workerHooks = {
|
||||
codeBeforeRunWorker: () =>
|
||||
[stdlib, ...hooks.codeBeforeRunWorker].map(dedent).join("\n"),
|
||||
codeBeforeRunWorkerAsync: () =>
|
||||
[stdlib, ...hooks.codeBeforeRunWorkerAsync].map(dedent).join("\n"),
|
||||
codeAfterRunWorker: () =>
|
||||
[...hooks.codeAfterRunWorker].map(dedent).join("\n"),
|
||||
codeAfterRunWorkerAsync: () =>
|
||||
[...hooks.codeAfterRunWorkerAsync].map(dedent).join("\n"),
|
||||
// avoid multiple initialization of the same library
|
||||
const [
|
||||
{
|
||||
PyWorker: exportedPyWorker,
|
||||
hooks: exportedHooks,
|
||||
config: exportedConfig,
|
||||
whenDefined: exportedWhenDefined,
|
||||
},
|
||||
alreadyLive,
|
||||
] = stickyModule("@pyscript/core", {
|
||||
PyWorker,
|
||||
hooks,
|
||||
config: {},
|
||||
whenDefined,
|
||||
});
|
||||
|
||||
export {
|
||||
TYPES,
|
||||
exportedPyWorker as PyWorker,
|
||||
exportedHooks as hooks,
|
||||
exportedConfig as config,
|
||||
exportedWhenDefined as whenDefined,
|
||||
};
|
||||
|
||||
const exportedConfig = {};
|
||||
export { exportedConfig as config, hooks };
|
||||
const hooked = new Map();
|
||||
|
||||
for (const [TYPE, interpreter] of TYPES) {
|
||||
// avoid any dance if the module already landed
|
||||
if (alreadyLive) break;
|
||||
|
||||
const dispatchDone = (element, isAsync, result) => {
|
||||
if (isAsync) result.then(() => dispatch(element, TYPE, "done"));
|
||||
else dispatch(element, TYPE, "done");
|
||||
@@ -135,50 +128,11 @@ for (const [TYPE, interpreter] of TYPES) {
|
||||
// possible early errors sent by polyscript
|
||||
const errors = new Map();
|
||||
|
||||
define(TYPE, {
|
||||
config,
|
||||
interpreter,
|
||||
env: `${TYPE}-script`,
|
||||
version: config?.interpreter,
|
||||
onerror(error, element) {
|
||||
errors.set(element, error);
|
||||
},
|
||||
...workerHooks,
|
||||
onWorkerReady(_, xworker) {
|
||||
assign(xworker.sync, sync);
|
||||
for (const callback of hooks.onWorkerReady)
|
||||
callback(_, xworker);
|
||||
},
|
||||
onBeforeRun(wrap, element) {
|
||||
currentElement = element;
|
||||
bootstrapNodeAndPlugins(
|
||||
wrap,
|
||||
element,
|
||||
before,
|
||||
"onBeforeRun",
|
||||
);
|
||||
},
|
||||
onBeforeRunAsync(wrap, element) {
|
||||
currentElement = element;
|
||||
bootstrapNodeAndPlugins(
|
||||
wrap,
|
||||
element,
|
||||
before,
|
||||
"onBeforeRunAsync",
|
||||
);
|
||||
},
|
||||
onAfterRun(wrap, element) {
|
||||
bootstrapNodeAndPlugins(wrap, element, after, "onAfterRun");
|
||||
},
|
||||
onAfterRunAsync(wrap, element) {
|
||||
bootstrapNodeAndPlugins(
|
||||
wrap,
|
||||
element,
|
||||
after,
|
||||
"onAfterRunAsync",
|
||||
);
|
||||
},
|
||||
async onInterpreterReady(wrap, element) {
|
||||
// specific main and worker hooks
|
||||
const hooks = {
|
||||
main: {
|
||||
...codeFor(main),
|
||||
async onReady(wrap, element) {
|
||||
if (shouldRegister) {
|
||||
shouldRegister = false;
|
||||
registerModule(wrap);
|
||||
@@ -186,8 +140,8 @@ for (const [TYPE, interpreter] of TYPES) {
|
||||
|
||||
// allows plugins to do whatever they want with the element
|
||||
// before regular stuff happens in here
|
||||
for (const callback of hooks.onInterpreterReady)
|
||||
callback(wrap, element);
|
||||
for (const callback of main("onReady"))
|
||||
await callback(wrap, element);
|
||||
|
||||
// now that all possible plugins are configured,
|
||||
// bail out if polyscript encountered an error
|
||||
@@ -236,6 +190,79 @@ for (const [TYPE, interpreter] of TYPES) {
|
||||
}
|
||||
console.debug("[pyscript/main] PyScript Ready");
|
||||
},
|
||||
onWorker(_, xworker) {
|
||||
assign(xworker.sync, sync);
|
||||
for (const callback of main("onWorker"))
|
||||
callback(_, xworker);
|
||||
},
|
||||
onBeforeRun(wrap, element) {
|
||||
currentElement = element;
|
||||
bootstrapNodeAndPlugins(
|
||||
main,
|
||||
wrap,
|
||||
element,
|
||||
"onBeforeRun",
|
||||
);
|
||||
},
|
||||
onBeforeRunAsync(wrap, element) {
|
||||
currentElement = element;
|
||||
return bootstrapNodeAndPlugins(
|
||||
main,
|
||||
wrap,
|
||||
element,
|
||||
"onBeforeRunAsync",
|
||||
);
|
||||
},
|
||||
onAfterRun(wrap, element) {
|
||||
bootstrapNodeAndPlugins(
|
||||
main,
|
||||
wrap,
|
||||
element,
|
||||
"onAfterRun",
|
||||
);
|
||||
},
|
||||
onAfterRunAsync(wrap, element) {
|
||||
return bootstrapNodeAndPlugins(
|
||||
main,
|
||||
wrap,
|
||||
element,
|
||||
"onAfterRunAsync",
|
||||
);
|
||||
},
|
||||
},
|
||||
worker: {
|
||||
...codeFor(worker),
|
||||
// these are lazy getters that returns a composition
|
||||
// of the current hooks or undefined, if no hook is present
|
||||
get onReady() {
|
||||
return createFunction(this, "onReady", true);
|
||||
},
|
||||
get onBeforeRun() {
|
||||
return createFunction(this, "onBeforeRun", false);
|
||||
},
|
||||
get onBeforeRunAsync() {
|
||||
return createFunction(this, "onBeforeRunAsync", true);
|
||||
},
|
||||
get onAfterRun() {
|
||||
return createFunction(this, "onAfterRun", false);
|
||||
},
|
||||
get onAfterRunAsync() {
|
||||
return createFunction(this, "onAfterRunAsync", true);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
hooked.set(TYPE, hooks);
|
||||
|
||||
define(TYPE, {
|
||||
config,
|
||||
interpreter,
|
||||
hooks,
|
||||
env: `${TYPE}-script`,
|
||||
version: config?.interpreter,
|
||||
onerror(error, element) {
|
||||
errors.set(element, error);
|
||||
},
|
||||
});
|
||||
|
||||
customElements.define(
|
||||
@@ -290,13 +317,14 @@ for (const [TYPE, interpreter] of TYPES) {
|
||||
* @param {{config?: string | object, async?: boolean}} [options] optional configuration for the worker.
|
||||
* @returns {Worker & {sync: ProxyHandler<object>}}
|
||||
*/
|
||||
export function PyWorker(file, options) {
|
||||
function PyWorker(file, options) {
|
||||
const hooks = hooked.get("py");
|
||||
// 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, {
|
||||
const xworker = XWorker.call(new Hook(null, hooks), file, {
|
||||
type: "pyodide",
|
||||
...options,
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { assign } from "polyscript/exports";
|
||||
|
||||
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>";
|
||||
|
||||
@@ -87,13 +89,13 @@ export function _createAlertBanner(
|
||||
}
|
||||
|
||||
const content = messageType === "html" ? "innerHTML" : "textContent";
|
||||
const banner = Object.assign(document.createElement("div"), {
|
||||
const banner = assign(document.createElement("div"), {
|
||||
className: `alert-banner py-${level}`,
|
||||
[content]: message,
|
||||
});
|
||||
|
||||
if (level === "warning") {
|
||||
const closeButton = Object.assign(document.createElement("button"), {
|
||||
const closeButton = assign(document.createElement("button"), {
|
||||
id: "alert-close-button",
|
||||
innerHTML: CLOSEBUTTON,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FetchError, ErrorCode } from "./exceptions.js";
|
||||
import { getText } from "../node_modules/polyscript/esm/fetch-utils.js";
|
||||
import { getText } from "polyscript/exports";
|
||||
|
||||
export { getText };
|
||||
|
||||
|
||||
@@ -1,11 +1,69 @@
|
||||
import { typedSet } from "type-checked-collections";
|
||||
import { dedent } from "polyscript/exports";
|
||||
import toJSONCallback from "to-json-callback";
|
||||
|
||||
import stdlib from "./stdlib.js";
|
||||
|
||||
export const main = (name) => hooks.main[name];
|
||||
export const worker = (name) => hooks.worker[name];
|
||||
|
||||
const code = (hooks, branch, key, lib) => {
|
||||
hooks[key] = () => {
|
||||
const arr = lib ? [lib] : [];
|
||||
arr.push(...branch(key));
|
||||
return arr.map(dedent).join("\n");
|
||||
};
|
||||
};
|
||||
|
||||
export const codeFor = (branch) => {
|
||||
const hooks = {};
|
||||
code(hooks, branch, `codeBeforeRun`, stdlib);
|
||||
code(hooks, branch, `codeBeforeRunAsync`, stdlib);
|
||||
code(hooks, branch, `codeAfterRun`);
|
||||
code(hooks, branch, `codeAfterRunAsync`);
|
||||
return hooks;
|
||||
};
|
||||
|
||||
export const createFunction = (self, name) => {
|
||||
const cbs = [...worker(name)];
|
||||
if (cbs.length) {
|
||||
const cb = toJSONCallback(
|
||||
self[`_${name}`] ||
|
||||
(name.endsWith("Async")
|
||||
? async (wrap, xworker, ...cbs) => {
|
||||
for (const cb of cbs) await cb(wrap, xworker);
|
||||
}
|
||||
: (wrap, xworker, ...cbs) => {
|
||||
for (const cb of cbs) cb(wrap, xworker);
|
||||
}),
|
||||
);
|
||||
const a = cbs.map(toJSONCallback).join(", ");
|
||||
return Function(`return(w,x)=>(${cb})(w,x,...[${a}])`)();
|
||||
}
|
||||
};
|
||||
|
||||
const SetFunction = typedSet({ typeof: "function" });
|
||||
const SetString = typedSet({ typeof: "string" });
|
||||
|
||||
export default {
|
||||
const inputFailure = `
|
||||
import builtins
|
||||
def input(prompt=""):
|
||||
raise Exception("\\n ".join([
|
||||
"input() doesn't work when PyScript runs in the main thread.",
|
||||
"Consider using the worker attribute: https://docs.pyscript.net/2023.11.1/user-guide/workers/"
|
||||
]))
|
||||
|
||||
builtins.input = input
|
||||
del builtins
|
||||
del input
|
||||
`;
|
||||
|
||||
export const hooks = {
|
||||
main: {
|
||||
/** @type {Set<function>} */
|
||||
onInterpreterReady: new SetFunction(),
|
||||
onWorker: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onReady: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRun: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
@@ -14,15 +72,33 @@ export default {
|
||||
onAfterRun: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRunAsync: new SetFunction(),
|
||||
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRun: new SetString([inputFailure]),
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunAsync: new SetString(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRun: new SetString(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunAsync: new SetString(),
|
||||
},
|
||||
worker: {
|
||||
/** @type {Set<function>} */
|
||||
onWorkerReady: new SetFunction(),
|
||||
onReady: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRun: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRunAsync: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRun: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRunAsync: new SetFunction(),
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunWorker: new SetString(),
|
||||
codeBeforeRun: new SetString(),
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunWorkerAsync: new SetString(),
|
||||
codeBeforeRunAsync: new SetString(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunWorker: new SetString(),
|
||||
codeAfterRun: new SetString(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunWorkerAsync: new SetString(),
|
||||
codeAfterRunAsync: new SetString(),
|
||||
},
|
||||
};
|
||||
|
||||
26
pyscript.core/src/plugins-helper.js
Normal file
26
pyscript.core/src/plugins-helper.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { defineProperty } from "polyscript/exports";
|
||||
|
||||
// helper for all script[type="py"] out there
|
||||
const before = (script) => {
|
||||
defineProperty(document, "currentScript", {
|
||||
configurable: true,
|
||||
get: () => script,
|
||||
});
|
||||
};
|
||||
|
||||
const after = () => {
|
||||
delete document.currentScript;
|
||||
};
|
||||
|
||||
// common life-cycle handlers for any node
|
||||
export default async (main, wrap, element, hook) => {
|
||||
const isAsync = hook.endsWith("Async");
|
||||
const isBefore = hook.startsWith("onBefore");
|
||||
// make it possible to reach the current target node via Python
|
||||
// or clean up for other scripts executing around this one
|
||||
(isBefore ? before : after)(element);
|
||||
for (const fn of main(hook)) {
|
||||
if (isAsync) await fn(wrap, element);
|
||||
else fn(wrap, element);
|
||||
}
|
||||
};
|
||||
27
pyscript.core/src/plugins/deprecations-manager.js
Normal file
27
pyscript.core/src/plugins/deprecations-manager.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// PyScript Derepcations Plugin
|
||||
import { hooks } from "../core.js";
|
||||
import { notify } from "./error.js";
|
||||
|
||||
// react lazily on PyScript bootstrap
|
||||
hooks.main.onReady.add(checkDeprecations);
|
||||
hooks.main.onWorker.add(checkDeprecations);
|
||||
|
||||
/**
|
||||
* Check that there are no scripts loading from pyscript.net/latest
|
||||
*/
|
||||
function checkDeprecations() {
|
||||
const scripts = document.querySelectorAll("script");
|
||||
for (const script of scripts) checkLoadingScriptsFromLatest(script.src);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if src being loaded from pyscript.net/latest and display a notification if true
|
||||
* * @param {string} src
|
||||
*/
|
||||
function checkLoadingScriptsFromLatest(src) {
|
||||
if (/\/pyscript\.net\/latest/.test(src)) {
|
||||
notify(
|
||||
"Loading scripts from latest is deprecated and will be removed soon. Please use a specific version instead.",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
// PyScript Error Plugin
|
||||
import { hooks } from "../core.js";
|
||||
|
||||
hooks.onInterpreterReady.add(function override(pyScript) {
|
||||
hooks.main.onReady.add(function override(pyScript) {
|
||||
// be sure this override happens only once
|
||||
hooks.onInterpreterReady.delete(override);
|
||||
hooks.main.onReady.delete(override);
|
||||
|
||||
// trap generic `stderr` to propagate to it regardless
|
||||
const { stderr } = pyScript.io;
|
||||
|
||||
146
pyscript.core/src/plugins/py-terminal.js
Normal file
146
pyscript.core/src/plugins/py-terminal.js
Normal file
@@ -0,0 +1,146 @@
|
||||
// PyScript py-terminal plugin
|
||||
import { TYPES, hooks } from "../core.js";
|
||||
import { notify } from "./error.js";
|
||||
|
||||
const SELECTOR = [...TYPES.keys()]
|
||||
.map((type) => `script[type="${type}"][terminal],${type}-script[terminal]`)
|
||||
.join(",");
|
||||
|
||||
// show the error on main and
|
||||
// stops the module from keep executing
|
||||
const notifyAndThrow = (message) => {
|
||||
notify(message);
|
||||
throw new Error(message);
|
||||
};
|
||||
|
||||
const pyTerminal = async () => {
|
||||
const terminals = document.querySelectorAll(SELECTOR);
|
||||
|
||||
// no results will look further for runtime nodes
|
||||
if (!terminals.length) return;
|
||||
|
||||
// if we arrived this far, let's drop the MutationObserver
|
||||
// as we only support one terminal per page (right now).
|
||||
mo.disconnect();
|
||||
|
||||
// we currently support only one terminal as in "classic"
|
||||
if (terminals.length > 1) notifyAndThrow("You can use at most 1 terminal.");
|
||||
|
||||
const [element] = terminals;
|
||||
// hopefully to be removed in the near future!
|
||||
if (element.matches('script[type="mpy"],mpy-script'))
|
||||
notifyAndThrow("Unsupported terminal.");
|
||||
|
||||
// import styles lazily
|
||||
document.head.append(
|
||||
Object.assign(document.createElement("link"), {
|
||||
rel: "stylesheet",
|
||||
href: new URL("./xterm.css", import.meta.url),
|
||||
}),
|
||||
);
|
||||
|
||||
// lazy load these only when a valid terminal is found
|
||||
const [{ Terminal }, { Readline }] = await Promise.all([
|
||||
import(/* webpackIgnore: true */ "../3rd-party/xterm.js"),
|
||||
import(/* webpackIgnore: true */ "../3rd-party/xterm-readline.js"),
|
||||
]);
|
||||
|
||||
const readline = new Readline();
|
||||
|
||||
// common main thread initialization for both worker
|
||||
// or main case, bootstrapping the terminal on its target
|
||||
const init = (options) => {
|
||||
let target = element;
|
||||
const selector = element.getAttribute("target");
|
||||
if (selector) {
|
||||
target =
|
||||
document.getElementById(selector) ||
|
||||
document.querySelector(selector);
|
||||
if (!target) throw new Error(`Unknown target ${selector}`);
|
||||
} else {
|
||||
target = document.createElement("py-terminal");
|
||||
target.style.display = "block";
|
||||
element.after(target);
|
||||
}
|
||||
const terminal = new Terminal({
|
||||
theme: {
|
||||
background: "#191A19",
|
||||
foreground: "#F5F2E7",
|
||||
},
|
||||
...options,
|
||||
});
|
||||
terminal.loadAddon(readline);
|
||||
terminal.open(target);
|
||||
terminal.focus();
|
||||
};
|
||||
|
||||
// branch logic for the worker
|
||||
if (element.hasAttribute("worker")) {
|
||||
// when the remote thread onReady triggers:
|
||||
// setup the interpreter stdout and stderr
|
||||
const workerReady = ({ interpreter }, { sync }) => {
|
||||
sync.pyterminal_drop_hooks();
|
||||
const decoder = new TextDecoder();
|
||||
let data = "";
|
||||
const generic = {
|
||||
isatty: true,
|
||||
write(buffer) {
|
||||
data = decoder.decode(buffer);
|
||||
sync.pyterminal_write(data);
|
||||
return buffer.length;
|
||||
},
|
||||
};
|
||||
interpreter.setStdout(generic);
|
||||
interpreter.setStderr(generic);
|
||||
interpreter.setStdin({
|
||||
isatty: true,
|
||||
stdin: () => sync.pyterminal_read(data),
|
||||
});
|
||||
};
|
||||
|
||||
// add a hook on the main thread to setup all sync helpers
|
||||
// also bootstrapping the XTerm target on main
|
||||
hooks.main.onWorker.add(function worker(_, xworker) {
|
||||
hooks.main.onWorker.delete(worker);
|
||||
init({
|
||||
disableStdin: false,
|
||||
cursorBlink: true,
|
||||
cursorStyle: "block",
|
||||
});
|
||||
xworker.sync.pyterminal_read = readline.read.bind(readline);
|
||||
xworker.sync.pyterminal_write = readline.write.bind(readline);
|
||||
// allow a worker to drop main thread hooks ASAP
|
||||
xworker.sync.pyterminal_drop_hooks = () => {
|
||||
hooks.worker.onReady.delete(workerReady);
|
||||
};
|
||||
});
|
||||
|
||||
// setup remote thread JS/Python code for whenever the
|
||||
// worker is ready to become a terminal
|
||||
hooks.worker.onReady.add(workerReady);
|
||||
} else {
|
||||
// in the main case, just bootstrap XTerm without
|
||||
// allowing any input as that's not possible / awkward
|
||||
hooks.main.onReady.add(function main({ io }) {
|
||||
console.warn("py-terminal is read only on main thread");
|
||||
hooks.main.onReady.delete(main);
|
||||
init({
|
||||
disableStdin: true,
|
||||
cursorBlink: false,
|
||||
cursorStyle: "underline",
|
||||
});
|
||||
io.stdout = (value) => {
|
||||
readline.write(`${value}\n`);
|
||||
};
|
||||
io.stderr = (error) => {
|
||||
readline.write(`${error.message || error}\n`);
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const mo = new MutationObserver(pyTerminal);
|
||||
mo.observe(document, { childList: true, subtree: true });
|
||||
|
||||
// try to check the current document ASAP
|
||||
export default pyTerminal();
|
||||
@@ -10,16 +10,22 @@ import pyscript from "./stdlib/pyscript.js";
|
||||
|
||||
const { entries } = Object;
|
||||
|
||||
const python = ["from pathlib import Path as _Path"];
|
||||
const python = [
|
||||
"import os as _os",
|
||||
"from pathlib import Path as _Path",
|
||||
"_path = None",
|
||||
];
|
||||
|
||||
const write = (base, literal) => {
|
||||
for (const [key, value] of entries(literal)) {
|
||||
const path = `_Path("${base}/${key}")`;
|
||||
python.push(`_path = _Path("${base}/${key}")`);
|
||||
if (typeof value === "string") {
|
||||
const code = JSON.stringify(value);
|
||||
python.push(`${path}.write_text(${code})`);
|
||||
python.push(`_path.write_text(${code})`);
|
||||
} else {
|
||||
python.push(`${path}.mkdir(parents=True, exist_ok=True)`);
|
||||
// @see https://github.com/pyscript/pyscript/pull/1813#issuecomment-1781502909
|
||||
python.push(`if not _os.path.exists("${base}/${key}"):`);
|
||||
python.push(" _path.mkdir(parents=True, exist_ok=True)");
|
||||
write(`${base}/${key}`, value);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +34,8 @@ const write = (base, literal) => {
|
||||
write(".", pyscript);
|
||||
|
||||
python.push("del _Path");
|
||||
python.push("del _path");
|
||||
python.push("del _os");
|
||||
python.push("\n");
|
||||
|
||||
export default python.join("\n");
|
||||
|
||||
@@ -6,8 +6,6 @@ from typing import Any
|
||||
from pyodide.ffi import JsProxy
|
||||
from pyscript import display, document, window
|
||||
|
||||
# from pyscript import when as _when
|
||||
|
||||
alert = window.alert
|
||||
|
||||
|
||||
@@ -131,6 +129,22 @@ class Element(BaseElement):
|
||||
def id(self, value):
|
||||
self._js.id = value
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._js.value
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
# in order to avoid confusion to the user, we don't allow setting the
|
||||
# value of elements that don't have a value attribute
|
||||
if not hasattr(self._js, "value"):
|
||||
raise AttributeError(
|
||||
f"Element {self._js.tagName} has no value attribute. If you want to "
|
||||
"force a value attribute, set it directly using the `_js.value = <value>` "
|
||||
"javascript API attribute instead."
|
||||
)
|
||||
self._js.value = value
|
||||
|
||||
def clone(self, new_id=None):
|
||||
clone = Element(self._js.cloneNode(True))
|
||||
clone.id = new_id
|
||||
@@ -161,9 +175,6 @@ class Element(BaseElement):
|
||||
def show_me(self):
|
||||
self._js.scrollIntoView()
|
||||
|
||||
def when(self, event, handler):
|
||||
document.when(event, selector=self)(handler)
|
||||
|
||||
|
||||
class StyleProxy(dict):
|
||||
def __init__(self, element: Element) -> None:
|
||||
@@ -264,6 +275,14 @@ class ElementCollection:
|
||||
def html(self, value):
|
||||
self._set_attribute("html", value)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._get_attribute("value")
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
self._set_attribute("value", value)
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
return self._elements
|
||||
@@ -295,8 +314,8 @@ class PyDom(BaseElement):
|
||||
self.body = Element(document.body)
|
||||
self.head = Element(document.head)
|
||||
|
||||
def create(self, type_, parent=None, classes=None, html=None):
|
||||
return super().create(type_, is_child=False)
|
||||
def create(self, type_, classes=None, html=None):
|
||||
return super().create(type_, is_child=False, classes=classes, html=html)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, int):
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
def on_click(event):
|
||||
print(f"Hello from Python! {dt.now()}")
|
||||
display(f"Hello from Python! {dt.now()}", append=False, target='eresult')
|
||||
display(f"Hello from Python! {dt.now()}", append=False, target='result')
|
||||
|
||||
add_event_listener(element, "click", on_click)
|
||||
</script>
|
||||
|
||||
60
pyscript.core/test/hooks.html
Normal file
60
pyscript.core/test/hooks.html
Normal file
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PyScript Next Plugin Bug?</title>
|
||||
<link rel="stylesheet" href="../dist/core.css">
|
||||
<script type="module">
|
||||
addEventListener('mpy:done', () => {
|
||||
document.documentElement.classList.add('done');
|
||||
});
|
||||
|
||||
import { hooks } from "../dist/core.js";
|
||||
|
||||
// Main
|
||||
hooks.main.onReady.add((wrap, element) => {
|
||||
console.log("main", "onReady");
|
||||
if (location.search === '?debug') {
|
||||
console.debug("main", "wrap", wrap);
|
||||
console.debug("main", "element", element);
|
||||
}
|
||||
});
|
||||
hooks.main.onBeforeRun.add(() => {
|
||||
console.log("main", "onBeforeRun");
|
||||
});
|
||||
hooks.main.codeBeforeRun.add('print("main", "codeBeforeRun")');
|
||||
hooks.main.codeAfterRun.add('print("main", "codeAfterRun")');
|
||||
hooks.main.onAfterRun.add(() => {
|
||||
console.log("main", "onAfterRun");
|
||||
});
|
||||
|
||||
// Worker
|
||||
hooks.worker.onReady.add((wrap, xworker) => {
|
||||
console.log("worker", "onReady");
|
||||
if (location.search === '?debug') {
|
||||
console.debug("worker", "wrap", wrap);
|
||||
console.debug("worker", "xworker", xworker);
|
||||
}
|
||||
});
|
||||
hooks.worker.onBeforeRun.add(() => {
|
||||
console.log("worker", "onBeforeRun");
|
||||
});
|
||||
hooks.worker.codeBeforeRun.add('print("worker", "codeBeforeRun")');
|
||||
hooks.worker.codeAfterRun.add('print("worker", "codeAfterRun")');
|
||||
hooks.worker.onAfterRun.add(() => {
|
||||
console.log("worker", "onAfterRun");
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy" worker>
|
||||
from pyscript import document
|
||||
print("actual code in worker")
|
||||
document.documentElement.classList.add('worker')
|
||||
</script>
|
||||
<script type="mpy">
|
||||
print("actual code in main")
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
21
pyscript.core/test/input.html
Normal file
21
pyscript.core/test/input.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PyScript Next</title>
|
||||
<script>
|
||||
addEventListener("py:ready", console.log);
|
||||
</script>
|
||||
<link rel="stylesheet" href="../dist/core.css">
|
||||
<script type="module" src="../dist/core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<py-script>
|
||||
input("what's your name?")
|
||||
</py-script>
|
||||
<mpy-script>
|
||||
input("what's your name?")
|
||||
</mpy-script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,7 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PyScript Next</title>
|
||||
<script>
|
||||
addEventListener("mpy:ready", console.log);
|
||||
addEventListener('mpy:done', () => {
|
||||
document.documentElement.classList.add('done');
|
||||
});
|
||||
</script>
|
||||
<link rel="stylesheet" href="../dist/core.css">
|
||||
<script type="module" src="../dist/core.js"></script>
|
||||
@@ -13,11 +15,16 @@
|
||||
<body>
|
||||
<script type="mpy">
|
||||
from pyscript import display
|
||||
display("Hello", "M-PyScript Next", append=False)
|
||||
display("Hello", "M-PyScript Main 1", append=False)
|
||||
</script>
|
||||
<mpy-script worker>
|
||||
<mpy-script>
|
||||
from pyscript import display
|
||||
display("Hello", "M-PyScript Next Worker", append=False)
|
||||
display("Hello", "M-PyScript Main 2", append=False)
|
||||
</mpy-script>
|
||||
<mpy-script worker>
|
||||
from pyscript import display, document
|
||||
display("Hello", "M-PyScript Worker", append=False)
|
||||
document.documentElement.classList.add('worker')
|
||||
</mpy-script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
37
pyscript.core/test/mpy.spec.js
Normal file
37
pyscript.core/test/mpy.spec.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('MicroPython display', async ({ page }) => {
|
||||
await page.goto('http://localhost:8080/test/mpy.html');
|
||||
await page.waitForSelector('html.done.worker');
|
||||
const body = await page.evaluate(() => document.body.innerText);
|
||||
await expect(body.trim()).toBe([
|
||||
'M-PyScript Main 1',
|
||||
'M-PyScript Main 2',
|
||||
'M-PyScript Worker',
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
test('MicroPython hooks', async ({ page }) => {
|
||||
const logs = [];
|
||||
page.on('console', msg => {
|
||||
const text = msg.text();
|
||||
if (!text.startsWith('['))
|
||||
logs.push(text);
|
||||
});
|
||||
await page.goto('http://localhost:8080/test/hooks.html');
|
||||
await page.waitForSelector('html.done.worker');
|
||||
await expect(logs.join('\n')).toBe([
|
||||
'main onReady',
|
||||
'main onBeforeRun',
|
||||
'main codeBeforeRun',
|
||||
'actual code in main',
|
||||
'main codeAfterRun',
|
||||
'main onAfterRun',
|
||||
'worker onReady',
|
||||
'worker onBeforeRun',
|
||||
'worker codeBeforeRun',
|
||||
'actual code in worker',
|
||||
'worker codeAfterRun',
|
||||
'worker onAfterRun',
|
||||
].join('\n'));
|
||||
});
|
||||
29
pyscript.core/test/py-terminal.html
Normal file
29
pyscript.core/test/py-terminal.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PyTerminal</title>
|
||||
<link rel="stylesheet" href="../dist/core.css">
|
||||
<script type="module" src="../dist/core.js"></script>
|
||||
<style>.xterm { padding: .5rem; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py">
|
||||
def greetings(event):
|
||||
print('hello world')
|
||||
</script>
|
||||
<py-script worker terminal>
|
||||
import sys
|
||||
from pyscript import display, document
|
||||
display("Hello", "PyScript Next - PyTerminal", append=False)
|
||||
print("this should go to the terminal")
|
||||
print("another line")
|
||||
|
||||
# this works as expected
|
||||
print("this goes to stderr", file=sys.stderr)
|
||||
document.addEventListener('click', lambda event: print(event.type));
|
||||
</py-script>
|
||||
<button id="my-button" py-click="greetings">Click me</button>
|
||||
</body>
|
||||
</html>
|
||||
@@ -64,8 +64,8 @@
|
||||
<h2 id="multi-elem-h2" class="multi-elems">Content multi-elem-h2</h2>
|
||||
|
||||
<form>
|
||||
<input id="test_rr_input_txt" type="text" value="Content test_rr_input_txt">
|
||||
<input id="test_rr_input_btn" type="button" value="Content test_rr_input_btn">
|
||||
<input id="test_rr_input_text" type="text" value="Content test_rr_input_text">
|
||||
<input id="test_rr_input_button" type="button" value="Content test_rr_input_button">
|
||||
<input id="test_rr_input_email" type="email" value="Content test_rr_input_email">
|
||||
<input id="test_rr_input_password" type="password" value="Content test_rr_input_password">
|
||||
</form>
|
||||
@@ -89,7 +89,6 @@
|
||||
const log = console.log.bind(console)
|
||||
let testsStarted = false;
|
||||
console.log = (...args) => {
|
||||
log("---IN---");
|
||||
let txt = args.join(" ");
|
||||
let token = "<br>";
|
||||
if (txt.endsWith("FAILED"))
|
||||
|
||||
@@ -244,3 +244,51 @@ class TestCreation:
|
||||
assert new_el.parent == parent_div
|
||||
|
||||
assert pydom[selector][0].children[0] == new_el
|
||||
|
||||
|
||||
class TestInput:
|
||||
input_ids = [
|
||||
"test_rr_input_text",
|
||||
"test_rr_input_button",
|
||||
"test_rr_input_email",
|
||||
"test_rr_input_password",
|
||||
]
|
||||
|
||||
def test_value(self):
|
||||
for id_ in self.input_ids:
|
||||
expected_type = id_.split("_")[-1]
|
||||
result = pydom[f"#{id_}"]
|
||||
input_el = result[0]
|
||||
assert input_el._js.type == expected_type
|
||||
assert input_el.value == f"Content {id_}" == input_el._js.value
|
||||
|
||||
# Check that we can set the value
|
||||
new_value = f"New Value {expected_type}"
|
||||
input_el.value = new_value
|
||||
assert input_el.value == new_value
|
||||
|
||||
# Check that we can set the value back to the original using
|
||||
# the collection
|
||||
new_value = f"Content {id_}"
|
||||
result.value = new_value
|
||||
assert input_el.value == new_value
|
||||
|
||||
def test_set_value_collection(self):
|
||||
for id_ in self.input_ids:
|
||||
input_el = pydom[f"#{id_}"]
|
||||
|
||||
assert input_el.value[0] == f"Content {id_}" == input_el[0].value
|
||||
|
||||
new_value = f"New Value {id_}"
|
||||
input_el.value = new_value
|
||||
assert input_el.value[0] == new_value == input_el[0].value
|
||||
|
||||
def test_element_without_value(self):
|
||||
result = pydom[f"#tests-terminal"][0]
|
||||
with pytest.raises(AttributeError):
|
||||
result.value = "some value"
|
||||
|
||||
def test_element_without_collection(self):
|
||||
result = pydom[f"#tests-terminal"]
|
||||
with pytest.raises(AttributeError):
|
||||
result.value = "some value"
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>PyScript Next - Terminal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@latest/css/xterm.min.css">
|
||||
<script type="module">
|
||||
import { Readline } from "https://cdn.jsdelivr.net/npm/xterm-readline@latest/+esm";
|
||||
const rl = new Readline;
|
||||
rl.setCheckHandler(text => !text.trimEnd().endsWith("&&"));
|
||||
|
||||
import { Terminal } from "https://cdn.jsdelivr.net/npm/xterm@latest/+esm";
|
||||
const term = new Terminal({
|
||||
theme: {
|
||||
background: "#191A19",
|
||||
foreground: "#F5F2E7",
|
||||
},
|
||||
cursorBlink: true,
|
||||
cursorStyle: "block",
|
||||
});
|
||||
term.loadAddon(rl);
|
||||
term.open(terminal);
|
||||
term.focus();
|
||||
|
||||
import { PyWorker } from "../dist/core.js";
|
||||
const { sync } = new PyWorker("terminal.py");
|
||||
Object.assign(sync, {
|
||||
readline: prompt => rl.read(prompt),
|
||||
write: line => term.write(line),
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="terminal"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,14 +0,0 @@
|
||||
###### magic monkey patching ######
|
||||
import builtins
|
||||
import sys
|
||||
|
||||
from pyodide.code import eval_code
|
||||
from pyscript import sync
|
||||
|
||||
sys.stdout = sync
|
||||
builtins.input = sync.readline
|
||||
|
||||
####### main code ######
|
||||
import code
|
||||
|
||||
code.interact()
|
||||
@@ -8,8 +8,10 @@
|
||||
|
||||
<!-- the PyWorker approach -->
|
||||
<script type="module">
|
||||
import { PyWorker } from '../dist/core.js';
|
||||
import { PyWorker, whenDefined } from '../dist/core.js';
|
||||
whenDefined('py').then(() => {
|
||||
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>
|
||||
|
||||
@@ -118,6 +118,20 @@ def only_main(fn):
|
||||
return decorated
|
||||
|
||||
|
||||
def only_worker(fn):
|
||||
"""
|
||||
Decorator to mark a test which make sense only in the worker thread
|
||||
"""
|
||||
|
||||
@functools.wraps(fn)
|
||||
def decorated(self, *args):
|
||||
if self.execution_thread != "worker":
|
||||
return
|
||||
return fn(self, *args)
|
||||
|
||||
return decorated
|
||||
|
||||
|
||||
def filter_inner_text(text, exclude=None):
|
||||
return "\n".join(filter_page_content(text.splitlines(), exclude=exclude))
|
||||
|
||||
@@ -531,7 +545,9 @@ class PyScriptTest:
|
||||
- wait until pyscript has been fully loaded
|
||||
"""
|
||||
doc = self._pyscript_format(
|
||||
snippet, execution_thread=self.execution_thread, extra_head=extra_head
|
||||
snippet,
|
||||
execution_thread=self.execution_thread,
|
||||
extra_head=extra_head,
|
||||
)
|
||||
if not wait_for_pyscript and timeout is not None:
|
||||
raise ValueError("Cannot set a timeout if wait_for_pyscript=False")
|
||||
@@ -652,7 +668,7 @@ TEST_ITERATIONS = math.ceil(
|
||||
) # 120 iters of 1/4 second
|
||||
|
||||
|
||||
def wait_for_render(page, selector, pattern, timeout_seconds: int | None = None):
|
||||
def wait_for_render(page, selector, pattern, timeout_seconds=None):
|
||||
"""
|
||||
Assert that rendering inserts data into the page as expected: search the
|
||||
DOM from within the timing loop for a string that is not present in the
|
||||
|
||||
@@ -80,7 +80,7 @@ class TestBasic(PyScriptTest):
|
||||
'"Cross-Origin-Opener-Policy":"same-origin"}. '
|
||||
"The problem may be that one or both of these are missing."
|
||||
)
|
||||
alert_banner = self.page.wait_for_selector(".alert-banner")
|
||||
alert_banner = self.page.wait_for_selector(".py-error")
|
||||
assert expected_alert_banner_msg in alert_banner.inner_text()
|
||||
|
||||
def test_print(self):
|
||||
@@ -93,6 +93,19 @@ class TestBasic(PyScriptTest):
|
||||
)
|
||||
assert self.console.log.lines[-1] == "hello pyscript"
|
||||
|
||||
@only_main
|
||||
def test_input_exception(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<script type="py">
|
||||
input("what's your name?")
|
||||
</script>
|
||||
"""
|
||||
)
|
||||
self.check_py_errors(
|
||||
"Exception: input() doesn't work when PyScript runs in the main thread."
|
||||
)
|
||||
|
||||
@skip_worker("NEXT: exceptions should be displayed in the DOM")
|
||||
def test_python_exception(self):
|
||||
self.pyscript_run(
|
||||
|
||||
@@ -3,15 +3,62 @@ import time
|
||||
import pytest
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from .support import PyScriptTest, skip_worker
|
||||
|
||||
pytest.skip(
|
||||
reason="FIX LATER: pyscript NEXT doesn't support the Terminal yet",
|
||||
allow_module_level=True,
|
||||
)
|
||||
from .support import PageErrors, PyScriptTest, only_worker, skip_worker
|
||||
|
||||
|
||||
class TestPyTerminal(PyScriptTest):
|
||||
def test_multiple_terminals(self):
|
||||
"""
|
||||
Multiple terminals are not currently supported
|
||||
"""
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<script type="py" terminal></script>
|
||||
<script type="py" terminal></script>
|
||||
""",
|
||||
wait_for_pyscript=False,
|
||||
check_js_errors=False,
|
||||
)
|
||||
assert self.assert_banner_message("You can use at most 1 terminal")
|
||||
|
||||
with pytest.raises(PageErrors, match="You can use at most 1 terminal"):
|
||||
self.check_js_errors()
|
||||
|
||||
# TODO: interactive shell still unclear
|
||||
# @only_worker
|
||||
# def test_py_terminal_input(self):
|
||||
# """
|
||||
# Only worker py-terminal accepts an input
|
||||
# """
|
||||
# self.pyscript_run(
|
||||
# """
|
||||
# <script type="py" terminal></script>
|
||||
# """,
|
||||
# wait_for_pyscript=False,
|
||||
# )
|
||||
# self.page.get_by_text(">>> ", exact=True).wait_for()
|
||||
# self.page.keyboard.type("'the answer is ' + str(6 * 7)")
|
||||
# self.page.keyboard.press("Enter")
|
||||
# self.page.get_by_text("the answer is 42").wait_for()
|
||||
|
||||
@only_worker
|
||||
def test_py_terminal_os_write(self):
|
||||
"""
|
||||
An `os.write("text")` should land in the terminal
|
||||
"""
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<script type="py" terminal>
|
||||
import os
|
||||
os.write(1, str.encode("hello\\n"))
|
||||
os.write(2, str.encode("world\\n"))
|
||||
</script>
|
||||
""",
|
||||
wait_for_pyscript=False,
|
||||
)
|
||||
self.page.get_by_text("hello\n").wait_for()
|
||||
self.page.get_by_text("world\n").wait_for()
|
||||
|
||||
def test_py_terminal(self):
|
||||
"""
|
||||
1. <py-terminal> should redirect stdout and stderr to the DOM
|
||||
@@ -20,138 +67,44 @@ class TestPyTerminal(PyScriptTest):
|
||||
"""
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-terminal></py-terminal>
|
||||
|
||||
<script type="py">
|
||||
<script type="py" terminal>
|
||||
import sys
|
||||
print('hello world')
|
||||
print('this goes to stderr', file=sys.stderr)
|
||||
print('this goes to stdout')
|
||||
</script>
|
||||
"""
|
||||
""",
|
||||
wait_for_pyscript=False,
|
||||
)
|
||||
self.page.get_by_text("hello world").wait_for()
|
||||
term = self.page.locator("py-terminal")
|
||||
term_lines = term.inner_text().splitlines()
|
||||
assert term_lines == [
|
||||
"hello world",
|
||||
"this goes to stderr",
|
||||
"this goes to stdout",
|
||||
]
|
||||
assert self.console.log.lines[-3:] == [
|
||||
assert term_lines[0:3] == [
|
||||
"hello world",
|
||||
"this goes to stderr",
|
||||
"this goes to stdout",
|
||||
]
|
||||
|
||||
@skip_worker("FIXME: js.document")
|
||||
def test_two_terminals(self):
|
||||
"""
|
||||
Multiple <py-terminal>s can cohexist.
|
||||
A <py-terminal> receives only output from the moment it is added to
|
||||
the DOM.
|
||||
"""
|
||||
@skip_worker(
|
||||
"Workers don't have events + two different workers don't share the same I/O"
|
||||
)
|
||||
def test_button_action(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-terminal id="term1"></py-terminal>
|
||||
|
||||
<script type="py">
|
||||
import js
|
||||
print('one')
|
||||
term2 = js.document.createElement('py-terminal')
|
||||
term2.id = 'term2'
|
||||
js.document.body.append(term2)
|
||||
|
||||
print('two')
|
||||
print('three')
|
||||
</script>
|
||||
"""
|
||||
)
|
||||
term1 = self.page.locator("#term1")
|
||||
term2 = self.page.locator("#term2")
|
||||
term1_lines = term1.inner_text().splitlines()
|
||||
term2_lines = term2.inner_text().splitlines()
|
||||
assert term1_lines == ["one", "two", "three"]
|
||||
assert term2_lines == ["two", "three"]
|
||||
|
||||
def test_auto_attribute(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-terminal auto></py-terminal>
|
||||
|
||||
<button id="my-button" py-click="print('hello world')">Click me</button>
|
||||
"""
|
||||
)
|
||||
term = self.page.locator("py-terminal")
|
||||
expect(term).to_be_hidden()
|
||||
self.page.locator("button").click()
|
||||
expect(term).to_be_visible()
|
||||
assert term.inner_text() == "hello world\n"
|
||||
|
||||
def test_config_auto(self):
|
||||
"""
|
||||
config.terminal == "auto" is the default: a <py-terminal auto> is
|
||||
automatically added to the page
|
||||
"""
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<button id="my-button" py-click="print('hello world')">Click me</button>
|
||||
"""
|
||||
)
|
||||
term = self.page.locator("py-terminal")
|
||||
expect(term).to_be_hidden()
|
||||
assert "No <py-terminal> found, adding one" in self.console.info.text
|
||||
#
|
||||
self.page.locator("button").click()
|
||||
expect(term).to_be_visible()
|
||||
assert term.inner_text() == "hello world\n"
|
||||
|
||||
def test_config_true(self):
|
||||
"""
|
||||
If we set config.terminal == true, a <py-terminal> is automatically added
|
||||
"""
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-config>
|
||||
terminal = true
|
||||
</py-config>
|
||||
|
||||
<script type="py">
|
||||
def greetings(event):
|
||||
print('hello world')
|
||||
</script>
|
||||
"""
|
||||
)
|
||||
term = self.page.locator("py-terminal")
|
||||
expect(term).to_be_visible()
|
||||
assert term.inner_text() == "hello world\n"
|
||||
<script type="py" terminal></script>
|
||||
|
||||
def test_config_false(self):
|
||||
"""
|
||||
If we set config.terminal == false, no <py-terminal> is added
|
||||
"""
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-config>
|
||||
terminal = false
|
||||
</py-config>
|
||||
"""
|
||||
)
|
||||
term = self.page.locator("py-terminal")
|
||||
assert term.count() == 0
|
||||
|
||||
def test_config_docked(self):
|
||||
"""
|
||||
config.docked == "docked" is also the default: a <py-terminal auto docked> is
|
||||
automatically added to the page
|
||||
"""
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<button id="my-button" py-click="print('hello world')">Click me</button>
|
||||
<button id="my-button" py-click="greetings">Click me</button>
|
||||
"""
|
||||
)
|
||||
term = self.page.locator("py-terminal")
|
||||
self.page.locator("button").click()
|
||||
expect(term).to_be_visible()
|
||||
assert term.get_attribute("docked") == ""
|
||||
last_line = self.page.get_by_text("hello world")
|
||||
last_line.wait_for()
|
||||
assert term.inner_text().rstrip() == "hello world"
|
||||
|
||||
def test_xterm_function(self):
|
||||
"""Test a few basic behaviors of the xtermjs terminal.
|
||||
@@ -164,17 +117,15 @@ class TestPyTerminal(PyScriptTest):
|
||||
"""
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-config>
|
||||
xterm = true
|
||||
</py-config>
|
||||
<script type="py">
|
||||
<script type="py" terminal>
|
||||
print("\x1b[33mYellow\x1b[0m")
|
||||
print("\x1b[4mUnderline\x1b[24m")
|
||||
print("\x1b[1mBold\x1b[22m")
|
||||
print("\x1b[3mItalic\x1b[23m")
|
||||
print("done")
|
||||
</script>
|
||||
"""
|
||||
""",
|
||||
wait_for_pyscript=False,
|
||||
)
|
||||
|
||||
# Wait for "done" to actually appear in the xterm; may be delayed,
|
||||
@@ -234,37 +185,3 @@ class TestPyTerminal(PyScriptTest):
|
||||
"(element) => getComputedStyle(element).getPropertyValue('font-style')"
|
||||
)
|
||||
assert font_style == "italic"
|
||||
|
||||
def test_xterm_multiple(self):
|
||||
"""Test whether multiple x-terms on the page all function"""
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-config>
|
||||
xterm = true
|
||||
</py-config>
|
||||
<script type="py">
|
||||
print("\x1b[33mYellow\x1b[0m")
|
||||
print("done")
|
||||
</script>
|
||||
<py-terminal id="a"></py-terminal>
|
||||
<py-terminal id="b" data-testid="b"></py-terminal>
|
||||
"""
|
||||
)
|
||||
|
||||
# Wait for "done" to actually appear in the xterm; may be delayed,
|
||||
# since xtermjs processes its input buffer in chunks
|
||||
last_line = self.page.get_by_test_id("b").get_by_text("done")
|
||||
last_line.wait_for()
|
||||
|
||||
# Yes, this is not ideal. See note in `test_xterm_function`
|
||||
time.sleep(1)
|
||||
|
||||
rows = self.page.locator("#a .xterm-rows")
|
||||
|
||||
# First line should be yellow
|
||||
first_line = rows.locator("div").nth(0)
|
||||
first_char = first_line.locator("span").nth(0)
|
||||
color = first_char.evaluate(
|
||||
"(element) => getComputedStyle(element).getPropertyValue('color')"
|
||||
)
|
||||
assert color == "rgb(196, 160, 0)"
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
import pytest
|
||||
|
||||
from .support import PyScriptTest
|
||||
|
||||
pytest.skip(reason="NEXT: Restore the banner", allow_module_level=True)
|
||||
from .support import PyScriptTest, skip_worker
|
||||
|
||||
|
||||
class TestWarningsAndBanners(PyScriptTest):
|
||||
# Test the behavior of generated warning banners
|
||||
|
||||
def test_deprecate_loading_scripts_from_latest(self):
|
||||
# Use a script tag with an invalid output attribute to generate a warning, but only one
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<script type="py">
|
||||
print("whatever..")
|
||||
</script>
|
||||
""",
|
||||
extra_head='<script type="ignore-me" src="https://pyscript.net/latest/any-path-triggers-the-warning-anyway.js"></script>',
|
||||
)
|
||||
|
||||
# wait for the banner to appear (we could have a page.locater call but for some reason
|
||||
# the worker takes to long to render on CI, since it's a test we can afford 2 calls)
|
||||
loc = self.page.wait_for_selector(".py-error")
|
||||
assert (
|
||||
loc.inner_text()
|
||||
== "Loading scripts from latest is deprecated and will be removed soon. Please use a specific version instead."
|
||||
)
|
||||
|
||||
# Only one banner should appear
|
||||
loc = self.page.locator(".py-error")
|
||||
assert loc.count() == 1
|
||||
|
||||
@pytest.mark.skip("NEXT: To check if behaviour is consistent with classic")
|
||||
def test_create_singular_warning(self):
|
||||
# Use a script tag with an invalid output attribute to generate a warning, but only one
|
||||
self.pyscript_run(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ES2022",
|
||||
"target": "ES2022",
|
||||
"moduleResolution": "Classic",
|
||||
"module": "NodeNext",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "nodenext",
|
||||
"allowJs": true,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
|
||||
12
pyscript.core/types/3rd-party/toml.d.ts
vendored
Normal file
12
pyscript.core/types/3rd-party/toml.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/*! (c) Jak Wings - MIT */ declare class e extends SyntaxError {
|
||||
constructor(r: any, { offset: t, line: e, column: n }: {
|
||||
offset: any;
|
||||
line: any;
|
||||
column: any;
|
||||
});
|
||||
offset: any;
|
||||
line: any;
|
||||
column: any;
|
||||
}
|
||||
declare function n(n: any): any;
|
||||
export { e as SyntaxError, n as parse };
|
||||
138
pyscript.core/types/3rd-party/xterm-readline.d.ts
vendored
Normal file
138
pyscript.core/types/3rd-party/xterm-readline.d.ts
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
declare var b: any;
|
||||
declare var I: boolean;
|
||||
declare namespace r {
|
||||
export let __esModule: boolean;
|
||||
export { Readline };
|
||||
}
|
||||
declare class Readline {
|
||||
highlighter: {
|
||||
highlight(t: any, e: any): any;
|
||||
highlightPrompt(t: any): any;
|
||||
highlightChar(t: any, e: any): boolean;
|
||||
};
|
||||
history: {
|
||||
entries: any[];
|
||||
cursor: number;
|
||||
maxEntries: any;
|
||||
saveToLocalStorage(): void;
|
||||
restoreFromLocalStorage(): void;
|
||||
append(t: any): void;
|
||||
resetCursor(): void;
|
||||
next(): any;
|
||||
prev(): any;
|
||||
};
|
||||
disposables: any[];
|
||||
watermark: number;
|
||||
highWatermark: number;
|
||||
lowWatermark: number;
|
||||
highWater: boolean;
|
||||
state: {
|
||||
line: {
|
||||
buf: string;
|
||||
pos: number;
|
||||
buffer(): string;
|
||||
pos_buffer(): string;
|
||||
length(): number;
|
||||
char_length(): number;
|
||||
update(t: any, e: any): void;
|
||||
insert(t: any): boolean;
|
||||
moveBack(t: any): boolean;
|
||||
moveForward(t: any): boolean;
|
||||
moveHome(): boolean;
|
||||
moveEnd(): boolean;
|
||||
startOfLine(): number;
|
||||
endOfLine(): number;
|
||||
moveLineUp(t: any): boolean;
|
||||
moveLineDown(t: any): boolean;
|
||||
set_pos(t: any): void;
|
||||
prevPos(t: any): number;
|
||||
nextPos(t: any): number;
|
||||
backspace(t: any): boolean;
|
||||
delete(t: any): boolean;
|
||||
deleteEndOfLine(): boolean;
|
||||
};
|
||||
highlighting: boolean;
|
||||
prompt: any;
|
||||
tty: any;
|
||||
highlighter: any;
|
||||
history: any;
|
||||
promptSize: any;
|
||||
layout: p;
|
||||
buffer(): string;
|
||||
shouldHighlight(): boolean;
|
||||
clearScreen(): void;
|
||||
editInsert(t: any): void;
|
||||
update(t: any): void;
|
||||
editBackspace(t: any): void;
|
||||
editDelete(t: any): void;
|
||||
editDeleteEndOfLine(): void;
|
||||
refresh(): void;
|
||||
moveCursorBack(t: any): void;
|
||||
moveCursorForward(t: any): void;
|
||||
moveCursorUp(t: any): void;
|
||||
moveCursorDown(t: any): void;
|
||||
moveCursorHome(): void;
|
||||
moveCursorEnd(): void;
|
||||
moveCursorToEnd(): void;
|
||||
previousHistory(): void;
|
||||
nextHistory(): void;
|
||||
moveCursor(): void;
|
||||
};
|
||||
checkHandler: () => boolean;
|
||||
ctrlCHandler: () => void;
|
||||
pauseHandler: (t: any) => void;
|
||||
activate(t: any): void;
|
||||
term: any;
|
||||
dispose(): void;
|
||||
appendHistory(t: any): void;
|
||||
setHighlighter(t: any): void;
|
||||
setCheckHandler(t: any): void;
|
||||
setCtrlCHandler(t: any): void;
|
||||
setPauseHandler(t: any): void;
|
||||
writeReady(): boolean;
|
||||
write(t: any): void;
|
||||
print(t: any): void;
|
||||
println(t: any): void;
|
||||
output(): this;
|
||||
tty(): {
|
||||
tabWidth: any;
|
||||
col: any;
|
||||
row: any;
|
||||
out: any;
|
||||
write(t: any): any;
|
||||
print(t: any): any;
|
||||
println(t: any): any;
|
||||
clearScreen(): void;
|
||||
calculatePosition(t: any, e: any): any;
|
||||
computeLayout(t: any, e: any): {
|
||||
promptSize: any;
|
||||
cursor: any;
|
||||
end: any;
|
||||
};
|
||||
refreshLine(t: any, e: any, s: any, i: any, r: any): void;
|
||||
clearOldRows(t: any): void;
|
||||
moveCursor(t: any, e: any): void;
|
||||
};
|
||||
read(t: any): Promise<any>;
|
||||
activeRead: {
|
||||
prompt: any;
|
||||
resolve: (value: any) => void;
|
||||
reject: (reason?: any) => void;
|
||||
};
|
||||
handleKeyEvent(t: any): boolean;
|
||||
readData(t: any): void;
|
||||
readPaste(t: any): void;
|
||||
readKey(t: any): void;
|
||||
}
|
||||
declare class p {
|
||||
constructor(t: any);
|
||||
promptSize: any;
|
||||
cursor: c;
|
||||
end: c;
|
||||
}
|
||||
declare class c {
|
||||
constructor(t: any, e: any);
|
||||
row: any;
|
||||
col: any;
|
||||
}
|
||||
export { b as Readline, I as __esModule, r as default };
|
||||
4
pyscript.core/types/3rd-party/xterm.d.ts
vendored
Normal file
4
pyscript.core/types/3rd-party/xterm.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare var i: any;
|
||||
declare var s: any;
|
||||
declare var t: {};
|
||||
export { i as Terminal, s as __esModule, t as default };
|
||||
34
pyscript.core/types/core.d.ts
vendored
34
pyscript.core/types/core.d.ts
vendored
@@ -1,16 +1,42 @@
|
||||
import TYPES from "./types.js";
|
||||
/**
|
||||
* 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?: {
|
||||
declare function exportedPyWorker(file: string, options?: {
|
||||
config?: string | object;
|
||||
async?: boolean;
|
||||
}): Worker & {
|
||||
sync: ProxyHandler<object>;
|
||||
};
|
||||
import sync from "./sync.js";
|
||||
declare const exportedHooks: {
|
||||
main: {
|
||||
onWorker: Set<Function>;
|
||||
onReady: Set<Function>;
|
||||
onBeforeRun: Set<Function>;
|
||||
onBeforeRunAsync: Set<Function>;
|
||||
onAfterRun: Set<Function>;
|
||||
onAfterRunAsync: Set<Function>;
|
||||
codeBeforeRun: Set<string>;
|
||||
codeBeforeRunAsync: Set<string>;
|
||||
codeAfterRun: Set<string>;
|
||||
codeAfterRunAsync: Set<string>;
|
||||
};
|
||||
worker: {
|
||||
onReady: Set<Function>;
|
||||
onBeforeRun: Set<Function>;
|
||||
onBeforeRunAsync: Set<Function>;
|
||||
onAfterRun: Set<Function>;
|
||||
onAfterRunAsync: Set<Function>;
|
||||
codeBeforeRun: Set<string>;
|
||||
codeBeforeRunAsync: Set<string>;
|
||||
codeAfterRun: Set<string>;
|
||||
codeAfterRunAsync: Set<string>;
|
||||
};
|
||||
};
|
||||
declare const exportedConfig: {};
|
||||
import hooks from "./hooks.js";
|
||||
export { exportedConfig as config, hooks };
|
||||
declare const exportedWhenDefined: (type: string) => Promise<any>;
|
||||
import sync from "./sync.js";
|
||||
export { TYPES, exportedPyWorker as PyWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };
|
||||
|
||||
1
pyscript.core/types/fetch.d.ts
vendored
1
pyscript.core/types/fetch.d.ts
vendored
@@ -9,3 +9,4 @@
|
||||
*/
|
||||
export function robustFetch(url: string, options?: Request): Promise<Response>;
|
||||
export { getText };
|
||||
import { getText } from "polyscript/exports";
|
||||
|
||||
41
pyscript.core/types/hooks.d.ts
vendored
41
pyscript.core/types/hooks.d.ts
vendored
@@ -1,13 +1,38 @@
|
||||
declare namespace _default {
|
||||
let onInterpreterReady: Set<Function>;
|
||||
export function main(name: any): any;
|
||||
export function worker(name: any): any;
|
||||
export function codeFor(branch: any): {};
|
||||
export function createFunction(self: any, name: any): any;
|
||||
export namespace hooks {
|
||||
namespace main {
|
||||
let onWorker: Set<Function>;
|
||||
let onReady: Set<Function>;
|
||||
let onBeforeRun: Set<Function>;
|
||||
let onBeforeRunAsync: Set<Function>;
|
||||
let onAfterRun: Set<Function>;
|
||||
let onAfterRunAsync: Set<Function>;
|
||||
let onWorkerReady: Set<Function>;
|
||||
let codeBeforeRunWorker: Set<string>;
|
||||
let codeBeforeRunWorkerAsync: Set<string>;
|
||||
let codeAfterRunWorker: Set<string>;
|
||||
let codeAfterRunWorkerAsync: Set<string>;
|
||||
let codeBeforeRun: Set<string>;
|
||||
let codeBeforeRunAsync: Set<string>;
|
||||
let codeAfterRun: Set<string>;
|
||||
let codeAfterRunAsync: Set<string>;
|
||||
}
|
||||
namespace worker {
|
||||
let onReady_1: Set<Function>;
|
||||
export { onReady_1 as onReady };
|
||||
let onBeforeRun_1: Set<Function>;
|
||||
export { onBeforeRun_1 as onBeforeRun };
|
||||
let onBeforeRunAsync_1: Set<Function>;
|
||||
export { onBeforeRunAsync_1 as onBeforeRunAsync };
|
||||
let onAfterRun_1: Set<Function>;
|
||||
export { onAfterRun_1 as onAfterRun };
|
||||
let onAfterRunAsync_1: Set<Function>;
|
||||
export { onAfterRunAsync_1 as onAfterRunAsync };
|
||||
let codeBeforeRun_1: Set<string>;
|
||||
export { codeBeforeRun_1 as codeBeforeRun };
|
||||
let codeBeforeRunAsync_1: Set<string>;
|
||||
export { codeBeforeRunAsync_1 as codeBeforeRunAsync };
|
||||
let codeAfterRun_1: Set<string>;
|
||||
export { codeAfterRun_1 as codeAfterRun };
|
||||
let codeAfterRunAsync_1: Set<string>;
|
||||
export { codeAfterRunAsync_1 as codeAfterRunAsync };
|
||||
}
|
||||
}
|
||||
export default _default;
|
||||
|
||||
2
pyscript.core/types/plugins-helper.d.ts
vendored
Normal file
2
pyscript.core/types/plugins-helper.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare function _default(main: any, wrap: any, element: any, hook: any): Promise<void>;
|
||||
export default _default;
|
||||
8
pyscript.core/types/plugins.d.ts
vendored
8
pyscript.core/types/plugins.d.ts
vendored
@@ -1,4 +1,6 @@
|
||||
declare namespace _default {
|
||||
function error(): Promise<typeof import("./plugins/error.js")>;
|
||||
}
|
||||
declare const _default: {
|
||||
"deprecations-manager": () => Promise<typeof import("./plugins/deprecations-manager.js")>;
|
||||
error: () => Promise<typeof import("./plugins/error.js")>;
|
||||
"py-terminal": () => Promise<typeof import("./plugins/py-terminal.js")>;
|
||||
};
|
||||
export default _default;
|
||||
|
||||
1
pyscript.core/types/plugins/deprecations-manager.d.ts
vendored
Normal file
1
pyscript.core/types/plugins/deprecations-manager.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
2
pyscript.core/types/plugins/py-terminal.d.ts
vendored
Normal file
2
pyscript.core/types/plugins/py-terminal.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare const _default: Promise<void>;
|
||||
export default _default;
|
||||
12
pyscript.core/types/toml.d.ts
vendored
Normal file
12
pyscript.core/types/toml.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/*! (c) Jak Wings - MIT */ declare class e extends SyntaxError {
|
||||
constructor(r: any, { offset: t, line: e, column: n }: {
|
||||
offset: any;
|
||||
line: any;
|
||||
column: any;
|
||||
});
|
||||
offset: any;
|
||||
line: any;
|
||||
column: any;
|
||||
}
|
||||
declare function n(n: any): any;
|
||||
export { e as SyntaxError, n as parse };
|
||||
13
requirements.txt
Normal file
13
requirements.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
black
|
||||
isort
|
||||
pytest==7.1.2
|
||||
pre-commit
|
||||
playwright==1.33.0
|
||||
pytest-playwright==0.3.3
|
||||
pytest-xdist==3.3.0
|
||||
pexpect
|
||||
pyodide_py==0.24.1
|
||||
micropip
|
||||
toml
|
||||
numpy
|
||||
pillow
|
||||
Reference in New Issue
Block a user