mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-20 02:37:41 -05:00
Compare commits
117 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f319452d5 | ||
|
|
60d505d2d1 | ||
|
|
f64cc4dcae | ||
|
|
60e6f4293a | ||
|
|
00ab9a8d02 | ||
|
|
7d5f6c9ead | ||
|
|
a295edf19d | ||
|
|
b674515d06 | ||
|
|
d033ab04da | ||
|
|
304d76d088 | ||
|
|
978afdad97 | ||
|
|
d4e41e679d | ||
|
|
146264ff12 | ||
|
|
dcb107ae65 | ||
|
|
c236269d13 | ||
|
|
8f658e6d85 | ||
|
|
d203b60f44 | ||
|
|
a1a16aba74 | ||
|
|
6ded003447 | ||
|
|
4841e29fc6 | ||
|
|
0b014eea56 | ||
|
|
1c0be16f30 | ||
|
|
27ba8bea2f | ||
|
|
c566977749 | ||
|
|
5c1b785b4b | ||
|
|
8657dfb5da | ||
|
|
dfa837754e | ||
|
|
0a7df78770 | ||
|
|
066ecbe022 | ||
|
|
6c80db810f | ||
|
|
6023c413ab | ||
|
|
7910d040b6 | ||
|
|
5bd99f5224 | ||
|
|
f3157b377f | ||
|
|
e31e03afde | ||
|
|
eddde7c94c | ||
|
|
7be72ee4c1 | ||
|
|
6731467514 | ||
|
|
8dd699d235 | ||
|
|
6cb81b5c3d | ||
|
|
17187ba3ec | ||
|
|
531ee928b0 | ||
|
|
db806a5df9 | ||
|
|
9de154595a | ||
|
|
9e4cb79679 | ||
|
|
b7834073b8 | ||
|
|
b0e56577b5 | ||
|
|
ccb0e6b269 | ||
|
|
edfd4baa1f | ||
|
|
0f50f4a9fd | ||
|
|
47494e62a7 | ||
|
|
7aa25712d9 | ||
|
|
1db155570d | ||
|
|
1054e8e644 | ||
|
|
aa429f34d8 | ||
|
|
e351889811 | ||
|
|
24a70a8273 | ||
|
|
3f26657116 | ||
|
|
d41669af8b | ||
|
|
56466c2a00 | ||
|
|
fa7a97ca30 | ||
|
|
8aba271a42 | ||
|
|
8275aa2810 | ||
|
|
410ddf314c | ||
|
|
fa217bee20 | ||
|
|
817d0edc69 | ||
|
|
513dfe0b42 | ||
|
|
bd7a20309b | ||
|
|
a726be3c7c | ||
|
|
10f2054e9a | ||
|
|
2fa47f310d | ||
|
|
5b927a70c2 | ||
|
|
2a59ff8e68 | ||
|
|
e4d1befcdb | ||
|
|
844e04ff96 | ||
|
|
cc05a98b0e | ||
|
|
006d161a32 | ||
|
|
a4839db79a | ||
|
|
87e3b5b1dc | ||
|
|
faa900d502 | ||
|
|
a5275db3ec | ||
|
|
77e017a574 | ||
|
|
8ed8ddbf76 | ||
|
|
eb31978488 | ||
|
|
677d708588 | ||
|
|
ade0dca8f9 | ||
|
|
8e1cd0b268 | ||
|
|
9102768366 | ||
|
|
0c722b9164 | ||
|
|
b6f514451a | ||
|
|
c49fdfc56c | ||
|
|
676e04b28e | ||
|
|
6aa864a351 | ||
|
|
72acb4826c | ||
|
|
734be5f355 | ||
|
|
cede06ae19 | ||
|
|
19491d8010 | ||
|
|
c580aac991 | ||
|
|
032d1aaad7 | ||
|
|
afa216dc5e | ||
|
|
69339fe3de | ||
|
|
571bb2b294 | ||
|
|
91a09a09f7 | ||
|
|
9b3433f6ae | ||
|
|
ee9b0960f7 | ||
|
|
506ac2574f | ||
|
|
dc84d7c1b5 | ||
|
|
fcaa57307f | ||
|
|
d25e754beb | ||
|
|
7f6f411ea8 | ||
|
|
96a73e31f3 | ||
|
|
c7942d7d8f | ||
|
|
479348eec9 | ||
|
|
ebfed27630 | ||
|
|
8923485169 | ||
|
|
d62de26683 | ||
|
|
1dd9c5b009 |
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: true
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Question
|
- name: Question
|
||||||
url: https://community.anaconda.cloud/c/tech-topics/pyscript
|
url: https://community.anaconda.cloud/c/tech-topics/pyscript
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -35,7 +35,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I searched for other feature requests and couldn't find a duplicate (including also the ``type-feature`` tag)
|
- label: I searched for other feature requests and couldn't find a duplicate (including also the ``type-feature`` tag)
|
||||||
required: true
|
required: true
|
||||||
- label: I confirmed that it's not related to another project are area (see the above section)
|
- label: I confirmed that it's not related to another project area (see the above section)
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: request-idea
|
id: request-idea
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: '[CI] Build Latest'
|
name: '[CI] Build Unstable'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push: # Only run on merges into main that modify files under pyscriptjs/
|
push: # Only run on merges into main that modify files under pyscriptjs/
|
||||||
@@ -8,27 +8,23 @@ on:
|
|||||||
- pyscriptjs/**
|
- pyscriptjs/**
|
||||||
- .github/workflows/build-latest.yml # Test that workflow works when changed
|
- .github/workflows/build-latest.yml # Test that workflow works when changed
|
||||||
|
|
||||||
pull_request: # Run on any PR that modifies files in pyscriptjs/
|
pull_request: # Run on any PR that modifies files under pyscriptjs/
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- pyscriptjs/**
|
- pyscriptjs/**
|
||||||
- .github/workflows/build-latest.yml # Test that workflow works when changed
|
- .github/workflows/build-unstable.yml # Test that workflow works when changed
|
||||||
|
workflow_dispatch:
|
||||||
env:
|
|
||||||
MINICONDA_PYTHON_VERSION: py38
|
|
||||||
MINICONDA_VERSION: 4.11.0
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: pyscriptjs
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
BuildAndTest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
defaults:
|
||||||
contents: read
|
run:
|
||||||
id-token: write
|
working-directory: pyscriptjs
|
||||||
|
env:
|
||||||
|
MINICONDA_PYTHON_VERSION: py38
|
||||||
|
MINICONDA_VERSION: 4.11.0
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -58,17 +54,46 @@ jobs:
|
|||||||
- name: Setup Environment
|
- name: Setup Environment
|
||||||
run: make setup
|
run: make setup
|
||||||
|
|
||||||
- name: Build and Test
|
- name: Build
|
||||||
run: make test
|
run: make build
|
||||||
|
|
||||||
|
- name: TypeScript Tests
|
||||||
|
run: make test-ts
|
||||||
|
|
||||||
|
- name: Python Tests
|
||||||
|
run: make test-py
|
||||||
|
|
||||||
|
- name: Integration Tests
|
||||||
|
run: make test-integration
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: pyscript
|
||||||
|
path: |
|
||||||
|
pyscriptjs/build/
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
Deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: BuildAndTest
|
||||||
|
if: github.ref == 'refs/heads/main' # Only deploy on merge into main
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: pyscript
|
||||||
|
path: ./build/
|
||||||
|
|
||||||
# Deploy to S3
|
# Deploy to S3
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
if: github.ref == 'refs/heads/main' # Only deploy on merge into main
|
|
||||||
uses: aws-actions/configure-aws-credentials@v1.6.1
|
uses: aws-actions/configure-aws-credentials@v1.6.1
|
||||||
with:
|
with:
|
||||||
aws-region: ${{secrets.AWS_REGION}}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||||
|
|
||||||
- name: Sync to S3
|
- name: Sync to S3
|
||||||
if: github.ref == 'refs/heads/main'
|
run: aws s3 sync --quiet ./build/ s3://pyscript.net/unstable/
|
||||||
run: aws s3 sync --quiet ./examples/build/ s3://pyscript.net/latest/
|
|
||||||
2
.github/workflows/docs-latest.yml
vendored
2
.github/workflows/docs-latest.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
|||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v1.6.1
|
uses: aws-actions/configure-aws-credentials@v1.6.1
|
||||||
with:
|
with:
|
||||||
aws-region: ${{secrets.AWS_REGION}}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||||
|
|
||||||
- name: Copy redirect file
|
- name: Copy redirect file
|
||||||
|
|||||||
2
.github/workflows/docs-release.yml
vendored
2
.github/workflows/docs-release.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
|||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v1.6.1
|
uses: aws-actions/configure-aws-credentials@v1.6.1
|
||||||
with:
|
with:
|
||||||
aws-region: ${{secrets.AWS_REGION}}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||||
|
|
||||||
- name: Copy redirect file
|
- name: Copy redirect file
|
||||||
|
|||||||
5
.github/workflows/docs-review.yml
vendored
5
.github/workflows/docs-review.yml
vendored
@@ -18,8 +18,7 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: >-
|
if: github.repository_owner == 'pyscript'
|
||||||
!github.event.repository.fork
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -58,7 +57,7 @@ jobs:
|
|||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v1.6.1
|
uses: aws-actions/configure-aws-credentials@v1.6.1
|
||||||
with:
|
with:
|
||||||
aws-region: ${{secrets.AWS_REGION}}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||||
|
|
||||||
- name: Copy redirect file
|
- name: Copy redirect file
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: '[CI] Build Release'
|
name: '[CI] Prepare Release'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
7
.github/workflows/publish-release.yml
vendored
7
.github/workflows/publish-release.yml
vendored
@@ -53,9 +53,10 @@ jobs:
|
|||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v1.6.1
|
uses: aws-actions/configure-aws-credentials@v1.6.1
|
||||||
with:
|
with:
|
||||||
aws-region: ${{secrets.AWS_REGION}}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||||
|
|
||||||
- name: Sync to S3
|
- name: Sync to S3
|
||||||
run: | # Overwrite "latest" alpha + versioned subdirectory
|
run: | # Update /latest and create an explicitly versioned directory under releases/YYYY.MM.MICRO/
|
||||||
aws s3 sync --quiet ./examples/build/ s3://pyscript.net/releases/${{ github.ref_name }}
|
aws s3 sync --quiet ./build/ s3://pyscript.net/latest/
|
||||||
|
aws s3 sync --quiet ./build/ s3://pyscript.net/releases/${{ github.ref_name }}/
|
||||||
|
|||||||
26
.github/workflows/publish-snapshot.yml
vendored
Normal file
26
.github/workflows/publish-snapshot.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: '[CI] Publish Snapshot'
|
||||||
|
# Copy /unstable/ to /snapshots/2022.09.1.RC1/
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
snapshot_version:
|
||||||
|
description: 'The calver version of this snapshot: 2022.09.1 or 2022.09.1.RC1'
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
snapshot:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v1.6.1
|
||||||
|
with:
|
||||||
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
|
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||||
|
- name: Sync to S3
|
||||||
|
run: >
|
||||||
|
aws s3 sync s3://pyscript.net/unstable/ s3://pyscript.net/snapshots/${{ inputs.snapshot_version }}/
|
||||||
10
.github/workflows/sync-examples.yml
vendored
10
.github/workflows/sync-examples.yml
vendored
@@ -1,12 +1,8 @@
|
|||||||
name: '[CI] Sync Examples'
|
name: '[CI] Sync Examples'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push: # Only run on merges into main that modify files under examples/
|
release:
|
||||||
branches:
|
types: [published]
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- examples/**
|
|
||||||
- .github/workflows/sync-examples.yml # Test that workflow works when changed
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -26,7 +22,7 @@ jobs:
|
|||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v1.6.1
|
uses: aws-actions/configure-aws-credentials@v1.6.1
|
||||||
with:
|
with:
|
||||||
aws-region: ${{secrets.AWS_REGION}}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||||
- name: Sync to S3
|
- name: Sync to S3
|
||||||
# Sync outdated or new files, delete ones no longer in source
|
# Sync outdated or new files, delete ones no longer in source
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -136,3 +136,5 @@ dmypy.json
|
|||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
coverage/
|
||||||
|
|||||||
@@ -25,20 +25,21 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: bandit
|
- id: bandit
|
||||||
args:
|
args:
|
||||||
- --skip=B201
|
- --skip=B101,B201
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 22.3.0
|
rev: 22.8.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
|
||||||
|
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/codespell-project/codespell
|
||||||
rev: v2.1.0
|
rev: v2.2.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: codespell # See 'setup.cfg' for args
|
- id: codespell # See 'setup.cfg' for args
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 4.0.1
|
rev: 5.0.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8 # See 'setup.cfg' for args
|
- id: flake8 # See 'setup.cfg' for args
|
||||||
additional_dependencies: [flake8-bugbear, flake8-comprehensions]
|
additional_dependencies: [flake8-bugbear, flake8-comprehensions]
|
||||||
@@ -51,21 +52,21 @@ repos:
|
|||||||
args: [--profile, black]
|
args: [--profile, black]
|
||||||
|
|
||||||
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
|
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
|
||||||
rev: v2.3.0
|
rev: v2.4.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pretty-format-yaml
|
- id: pretty-format-yaml
|
||||||
args: [--autofix, --indent, '4']
|
args: [--autofix, --indent, '4']
|
||||||
exclude: .github/ISSUE_TEMPLATE/.*\.yml$
|
exclude: .github/ISSUE_TEMPLATE/.*\.yml$
|
||||||
|
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.34.0
|
rev: v2.38.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args:
|
args:
|
||||||
- --py310-plus
|
- --py310-plus
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||||
rev: v8.18.0
|
rev: v8.24.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: eslint
|
- id: eslint
|
||||||
files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
|
files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
|
||||||
|
|||||||
161
CONTRIBUTING.md
161
CONTRIBUTING.md
@@ -9,58 +9,182 @@ Thank you for wanting to contribute to the PyScript project!
|
|||||||
* [Reporting bugs](#reporting-bugs)
|
* [Reporting bugs](#reporting-bugs)
|
||||||
* [Reporting security issues](#reporting-security-issues)
|
* [Reporting security issues](#reporting-security-issues)
|
||||||
* [Asking questions](#asking-questions)
|
* [Asking questions](#asking-questions)
|
||||||
* [Setting up your environment](#setting-up-your-environment)
|
* [Setting up your local environment](#setting-up-your-local-environment)
|
||||||
* [Places to start](#places-to-start)
|
* [Places to start](#places-to-start)
|
||||||
* [Submitting a change](#submitting-a-change)
|
* [Submitting a change](#submitting-a-change)
|
||||||
* [License terms for contributions](#license-terms-for-contributions)
|
* [License terms for contributions](#license-terms-for-contributions)
|
||||||
* [Becoming a maintainer](#becoming-a-maintainer)
|
* [Becoming a maintainer](#becoming-a-maintainer)
|
||||||
* [Trademarks](#trademarks)
|
* [Trademarks](#trademarks)
|
||||||
|
|
||||||
## Code of Conduct
|
# Code of Conduct
|
||||||
|
|
||||||
The [PyScript Code of Conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md) governs the project and everyone participating in it. By participating, you are expected to uphold this code. Please report unacceptable behavior to the maintainers or administrators as described in that document.
|
The [PyScript Code of Conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md) governs the project and everyone participating in it. By participating, you are expected to uphold this code. Please report unacceptable behavior to the maintainers or administrators as described in that document.
|
||||||
|
|
||||||
## Contributing
|
# Contributing
|
||||||
|
|
||||||
### Reporting bugs
|
## Reporting bugs
|
||||||
|
|
||||||
Bugs are tracked on the [project issues page](https://github.com/pyscript/pyscript/issues). Please check if your issue has already been filed by someone else by searching the existing issues before filing a new one. Once your issue is filed, it will be triaged by another contributor or maintainer. If there are questions raised about your issue, please respond promptly.
|
Bugs are tracked on the [project issues page](https://github.com/pyscript/pyscript/issues). Please check if your issue has already been filed by someone else by searching the existing issues before filing a new one. Once your issue is filed, it will be triaged by another contributor or maintainer. If there are questions raised about your issue, please respond promptly.
|
||||||
|
|
||||||
#### Creating useful issues
|
## Creating useful issues
|
||||||
|
|
||||||
* Use a clear and descriptive title.
|
* Use a clear and descriptive title.
|
||||||
* Describe the specific steps that reproduce the problem with as many details as possible so that someone can verify the issue.
|
* Describe the specific steps that reproduce the problem with as many details as possible so that someone can verify the issue.
|
||||||
* Describe the behavior you observed, and the behavior you had expected.
|
* Describe the behavior you observed, and the behavior you had expected.
|
||||||
* Include screenshots if they help make the issue clear.
|
* Include screenshots if they help make the issue clear.
|
||||||
|
|
||||||
### Reporting security issues
|
## Reporting security issues
|
||||||
|
|
||||||
If you aren't confident that it is appropriate to submit a security issue using the above process, you can e-mail it to security@pyscript.net
|
If you aren't confident that it is appropriate to submit a security issue using the above process, you can e-mail it to security@pyscript.net
|
||||||
|
|
||||||
### Asking questions
|
## Asking questions
|
||||||
|
|
||||||
If you have questions about the project, using PyScript, or anything else, please ask in the [PyScript forum](https://community.anaconda.cloud/c/tech-topics/pyscript).
|
If you have questions about the project, using PyScript, or anything else, please ask in the [PyScript forum](https://community.anaconda.cloud/c/tech-topics/pyscript).
|
||||||
|
|
||||||
### Setting up your environment
|
## Setting up your local environment
|
||||||
|
|
||||||
|
* Fork the repository - [quicklink](https://github.com/pyscript/pyscript/fork)
|
||||||
|
|
||||||
|
* Clone your fork of the project
|
||||||
|
|
||||||
* clone the repo
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/pyscript/pyscript
|
git clone https://github.com/<your username>/pyscript
|
||||||
```
|
```
|
||||||
* cd into the main project folder
|
|
||||||
|
* Add the original project as your upstream (this will allow you to pull the latest changes)
|
||||||
|
|
||||||
|
```
|
||||||
|
git remote add upstream git@github.com:pyscript/pyscript.git
|
||||||
|
```
|
||||||
|
|
||||||
|
* cd into the `pyscriptjs` folder using the line below in your terminal (if your terminal is already in pyscript then use **cd pyscriptjs** instead)
|
||||||
```
|
```
|
||||||
cd pyscript/pyscriptjs
|
cd pyscript/pyscriptjs
|
||||||
```
|
```
|
||||||
* install the dependencies with npm install - make sure to use nodejs version >= 16
|
* Install the dependencies with the command below
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install
|
make setup
|
||||||
```
|
```
|
||||||
* run npm run dev to build and run the dev server. This will also watch for changes and rebuild when a file is saved.
|
**NOTE**: If `make setup` gives a node/npm version required error then go to [troubleshooting](https://github.com/pyscript/pyscript/blob/main/TROUBLESHOOTING.md)
|
||||||
|
|
||||||
|
* You can also run the examples locally by running the command below in your terminal
|
||||||
|
```
|
||||||
|
make examples
|
||||||
|
```
|
||||||
|
* Run ***npm run dev*** to build and run the dev server. This will also watch for changes and rebuild when a file is saved.
|
||||||
```
|
```
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
**NOTE**: To access your local build paste `http://localhost:8080` into your browser
|
||||||
|
|
||||||
### Places to start
|
|
||||||
|
Now that node and npm have both been updated `make setup` should work, and you can continue [setting up your local environment](#setting-up-your-local-environment) without problems (hopefully).
|
||||||
|
|
||||||
|
### Developing
|
||||||
|
|
||||||
|
* First, make sure you are using the latest version of the pyscript main branch
|
||||||
|
|
||||||
|
```
|
||||||
|
git pull upstream main
|
||||||
|
```
|
||||||
|
|
||||||
|
* Update your fork with the latest changes
|
||||||
|
|
||||||
|
```
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
* Activate the conda environment (this environment will contain all the necessary dependencies)
|
||||||
|
|
||||||
|
```
|
||||||
|
conda activate pyscriptjs/env/
|
||||||
|
```
|
||||||
|
**NOTE**: We are assuming you are in the root folder. If you are in the pyscriptjs you can run `conda activate env/` instead.
|
||||||
|
|
||||||
|
* Install pre-commit (you only need to do this once)
|
||||||
|
|
||||||
|
```
|
||||||
|
pre-commit install
|
||||||
|
```
|
||||||
|
**NOTE**: On first run, pre-commit installs a bunch of hooks that will be run when you commit changes to your branch - this will make sure that your code is following our style (it will also lint your code automatically).
|
||||||
|
|
||||||
|
* Create a branch for the issue that you want to work on
|
||||||
|
|
||||||
|
```
|
||||||
|
git checkout -b <your branch name>
|
||||||
|
```
|
||||||
|
|
||||||
|
* Work on your change
|
||||||
|
|
||||||
|
**NOTE**: If you are working on a python file, you may encounter linting issues when pre-commit runs. Pyscript uses [black](https://black.readthedocs.io/en/stable/) to fix any linting problems automatically. All you need to do is add the changes again and commit using your previous commit message (the previous one that failed didn't complete due to black formatting files).
|
||||||
|
|
||||||
|
* Run tests before pushing the changes
|
||||||
|
|
||||||
|
```
|
||||||
|
make tests
|
||||||
|
```
|
||||||
|
|
||||||
|
* When you make changes locally, double check that your contribution follows the PyScript formatting rules by running `npm run lint`. Note that in this case you're looking for the errors, <u>**NOT**</u> the warnings (Unless the warning is created by a local change). If an error is found by lint you should fix it <u>**before**</u> creating a pull request
|
||||||
|
|
||||||
|
#### Rebasing changes
|
||||||
|
|
||||||
|
Sometimes you might be asked to rebase main into your branch. You can do such by:
|
||||||
|
|
||||||
|
* Checkout into your main branch and pull the upstream changes
|
||||||
|
|
||||||
|
```
|
||||||
|
git checkout main
|
||||||
|
git pull upstream main
|
||||||
|
```
|
||||||
|
|
||||||
|
* Checkout your branch and rebase on main
|
||||||
|
|
||||||
|
```
|
||||||
|
git rebase -i main
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have conflicts, you must fix them by comparing yours and incoming changes. Your editor can probably help you with this, but do ask for help if you need it!
|
||||||
|
|
||||||
|
* Once all conflicts have been fixed
|
||||||
|
|
||||||
|
```
|
||||||
|
git rebase --continue
|
||||||
|
```
|
||||||
|
**NOTE**: You may see more conflicts that you need to address until all are resolved.
|
||||||
|
|
||||||
|
* Force push the fixed conflicts
|
||||||
|
|
||||||
|
```
|
||||||
|
git push -f origin <your branch name>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Building the docs
|
||||||
|
|
||||||
|
To build the documentation locally first make sure you are in the `docs` directory.
|
||||||
|
|
||||||
|
You'll need `make` and `conda` installed in your machine. The rest of the environment should be automatically download and created for you once you use the command:
|
||||||
|
|
||||||
|
```
|
||||||
|
make setup
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `conda activate $environment_name` to activate your environment.
|
||||||
|
|
||||||
|
To add new information to the documentation make sure you conform with PyScript's code of conduct and with the general principles of Diataxis. Don't worry about reading too much on it, just do your best to keep your contributions on the correct axis.
|
||||||
|
|
||||||
|
Write your documentation files using [Markedly Structured Text](https://myst-parser.readthedocs.io/en/latest/syntax/optional.html), which is very similar to vanilla Markdown but with some addons to create the documentation infrastructure.
|
||||||
|
|
||||||
|
Once done, initialize a server to check your work:
|
||||||
|
|
||||||
|
```
|
||||||
|
make livehtml
|
||||||
|
```
|
||||||
|
|
||||||
|
Visible here: http:///127.0.0.1:8000
|
||||||
|
|
||||||
|
## Places to start
|
||||||
|
|
||||||
If you would like to contribute to PyScript, but you aren't sure where to begin, here are some suggestions.
|
If you would like to contribute to PyScript, but you aren't sure where to begin, here are some suggestions.
|
||||||
|
|
||||||
@@ -69,15 +193,14 @@ If you would like to contribute to PyScript, but you aren't sure where to begin,
|
|||||||
* **Look over the open pull requests.** Do you have comments or suggestions for the proposed changes? Add them.
|
* **Look over the open pull requests.** Do you have comments or suggestions for the proposed changes? Add them.
|
||||||
* **Check out the examples.** Is there a use case that would be good to have sample code for? Create an example for it.
|
* **Check out the examples.** Is there a use case that would be good to have sample code for? Create an example for it.
|
||||||
|
|
||||||
### Submitting a change
|
# Submitting a change
|
||||||
|
|
||||||
All contributions must be licensed Apache 2.0, and all files must have a copy of the boilerplate license comment (can be copied from an existing file).
|
All contributions must be licensed Apache 2.0, and all files must have a copy of the boilerplate license comment (can be copied from an existing file).
|
||||||
|
|
||||||
To create a change for PyScript, you can follow the process described [here](https://docs.github.com/en/get-started/quickstart/contributing-to-projects).
|
To create a change for PyScript, you can follow the process described [here](https://docs.github.com/en/get-started/quickstart/contributing-to-projects).
|
||||||
|
|
||||||
* Fork a personal copy of the PyScript project.
|
* Follow the steps in [setting your local environment](#setting-up-your-local-environment) and [developing](#developing)
|
||||||
* Make the changes you would like (don't forget to test them!)
|
* Make the changes you would like (don't forget to test them with `make test`)
|
||||||
* Please squash all commits for a change into a single commit (this can be done using "git rebase -i"). Do your best to have a well-formed commit message for the change.
|
|
||||||
* Open a pull request back to the PyScript project and address any comments/questions from the maintainers and other contributors.
|
* Open a pull request back to the PyScript project and address any comments/questions from the maintainers and other contributors.
|
||||||
|
|
||||||
## License terms for contributions
|
## License terms for contributions
|
||||||
|
|||||||
@@ -2,15 +2,19 @@
|
|||||||
|
|
||||||
This document lists the Maintainers of the Project. Maintainers may be added once approved by the existing maintainers as described in the [Governance document](https://github.com/pyscript/pyscript/blob/main/GOVERNANCE.md). By adding your name to this list you are agreeing to abide by the Project governance documents and to abide by all of the Organization's polices, including the [code of conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md), [trademark policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md), and [antitrust policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md). If you are participating because of your affiliation with another organization (designated below), you represent that you have the authority to bind that organization to these policies.
|
This document lists the Maintainers of the Project. Maintainers may be added once approved by the existing maintainers as described in the [Governance document](https://github.com/pyscript/pyscript/blob/main/GOVERNANCE.md). By adding your name to this list you are agreeing to abide by the Project governance documents and to abide by all of the Organization's polices, including the [code of conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md), [trademark policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md), and [antitrust policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md). If you are participating because of your affiliation with another organization (designated below), you represent that you have the authority to bind that organization to these policies.
|
||||||
|
|
||||||
| **NAME** | **Organization** |
|
| **NAME** | **Organization** |
|
||||||
| ---------------- | ---------------- |
|
| -------------------- | ---------------- |
|
||||||
| Fabio Pliger | Anaconda, Inc |
|
| Fabio Pliger | Anaconda, Inc |
|
||||||
| Antonio Cuni | Anaconda, Inc |
|
| Antonio Cuni | Anaconda, Inc |
|
||||||
| Philipp Rudiger | Anaconda, Inc |
|
| Philipp Rudiger | Anaconda, Inc |
|
||||||
| Peter Wang | Anaconda, Inc |
|
| Peter Wang | Anaconda, Inc |
|
||||||
| Kevin Goldsmith | Anaconda, Inc |
|
| Kevin Goldsmith | Anaconda, Inc |
|
||||||
| Mariana Meireles | Anaconda, Inc |
|
| Mariana Meireles | Anaconda, Inc |
|
||||||
| --- | --- |
|
| Nicholas H.Tollervey | Anaconda, Inc |
|
||||||
|
| Madhur Tandon | Anaconda, Inc |
|
||||||
|
| Ted Patrick | Anaconda, Inc |
|
||||||
|
| Jeff Glass | --- |
|
||||||
|
| --- | --- |
|
||||||
|
|
||||||
______________________________________________________________________
|
______________________________________________________________________
|
||||||
|
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
|
|
||||||
PyScript is a Pythonic alternative to Scratch, JSFiddle, and other "easy to use" programming frameworks, with the goal of making the web a friendly, hackable place where anyone can author interesting and interactive applications.
|
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.
|
||||||
|
|
||||||
To get started see the [getting started tutorial](docs/tutorials/getting-started.md).
|
To get started see the [getting started tutorial](docs/tutorials/getting-started.md).
|
||||||
|
|
||||||
@@ -15,10 +15,12 @@ PyScript is a meta project that aims to combine multiple open technologies into
|
|||||||
|
|
||||||
## Try PyScript
|
## Try PyScript
|
||||||
|
|
||||||
To try PyScript, import the appropriate pyscript files to your html page with:
|
To try PyScript, import the appropriate pyscript files into the ```<head>``` tag of your html page with:
|
||||||
```html
|
```html
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<head>
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
</head>
|
||||||
```
|
```
|
||||||
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 implements the following elements:
|
||||||
|
|
||||||
|
|||||||
18
TROUBLESHOOTING.md
Normal file
18
TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
This page is meant for troubleshooting common problems with PyScript.
|
||||||
|
|
||||||
|
## Table of contents:
|
||||||
|
* [Make Setup](#make-setup)
|
||||||
|
|
||||||
|
## Make setup
|
||||||
|
|
||||||
|
A lot of problems related to `make setup` are related to node and npm being outdated. Once npm and node are updated, `make setup` should work. You can follow the steps on the [npm documentation](https://docs.npmjs.com/try-the-latest-stable-version-of-npm) to update npm (the update command for Linux should work for Mac as well). Once npm has been updated you can continue to the instructions to update node below.
|
||||||
|
|
||||||
|
To update Node run the following commands in order (Most likely you'll be prompted for your user password, this is normal):
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo npm cache clean -f
|
||||||
|
sudo npm install -g n
|
||||||
|
sudo n stable
|
||||||
|
```
|
||||||
@@ -31,7 +31,8 @@ shell:
|
|||||||
@echo 'conda activate $(env)'
|
@echo 'conda activate $(env)'
|
||||||
|
|
||||||
htmlserve:
|
htmlserve:
|
||||||
python -m http.server -d "$(BUILDDIR)/html/"
|
@echo 'visit docs at http://localhost:8080'
|
||||||
|
python -m http.server -d "$(BUILDDIR)/html/" 8080
|
||||||
|
|
||||||
livehtml:
|
livehtml:
|
||||||
sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|||||||
@@ -9,9 +9,23 @@ Before you start contributing to the documentation, it's worthwhile to
|
|||||||
take a look at the general contributing guidelines for the PyScript project. You can find these guidelines here
|
take a look at the general contributing guidelines for the PyScript project. You can find these guidelines here
|
||||||
[Contributing Guidelines](https://github.com/pyscript/pyscript/blob/main/CONTRIBUTING.md)
|
[Contributing Guidelines](https://github.com/pyscript/pyscript/blob/main/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
## Documentation Principles
|
||||||
|
|
||||||
|
The PyScript documentation is based on a documentation framework called [Diátaxis](https://diataxis.fr/). This framework helps to solve the problem of structure in technical documentation and identifies four modes of documentation - **tutorials, how-to guides, technical reference and explanation**. Each one of these modes answers to a different user need, fulfills a different purpose and requires a different approach to its creation.
|
||||||
|
|
||||||
|
The picture below gives a good visual representation of that separation of concerns:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
So, please keep that in mind when contributing to the project documentation. For more information on, make sure to check [their website](https://diataxis.fr/).
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
The `docs` directory in the pyscript repository contains a
|
The `docs` directory in the pyscript repository contains a
|
||||||
[Sphinx](https://www.sphinx-doc.org/) documentation project. Sphinx is a system
|
[Sphinx](https://www.sphinx-doc.org/) documentation project. Sphinx is a system
|
||||||
that takes plaintext files containing documentation written in Markdown, along with
|
that takes plaintext files containing documentation written in Markdown, along with
|
||||||
and static files like templates and themes, to build the static end result.
|
static files like templates and themes, to build the static end result.
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
To learn how to build the docs, head over the [CONTRIBUTING](../CONTRIBUTING.md) page.
|
||||||
|
|||||||
4
docs/_static/examples/what-is-pyscript.html
vendored
4
docs/_static/examples/what-is-pyscript.html
vendored
@@ -1,7 +1,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
<style>
|
<style>
|
||||||
.pulse {
|
.pulse {
|
||||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ To try it in your browser, copy the code below into an online HTML editor like W
|
|||||||
|
|
||||||
```{literalinclude} ../_static/examples/what-is-pyscript.html
|
```{literalinclude} ../_static/examples/what-is-pyscript.html
|
||||||
---
|
---
|
||||||
linenos:
|
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|||||||
210
docs/howtos/http-requests.md
Normal file
210
docs/howtos/http-requests.md
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
# How to make HTTP requests using `PyScript`, in pure Python
|
||||||
|
|
||||||
|
[Pyodide](https://pyodide.org), the runtime that underlies `PyScript`, does not have the `requests` module
|
||||||
|
(or other similar modules) available by default, which are traditionally used to make HTTP requests in Python.
|
||||||
|
However, it is possible to make HTTP requests in Pyodide using the modern `JavaScript` `fetch` API
|
||||||
|
([docs](https://developer.mozilla.org/en-US/docs/Web/API/fetch)). This example shows how to make common HTTP request
|
||||||
|
(GET, POST, PUT, DELETE) to an API, using only Python code! We will use asynchronous functions with
|
||||||
|
async/await syntax, as concurrent code is preferred for HTTP requests.
|
||||||
|
|
||||||
|
The purpose of this guide is not to teach the basics of HTTP requests, but to show how to make them
|
||||||
|
from `PyScript` using Python, since currently, the common tools such as `requests` and `httpx` are not available.
|
||||||
|
|
||||||
|
## Fetch
|
||||||
|
|
||||||
|
The `fetch` API is a modern way to make HTTP requests. It is available in all modern browsers, and in Pyodide.
|
||||||
|
|
||||||
|
Although there are two ways to use `fetch`, 1) using `JavaScript` from `PyScript`, and 2) using Pyodide's Python wrapper,
|
||||||
|
`Pyodide.http.pyfetch`, this example will only show how to use the Python wrapper. Still, the
|
||||||
|
[fetch documentation](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters) is a useful reference, as its
|
||||||
|
parameters can be called from Python using the `pyfetch` wrapper.
|
||||||
|
|
||||||
|
## Pyodide.http, pyfetch, and FetchResponse
|
||||||
|
|
||||||
|
[Pyodide.http module](https://pyodide.org/en/stable/usage/api/python-api/http.html#module-pyodide.http) is a Python API
|
||||||
|
for dealing with HTTP requests. It provides the `pyfetch` function as a wrapper for the `fetch` API,
|
||||||
|
which returns a `FetchResponse` object whenever a request is made. Extra keyword arguments can be passed to `pyfetch`
|
||||||
|
which will be passed to the `fetch` API.
|
||||||
|
|
||||||
|
The returned object `FetchResponse` has familiar methods and properties
|
||||||
|
for dealing with the response, such as `json()` or `status`. See the
|
||||||
|
[FetchResponse documentation](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.FetchResponse)
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
# Example
|
||||||
|
We will make async HTTP requests to [JSONPlaceholder](https://jsonplaceholder.typicode.com/)'s fake API using `pyfetch`.
|
||||||
|
First we write a helper function in pure Python that makes a request and returns the response. This function
|
||||||
|
makes it easier to make specific types of requests with the most common parameters.
|
||||||
|
|
||||||
|
## Python convenience function
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pyodide.http import pyfetch, FetchResponse
|
||||||
|
from typing import Optional, Any
|
||||||
|
|
||||||
|
async def request(url: str, method: str = "GET", body: Optional[str] = None,
|
||||||
|
headers: Optional[dict[str, str]] = None, **fetch_kwargs: Any) -> FetchResponse:
|
||||||
|
"""
|
||||||
|
Async request function. Pass in Method and make sure to await!
|
||||||
|
Parameters:
|
||||||
|
url: str = URL to make request to
|
||||||
|
method: str = {"GET", "POST", "PUT", "DELETE"} from `JavaScript` global fetch())
|
||||||
|
body: str = body as json string. Example, body=json.dumps(my_dict)
|
||||||
|
headers: dict[str, str] = header as dict, will be converted to string...
|
||||||
|
Example, headers=json.dumps({"Content-Type": "application/json"})
|
||||||
|
fetch_kwargs: Any = any other keyword arguments to pass to `pyfetch` (will be passed to `fetch`)
|
||||||
|
Return:
|
||||||
|
response: pyodide.http.FetchResponse = use with .status or await.json(), etc.
|
||||||
|
"""
|
||||||
|
kwargs = {"method": method, "mode": "cors"} # CORS: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
|
||||||
|
if body and method not in ["GET", "HEAD"]:
|
||||||
|
kwargs["body"] = body
|
||||||
|
if headers:
|
||||||
|
kwargs["headers"] = headers
|
||||||
|
kwargs.update(fetch_kwargs)
|
||||||
|
|
||||||
|
response = await pyfetch(url, **kwargs)
|
||||||
|
return response
|
||||||
|
```
|
||||||
|
This function is a wrapper for `pyfetch`, which is a wrapper for the `fetch` API. It is a coroutine function,
|
||||||
|
so it must be awaited. It also has type hints, which are not required, but are useful for IDEs and other tools.
|
||||||
|
The basic idea is that the `PyScript` will import and call this function, then await the response. Therefore,
|
||||||
|
the script containing this function must be importable by `PyScript`.
|
||||||
|
|
||||||
|
For this example, we will name the file containing the Python code `request.py` and place it in the same directory as the file
|
||||||
|
containing the html code, which is described below.
|
||||||
|
|
||||||
|
## `PyScript` HTML code
|
||||||
|
|
||||||
|
In this How-to, the HTML code is split into separate code blocks to enable context highlighting (coloring of the Python
|
||||||
|
code inside the html code block), but in reality it is all in the same file. The first part is a bare bones `PyScript`
|
||||||
|
html page, using the [community examples](https://github.com/pyscript/pyscript-collective/) set-up. The second part is
|
||||||
|
the actual Python code for HTTP requests, which is wrapped in `<py-script>` tags, while the third block has the
|
||||||
|
concluding html code.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
|
||||||
|
<title>GET, POST, PUT, DELETE example</title>
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
|
<link rel="stylesheet" href="../build/pyscript.css" />
|
||||||
|
|
||||||
|
<script defer src="../build/pyscript.js"></script>
|
||||||
|
<py-env>
|
||||||
|
- paths:
|
||||||
|
- /request.py
|
||||||
|
</py-env>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body><p>
|
||||||
|
Hello world request example! <br>
|
||||||
|
Here is the output of your request:
|
||||||
|
</p>
|
||||||
|
<py-script>
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
import asyncio # important!!
|
||||||
|
import json
|
||||||
|
from request import request # import our request function.
|
||||||
|
|
||||||
|
baseurl = "https://jsonplaceholder.typicode.com/"
|
||||||
|
|
||||||
|
# GET
|
||||||
|
headers = {"Content-type": "application/json"}
|
||||||
|
response = await request(baseurl+"posts/2", method="GET", headers=headers)
|
||||||
|
print(f"GET request=> status:{response.status}, json:{await response.json()}")
|
||||||
|
|
||||||
|
# POST
|
||||||
|
body = json.dumps({"title": "test_title", "body": "test body", "userId": 1})
|
||||||
|
new_post = await request(baseurl+"posts", body=body, method="POST", headers=headers)
|
||||||
|
print(f"POST request=> status:{new_post.status}, json:{await new_post.json()}")
|
||||||
|
|
||||||
|
# PUT
|
||||||
|
body = json.dumps({"id": 1, "title": "test_title", "body": "test body", "userId": 2})
|
||||||
|
new_post = await request(baseurl+"posts/1", body=body, method="PUT", headers=headers)
|
||||||
|
print(f"PUT request=> status:{new_post.status}, json:{await new_post.json()}")
|
||||||
|
|
||||||
|
# DELETE
|
||||||
|
new_post = await request(baseurl+"posts/1", method="DELETE", headers=headers)
|
||||||
|
print(f"DELETE request=> status:{new_post.status}, json:{await new_post.json()}")
|
||||||
|
```
|
||||||
|
```html
|
||||||
|
</py-script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You can also use other methods. See fetch documentation: <br>
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
See pyodide documentation for what to do with a FetchResponse object: <br>
|
||||||
|
https://pyodide.org/en/stable/usage/api/python-api.html#pyodide.http.FetchResponse
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
### `py-env` tag for importing our Python code
|
||||||
|
The very first thing to notice is the `py-env` tag. This tag is used to import Python files into the `PyScript`.
|
||||||
|
In this case, we are importing the `request.py` file, which contains the `request` function we wrote above.
|
||||||
|
|
||||||
|
### `py-script` tag for making async HTTP requests.
|
||||||
|
Next, the `py-script` tag contains the actual Python code where we import `asyncio` and `json`,
|
||||||
|
which are required or helpful for the `request` function.
|
||||||
|
The `# GET`, `# POST`, `# PUT`, `# DELETE` blocks show examples of how to use the `request` function to make basic
|
||||||
|
HTTP requests. The `await` keyword is required not only for the `request` function, but also for certain methods of the
|
||||||
|
`FetchResponse` object, such as `json()`, meaning that the code is asynchronous and slower requests will not block the
|
||||||
|
faster ones.
|
||||||
|
|
||||||
|
### HTTP Requests
|
||||||
|
HTTP requests are a very common way to communicate with a server. They are used for everything from getting data from
|
||||||
|
a database, to sending emails, to authorization, and more. Due to safety concerns, files loaded from the
|
||||||
|
local file system are not accessible by `PyScript`. Therefore, the proper way to load data into `PyScript` is also
|
||||||
|
through HTTP requests.
|
||||||
|
|
||||||
|
In our example, we show how to pass in a request `body`, `headers`, and specify the request `method`, in order to make
|
||||||
|
`GET`, `POST`, `PUT`, and `DELETE` requests, although methods such as `PATCH` are also available. Additional
|
||||||
|
parameters for the `fetch` API are also available, which can be specified as keyword arguments passed to our helper
|
||||||
|
function or to `pyfetch`. See the
|
||||||
|
[fetch documentation](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters) for more information.
|
||||||
|
HTTP requests are defined by standards-setting bodies in [RFC 1945](https://www.rfc-editor.org/info/rfc1945) and
|
||||||
|
[RFC 9110](https://www.rfc-editor.org/info/rfc9110).
|
||||||
|
|
||||||
|
# Conclusion
|
||||||
|
This tutorial demonstrates how to make HTTP requests using `pyfetch` and the `FetchResponse` objects. Importing Python
|
||||||
|
code/files into the `PyScript` using the `py-env` tag is also covered.
|
||||||
|
|
||||||
|
Although a simple example, the principals here can be used to create complex web applications inside of `PyScript`,
|
||||||
|
or load data into `PyScript` for use by an application, all served as a static HTML page, which is pretty amazing!
|
||||||
|
|
||||||
|
|
||||||
|
# API Quick Reference
|
||||||
|
## pyodide.http.pyfetch
|
||||||
|
### Usage
|
||||||
|
```python
|
||||||
|
await pyodide.http.pyfetch(url: str, **kwargs: Any) -> FetchResponse
|
||||||
|
```
|
||||||
|
Use `pyfetch` to make HTTP requests in `PyScript`. This is a wrapper around the `fetch` API. Returns a `FetchResponse`.
|
||||||
|
|
||||||
|
### [`pyfetch` Docs.](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.pyfetch)
|
||||||
|
|
||||||
|
## pyodide.http.FetchResponse
|
||||||
|
### Usage
|
||||||
|
```python
|
||||||
|
response: pyodide.http.FetchResponse = await <pyfetch call>
|
||||||
|
status = response.status
|
||||||
|
json = await response.json()
|
||||||
|
```
|
||||||
|
Class for handling HTTP responses. This is a wrapper around the `JavaScript` fetch `Response`. Contains common (async)
|
||||||
|
methods and properties for handling HTTP responses, such as `json()`, `url`, `status`, `headers`, etc.
|
||||||
|
|
||||||
|
### [`FetchResponse` Docs.](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.FetchResponse)
|
||||||
@@ -14,5 +14,6 @@ maxdepth: 2
|
|||||||
glob:
|
glob:
|
||||||
caption: 'Contents:'
|
caption: 'Contents:'
|
||||||
---
|
---
|
||||||
*
|
passing-objects
|
||||||
|
http-requests
|
||||||
```
|
```
|
||||||
|
|||||||
140
docs/howtos/passing-objects.md
Normal file
140
docs/howtos/passing-objects.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# How to Pass Objects from PyScript to Javascript (and Vice Versa)
|
||||||
|
|
||||||
|
[Pyodide](https://pyodide.org), the runtime that underlies PyScript, does a lot of work under the hood to translate objects between Python and JavaScript. This allows code in one language to access objects defined in the other.
|
||||||
|
|
||||||
|
This guide discusses how to pass objects between JavaScript and Python within PyScript. For more details on how Pyodide handles translating and proxying objects between the two languages, see the [Pyodide Type Translations Page](https://pyodide.org/en/stable/usage/type-conversions.html).
|
||||||
|
|
||||||
|
For our purposes, an 'object' is anything that can be bound to a variable (a number, string, object, [function](https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function), etc).
|
||||||
|
|
||||||
|
## JavaScript to PyScript
|
||||||
|
|
||||||
|
We can use the syntax `from js import ...` to import JavaScript objects directly into PyScript. Simple JavaScript objects are converted to equivalent Python types; these are called [implicit conversions](https://pyodide.org/en/stable/usage/type-conversions.html#implicit-conversions). More complicated objects are wrapped in [JSProxy](https://pyodide.org/en/stable/usage/type-conversions.html) objects to make them behave like Python objects.
|
||||||
|
|
||||||
|
`import js` and `from js import ...` [in Pyodide](https://pyodide.org/en/stable/usage/type-conversions.html#type-translations-using-js-obj-from-py) get objects from the [JavaScript globalThis scope](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis), so keep the[ rules of JavaScript variable scoping](https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/) in mind.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
name = "Guido" //A JS variable
|
||||||
|
|
||||||
|
// Define a JS Function
|
||||||
|
function addTwoNumbers(x, y){
|
||||||
|
return x + y;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
<py-script>
|
||||||
|
# Import and use JS function and variable into Python
|
||||||
|
from js import name, addTwoNumbers
|
||||||
|
|
||||||
|
print(f"Hello {name}")
|
||||||
|
print("Adding 1 and 2 in Javascript: " + str(addTwoNumbers(1, 2)))
|
||||||
|
</py-script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## PyScript to JavaScript
|
||||||
|
|
||||||
|
Since [PyScript doesn't export its instance of Pyodide](https://github.com/pyscript/pyscript/issues/494) and only one instance of Pyodide can be running in a browser window at a time, there isn't currently a way for Javascript to access Objects defined inside PyScript tags "directly".
|
||||||
|
|
||||||
|
We can work around this limitation using [JavaScript's eval() function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval), which executes a string as code much like [Python's eval()](https://docs.python.org/3/library/functions.html#eval). First, we create a JS function `createObject` which takes an object and a string, then uses `eval()` to create a variable named after the string and bind it to that object. By calling this function from PyScript (where we have access to the Pyodide global namespace), we can bind JavaScript variables to Python objects without having direct access to that global namespace.
|
||||||
|
|
||||||
|
Include the following script tag anywhere in your html document:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
function createObject(object, variableName){
|
||||||
|
//Bind a variable whose name is the string variableName
|
||||||
|
// to the object called 'object'
|
||||||
|
let execString = variableName + " = object"
|
||||||
|
console.log("Running '" + execString + "'");
|
||||||
|
eval(execString)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
This function takes a Python Object and creates a variable pointing to it in the JavaScript global scope.
|
||||||
|
|
||||||
|
### Exporting all Global Python Objects
|
||||||
|
|
||||||
|
We can use our new `createObject` function to "export" the entire Python global object dictionary as a JavaScript object:
|
||||||
|
|
||||||
|
```python
|
||||||
|
<py-script>
|
||||||
|
from js import createObject
|
||||||
|
from pyodide.ffi import create_proxy
|
||||||
|
createObject(create_proxy(globals()), "pyodideGlobals")
|
||||||
|
</py-script>
|
||||||
|
```
|
||||||
|
This will make all Python global variables available in JavaScript with `pyodideGlobals.get('my_variable_name')`.
|
||||||
|
|
||||||
|
(Since PyScript tags evaluate _after_ all JavaScript on the page, we can't just dump a `console.log(...)` into a `<script>` tag, since that tag will evaluate before any PyScript has a chance to. We need to delay accessing the Python variable in JavaScript until after the Python code has a chance to run. The following example uses a button with `id="do-math"` to achieve this, but any method would be valid.)
|
||||||
|
|
||||||
|
```python
|
||||||
|
<py-script>
|
||||||
|
# create some Python objects:
|
||||||
|
symbols = {'pi': 3.1415926, 'e': 2.7182818}
|
||||||
|
|
||||||
|
def rough_exponential(x):
|
||||||
|
return symbols['e']**x
|
||||||
|
|
||||||
|
class Circle():
|
||||||
|
def __init__(self, radius):
|
||||||
|
self.radius = radius
|
||||||
|
|
||||||
|
@property
|
||||||
|
def area:
|
||||||
|
return symbols['pi'] * self.radius**2
|
||||||
|
</py-script>
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<input type="button" value="Log Python Variables" id="do-mmath">
|
||||||
|
<script>
|
||||||
|
document.getElementById("do-math").addEventListener("click", () => {
|
||||||
|
const exp = pyodideGlobals.get('rough_exponential');
|
||||||
|
console.log("e squared is about ${exp(2)}");
|
||||||
|
const c = pyodideGlobals.get('Circle')(4);
|
||||||
|
console.log("The area of c is ${c.area}");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Exporting Individual Python Objects
|
||||||
|
|
||||||
|
We can also export individual Python objects to the JavaScript global scope if we wish.
|
||||||
|
|
||||||
|
(As above, the following example uses a button to delay the execution of the `<script>` until after the PyScript has run.)
|
||||||
|
|
||||||
|
```python
|
||||||
|
<py-script>
|
||||||
|
import js
|
||||||
|
from pyodide.ffi import create_proxy
|
||||||
|
|
||||||
|
# Create 3 python objects
|
||||||
|
language = "Python 3"
|
||||||
|
animals = ['dog', 'cat', 'bird']
|
||||||
|
multiply3 = lambda a, b, c: a * b * c
|
||||||
|
|
||||||
|
# js object can be named the same as Python objects...
|
||||||
|
js.createObject(language, "language")
|
||||||
|
|
||||||
|
# ...but don't have to be
|
||||||
|
js.createObject(create_proxy(animals), "animals_from_py")
|
||||||
|
|
||||||
|
# functions are objects too, in both Python and Javascript
|
||||||
|
js.createObject(create_proxy(multiply3), "multiply")
|
||||||
|
</py-script>
|
||||||
|
```
|
||||||
|
```html
|
||||||
|
<input type="button" value="Log Python Variables" id="log-python-variables">
|
||||||
|
<script>
|
||||||
|
document.getElementById("log-python-variables").addEventListener("click", () => {
|
||||||
|
console.log(`Nice job using ${language}`);
|
||||||
|
for (const animal of animals_from_py){
|
||||||
|
console.log(`Do you like ${animal}s? `);
|
||||||
|
}
|
||||||
|
console.log(`2 times 3 times 4 is ${multiply(2,3,4)}`);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
BIN
docs/img/diataxis.png
Normal file
BIN
docs/img/diataxis.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
@@ -24,7 +24,10 @@ Check out our [getting started guide](tutorials/getting-started.md)!
|
|||||||
:::
|
:::
|
||||||
:::{grid-item-card} [How-to guides](howtos/index.md)
|
:::{grid-item-card} [How-to guides](howtos/index.md)
|
||||||
|
|
||||||
**Coming soon!**
|
You already know the basics and want to learn specifics!
|
||||||
|
|
||||||
|
[Passing Objects between JavaScript and Python](howtos/passing-objects.md)
|
||||||
|
[Making async HTTP requests in pure Python](howtos/http-requests.md)
|
||||||
|
|
||||||
:::
|
:::
|
||||||
:::{grid-item-card} [Concepts](concepts/index.md)
|
:::{grid-item-card} [Concepts](concepts/index.md)
|
||||||
|
|||||||
@@ -5,16 +5,50 @@ This page will guide you through getting started with PyScript.
|
|||||||
## Development setup
|
## Development setup
|
||||||
|
|
||||||
PyScript does not require any development environment other
|
PyScript does not require any development environment other
|
||||||
than a web browser. We recommend using [Chrome](https://www.google.com/chrome/).
|
than a web browser (we recommend using [Chrome](https://www.google.com/chrome/)) and a text editor, even though using your [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) of choice might be convenient.
|
||||||
|
|
||||||
If you're using [VSCode](https://code.visualstudio.com/), the
|
If you're using [VSCode](https://code.visualstudio.com/), the
|
||||||
[Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
|
[Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
|
||||||
can be used to reload the page as you edit the HTML file.
|
can be used to reload the page as you edit the HTML file.
|
||||||
|
|
||||||
|
## Trying before installing
|
||||||
|
|
||||||
|
If you're new to programming and know nothing about HTML or just want to try some of PyScript features, we recommend using the [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) element in the [PyScript REPL example](https://pyscript.net/examples/repl.html) instead so you can have a programming experience in a REPL that doesn't require any setup. This REPL can be used to have an interactive experience using Python directly.
|
||||||
|
|
||||||
|
Alternatively, you can also use an online editor like W3School's [TryIt Editor](https://www.w3schools.com/html/tryit.asp?filename=tryhtml_default_default) and just plug the code below into it, as shown in the [example](https://docs.pyscript.net/latest/concepts/what-is-pyscript.html#example) page and click the run button.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
|
||||||
|
<title>REPL</title>
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
Hello world! <br>
|
||||||
|
This is the current date and time, as computed by Python:
|
||||||
|
<py-script>
|
||||||
|
from datetime import datetime
|
||||||
|
now = datetime.now()
|
||||||
|
now.strftime("%m/%d/%Y, %H:%M:%S")
|
||||||
|
</py-script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
You could try changing the code above to explore and play with pyscript yourself.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
There is no installation required. In this document, we'll use
|
There is no installation required. In this document, we'll use
|
||||||
the PyScript assets served on https://pyscript.net.
|
the PyScript assets served on [https://pyscript.net](https://pyscript.net).
|
||||||
|
|
||||||
If you want to download the source and build it yourself, follow
|
If you want to download the source and build it yourself, follow
|
||||||
the instructions in the [README.md](https://github.com/pyscript/pyscript/blob/main/README.md) file.
|
the instructions in the [README.md](https://github.com/pyscript/pyscript/blob/main/README.md) file.
|
||||||
@@ -31,8 +65,8 @@ open an HTML by double-clicking it in your file explorer.
|
|||||||
```html
|
```html
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body> <py-script> print('Hello, World!') </py-script> </body>
|
<body> <py-script> print('Hello, World!') </py-script> </body>
|
||||||
</html>
|
</html>
|
||||||
@@ -50,8 +84,8 @@ print back onto the page. For example, we can compute π.
|
|||||||
```html
|
```html
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<py-script>
|
<py-script>
|
||||||
@@ -83,8 +117,8 @@ the `<py-script>` tag to write to.
|
|||||||
```html
|
```html
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -109,7 +143,243 @@ the `<py-script>` tag to write to.
|
|||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
## The py-env tag
|
## The py-config tag
|
||||||
|
|
||||||
|
Use the `<py-config>` tag to set and configure general metadata along with declaring dependencies for your PyScript application. The configuration has to be set in either TOML or JSON format. If you are unfamiliar with JSON, consider reading [freecodecamp's JSON for beginners](https://www.freecodecamp.org/news/what-is-json-a-json-file-example/) guide for more information. And for TOML, consider reading about it [here](https://learnxinyminutes.com/docs/toml/).
|
||||||
|
|
||||||
|
The ideal place to use `<py-config>` in between the `<head>...</head>` tags.
|
||||||
|
|
||||||
|
The `<py-config>` tag can be used as follows:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<py-config>
|
||||||
|
autoclose_loader = true
|
||||||
|
|
||||||
|
[[runtimes]]
|
||||||
|
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
|
||||||
|
name = "pyodide-0.21.2"
|
||||||
|
lang = "python"
|
||||||
|
</py-config>
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, a JSON config can be passed using the `type` attribute.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<py-config type="json">
|
||||||
|
{
|
||||||
|
"autoclose_loader": true,
|
||||||
|
"runtimes": [{
|
||||||
|
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
|
||||||
|
"name": "pyodide-0.21.2",
|
||||||
|
"lang": "python"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
</py-config>
|
||||||
|
```
|
||||||
|
|
||||||
|
Besides passing the config as inline (as shown above), one can also pass it with the `src` attribute. This is demonstrated below:
|
||||||
|
|
||||||
|
```
|
||||||
|
<py-config src="./custom.toml"></py-config>
|
||||||
|
```
|
||||||
|
|
||||||
|
where `custom.toml` contains
|
||||||
|
|
||||||
|
```
|
||||||
|
autoclose_loader = true
|
||||||
|
[[runtimes]]
|
||||||
|
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
|
||||||
|
name = "pyodide-0.21.2"
|
||||||
|
lang = "python"
|
||||||
|
```
|
||||||
|
|
||||||
|
This can also be done via JSON using the `type` attribute. By default, `type` is set to `"toml"` if not supplied.
|
||||||
|
|
||||||
|
```
|
||||||
|
<py-config type="json" src="./custom.json"></py-config>
|
||||||
|
```
|
||||||
|
|
||||||
|
where `custom.json` contains
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"autoclose_loader": true,
|
||||||
|
"runtimes": [{
|
||||||
|
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
|
||||||
|
"name": "pyodide-0.21.2",
|
||||||
|
"lang": "python"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
One can also use both i.e pass the config from `src` attribute as well as specify it as `inline`. So the following snippet is also valid:
|
||||||
|
|
||||||
|
```
|
||||||
|
<py-config src="./custom.toml">
|
||||||
|
paths = ["./utils.py"]
|
||||||
|
</py-config>
|
||||||
|
```
|
||||||
|
|
||||||
|
This can also be done via JSON using the `type` attribute.
|
||||||
|
|
||||||
|
```
|
||||||
|
<py-config type="json" src="./custom.json">
|
||||||
|
{
|
||||||
|
"paths": ["./utils.py"]
|
||||||
|
}
|
||||||
|
</py-config>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: While the `<py-config>` tag supports both TOML and JSON, one cannot mix the type of config passed from 2 different sources i.e. the case when inline config is in TOML format while config from src is in JSON format is NOT allowed. Similarly for the opposite case.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This is helpful in cases where a number of applications share a common configuration (which can be supplied via `src`), but their specific keys need to be customised and overridden.
|
||||||
|
|
||||||
|
The keys supplied through `inline` override the values present in config supplied via `src`.
|
||||||
|
|
||||||
|
One can also declare dependencies so as to get access to many 3rd party OSS packages that are supported by PyScript.
|
||||||
|
You can also link to `.whl` files directly on disk like in our [toga example](https://github.com/pyscript/pyscript/blob/main/examples/toga/freedom.html).
|
||||||
|
|
||||||
|
```
|
||||||
|
<py-config>
|
||||||
|
packages = ["./static/wheels/travertino-0.1.3-py3-none-any.whl"]
|
||||||
|
</py-config>
|
||||||
|
```
|
||||||
|
|
||||||
|
OR in JSON like
|
||||||
|
|
||||||
|
```
|
||||||
|
<py-config type="json">
|
||||||
|
{
|
||||||
|
"packages": ["./static/wheels/travertino-0.1.3-py3-none-any.whl"]
|
||||||
|
}
|
||||||
|
</py-config>
|
||||||
|
```
|
||||||
|
|
||||||
|
If your `.whl` is not a pure Python wheel, then open a PR or issue with [pyodide](https://github.com/pyodide/pyodide) to get it added [here](https://github.com/pyodide/pyodide/tree/main/packages).
|
||||||
|
If there's enough popular demand, the pyodide team will likely work on supporting your package. Regardless, things will likely move faster if you make the PR and consult with the team to get unblocked.
|
||||||
|
For example, NumPy and Matplotlib are available. Notice here we're using `<py-script output="plot">`
|
||||||
|
as a shortcut, which takes the expression on the last line of the script and runs `pyscript.write('plot', fig)`.
|
||||||
|
```html
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
<py-config type="json">
|
||||||
|
{
|
||||||
|
"packages": ["numpy", "matplotlib"]
|
||||||
|
}
|
||||||
|
</py-config>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Let's plot random numbers</h1>
|
||||||
|
<div id="plot"></div>
|
||||||
|
<py-script output="plot">
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
x = np.random.randn(1000)
|
||||||
|
y = np.random.randn(1000)
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.scatter(x, y)
|
||||||
|
fig
|
||||||
|
</py-script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Local modules
|
||||||
|
In addition to packages, you can declare local Python modules that will
|
||||||
|
be imported in the `<py-script>` tag. For example, we can place the random
|
||||||
|
number generation steps in a function in the file `data.py`.
|
||||||
|
```python
|
||||||
|
# data.py
|
||||||
|
import numpy as np
|
||||||
|
def make_x_and_y(n):
|
||||||
|
x = np.random.randn(n)
|
||||||
|
y = np.random.randn(n)
|
||||||
|
return x, y
|
||||||
|
```
|
||||||
|
|
||||||
|
In the HTML tag `<py-config>`, paths to local modules are provided in the
|
||||||
|
`paths:` key.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
<py-config type="toml">
|
||||||
|
packages = ["numpy", "matplotlib"]
|
||||||
|
paths = ["./data.py"]
|
||||||
|
</py-config>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Let's plot random numbers</h1>
|
||||||
|
<div id="plot"></div>
|
||||||
|
<py-script output="plot">
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from data import make_x_and_y
|
||||||
|
x, y = make_x_and_y(n=1000)
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.scatter(x, y)
|
||||||
|
fig
|
||||||
|
</py-script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
The following optional values are supported by `<py-config>`:
|
||||||
|
| Value | Type | Description |
|
||||||
|
| ------ | ---- | ----------- |
|
||||||
|
| `name` | string | Name of the user application. This field can be any string and is to be used by the application author for their own customization purposes. |
|
||||||
|
| `description` | string | Description of the user application. This field can be any string and is to be used by the application author for their own customization purposes. |
|
||||||
|
| `version` | string | Version of the user application. This field can be any string and is to be used by the application author for their own customization purposes. It is not related to the PyScript version. |
|
||||||
|
| `schema_version` | number | The version of the config schema which determines what all keys are supported. This can be supplied by the user so PyScript knows what to expect in the config. If not supplied, the latest version for the schema is automatically used. |
|
||||||
|
| `type` | string | Type of the project. The default is an "app" i.e. a user application |
|
||||||
|
| `author_name` | string | Name of the author. |
|
||||||
|
| `author_email` | string | Email of the author. |
|
||||||
|
| `license` | string | License to be used for the user application. |
|
||||||
|
| `autoclose_loader` | boolean | If false, PyScript will not close the loading splash screen when the startup operations finish. |
|
||||||
|
| `packages` | List of Packages | Dependencies on 3rd party OSS packages are specified here. The default value is an empty list. |
|
||||||
|
| `paths` | List of Paths | Local Python modules are to be specified here. The default value is an empty list. |
|
||||||
|
| `plugins` | List of Plugins | List of Plugins are to be specified here. The default value is an empty list. |
|
||||||
|
| `runtimes` | List of Runtimes | List of runtime configurations, described below. The default value contains a single Pyodide based runtime. |
|
||||||
|
|
||||||
|
A runtime configuration consists of the following:
|
||||||
|
| Value | Type | Description |
|
||||||
|
| ----- | ---- | ----------- |
|
||||||
|
| `src` | string (Required) | URL to the runtime source. |
|
||||||
|
| `name` | string | Name of the runtime. This field can be any string and is to be used by the application author for their own customization purposes |
|
||||||
|
| `lang` | string | Programming language supported by the runtime. This field can be used by the application author to provide clarification. It currently has no implications on how PyScript behaves. |
|
||||||
|
|
||||||
|
Besides the above format, a user can also supply any extra keys and values that are relevant as metadata information or perhaps are being used within the application.
|
||||||
|
|
||||||
|
For example, a valid config could also be with the snippet below:
|
||||||
|
|
||||||
|
```
|
||||||
|
<py-config type="toml">
|
||||||
|
magic = "unicorn"
|
||||||
|
</py-config>
|
||||||
|
```
|
||||||
|
|
||||||
|
OR in JSON like
|
||||||
|
|
||||||
|
```
|
||||||
|
<py-config type="json">
|
||||||
|
{
|
||||||
|
"magic": "unicorn"
|
||||||
|
}
|
||||||
|
</py-config>
|
||||||
|
```
|
||||||
|
|
||||||
|
If this `"magic"` key is present in config supplied via `src` and also present in config supplied via `inline`, then the value in the inline config is given priority i.e. the overriding process also works for custom keys.
|
||||||
|
|
||||||
|
## The py-env tag (Deprecated)
|
||||||
|
|
||||||
|
**The <py-env> tag is deprecated as of `2022.09.1` release but you can still use the functionality explained below. It will be removed in the next release. To specify packages in the recommended way, please see the <py-config> section.**
|
||||||
|
|
||||||
In addition to the [Python Standard Library](https://docs.python.org/3/library/) and
|
In addition to the [Python Standard Library](https://docs.python.org/3/library/) and
|
||||||
the `pyscript` module, many 3rd-party OSS packages will work out-of-the-box with PyScript.
|
the `pyscript` module, many 3rd-party OSS packages will work out-of-the-box with PyScript.
|
||||||
@@ -132,8 +402,8 @@ as a shortcut, which takes the expression on the last line of the script and run
|
|||||||
```html
|
```html
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
<py-env>
|
<py-env>
|
||||||
- numpy
|
- numpy
|
||||||
- matplotlib
|
- matplotlib
|
||||||
@@ -158,17 +428,16 @@ as a shortcut, which takes the expression on the last line of the script and run
|
|||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Local modules
|
### Local modules with py-env (Deprecated)
|
||||||
|
|
||||||
|
**The <py-env> tag is deprecated as of `2022.09.1` release but you can still use the functionality explained below. It will be removed in the next release. To specify local modules in the recommended way, please see the <py-config> section.**
|
||||||
|
|
||||||
In addition to packages, you can declare local Python modules that will
|
In addition to packages, you can declare local Python modules that will
|
||||||
be imported in the `<py-script>` tag. For example, we can place the random
|
be imported in the `<py-script>` tag. For example, we can place the random
|
||||||
number generation steps in a function in the file `data.py`.
|
number generation steps in a function in the file `data.py`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# data.py
|
# data.py
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
def make_x_and_y(n):
|
def make_x_and_y(n):
|
||||||
x = np.random.randn(n)
|
x = np.random.randn(n)
|
||||||
y = np.random.randn(n)
|
y = np.random.randn(n)
|
||||||
@@ -181,8 +450,8 @@ In the HTML tag `<py-env>`, paths to local modules are provided in the
|
|||||||
```html
|
```html
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
<py-env>
|
<py-env>
|
||||||
- numpy
|
- numpy
|
||||||
- matplotlib
|
- matplotlib
|
||||||
@@ -197,9 +466,7 @@ In the HTML tag `<py-env>`, paths to local modules are provided in the
|
|||||||
<py-script output="plot">
|
<py-script output="plot">
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from data import make_x_and_y
|
from data import make_x_and_y
|
||||||
|
|
||||||
x, y = make_x_and_y(n=1000)
|
x, y = make_x_and_y(n=1000)
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.scatter(x, y)
|
ax.scatter(x, y)
|
||||||
fig
|
fig
|
||||||
@@ -207,50 +474,20 @@ In the HTML tag `<py-env>`, paths to local modules are provided in the
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
## The py-repl tag
|
## The py-repl tag
|
||||||
|
|
||||||
The `<py-repl>` tag creates a REPL component that is rendered to the page as a code editor, allowing you to write executable code inline.
|
The `<py-repl>` tag creates a REPL component that is rendered to the page as a code editor, allowing you to write executable code inline.
|
||||||
```html
|
```html
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<py-repl></py-repl>
|
<py-repl></py-repl>
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
## The py-config tag
|
|
||||||
|
|
||||||
Use the `<py-config>` tag to set and configure general metadata about your PyScript application in YAML format. If you are unfamiliar with YAML, consider reading [Red Hat's YAML for beginners](https://www.redhat.com/sysadmin/yaml-beginners) guide for more information.
|
|
||||||
|
|
||||||
The `<py-config>` tag can be used as follows:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<py-config>
|
|
||||||
autoclose_loader: false
|
|
||||||
runtimes:
|
|
||||||
- src: "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"
|
|
||||||
name: pyodide-0.20
|
|
||||||
lang: python
|
|
||||||
</py-config>
|
|
||||||
```
|
|
||||||
|
|
||||||
The following optional values are supported by `<py-config>`:
|
|
||||||
| Value | Type | Description |
|
|
||||||
| ------ | ---- | ----------- |
|
|
||||||
| `autoclose_loader` | boolean | If false, PyScript will not close the loading splash screen when the startup operations finish. |
|
|
||||||
| `name` | string | Name of the user application. This field can be any string and is to be used by the application author for their own customization purposes. |
|
|
||||||
| `version` | string | Version of the user application. This field can be any string and is to be used by the application author for their own customization purposes. It is not related to the PyScript version. |
|
|
||||||
| `runtimes` | List of Runtimes | List of runtime configurations, described below.
|
|
||||||
|
|
||||||
A runtime configuration consists of the following:
|
|
||||||
| Value | Type | Description |
|
|
||||||
| ----- | ---- | ----------- |
|
|
||||||
| `src` | string (Required) | URL to the runtime source. |
|
|
||||||
| `name` | string | Name of the runtime. This field can be any string and is to be used by the application author for their own customization purposes |
|
|
||||||
| `lang` | string | Programming language supported by the runtime. This field can be used by the application author to provide clarification. It currently has no implications on how PyScript behaves. |
|
|
||||||
|
|
||||||
## Visual component tags
|
## Visual component tags
|
||||||
|
|
||||||
The following tags can be used to add visual attributes to your HTML page.
|
The following tags can be used to add visual attributes to your HTML page.
|
||||||
|
|||||||
@@ -3,13 +3,15 @@
|
|||||||
<title>Altair</title>
|
<title>Altair</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
<py-env>
|
<py-config>
|
||||||
- altair
|
packages = [
|
||||||
- pandas
|
"altair",
|
||||||
- vega_datasets
|
"pandas",
|
||||||
</py-env>
|
"vega_datasets"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="altair" style="width: 100%; height: 100%"></div>
|
<div id="altair" style="width: 100%; height: 100%"></div>
|
||||||
|
|||||||
@@ -3,13 +3,14 @@
|
|||||||
<title>Antigravity</title>
|
<title>Antigravity</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<py-env>
|
<py-config>
|
||||||
- paths:
|
paths = [
|
||||||
- ./antigravity.py
|
"./antigravity.py"
|
||||||
</py-env>
|
]
|
||||||
|
</py-config>
|
||||||
<body>
|
<body>
|
||||||
<b>Based on xkcd: antigravity https://xkcd.com/353/.</b>
|
<b>Based on xkcd: antigravity https://xkcd.com/353/.</b>
|
||||||
<py-script>
|
<py-script>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import random
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from js import DOMParser, document, setInterval
|
from js import DOMParser, document, setInterval
|
||||||
from pyodide import create_proxy
|
from pyodide.ffi import create_proxy
|
||||||
from pyodide.http import open_url
|
from pyodide.http import open_url
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
26
examples/await/await0.html
Normal file
26
examples/await/await0.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>Async Await BLOCKING LOOP Pyscript Twice</title>
|
||||||
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<py-script>
|
||||||
|
import js
|
||||||
|
import asyncio
|
||||||
|
for i in range(3):
|
||||||
|
js.console.log('A', i)
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
</py-script>
|
||||||
|
<py-script>
|
||||||
|
import js
|
||||||
|
import asyncio
|
||||||
|
for i in range(3):
|
||||||
|
js.console.log('B', i)
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
</py-script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
40
examples/await/await1.html
Normal file
40
examples/await/await1.html
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>Async Await BLOCKING LOOP Pyscript Twice</title>
|
||||||
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
Pyscript - FIRST ASYNC WITH INVOKED LOOP BLOCKING AWAIT AT SAME LEVEL AS LOOP Pyscript writing to console.log:
|
||||||
|
<py-script>
|
||||||
|
import js
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def asyncCallLoop1():
|
||||||
|
for i in range(3):
|
||||||
|
js.console.log('A', i)
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
asyncCallLoop1()
|
||||||
|
</py-script>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Pyscript - SECOND ASYNC WITH INVOKED LOOP BLOCKING AWAIT AT SAME LEVEL AS LOOP Pyscript writing to console.log:
|
||||||
|
<py-script>
|
||||||
|
import js
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def asyncCallLoop2():
|
||||||
|
for i in range(3):
|
||||||
|
js.console.log('B', i)
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
asyncCallLoop2()
|
||||||
|
</py-script>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
37
examples/await/await2.html
Normal file
37
examples/await/await2.html
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>Async Await BLOCKING LOOP Pyscript Twice</title>
|
||||||
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
Pyscript - FIRST ASYNC WITH INVOKED LOOP BLOCKING AWAIT AT SAME LEVEL AS LOOP Pyscript writing to console.log:
|
||||||
|
<py-script>
|
||||||
|
import js
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def asyncCallLoop1():
|
||||||
|
for i in range(3):
|
||||||
|
js.console.log('A', i)
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
asyncCallLoop1()
|
||||||
|
</py-script>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Pyscript - SECOND ASYNC WITH TOP-LEVEL LOOP BLOCKING AWAIT AT SAME LEVEL AS LOOP Pyscript writing to console.log:
|
||||||
|
<py-script>
|
||||||
|
import js
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
js.console.log('B', i)
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
</py-script>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
40
examples/await/await3.html
Normal file
40
examples/await/await3.html
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>Async Await NON-BLOCKING Pyscript Twice</title>
|
||||||
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
Pyscript - FIRST ASYNC WITH NON-BLOCKING AWAIT AT ONE LEVEL LOWER THAN LOOP Pyscript writing to console.log:
|
||||||
|
<py-script>
|
||||||
|
import js
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def asyncCall1():
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
js.console.log('A', i)
|
||||||
|
asyncCall1()
|
||||||
|
</py-script>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Pyscript - SECOND ASYNC WITH NON-BLOCKING AWAIT AT ONE LEVEL LOWER THAN LOOP Pyscript writing to console.log:
|
||||||
|
<py-script>
|
||||||
|
import js
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def asyncCall2():
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
js.console.log('B', i)
|
||||||
|
asyncCall2()
|
||||||
|
</py-script>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
34
examples/await/await4.html
Normal file
34
examples/await/await4.html
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>Async Await BLOCKING LOOP Pyscript Twice</title>
|
||||||
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
Pyscript - FIRST ASYNC WITH TOP-LEVEL LOOP BLOCKING AWAIT AT SAME LEVEL AS LOOP Pyscript writing to console.log:
|
||||||
|
<py-script>
|
||||||
|
import js
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
js.console.log('A', i)
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
</py-script>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Pyscript - SECOND ASYNC WITH TOP-LEVEL LOOP BLOCKING AWAIT AT SAME LEVEL AS LOOP Pyscript writing to console.log:
|
||||||
|
<py-script>
|
||||||
|
import js
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
js.console.log('B', i)
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
</py-script>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
examples/await/await5.html
Normal file
19
examples/await/await5.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>Async Await BLOCKING LOOP Pyscript Twice</title>
|
||||||
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<py-script>
|
||||||
|
import asyncio
|
||||||
|
from itertools import count
|
||||||
|
for i in count():
|
||||||
|
print(f"Count: {i}")
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
</py-script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -11,16 +11,18 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
Bokeh.set_log_level("info");
|
Bokeh.set_log_level("info");
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<py-env>
|
<py-config>
|
||||||
- bokeh
|
packages = [
|
||||||
- numpy
|
"bokeh",
|
||||||
</py-env>
|
"numpy"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
<h1>Bokeh Example</h1>
|
<h1>Bokeh Example</h1>
|
||||||
<div id="myplot"></div>
|
<div id="myplot"></div>
|
||||||
|
|
||||||
|
|||||||
@@ -11,16 +11,18 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
Bokeh.set_log_level("info");
|
Bokeh.set_log_level("info");
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<py-env>
|
<py-config>
|
||||||
- bokeh
|
packages = [
|
||||||
- numpy
|
"bokeh",
|
||||||
</py-env>
|
"numpy"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
<h1>Bokeh Example</h1>
|
<h1>Bokeh Example</h1>
|
||||||
<div id="myplot"></div>
|
<div id="myplot"></div>
|
||||||
|
|
||||||
@@ -71,17 +73,17 @@ def _link_docs(pydoc, jsdoc):
|
|||||||
if getattr(event, 'setter_id', None) is not None:
|
if getattr(event, 'setter_id', None) is not None:
|
||||||
return
|
return
|
||||||
events = [event]
|
events = [event]
|
||||||
json_patch = jsdoc.create_json_patch_string(pyodide.to_js(events))
|
json_patch = jsdoc.create_json_patch_string(pyodide.ffi.to_js(events))
|
||||||
pydoc.apply_json_patch(json.loads(json_patch))
|
pydoc.apply_json_patch(json.loads(json_patch))
|
||||||
|
|
||||||
jsdoc.on_change(pyodide.create_proxy(jssync), pyodide.to_js(False))
|
jsdoc.on_change(pyodide.ffi.create_proxy(jssync), pyodide.ffi.to_js(False))
|
||||||
|
|
||||||
def pysync(event):
|
def pysync(event):
|
||||||
json_patch, buffers = process_document_events([event], use_buffers=True)
|
json_patch, buffers = process_document_events([event], use_buffers=True)
|
||||||
buffer_map = {}
|
buffer_map = {}
|
||||||
for (ref, buffer) in buffers:
|
for (ref, buffer) in buffers:
|
||||||
buffer_map[ref['id']] = buffer
|
buffer_map[ref['id']] = buffer
|
||||||
jsdoc.apply_json_patch(JSON.parse(json_patch), pyodide.to_js(buffer_map), setter_id='js')
|
jsdoc.apply_json_patch(JSON.parse(json_patch), pyodide.ffi.to_js(buffer_map), setter_id='js')
|
||||||
|
|
||||||
pydoc.on_change(pysync)
|
pydoc.on_change(pysync)
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.loading {
|
.loading {
|
||||||
@@ -48,13 +48,13 @@
|
|||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
"d3": "https://cdn.skypack.dev/d3@7"
|
"d3": "https://cdn.skypack.dev/pin/d3@v7.6.1-1Q0NZ0WZnbYeSjDusJT3/mode=imports,min/optimized/d3.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import * as d3 from "https://cdn.skypack.dev/d3@7";
|
import * as d3 from "https://cdn.skypack.dev/pin/d3@v7.6.1-1Q0NZ0WZnbYeSjDusJT3/mode=imports,min/optimized/d3.js";
|
||||||
|
|
||||||
const fruits = [
|
const fruits = [
|
||||||
{name: "🍊", count: 21},
|
{name: "🍊", count: 21},
|
||||||
@@ -109,7 +109,7 @@ for (const d of data) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<py-script>
|
<py-script>
|
||||||
from pyodide import create_proxy, to_js
|
from pyodide.ffi import create_proxy, to_js
|
||||||
import d3
|
import d3
|
||||||
|
|
||||||
fruits = [
|
fruits = [
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
<title>Folium</title>
|
<title>Folium</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
<py-env>
|
<py-config>
|
||||||
- folium
|
packages = [
|
||||||
- pandas
|
"folium",
|
||||||
</py-env>
|
"pandas"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="folium" style="width: 100%; height: 100%"></div>
|
<div id="folium" style="width: 100%; height: 100%"></div>
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
<title>Svelte app</title>
|
<title>Svelte app</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="../favicon.png" />
|
<link rel="icon" type="image/png" href="../favicon.png" />
|
||||||
<link rel="stylesheet" href="../https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
|
||||||
<script defer src="../https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -106,7 +106,7 @@ pyscript.run_until_complete(start())
|
|||||||
</py-script>
|
</py-script>
|
||||||
|
|
||||||
<div class="mb10">
|
<div class="mb10">
|
||||||
<button id="trackbutton" class="bx--btn bx--btn--secondary" type="button" pys-onClick="toggle_video">
|
<button id="trackbutton" class="bx--btn bx--btn--secondary" type="button" py-onClick="toggle_video()">
|
||||||
Toggle Video
|
Toggle Video
|
||||||
</button>
|
</button>
|
||||||
<button id="nextimagebutton" class="mt10 bx--btn bx--btn--secondary" type="button" disabled>
|
<button id="nextimagebutton" class="mt10 bx--btn bx--btn--secondary" type="button" disabled>
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
<title>PyScript Hello World</title>
|
<title>PyScript Hello World</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<h2>REPL</h2>
|
<h2>REPL</h2>
|
||||||
</a>
|
</a>
|
||||||
<p>
|
<p>
|
||||||
A Python REPL (Read Eval Print Loop).
|
A Python REPL (Read Eval Print Loop)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
<h2>REPL2</h2>
|
<h2>REPL2</h2>
|
||||||
</a>
|
</a>
|
||||||
<p>
|
<p>
|
||||||
A Python REPL (Read Eval Print Loop) with slightly better formatting..
|
A Python REPL (Read Eval Print Loop) with slightly better formatting
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
<h2>TODO App</h2>
|
<h2>TODO App</h2>
|
||||||
</a>
|
</a>
|
||||||
<p>
|
<p>
|
||||||
Demo showing how would a Simple TODO App would look like in PyScript</code> tag
|
Simple TODO App
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
<h2>PyScript Native TODO App</h2>
|
<h2>PyScript Native TODO App</h2>
|
||||||
</a>
|
</a>
|
||||||
<p>
|
<p>
|
||||||
Demo showing how would a Simple TODO App would look like in PyScript</code> tag
|
Simple TODO App using <code><py-list></code>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
<h2>Matplotlib</h2>
|
<h2>Matplotlib</h2>
|
||||||
</a>
|
</a>
|
||||||
<p>
|
<p>
|
||||||
Demonstrates rendering <a href="https://matplotlib.org/" target="_blank">Matplotlib</a> figure as output of the py-script tag
|
Demonstrates rendering a <a href="https://matplotlib.org/" target="_blank">Matplotlib</a> figure as output of the py-script tag
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
</a>
|
</a>
|
||||||
<p>
|
<p>
|
||||||
Demonstrates rendering <a href="https://altair-viz.github.io/" target="_blank">Altair</a> plot as output of the py-script tag
|
Demonstrates rendering a <a href="https://altair-viz.github.io/" target="_blank">Altair</a> plot as output of the py-script tag
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
</a>
|
</a>
|
||||||
<p>
|
<p>
|
||||||
Demonstrates rendering
|
Demonstrates rendering a
|
||||||
<a href="https://python-visualization.github.io/folium/" target="_blank">Folium</a>
|
<a href="https://python-visualization.github.io/folium/" target="_blank">Folium</a>
|
||||||
map as output of the py-script tag
|
map as output of the py-script tag
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
<title>Svelte app</title>
|
<title>Svelte app</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="../favicon.png" />
|
<link rel="icon" type="image/png" href="../favicon.png" />
|
||||||
<link rel="stylesheet" href="../https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
|
||||||
<script defer src="../https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -72,7 +72,7 @@ def toggle_video(evt):
|
|||||||
|
|
||||||
async def start_video():
|
async def start_video():
|
||||||
global isVideo
|
global isVideo
|
||||||
update_note.write("Inside sstart video")
|
update_note.write("Inside start video")
|
||||||
status = await handTrack.startVideo(video.element)
|
status = await handTrack.startVideo(video.element)
|
||||||
console.log("video started", status)
|
console.log("video started", status)
|
||||||
if status:
|
if status:
|
||||||
@@ -131,7 +131,7 @@ pyscript.run_until_complete(start())
|
|||||||
|
|
||||||
<div class="mb10">
|
<div class="mb10">
|
||||||
<p>Use < > to move, ↓ to crouch and x to jump. If video is enabled, say hi to jump as well! </p>
|
<p>Use < > to move, ↓ to crouch and x to jump. If video is enabled, say hi to jump as well! </p>
|
||||||
<button id="trackbutton" class="bx--btn bx--btn--secondary" type="button" pys-onClick="toggle_video">
|
<button id="trackbutton" class="bx--btn bx--btn--secondary" type="button" py-onClick="toggle_video()">
|
||||||
Start Video
|
Start Video
|
||||||
</button>
|
</button>
|
||||||
<div id="update-note" py-mount class="updatenote mt10">loading model ..</div>
|
<div id="update-note" py-mount class="updatenote mt10">loading model ..</div>
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
<title>Matplotlib</title>
|
<title>Matplotlib</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
<py-env>
|
<py-config>
|
||||||
- matplotlib
|
packages = [
|
||||||
</py-env>
|
"matplotlib"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mpl"></div>
|
<div id="mpl"></div>
|
||||||
|
|||||||
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<py-env>
|
<py-config>
|
||||||
- numpy
|
packages = [
|
||||||
- networkx
|
"numpy",
|
||||||
- matplotlib
|
"networkx",
|
||||||
</py-env>
|
"matplotlib"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
<py-script>
|
<py-script>
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
|
|||||||
@@ -6,14 +6,16 @@
|
|||||||
|
|
||||||
<title>micrograd</title>
|
<title>micrograd</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
|
||||||
<py-env>
|
<py-config>
|
||||||
- micrograd
|
packages = [
|
||||||
- numpy
|
"micrograd",
|
||||||
- matplotlib
|
"numpy",
|
||||||
</py-env>
|
"matplotlib"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||||
</head>
|
</head>
|
||||||
@@ -49,7 +51,7 @@
|
|||||||
<div id="python-status">Python is currently starting. Please wait...</div>
|
<div id="python-status">Python is currently starting. Please wait...</div>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<button id="run-all-button" class="btn btn-primary" type="submit" pys-onClick="run_all_micrograd_demo">Run All</button><br>
|
<button id="run-all-button" class="btn btn-primary" type="submit" py-onClick="run_all_micrograd_demo()">Run All</button><br>
|
||||||
<py-script src="/micrograd_ai.py"></py-script>
|
<py-script src="/micrograd_ai.py"></py-script>
|
||||||
<div id="micrograd-run-all-print-div"></div><br>
|
<div id="micrograd-run-all-print-div"></div><br>
|
||||||
<div id="micrograd-run-all-fig1-div"></div>
|
<div id="micrograd-run-all-fig1-div"></div>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
<title>Visualization of Mandelbrot, Julia and Newton sets with NumPy and HTML5 canvas</title>
|
<title>Visualization of Mandelbrot, Julia and Newton sets with NumPy and HTML5 canvas</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.loading {
|
.loading {
|
||||||
@@ -78,16 +78,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<py-env>
|
<py-config type="json">
|
||||||
- numpy
|
{
|
||||||
- sympy
|
"packages": [
|
||||||
- paths:
|
"numpy",
|
||||||
- ./palettes.py
|
"sympy"
|
||||||
- ./fractals.py
|
],
|
||||||
</py-env>
|
"paths": [
|
||||||
|
"./palettes.py",
|
||||||
|
"./fractals.py"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</py-config>
|
||||||
|
|
||||||
<py-script>
|
<py-script>
|
||||||
from pyodide import to_js, create_proxy
|
from pyodide.ffi import to_js, create_proxy
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import sympy
|
import sympy
|
||||||
|
|||||||
@@ -7,14 +7,16 @@
|
|||||||
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script>
|
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script>
|
||||||
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script>
|
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script>
|
||||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@holoviz/panel@0.13.1/dist/panel.min.js"></script>
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@holoviz/panel@0.13.1/dist/panel.min.js"></script>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<py-env>
|
<py-config>
|
||||||
- bokeh
|
packages = [
|
||||||
- numpy
|
"bokeh",
|
||||||
- panel==0.13.1
|
"numpy",
|
||||||
</py-env>
|
"panel==0.13.1"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
<body>
|
<body>
|
||||||
<h1>Panel Example</h1>
|
<h1>Panel Example</h1>
|
||||||
<div id="simple_app"></div>
|
<div id="simple_app"></div>
|
||||||
|
|||||||
@@ -38,16 +38,18 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<py-env>
|
<py-config>
|
||||||
- bokeh
|
packages = [
|
||||||
- numpy
|
"bokeh",
|
||||||
- pandas
|
"numpy",
|
||||||
- panel==0.13.1
|
"pandas",
|
||||||
</py-env>
|
"panel==0.13.1"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
|
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
|
||||||
|
|||||||
@@ -39,17 +39,19 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<py-env>
|
<py-config>
|
||||||
- altair
|
packages = [
|
||||||
- numpy
|
"altair",
|
||||||
- pandas
|
"numpy",
|
||||||
- scikit-learn
|
"pandas",
|
||||||
- panel==0.13.1
|
"scikit-learn",
|
||||||
</py-env>
|
"panel==0.13.1"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
|
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
|
||||||
|
|||||||
@@ -30,16 +30,18 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<py-env>
|
<py-config>
|
||||||
- bokeh
|
packages = [
|
||||||
- numpy
|
"bokeh",
|
||||||
- pandas
|
"numpy",
|
||||||
- panel==0.13.1
|
"pandas",
|
||||||
</py-env>
|
"panel==0.13.1"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
|
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
|
||||||
|
|||||||
@@ -7,14 +7,15 @@
|
|||||||
<title>REPL</title>
|
<title>REPL</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<py-env>
|
<py-config>
|
||||||
- paths:
|
paths = [
|
||||||
- ./antigravity.py
|
"./antigravity.py"
|
||||||
</py-env>
|
]
|
||||||
|
</py-config>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1><b>pyscript REPL</b></h1>
|
<h1><b>pyscript REPL</b></h1>
|
||||||
|
|||||||
@@ -7,23 +7,26 @@
|
|||||||
<title>Custom REPL Example</title>
|
<title>Custom REPL Example</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<link rel="stylesheet" href="repl.css" />
|
<link rel="stylesheet" href="repl.css" />
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<py-env>
|
<py-config>
|
||||||
- bokeh
|
packages = [
|
||||||
- numpy
|
"bokeh",
|
||||||
- paths:
|
"numpy"
|
||||||
- ./utils.py
|
]
|
||||||
- ./antigravity.py
|
paths = [
|
||||||
</py-env>
|
"./utils.py",
|
||||||
|
"./antigravity.py"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1 class="font-semibold text-2xl ml-5">Custom REPL</h1>
|
<h1 class="font-semibold text-2xl ml-5">Custom REPL</h1>
|
||||||
<py-box widths="1/2;1/2">
|
<py-box widths="2/3;1/3">
|
||||||
<py-repl id="my-repl" auto-generate="true" std-out="output" std-err="err-div"> </py-repl>
|
<py-repl id="my-repl" auto-generate="true" std-out="output" std-err="err-div"> </py-repl>
|
||||||
<div id="output" class="p-4"></div>
|
<div id="output" class="p-4"></div>
|
||||||
</py-box>
|
</py-box>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||||||
<link rel='stylesheet' type='text/css' media='screen' href='https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css'>
|
<link rel='stylesheet' type='text/css' media='screen' href='https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css'>
|
||||||
<link rel="icon" type="image/png" href="https://user-images.githubusercontent.com/49681382/166738771-d0c26557-426c-4688-9641-8db5e6b08348.png" />
|
<link rel="icon" type="image/png" href="https://user-images.githubusercontent.com/49681382/166738771-d0c26557-426c-4688-9641-8db5e6b08348.png" />
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -49,8 +49,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button id="run" type="button" class="button is-primary" pys-onClick="run">Run!</button>
|
<button id="run" type="button" class="button is-primary" py-onClick="run()">Run!</button>
|
||||||
<button id="clear" type="button" class="button is-danger" pys-onClick="clear">Clear</button>
|
<button id="clear" type="button" class="button is-danger" py-onClick="clear()">Clear</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,14 @@
|
|||||||
<title>Simple Clock Demo</title>
|
<title>Simple Clock Demo</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
<py-env>
|
<py-config>
|
||||||
- paths:
|
paths = [
|
||||||
- ./utils.py
|
"./utils.py"
|
||||||
</py-env>
|
]
|
||||||
|
</py-config>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -6,13 +6,14 @@
|
|||||||
<title>Todo App</title>
|
<title>Todo App</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
<py-env>
|
<py-config>
|
||||||
- paths:
|
paths = [
|
||||||
- ./utils.py
|
"./utils.py"
|
||||||
</py-env>
|
]
|
||||||
|
</py-config>
|
||||||
<py-register-widget src="./pylist.py" name="py-list" klass="PyList"></py-register-widget>
|
<py-register-widget src="./pylist.py" name="py-list" klass="PyList"></py-register-widget>
|
||||||
|
|
||||||
<py-script>
|
<py-script>
|
||||||
|
|||||||
@@ -7,28 +7,29 @@
|
|||||||
<title>Todo App</title>
|
<title>Todo App</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
<py-env>
|
<py-config>
|
||||||
- paths:
|
paths = [
|
||||||
- ./utils.py
|
"./utils.py"
|
||||||
</py-env>
|
]
|
||||||
|
</py-config>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="container">
|
<body class="container">
|
||||||
<!-- <py-repl id="my-repl" auto-generate="true"> </py-repl> -->
|
<!-- <py-repl id="my-repl" auto-generate="true"> </py-repl> -->
|
||||||
<py-script src="./todo.py"> </py-script>
|
<py-script src="./todo.py"> </py-script>
|
||||||
|
|
||||||
<main class="max-w-xs mx-auto mt-4">
|
<main>
|
||||||
<section>
|
<section>
|
||||||
|
|
||||||
<div class="text-center w-full mb-8">
|
<div class="text-center w-full mb-8">
|
||||||
<h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">To Do List</h1>
|
<h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">To Do List</h1>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input id="new-task-content" class="border flex-1 mr-3 border-gray-300 p-2 rounded" type="text">
|
<input id="new-task-content" class="py-input" type="text">
|
||||||
<button id="new-task-btn" class="p-2 text-white bg-blue-600 border border-blue-600 rounded" type="submit" pys-onClick="add_task">
|
<button id="new-task-btn" class="py-button" type="submit" py-click="add_task()">
|
||||||
Add task
|
Add task
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,9 +39,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template id="task-template">
|
<template id="task-template">
|
||||||
<section class="task bg-white my-1">
|
<section class="task py-li-element">
|
||||||
<label for="flex items-center p-2 ">
|
<label for="flex items-center p-2 ">
|
||||||
<input class="mr-2" type="checkbox" class="task-check">
|
<input class="mr-2" type="checkbox">
|
||||||
<p class="m-0 inline"></p>
|
<p class="m-0 inline"></p>
|
||||||
</label>
|
</label>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ def add_task(*ags, **kws):
|
|||||||
|
|
||||||
# add the task element to the page as new node in the list by cloning from a
|
# add the task element to the page as new node in the list by cloning from a
|
||||||
# template
|
# template
|
||||||
task_html = task_template.clone(task_id, to=task_list)
|
task_html = task_template.clone(task_id)
|
||||||
task_html_content = task_html.select("p")
|
task_html_content = task_html.select("p")
|
||||||
task_html_content.element.innerText = task["content"]
|
task_html_content.element.innerText = task["content"]
|
||||||
task_html_check = task_html.select("input")
|
task_html_check = task_html.select("input")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
crossorigin="anonymous">
|
crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="./static/toga.css">
|
<link rel="stylesheet" href="./static/toga.css">
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
|
|
||||||
<title>Loading...</title>
|
<title>Loading...</title>
|
||||||
<link rel="icon" type="image/png" href="../favicon.png" />
|
<link rel="icon" type="image/png" href="../favicon.png" />
|
||||||
@@ -34,12 +34,14 @@
|
|||||||
crossorigin="anonymous">
|
crossorigin="anonymous">
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
<py-env>
|
<py-config>
|
||||||
- './static/wheels/travertino-0.1.3-py3-none-any.whl'
|
packages = [
|
||||||
- './static/wheels/toga_core-0.3.0.dev33-py3-none-any.whl'
|
"./static/wheels/travertino-0.1.3-py3-none-any.whl",
|
||||||
- './static/wheels/toga_web-0.3.0.dev33-py3-none-any.whl'
|
"./static/wheels/toga_core-0.3.0.dev33-py3-none-any.whl",
|
||||||
- './static/wheels/freedom-0.0.1-py3-none-any.whl'
|
"./static/wheels/toga_web-0.3.0.dev33-py3-none-any.whl",
|
||||||
</py-env>
|
"./static/wheels/freedom-0.0.1-py3-none-any.whl"
|
||||||
|
]
|
||||||
|
</py-config>
|
||||||
<py-script>
|
<py-script>
|
||||||
from toga_web.dom import handle as dom_handle
|
from toga_web.dom import handle as dom_handle
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from datetime import datetime as dt
|
|||||||
|
|
||||||
|
|
||||||
def format_date(dt_, fmt="%m/%d/%Y, %H:%M:%S"):
|
def format_date(dt_, fmt="%m/%d/%Y, %H:%M:%S"):
|
||||||
return dt_.strftime(fmt)
|
return f"{dt_:{fmt}}"
|
||||||
|
|
||||||
|
|
||||||
def now(fmt="%m/%d/%Y, %H:%M:%S"):
|
def now(fmt="%m/%d/%Y, %H:%M:%S"):
|
||||||
|
|||||||
@@ -22,19 +22,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/89/three.min.js'></script>
|
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/89/three.min.js'></script>
|
||||||
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||||
<script>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<py-script>
|
<py-script>
|
||||||
from pyodide import create_proxy, to_js
|
from pyodide.ffi import create_proxy, to_js
|
||||||
from js import window
|
from js import window
|
||||||
from js import Math
|
from js import Math
|
||||||
from js import THREE
|
from js import THREE
|
||||||
from js import performance
|
from js import performance
|
||||||
from pyodide import to_js
|
|
||||||
from js import Object
|
from js import Object
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -53,7 +50,7 @@ def onMouseMove(event):
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||||
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||||||
js.document.addEventListener('mousemove', pyodide.create_proxy(onMouseMove))
|
js.document.addEventListener('mousemove', pyodide.ffi.create_proxy(onMouseMove))
|
||||||
|
|
||||||
camera = THREE.PerspectiveCamera.new( 35, window.innerWidth / window.innerHeight, 1, 500 )
|
camera = THREE.PerspectiveCamera.new( 35, window.innerWidth / window.innerHeight, 1, 500 )
|
||||||
scene = THREE.Scene.new()
|
scene = THREE.Scene.new()
|
||||||
|
|||||||
@@ -10,25 +10,12 @@ module.exports = {
|
|||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
tsconfigRootDir: __dirname,
|
tsconfigRootDir: __dirname,
|
||||||
project: ['./tsconfig.json'],
|
project: ['./tsconfig.json'],
|
||||||
extraFileExtensions: ['.svelte'],
|
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
es6: true,
|
es6: true,
|
||||||
browser: true,
|
browser: true,
|
||||||
},
|
},
|
||||||
overrides: [
|
plugins: ['@typescript-eslint'],
|
||||||
{
|
|
||||||
files: ['*.svelte'],
|
|
||||||
processor: 'svelte3/svelte3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
settings: {
|
|
||||||
'svelte3/typescript': require('typescript'),
|
|
||||||
// ignore style tags in Svelte because of Tailwind CSS
|
|
||||||
// See https://github.com/sveltejs/eslint-plugin-svelte3/issues/70
|
|
||||||
'svelte3/ignore-styles': () => true,
|
|
||||||
},
|
|
||||||
plugins: ['svelte3', '@typescript-eslint'],
|
|
||||||
ignorePatterns: ['node_modules'],
|
ignorePatterns: ['node_modules'],
|
||||||
rules: {
|
rules: {
|
||||||
'no-prototype-builtins': 'warn',
|
'no-prototype-builtins': 'warn',
|
||||||
|
|||||||
@@ -3,11 +3,7 @@ module.exports = {
|
|||||||
bracketSameLine: true,
|
bracketSameLine: true,
|
||||||
singleQuote: true,
|
singleQuote: true,
|
||||||
printWidth: 120,
|
printWidth: 120,
|
||||||
plugins: ['prettier-plugin-svelte'],
|
|
||||||
semi: true,
|
semi: true,
|
||||||
svelteSortOrder: 'options-styles-scripts-markup',
|
|
||||||
svelteStrictMode: false,
|
|
||||||
svelteIndentScriptAndStyle: true,
|
|
||||||
tabWidth: 4,
|
tabWidth: 4,
|
||||||
trailingComma: 'all',
|
trailingComma: 'all',
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -2,20 +2,44 @@ tag := latest
|
|||||||
git_hash ?= $(shell git log -1 --pretty=format:%h)
|
git_hash ?= $(shell git log -1 --pretty=format:%h)
|
||||||
|
|
||||||
base_dir ?= $(shell git rev-parse --show-toplevel)
|
base_dir ?= $(shell git rev-parse --show-toplevel)
|
||||||
src_dir ?= $(base_dir)/src
|
src_dir ?= $(base_dir)/pyscriptjs/src
|
||||||
examples ?= ../$(base_dir)/examples
|
examples ?= ../$(base_dir)/examples
|
||||||
app_dir ?= $(shell git rev-parse --show-prefix)
|
app_dir ?= $(shell git rev-parse --show-prefix)
|
||||||
|
|
||||||
CONDA_EXE := conda
|
CONDA_EXE := conda
|
||||||
CONDA_ENV ?= ./env
|
CONDA_ENV ?= $(base_dir)/pyscriptjs/env
|
||||||
env := $(CONDA_ENV)
|
env := $(CONDA_ENV)
|
||||||
conda_run := $(CONDA_EXE) run -p $(env)
|
conda_run := $(CONDA_EXE) run -p $(env)
|
||||||
|
PYTEST_EXE := $(CONDA_ENV)/bin/pytest
|
||||||
|
GOOD_NODE_VER := 14
|
||||||
|
GOOD_NPM_VER := 6
|
||||||
|
NODE_VER := $(shell node -v | cut -d. -f1 | sed 's/^v\(.*\)/\1/')
|
||||||
|
NPM_VER := $(shell npm -v | cut -d. -f1)
|
||||||
|
|
||||||
|
ifeq ($(shell uname -s), Darwin)
|
||||||
|
SED_I_ARG := -i ''
|
||||||
|
else
|
||||||
|
SED_I_ARG := -i
|
||||||
|
endif
|
||||||
|
|
||||||
|
GOOD_NODE := $(shell if [ $(NODE_VER) -ge $(GOOD_NODE_VER) ]; then echo true; else echo false; fi)
|
||||||
|
GOOD_NPM := $(shell if [ $(NPM_VER) -ge $(GOOD_NPM_VER) ]; then echo true; else echo false; fi)
|
||||||
|
|
||||||
|
.PHONY: check-node
|
||||||
|
check-node:
|
||||||
|
@echo Build requires Node $(GOOD_NODE_VER).x or higher: $(NODE_VER) detected && $(GOOD_NODE)
|
||||||
|
|
||||||
|
.PHONY: check-npm
|
||||||
|
check-npm:
|
||||||
|
@echo Build requires npm $(GOOD_NPM_VER).x or higher: $(NPM_VER) detected && $(GOOD_NPM)
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
|
make check-node
|
||||||
|
make check-npm
|
||||||
npm install
|
npm install
|
||||||
$(CONDA_EXE) env $(shell [ -d $(env) ] && echo update || echo create) -p $(env) --file environment.yml
|
$(CONDA_EXE) env $(shell [ -d $(env) ] && echo update || echo create) -p $(env) --file environment.yml
|
||||||
$(conda_run) playwright install
|
$(conda_run) playwright install
|
||||||
$(CONDA_EXE) install -c anaconda pytest
|
$(CONDA_EXE) install -c anaconda pytest -y
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
find . -name \*.py[cod] -delete
|
find . -name \*.py[cod] -delete
|
||||||
@@ -34,26 +58,41 @@ dev:
|
|||||||
build:
|
build:
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
example:
|
examples:
|
||||||
mkdir -p ./examples
|
mkdir -p ./examples
|
||||||
cp -r ../examples/* ./examples
|
cp -r ../examples/* ./examples
|
||||||
chmod -R 755 examples
|
chmod -R 755 examples
|
||||||
find ./examples/toga -type f -name '*.html' -exec sed -i '' s+https://pyscript.net/alpha/+../build/+g {} \;
|
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 -i '' s+https://pyscript.net/alpha/+../../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 -i '' s+https://pyscript.net/alpha/+./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
|
||||||
|
|
||||||
test:
|
test:
|
||||||
make example
|
make examples
|
||||||
npm run build
|
make test-ts
|
||||||
$(conda_run) pytest -vv $(ARGS) tests/ --log-cli-level=warning
|
make test-py
|
||||||
|
|
||||||
|
test-integration:
|
||||||
|
make examples
|
||||||
|
$(PYTEST_EXE) -vv $(ARGS) tests/integration/ --log-cli-level=warning
|
||||||
|
|
||||||
test-py:
|
test-py:
|
||||||
@echo "Tests are coming :( this is a placeholder and it's meant to fail!"
|
@echo "Tests from $(src_dir)"
|
||||||
$(conda_run) pytest -vv $(ARGS) tests/ --log-cli-level=warning
|
$(PYTEST_EXE) -vv $(ARGS) tests/py-unit/ --log-cli-level=warning
|
||||||
|
|
||||||
test-ts:
|
test-ts:
|
||||||
@echo "Tests are coming :( this is a placeholder and it's meant to fail!"
|
@echo "Tests are coming :( this is a placeholder and it's meant to fail!"
|
||||||
npm run tests
|
npm run test
|
||||||
|
|
||||||
fmt: fmt-py fmt-ts
|
fmt: fmt-py fmt-ts
|
||||||
@echo "Format completed"
|
@echo "Format completed"
|
||||||
@@ -74,10 +113,4 @@ fmt-py:
|
|||||||
fmt-py-check:
|
fmt-py-check:
|
||||||
$(conda_run) black -l 88 --check .
|
$(conda_run) black -l 88 --check .
|
||||||
|
|
||||||
lint: lint-ts
|
|
||||||
@echo "Format check completed"
|
|
||||||
|
|
||||||
lint-ts:
|
|
||||||
$(conda_run) npm run lint
|
|
||||||
|
|
||||||
.PHONY: $(MAKECMDGOALS)
|
.PHONY: $(MAKECMDGOALS)
|
||||||
|
|||||||
12
pyscriptjs/__mocks__/fileMock.js
Normal file
12
pyscriptjs/__mocks__/fileMock.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* this file mocks the `src/python/pyscript.py` file
|
||||||
|
* since importing of `.py` files isn't usually supported
|
||||||
|
* inside JS/TS files.
|
||||||
|
*
|
||||||
|
* It sets the value of whatever is imported from
|
||||||
|
* `src/python/pyscript.py` to be an empty string i.e. ""
|
||||||
|
*
|
||||||
|
* This is needed since the imported object is further
|
||||||
|
* passed to a function which only accepts a string.
|
||||||
|
*/
|
||||||
|
module.exports = "";
|
||||||
@@ -4,14 +4,16 @@ channels:
|
|||||||
- microsoft
|
- microsoft
|
||||||
dependencies:
|
dependencies:
|
||||||
- python=3.9
|
- python=3.9
|
||||||
- pip=20.2.2
|
- pip
|
||||||
- pytest=7
|
- pytest=7
|
||||||
- nodejs=16
|
- nodejs=16
|
||||||
- black
|
- black
|
||||||
- isort
|
- isort
|
||||||
- codespell
|
- codespell
|
||||||
- pre-commit
|
- pre-commit
|
||||||
- playwright
|
- pillow
|
||||||
|
- numpy
|
||||||
|
|
||||||
- pip:
|
- pip:
|
||||||
|
- playwright
|
||||||
- pytest-playwright
|
- pytest-playwright
|
||||||
|
|||||||
19
pyscriptjs/jest.config.js
Normal file
19
pyscriptjs/jest.config.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//jest.config.js
|
||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'jest-environment-jsdom',
|
||||||
|
extensionsToTreatAsEsm: ['.ts'],
|
||||||
|
globals: {
|
||||||
|
'ts-jest': {
|
||||||
|
tsconfig: 'tsconfig.json',
|
||||||
|
useESM: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
verbose: true,
|
||||||
|
testEnvironmentOptions: {
|
||||||
|
url: "http://localhost"
|
||||||
|
},
|
||||||
|
moduleNameMapper: {
|
||||||
|
"^[./a-zA-Z0-9$_-]+\\.py$": "<rootDir>/__mocks__/fileMock.js",
|
||||||
|
}
|
||||||
|
};
|
||||||
10897
pyscriptjs/package-lock.json
generated
10897
pyscriptjs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,39 +7,43 @@
|
|||||||
"dev": "rollup -c -w",
|
"dev": "rollup -c -w",
|
||||||
"start": "sirv public --no-clear --port 8080",
|
"start": "sirv public --no-clear --port 8080",
|
||||||
"validate": "svelte-check",
|
"validate": "svelte-check",
|
||||||
"format:check": "prettier --check './src/**/*.{js,svelte,html,ts}'",
|
"format:check": "prettier --check './src/**/*.{js,html,ts}'",
|
||||||
"format": "prettier --write './src/**/*.{js,svelte,html,ts}'",
|
"format": "prettier --write './src/**/*.{js,html,ts}'",
|
||||||
"lint": "eslint './src/**/*.{js,svelte,html,ts}'",
|
"lint": "eslint './src/**/*.{js,html,ts}'",
|
||||||
"lint:fix": "eslint --fix './src/**/*.{js,svelte,html,ts}'",
|
"lint:fix": "eslint --fix './src/**/*.{js,html,ts}'",
|
||||||
"xprelint": "npm run format"
|
"xprelint": "npm run format",
|
||||||
|
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
|
||||||
|
"test:watch": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^17.0.0",
|
"@rollup/plugin-commonjs": "^17.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^11.0.0",
|
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||||
"@rollup/plugin-typescript": "^8.3.2",
|
"@rollup/plugin-typescript": "^8.4.0",
|
||||||
"@tsconfig/svelte": "^1.0.0",
|
"@tsconfig/svelte": "^1.0.0",
|
||||||
|
"@types/jest": "^28.1.6",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.20.0",
|
"@types/node": "^18.7.11",
|
||||||
"@typescript-eslint/parser": "^5.20.0",
|
"@typescript-eslint/eslint-plugin": "^5.36.0",
|
||||||
|
"@typescript-eslint/parser": "^5.36.0",
|
||||||
"autoprefixer": "^10.4.7",
|
"autoprefixer": "^10.4.7",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.14.0",
|
"eslint": "^8.14.0",
|
||||||
"eslint-plugin-svelte3": "^3.4.1",
|
"jest": "^28.1.3",
|
||||||
"postcss": "^8.4.13",
|
"jest-environment-jsdom": "^28.1.3",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
"prettier-plugin-svelte": "^2.7.0",
|
"pyodide": "0.21.2",
|
||||||
"rollup": "^2.71.1",
|
"rollup": "^2.71.1",
|
||||||
|
"rollup-plugin-copy": "^3.4.0",
|
||||||
"rollup-plugin-css-only": "^3.1.0",
|
"rollup-plugin-css-only": "^3.1.0",
|
||||||
"rollup-plugin-livereload": "^2.0.0",
|
"rollup-plugin-livereload": "^2.0.0",
|
||||||
"rollup-plugin-serve": "^1.1.0",
|
"rollup-plugin-serve": "^1.1.0",
|
||||||
"rollup-plugin-string": "^3.0.0",
|
"rollup-plugin-string": "^3.0.0",
|
||||||
"rollup-plugin-svelte": "^7.0.0",
|
|
||||||
"rollup-plugin-terser": "^7.0.0",
|
"rollup-plugin-terser": "^7.0.0",
|
||||||
"svelte": "^3.48.0",
|
"svelte": "^3.48.0",
|
||||||
"svelte-check": "^1.0.0",
|
"svelte-check": "^1.0.0",
|
||||||
"svelte-preprocess": "^4.10.6",
|
"ts-jest": "^28.0.7",
|
||||||
"tailwindcss": "^2.0.2",
|
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
"typescript": "^4.6.4"
|
"typescript": "^4.8.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/basic-setup": "^0.19.1",
|
"@codemirror/basic-setup": "^0.19.1",
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import svelte from "rollup-plugin-svelte";
|
|
||||||
import commonjs from "@rollup/plugin-commonjs";
|
import commonjs from "@rollup/plugin-commonjs";
|
||||||
import resolve from "@rollup/plugin-node-resolve";
|
import resolve from "@rollup/plugin-node-resolve";
|
||||||
import livereload from "rollup-plugin-livereload";
|
import livereload from "rollup-plugin-livereload";
|
||||||
import { terser } from "rollup-plugin-terser";
|
import { terser } from "rollup-plugin-terser";
|
||||||
import sveltePreprocess from "svelte-preprocess";
|
|
||||||
import typescript from "@rollup/plugin-typescript";
|
import typescript from "@rollup/plugin-typescript";
|
||||||
import css from "rollup-plugin-css-only";
|
import css from "rollup-plugin-css-only";
|
||||||
import serve from "rollup-plugin-serve";
|
import serve from "rollup-plugin-serve";
|
||||||
import { string } from "rollup-plugin-string";
|
import { string } from "rollup-plugin-string";
|
||||||
|
import copy from 'rollup-plugin-copy'
|
||||||
|
|
||||||
const production = !process.env.ROLLUP_WATCH || (process.env.NODE_ENV === "production");
|
const production = !process.env.ROLLUP_WATCH || (process.env.NODE_ENV === "production");
|
||||||
|
|
||||||
@@ -17,28 +16,19 @@ export default {
|
|||||||
{
|
{
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
format: "iife",
|
format: "iife",
|
||||||
|
inlineDynamicImports: true,
|
||||||
name: "app",
|
name: "app",
|
||||||
file: "examples/build/pyscript.js",
|
file: "build/pyscript.js",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: "examples/build/pyscript.min.js",
|
file: "build/pyscript.min.js",
|
||||||
format: "iife",
|
format: "iife",
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
inlineDynamicImports: true,
|
||||||
plugins: [terser()],
|
plugins: [terser()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
svelte({
|
|
||||||
// add postcss config with tailwind
|
|
||||||
preprocess: sveltePreprocess({
|
|
||||||
postcss: {
|
|
||||||
plugins: [require("tailwindcss"), require("autoprefixer")],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
compilerOptions: {
|
|
||||||
dev: !production,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
css({ output: "pyscript.css" }),
|
css({ output: "pyscript.css" }),
|
||||||
// Bundle all the Python files into the output file
|
// Bundle all the Python files into the output file
|
||||||
string({
|
string({
|
||||||
@@ -53,6 +43,12 @@ export default {
|
|||||||
sourceMap: !production,
|
sourceMap: !production,
|
||||||
inlineSources: !production,
|
inlineSources: !production,
|
||||||
}),
|
}),
|
||||||
|
// This will make sure that examples will always get the latest build folder
|
||||||
|
!production && copy({
|
||||||
|
targets: [
|
||||||
|
{ src: 'build/*', dest: 'examples/build' }
|
||||||
|
]
|
||||||
|
}),
|
||||||
!production && serve(),
|
!production && serve(),
|
||||||
!production && livereload("public"),
|
!production && livereload("public"),
|
||||||
// production && terser(),
|
// production && terser(),
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
<style global>
|
|
||||||
.spinner::after {
|
|
||||||
content: '';
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
position: absolute;
|
|
||||||
top: calc(40% - 20px);
|
|
||||||
left: calc(50% - 20px);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner.smooth::after {
|
|
||||||
border-top: 4px solid rgba(255, 255, 255, 1);
|
|
||||||
border-left: 4px solid rgba(255, 255, 255, 1);
|
|
||||||
border-right: 4px solid rgba(255, 255, 255, 0);
|
|
||||||
animation: spinner 0.6s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes spinner {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
color: rgba(255, 255, 255, 0.8);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
margin-top: 6rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Tailwind from './Tailwind.svelte';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Tailwind />
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<style global lang="postcss">
|
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
</style>
|
|
||||||
@@ -1,26 +1,18 @@
|
|||||||
import { loadedEnvironments, mode, pyodideLoaded } from '../stores';
|
import { runtimeLoaded } from '../stores';
|
||||||
import { guidGenerator, addClasses, removeClasses } from '../utils';
|
import { guidGenerator, addClasses, removeClasses } from '../utils';
|
||||||
// Premise used to connect to the first available pyodide interpreter
|
|
||||||
let runtime;
|
import type { Runtime } from '../runtime';
|
||||||
let environments;
|
import { getLogger } from '../logger';
|
||||||
let currentMode;
|
|
||||||
|
const logger = getLogger('pyscript/base');
|
||||||
|
|
||||||
|
// Global `Runtime` that implements the generic runtimes API
|
||||||
|
let runtime: Runtime;
|
||||||
let Element;
|
let Element;
|
||||||
|
|
||||||
pyodideLoaded.subscribe(value => {
|
runtimeLoaded.subscribe(value => {
|
||||||
runtime = value;
|
runtime = value;
|
||||||
});
|
});
|
||||||
loadedEnvironments.subscribe(value => {
|
|
||||||
environments = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
mode.subscribe(value => {
|
|
||||||
currentMode = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: use type declaractions
|
|
||||||
type PyodideInterface = {
|
|
||||||
registerJsModule(name: string, module: object): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class BaseEvalElement extends HTMLElement {
|
export class BaseEvalElement extends HTMLElement {
|
||||||
shadow: ShadowRoot;
|
shadow: ShadowRoot;
|
||||||
@@ -60,7 +52,7 @@ export class BaseEvalElement extends HTMLElement {
|
|||||||
this.appendOutput = false;
|
this.appendOutput = false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log(`${this.id}: custom output-modes are currently not implemented`);
|
logger.warn(`${this.id}: custom output-modes are currently not implemented`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,20 +82,23 @@ export class BaseEvalElement extends HTMLElement {
|
|||||||
return this.code;
|
return this.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async _register_esm(pyodide: PyodideInterface): Promise<void> {
|
protected async _register_esm(runtime: Runtime): Promise<void> {
|
||||||
const imports: { [key: string]: unknown } = {};
|
const imports: { [key: string]: unknown } = {};
|
||||||
|
const nodes = document.querySelectorAll("script[type='importmap']");
|
||||||
for (const node of document.querySelectorAll("script[type='importmap']")) {
|
const importmaps: Array<any> = [];
|
||||||
const importmap = (() => {
|
nodes.forEach( node =>
|
||||||
|
{
|
||||||
|
let importmap;
|
||||||
try {
|
try {
|
||||||
return JSON.parse(node.textContent);
|
importmap = JSON.parse(node.textContent);
|
||||||
|
if (importmap?.imports == null) return;
|
||||||
|
importmaps.push(importmap);
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
})();
|
}
|
||||||
|
)
|
||||||
if (importmap?.imports == null) continue;
|
for (const importmap of importmaps){
|
||||||
|
|
||||||
for (const [name, url] of Object.entries(importmap.imports)) {
|
for (const [name, url] of Object.entries(importmap.imports)) {
|
||||||
if (typeof name != 'string' || typeof url != 'string') continue;
|
if (typeof name != 'string' || typeof url != 'string') continue;
|
||||||
|
|
||||||
@@ -112,42 +107,32 @@ export class BaseEvalElement extends HTMLElement {
|
|||||||
// "can't read 'name' of undefined" at import time
|
// "can't read 'name' of undefined" at import time
|
||||||
imports[name] = { ...(await import(url)) };
|
imports[name] = { ...(await import(url)) };
|
||||||
} catch {
|
} catch {
|
||||||
console.error(`failed to fetch '${url}' for '${name}'`);
|
logger.error(`failed to fetch '${url}' for '${name}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pyodide.registerJsModule('esm', imports);
|
runtime.registerJsModule('esm', imports);
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluate(): Promise<void> {
|
async evaluate(): Promise<void> {
|
||||||
console.log('evaluate');
|
|
||||||
this.preEvaluate();
|
this.preEvaluate();
|
||||||
|
|
||||||
const pyodide = runtime;
|
|
||||||
let source: string;
|
let source: string;
|
||||||
let output;
|
let output: string;
|
||||||
try {
|
try {
|
||||||
source = this.source ? await this.getSourceFromFile(this.source)
|
source = this.source ? await this.getSourceFromFile(this.source)
|
||||||
: this.getSourceFromElement();
|
: this.getSourceFromElement();
|
||||||
const is_async = source.includes('asyncio')
|
|
||||||
|
|
||||||
await this._register_esm(pyodide);
|
this._register_esm(runtime);
|
||||||
if (is_async) {
|
<string>await runtime.run(
|
||||||
await pyodide.runPythonAsync(
|
`output_manager.change(out="${this.outputElement.id}", err="${this.errorElement.id}", append=${this.appendOutput ? 'True' : 'False'})`,
|
||||||
`output_manager.change(out="${this.outputElement.id}", err="${this.errorElement.id}", append=${this.appendOutput ? 'True' : 'False'})`,
|
);
|
||||||
);
|
output = <string>await runtime.run(source);
|
||||||
output = await pyodide.runPythonAsync(source);
|
|
||||||
} else {
|
|
||||||
output = pyodide.runPython(
|
|
||||||
`output_manager.change(out="${this.outputElement.id}", err="${this.errorElement.id}", append=${this.appendOutput ? 'True' : 'False'})`,
|
|
||||||
);
|
|
||||||
output = pyodide.runPython(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output !== undefined) {
|
if (output !== undefined) {
|
||||||
if (Element === undefined) {
|
if (Element === undefined) {
|
||||||
Element = pyodide.globals.get('Element');
|
Element = <Element>runtime.globals.get('Element');
|
||||||
}
|
}
|
||||||
const out = Element(this.outputElement.id);
|
const out = Element(this.outputElement.id);
|
||||||
out.write.callKwargs(output, { append: this.appendOutput });
|
out.write.callKwargs(output, { append: this.appendOutput });
|
||||||
@@ -156,61 +141,66 @@ export class BaseEvalElement extends HTMLElement {
|
|||||||
this.outputElement.style.display = 'block';
|
this.outputElement.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_async) {
|
await runtime.run(`output_manager.revert()`);
|
||||||
await pyodide.runPythonAsync(`output_manager.revert()`);
|
|
||||||
} else {
|
|
||||||
await pyodide.runPython(`output_manager.revert()`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if this REPL contains errors, delete them and remove error classes
|
// check if this REPL contains errors, delete them and remove error classes
|
||||||
const errorElements = document.querySelectorAll(`div[id^='${this.errorElement.id}'][error]`);
|
const errorElements = document.querySelectorAll(`div[id^='${this.errorElement.id}'][error]`);
|
||||||
if (errorElements.length > 0) {
|
if (errorElements.length > 0) {
|
||||||
for (const errorElement of errorElements) {
|
errorElements.forEach( errorElement =>
|
||||||
errorElement.classList.add('hidden');
|
{
|
||||||
if (this.hasAttribute('std-err')) {
|
errorElement.classList.add('hidden');
|
||||||
this.errorElement.hidden = true;
|
if (this.hasAttribute('std-err')) {
|
||||||
this.errorElement.style.removeProperty('display');
|
this.errorElement.hidden = true;
|
||||||
|
this.errorElement.style.removeProperty('display');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
removeClasses(this.errorElement, ['bg-red-200', 'p-2']);
|
removeClasses(this.errorElement, ['bg-red-200', 'p-2']);
|
||||||
|
|
||||||
this.postEvaluate();
|
this.postEvaluate();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (Element === undefined) {
|
logger.error(err);
|
||||||
Element = pyodide.globals.get('Element');
|
try{
|
||||||
|
if (Element === undefined) {
|
||||||
|
Element = <Element>runtime.globals.get('Element');
|
||||||
|
}
|
||||||
|
const out = Element(this.errorElement.id);
|
||||||
|
|
||||||
|
addClasses(this.errorElement, ['bg-red-200', 'p-2']);
|
||||||
|
out.write.callKwargs(err.toString(), { append: this.appendOutput });
|
||||||
|
if (this.errorElement.children.length === 0){
|
||||||
|
this.errorElement.setAttribute('error', '');
|
||||||
|
}else{
|
||||||
|
this.errorElement.children[this.errorElement.children.length - 1].setAttribute('error', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.errorElement.hidden = false;
|
||||||
|
this.errorElement.style.display = 'block';
|
||||||
|
this.errorElement.style.visibility = 'visible';
|
||||||
|
} catch (internalErr){
|
||||||
|
logger.error("Unnable to write error to error element in page.")
|
||||||
}
|
}
|
||||||
const out = Element(this.errorElement.id);
|
|
||||||
|
|
||||||
addClasses(this.errorElement, ['bg-red-200', 'p-2']);
|
|
||||||
out.write.callKwargs(err, { append: this.appendOutput });
|
|
||||||
|
|
||||||
this.errorElement.children[this.errorElement.children.length - 1].setAttribute('error', '');
|
|
||||||
this.errorElement.hidden = false;
|
|
||||||
this.errorElement.style.display = 'block';
|
|
||||||
this.errorElement.style.visibility = 'visible';
|
|
||||||
}
|
}
|
||||||
} // end evaluate
|
} // end evaluate
|
||||||
|
|
||||||
async eval(source: string): Promise<void> {
|
async eval(source: string): Promise<void> {
|
||||||
let output;
|
|
||||||
const pyodide = runtime;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
output = await pyodide.runPythonAsync(source);
|
const output = await runtime.run(source);
|
||||||
if (output !== undefined) {
|
if (output !== undefined) {
|
||||||
console.log(output);
|
logger.info(output);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
logger.error(err);
|
||||||
}
|
}
|
||||||
} // end eval
|
} // end eval
|
||||||
|
|
||||||
runAfterRuntimeInitialized(callback: () => Promise<void>){
|
runAfterRuntimeInitialized(callback: () => Promise<void>) {
|
||||||
pyodideLoaded.subscribe(value => {
|
runtimeLoaded.subscribe(value => {
|
||||||
if ('runPythonAsync' in value) {
|
if ('run' in value) {
|
||||||
setTimeout(async () => {
|
setTimeout(() => {
|
||||||
await callback();
|
void callback();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -243,45 +233,44 @@ function createWidget(name: string, code: string, klass: string) {
|
|||||||
// ideally we can just wait for it to load and then run. To do
|
// ideally we can just wait for it to load and then run. To do
|
||||||
// so we need to replace using the promise and actually using
|
// so we need to replace using the promise and actually using
|
||||||
// the interpreter after it loads completely
|
// the interpreter after it loads completely
|
||||||
// setTimeout(async () => {
|
// setTimeout(() => {
|
||||||
// await this.eval(this.code);
|
// void (async () => {
|
||||||
// this.proxy = this.proxyClass(this);
|
// await this.eval(this.code);
|
||||||
// console.log('proxy', this.proxy);
|
// this.proxy = this.proxyClass(this);
|
||||||
// this.proxy.connect();
|
// console.log('proxy', this.proxy);
|
||||||
// this.registerWidget();
|
// this.proxy.connect();
|
||||||
|
// this.registerWidget();
|
||||||
|
// })();
|
||||||
// }, 2000);
|
// }, 2000);
|
||||||
pyodideLoaded.subscribe(value => {
|
runtimeLoaded.subscribe(value => {
|
||||||
console.log('RUNTIME READY', value);
|
if ('run' in value) {
|
||||||
if ('runPythonAsync' in value) {
|
|
||||||
runtime = value;
|
runtime = value;
|
||||||
setTimeout(async () => {
|
setTimeout(() => {
|
||||||
await this.eval(this.code);
|
void (async () => {
|
||||||
this.proxy = this.proxyClass(this);
|
await this.eval(this.code);
|
||||||
console.log('proxy', this.proxy);
|
this.proxy = this.proxyClass(this);
|
||||||
this.proxy.connect();
|
this.proxy.connect();
|
||||||
this.registerWidget();
|
this.registerWidget();
|
||||||
|
})();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerWidget() {
|
registerWidget() {
|
||||||
const pyodide = runtime;
|
logger.info('new widget registered:', this.name);
|
||||||
console.log('new widget registered:', this.name);
|
runtime.globals.set(this.id, this.proxy);
|
||||||
pyodide.globals.set(this.id, this.proxy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async eval(source: string): Promise<void> {
|
async eval(source: string): Promise<void> {
|
||||||
let output;
|
|
||||||
const pyodide = runtime;
|
|
||||||
try {
|
try {
|
||||||
output = await pyodide.runPythonAsync(source);
|
const output = await runtime.run(source);
|
||||||
this.proxyClass = pyodide.globals.get(this.klass);
|
this.proxyClass = runtime.globals.get(this.klass);
|
||||||
if (output !== undefined) {
|
if (output !== undefined) {
|
||||||
console.log(output);
|
logger.info('CustomWidget.eval: ', output);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
logger.error('CustomWidget.eval: ', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,16 +297,15 @@ export class PyWidget extends HTMLElement {
|
|||||||
this.wrapper = document.createElement('slot');
|
this.wrapper = document.createElement('slot');
|
||||||
this.shadow.appendChild(this.wrapper);
|
this.shadow.appendChild(this.wrapper);
|
||||||
|
|
||||||
if (this.hasAttribute('src')) {
|
this.addAttributes('src','name','klass');
|
||||||
this.source = this.getAttribute('src');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasAttribute('name')) {
|
addAttributes(...attrs:string[]){
|
||||||
this.name = this.getAttribute('name');
|
for (const each of attrs){
|
||||||
}
|
const property = each === "src" ? "source" : each;
|
||||||
|
if (this.hasAttribute(each)) {
|
||||||
if (this.hasAttribute('klass')) {
|
this[property]=this.getAttribute(each);
|
||||||
this.klass = this.getAttribute('klass');
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +319,7 @@ export class PyWidget extends HTMLElement {
|
|||||||
const mainDiv = document.createElement('div');
|
const mainDiv = document.createElement('div');
|
||||||
mainDiv.id = this.id + '-main';
|
mainDiv.id = this.id + '-main';
|
||||||
this.appendChild(mainDiv);
|
this.appendChild(mainDiv);
|
||||||
console.log('reading source');
|
logger.debug('PyWidget: reading source', this.source);
|
||||||
this.code = await this.getSourceFromFile(this.source);
|
this.code = await this.getSourceFromFile(this.source);
|
||||||
createWidget(this.name, this.code, this.klass);
|
createWidget(this.name, this.code, this.klass);
|
||||||
}
|
}
|
||||||
@@ -365,21 +353,18 @@ export class PyWidget extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getSourceFromFile(s: string): Promise<string> {
|
async getSourceFromFile(s: string): Promise<string> {
|
||||||
const pyodide = runtime;
|
|
||||||
const response = await fetch(s);
|
const response = await fetch(s);
|
||||||
return await response.text();
|
return await response.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
async eval(source: string): Promise<void> {
|
async eval(source: string): Promise<void> {
|
||||||
let output;
|
|
||||||
const pyodide = runtime;
|
|
||||||
try {
|
try {
|
||||||
output = await pyodide.runPythonAsync(source);
|
const output = await runtime.run(source);
|
||||||
if (output !== undefined) {
|
if (output !== undefined) {
|
||||||
console.log(output);
|
logger.info('PyWidget.eval: ', output);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
logger.error('PyWidget.eval: ', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
pyscriptjs/src/components/elements.ts
Normal file
35
pyscriptjs/src/components/elements.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { PyRepl } from './pyrepl';
|
||||||
|
import { PyBox } from './pybox';
|
||||||
|
import { PyButton } from './pybutton';
|
||||||
|
import { PyTitle } from './pytitle';
|
||||||
|
import { PyInputBox } from './pyinputbox';
|
||||||
|
import { PyWidget } from './base';
|
||||||
|
|
||||||
|
/*
|
||||||
|
These were taken from main.js because some of our components call
|
||||||
|
runAfterRuntimeInitialized immediately when we are creating the custom
|
||||||
|
element, this was causing tests to fail since runAfterRuntimeInitialized
|
||||||
|
expects the runtime to have been loaded before being called.
|
||||||
|
|
||||||
|
This function is now called from within the `runtime.initialize`. Once
|
||||||
|
the runtime finished initializing, then we will create the custom elements
|
||||||
|
so they are rendered in the page and we will always have a runtime available.
|
||||||
|
|
||||||
|
Ideally, this would live under utils.js, but importing all the components in
|
||||||
|
the utils.js file was causing jest to fail with weird errors such as:
|
||||||
|
"ReferenceError: Cannot access 'BaseEvalElement' before initialization" coming
|
||||||
|
from the PyScript class.
|
||||||
|
|
||||||
|
*/
|
||||||
|
function createCustomElements() {
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
const xPyRepl = customElements.define('py-repl', PyRepl);
|
||||||
|
const xPyBox = customElements.define('py-box', PyBox);
|
||||||
|
const xPyTitle = customElements.define('py-title', PyTitle);
|
||||||
|
const xPyWidget = customElements.define('py-register-widget', PyWidget);
|
||||||
|
const xPyInputBox = customElements.define('py-inputbox', PyInputBox);
|
||||||
|
const xPyButton = customElements.define('py-button', PyButton);
|
||||||
|
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createCustomElements };
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
import { addClasses } from '../utils';
|
import { addClasses } from '../utils';
|
||||||
|
import { getLogger } from '../logger';
|
||||||
|
|
||||||
|
const logger = getLogger('py-box');
|
||||||
|
|
||||||
export class PyBox extends HTMLElement {
|
export class PyBox extends HTMLElement {
|
||||||
shadow: ShadowRoot;
|
shadow: ShadowRoot;
|
||||||
@@ -18,13 +21,12 @@ export class PyBox extends HTMLElement {
|
|||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
const mainDiv = document.createElement('div');
|
const mainDiv = document.createElement('div');
|
||||||
addClasses(mainDiv, ['flex', 'mx-8']);
|
addClasses(mainDiv, ['py-box']);
|
||||||
|
|
||||||
// Hack: for some reason when moving children, the editor box duplicates children
|
// Hack: for some reason when moving children, the editor box duplicates children
|
||||||
// meaning that we end up with 2 editors, if there's a <py-repl> inside the <py-box>
|
// meaning that we end up with 2 editors, if there's a <py-repl> inside the <py-box>
|
||||||
// so, if we have more than 2 children with the cm-editor class, we remove one of them
|
// so, if we have more than 2 children with the cm-editor class, we remove one of them
|
||||||
while (this.childNodes.length > 0) {
|
while (this.childNodes.length > 0) {
|
||||||
console.log(this.firstChild);
|
|
||||||
if (this.firstChild.nodeName == 'PY-REPL') {
|
if (this.firstChild.nodeName == 'PY-REPL') {
|
||||||
// in this case we need to remove the child and create a new one from scratch
|
// in this case we need to remove the child and create a new one from scratch
|
||||||
const replDiv = document.createElement('div');
|
const replDiv = document.createElement('div');
|
||||||
@@ -44,20 +46,23 @@ export class PyBox extends HTMLElement {
|
|||||||
|
|
||||||
// now we need to set widths
|
// now we need to set widths
|
||||||
this.widths = [];
|
this.widths = [];
|
||||||
|
|
||||||
if (this.hasAttribute('widths')) {
|
if (this.hasAttribute('widths')) {
|
||||||
for (const w of this.getAttribute('widths').split(';')) {
|
for (const w of this.getAttribute('widths').split(';')) {
|
||||||
this.widths.push(`w-${w}`);
|
if (w.includes('/')) this.widths.push(w.split('/')[0])
|
||||||
|
else this.widths.push(w)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.widths = [...this.widths, ...[`w-1/${mainDiv.childNodes.length}`]];
|
this.widths = Array<string>(mainDiv.children.length).fill('1 1 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.widths.forEach((width, index) => {
|
this.widths.forEach((width, index) => {
|
||||||
const node: ChildNode = mainDiv.childNodes[index];
|
const node: ChildNode = mainDiv.childNodes[index];
|
||||||
addClasses(node as HTMLElement, [width, 'mx-1']);
|
(<HTMLElement>node).style.flex = width;
|
||||||
|
addClasses((<HTMLElement>node), ['py-box-child']);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.appendChild(mainDiv);
|
this.appendChild(mainDiv);
|
||||||
console.log('py-box connected');
|
logger.info('py-box connected');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { BaseEvalElement } from './base';
|
import { BaseEvalElement } from './base';
|
||||||
import { addClasses, htmlDecode } from '../utils';
|
import { addClasses, htmlDecode } from '../utils';
|
||||||
|
import { getLogger } from '../logger'
|
||||||
|
|
||||||
|
const logger = getLogger('py-button');
|
||||||
|
|
||||||
|
|
||||||
export class PyButton extends BaseEvalElement {
|
export class PyButton extends BaseEvalElement {
|
||||||
shadow: ShadowRoot;
|
|
||||||
wrapper: HTMLElement;
|
|
||||||
theme: string;
|
|
||||||
widths: Array<string>;
|
widths: Array<string>;
|
||||||
label: string;
|
label: string;
|
||||||
class: Array<string>;
|
class: Array<string>;
|
||||||
@@ -13,7 +14,7 @@ export class PyButton extends BaseEvalElement {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.defaultClass = ['p-2', 'text-white', 'bg-blue-600', 'border', 'border-blue-600', 'rounded'];
|
this.defaultClass = ['py-button'];
|
||||||
|
|
||||||
if (this.hasAttribute('label')) {
|
if (this.hasAttribute('label')) {
|
||||||
this.label = this.getAttribute('label');
|
this.label = this.getAttribute('label');
|
||||||
@@ -51,7 +52,7 @@ export class PyButton extends BaseEvalElement {
|
|||||||
|
|
||||||
this.appendChild(mainDiv);
|
this.appendChild(mainDiv);
|
||||||
this.code = this.code.split('self').join(this.mount_name);
|
this.code = this.code.split('self').join(this.mount_name);
|
||||||
let registrationCode = `from pyodide import create_proxy`;
|
let registrationCode = `from pyodide.ffi import create_proxy`;
|
||||||
registrationCode += `\n${this.mount_name} = Element("${mainDiv.id}")`;
|
registrationCode += `\n${this.mount_name} = Element("${mainDiv.id}")`;
|
||||||
if (this.code.includes('def on_focus')) {
|
if (this.code.includes('def on_focus')) {
|
||||||
this.code = this.code.replace('def on_focus', `def on_focus_${this.mount_name}`);
|
this.code = this.code.replace('def on_focus', `def on_focus_${this.mount_name}`);
|
||||||
@@ -68,9 +69,9 @@ export class PyButton extends BaseEvalElement {
|
|||||||
this.runAfterRuntimeInitialized(async () => {
|
this.runAfterRuntimeInitialized(async () => {
|
||||||
await this.eval(this.code);
|
await this.eval(this.code);
|
||||||
await this.eval(registrationCode);
|
await this.eval(registrationCode);
|
||||||
console.log('registered handlers');
|
logger.debug('registered handlers');
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('py-button connected');
|
logger.debug('py-button connected');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,176 +1,86 @@
|
|||||||
import * as jsyaml from 'js-yaml';
|
|
||||||
import { BaseEvalElement } from './base';
|
import { BaseEvalElement } from './base';
|
||||||
import {
|
import { appConfig, addInitializer, runtimeLoaded } from '../stores';
|
||||||
initializers,
|
import type { AppConfig, Runtime } from '../runtime';
|
||||||
loadedEnvironments,
|
import { version } from '../runtime';
|
||||||
mode,
|
import { PyodideRuntime } from '../pyodide';
|
||||||
postInitializers,
|
import { getLogger } from '../logger';
|
||||||
pyodideLoaded,
|
import { readTextFromPath, handleFetchError, mergeConfig, validateConfig, defaultConfig } from '../utils'
|
||||||
scriptsQueue,
|
|
||||||
globalLoader,
|
|
||||||
appConfig,
|
|
||||||
Initializer,
|
|
||||||
} from '../stores';
|
|
||||||
import { loadInterpreter } from '../interpreter';
|
|
||||||
import type { PyLoader } from './pyloader';
|
|
||||||
import type { PyScript } from './pyscript';
|
|
||||||
|
|
||||||
const DEFAULT_RUNTIME = {
|
// Subscriber used to connect to the first available runtime (can be pyodide or others)
|
||||||
src: 'https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js',
|
let runtimeSpec: Runtime;
|
||||||
name: 'pyodide-default',
|
runtimeLoaded.subscribe(value => {
|
||||||
lang: 'python',
|
runtimeSpec = value;
|
||||||
};
|
|
||||||
|
|
||||||
export type Runtime = {
|
|
||||||
src: string;
|
|
||||||
name?: string;
|
|
||||||
lang?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AppConfig = {
|
|
||||||
autoclose_loader: boolean;
|
|
||||||
name?: string;
|
|
||||||
version?: string;
|
|
||||||
runtimes?: Array<Runtime>;
|
|
||||||
};
|
|
||||||
|
|
||||||
let appConfig_: AppConfig = {
|
|
||||||
autoclose_loader: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
appConfig.subscribe((value: AppConfig) => {
|
|
||||||
if (value) {
|
|
||||||
appConfig_ = value;
|
|
||||||
}
|
|
||||||
console.log('config set!');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let initializers_: Initializer[];
|
let appConfig_: AppConfig;
|
||||||
initializers.subscribe((value: Initializer[]) => {
|
appConfig.subscribe(value => {
|
||||||
initializers_ = value;
|
appConfig_ = value;
|
||||||
console.log('initializers set');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let postInitializers_: Initializer[];
|
const logger = getLogger('py-config');
|
||||||
postInitializers.subscribe((value: Initializer[]) => {
|
|
||||||
postInitializers_ = value;
|
|
||||||
console.log('post initializers set');
|
|
||||||
});
|
|
||||||
|
|
||||||
let scriptsQueue_: PyScript[];
|
/**
|
||||||
scriptsQueue.subscribe((value: PyScript[]) => {
|
* Configures general metadata about the PyScript application such
|
||||||
scriptsQueue_ = value;
|
* as a list of runtimes, name, version, closing the loader
|
||||||
console.log('post initializers set');
|
* automatically, etc.
|
||||||
});
|
*
|
||||||
|
* Also initializes the different runtimes passed. If no runtime is passed,
|
||||||
let mode_: string;
|
* the default runtime based on Pyodide is used.
|
||||||
mode.subscribe((value: string) => {
|
*/
|
||||||
mode_ = value;
|
|
||||||
console.log('post initializers set');
|
|
||||||
});
|
|
||||||
|
|
||||||
let pyodideReadyPromise;
|
|
||||||
|
|
||||||
let loader: PyLoader | undefined;
|
|
||||||
globalLoader.subscribe(value => {
|
|
||||||
loader = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
export class PyodideRuntime extends Object {
|
|
||||||
src: string;
|
|
||||||
|
|
||||||
constructor(url: string) {
|
|
||||||
super();
|
|
||||||
this.src = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
async initialize() {
|
|
||||||
loader?.log('Loading runtime...');
|
|
||||||
pyodideReadyPromise = loadInterpreter(this.src);
|
|
||||||
const pyodide = await pyodideReadyPromise;
|
|
||||||
const newEnv = {
|
|
||||||
id: 'a',
|
|
||||||
promise: pyodideReadyPromise,
|
|
||||||
runtime: pyodide,
|
|
||||||
state: 'loading',
|
|
||||||
};
|
|
||||||
pyodideLoaded.set(pyodide);
|
|
||||||
|
|
||||||
// Inject the loader into the runtime namespace
|
|
||||||
// eslint-disable-next-line
|
|
||||||
pyodide.globals.set('pyscript_loader', loader);
|
|
||||||
|
|
||||||
loader?.log('Runtime created...');
|
|
||||||
loadedEnvironments.update((value: any): any => {
|
|
||||||
value[newEnv['id']] = newEnv;
|
|
||||||
});
|
|
||||||
|
|
||||||
// now we call all initializers before we actually executed all page scripts
|
|
||||||
loader?.log('Initializing components...');
|
|
||||||
for (const initializer of initializers_) {
|
|
||||||
await initializer();
|
|
||||||
}
|
|
||||||
|
|
||||||
// now we can actually execute the page scripts if we are in play mode
|
|
||||||
loader?.log('Initializing scripts...');
|
|
||||||
if (mode_ == 'play') {
|
|
||||||
for (const script of scriptsQueue_) {
|
|
||||||
script.evaluate();
|
|
||||||
}
|
|
||||||
scriptsQueue.set([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// now we call all post initializers AFTER we actually executed all page scripts
|
|
||||||
loader?.log('Running post initializers...');
|
|
||||||
|
|
||||||
if (appConfig_ && appConfig_.autoclose_loader) {
|
|
||||||
loader?.close();
|
|
||||||
console.log('------ loader closed ------');
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
for (const initializer of postInitializers_) {
|
|
||||||
initializer();
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PyConfig extends BaseEvalElement {
|
export class PyConfig extends BaseEvalElement {
|
||||||
shadow: ShadowRoot;
|
|
||||||
wrapper: HTMLElement;
|
|
||||||
theme: string;
|
|
||||||
widths: Array<string>;
|
widths: Array<string>;
|
||||||
label: string;
|
label: string;
|
||||||
mount_name: string;
|
mount_name: string;
|
||||||
details: HTMLElement;
|
details: HTMLElement;
|
||||||
operation: HTMLElement;
|
operation: HTMLElement;
|
||||||
code: string;
|
|
||||||
values: AppConfig;
|
values: AppConfig;
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extractFromSrc(configType: string) {
|
||||||
|
if (this.hasAttribute('src'))
|
||||||
|
{
|
||||||
|
logger.info('config set from src attribute');
|
||||||
|
return validateConfig(readTextFromPath(this.getAttribute('src')), configType);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
extractFromInline(configType: string) {
|
||||||
|
if (this.innerHTML!=='')
|
||||||
|
{
|
||||||
|
this.code = this.innerHTML;
|
||||||
|
this.innerHTML = '';
|
||||||
|
logger.info('config set from inline');
|
||||||
|
return validateConfig(this.code, configType);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
injectMetadata() {
|
||||||
|
this.values.pyscript = {
|
||||||
|
"version": version,
|
||||||
|
"time": new Date().toISOString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.code = this.innerHTML;
|
const configType: string = this.hasAttribute("type") ? this.getAttribute("type") : "toml";
|
||||||
this.innerHTML = '';
|
let srcConfig = this.extractFromSrc(configType);
|
||||||
|
const inlineConfig = this.extractFromInline(configType);
|
||||||
|
// first make config from src whole if it is partial
|
||||||
|
srcConfig = mergeConfig(srcConfig, defaultConfig);
|
||||||
|
// then merge inline config and config from src
|
||||||
|
this.values = mergeConfig(inlineConfig, srcConfig);
|
||||||
|
this.injectMetadata();
|
||||||
|
|
||||||
const loadedValues = jsyaml.load(this.code);
|
|
||||||
if (loadedValues === undefined) {
|
|
||||||
this.values = {
|
|
||||||
autoclose_loader: true,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
// @ts-ignore
|
|
||||||
this.values = loadedValues;
|
|
||||||
}
|
|
||||||
if (this.values.runtimes === undefined) {
|
|
||||||
this.values.runtimes = [DEFAULT_RUNTIME];
|
|
||||||
}
|
|
||||||
appConfig.set(this.values);
|
appConfig.set(this.values);
|
||||||
console.log('config set', this.values);
|
logger.info('config set:', this.values);
|
||||||
|
|
||||||
|
addInitializer(this.loadPackages);
|
||||||
|
addInitializer(this.loadPaths);
|
||||||
this.loadRuntimes();
|
this.loadRuntimes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,14 +94,35 @@ export class PyConfig extends BaseEvalElement {
|
|||||||
this.remove();
|
this.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadPackages = async () => {
|
||||||
|
const env = appConfig_.packages;
|
||||||
|
logger.info("Loading env: ", env);
|
||||||
|
await runtimeSpec.installPackage(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPaths = async () => {
|
||||||
|
const paths = appConfig_.paths;
|
||||||
|
logger.info("Paths to load: ", paths)
|
||||||
|
for (const singleFile of paths) {
|
||||||
|
logger.info(` loading path: ${singleFile}`);
|
||||||
|
try {
|
||||||
|
await runtimeSpec.loadFromFile(singleFile);
|
||||||
|
} catch (e) {
|
||||||
|
//Should we still export full error contents to console?
|
||||||
|
handleFetchError(<Error>e, singleFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("All paths loaded");
|
||||||
|
}
|
||||||
|
|
||||||
loadRuntimes() {
|
loadRuntimes() {
|
||||||
console.log('Initializing runtimes...');
|
logger.info('Initializing runtimes');
|
||||||
for (const runtime of this.values.runtimes) {
|
for (const runtime of this.values.runtimes) {
|
||||||
|
const runtimeObj: Runtime = new PyodideRuntime(runtime.src, runtime.name, runtime.lang);
|
||||||
const script = document.createElement('script'); // create a script DOM node
|
const script = document.createElement('script'); // create a script DOM node
|
||||||
const runtimeSpec = new PyodideRuntime(runtime.src);
|
script.src = runtimeObj.src; // set its src to the provided URL
|
||||||
script.src = runtime.src; // set its src to the provided URL
|
|
||||||
script.addEventListener('load', () => {
|
script.addEventListener('load', () => {
|
||||||
void runtimeSpec.initialize();
|
void runtimeObj.initialize();
|
||||||
});
|
});
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import * as jsyaml from 'js-yaml';
|
import * as jsyaml from 'js-yaml';
|
||||||
|
|
||||||
import { pyodideLoaded, addInitializer } from '../stores';
|
import { runtimeLoaded, addInitializer } from '../stores';
|
||||||
import { loadPackage, loadFromFile } from '../interpreter';
|
|
||||||
import { handleFetchError } from '../utils';
|
import { handleFetchError } from '../utils';
|
||||||
|
import type { Runtime } from '../runtime';
|
||||||
|
import { getLogger } from '../logger';
|
||||||
|
|
||||||
// Premise used to connect to the first available pyodide interpreter
|
const logger = getLogger('py-env');
|
||||||
let pyodideReadyPromise;
|
|
||||||
let runtime;
|
|
||||||
|
|
||||||
pyodideLoaded.subscribe(value => {
|
// Premise used to connect to the first available runtime (can be pyodide or others)
|
||||||
|
let runtime: Runtime;
|
||||||
|
|
||||||
|
runtimeLoaded.subscribe(value => {
|
||||||
runtime = value;
|
runtime = value;
|
||||||
console.log('RUNTIME READY');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export class PyEnv extends HTMLElement {
|
export class PyEnv extends HTMLElement {
|
||||||
@@ -18,7 +19,7 @@ export class PyEnv extends HTMLElement {
|
|||||||
wrapper: HTMLElement;
|
wrapper: HTMLElement;
|
||||||
code: string;
|
code: string;
|
||||||
environment: unknown;
|
environment: unknown;
|
||||||
runtime: any;
|
runtime: Runtime;
|
||||||
env: string[];
|
env: string[];
|
||||||
paths: string[];
|
paths: string[];
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ export class PyEnv extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
logger.info("The <py-env> tag is deprecated, please use <py-config> instead.")
|
||||||
this.code = this.innerHTML;
|
this.code = this.innerHTML;
|
||||||
this.innerHTML = '';
|
this.innerHTML = '';
|
||||||
|
|
||||||
@@ -56,25 +58,25 @@ export class PyEnv extends HTMLElement {
|
|||||||
this.paths = paths;
|
this.paths = paths;
|
||||||
|
|
||||||
async function loadEnv() {
|
async function loadEnv() {
|
||||||
await loadPackage(env, runtime);
|
logger.info("Loading env: ", env);
|
||||||
console.log('environment loaded');
|
await runtime.installPackage(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadPaths() {
|
async function loadPaths() {
|
||||||
|
logger.info("Paths to load: ", paths)
|
||||||
for (const singleFile of paths) {
|
for (const singleFile of paths) {
|
||||||
console.log(`loading ${singleFile}`);
|
logger.info(` loading path: ${singleFile}`);
|
||||||
try {
|
try {
|
||||||
await loadFromFile(singleFile, runtime);
|
await runtime.loadFromFile(singleFile);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//Should we still export full error contents to console?
|
//Should we still export full error contents to console?
|
||||||
handleFetchError(e, singleFile);
|
handleFetchError(<Error>e, singleFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('paths loaded');
|
logger.info("All paths loaded");
|
||||||
}
|
}
|
||||||
|
|
||||||
addInitializer(loadEnv);
|
addInitializer(loadEnv);
|
||||||
addInitializer(loadPaths);
|
addInitializer(loadPaths);
|
||||||
console.log('environment loading...', this.env);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { BaseEvalElement } from './base';
|
import { BaseEvalElement } from './base';
|
||||||
import { addClasses, htmlDecode } from '../utils';
|
import { addClasses, htmlDecode } from '../utils';
|
||||||
|
import { getLogger } from '../logger'
|
||||||
|
|
||||||
|
const logger = getLogger('py-inputbox');
|
||||||
|
|
||||||
export class PyInputBox extends BaseEvalElement {
|
export class PyInputBox extends BaseEvalElement {
|
||||||
shadow: ShadowRoot;
|
|
||||||
wrapper: HTMLElement;
|
|
||||||
theme: string;
|
|
||||||
widths: Array<string>;
|
widths: Array<string>;
|
||||||
label: string;
|
label: string;
|
||||||
mount_name: string;
|
mount_name: string;
|
||||||
@@ -24,7 +24,7 @@ export class PyInputBox extends BaseEvalElement {
|
|||||||
|
|
||||||
const mainDiv = document.createElement('input');
|
const mainDiv = document.createElement('input');
|
||||||
mainDiv.type = 'text';
|
mainDiv.type = 'text';
|
||||||
addClasses(mainDiv, ['border', 'flex-1', 'w-full', 'mr-3', 'border-gray-300', 'p-2', 'rounded']);
|
addClasses(mainDiv, ['py-input']);
|
||||||
|
|
||||||
mainDiv.id = this.id;
|
mainDiv.id = this.id;
|
||||||
this.id = `${this.id}-container`;
|
this.id = `${this.id}-container`;
|
||||||
@@ -34,7 +34,7 @@ export class PyInputBox extends BaseEvalElement {
|
|||||||
// defined for this widget
|
// defined for this widget
|
||||||
this.appendChild(mainDiv);
|
this.appendChild(mainDiv);
|
||||||
this.code = this.code.split('self').join(this.mount_name);
|
this.code = this.code.split('self').join(this.mount_name);
|
||||||
let registrationCode = `from pyodide import create_proxy`;
|
let registrationCode = `from pyodide.ffi import create_proxy`;
|
||||||
registrationCode += `\n${this.mount_name} = Element("${mainDiv.id}")`;
|
registrationCode += `\n${this.mount_name} = Element("${mainDiv.id}")`;
|
||||||
if (this.code.includes('def on_keypress')) {
|
if (this.code.includes('def on_keypress')) {
|
||||||
this.code = this.code.replace('def on_keypress', `def on_keypress_${this.mount_name}`);
|
this.code = this.code.replace('def on_keypress', `def on_keypress_${this.mount_name}`);
|
||||||
@@ -46,7 +46,7 @@ export class PyInputBox extends BaseEvalElement {
|
|||||||
this.runAfterRuntimeInitialized(async () => {
|
this.runAfterRuntimeInitialized(async () => {
|
||||||
await this.eval(this.code);
|
await this.eval(this.code);
|
||||||
await this.eval(registrationCode);
|
await this.eval(registrationCode);
|
||||||
console.log('registered handlers');
|
logger.debug('registered handlers');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { BaseEvalElement } from './base';
|
import { BaseEvalElement } from './base';
|
||||||
|
import { getLogger } from '../logger';
|
||||||
|
|
||||||
|
const logger = getLogger('py-loader');
|
||||||
|
|
||||||
export class PyLoader extends BaseEvalElement {
|
export class PyLoader extends BaseEvalElement {
|
||||||
shadow: ShadowRoot;
|
|
||||||
wrapper: HTMLElement;
|
|
||||||
theme: string;
|
|
||||||
widths: Array<string>;
|
widths: Array<string>;
|
||||||
label: string;
|
label: string;
|
||||||
mount_name: string;
|
mount_name: string;
|
||||||
@@ -14,12 +14,14 @@ export class PyLoader extends BaseEvalElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.innerHTML = `<div id="pyscript_loading_splash" class="fixed top-0 left-0 right-0 bottom-0 w-full h-screen z-50 overflow-hidden bg-gray-600 opacity-75 flex flex-col items-center justify-center">
|
this.innerHTML = `<div id="pyscript_loading_splash" class="py-overlay">
|
||||||
|
<div class="py-pop-up">
|
||||||
<div class="smooth spinner"></div>
|
<div class="smooth spinner"></div>
|
||||||
<div id="pyscript-loading-label" class="label">
|
<div id="pyscript-loading-label" class="label">
|
||||||
<div id="pyscript-operation-details">
|
<div id="pyscript-operation-details">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
this.mount_name = this.id.split('-').join('_');
|
this.mount_name = this.id.split('-').join('_');
|
||||||
this.operation = document.getElementById('pyscript-operation');
|
this.operation = document.getElementById('pyscript-operation');
|
||||||
@@ -27,12 +29,15 @@ export class PyLoader extends BaseEvalElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log(msg: string) {
|
log(msg: string) {
|
||||||
|
// loader messages are showed both in the HTML and in the console
|
||||||
|
logger.info(msg);
|
||||||
const newLog = document.createElement('p');
|
const newLog = document.createElement('p');
|
||||||
newLog.innerText = msg;
|
newLog.innerText = msg;
|
||||||
this.details.appendChild(newLog);
|
this.details.appendChild(newLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
|
logger.info('Closing');
|
||||||
this.remove();
|
this.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,33 +4,12 @@ import { Compartment, StateCommand } from '@codemirror/state';
|
|||||||
import { keymap } from '@codemirror/view';
|
import { keymap } from '@codemirror/view';
|
||||||
import { defaultKeymap } from '@codemirror/commands';
|
import { defaultKeymap } from '@codemirror/commands';
|
||||||
import { oneDarkTheme } from '@codemirror/theme-one-dark';
|
import { oneDarkTheme } from '@codemirror/theme-one-dark';
|
||||||
|
|
||||||
import { componentDetailsNavOpen, loadedEnvironments, mode, pyodideLoaded } from '../stores';
|
|
||||||
import { addClasses, htmlDecode } from '../utils';
|
import { addClasses, htmlDecode } from '../utils';
|
||||||
import { BaseEvalElement } from './base';
|
import { BaseEvalElement } from './base';
|
||||||
|
import { getLogger } from '../logger';
|
||||||
|
|
||||||
// Premise used to connect to the first available pyodide interpreter
|
const logger = getLogger('py-repl');
|
||||||
|
|
||||||
let pyodideReadyPromise;
|
|
||||||
let environments;
|
|
||||||
let currentMode;
|
|
||||||
|
|
||||||
pyodideLoaded.subscribe(value => {
|
|
||||||
pyodideReadyPromise = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
loadedEnvironments.subscribe(value => {
|
|
||||||
environments = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
let propertiesNavOpen;
|
|
||||||
componentDetailsNavOpen.subscribe(value => {
|
|
||||||
propertiesNavOpen = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
mode.subscribe(value => {
|
|
||||||
currentMode = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
function createCmdHandler(el: PyRepl): StateCommand {
|
function createCmdHandler(el: PyRepl): StateCommand {
|
||||||
// Creates a codemirror cmd handler that calls the el.evaluate when an event
|
// Creates a codemirror cmd handler that calls the el.evaluate when an event
|
||||||
@@ -55,7 +34,7 @@ export class PyRepl extends BaseEvalElement {
|
|||||||
|
|
||||||
// add an extra div where we can attach the codemirror editor
|
// add an extra div where we can attach the codemirror editor
|
||||||
this.editorNode = document.createElement('div');
|
this.editorNode = document.createElement('div');
|
||||||
addClasses(this.editorNode, ['editor-box', 'border', 'border-gray-300', 'group', 'relative']);
|
addClasses(this.editorNode, ['editor-box']);
|
||||||
this.shadow.appendChild(this.wrapper);
|
this.shadow.appendChild(this.wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +67,7 @@ export class PyRepl extends BaseEvalElement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mainDiv = document.createElement('div');
|
const mainDiv = document.createElement('div');
|
||||||
addClasses(mainDiv, ['parentBox', 'flex', 'flex-col', 'mt-2', 'mx-8', 'relative']);
|
addClasses(mainDiv, ['py-repl-box']);
|
||||||
|
|
||||||
// Styles that we use to hide the labels whilst also keeping it accessible for screen readers
|
// Styles that we use to hide the labels whilst also keeping it accessible for screen readers
|
||||||
const labelStyle = 'overflow:hidden; display:block; width:1px; height:1px';
|
const labelStyle = 'overflow:hidden; display:block; width:1px; height:1px';
|
||||||
@@ -110,7 +89,7 @@ export class PyRepl extends BaseEvalElement {
|
|||||||
this.btnRun.id = 'btnRun';
|
this.btnRun.id = 'btnRun';
|
||||||
this.btnRun.innerHTML =
|
this.btnRun.innerHTML =
|
||||||
'<svg id="" class="svelte-fa svelte-ps5qeg" style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>';
|
'<svg id="" class="svelte-fa svelte-ps5qeg" style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>';
|
||||||
addClasses(this.btnRun, ['absolute', 'right-1', 'bottom-1', 'opacity-0', 'group-hover:opacity-100']);
|
addClasses(this.btnRun, ['absolute', 'repl-play-button']);
|
||||||
|
|
||||||
// Play Button Label
|
// Play Button Label
|
||||||
const btnLabel = document.createElement('label');
|
const btnLabel = document.createElement('label');
|
||||||
@@ -126,8 +105,8 @@ export class PyRepl extends BaseEvalElement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
console.log(
|
logger.warn(
|
||||||
"WARNING: <pyrepl> define with an id. <pyrepl> should always have an id. More than one <pyrepl> on a page won't work otherwise!",
|
"WARNING: <py-repl> defined without an id. <py-repl> should always have an id, otherwise multiple <py-repl> in the same page will not work!"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +127,7 @@ export class PyRepl extends BaseEvalElement {
|
|||||||
// In this case neither output or std-out have been provided so we need
|
// In this case neither output or std-out have been provided so we need
|
||||||
// to create a new output div to output to
|
// to create a new output div to output to
|
||||||
this.outputElement = document.createElement('div');
|
this.outputElement = document.createElement('div');
|
||||||
this.outputElement.classList.add('output', 'font-mono', 'ml-8', 'text-sm');
|
this.outputElement.classList.add('output');
|
||||||
this.outputElement.hidden = true;
|
this.outputElement.hidden = true;
|
||||||
this.outputElement.id = this.id + '-' + this.getAttribute('exec-id');
|
this.outputElement.id = this.id + '-' + this.getAttribute('exec-id');
|
||||||
|
|
||||||
@@ -163,7 +142,7 @@ export class PyRepl extends BaseEvalElement {
|
|||||||
|
|
||||||
this.appendChild(mainDiv);
|
this.appendChild(mainDiv);
|
||||||
this.editor.focus();
|
this.editor.focus();
|
||||||
console.log('connected');
|
logger.debug(`element ${this.id} successfully connected`);
|
||||||
}
|
}
|
||||||
|
|
||||||
addToOutput(s: string): void {
|
addToOutput(s: string): void {
|
||||||
@@ -217,15 +196,6 @@ export class PyRepl extends BaseEvalElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSourceFromElement(): string {
|
getSourceFromElement(): string {
|
||||||
const sourceStrings = [
|
return this.editor.state.doc.toString();
|
||||||
`output_manager.change(out="${this.outputElement.id}", append=True)`,
|
|
||||||
...this.editor.state.doc.toString().split('\n'),
|
|
||||||
];
|
|
||||||
|
|
||||||
return sourceStrings.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
console.log('rendered');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,33 +3,28 @@ import {
|
|||||||
addPostInitializer,
|
addPostInitializer,
|
||||||
addToScriptsQueue,
|
addToScriptsQueue,
|
||||||
loadedEnvironments,
|
loadedEnvironments,
|
||||||
mode,
|
runtimeLoaded,
|
||||||
pyodideLoaded,
|
type Environment,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
|
|
||||||
import { addClasses, htmlDecode } from '../utils';
|
import { addClasses, htmlDecode } from '../utils';
|
||||||
import { BaseEvalElement } from './base';
|
import { BaseEvalElement } from './base';
|
||||||
|
import type { Runtime } from '../runtime';
|
||||||
|
import { getLogger } from '../logger';
|
||||||
|
|
||||||
// Premise used to connect to the first available pyodide interpreter
|
const logger = getLogger('py-script');
|
||||||
let pyodideReadyPromise;
|
|
||||||
let environments;
|
|
||||||
let currentMode;
|
|
||||||
|
|
||||||
pyodideLoaded.subscribe(value => {
|
// Premise used to connect to the first available runtime (can be pyodide or others)
|
||||||
pyodideReadyPromise = value;
|
let runtime: Runtime;
|
||||||
|
let environments: Record<Environment['id'], Environment> = {};
|
||||||
|
|
||||||
|
runtimeLoaded.subscribe(value => {
|
||||||
|
runtime = value;
|
||||||
});
|
});
|
||||||
loadedEnvironments.subscribe(value => {
|
loadedEnvironments.subscribe(value => {
|
||||||
environments = value;
|
environments = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
mode.subscribe(value => {
|
|
||||||
currentMode = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: use type declaractions
|
|
||||||
type PyodideInterface = {
|
|
||||||
registerJsModule(name: string, module: object): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class PyScript extends BaseEvalElement {
|
export class PyScript extends BaseEvalElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -44,7 +39,7 @@ export class PyScript extends BaseEvalElement {
|
|||||||
this.innerHTML = '';
|
this.innerHTML = '';
|
||||||
|
|
||||||
const mainDiv = document.createElement('div');
|
const mainDiv = document.createElement('div');
|
||||||
addClasses(mainDiv, ['parentBox', 'flex', 'flex-col', 'mx-8']);
|
addClasses(mainDiv, ['output']);
|
||||||
// add Editor to main PyScript div
|
// add Editor to main PyScript div
|
||||||
|
|
||||||
if (this.hasAttribute('output')) {
|
if (this.hasAttribute('output')) {
|
||||||
@@ -77,22 +72,15 @@ export class PyScript extends BaseEvalElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentMode == 'edit') {
|
this.appendChild(mainDiv);
|
||||||
// TODO: We need to build a plan for this
|
addToScriptsQueue(this);
|
||||||
this.appendChild(mainDiv);
|
|
||||||
} else {
|
|
||||||
this.appendChild(mainDiv);
|
|
||||||
addToScriptsQueue(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('connected');
|
|
||||||
|
|
||||||
if (this.hasAttribute('src')) {
|
if (this.hasAttribute('src')) {
|
||||||
this.source = this.getAttribute('src');
|
this.source = this.getAttribute('src');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async _register_esm(pyodide: PyodideInterface): Promise<void> {
|
protected async _register_esm(runtime: Runtime): Promise<void> {
|
||||||
for (const node of document.querySelectorAll("script[type='importmap']")) {
|
for (const node of document.querySelectorAll("script[type='importmap']")) {
|
||||||
const importmap = (() => {
|
const importmap = (() => {
|
||||||
try {
|
try {
|
||||||
@@ -113,11 +101,11 @@ export class PyScript extends BaseEvalElement {
|
|||||||
// "can't read 'name' of undefined" at import time
|
// "can't read 'name' of undefined" at import time
|
||||||
exports = { ...(await import(url)) };
|
exports = { ...(await import(url)) };
|
||||||
} catch {
|
} catch {
|
||||||
console.warn(`failed to fetch '${url}' for '${name}'`);
|
logger.warn(`failed to fetch '${url}' for '${name}'`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
pyodide.registerJsModule(name, exports);
|
runtime.registerJsModule(name, exports);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,36 +115,132 @@ export class PyScript extends BaseEvalElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Defines all possible pys-on* and their corresponding event types */
|
/** Defines all possible py-on* and their corresponding event types */
|
||||||
const pysAttributeToEvent: Map<string, string> = new Map<string, string>([
|
const pyAttributeToEvent: Map<string, string> = new Map<string, string>([
|
||||||
|
// Leaving pys-onClick and pys-onKeyDown for backward compatibility
|
||||||
["pys-onClick", "click"],
|
["pys-onClick", "click"],
|
||||||
["pys-onKeyDown", "keydown"]
|
["pys-onKeyDown", "keydown"],
|
||||||
]);
|
["py-onClick", "click"],
|
||||||
|
["py-onKeyDown", "keydown"],
|
||||||
|
// Window Events
|
||||||
|
["py-afterprint", "afterprint"],
|
||||||
|
["py-beforeprint", "beforeprint"],
|
||||||
|
["py-beforeunload", "beforeunload"],
|
||||||
|
["py-error", "error"],
|
||||||
|
["py-hashchange", "hashchange"],
|
||||||
|
["py-load", "load"],
|
||||||
|
["py-message", "message"],
|
||||||
|
["py-offline", "offline"],
|
||||||
|
["py-online", "online"],
|
||||||
|
["py-pagehide", "pagehide"],
|
||||||
|
["py-pageshow", "pageshow"],
|
||||||
|
["py-popstate", "popstate"],
|
||||||
|
["py-resize", "resize"],
|
||||||
|
["py-storage", "storage"],
|
||||||
|
["py-unload", "unload"],
|
||||||
|
|
||||||
/** Initialize all elements with pys-on* handlers attributes */
|
// Form Events
|
||||||
|
["py-blur", "blur"],
|
||||||
|
["py-change", "change"],
|
||||||
|
["py-contextmenu", "contextmenu"],
|
||||||
|
["py-focus", "focus"],
|
||||||
|
["py-input", "input"],
|
||||||
|
["py-invalid", "invalid"],
|
||||||
|
["py-reset", "reset"],
|
||||||
|
["py-search", "search"],
|
||||||
|
["py-select", "select"],
|
||||||
|
["py-submit", "submit"],
|
||||||
|
|
||||||
|
// Keyboard Events
|
||||||
|
["py-keydown", "keydown"],
|
||||||
|
["py-keypress", "keypress"],
|
||||||
|
["py-keyup", "keyup"],
|
||||||
|
|
||||||
|
// Mouse Events
|
||||||
|
["py-click", "click"],
|
||||||
|
["py-dblclick", "dblclick"],
|
||||||
|
["py-mousedown", "mousedown"],
|
||||||
|
["py-mousemove", "mousemove"],
|
||||||
|
["py-mouseout", "mouseout"],
|
||||||
|
["py-mouseover", "mouseover"],
|
||||||
|
["py-mouseup", "mouseup"],
|
||||||
|
["py-mousewheel", "mousewheel"],
|
||||||
|
["py-wheel", "wheel"],
|
||||||
|
|
||||||
|
// Drag Events
|
||||||
|
["py-drag", "drag"],
|
||||||
|
["py-dragend", "dragend"],
|
||||||
|
["py-dragenter", "dragenter"],
|
||||||
|
["py-dragleave", "dragleave"],
|
||||||
|
["py-dragover", "dragover"],
|
||||||
|
["py-dragstart", "dragstart"],
|
||||||
|
["py-drop", "drop"],
|
||||||
|
["py-scroll", "scroll"],
|
||||||
|
|
||||||
|
// Clipboard Events
|
||||||
|
["py-copy", "copy"],
|
||||||
|
["py-cut", "cut"],
|
||||||
|
["py-paste", "paste"],
|
||||||
|
|
||||||
|
// Media Events
|
||||||
|
["py-abort", "abort"],
|
||||||
|
["py-canplay", "canplay"],
|
||||||
|
["py-canplaythrough", "canplaythrough"],
|
||||||
|
["py-cuechange", "cuechange"],
|
||||||
|
["py-durationchange", "durationchange"],
|
||||||
|
["py-emptied", "emptied"],
|
||||||
|
["py-ended", "ended"],
|
||||||
|
["py-loadeddata", "loadeddata"],
|
||||||
|
["py-loadedmetadata", "loadedmetadata"],
|
||||||
|
["py-loadstart", "loadstart"],
|
||||||
|
["py-pause", "pause"],
|
||||||
|
["py-play", "play"],
|
||||||
|
["py-playing", "playing"],
|
||||||
|
["py-progress", "progress"],
|
||||||
|
["py-ratechange", "ratechange"],
|
||||||
|
["py-seeked", "seeked"],
|
||||||
|
["py-seeking", "seeking"],
|
||||||
|
["py-stalled", "stalled"],
|
||||||
|
["py-suspend", "suspend"],
|
||||||
|
["py-timeupdate", "timeupdate"],
|
||||||
|
["py-volumechange", "volumechange"],
|
||||||
|
["py-waiting", "waiting"],
|
||||||
|
|
||||||
|
// Misc Events
|
||||||
|
["py-toggle", "toggle"],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** Initialize all elements with py-* handlers attributes */
|
||||||
async function initHandlers() {
|
async function initHandlers() {
|
||||||
console.log('Collecting nodes...');
|
logger.debug('Initializing py-* event handlers...');
|
||||||
const pyodide = await pyodideReadyPromise;
|
for (const pyAttribute of pyAttributeToEvent.keys()) {
|
||||||
for (const pysAttribute of pysAttributeToEvent.keys()) {
|
await createElementsWithEventListeners(runtime, pyAttribute);
|
||||||
await createElementsWithEventListeners(pyodide, pysAttribute);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Initializes an element with the given pys-on* attribute and its handler */
|
/** Initializes an element with the given py-on* attribute and its handler */
|
||||||
async function createElementsWithEventListeners(pyodide: any, pysAttribute: string) {
|
async function createElementsWithEventListeners(runtime: Runtime, pyAttribute: string): Promise<void> {
|
||||||
const matches: NodeListOf<HTMLElement> = document.querySelectorAll(`[${pysAttribute}]`);
|
const matches: NodeListOf<HTMLElement> = document.querySelectorAll(`[${pyAttribute}]`);
|
||||||
for (const el of matches) {
|
for (const el of matches) {
|
||||||
if (el.id.length === 0) {
|
if (el.id.length === 0) {
|
||||||
throw new TypeError(`<${el.tagName.toLowerCase()}> must have an id attribute, when using the ${pysAttribute} attribute`)
|
throw new TypeError(`<${el.tagName.toLowerCase()}> must have an id attribute, when using the ${pyAttribute} attribute`)
|
||||||
}
|
}
|
||||||
const handlerCode = el.getAttribute(pysAttribute);
|
const handlerCode = el.getAttribute(pyAttribute);
|
||||||
const event = pysAttributeToEvent.get(pysAttribute);
|
const event = pyAttributeToEvent.get(pyAttribute);
|
||||||
const source = `
|
|
||||||
from pyodide import create_proxy
|
|
||||||
Element("${el.id}").element.addEventListener("${event}", create_proxy(${handlerCode}))
|
|
||||||
`;
|
|
||||||
await pyodide.runPythonAsync(source);
|
|
||||||
|
|
||||||
|
if (pyAttribute === 'pys-onClick' || pyAttribute === 'pys-onKeyDown'){
|
||||||
|
console.warn("Use of pys-onClick and pys-onKeyDown attributes is deprecated in favor of py-onClick() and py-onKeyDown(). pys-on* attributes will be deprecated in a future version of PyScript.")
|
||||||
|
const source = `
|
||||||
|
from pyodide.ffi import create_proxy
|
||||||
|
Element("${el.id}").element.addEventListener("${event}", create_proxy(${handlerCode}))
|
||||||
|
`;
|
||||||
|
await runtime.run(source);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
el.addEventListener(event, () => {
|
||||||
|
(async() => {await runtime.run(handlerCode)})();
|
||||||
|
});
|
||||||
|
}
|
||||||
// TODO: Should we actually map handlers in JS instead of Python?
|
// TODO: Should we actually map handlers in JS instead of Python?
|
||||||
// el.onclick = (evt: any) => {
|
// el.onclick = (evt: any) => {
|
||||||
// console.log("click");
|
// console.log("click");
|
||||||
@@ -167,7 +251,7 @@ async function createElementsWithEventListeners(pyodide: any, pysAttribute: stri
|
|||||||
// }).then(() => {
|
// }).then(() => {
|
||||||
// console.log("resolved")
|
// console.log("resolved")
|
||||||
// });
|
// });
|
||||||
// // let handlerCode = el.getAttribute('pys-onClick');
|
// // let handlerCode = el.getAttribute('py-onClick');
|
||||||
// // pyodide.runPython(handlerCode);
|
// // pyodide.runPython(handlerCode);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
@@ -176,16 +260,15 @@ async function createElementsWithEventListeners(pyodide: any, pysAttribute: stri
|
|||||||
|
|
||||||
/** Mount all elements with attribute py-mount into the Python namespace */
|
/** Mount all elements with attribute py-mount into the Python namespace */
|
||||||
async function mountElements() {
|
async function mountElements() {
|
||||||
console.log('Collecting nodes to be mounted into python namespace...');
|
|
||||||
const pyodide = await pyodideReadyPromise;
|
|
||||||
const matches: NodeListOf<HTMLElement> = document.querySelectorAll('[py-mount]');
|
const matches: NodeListOf<HTMLElement> = document.querySelectorAll('[py-mount]');
|
||||||
|
logger.info(`py-mount: found ${matches.length} elements`);
|
||||||
|
|
||||||
let source = '';
|
let source = '';
|
||||||
for (const el of matches) {
|
for (const el of matches) {
|
||||||
const mountName = el.getAttribute('py-mount') || el.id.split('-').join('_');
|
const mountName = el.getAttribute('py-mount') || el.id.split('-').join('_');
|
||||||
source += `\n${mountName} = Element("${el.id}")`;
|
source += `\n${mountName} = Element("${el.id}")`;
|
||||||
}
|
}
|
||||||
await pyodide.runPythonAsync(source);
|
await runtime.run(source);
|
||||||
}
|
}
|
||||||
addInitializer(mountElements);
|
addInitializer(mountElements);
|
||||||
addPostInitializer(initHandlers);
|
addPostInitializer(initHandlers);
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ import { BaseEvalElement } from './base';
|
|||||||
import { addClasses, htmlDecode } from '../utils';
|
import { addClasses, htmlDecode } from '../utils';
|
||||||
|
|
||||||
export class PyTitle extends BaseEvalElement {
|
export class PyTitle extends BaseEvalElement {
|
||||||
shadow: ShadowRoot;
|
|
||||||
wrapper: HTMLElement;
|
|
||||||
theme: string;
|
|
||||||
widths: Array<string>;
|
widths: Array<string>;
|
||||||
label: string;
|
label: string;
|
||||||
mount_name: string;
|
mount_name: string;
|
||||||
@@ -20,8 +17,7 @@ export class PyTitle extends BaseEvalElement {
|
|||||||
const mainDiv = document.createElement('div');
|
const mainDiv = document.createElement('div');
|
||||||
const divContent = document.createElement('h1');
|
const divContent = document.createElement('h1');
|
||||||
|
|
||||||
addClasses(mainDiv, ['text-center', 'w-full', 'mb-8']);
|
addClasses(mainDiv, ['py-title']);
|
||||||
addClasses(divContent, ['text-3xl', 'font-bold', 'text-gray-800', 'uppercase', 'tracking-tight']);
|
|
||||||
divContent.innerHTML = this.label;
|
divContent.innerHTML = this.label;
|
||||||
|
|
||||||
mainDiv.id = this.id;
|
mainDiv.id = this.id;
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import { getLastPath } from './utils';
|
|
||||||
// eslint-disable-next-line
|
|
||||||
// @ts-ignore
|
|
||||||
import pyscript from './pyscript.py';
|
|
||||||
|
|
||||||
let pyodideReadyPromise;
|
|
||||||
let pyodide;
|
|
||||||
|
|
||||||
const loadInterpreter = async function (indexUrl: string): Promise<any> {
|
|
||||||
console.log('creating pyodide runtime');
|
|
||||||
// eslint-disable-next-line
|
|
||||||
// @ts-ignore
|
|
||||||
pyodide = await loadPyodide({
|
|
||||||
// indexURL: indexUrl,
|
|
||||||
stdout: console.log,
|
|
||||||
stderr: console.log,
|
|
||||||
fullStdLib: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// now that we loaded, add additional convenience functions
|
|
||||||
console.log('loading micropip');
|
|
||||||
await pyodide.loadPackage('micropip');
|
|
||||||
|
|
||||||
console.log('loading pyscript...');
|
|
||||||
await pyodide.runPythonAsync(pyscript);
|
|
||||||
|
|
||||||
console.log('done setting up environment');
|
|
||||||
return pyodide;
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadPackage = async function (package_name: string[] | string, runtime: any): Promise<any> {
|
|
||||||
if (package_name.length > 0){
|
|
||||||
const micropip = pyodide.globals.get('micropip');
|
|
||||||
await micropip.install(package_name);
|
|
||||||
micropip.destroy();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadFromFile = async function (s: string, runtime: any): Promise<any> {
|
|
||||||
const filename = getLastPath(s);
|
|
||||||
await runtime.runPythonAsync(
|
|
||||||
`
|
|
||||||
from pyodide.http import pyfetch
|
|
||||||
from js import console
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = await pyfetch("` +
|
|
||||||
s +
|
|
||||||
`")
|
|
||||||
except Exception as err:
|
|
||||||
console.warn("PyScript: Access to local files (using 'Paths:' in py-env) is not available when directly opening a HTML file; you must use a webserver to serve the additional files. See https://github.com/pyscript/pyscript/issues/257#issuecomment-1119595062 on starting a simple webserver with Python.")
|
|
||||||
raise(err)
|
|
||||||
content = await response.bytes()
|
|
||||||
with open("` +
|
|
||||||
filename +
|
|
||||||
`", "wb") as f:
|
|
||||||
f.write(content)
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { loadInterpreter, pyodideReadyPromise, loadPackage, loadFromFile };
|
|
||||||
64
pyscriptjs/src/logger.ts
Normal file
64
pyscriptjs/src/logger.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/* Very simple logger interface.
|
||||||
|
|
||||||
|
Each module is expected to create its own logger by doing e.g.:
|
||||||
|
|
||||||
|
const logger = getLogger('my-prefix');
|
||||||
|
|
||||||
|
and then use it instead of console:
|
||||||
|
|
||||||
|
logger.info('hello', 'world');
|
||||||
|
logger.warn('...');
|
||||||
|
logger.error('...');
|
||||||
|
|
||||||
|
The logger automatically adds the prefix "[my-prefix]" to all logs; so e.g., the
|
||||||
|
above call would print:
|
||||||
|
|
||||||
|
[my-prefix] hello world
|
||||||
|
|
||||||
|
logger.log is intentionally omitted. The idea is that PyScript should not
|
||||||
|
write anything to console.log, to leave it free for the user.
|
||||||
|
|
||||||
|
Currently, the logger does not to anything more than that. In the future,
|
||||||
|
we might want to add additional features such as the ability to
|
||||||
|
enable/disable logs on a global or per-module basis.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface Logger {
|
||||||
|
debug(message: string, ...args: any[]): void;
|
||||||
|
info(message: string, ...args: any[]): void;
|
||||||
|
warn(message: string, ...args: any[]): void;
|
||||||
|
error(message: string, ...args: any[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _cache = new Map<string, Logger>();
|
||||||
|
|
||||||
|
function getLogger(prefix: string): Logger {
|
||||||
|
let logger = _cache.get(prefix);
|
||||||
|
if (logger === undefined) {
|
||||||
|
logger = _makeLogger(prefix);
|
||||||
|
_cache.set(prefix, logger);
|
||||||
|
}
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _makeLogger(prefix: string): Logger {
|
||||||
|
prefix = "[" + prefix + "] ";
|
||||||
|
|
||||||
|
function make(level: string) {
|
||||||
|
const out_fn = console[level].bind(console);
|
||||||
|
function fn(fmt: string, ...args: any[]) {
|
||||||
|
out_fn(prefix + fmt, ...args);
|
||||||
|
}
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'log' is intentionally omitted
|
||||||
|
const debug = make('debug');
|
||||||
|
const info = make('info');
|
||||||
|
const warn = make('warn');
|
||||||
|
const error = make('error');
|
||||||
|
|
||||||
|
return {debug, info, warn, error};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getLogger };
|
||||||
@@ -1,29 +1,23 @@
|
|||||||
import App from './App.svelte';
|
import './styles/pyscript_base.css';
|
||||||
|
|
||||||
import { PyScript } from './components/pyscript';
|
import { PyScript } from './components/pyscript';
|
||||||
import { PyRepl } from './components/pyrepl';
|
|
||||||
import { PyEnv } from './components/pyenv';
|
import { PyEnv } from './components/pyenv';
|
||||||
import { PyBox } from './components/pybox';
|
|
||||||
import { PyButton } from './components/pybutton';
|
|
||||||
import { PyTitle } from './components/pytitle';
|
|
||||||
import { PyInputBox } from './components/pyinputbox';
|
|
||||||
import { PyWidget } from './components/base';
|
|
||||||
import { PyLoader } from './components/pyloader';
|
import { PyLoader } from './components/pyloader';
|
||||||
import { globalLoader } from './stores';
|
|
||||||
import { PyConfig } from './components/pyconfig';
|
import { PyConfig } from './components/pyconfig';
|
||||||
|
import { getLogger } from './logger';
|
||||||
|
import { globalLoader } from './stores';
|
||||||
|
|
||||||
|
const logger = getLogger('pyscript/main');
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
const xPyScript = customElements.define('py-script', PyScript);
|
const xPyScript = customElements.define('py-script', PyScript);
|
||||||
const xPyRepl = customElements.define('py-repl', PyRepl);
|
|
||||||
const xPyEnv = customElements.define('py-env', PyEnv);
|
|
||||||
const xPyBox = customElements.define('py-box', PyBox);
|
|
||||||
const xPyButton = customElements.define('py-button', PyButton);
|
|
||||||
const xPyTitle = customElements.define('py-title', PyTitle);
|
|
||||||
const xPyInputBox = customElements.define('py-inputbox', PyInputBox);
|
|
||||||
const xPyWidget = customElements.define('py-register-widget', PyWidget);
|
|
||||||
const xPyLoader = customElements.define('py-loader', PyLoader);
|
const xPyLoader = customElements.define('py-loader', PyLoader);
|
||||||
const xPyConfig = customElements.define('py-config', PyConfig);
|
const xPyConfig = customElements.define('py-config', PyConfig);
|
||||||
|
const xPyEnv = customElements.define('py-env', PyEnv);
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
|
||||||
// As first thing, loop for application configs
|
// As first thing, loop for application configs
|
||||||
|
logger.info('checking for py-config');
|
||||||
const config: PyConfig = document.querySelector('py-config');
|
const config: PyConfig = document.querySelector('py-config');
|
||||||
if (!config) {
|
if (!config) {
|
||||||
const loader = document.createElement('py-config');
|
const loader = document.createElement('py-config');
|
||||||
@@ -31,12 +25,7 @@ if (!config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add loader to the page body
|
// add loader to the page body
|
||||||
|
logger.info('add py-loader');
|
||||||
const loader = <PyLoader>document.createElement('py-loader');
|
const loader = <PyLoader>document.createElement('py-loader');
|
||||||
document.body.append(loader);
|
document.body.append(loader);
|
||||||
globalLoader.set(loader);
|
globalLoader.set(loader);
|
||||||
|
|
||||||
const app = new App({
|
|
||||||
target: document.body,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default app;
|
|
||||||
|
|||||||
109
pyscriptjs/src/pyodide.ts
Normal file
109
pyscriptjs/src/pyodide.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { Runtime, RuntimeConfig } from './runtime';
|
||||||
|
import { getLastPath } from './utils';
|
||||||
|
import { getLogger } from './logger';
|
||||||
|
import type { PyodideInterface } from 'pyodide';
|
||||||
|
// eslint-disable-next-line
|
||||||
|
// @ts-ignore
|
||||||
|
import pyscript from './python/pyscript.py';
|
||||||
|
|
||||||
|
const logger = getLogger('pyscript/pyodide');
|
||||||
|
|
||||||
|
export class PyodideRuntime extends Runtime {
|
||||||
|
src: string;
|
||||||
|
name?: string;
|
||||||
|
lang?: string;
|
||||||
|
interpreter: PyodideInterface;
|
||||||
|
globals: any;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
src = 'https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js',
|
||||||
|
name = 'pyodide-default',
|
||||||
|
lang = 'python',
|
||||||
|
) {
|
||||||
|
logger.info('Runtime config:', { name, lang, src });
|
||||||
|
super();
|
||||||
|
this.src = src;
|
||||||
|
this.name = name;
|
||||||
|
this.lang = lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Although `loadPyodide` is used below,
|
||||||
|
* notice that it is not imported i.e.
|
||||||
|
* import { loadPyodide } from 'pyodide';
|
||||||
|
* is not used at the top of this file.
|
||||||
|
*
|
||||||
|
* This is because, if it's used, loadPyodide
|
||||||
|
* behaves mischievously i.e. it tries to load
|
||||||
|
* `pyodide.asm.js` and `pyodide_py.tar` but
|
||||||
|
* with paths that are wrong such as:
|
||||||
|
*
|
||||||
|
* http://127.0.0.1:8080/build/pyodide_py.tar
|
||||||
|
* which results in a 404 since `build` doesn't
|
||||||
|
* contain these files and is clearly the wrong
|
||||||
|
* path.
|
||||||
|
*/
|
||||||
|
async loadInterpreter(): Promise<void> {
|
||||||
|
logger.info('Loading pyodide');
|
||||||
|
// eslint-disable-next-line
|
||||||
|
// @ts-ignore
|
||||||
|
this.interpreter = await loadPyodide({
|
||||||
|
stdout: console.log,
|
||||||
|
stderr: console.log,
|
||||||
|
fullStdLib: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.globals = this.interpreter.globals;
|
||||||
|
|
||||||
|
// XXX: ideally, we should load micropip only if we actually need it
|
||||||
|
await this.loadPackage('micropip');
|
||||||
|
|
||||||
|
logger.info('importing pyscript.py');
|
||||||
|
await this.run(pyscript);
|
||||||
|
|
||||||
|
logger.info('pyodide loaded and initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(code: string): Promise<any> {
|
||||||
|
return await this.interpreter.runPythonAsync(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerJsModule(name: string, module: object): void {
|
||||||
|
this.interpreter.registerJsModule(name, module);
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadPackage(names: string | string[]): Promise<void> {
|
||||||
|
logger.info(`pyodide.loadPackage: ${names.toString()}`);
|
||||||
|
await this.interpreter.loadPackage(names,
|
||||||
|
logger.info.bind(logger),
|
||||||
|
logger.info.bind(logger));
|
||||||
|
}
|
||||||
|
|
||||||
|
async installPackage(package_name: string | string[]): Promise<void> {
|
||||||
|
if (package_name.length > 0) {
|
||||||
|
logger.info(`micropip install ${package_name.toString()}`);
|
||||||
|
const micropip = this.globals.get('micropip');
|
||||||
|
await micropip.install(package_name);
|
||||||
|
micropip.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadFromFile(path: string): Promise<void> {
|
||||||
|
const filename = getLastPath(path);
|
||||||
|
await this.run(
|
||||||
|
`
|
||||||
|
from pyodide.http import pyfetch
|
||||||
|
from js import console
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await pyfetch("${path}")
|
||||||
|
except Exception as err:
|
||||||
|
console.warn("PyScript: Access to local files (using 'paths:' in py-env) is not available when directly opening a HTML file; you must use a webserver to serve the additional files. See https://github.com/pyscript/pyscript/issues/257#issuecomment-1119595062 on starting a simple webserver with Python.")
|
||||||
|
raise(err)
|
||||||
|
content = await response.bytes()
|
||||||
|
with open("${filename}", "wb") as f:
|
||||||
|
f.write(content)
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -92,7 +92,7 @@ def format_mime(obj):
|
|||||||
break
|
break
|
||||||
if output is None:
|
if output is None:
|
||||||
if not_available:
|
if not_available:
|
||||||
console.warning(
|
console.warn(
|
||||||
f"Rendered object requested unavailable MIME renderers: {not_available}"
|
f"Rendered object requested unavailable MIME renderers: {not_available}"
|
||||||
)
|
)
|
||||||
output = repr(output)
|
output = repr(output)
|
||||||
@@ -149,8 +149,6 @@ class Element:
|
|||||||
return self.element.innerHtml
|
return self.element.innerHtml
|
||||||
|
|
||||||
def write(self, value, append=False):
|
def write(self, value, append=False):
|
||||||
console.log(f"Element.write: {value} --> {append}")
|
|
||||||
|
|
||||||
out_element_id = self.id
|
out_element_id = self.id
|
||||||
|
|
||||||
html, mime_type = format_mime(value)
|
html, mime_type = format_mime(value)
|
||||||
@@ -186,7 +184,7 @@ class Element:
|
|||||||
if _el:
|
if _el:
|
||||||
return Element(_el.id, _el)
|
return Element(_el.id, _el)
|
||||||
else:
|
else:
|
||||||
console.log(f"WARNING: can't find element matching query {query}")
|
console.warn(f"WARNING: can't find element matching query {query}")
|
||||||
|
|
||||||
def clone(self, new_id=None, to=None):
|
def clone(self, new_id=None, to=None):
|
||||||
if new_id is None:
|
if new_id is None:
|
||||||
@@ -261,21 +259,15 @@ class PyItemTemplate(Element):
|
|||||||
self._id = None
|
self._id = None
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
console.log("creating section")
|
new_child = create("div", self._id, "py-li-element")
|
||||||
new_child = create("section", self._id, "task bg-white my-1")
|
|
||||||
console.log("creating values")
|
|
||||||
|
|
||||||
console.log("creating innerHtml")
|
|
||||||
new_child._element.innerHTML = dedent(
|
new_child._element.innerHTML = dedent(
|
||||||
f"""
|
f"""
|
||||||
<label for="flex items-center p-2 ">
|
<label id="{self._id}" for="flex items-center p-2 ">
|
||||||
<input class="mr-2" type="checkbox" class="task-check">
|
<input class="mr-2" type="checkbox" class="task-check">
|
||||||
<p class="m-0 inline">{self.render_content()}</p>
|
<p>{self.render_content()}</p>
|
||||||
</label>
|
</label>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log("returning")
|
|
||||||
return new_child
|
return new_child
|
||||||
|
|
||||||
def on_click(self, evt):
|
def on_click(self, evt):
|
||||||
@@ -304,7 +296,7 @@ class PyItemTemplate(Element):
|
|||||||
|
|
||||||
|
|
||||||
class PyListTemplate:
|
class PyListTemplate:
|
||||||
theme = PyWidgetTheme("flex flex-col-reverse mt-8 mx-8")
|
theme = PyWidgetTheme("py-li-element")
|
||||||
item_class = PyItemTemplate
|
item_class = PyItemTemplate
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
@@ -331,7 +323,6 @@ class PyListTemplate:
|
|||||||
print(txt)
|
print(txt)
|
||||||
|
|
||||||
def foo(evt):
|
def foo(evt):
|
||||||
console.log(evt)
|
|
||||||
evtEl = evt.srcElement
|
evtEl = evt.srcElement
|
||||||
srcEl = Element(binds[evtEl.id])
|
srcEl = Element(binds[evtEl.id])
|
||||||
srcEl.element.onclick()
|
srcEl.element.onclick()
|
||||||
@@ -358,7 +349,6 @@ class PyListTemplate:
|
|||||||
return self._add(child)
|
return self._add(child)
|
||||||
|
|
||||||
def _add(self, child_elem):
|
def _add(self, child_elem):
|
||||||
console.log("appending child", child_elem.element)
|
|
||||||
self.pre_child_append(child_elem)
|
self.pre_child_append(child_elem)
|
||||||
child_elem.pre_append()
|
child_elem.pre_append()
|
||||||
self._children.append(child_elem)
|
self._children.append(child_elem)
|
||||||
@@ -387,19 +377,16 @@ class OutputCtxManager:
|
|||||||
self._out = out
|
self._out = out
|
||||||
self.output_to_console = output_to_console
|
self.output_to_console = output_to_console
|
||||||
self._append = append
|
self._append = append
|
||||||
console.log("----> changed out to", self._out, self._append)
|
|
||||||
|
|
||||||
def revert(self):
|
def revert(self):
|
||||||
console.log("----> reverted")
|
|
||||||
self._out = self._prev
|
self._out = self._prev
|
||||||
|
|
||||||
def write(self, value):
|
def write(self, value):
|
||||||
console.log("writing to", self._out, value, self._append)
|
|
||||||
if self._out:
|
if self._out:
|
||||||
Element(self._out).write(value, self._append)
|
Element(self._out).write(value, self._append)
|
||||||
|
|
||||||
if self.output_to_console:
|
if self.output_to_console:
|
||||||
console.log(self._out, value)
|
console.info(value)
|
||||||
|
|
||||||
|
|
||||||
class OutputManager:
|
class OutputManager:
|
||||||
@@ -430,7 +417,6 @@ class OutputManager:
|
|||||||
self._err_manager.revert()
|
self._err_manager.revert()
|
||||||
sys.stdout = self._out_manager
|
sys.stdout = self._out_manager
|
||||||
sys.stderr = self._err_manager
|
sys.stderr = self._err_manager
|
||||||
console.log("----> reverted")
|
|
||||||
|
|
||||||
|
|
||||||
pyscript = PyScript()
|
pyscript = PyScript()
|
||||||
202
pyscriptjs/src/runtime.ts
Normal file
202
pyscriptjs/src/runtime.ts
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
import type { PyodideInterface } from 'pyodide';
|
||||||
|
import type { PyLoader } from './components/pyloader';
|
||||||
|
import {
|
||||||
|
runtimeLoaded,
|
||||||
|
loadedEnvironments,
|
||||||
|
globalLoader,
|
||||||
|
initializers,
|
||||||
|
postInitializers,
|
||||||
|
Initializer,
|
||||||
|
scriptsQueue,
|
||||||
|
appConfig,
|
||||||
|
} from './stores';
|
||||||
|
import { createCustomElements } from './components/elements';
|
||||||
|
import type { PyScript } from './components/pyscript';
|
||||||
|
import { getLogger } from './logger';
|
||||||
|
|
||||||
|
const logger = getLogger('pyscript/runtime');
|
||||||
|
|
||||||
|
export const version = "<<VERSION>>";
|
||||||
|
export type RuntimeInterpreter = PyodideInterface | null;
|
||||||
|
|
||||||
|
export interface AppConfig extends Record<string, any> {
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
version?: string;
|
||||||
|
schema_version?: number;
|
||||||
|
type?: string;
|
||||||
|
author_name?: string;
|
||||||
|
author_email?: string;
|
||||||
|
license?: string;
|
||||||
|
autoclose_loader?: boolean;
|
||||||
|
runtimes?: Array<RuntimeConfig>;
|
||||||
|
packages?: Array<string>;
|
||||||
|
paths?: Array<string>;
|
||||||
|
plugins?: Array<string>;
|
||||||
|
pyscript?: PyScriptMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PyScriptMetadata = {
|
||||||
|
version?: string;
|
||||||
|
time?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RuntimeConfig = {
|
||||||
|
src?: string;
|
||||||
|
name?: string;
|
||||||
|
lang?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let loader: PyLoader | undefined;
|
||||||
|
globalLoader.subscribe(value => {
|
||||||
|
loader = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
let initializers_: Initializer[];
|
||||||
|
initializers.subscribe((value: Initializer[]) => {
|
||||||
|
initializers_ = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
let postInitializers_: Initializer[];
|
||||||
|
postInitializers.subscribe((value: Initializer[]) => {
|
||||||
|
postInitializers_ = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
let scriptsQueue_: PyScript[];
|
||||||
|
scriptsQueue.subscribe((value: PyScript[]) => {
|
||||||
|
scriptsQueue_ = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
let appConfig_: AppConfig = {
|
||||||
|
autoclose_loader: true
|
||||||
|
};
|
||||||
|
|
||||||
|
appConfig.subscribe((value: AppConfig) => {
|
||||||
|
if (value) {
|
||||||
|
appConfig_ = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Runtime class is a super class that all different runtimes must respect
|
||||||
|
and adhere to.
|
||||||
|
|
||||||
|
Currently, the only runtime available is Pyodide as indicated by the
|
||||||
|
`RuntimeInterpreter` type above. This serves as a Union of types of
|
||||||
|
different runtimes/interpreters which will be added in near future.
|
||||||
|
|
||||||
|
The class has abstract methods available which each runtime is supposed
|
||||||
|
to implement.
|
||||||
|
|
||||||
|
Methods available handle loading of the interpreter, initialization,
|
||||||
|
running code, loading and installation of packages, loading from files etc.
|
||||||
|
|
||||||
|
For an example implementation, refer to the `PyodideRuntime` class
|
||||||
|
in `pyodide.ts`
|
||||||
|
*/
|
||||||
|
export abstract class Runtime extends Object {
|
||||||
|
abstract src: string;
|
||||||
|
abstract name?: string;
|
||||||
|
abstract lang?: string;
|
||||||
|
abstract interpreter: RuntimeInterpreter;
|
||||||
|
/**
|
||||||
|
* global symbols table for the underlying interpreter.
|
||||||
|
* */
|
||||||
|
abstract globals: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* loads the interpreter for the runtime and saves an instance of it
|
||||||
|
* in the `this.interpreter` property along with calling of other
|
||||||
|
* additional convenience functions.
|
||||||
|
* */
|
||||||
|
abstract loadInterpreter(): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delegates the code to be run to the underlying interpreter
|
||||||
|
* (asynchronously) which can call its own API behind the scenes.
|
||||||
|
* */
|
||||||
|
abstract run(code: string): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delegates the setting of JS objects to
|
||||||
|
* the underlying interpreter.
|
||||||
|
* */
|
||||||
|
abstract registerJsModule(name: string, module: object): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delegates the loading of packages to
|
||||||
|
* the underlying interpreter.
|
||||||
|
* */
|
||||||
|
abstract loadPackage(names: string | string[]): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delegates the installation of packages
|
||||||
|
* (using a package manager, which can be specific to
|
||||||
|
* the runtime) to the underlying interpreter.
|
||||||
|
*
|
||||||
|
* For Pyodide, we use `micropip`
|
||||||
|
* */
|
||||||
|
abstract installPackage(package_name: string | string[]): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delegates the loading of files to the
|
||||||
|
* underlying interpreter.
|
||||||
|
* */
|
||||||
|
abstract loadFromFile(path: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initializes the page which involves loading of runtime,
|
||||||
|
* as well as evaluating all the code inside <py-script> tags
|
||||||
|
* along with initializers and postInitializers
|
||||||
|
* */
|
||||||
|
async initialize(): Promise<void> {
|
||||||
|
loader?.log('Loading runtime...');
|
||||||
|
await this.loadInterpreter();
|
||||||
|
const newEnv = {
|
||||||
|
id: 'default',
|
||||||
|
runtime: this,
|
||||||
|
state: 'loading',
|
||||||
|
};
|
||||||
|
runtimeLoaded.set(this);
|
||||||
|
|
||||||
|
// Inject the loader into the runtime namespace
|
||||||
|
// eslint-disable-next-line
|
||||||
|
this.globals.set('pyscript_loader', loader);
|
||||||
|
|
||||||
|
loader?.log('Runtime created...');
|
||||||
|
loadedEnvironments.update(environments => ({
|
||||||
|
...environments,
|
||||||
|
[newEnv['id']]: newEnv,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// now we call all initializers before we actually executed all page scripts
|
||||||
|
loader?.log('Initializing components...');
|
||||||
|
for (const initializer of initializers_) {
|
||||||
|
await initializer();
|
||||||
|
}
|
||||||
|
|
||||||
|
loader?.log('Initializing scripts...');
|
||||||
|
for (const script of scriptsQueue_) {
|
||||||
|
void script.evaluate();
|
||||||
|
}
|
||||||
|
scriptsQueue.set([]);
|
||||||
|
|
||||||
|
// now we call all post initializers AFTER we actually executed all page scripts
|
||||||
|
loader?.log('Running post initializers...');
|
||||||
|
|
||||||
|
// Finally create the custom elements for pyscript such as pybutton
|
||||||
|
createCustomElements();
|
||||||
|
|
||||||
|
if (appConfig_ && appConfig_.autoclose_loader) {
|
||||||
|
loader?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const initializer of postInitializers_) {
|
||||||
|
await initializer();
|
||||||
|
}
|
||||||
|
// NOTE: this message is used by integration tests to know that
|
||||||
|
// pyscript initialization has complete. If you change it, you need to
|
||||||
|
// change it also in tests/integration/support.py
|
||||||
|
logger.info('PyScript page fully initialized');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,57 +1,44 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import type { PyLoader } from './components/pyloader';
|
import type { PyLoader } from './components/pyloader';
|
||||||
import type { PyScript } from './components/pyscript';
|
import type { PyScript } from './components/pyscript';
|
||||||
|
import type { Runtime, AppConfig } from './runtime';
|
||||||
|
import { getLogger } from './logger';
|
||||||
|
|
||||||
export type Initializer = () => Promise<void>;
|
export type Initializer = () => Promise<void>;
|
||||||
|
|
||||||
export const pyodideLoaded = writable({
|
export type Environment = {
|
||||||
loaded: false,
|
id: string;
|
||||||
premise: null,
|
runtime: Runtime;
|
||||||
});
|
state: string;
|
||||||
|
};
|
||||||
|
|
||||||
export const loadedEnvironments = writable([{}]);
|
/*
|
||||||
export const DEFAULT_MODE = 'play';
|
A store for Runtime which can encompass any
|
||||||
|
runtime, but currently only has Pyodide as its offering.
|
||||||
|
*/
|
||||||
|
export const runtimeLoaded = writable<Runtime>();
|
||||||
|
|
||||||
|
export const loadedEnvironments = writable<Record<Environment['id'], Environment>>({});
|
||||||
|
|
||||||
export const navBarOpen = writable(false);
|
export const navBarOpen = writable(false);
|
||||||
export const componentsNavOpen = writable(false);
|
export const componentsNavOpen = writable(false);
|
||||||
export const componentDetailsNavOpen = writable(false);
|
export const componentDetailsNavOpen = writable(false);
|
||||||
export const mainDiv = writable(null);
|
export const mainDiv = writable(null);
|
||||||
export const currentComponentDetails = writable([]);
|
export const currentComponentDetails = writable([]);
|
||||||
export const mode = writable(DEFAULT_MODE);
|
|
||||||
export const scriptsQueue = writable<PyScript[]>([]);
|
export const scriptsQueue = writable<PyScript[]>([]);
|
||||||
export const initializers = writable<Initializer[]>([]);
|
export const initializers = writable<Initializer[]>([]);
|
||||||
export const postInitializers = writable<Initializer[]>([]);
|
export const postInitializers = writable<Initializer[]>([]);
|
||||||
export const globalLoader = writable<PyLoader | undefined>();
|
export const globalLoader = writable<PyLoader | undefined>();
|
||||||
export const appConfig = writable();
|
export const appConfig = writable<AppConfig>();
|
||||||
|
|
||||||
let scriptsQueue_: PyScript[] = [];
|
|
||||||
let initializers_: Initializer[] = [];
|
|
||||||
let postInitializers_: Initializer[] = [];
|
|
||||||
|
|
||||||
scriptsQueue.subscribe(value => {
|
|
||||||
scriptsQueue_ = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addToScriptsQueue = (script: PyScript) => {
|
export const addToScriptsQueue = (script: PyScript) => {
|
||||||
scriptsQueue.set([...scriptsQueue_, script]);
|
scriptsQueue.update(scriptsQueue => [...scriptsQueue, script]);
|
||||||
};
|
};
|
||||||
|
|
||||||
initializers.subscribe(value => {
|
|
||||||
initializers_ = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addInitializer = (initializer: Initializer) => {
|
export const addInitializer = (initializer: Initializer) => {
|
||||||
console.log('adding initializer', initializer);
|
initializers.update(initializers => [...initializers, initializer]);
|
||||||
initializers.set([...initializers_, initializer]);
|
|
||||||
console.log('added initializer', initializer);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
postInitializers.subscribe(value => {
|
|
||||||
postInitializers_ = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addPostInitializer = (initializer: Initializer) => {
|
export const addPostInitializer = (initializer: Initializer) => {
|
||||||
console.log('adding post initializer', initializer);
|
postInitializers.update(postInitializers => [...postInitializers, initializer]);
|
||||||
postInitializers.set([...postInitializers_, initializer]);
|
|
||||||
console.log('added post initializer', initializer);
|
|
||||||
};
|
};
|
||||||
|
|||||||
244
pyscriptjs/src/styles/pyscript_base.css
Normal file
244
pyscriptjs/src/styles/pyscript_base.css
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
:not(:defined) {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
html{
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner::after {
|
||||||
|
content: '';
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(40% - 20px);
|
||||||
|
left: calc(50% - 20px);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner.smooth::after {
|
||||||
|
border-top: 4px solid rgba(255, 255, 255, 1);
|
||||||
|
border-left: 4px solid rgba(255, 255, 255, 1);
|
||||||
|
border-right: 4px solid rgba(255, 255, 255, 0);
|
||||||
|
animation: spinner 0.6s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes spinner {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-top: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pop-up second layer begin */
|
||||||
|
|
||||||
|
.py-overlay {
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: white;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
transition: opacity 500ms;
|
||||||
|
visibility: hidden;
|
||||||
|
color: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-overlay {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-pop-up {
|
||||||
|
text-align: center;
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-pop-up p {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-pop-up a {
|
||||||
|
position: absolute;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 200%;
|
||||||
|
top: 3.5%;
|
||||||
|
right: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-box{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-box div.py-box-child *
|
||||||
|
{
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-repl-box{
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-box{
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgba(209, 213, 219, var(--tw-border-opacity));
|
||||||
|
border-width: 1px;
|
||||||
|
position: relative;
|
||||||
|
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: rgb(209, 213, 219)
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-box:hover button{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repl-play-button{
|
||||||
|
opacity: 0;
|
||||||
|
bottom: 0.25rem;
|
||||||
|
right: 0.25rem;
|
||||||
|
position: absolute;
|
||||||
|
padding: 0;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent;
|
||||||
|
background-image: none;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
text-transform: none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 100%;
|
||||||
|
margin: 0;
|
||||||
|
text-rendering: auto;
|
||||||
|
letter-spacing: normal;
|
||||||
|
word-spacing: normal;
|
||||||
|
line-height: normal;
|
||||||
|
text-transform: none;
|
||||||
|
text-indent: 0px;
|
||||||
|
text-shadow: none;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
cursor: default;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: -internal-light-dark(rgb(239, 239, 239), rgb(59, 59, 59));
|
||||||
|
margin: 0em;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repl-play-button:hover{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-title{
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-title h1{
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-input{
|
||||||
|
padding: 0.5rem;
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgba(209, 213, 219, var(--tw-border-opacity));
|
||||||
|
border-width: 1px;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
border-style: solid;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-box input.py-input{
|
||||||
|
width: -webkit-fill-available;
|
||||||
|
}
|
||||||
|
|
||||||
|
.central-content{
|
||||||
|
max-width: 20rem;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
text-rendering: auto;
|
||||||
|
color: -internal-light-dark(black, white);
|
||||||
|
letter-spacing: normal;
|
||||||
|
word-spacing: normal;
|
||||||
|
line-height: normal;
|
||||||
|
text-transform: none;
|
||||||
|
text-indent: 0px;
|
||||||
|
text-shadow: none;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: start;
|
||||||
|
appearance: auto;
|
||||||
|
-webkit-rtl-ordering: logical;
|
||||||
|
cursor: text;
|
||||||
|
background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59));
|
||||||
|
margin: 0em;
|
||||||
|
padding: 1px 2px;
|
||||||
|
border-width: 2px;
|
||||||
|
border-style: inset;
|
||||||
|
border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133));
|
||||||
|
border-image: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-button{
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgba(255, 255, 255, var(--tw-text-opacity));
|
||||||
|
padding: 0.5rem;
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgba(37, 99, 235, var(--tw-bg-opacity));
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgba(37, 99, 235, var(--tw-border-opacity));
|
||||||
|
border-width: 1px;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-li-element p{
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-li-element p{
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, input, optgroup, select, textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 100%;
|
||||||
|
line-height: 1.15;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-through {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
360
pyscriptjs/src/toml.ts
Normal file
360
pyscriptjs/src/toml.ts
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
// taken from https://github.com/Gin-Quin/fast-toml
|
||||||
|
/* eslint-disable */
|
||||||
|
"use strict";
|
||||||
|
let e = "",
|
||||||
|
t = 0;
|
||||||
|
function i(e:any, t:any = 0):any {
|
||||||
|
let i;
|
||||||
|
for (; (i = e[t++]) && (" " == i || "\t" == i || "\r" == i); );
|
||||||
|
return t - 1;
|
||||||
|
}
|
||||||
|
function n(e:any):any {
|
||||||
|
switch (e[0]) {
|
||||||
|
case void 0:
|
||||||
|
return "";
|
||||||
|
case '"':
|
||||||
|
return (function (e) {
|
||||||
|
let t,
|
||||||
|
i = 0,
|
||||||
|
n = "";
|
||||||
|
for (; (t = e.indexOf("\\", i) + 1); ) {
|
||||||
|
switch (((n += e.slice(i, t - 1)), e[t])) {
|
||||||
|
case "\\":
|
||||||
|
n += "\\";
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
n += '"';
|
||||||
|
break;
|
||||||
|
case "\r":
|
||||||
|
"\n" == e[t + 1] && t++;
|
||||||
|
case "\n":
|
||||||
|
break;
|
||||||
|
case "b":
|
||||||
|
n += "\b";
|
||||||
|
break;
|
||||||
|
case "t":
|
||||||
|
n += "\t";
|
||||||
|
break;
|
||||||
|
case "n":
|
||||||
|
n += "\n";
|
||||||
|
break;
|
||||||
|
case "f":
|
||||||
|
n += "\f";
|
||||||
|
break;
|
||||||
|
case "r":
|
||||||
|
n += "\r";
|
||||||
|
break;
|
||||||
|
case "u":
|
||||||
|
(n += String.fromCharCode(parseInt(e.substr(t + 1, 4), 16))), (t += 4);
|
||||||
|
break;
|
||||||
|
case "U":
|
||||||
|
(n += String.fromCharCode(parseInt(e.substr(t + 1, 8), 16))), (t += 8);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw r(e[t]);
|
||||||
|
}
|
||||||
|
i = t + 1;
|
||||||
|
}
|
||||||
|
return n + e.slice(i);
|
||||||
|
})(e.slice(1, -1));
|
||||||
|
case "'":
|
||||||
|
return e.slice(1, -1);
|
||||||
|
case "0":
|
||||||
|
case "1":
|
||||||
|
case "2":
|
||||||
|
case "3":
|
||||||
|
case "4":
|
||||||
|
case "5":
|
||||||
|
case "6":
|
||||||
|
case "7":
|
||||||
|
case "8":
|
||||||
|
case "9":
|
||||||
|
case "+":
|
||||||
|
case "-":
|
||||||
|
case ".":
|
||||||
|
let t = e;
|
||||||
|
if ((-1 != t.indexOf("_") && (t = t.replace(/_/g, "")), !isNaN(t))) return +t;
|
||||||
|
if ("-" == e[4] && "-" == e[7]) {
|
||||||
|
let t = new Date(e);
|
||||||
|
if ("Invalid Date" != t.toString()) return t;
|
||||||
|
} else if (":" == e[2] && ":" == e[5] && e.length >= 7) {
|
||||||
|
let t = new Date("0000-01-01T" + e + "Z");
|
||||||
|
if ("Invalid Date" != t.toString()) return t;
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
switch (e) {
|
||||||
|
case "true":
|
||||||
|
return !0;
|
||||||
|
case "false":
|
||||||
|
return !1;
|
||||||
|
case "nan":
|
||||||
|
case "NaN":
|
||||||
|
return !1;
|
||||||
|
case "null":
|
||||||
|
return null;
|
||||||
|
case "inf":
|
||||||
|
case "+inf":
|
||||||
|
case "Infinity":
|
||||||
|
case "+Infinity":
|
||||||
|
return 1 / 0;
|
||||||
|
case "-inf":
|
||||||
|
case "-Infinity":
|
||||||
|
return -1 / 0;
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
function r(i:any):any {
|
||||||
|
let n = (function () {
|
||||||
|
let i = e[t],
|
||||||
|
n = t;
|
||||||
|
"\n" == i && n--;
|
||||||
|
let r = 1,
|
||||||
|
s = e.lastIndexOf("\n", n),
|
||||||
|
a = e.indexOf("\n", n);
|
||||||
|
-1 == a && (a = 1 / 0);
|
||||||
|
("," != i && "\n" != i) || (n = s + 1);
|
||||||
|
if (-1 == s) return { line: r, column: n + 1, position: n, lineContent: e.slice(0, a).trim() };
|
||||||
|
const c = n - s + 1,
|
||||||
|
o = e.slice(s + 1, a).trim();
|
||||||
|
r++;
|
||||||
|
for (; -1 != (s = e.lastIndexOf("\n", s - 1)); ) r++;
|
||||||
|
return { line: r, column: c, position: n, lineContent: o };
|
||||||
|
})(),
|
||||||
|
r = String(n.line);
|
||||||
|
return (i += "\n" + r + " | " + n.lineContent + "\n"), (i += " ".repeat(r.length + n.column + 2) + "^"), SyntaxError(i);
|
||||||
|
}
|
||||||
|
function s(e:any, i:any = 0, n:any = !1):any {
|
||||||
|
let a,
|
||||||
|
c = e[i],
|
||||||
|
o = c,
|
||||||
|
f = c,
|
||||||
|
l = !0,
|
||||||
|
u = !1;
|
||||||
|
switch (c) {
|
||||||
|
case '"':
|
||||||
|
case "'":
|
||||||
|
if (((a = i + 1), n && e[i + 1] == c && e[i + 2] == c ? ((f = c + c + c), (a += 2)) : (u = !0), "'" == c)) a = e.indexOf(f, a) + 1;
|
||||||
|
else
|
||||||
|
for (; (a = e.indexOf(f, a) + 1); ) {
|
||||||
|
let t = !0,
|
||||||
|
i = a - 1;
|
||||||
|
for (; "\\" == e[--i]; ) t = !t;
|
||||||
|
if (t) break;
|
||||||
|
}
|
||||||
|
if (!a) throw r("Missing " + f + " closer");
|
||||||
|
if (c != f) a += 2;
|
||||||
|
else if (u) {
|
||||||
|
let n = e.indexOf("\n", i + 1) + 1;
|
||||||
|
if (n && n < a) throw ((t = n - 2), r("Forbidden end-of-line character in single-line string"));
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
case "(":
|
||||||
|
f = ")";
|
||||||
|
break;
|
||||||
|
case "{":
|
||||||
|
f = "}";
|
||||||
|
break;
|
||||||
|
case "[":
|
||||||
|
f = "]";
|
||||||
|
break;
|
||||||
|
case "<":
|
||||||
|
f = ">";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
l = !1;
|
||||||
|
}
|
||||||
|
let h = 0;
|
||||||
|
for (; (c = e[++i]); )
|
||||||
|
if (c == f) {
|
||||||
|
if (0 == h) return i + 1;
|
||||||
|
h--;
|
||||||
|
} else if ('"' == c || "'" == c) {
|
||||||
|
i = s(e, i, n) - 1;
|
||||||
|
} else l && c == o && h++;
|
||||||
|
throw r("Missing " + f);
|
||||||
|
}
|
||||||
|
function a(e:any):any {
|
||||||
|
"string" != typeof e && (e = String(e));
|
||||||
|
let t:any,
|
||||||
|
i:any,
|
||||||
|
n:any = -1,
|
||||||
|
a:any = "",
|
||||||
|
c:any = [];
|
||||||
|
for (; (i = e[++n]); )
|
||||||
|
switch (i) {
|
||||||
|
case ".":
|
||||||
|
if (!a) throw r('Unexpected "."');
|
||||||
|
c.push(a), (a = "");
|
||||||
|
continue;
|
||||||
|
case '"':
|
||||||
|
case "'":
|
||||||
|
if (((t = s(e, n)), t == n + 2)) throw r("Empty string key");
|
||||||
|
(a += e.slice(n + 1, t - 1)), (n = t - 1);
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
a += i;
|
||||||
|
}
|
||||||
|
return a && c.push(a), c;
|
||||||
|
}
|
||||||
|
function c(e:any, t:any = []):any {
|
||||||
|
const i = t.pop();
|
||||||
|
for (let i of t) {
|
||||||
|
if ("object" != typeof e) {
|
||||||
|
throw r('["' + t.slice(0, t.indexOf(i) + 1).join('"].["') + '"]' + " must be an object");
|
||||||
|
}
|
||||||
|
void 0 === e[i] && (e[i] = {}), (e = e[i]) instanceof Array && (e = e[e.length - 1]);
|
||||||
|
}
|
||||||
|
return [e, i];
|
||||||
|
}
|
||||||
|
class o {
|
||||||
|
root:any;
|
||||||
|
data:any;
|
||||||
|
inlineScopeList: any[];
|
||||||
|
constructor() {
|
||||||
|
this.root = {};
|
||||||
|
this.data = this.root;
|
||||||
|
this.inlineScopeList = [];
|
||||||
|
}
|
||||||
|
get isRoot():any {
|
||||||
|
return this.data == this.root;
|
||||||
|
}
|
||||||
|
set(e:any, t:any):any {
|
||||||
|
let [i, n] = c(this.data, a(e));
|
||||||
|
if ("string" == typeof i) throw "Wtf the scope is a string. Please report the bug";
|
||||||
|
if (n in i) throw r(`Re-writing the key '${e}'`);
|
||||||
|
return (i[n] = t), t;
|
||||||
|
}
|
||||||
|
push(e:any):any {
|
||||||
|
if (!(this.data instanceof Array)) {
|
||||||
|
if (!this.isRoot) throw r("Missing key");
|
||||||
|
(this.data = Object.assign([], this.data)), (this.root = this.data);
|
||||||
|
}
|
||||||
|
return this.data.push(e), this;
|
||||||
|
}
|
||||||
|
use(e:any):any {
|
||||||
|
return (
|
||||||
|
(this.data = (function (e:any, t:any = []) {
|
||||||
|
for (let i of t) {
|
||||||
|
//if (void 0 === e) e = lastData[lastElt] = {};
|
||||||
|
//else
|
||||||
|
if ("object" != typeof e) {
|
||||||
|
throw r('["' + t.slice(0, t.indexOf(i) + 1).join('"].["') + '"]' + " must be an object");
|
||||||
|
}
|
||||||
|
void 0 === e[i] && (e[i] = {}), (e = e[i]) instanceof Array && (e = e[e.length - 1]);
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
})(this.root, a(e))),
|
||||||
|
this
|
||||||
|
);
|
||||||
|
}
|
||||||
|
useArray(e:any):any {
|
||||||
|
let [t, i] = c(this.root, a(e));
|
||||||
|
return (this.data = {}), void 0 === t[i] && (t[i] = []), t[i].push(this.data), this;
|
||||||
|
}
|
||||||
|
enter(e:any, t:any):any {
|
||||||
|
return this.inlineScopeList.push(this.data), this.set(e, t), (this.data = t), this;
|
||||||
|
}
|
||||||
|
enterArray(e:any):any {
|
||||||
|
return this.inlineScopeList.push(this.data), this.push(e), (this.data = e), this;
|
||||||
|
}
|
||||||
|
exit():any {
|
||||||
|
return (this.data = this.inlineScopeList.pop()), this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function f(a:any):any {
|
||||||
|
"string" != typeof a && (a = String(a));
|
||||||
|
const c = new o(),
|
||||||
|
f:any[] = [];
|
||||||
|
(e = a), (t = 0);
|
||||||
|
let l:any,
|
||||||
|
u:any,
|
||||||
|
h:any = "",
|
||||||
|
d:any = "",
|
||||||
|
p:any = e[0],
|
||||||
|
w:any = !0;
|
||||||
|
const g = ():any => {
|
||||||
|
if (((h = h.trimEnd()), w)) h && c.push(n(h));
|
||||||
|
else {
|
||||||
|
if (!h) throw r("Expected key before =");
|
||||||
|
if (!d) throw r("Expected value after =");
|
||||||
|
c.set(h, n(d.trimEnd()));
|
||||||
|
}
|
||||||
|
(h = ""), (d = ""), (w = !0);
|
||||||
|
};
|
||||||
|
do {
|
||||||
|
switch (p) {
|
||||||
|
case " ":
|
||||||
|
w ? h && (h += p) : d && (d += p);
|
||||||
|
case "\t":
|
||||||
|
case "\r":
|
||||||
|
continue;
|
||||||
|
case "#":
|
||||||
|
(t = e.indexOf("\n", t + 1) - 1), -2 == t && (t = 1 / 0);
|
||||||
|
continue;
|
||||||
|
case '"':
|
||||||
|
case "'":
|
||||||
|
if (!w && d) {
|
||||||
|
d += p;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let n = e[t + 1] == p && e[t + 2] == p;
|
||||||
|
if (((l = s(e, t, !0)), w)) {
|
||||||
|
if (h) throw r("Unexpected " + p);
|
||||||
|
(h += n ? e.slice(t + 2, l - 2) : e.slice(t, l)), (t = l);
|
||||||
|
} else (d = e.slice(t, l)), (t = l), n && ((d = d.slice(2, -2)), "\n" == d[1] ? (d = d[0] + d.slice(2)) : "\r" == d[1] && "\n" == d[2] && (d = d[0] + d.slice(3)));
|
||||||
|
if (((t = i(e, t)), (p = e[t]), p && "," != p && "\n" != p && "#" != p && "}" != p && "]" != p && "=" != p)) throw r("Unexpected character after end of string");
|
||||||
|
t--;
|
||||||
|
continue;
|
||||||
|
case "\n":
|
||||||
|
case ",":
|
||||||
|
case void 0:
|
||||||
|
g();
|
||||||
|
continue;
|
||||||
|
case "[":
|
||||||
|
case "{":
|
||||||
|
if (((u = "[" == p ? "]" : "}"), w && !f.length)) {
|
||||||
|
if (h) throw r("Unexpected " + p);
|
||||||
|
if (((l = s(e, t)), "[" == p && "[" == e[t + 1])) {
|
||||||
|
if ("]" != e[l - 2]) throw r("Missing ]]");
|
||||||
|
c.useArray(e.slice(t + 2, l - 2));
|
||||||
|
} else c.use(e.slice(t + 1, l - 1));
|
||||||
|
t = l;
|
||||||
|
} else if (w) {
|
||||||
|
if (h) throw r("Unexpected " + p);
|
||||||
|
c.enterArray("[" == p ? [] : {}), f.push(u);
|
||||||
|
} else {
|
||||||
|
if (d) throw r("Unexpected " + p);
|
||||||
|
c.enter(h.trimEnd(), "[" == p ? [] : {}), f.push(u), (h = ""), (w = !0);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
case "]":
|
||||||
|
case "}":
|
||||||
|
if ((h && g(), f.pop() != p)) throw r("Unexpected " + p);
|
||||||
|
if ((c.exit(), (t = i(e, t + 1)), (p = e[t]), p && "," != p && "\n" != p && "#" != p && "}" != p && "]" != p)) throw r("Unexpected character after end of scope");
|
||||||
|
t--;
|
||||||
|
continue;
|
||||||
|
case "=":
|
||||||
|
if (!w) throw r("Unexpected " + p);
|
||||||
|
if (!h) throw r("Missing key before " + p);
|
||||||
|
w = !1;
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
w ? (h += p) : (d += p);
|
||||||
|
}
|
||||||
|
} while ((p = e[++t]) || h);
|
||||||
|
if (f.length) throw r("Missing " + f.pop());
|
||||||
|
return c.root;
|
||||||
|
}
|
||||||
|
let l = null,
|
||||||
|
u = null;
|
||||||
|
function h():any {
|
||||||
|
let e = "";
|
||||||
|
for (let t of arguments) e += "string" == typeof t ? t : t[0];
|
||||||
|
return f(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toml = (
|
||||||
|
(h.parse = f),
|
||||||
|
h
|
||||||
|
);
|
||||||
@@ -1,3 +1,27 @@
|
|||||||
|
import {toml} from './toml'
|
||||||
|
import type { AppConfig } from "./runtime";
|
||||||
|
|
||||||
|
const allKeys = {
|
||||||
|
"string": ["name", "description", "version", "type", "author_name", "author_email", "license"],
|
||||||
|
"number": ["schema_version"],
|
||||||
|
"boolean": ["autoclose_loader"],
|
||||||
|
"array": ["runtimes", "packages", "paths", "plugins"]
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultConfig: AppConfig = {
|
||||||
|
"schema_version": 1,
|
||||||
|
"type": "app",
|
||||||
|
"autoclose_loader": true,
|
||||||
|
"runtimes": [{
|
||||||
|
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
|
||||||
|
"name": "pyodide-0.21.2",
|
||||||
|
"lang": "python"
|
||||||
|
}],
|
||||||
|
"packages":[],
|
||||||
|
"paths":[],
|
||||||
|
"plugins": []
|
||||||
|
}
|
||||||
|
|
||||||
function addClasses(element: HTMLElement, classes: Array<string>) {
|
function addClasses(element: HTMLElement, classes: Array<string>) {
|
||||||
for (const entry of classes) {
|
for (const entry of classes) {
|
||||||
element.classList.add(entry);
|
element.classList.add(entry);
|
||||||
@@ -14,8 +38,12 @@ function getLastPath(str: string): string {
|
|||||||
return str.split('\\').pop().split('/').pop();
|
return str.split('\\').pop().split('/').pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escape(str: string): string {
|
||||||
|
return str.replace(/</g, "<").replace(/>/g, ">")
|
||||||
|
}
|
||||||
|
|
||||||
function htmlDecode(input: string): string {
|
function htmlDecode(input: string): string {
|
||||||
const doc = new DOMParser().parseFromString(ltrim(input), 'text/html');
|
const doc = new DOMParser().parseFromString(ltrim(escape(input)), 'text/html');
|
||||||
return doc.documentElement.textContent;
|
return doc.documentElement.textContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +88,7 @@ function showError(msg: string): void {
|
|||||||
|
|
||||||
function handleFetchError(e: Error, singleFile: string) {
|
function handleFetchError(e: Error, singleFile: string) {
|
||||||
//Should we still export full error contents to console?
|
//Should we still export full error contents to console?
|
||||||
console.warn('Caught an error in loadPaths:\r\n' + e);
|
console.warn(`Caught an error in loadPaths:\r\n ${e.toString()}`);
|
||||||
let errorContent: string;
|
let errorContent: string;
|
||||||
if (e.message.includes('TypeError: Failed to fetch')) {
|
if (e.message.includes('TypeError: Failed to fetch')) {
|
||||||
errorContent = `<p>PyScript: Access to local files
|
errorContent = `<p>PyScript: Access to local files
|
||||||
@@ -80,4 +108,152 @@ function handleFetchError(e: Error, singleFile: string) {
|
|||||||
showError(errorContent);
|
showError(errorContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { addClasses, removeClasses, getLastPath, ltrim, htmlDecode, guidGenerator, showError, handleFetchError };
|
function readTextFromPath(path: string) {
|
||||||
|
const request = new XMLHttpRequest();
|
||||||
|
request.open("GET", path, false);
|
||||||
|
request.send();
|
||||||
|
const returnValue = request.responseText;
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function inJest(): boolean {
|
||||||
|
return typeof process === 'object' && process.env.JEST_WORKER_ID !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillUserData(inputConfig: AppConfig, resultConfig: AppConfig): AppConfig
|
||||||
|
{
|
||||||
|
for (const key in inputConfig)
|
||||||
|
{
|
||||||
|
// fill in all extra keys ignored by the validator
|
||||||
|
if (!(key in defaultConfig))
|
||||||
|
{
|
||||||
|
resultConfig[key] = inputConfig[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeConfig(inlineConfig: AppConfig, externalConfig: AppConfig): AppConfig {
|
||||||
|
if (Object.keys(inlineConfig).length === 0 && Object.keys(externalConfig).length === 0)
|
||||||
|
{
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
else if (Object.keys(inlineConfig).length === 0)
|
||||||
|
{
|
||||||
|
return externalConfig;
|
||||||
|
}
|
||||||
|
else if(Object.keys(externalConfig).length === 0)
|
||||||
|
{
|
||||||
|
return inlineConfig;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let merged: AppConfig = {};
|
||||||
|
|
||||||
|
for (const keyType in allKeys)
|
||||||
|
{
|
||||||
|
const keys = allKeys[keyType];
|
||||||
|
keys.forEach(function(item: string){
|
||||||
|
if (keyType === "boolean")
|
||||||
|
{
|
||||||
|
merged[item] = (typeof inlineConfig[item] !== "undefined") ? inlineConfig[item] : externalConfig[item];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
merged[item] = inlineConfig[item] || externalConfig[item];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill extra keys from external first
|
||||||
|
// they will be overridden by inline if extra keys also clash
|
||||||
|
merged = fillUserData(externalConfig, merged);
|
||||||
|
merged = fillUserData(inlineConfig, merged);
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseConfig(configText: string, configType = "toml") {
|
||||||
|
let config: object;
|
||||||
|
if (configType === "toml") {
|
||||||
|
try {
|
||||||
|
// TOML parser is soft and can parse even JSON strings, this additional check prevents it.
|
||||||
|
if (configText.trim()[0] === "{")
|
||||||
|
{
|
||||||
|
const errMessage = `config supplied: ${configText} is an invalid TOML and cannot be parsed`;
|
||||||
|
showError(`<p>${errMessage}</p>`);
|
||||||
|
throw Error(errMessage);
|
||||||
|
}
|
||||||
|
config = toml.parse(configText);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
const errMessage: string = err.toString();
|
||||||
|
showError(`<p>config supplied: ${configText} is an invalid TOML and cannot be parsed: ${errMessage}</p>`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (configType === "json") {
|
||||||
|
try {
|
||||||
|
config = JSON.parse(configText);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
const errMessage: string = err.toString();
|
||||||
|
showError(`<p>config supplied: ${configText} is an invalid JSON and cannot be parsed: ${errMessage}</p>`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showError(`<p>type of config supplied is: ${configType}, supported values are ["toml", "json"].</p>`);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateConfig(configText: string, configType = "toml") {
|
||||||
|
const config = parseConfig(configText, configType);
|
||||||
|
|
||||||
|
const finalConfig: AppConfig = {}
|
||||||
|
|
||||||
|
for (const keyType in allKeys)
|
||||||
|
{
|
||||||
|
const keys = allKeys[keyType];
|
||||||
|
keys.forEach(function(item: string){
|
||||||
|
if (validateParamInConfig(item, keyType, config))
|
||||||
|
{
|
||||||
|
if (item === "runtimes")
|
||||||
|
{
|
||||||
|
finalConfig[item] = [];
|
||||||
|
const runtimes = config[item];
|
||||||
|
runtimes.forEach(function(eachRuntime: object){
|
||||||
|
const runtimeConfig: object = {};
|
||||||
|
for (const eachRuntimeParam in eachRuntime)
|
||||||
|
{
|
||||||
|
if (validateParamInConfig(eachRuntimeParam, "string", eachRuntime))
|
||||||
|
{
|
||||||
|
runtimeConfig[eachRuntimeParam] = eachRuntime[eachRuntimeParam];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalConfig[item].push(runtimeConfig);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
finalConfig[item] = config[item];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return fillUserData(config, finalConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateParamInConfig(paramName: string, paramType: string, config: object): boolean {
|
||||||
|
if (paramName in config)
|
||||||
|
{
|
||||||
|
return paramType === "array" ? Array.isArray(config[paramName]) : typeof config[paramName] === paramType;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { defaultConfig, addClasses, removeClasses, getLastPath, ltrim, htmlDecode, guidGenerator, showError, handleFetchError, readTextFromPath, inJest, mergeConfig, validateConfig };
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
const { tailwindExtractor } = require("tailwindcss/lib/lib/purgeUnusedStyles");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
purge: {
|
|
||||||
content: ["src/**/*.svelte", "public/index.html"],
|
|
||||||
options: {
|
|
||||||
defaultExtractor: (content) => [
|
|
||||||
...tailwindExtractor(content),
|
|
||||||
...[...content.matchAll(/(?:class:)*([\w\d-/:%.]+)/gm)].map(
|
|
||||||
([_match, group, ..._rest]) => group
|
|
||||||
),
|
|
||||||
],
|
|
||||||
keyframes: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
darkMode: false, // or 'media' or 'class'
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
container: {
|
|
||||||
center: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
variants: {
|
|
||||||
extend: {
|
|
||||||
display: ['group-hover']
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
};
|
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
"""All data required for testing examples"""
|
"""All data required for testing examples"""
|
||||||
import os
|
|
||||||
import threading
|
import threading
|
||||||
from http.server import HTTPServer as SuperHTTPServer
|
from http.server import HTTPServer as SuperHTTPServer
|
||||||
from http.server import SimpleHTTPRequestHandler
|
from http.server import SimpleHTTPRequestHandler
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
my_path = Path.cwd() / "examples"
|
from .support import Logger
|
||||||
os.chdir(my_path)
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def logger():
|
||||||
|
return Logger()
|
||||||
|
|
||||||
|
|
||||||
class HTTPServer(SuperHTTPServer):
|
class HTTPServer(SuperHTTPServer):
|
||||||
@@ -28,12 +30,17 @@ class HTTPServer(SuperHTTPServer):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def http_server():
|
def http_server(logger):
|
||||||
|
class MyHTTPRequestHandler(SimpleHTTPRequestHandler):
|
||||||
|
def log_message(self, fmt, *args):
|
||||||
|
logger.log("http_server", fmt % args, color="blue")
|
||||||
|
|
||||||
host, port = "127.0.0.1", 8080
|
host, port = "127.0.0.1", 8080
|
||||||
base_url = f"http://{host}:{port}"
|
base_url = f"http://{host}:{port}"
|
||||||
|
|
||||||
# serve_Run forever under thread
|
# serve_Run forever under thread
|
||||||
server = HTTPServer((host, port), SimpleHTTPRequestHandler)
|
server = HTTPServer((host, port), MyHTTPRequestHandler)
|
||||||
|
|
||||||
thread = threading.Thread(None, server.run)
|
thread = threading.Thread(None, server.run)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
412
pyscriptjs/tests/integration/support.py
Normal file
412
pyscriptjs/tests/integration/support.py
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
import pdb
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
import py
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
ROOT = py.path.local(__file__).dirpath("..", "..", "..")
|
||||||
|
BUILD = ROOT.join("pyscriptjs", "build")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("init")
|
||||||
|
class PyScriptTest:
|
||||||
|
"""
|
||||||
|
Base class to write PyScript integration tests, based on playwright.
|
||||||
|
|
||||||
|
It provides a simple API to generate HTML files and load them in
|
||||||
|
playwright.
|
||||||
|
|
||||||
|
It also provides a Pythonic API on top of playwright for the most
|
||||||
|
common tasks; in particular:
|
||||||
|
|
||||||
|
- self.console collects all the JS console.* messages. Look at the doc
|
||||||
|
of ConsoleMessageCollection for more details.
|
||||||
|
|
||||||
|
- self.check_errors() checks that no JS errors have been thrown
|
||||||
|
|
||||||
|
- after each test, self.check_errors() is automatically run to ensure
|
||||||
|
that no JS error passes uncaught.
|
||||||
|
|
||||||
|
- self.wait_for_console waits until the specified message appears in the
|
||||||
|
console
|
||||||
|
|
||||||
|
- self.wait_for_pyscript waits until all the PyScript tags have been
|
||||||
|
evaluated
|
||||||
|
|
||||||
|
- self.pyscript_run is the main entry point for pyscript tests: it
|
||||||
|
creates an HTML page to run the specified snippet.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Pyodide always print()s this message upon initialization. Make it
|
||||||
|
# available to all tests so that it's easiert to check.
|
||||||
|
PY_COMPLETE = "Python initialization complete"
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def init(self, request, tmpdir, http_server, logger, page):
|
||||||
|
"""
|
||||||
|
Fixture to automatically initialize all the tests in this class and its
|
||||||
|
subclasses.
|
||||||
|
|
||||||
|
The magic is done by the decorator @pyest.mark.usefixtures("init"),
|
||||||
|
which tells pytest to automatically use this fixture for all the test
|
||||||
|
method of this class.
|
||||||
|
|
||||||
|
Using the standard pytest behavior, we can request more fixtures:
|
||||||
|
tmpdir, http_server and page; 'page' is a fixture provided by
|
||||||
|
pytest-playwright.
|
||||||
|
|
||||||
|
Then, we save these fixtures on the self and proceed with more
|
||||||
|
initialization. The end result is that the requested fixtures are
|
||||||
|
automatically made available as self.xxx in all methods.
|
||||||
|
"""
|
||||||
|
self.testname = request.function.__name__.replace("test_", "")
|
||||||
|
self.tmpdir = tmpdir
|
||||||
|
# create a symlink to BUILD inside tmpdir
|
||||||
|
tmpdir.join("build").mksymlinkto(BUILD)
|
||||||
|
self.tmpdir.chdir()
|
||||||
|
self.http_server = http_server
|
||||||
|
self.logger = logger
|
||||||
|
self.init_page(page)
|
||||||
|
#
|
||||||
|
# this extra print is useful when using pytest -s, else we start printing
|
||||||
|
# in the middle of the line
|
||||||
|
print()
|
||||||
|
#
|
||||||
|
# if you use pytest --headed you can see the browser page while
|
||||||
|
# playwright executes the tests, but the page is closed very quickly
|
||||||
|
# as soon as the test finishes. To avoid that, we automatically start
|
||||||
|
# a pdb so that we can wait as long as we want.
|
||||||
|
yield
|
||||||
|
if request.config.option.headed:
|
||||||
|
pdb.Pdb.intro = (
|
||||||
|
"\n"
|
||||||
|
"This (Pdb) was started automatically because you passed --headed:\n"
|
||||||
|
"the execution of the test pauses here to give you the time to inspect\n"
|
||||||
|
"the browser. When you are done, type one of the following commands:\n"
|
||||||
|
" (Pdb) continue\n"
|
||||||
|
" (Pdb) cont\n"
|
||||||
|
" (Pdb) c\n"
|
||||||
|
)
|
||||||
|
pdb.set_trace()
|
||||||
|
|
||||||
|
def init_page(self, page):
|
||||||
|
self.page = page
|
||||||
|
self.console = ConsoleMessageCollection(self.logger)
|
||||||
|
self._page_errors = []
|
||||||
|
page.on("console", self.console.add_message)
|
||||||
|
page.on("pageerror", self._on_pageerror)
|
||||||
|
|
||||||
|
def teardown_method(self):
|
||||||
|
# we call check_errors on teardown: this means that if there are still
|
||||||
|
# non-cleared errors, the test will fail. If you expect errors in your
|
||||||
|
# page and they should not cause the test to fail, you should call
|
||||||
|
# self.check_errors() in the test itself.
|
||||||
|
self.check_errors()
|
||||||
|
|
||||||
|
def _on_pageerror(self, error):
|
||||||
|
self.logger.log("JS exception", error.stack, color="red")
|
||||||
|
self._page_errors.append(error)
|
||||||
|
|
||||||
|
def check_errors(self):
|
||||||
|
"""
|
||||||
|
Check whether JS errors were reported.
|
||||||
|
|
||||||
|
If it finds a single JS error, raise JsError.
|
||||||
|
If it finds multiple JS errors, raise JsMultipleErrors.
|
||||||
|
|
||||||
|
Upon return, all the errors are cleared, so a subsequent call to
|
||||||
|
check_errors will not raise, unless NEW JS errors have been reported
|
||||||
|
in the meantime.
|
||||||
|
"""
|
||||||
|
exc = None
|
||||||
|
if len(self._page_errors) == 1:
|
||||||
|
# if there is a single error, wrap it
|
||||||
|
exc = JsError(self._page_errors[0])
|
||||||
|
elif len(self._page_errors) >= 2:
|
||||||
|
exc = JsMultipleErrors(self._page_errors)
|
||||||
|
self._page_errors = []
|
||||||
|
if exc:
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
def clear_errors(self):
|
||||||
|
"""
|
||||||
|
Clear all JS errors.
|
||||||
|
"""
|
||||||
|
self._page_errors = []
|
||||||
|
|
||||||
|
def writefile(self, filename, content):
|
||||||
|
"""
|
||||||
|
Very thin helper to write a file in the tmpdir
|
||||||
|
"""
|
||||||
|
f = self.tmpdir.join(filename)
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
def goto(self, path):
|
||||||
|
self.logger.reset()
|
||||||
|
self.logger.log("page.goto", path, color="yellow")
|
||||||
|
url = f"{self.http_server}/{path}"
|
||||||
|
self.page.goto(url)
|
||||||
|
|
||||||
|
def wait_for_console(self, text, *, timeout=None, check_errors=True):
|
||||||
|
"""
|
||||||
|
Wait until the given message appear in the console.
|
||||||
|
|
||||||
|
Note: it must be the *exact* string as printed by e.g. console.log.
|
||||||
|
If you need more control on the predicate (e.g. if you want to match a
|
||||||
|
substring), use self.page.expect_console_message directly.
|
||||||
|
|
||||||
|
timeout is expressed in milliseconds. If it's None, it will use
|
||||||
|
playwright's own default value, which is 30 seconds).
|
||||||
|
|
||||||
|
If check_errors is True (the default), it also checks that no JS
|
||||||
|
errors were raised during the waiting.
|
||||||
|
"""
|
||||||
|
pred = lambda msg: msg.text == text
|
||||||
|
try:
|
||||||
|
with self.page.expect_console_message(pred, timeout=timeout):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
# raise JsError if there were any javascript exception. Note that
|
||||||
|
# this might happen also in case of a TimeoutError. In that case,
|
||||||
|
# the JsError will shadow the TimeoutError but this is correct,
|
||||||
|
# because it's very likely that the console message never appeared
|
||||||
|
# precisely because of the exception in JS.
|
||||||
|
if check_errors:
|
||||||
|
self.check_errors()
|
||||||
|
|
||||||
|
def wait_for_pyscript(self, *, timeout=None, check_errors=True):
|
||||||
|
"""
|
||||||
|
Wait until pyscript has been fully loaded.
|
||||||
|
|
||||||
|
Timeout is expressed in milliseconds. If it's None, it will use
|
||||||
|
playwright's own default value, which is 30 seconds).
|
||||||
|
|
||||||
|
If check_errors is True (the default), it also checks that no JS
|
||||||
|
errors were raised during the waiting.
|
||||||
|
"""
|
||||||
|
# this is printed by runtime.ts:Runtime.initialize
|
||||||
|
self.wait_for_console(
|
||||||
|
"[pyscript/runtime] PyScript page fully initialized",
|
||||||
|
timeout=timeout,
|
||||||
|
check_errors=check_errors,
|
||||||
|
)
|
||||||
|
# We still don't know why this wait is necessary, but without it
|
||||||
|
# events aren't being triggered in the tests.
|
||||||
|
self.page.wait_for_timeout(100)
|
||||||
|
|
||||||
|
def pyscript_run(self, snippet, *, extra_head=""):
|
||||||
|
"""
|
||||||
|
Main entry point for pyscript tests.
|
||||||
|
|
||||||
|
snippet contains a fragment of HTML which will be put inside a full
|
||||||
|
HTML document. In particular, the <head> automatically contains the
|
||||||
|
correct <script> and <link> tags which are necessary to load pyscript
|
||||||
|
correctly.
|
||||||
|
|
||||||
|
This method does the following:
|
||||||
|
- write a full HTML file containing the snippet
|
||||||
|
- open a playwright page for it
|
||||||
|
- wait until pyscript has been fully loaded
|
||||||
|
"""
|
||||||
|
doc = f"""
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="{self.http_server}/build/pyscript.css" />
|
||||||
|
<script defer src="{self.http_server}/build/pyscript.js"></script>
|
||||||
|
{extra_head}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{snippet}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
filename = f"{self.testname}.html"
|
||||||
|
self.writefile(filename, doc)
|
||||||
|
self.goto(filename)
|
||||||
|
self.wait_for_pyscript()
|
||||||
|
|
||||||
|
|
||||||
|
# ============== Helpers and utility functions ==============
|
||||||
|
|
||||||
|
|
||||||
|
class JsError(Exception):
|
||||||
|
"""
|
||||||
|
Represent an exception which happened in JS.
|
||||||
|
|
||||||
|
It's a thin wrapper around playwright.sync_api.Error, with two important
|
||||||
|
differences:
|
||||||
|
|
||||||
|
1. it has a better name: if you see JsError in a traceback, it's
|
||||||
|
immediately obvious that it's a JS exception.
|
||||||
|
|
||||||
|
2. Show also the JS stacktrace by default, contrarily to
|
||||||
|
playwright.sync_api.Error
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, error):
|
||||||
|
super().__init__(self.format_playwright_error(error))
|
||||||
|
self.error = error
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_playwright_error(error):
|
||||||
|
# apparently, playwright Error.stack contains all the info that we
|
||||||
|
# want: exception name, message and stacktrace. The docs say that
|
||||||
|
# error.stack is optional, so fallback to the standard repr if it's
|
||||||
|
# unavailable.
|
||||||
|
return error.stack or str(error)
|
||||||
|
|
||||||
|
|
||||||
|
class JsMultipleErrors(Exception):
|
||||||
|
"""
|
||||||
|
This is raised in case we get multiple JS errors in the page
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, errors):
|
||||||
|
lines = ["Multiple JS errors found:"]
|
||||||
|
for err in errors:
|
||||||
|
lines.append(JsError.format_playwright_error(err))
|
||||||
|
msg = "\n".join(lines)
|
||||||
|
super().__init__(msg)
|
||||||
|
self.errors = errors
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleMessageCollection:
|
||||||
|
"""
|
||||||
|
Helper class to collect and expose ConsoleMessage in a Pythonic way.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
console.log.messages: list of ConsoleMessage with type=='log'
|
||||||
|
console.log.lines: list of strings
|
||||||
|
console.log.text: the whole text as single string
|
||||||
|
|
||||||
|
console.debug.* same as above, but with different types
|
||||||
|
console.info.*
|
||||||
|
console.error.*
|
||||||
|
console.warning.*
|
||||||
|
|
||||||
|
console.all.* same as above, but considering all messages, no filters
|
||||||
|
"""
|
||||||
|
|
||||||
|
class View:
|
||||||
|
"""
|
||||||
|
Filter console messages by the given msg_type
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, console, msg_type):
|
||||||
|
self.console = console
|
||||||
|
self.msg_type = msg_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def messages(self):
|
||||||
|
if self.msg_type is None:
|
||||||
|
return self.console._messages
|
||||||
|
else:
|
||||||
|
return [
|
||||||
|
msg for msg in self.console._messages if msg.type == self.msg_type
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lines(self):
|
||||||
|
return [msg.text for msg in self.messages]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self):
|
||||||
|
return "\n".join(self.lines)
|
||||||
|
|
||||||
|
_COLORS = {
|
||||||
|
"error": "red",
|
||||||
|
"warning": "brown",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, logger):
|
||||||
|
self.logger = logger
|
||||||
|
self._messages = []
|
||||||
|
self.all = self.View(self, None)
|
||||||
|
self.log = self.View(self, "log")
|
||||||
|
self.debug = self.View(self, "debug")
|
||||||
|
self.info = self.View(self, "info")
|
||||||
|
self.error = self.View(self, "error")
|
||||||
|
self.warning = self.View(self, "warning")
|
||||||
|
|
||||||
|
def add_message(self, msg):
|
||||||
|
# log the message: pytest will capute the output and display the
|
||||||
|
# messages if the test fails.
|
||||||
|
category = f"console.{msg.type}"
|
||||||
|
color = self._COLORS.get(msg.type)
|
||||||
|
self.logger.log(category, msg.text, color=color)
|
||||||
|
self._messages.append(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
"""
|
||||||
|
Helper class to log messages to stdout.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- nice formatted category
|
||||||
|
- keep track of time passed since the last reset
|
||||||
|
- support colors
|
||||||
|
|
||||||
|
NOTE: the (lowercase) logger fixture is defined in conftest.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.reset()
|
||||||
|
# capture things like [pyscript/main]
|
||||||
|
self.prefix_regexp = re.compile(r"(\[.+?\])")
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.start_time = time.time()
|
||||||
|
|
||||||
|
def colorize_prefix(self, text, *, color):
|
||||||
|
# find the first occurrence of something like [pyscript/main] and
|
||||||
|
# colorize it
|
||||||
|
start, end = Color.escape_pair(color)
|
||||||
|
return self.prefix_regexp.sub(rf"{start}\1{end}", text, 1)
|
||||||
|
|
||||||
|
def log(self, category, text, *, color=None):
|
||||||
|
delta = time.time() - self.start_time
|
||||||
|
text = self.colorize_prefix(text, color="teal")
|
||||||
|
line = f"[{delta:6.2f} {category:15}] {text}"
|
||||||
|
if color:
|
||||||
|
line = Color.set(color, line)
|
||||||
|
print(line)
|
||||||
|
|
||||||
|
|
||||||
|
class Color:
|
||||||
|
"""
|
||||||
|
Helper method to print colored output using ANSI escape codes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
black = "30"
|
||||||
|
darkred = "31"
|
||||||
|
darkgreen = "32"
|
||||||
|
brown = "33"
|
||||||
|
darkblue = "34"
|
||||||
|
purple = "35"
|
||||||
|
teal = "36"
|
||||||
|
lightgray = "37"
|
||||||
|
darkgray = "30;01"
|
||||||
|
red = "31;01"
|
||||||
|
green = "32;01"
|
||||||
|
yellow = "33;01"
|
||||||
|
blue = "34;01"
|
||||||
|
fuchsia = "35;01"
|
||||||
|
turquoise = "36;01"
|
||||||
|
white = "37;01"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set(cls, color, string):
|
||||||
|
start, end = cls.escape_pair(color)
|
||||||
|
return f"{start}{string}{end}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def escape_pair(cls, color):
|
||||||
|
try:
|
||||||
|
color = getattr(cls, color)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
start = f"\x1b[{color}m"
|
||||||
|
end = "\x1b[00m"
|
||||||
|
return start, end
|
||||||
222
pyscriptjs/tests/integration/test_00_support.py
Normal file
222
pyscriptjs/tests/integration/test_00_support.py
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
import textwrap
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from playwright import sync_api
|
||||||
|
|
||||||
|
from .support import JsError, JsMultipleErrors, PyScriptTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestSupport(PyScriptTest):
|
||||||
|
"""
|
||||||
|
These are NOT tests about PyScript.
|
||||||
|
|
||||||
|
They test the PyScriptTest class, i.e. we want to ensure that all the
|
||||||
|
testing machinery that we have works correctly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
"""
|
||||||
|
Very basic test, just to check that we can write, serve and read a simple
|
||||||
|
HTML (no pyscript yet)
|
||||||
|
"""
|
||||||
|
doc = """
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Hello world</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
self.writefile("mytest.html", doc)
|
||||||
|
self.goto("mytest.html")
|
||||||
|
content = self.page.content()
|
||||||
|
assert "<h1>Hello world</h1>" in content
|
||||||
|
|
||||||
|
def test_console(self):
|
||||||
|
"""
|
||||||
|
Test that we capture console.log messages correctly.
|
||||||
|
"""
|
||||||
|
doc = """
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
console.log("my log 1");
|
||||||
|
console.debug("my debug");
|
||||||
|
console.info("my info");
|
||||||
|
console.error("my error");
|
||||||
|
console.warn("my warning");
|
||||||
|
console.log("my log 2");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
self.writefile("mytest.html", doc)
|
||||||
|
self.goto("mytest.html")
|
||||||
|
assert len(self.console.all.messages) == 6
|
||||||
|
assert self.console.all.lines == [
|
||||||
|
"my log 1",
|
||||||
|
"my debug",
|
||||||
|
"my info",
|
||||||
|
"my error",
|
||||||
|
"my warning",
|
||||||
|
"my log 2",
|
||||||
|
]
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
assert self.console.all.text == textwrap.dedent("""
|
||||||
|
my log 1
|
||||||
|
my debug
|
||||||
|
my info
|
||||||
|
my error
|
||||||
|
my warning
|
||||||
|
my log 2
|
||||||
|
""").strip()
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
assert self.console.log.lines == ["my log 1", "my log 2"]
|
||||||
|
assert self.console.debug.lines == ["my debug"]
|
||||||
|
|
||||||
|
def test_check_errors(self):
|
||||||
|
doc = """
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>throw new Error('this is an error');</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
self.writefile("mytest.html", doc)
|
||||||
|
self.goto("mytest.html")
|
||||||
|
with pytest.raises(JsError) as exc:
|
||||||
|
self.check_errors()
|
||||||
|
# check that the exception message contains the error message and the
|
||||||
|
# stack trace
|
||||||
|
msg = str(exc.value)
|
||||||
|
assert "Error: this is an error" in msg
|
||||||
|
assert f"at {self.http_server}/mytest.html" in msg
|
||||||
|
#
|
||||||
|
# after a call to check_errors, the errors are cleared
|
||||||
|
self.check_errors()
|
||||||
|
|
||||||
|
def test_check_errors_multiple(self):
|
||||||
|
doc = """
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>throw new Error('error 1');</script>
|
||||||
|
<script>throw new Error('error 2');</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
self.writefile("mytest.html", doc)
|
||||||
|
self.goto("mytest.html")
|
||||||
|
with pytest.raises(JsMultipleErrors) as exc:
|
||||||
|
self.check_errors()
|
||||||
|
assert "error 1" in str(exc.value)
|
||||||
|
assert "error 2" in str(exc.value)
|
||||||
|
#
|
||||||
|
# check that errors are cleared
|
||||||
|
self.check_errors()
|
||||||
|
|
||||||
|
def test_clear_errors(self):
|
||||||
|
doc = """
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>throw new Error('this is an error');</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
self.writefile("mytest.html", doc)
|
||||||
|
self.goto("mytest.html")
|
||||||
|
self.clear_errors()
|
||||||
|
# self.check_errors does not raise, because the errors have been
|
||||||
|
# cleared
|
||||||
|
self.check_errors()
|
||||||
|
|
||||||
|
def test_wait_for_console(self):
|
||||||
|
"""
|
||||||
|
Test that self.wait_for_console actually waits.
|
||||||
|
If it's buggy, the test will try to read self.console.log BEFORE the
|
||||||
|
log has been written and it will fail.
|
||||||
|
"""
|
||||||
|
doc = """
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
setTimeout(function() {
|
||||||
|
console.log('Page loaded!');
|
||||||
|
}, 100);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
self.writefile("mytest.html", doc)
|
||||||
|
self.goto("mytest.html")
|
||||||
|
# we use a timeout of 500ms to give plenty of time to the page to
|
||||||
|
# actually run the setTimeout callback
|
||||||
|
self.wait_for_console("Page loaded!", timeout=200)
|
||||||
|
assert self.console.log.lines[-1] == "Page loaded!"
|
||||||
|
|
||||||
|
def test_wait_for_console_exception_1(self):
|
||||||
|
"""
|
||||||
|
Test that if a JS exception is raised while waiting for the console, we
|
||||||
|
report the exception and not the timeout.
|
||||||
|
|
||||||
|
There are two main cases:
|
||||||
|
1. there is an exception and the console message does not appear
|
||||||
|
2. there is an exception but the console message appears anyway
|
||||||
|
|
||||||
|
This test checks for case 1. Case 2 is tested by
|
||||||
|
test_wait_for_console_exception_2
|
||||||
|
"""
|
||||||
|
# case 1: there is an exception and the console message does not appear
|
||||||
|
doc = """
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>throw new Error('this is an error');</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
self.writefile("mytest.html", doc)
|
||||||
|
# "Page loaded!" will never appear, of course.
|
||||||
|
self.goto("mytest.html")
|
||||||
|
with pytest.raises(JsError) as exc:
|
||||||
|
self.wait_for_console("Page loaded!", timeout=200)
|
||||||
|
assert "this is an error" in str(exc.value)
|
||||||
|
assert isinstance(exc.value.__context__, sync_api.TimeoutError)
|
||||||
|
#
|
||||||
|
# if we use check_errors=False, the error are ignored, but we get the
|
||||||
|
# Timeout anyway
|
||||||
|
self.goto("mytest.html")
|
||||||
|
with pytest.raises(sync_api.TimeoutError):
|
||||||
|
self.wait_for_console("Page loaded!", timeout=200, check_errors=False)
|
||||||
|
# we still got a JsError, so we need to manually clear it, else the
|
||||||
|
# test fails at teardown
|
||||||
|
self.clear_errors()
|
||||||
|
|
||||||
|
def test_wait_for_console_exception_2(self):
|
||||||
|
"""
|
||||||
|
See the description in test_wait_for_console_exception_1.
|
||||||
|
"""
|
||||||
|
# case 2: there is an exception, but the console message appears
|
||||||
|
doc = """
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
setTimeout(function() {
|
||||||
|
console.log('Page loaded!');
|
||||||
|
}, 100);
|
||||||
|
throw new Error('this is an error');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
self.writefile("mytest.html", doc)
|
||||||
|
self.goto("mytest.html")
|
||||||
|
with pytest.raises(JsError) as exc:
|
||||||
|
self.wait_for_console("Page loaded!", timeout=200)
|
||||||
|
assert "this is an error" in str(exc.value)
|
||||||
|
#
|
||||||
|
# with check_errors=False, the Error is ignored and the
|
||||||
|
# wait_for_console succeeds
|
||||||
|
self.goto("mytest.html")
|
||||||
|
self.wait_for_console("Page loaded!", timeout=200, check_errors=False)
|
||||||
|
# clear the errors, else the test fails at teardown
|
||||||
|
self.clear_errors()
|
||||||
53
pyscriptjs/tests/integration/test_01_basic.py
Normal file
53
pyscriptjs/tests/integration/test_01_basic.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from .support import PyScriptTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestBasic(PyScriptTest):
|
||||||
|
def test_pyscript_hello(self):
|
||||||
|
self.pyscript_run(
|
||||||
|
"""
|
||||||
|
<py-script>
|
||||||
|
print('hello pyscript')
|
||||||
|
</py-script>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
# this is a very ugly way of checking the content of the DOM. If we
|
||||||
|
# find ourselves to write a lot of code in this style, we will
|
||||||
|
# probably want to write a nicer API for it.
|
||||||
|
inner_html = self.page.locator("py-script").inner_html()
|
||||||
|
pattern = r'<div id="py-.*">hello pyscript</div>'
|
||||||
|
assert re.search(pattern, inner_html)
|
||||||
|
|
||||||
|
def test_execution_in_order(self):
|
||||||
|
"""
|
||||||
|
Check that they py-script tags are executed in the same order they are
|
||||||
|
defined
|
||||||
|
"""
|
||||||
|
self.pyscript_run(
|
||||||
|
"""
|
||||||
|
<py-script>import js; js.console.log('one')</py-script>
|
||||||
|
<py-script>js.console.log('two')</py-script>
|
||||||
|
<py-script>js.console.log('three')</py-script>
|
||||||
|
<py-script>js.console.log('four')</py-script>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assert self.console.log.lines == [
|
||||||
|
self.PY_COMPLETE,
|
||||||
|
"one",
|
||||||
|
"two",
|
||||||
|
"three",
|
||||||
|
"four",
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_escaping_of_angle_brackets(self):
|
||||||
|
"""
|
||||||
|
Check that py-script tags escape angle brackets
|
||||||
|
"""
|
||||||
|
self.pyscript_run(
|
||||||
|
"""
|
||||||
|
<py-script>import js; js.console.log(1<2, 1>2)</py-script>
|
||||||
|
<py-script>js.console.log("<div></div>")</py-script>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assert self.console.log.lines == [self.PY_COMPLETE, "true false", "<div></div>"]
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user