mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-20 02:37:41 -05:00
Compare commits
11 Commits
fix-404-ge
...
fpliger/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20e8c00f79 | ||
|
|
b1aa4d345b | ||
|
|
6f516ed4d1 | ||
|
|
04d117c12d | ||
|
|
579e7ab87a | ||
|
|
10e497c753 | ||
|
|
3b46609614 | ||
|
|
320ca306bd | ||
|
|
e087deef09 | ||
|
|
c3bac976c8 | ||
|
|
4c3e5fabb9 |
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -11,5 +11,5 @@
|
||||
<!-- Note: Only user-facing changes require a changelog entry. Internal-only API changes do not require a changelog entry. Changes in documentation do not require a changelog entry. -->
|
||||
|
||||
- [ ] All tests pass locally
|
||||
- [ ] I have updated `CHANGELOG.md`
|
||||
- [ ] I have updated `docs/changelog.md`
|
||||
- [ ] I have created documentation for this(if applicable)
|
||||
|
||||
142
.github/workflows/build-unstable.yml
vendored
Normal file
142
.github/workflows/build-unstable.yml
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
name: "[CI] Build Unstable"
|
||||
|
||||
on:
|
||||
push: # Only run on merges into main that modify files under pyscriptjs/ and examples/
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- pyscriptjs/**
|
||||
- examples/**
|
||||
- .github/workflows/build-unstable.yml # Test that workflow works when changed
|
||||
|
||||
pull_request: # Run on any PR that modifies files under pyscriptjs/ and examples/
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- pyscriptjs/**
|
||||
- examples/**
|
||||
- .github/workflows/build-unstable.yml # Test that workflow works when changed
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
BuildAndTest:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: pyscriptjs
|
||||
env:
|
||||
MINICONDA_PYTHON_VERSION: py38
|
||||
MINICONDA_VERSION: 4.11.0
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: setup Miniconda
|
||||
uses: conda-incubator/setup-miniconda@v2
|
||||
|
||||
- name: Setup Environment
|
||||
run: make setup
|
||||
|
||||
- name: Build
|
||||
run: make build
|
||||
|
||||
- name: TypeScript Tests
|
||||
run: make test-ts
|
||||
|
||||
- name: Python Tests
|
||||
run: make test-py
|
||||
|
||||
- name: Integration Tests
|
||||
run: make test-integration-parallel
|
||||
|
||||
- name: Examples Tests
|
||||
run: make test-examples
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: pyscript
|
||||
path: |
|
||||
pyscriptjs/build/
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: success() || failure()
|
||||
with:
|
||||
name: test_results
|
||||
path: pyscriptjs/test_results
|
||||
if-no-files-found: error
|
||||
eslint:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: pyscriptjs
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: npm install
|
||||
run: npm install
|
||||
|
||||
- name: Eslint
|
||||
run: npx eslint src -c .eslintrc.js
|
||||
|
||||
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
|
||||
- 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 --quiet ./build/ s3://pyscript.net/unstable/
|
||||
62
.github/workflows/docs-release.yml
vendored
Normal file
62
.github/workflows/docs-release.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: "[Docs] Build Release"
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
env:
|
||||
SPHINX_HTML_BASE_URL: https://docs.pyscript.net/
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
|
||||
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
|
||||
|
||||
- name: Setup
|
||||
uses: conda-incubator/setup-miniconda@v2
|
||||
with:
|
||||
auto-update-conda: true
|
||||
activate-environment: docs
|
||||
environment-file: docs/environment.yml
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Build
|
||||
shell: bash -l {0}
|
||||
run: |
|
||||
cd docs/
|
||||
make html
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: pyscript-docs-${{ github.ref_name }}
|
||||
path: docs/_build/html/
|
||||
|
||||
# Deploy to S3
|
||||
- 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: Copy redirect file
|
||||
run: aws s3 cp --quiet ./docs/_build/html/_static/redirect.html s3://docs.pyscript.net/index.html
|
||||
|
||||
- name: Sync to S3
|
||||
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/${{ github.ref_name }}/
|
||||
|
||||
# Make sure to remove the latest folder so we sync the full docs upon release
|
||||
- name: Delete latest directory
|
||||
run: aws s3 rm --recursive s3://docs.pyscript.net/latest/
|
||||
|
||||
# Note that the files are the same as above, but we want to have folders with
|
||||
# /<tag name>/ AND /latest/ which latest will always point to the latest release
|
||||
- name: Sync to /latest
|
||||
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/latest/
|
||||
53
.github/workflows/docs-review.yml
vendored
Normal file
53
.github/workflows/docs-review.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: "[Docs] Build Review"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
paths:
|
||||
- docs/**
|
||||
|
||||
concurrency:
|
||||
# Concurrency group that uses the workflow name and PR number if available
|
||||
# or commit SHA as a fallback. If a new build is triggered under that
|
||||
# concurrency group while a previous build is running it will be canceled.
|
||||
# Repeated pushes to a PR will cancel all previous builds, while multiple
|
||||
# merges to main will not cancel.
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'pyscript'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
env:
|
||||
SPHINX_HTML_BASE_URL: https://docs.pyscript.net/
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
|
||||
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
|
||||
|
||||
- name: Setup
|
||||
uses: conda-incubator/setup-miniconda@v2
|
||||
with:
|
||||
auto-update-conda: true
|
||||
activate-environment: docs
|
||||
environment-file: docs/environment.yml
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Build
|
||||
shell: bash -l {0}
|
||||
run: |
|
||||
cd docs/
|
||||
make html
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: pyscript-docs-review-${{ github.event.number }}
|
||||
path: docs/_build/html/
|
||||
58
.github/workflows/docs-unstable.yml
vendored
Normal file
58
.github/workflows/docs-unstable.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: "[Docs] Build Latest"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- docs/**
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
env:
|
||||
SPHINX_HTML_BASE_URL: https://docs.pyscript.net/
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
|
||||
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
|
||||
|
||||
- name: Setup
|
||||
uses: conda-incubator/setup-miniconda@v2
|
||||
with:
|
||||
auto-update-conda: true
|
||||
activate-environment: docs
|
||||
environment-file: docs/environment.yml
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Build
|
||||
shell: bash -l {0}
|
||||
run: |
|
||||
cd docs/
|
||||
make html
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: pyscript-docs-latest
|
||||
path: docs/_build/html/
|
||||
|
||||
# Deploy to S3
|
||||
- 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 }}
|
||||
|
||||
# Sync will only copy changed files
|
||||
- name: Sync Error
|
||||
run: aws s3 cp --quiet ./docs/_static/s3_error.html s3://docs.pyscript.net/error.html
|
||||
|
||||
# Sync will only copy changed files
|
||||
- name: Sync to S3
|
||||
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/unstable/
|
||||
29
.github/workflows/prepare-release.yml
vendored
29
.github/workflows/prepare-release.yml
vendored
@@ -1,20 +1,24 @@
|
||||
name: "Prepare Release"
|
||||
name: "[CI] Prepare Release"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9]+" # YYYY.MM.MICRO
|
||||
|
||||
env:
|
||||
MINICONDA_PYTHON_VERSION: py38
|
||||
MINICONDA_VERSION: 4.11.0
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./pyscript.core
|
||||
working-directory: pyscriptjs
|
||||
|
||||
jobs:
|
||||
prepare-release:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
@@ -34,18 +38,17 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: NPM Install
|
||||
run: npm install && npx playwright install
|
||||
- name: setup Miniconda
|
||||
uses: conda-incubator/setup-miniconda@v2
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: Setup Environment
|
||||
run: make setup
|
||||
|
||||
- name: Generate index.html
|
||||
working-directory: .
|
||||
run: sed 's#_PATH_#./#' ./public/index.html > ./pyscript.core/dist/index.html
|
||||
- name: Build and Test
|
||||
run: make test
|
||||
|
||||
- name: Zip dist folder
|
||||
run: zip -r -q ./build.zip ./dist
|
||||
- name: Zip build folder
|
||||
run: zip -r -q ./build.zip ./build
|
||||
|
||||
- name: Prepare Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
|
||||
34
.github/workflows/publish-release.yml
vendored
34
.github/workflows/publish-release.yml
vendored
@@ -1,22 +1,26 @@
|
||||
name: "Publish Release"
|
||||
name: "[CI] Publish Release"
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
MINICONDA_PYTHON_VERSION: py38
|
||||
MINICONDA_VERSION: 4.11.0
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./pyscript.core
|
||||
working-directory: pyscriptjs
|
||||
|
||||
jobs:
|
||||
publish-release:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
@@ -36,18 +40,18 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: npm install
|
||||
run: npm install && npx playwright install
|
||||
- name: setup Miniconda
|
||||
uses: conda-incubator/setup-miniconda@v2
|
||||
|
||||
- name: build
|
||||
run: npm run build
|
||||
- name: Setup Environment
|
||||
run: make setup
|
||||
|
||||
- name: Generate index.html in snapshot
|
||||
working-directory: .
|
||||
run: sed 's#_PATH_#https://pyscript.net/releases/${{ github.ref_name }}/#' ./public/index.html > ./pyscript.core/dist/index.html
|
||||
- name: Build and Test
|
||||
run: make test
|
||||
|
||||
# Upload to S3
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v1.6.1
|
||||
with:
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||
@@ -55,5 +59,5 @@ jobs:
|
||||
- name: Sync to S3
|
||||
run:
|
||||
| # Update /latest and create an explicitly versioned directory under releases/YYYY.MM.MICRO/
|
||||
aws s3 sync --quiet ./dist/ s3://pyscript.net/latest/
|
||||
aws s3 sync --quiet ./dist/ s3://pyscript.net/releases/${{ github.ref_name }}/
|
||||
aws s3 sync --quiet ./build/ s3://pyscript.net/latest/
|
||||
aws s3 sync --quiet ./build/ s3://pyscript.net/releases/${{ github.ref_name }}/
|
||||
|
||||
47
.github/workflows/publish-snapshot.yml
vendored
47
.github/workflows/publish-snapshot.yml
vendored
@@ -1,4 +1,5 @@
|
||||
name: "Publish Snapshot"
|
||||
name: "[CI] Publish Snapshot"
|
||||
# Copy /unstable/ to /snapshots/2022.09.1.RC1/
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -8,54 +9,18 @@ on:
|
||||
type: string
|
||||
required: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./pyscript.core
|
||||
|
||||
jobs:
|
||||
publish-snapshot:
|
||||
snapshot:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install && npx playwright install
|
||||
|
||||
- name: Build Pyscript.core
|
||||
run: npm run build
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v1.6.1
|
||||
with:
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||
|
||||
- name: Generate index.html in snapshot
|
||||
working-directory: .
|
||||
run: sed 's#_PATH_#https://pyscript.net/snapshots/${{ inputs.snapshot_version }}/#' ./public/index.html > ./pyscript.core/dist/index.html
|
||||
|
||||
- name: Copy to Snapshot
|
||||
- name: Sync to S3
|
||||
run: >
|
||||
aws s3 sync ./dist/ s3://pyscript.net/snapshots/${{ inputs.snapshot_version }}/
|
||||
aws s3 sync s3://pyscript.net/unstable/ s3://pyscript.net/snapshots/${{ inputs.snapshot_version }}/
|
||||
|
||||
61
.github/workflows/publish-unstable.yml
vendored
61
.github/workflows/publish-unstable.yml
vendored
@@ -1,61 +0,0 @@
|
||||
name: "Publish Unstable"
|
||||
|
||||
on:
|
||||
push: # Only run on merges into main that modify files under pyscript.core/ and examples/
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- pyscript.core/**
|
||||
- examples/**
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish-unstable:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./pyscript.core
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: NPM Install
|
||||
run: npm install && npx playwright install
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Generate index.html in snapshot
|
||||
working-directory: .
|
||||
run: sed 's#_PATH_#https://pyscript.net/unstable/#' ./public/index.html > ./pyscript.core/dist/index.html
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||
|
||||
- name: Sync to S3
|
||||
run: aws s3 sync --quiet ./dist/ s3://pyscript.net/unstable/
|
||||
29
.github/workflows/sync-examples.yml
vendored
Normal file
29
.github/workflows/sync-examples.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: "[CI] Sync Examples"
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: examples
|
||||
|
||||
steps:
|
||||
# Deploy to S3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- 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
|
||||
# Sync outdated or new files, delete ones no longer in source
|
||||
run: aws s3 sync --quiet --delete . s3://pyscript.net/examples/ # Sync directory, delete what is not in source
|
||||
74
.github/workflows/test-next.yml
vendored
Normal file
74
.github/workflows/test-next.yml
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
name: "[CI] Test Next"
|
||||
|
||||
on:
|
||||
push: # Only run on merges into main that modify files under pyscriptjs/ and examples/
|
||||
branches:
|
||||
- next
|
||||
paths:
|
||||
- pyscript.core/**
|
||||
- .github/workflows/test-next.yml # Test that workflow works when changed
|
||||
|
||||
pull_request: # Run on any PR that modifies files under pyscriptjs/ and examples/
|
||||
branches:
|
||||
- next
|
||||
paths:
|
||||
- pyscript.core/**
|
||||
- .github/workflows/test-next.yml # Test that workflow works when changed
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
TestNext:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: pyscript.core
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
# TODO: this will likely change soon to pyscript.next
|
||||
# - name: install next deps
|
||||
# working-directory: pyscript.core
|
||||
# run: npm i; npx playwright install
|
||||
|
||||
# - name: build next
|
||||
# working-directory: pyscript.core
|
||||
# run: npm run build
|
||||
|
||||
# - name: Run next tests
|
||||
# working-directory: pyscript.core
|
||||
# run: npm run test
|
||||
|
||||
# TODO: DO we want to upload next yet?
|
||||
# - uses: actions/upload-artifact@v3
|
||||
# with:
|
||||
# name: pyscript
|
||||
# path: |
|
||||
# pyscriptjs/build/
|
||||
# if-no-files-found: error
|
||||
# retention-days: 7
|
||||
|
||||
# - uses: actions/upload-artifact@v3
|
||||
# if: success() || failure()
|
||||
# with:
|
||||
# name: test_results
|
||||
# path: pyscriptjs/test_results
|
||||
# if-no-files-found: error
|
||||
92
.github/workflows/test.yml
vendored
92
.github/workflows/test.yml
vendored
@@ -1,92 +0,0 @@
|
||||
name: "[CI] Test"
|
||||
|
||||
on:
|
||||
push: # Only run on merges into main that modify certain files
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- pyscript.core/**
|
||||
- .github/workflows/test.yml
|
||||
|
||||
pull_request: # Only run on merges into main that modify certain files
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- pyscript.core/**
|
||||
- .github/workflows/test.yml
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
BuildAndTest:
|
||||
runs-on: ubuntu-latest-8core
|
||||
env:
|
||||
MINICONDA_PYTHON_VERSION: py38
|
||||
MINICONDA_VERSION: 4.11.0
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 3
|
||||
|
||||
# display a git log: when you run CI on PRs, github automatically
|
||||
# merges the PR into main and run the CI on that commit. The idea
|
||||
# here is to show enough of git log to understand what is the
|
||||
# actual commit (in the PR) that we are using. See also
|
||||
# 'fetch-depth: 3' above.
|
||||
- name: git log
|
||||
run: git log --graph -3
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: setup Miniconda
|
||||
uses: conda-incubator/setup-miniconda@v2
|
||||
|
||||
- name: Create and activate virtual environment
|
||||
run: |
|
||||
python3 -m venv test_venv
|
||||
source test_venv/bin/activate
|
||||
echo PATH=$PATH >> $GITHUB_ENV
|
||||
echo VIRTUAL_ENV=$VIRTUAL_ENV >> $GITHUB_ENV
|
||||
|
||||
- name: Setup dependencies in virtual environment
|
||||
run: |
|
||||
make setup
|
||||
|
||||
- name: Build
|
||||
run: make build
|
||||
|
||||
- name: Integration Tests
|
||||
#run: make test-integration-parallel
|
||||
run: |
|
||||
make test-integration
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: pyscript
|
||||
path: |
|
||||
pyscript.core/dist/
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: success() || failure()
|
||||
with:
|
||||
name: test_results
|
||||
path: test_results/
|
||||
if-no-files-found: error
|
||||
4
.github/workflows/test_report.yml
vendored
4
.github/workflows/test_report.yml
vendored
@@ -1,12 +1,12 @@
|
||||
name: Test Report
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['\[CI\] Test']
|
||||
workflows: ['\[CI\] Build Unstable']
|
||||
types:
|
||||
- completed
|
||||
jobs:
|
||||
report:
|
||||
runs-on: ubuntu-latest-8core
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dorny/test-reporter@v1.6.0
|
||||
with:
|
||||
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -51,6 +51,7 @@ coverage.xml
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
pyscriptjs/examples
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
@@ -140,12 +141,3 @@ coverage/
|
||||
|
||||
# junit xml for test results
|
||||
test_results
|
||||
|
||||
# @pyscript/core npm artifacts
|
||||
pyscript.core/core.*
|
||||
pyscript.core/dist
|
||||
pyscript.core/dist.zip
|
||||
pyscript.core/src/plugins.js
|
||||
pyscript.core/src/stdlib/pyscript.js
|
||||
pyscript.core/src/3rd-party/*
|
||||
!pyscript.core/src/3rd-party/READMEmd
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# This is the configuration for pre-commit, a local framework for managing pre-commit hooks
|
||||
# Check out the docs at: https://pre-commit.com/
|
||||
ci:
|
||||
#skip: [eslint]
|
||||
skip: [eslint]
|
||||
autoupdate_schedule: monthly
|
||||
|
||||
default_stages: [commit]
|
||||
@@ -16,19 +16,24 @@ repos:
|
||||
- id: check-json
|
||||
exclude: tsconfig\.json
|
||||
- id: check-toml
|
||||
exclude: bad\.toml
|
||||
- id: check-xml
|
||||
- id: check-yaml
|
||||
- id: detect-private-key
|
||||
- id: end-of-file-fixer
|
||||
exclude: pyscript\.core/dist|\.min\.js$
|
||||
exclude: pyscript\.core/core.*|\.min\.js$
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.257
|
||||
hooks:
|
||||
- id: ruff
|
||||
exclude: pyscript\.core/test|pyscript\.core/src/display.py
|
||||
args: [--fix]
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
exclude: pyscript\.core/src/stdlib/pyscript/__init__\.py
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.4
|
||||
@@ -42,12 +47,17 @@ repos:
|
||||
rev: "v3.0.0-alpha.6"
|
||||
hooks:
|
||||
- id: prettier
|
||||
exclude: pyscript\.core/test|pyscript\.core/dist|pyscript\.core/types|pyscript.core/src/stdlib/pyscript.js|pyscript\.sw/|pyscript.core/src/3rd-party
|
||||
exclude: pyscript\.core/test|pyscript\.core/core.*|pyscript\.core/types/|pyscript\.sw/
|
||||
args: [--tab-width, "4"]
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.12.0
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: v8.36.0
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
args: [--profile, black]
|
||||
- id: eslint
|
||||
files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
|
||||
types: [file]
|
||||
additional_dependencies:
|
||||
- eslint@8.25.0
|
||||
- typescript@5.0.4
|
||||
- "@typescript-eslint/eslint-plugin@5.58.0"
|
||||
- "@typescript-eslint/parser@5.58.0"
|
||||
|
||||
87
CHANGELOG.md
87
CHANGELOG.md
@@ -1,87 +0,0 @@
|
||||
# Release Notes
|
||||
|
||||
## 2023.05.01
|
||||
|
||||
### Features
|
||||
|
||||
- Added the `xterm` attribute to `py-config`. When set to `True` or `xterm`, an (output-only) [xterm.js](http://xtermjs.org/) terminal will be used in place of the default py-terminal.
|
||||
- The default version of Pyodide is now `0.23.2`. See the [Pyodide Changelog](https://pyodide.org/en/stable/project/changelog.html#version-0-23-2) for a detailed list of changes.
|
||||
- Added the `@when` decorator for attaching Python functions as event handlers
|
||||
- The `py-mount` attribute on HTML elements has been deprecated, and will be removed in a future release.
|
||||
|
||||
#### Runtime py- attributes
|
||||
|
||||
- Added logic to react to `py-*` attributes changes, removal, `py-*` attributes added to already live nodes but also `py-*` attributes added or defined via injected nodes (either appended or via `innerHTML` operations). ([#1435](https://github.com/pyscript/pyscript/pull/1435))
|
||||
|
||||
#### <script type="py">
|
||||
|
||||
- Added the ability to optionally use `<script type="py">`, `<script type="pyscript">` or `<script type="py-script">` instead of a `<py-script>` custom element, in order to tackle cases where the content of the `<py-script>` tag, inevitably parsed by browsers, could accidentally contain _HTML_ able to break the surrounding page layout. ([#1396](https://github.com/pyscript/pyscript/pull/1396))
|
||||
|
||||
#### <py-terminal>
|
||||
|
||||
- Added a `docked` field and attribute for the `<py-terminal>` custom element, enabled by default when the terminal is in `auto` mode, and able to dock the terminal at the bottom of the page with auto scroll on new code execution.
|
||||
|
||||
#### <py-script>
|
||||
|
||||
- Restored the `output` attribute of `py-script` tags to route `sys.stdout` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063))
|
||||
- Added a `stderr` attribute of `py-script` tags to route `sys.stderr` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063))
|
||||
|
||||
#### <py-repl>
|
||||
|
||||
- The `output` attribute of `py-repl` tags now specifies the id of the DOM element that `sys.stdout`, `sys.stderr`, and the results of a REPL execution are written to. It no longer affects the location of calls to `display()`
|
||||
- Added a `stderr` attribute of `py-repl` tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
|
||||
- Resored the `output-mode` attribute of `py-repl` tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results.
|
||||
- Load code from the attribute src of py-repl and preload it into the corresponding py-repl tag by use the attribute `str` in your `py-repl` tag([#1292](https://github.com/pyscript/pyscript/pull/1292))
|
||||
- <py-repl> elements now have a `getPySrc()` method, which returns the code inside the REPL as a string.([#1516](https://github.com/pyscript/pyscript/pull/1292))
|
||||
|
||||
#### Plugins
|
||||
|
||||
- Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a `py-repl` tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
|
||||
|
||||
#### Web worker support
|
||||
|
||||
- introduced the new experimental `execution_thread` config option: if you set `execution_thread = "worker"`, the python interpreter runs inside a web worker
|
||||
- worker support is still **very** experimental: not everything works, use it at your own risk
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fixes [#1280](https://github.com/pyscript/pyscript/issues/1280), which describes the errors on the PyRepl tests related to having auto-gen tags that shouldn't be there.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Py-REPL tests now run on both osx and non osx OSs
|
||||
- migrated from _rollup_ to _esbuild_ to create artifacts
|
||||
- updated `@codemirror` dependency to its latest
|
||||
|
||||
### Docs
|
||||
|
||||
- Add docs for event handlers
|
||||
|
||||
## 2023.03.1
|
||||
|
||||
### Features
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fixed an issue where `pyscript` would not be available when using the minified version of PyScript. ([#1054](https://github.com/pyscript/pyscript/pull/1054))
|
||||
- Fixed missing closing tag when rendering an image with `display`. ([#1058](https://github.com/pyscript/pyscript/pull/1058))
|
||||
- Fixed a bug where Python plugins methods were being executed twice. ([#1064](https://github.com/pyscript/pyscript/pull/1064))
|
||||
|
||||
### Enhancements
|
||||
|
||||
- When adding a `py-` attribute to an element but didn't added an `id` attribute, PyScript will now generate a random ID for the element instead of throwing an error which caused the splash screen to not shutdown. ([#1122](https://github.com/pyscript/pyscript/pull/1122))
|
||||
- You can now disable the splashscreen by setting `enabled = false` in your `py-config` under the `[splashscreen]` configuration section. ([#1138](https://github.com/pyscript/pyscript/pull/1138))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fixed 'Direct usage of document is deprecated' warning in the getting started guide. ([#1052](https://github.com/pyscript/pyscript/pull/1052))
|
||||
- Added reference documentation for the `py-splashscreen` plugin ([#1138](https://github.com/pyscript/pyscript/pull/1138))
|
||||
- Adds doc for installing tests ([#1156](https://github.com/pyscript/pyscript/pull/1156))
|
||||
- Adds docs for custom Pyscript attributes (`py-*`) that allow you to add event listeners to an element ([#1125](https://github.com/pyscript/pyscript/pull/1125))
|
||||
|
||||
### Deprecations and Removals
|
||||
|
||||
- The py-config `runtimes` to specify an interpreter has been deprecated. The `interpreters` config should be used instead. ([#1082](https://github.com/pyscript/pyscript/pull/1082))
|
||||
- The attributes `pys-onClick` and `pys-onKeyDown` have been deprecated, but the warning was only shown in the console. An alert banner will now be shown on the page if the attributes are used. They will be removed in the next release. ([#1084](https://github.com/pyscript/pyscript/pull/1084))
|
||||
- The pyscript elements `py-button`, `py-inputbox`, `py-box` and `py-title` have now completed their deprecation cycle and have been removed. ([#1084](https://github.com/pyscript/pyscript/pull/1084))
|
||||
- The attributes `pys-onClick` and `pys-onKeyDown` have been removed. Use `py-click` and `py-keydown` instead ([#1361](https://github.com/pyscript/pyscript/pull/1361))
|
||||
92
Makefile
92
Makefile
@@ -1,92 +0,0 @@
|
||||
MIN_NODE_VER := 20
|
||||
MIN_NPM_VER := 6
|
||||
MIN_PY3_VER := 8
|
||||
NODE_VER := $(shell node -v | cut -d. -f1 | sed 's/^v\(.*\)/\1/')
|
||||
NPM_VER := $(shell npm -v | cut -d. -f1)
|
||||
PY3_VER := $(shell python3 -c "import sys;t='{v[1]}'.format(v=list(sys.version_info[:2]));print(t)")
|
||||
PY_OK := $(shell python3 -c "print(int($(PY3_VER) >= $(MIN_PY3_VER)))")
|
||||
|
||||
all:
|
||||
@echo "\nThere is no default Makefile target right now. Try:\n"
|
||||
@echo "make setup - check your environment and install the dependencies."
|
||||
@echo "make clean - clean up auto-generated assets."
|
||||
@echo "make build - build PyScript."
|
||||
@echo "make precommit-check - run the precommit checks (run eslint)."
|
||||
@echo "make test-integration - run all integration tests sequentially."
|
||||
@echo "make fmt - format the code."
|
||||
@echo "make fmt-check - check the code formatting.\n"
|
||||
|
||||
.PHONY: check-node
|
||||
check-node:
|
||||
@if [ $(NODE_VER) -lt $(MIN_NODE_VER) ]; then \
|
||||
echo "\033[0;31mBuild requires Node $(MIN_NODE_VER).x or higher: $(NODE_VER) detected.\033[0m"; \
|
||||
false; \
|
||||
fi
|
||||
|
||||
.PHONY: check-npm
|
||||
check-npm:
|
||||
@if [ $(NPM_VER) -lt $(MIN_NPM_VER) ]; then \
|
||||
echo "\033[0;31mBuild requires Node $(MIN_NPM_VER).x or higher: $(NPM_VER) detected.\033[0m"; \
|
||||
false; \
|
||||
fi
|
||||
|
||||
.PHONY: check-python
|
||||
check-python:
|
||||
@if [ $(PY_OK) -eq 0 ]; then \
|
||||
echo "\033[0;31mRequires Python 3.$(MIN_PY3_VER).x or higher: 3.$(PY3_VER) detected.\033[0m"; \
|
||||
false; \
|
||||
fi
|
||||
|
||||
# Check the environment, install the dependencies.
|
||||
setup: check-node check-npm check-python
|
||||
cd pyscript.core && npm install && cd ..
|
||||
ifeq ($(VIRTUAL_ENV),)
|
||||
echo "\n\n\033[0;31mCannot install Python dependencies. Your virtualenv is not activated.\033[0m"
|
||||
false
|
||||
else
|
||||
python -m pip install -r requirements.txt
|
||||
playwright install
|
||||
endif
|
||||
|
||||
# Clean up generated assets.
|
||||
clean:
|
||||
find . -name \*.py[cod] -delete
|
||||
rm -rf $(env) *.egg-info
|
||||
rm -rf .pytest_cache .coverage coverage.xml
|
||||
|
||||
# Build PyScript.
|
||||
build:
|
||||
cd pyscript.core && npx playwright install && npm run build
|
||||
|
||||
# Run the precommit checks (run eslint).
|
||||
precommit-check:
|
||||
pre-commit run --all-files
|
||||
|
||||
# Run all integration tests sequentially.
|
||||
test-integration:
|
||||
mkdir -p test_results
|
||||
pytest -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml
|
||||
|
||||
# Run all integration tests in parallel.
|
||||
test-integration-parallel:
|
||||
mkdir -p test_results
|
||||
pytest --numprocesses auto -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml
|
||||
|
||||
# Format the code.
|
||||
fmt: fmt-py
|
||||
@echo "Format completed"
|
||||
|
||||
# Check the code formatting.
|
||||
fmt-check: fmt-py-check
|
||||
@echo "Format check completed"
|
||||
|
||||
# Format Python code.
|
||||
fmt-py:
|
||||
black -l 88 --skip-string-normalization .
|
||||
isort --profile black .
|
||||
|
||||
# Check the format of Python code.
|
||||
fmt-py-check:
|
||||
black -l 88 --check .
|
||||
|
||||
.PHONY: $(MAKECMDGOALS)
|
||||
48
README.md
48
README.md
@@ -4,9 +4,9 @@
|
||||
|
||||
### Summary
|
||||
|
||||
PyScript is a framework that allows users to create rich Python applications in the browser using HTML's interface and the power of [Pyodide](https://pyodide.org/en/stable/), [MicroPython](https://micropython.org/) and [WASM](https://webassembly.org/), and modern web technologies.
|
||||
PyScript is a framework that allows users to create rich Python applications in the browser using HTML's interface and the power of [Pyodide](https://pyodide.org/en/stable/), [WASM](https://webassembly.org/), and modern web technologies.
|
||||
|
||||
To get started see the [getting started tutorial](https://pyscript.github.io/docs/latest/beginning-pyscript/).
|
||||
To get started see the [getting started tutorial](docs/tutorials/getting-started.md).
|
||||
|
||||
For examples see [here](examples).
|
||||
|
||||
@@ -16,55 +16,27 @@ PyScript is a meta project that aims to combine multiple open technologies into
|
||||
|
||||
## Try PyScript
|
||||
|
||||
To try PyScript, import the appropriate pyscript files into the `<head>` tag of your html page:
|
||||
To try PyScript, import the appropriate pyscript files into the `<head>` tag of your html page with:
|
||||
|
||||
```html
|
||||
<head>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://pyscript.net/releases/2023.11.1/core.css"
|
||||
/>
|
||||
<script
|
||||
type="module"
|
||||
src="https://pyscript.net/releases/2023.11.1/core.js"
|
||||
></script>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py" terminal>
|
||||
from pyscript import display
|
||||
display("Hello World!") # this goes to the DOM
|
||||
print("Hello terminal") # this goes to the terminal
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
You can then use PyScript components in your html page. PyScript currently offers various ways of running Python code:
|
||||
You can then use PyScript components in your html page. PyScript currently implements the following elements:
|
||||
|
||||
- `<script type="py">`: can be used to define python code that is executable within the web page.
|
||||
- `<script type="py" src="hello.py">`: same as above, but the python source is fetched from the given URL.
|
||||
- `<script type="py" terminal>`: same as above, but also creates a terminal where to display stdout and stderr (e.g., the output of `print()`); `input()` does not work.
|
||||
- `<script type="py" terminal worker>`: run Python inside a web worker: the terminal if fully functional and `input()` works.
|
||||
- `<py-script>`: same as `<script type="py">`, but it is not recommended because if the code contains HTML tags, they could be parsed wrongly.
|
||||
- `<script type="mpy">`: same as above but use MicroPython instead of Python.
|
||||
- `<py-script>`: can be used to define python code that is executable within the web page. The element itself is not rendered to the page and is only used to add logic
|
||||
- `<py-repl>`: creates a REPL component that is rendered to the page as a code editor and allows users to write executable code
|
||||
|
||||
Check out the [official docs](https://docs.pyscript.net) for more detailed documentation.
|
||||
Check out the [the examples directory](examples) folder for more examples on how to use it, all you need to do is open them in Chrome.
|
||||
|
||||
## How to Contribute
|
||||
|
||||
Read the [contributing guide](CONTRIBUTING.md) to learn about our development process, reporting bugs and improvements, creating issues and asking questions.
|
||||
|
||||
Check out the [developing process](https://docs.pyscript.net/latest/contributing) documentation for more information on how to setup your development environment.
|
||||
|
||||
## Community calls and events
|
||||
|
||||
Every Tuesday at 15:30 UTC there is the _PyScript Community Call_ on zoom, where we can talk about PyScript development in the open. Most of the maintainers regularly participate in the call, and everybody is welcome to join.
|
||||
|
||||
Every other Thursday at 16:00 UTC there is the _PyScript FUN_ call: this is a call in which everybody is encouraged to show what they did with PyScript.
|
||||
|
||||
For more details on how to join the calls and up to date schedule, consult the official calendar:
|
||||
|
||||
- [Google calendar](https://calendar.google.com/calendar/u/0/embed?src=d3afdd81f9c132a8c8f3290f5cc5966adebdf61017fca784eef0f6be9fd519e0@group.calendar.google.com&ctz=UTC) in UTC time;
|
||||
- [iCal format](https://calendar.google.com/calendar/ical/d3afdd81f9c132a8c8f3290f5cc5966adebdf61017fca784eef0f6be9fd519e0%40group.calendar.google.com/public/basic.ics).
|
||||
Check out the [developing process](https://docs.pyscript.net/latest/development/developing.html) documentation for more information on how to setup your development environment.
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
46
docs/Makefile
Normal file
46
docs/Makefile
Normal file
@@ -0,0 +1,46 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
CONDA_ENV ?= _env
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
env := $(CONDA_ENV)
|
||||
conda_run := conda run -p $(env)
|
||||
|
||||
setup:
|
||||
@if [ -z "$${CONDA_SHLVL:+x}" ]; then echo "Conda is not installed." && exit 1; fi
|
||||
$(CONDA_EXE) env $(shell [ -d $(env) ] && echo update || echo create) -p $(env) --file environment.yml
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)
|
||||
|
||||
clean-all: clean
|
||||
rm -rf $(env) *.egg-info
|
||||
|
||||
shell:
|
||||
@export CONDA_ENV_PROMPT='<{name}>'
|
||||
@echo 'conda activate $(env)'
|
||||
|
||||
htmlserve: html
|
||||
@echo 'visit docs at http://localhost:8080'
|
||||
python -m http.server -d "$(BUILDDIR)/html/" 8080
|
||||
|
||||
livehtml:
|
||||
sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
|
||||
.PHONY: help Makefile setup clean clean-all shell
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
54
docs/README.md
Normal file
54
docs/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# PyScript documentation
|
||||
|
||||
Welcome to the PyScript documentation directory, where you can find
|
||||
and contribute to discussions around PyScript and related topics.
|
||||
|
||||
## Getting started
|
||||
|
||||
Before you start contributing to the documentation, it's worthwhile to
|
||||
take a look at the general contributing guidelines for the PyScript project. You can find these guidelines here
|
||||
[Contributing Guidelines](https://github.com/pyscript/pyscript/blob/main/CONTRIBUTING.md)
|
||||
|
||||
## Documentation Principles
|
||||
|
||||
The PyScript documentation is based on a documentation framework called [Diátaxis](https://diataxis.fr/). This framework helps to solve the problem of structure in technical documentation and identifies four modes of documentation - **tutorials, how-to guides, technical reference and explanation**. Each one of these modes answers to a different user need, fulfills a different purpose and requires a different approach to its creation.
|
||||
|
||||
The picture below gives a good visual representation of that separation of concerns:
|
||||
|
||||

|
||||
|
||||
So, please keep that in mind when contributing to the project documentation. For more information on, make sure to check [their website](https://diataxis.fr/).
|
||||
|
||||
### Setup
|
||||
|
||||
The `docs` directory in the pyscript repository contains a
|
||||
[Sphinx](https://www.sphinx-doc.org/) documentation project. Sphinx is a system
|
||||
that takes plaintext files containing documentation written in Markdown, along with
|
||||
static files like templates and themes, to build the static end result.
|
||||
|
||||
### Build
|
||||
|
||||
To learn how to build the docs, head over the [CONTRIBUTING](../CONTRIBUTING.md) page.
|
||||
|
||||
|
||||
## Cross-referencing
|
||||
|
||||
You can link to other pages in the documentation by using the `{doc}` role. For example, to link to the `docs/README.md` file, you would use:
|
||||
|
||||
```markdown
|
||||
{doc}`docs/README.md`
|
||||
```
|
||||
|
||||
You can also cross-reference the python glossary by using the `{term}` role. For example, to link to the `iterable` term, you would use:
|
||||
|
||||
```markdown
|
||||
{term}`iterable`
|
||||
```
|
||||
|
||||
You can also cross-reference functions, methods or data attributes by using the `{attr}` for example:
|
||||
|
||||
```markdown
|
||||
{py:func}`repr`
|
||||
```
|
||||
|
||||
This would link to the `repr` function in the python builtins.
|
||||
52
docs/_static/examples/what-is-pyscript.html
vendored
Normal file
52
docs/_static/examples/what-is-pyscript.html
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<style>
|
||||
h1 {
|
||||
color: #459db9;
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: .2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Let's plot random numbers</h1>
|
||||
<div id="plot">
|
||||
<div class="pulse" >
|
||||
<p style='font-family: monospace sans-serif;'><big><big><big><big>❰py❱</big></big></big></big></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"numpy",
|
||||
"matplotlib"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
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)
|
||||
pyscript.write('plot', fig)
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
docs/_static/fonts/Hack-Bold.woff
vendored
Normal file
BIN
docs/_static/fonts/Hack-Bold.woff
vendored
Normal file
Binary file not shown.
BIN
docs/_static/fonts/Hack-BoldItalic.woff
vendored
Normal file
BIN
docs/_static/fonts/Hack-BoldItalic.woff
vendored
Normal file
Binary file not shown.
BIN
docs/_static/fonts/Hack-Italic.woff
vendored
Normal file
BIN
docs/_static/fonts/Hack-Italic.woff
vendored
Normal file
Binary file not shown.
BIN
docs/_static/fonts/Hack-Regular.woff
vendored
Normal file
BIN
docs/_static/fonts/Hack-Regular.woff
vendored
Normal file
Binary file not shown.
16
docs/_static/images/pyscript.svg
vendored
Normal file
16
docs/_static/images/pyscript.svg
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg width="100%" viewBox="0 0 2057 974" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#fda703" stroke="none" transform="translate(0 100)">
|
||||
<path
|
||||
d="M 1092.534 158.364 C 1095.764 169.589 1102.374 179.795 1107.224 190.364 C 1119.104 216.243 1131.874 241.728 1144.274 267.364 C 1179.204 339.56 1214.064 411.844 1248.314 484.364 C 1260.474 510.112 1273.154 535.617 1285.314 561.364 C 1290.014 571.319 1299.154 583.378 1300.684 594.364 C 1301.444 599.785 1296.944 606.478 1294.984 611.364 C 1289.004 626.289 1282.004 640.557 1273.734 654.364 C 1265.284 668.483 1256.704 683.257 1245.444 695.364 C 1237.304 704.123 1228.664 712.851 1218.534 719.31 C 1176.654 746.023 1130.104 739.811 1084.534 729.364 L 1084.534 796.364 C 1137.744 803.235 1191.744 806.988 1241.534 782.094 C 1291.224 757.25 1321.144 708.125 1345.794 660.364 C 1391.424 571.949 1425.474 477.074 1463.954 385.364 C 1484.774 335.759 1505.144 285.968 1525.954 236.364 C 1532.804 220.048 1539.454 203.643 1546.384 187.364 C 1550.314 178.14 1555.824 168.274 1557.534 158.364 L 1503.534 158.364 C 1498.104 158.364 1487.624 156.363 1482.924 159.392 C 1477.284 163.031 1474.824 176.375 1472.254 182.364 C 1463.294 203.198 1455.174 224.401 1446.524 245.364 C 1422.624 303.289 1398.764 361.248 1375.334 419.364 C 1365.024 444.923 1349.894 471.569 1343.534 498.364 L 1341.534 498.364 L 1326.784 467.364 L 1300.794 414.364 L 1219.784 248.364 L 1188.284 184.364 L 1174.894 159.392 L 1152.534 158.364 L 1092.534 158.364 Z">
|
||||
</path>
|
||||
<path
|
||||
d="M 100.534 391.364 C 109.625 398.897 122.97 403.329 133.534 408.611 L 197.534 440.611 L 405.534 544.611 C 436.606 560.147 467.458 576.073 498.534 591.611 C 511.98 598.334 527.713 609.722 542.534 612.364 L 542.534 563.364 L 541.506 543.754 L 518.534 531.117 L 460.534 502.117 L 307.534 425.117 L 240.534 391.364 L 307.534 358.117 L 459.534 282.611 L 518.534 253.117 L 541.506 240.727 L 542.534 221.364 L 542.534 171.364 C 527.073 174.12 510.565 186.102 496.534 193.117 L 398.534 242.117 L 200.534 341.117 C 167.367 357.701 132.553 372.676 100.534 391.364 Z">
|
||||
</path>
|
||||
<path
|
||||
d="M 1600.534 171.364 L 1600.534 220.364 C 1600.534 225.605 1598.654 235.422 1601.564 239.974 C 1605.194 245.662 1617.614 249.159 1623.534 252.117 L 1680.534 280.611 C 1730.924 305.806 1781.134 331.41 1831.534 356.611 C 1853.974 367.829 1877.404 384.412 1901.534 391.364 L 1901.534 393.364 C 1875.624 400.829 1849.674 418.049 1825.534 430.117 L 1679.534 503.117 C 1661.964 511.903 1644.564 521.567 1626.534 529.364 C 1619.964 532.203 1605.494 536.596 1601.564 542.754 C 1598.654 547.306 1600.534 557.122 1600.534 562.364 L 1600.534 612.364 L 1655.534 585.611 L 1763.534 531.611 L 1947.534 439.611 L 2041.534 392.364 C 2031.474 382.202 2012.324 376.511 1999.534 370.117 L 1907.534 324.117 L 1701.534 221.117 L 1635.534 188.117 C 1624.294 182.495 1612.624 174.847 1600.534 171.364 Z">
|
||||
</path>
|
||||
<path
|
||||
d="M 704.534 384.364 C 704.534 374.13 702.051 360.064 705.503 350.364 C 710.589 336.071 722.183 321.459 731.164 309.364 C 737.516 300.809 743.992 292.429 750.959 284.364 C 786.81 242.863 854.576 189.488 905.519 239.403 C 931.848 265.201 939.204 301.065 941.623 336.364 C 946.631 409.413 926.04 491.22 860.534 532.928 C 811.862 563.917 757.912 556.382 704.534 545.364 Z M 705.534 259.364 L 704.534 259.364 L 704.534 158.364 L 628.534 158.364 L 628.534 789.364 L 704.534 789.364 L 704.534 613.364 C 728.157 613.38 751.915 618.29 775.534 619.325 C 816.206 621.106 857.009 614.508 893.534 596.116 C 989.069 548.011 1025.008 434.77 1024.535 335.364 C 1024.298 285.5 1013.766 232.452 979.364 194.364 C 968.209 182.013 954.851 171.287 940.534 162.816 C 875.388 124.27 794.704 158.21 745.534 207.364 C 730.887 222.007 713.84 240.114 705.534 259.364 Z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
1
docs/_static/redirect.html
vendored
Normal file
1
docs/_static/redirect.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<html><head><meta http-equiv="refresh" content="0; URL='/latest/'" /></head><body></body></html>
|
||||
4
docs/_static/s3_error.html
vendored
Normal file
4
docs/_static/s3_error.html
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<html><head><meta http-equiv="refresh" content="5; URL='/latest/'" /></head><body>
|
||||
<h1>404 - File not found</h1>
|
||||
<p>You will be redirected to the latest documentation in 5 seconds.</p>
|
||||
</body></html>
|
||||
94
docs/changelog.md
Normal file
94
docs/changelog.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Release Notes
|
||||
|
||||
2023.XX.X
|
||||
=========
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Added the `xterm` attribute to `py-config`. When set to `True` or `xterm`, an (output-only) [xterm.js](http://xtermjs.org/) terminal will be used in place of the default py-terminal.
|
||||
- The default version of Pyodide is now `0.23.2`. See the [Pyodide Changelog](https://pyodide.org/en/stable/project/changelog.html#version-0-23-2) for a detailed list of changes.
|
||||
- Added the `@when` decorator for attaching Python functions as event handlers
|
||||
- The `py-mount` attribute on HTML elements has been deprecated, and will be removed in a future release.
|
||||
|
||||
|
||||
### Runtime py- attributes
|
||||
|
||||
- Added logic to react to `py-*` attributes changes, removal, `py-*` attributes added to already live nodes but also `py-*` attributes added or defined via injected nodes (either appended or via `innerHTML` operations). ([#1435](https://github.com/pyscript/pyscript/pull/1435))
|
||||
|
||||
### <script type="py">
|
||||
- Added the ability to optionally use `<script type="py">`, `<script type="pyscript">` or `<script type="py-script">` instead of a `<py-script>` custom element, in order to tackle cases where the content of the `<py-script>` tag, inevitably parsed by browsers, could accidentally contain *HTML* able to break the surrounding page layout. ([#1396](https://github.com/pyscript/pyscript/pull/1396))
|
||||
|
||||
### <py-terminal>
|
||||
- Added a `docked` field and attribute for the `<py-terminal>` custom element, enabled by default when the terminal is in `auto` mode, and able to dock the terminal at the bottom of the page with auto scroll on new code execution.
|
||||
|
||||
### <py-script>
|
||||
- Restored the `output` attribute of `py-script` tags to route `sys.stdout` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063))
|
||||
- Added a `stderr` attribute of `py-script` tags to route `sys.stderr` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063))
|
||||
|
||||
### <py-repl>
|
||||
- The `output` attribute of `py-repl` tags now specifies the id of the DOM element that `sys.stdout`, `sys.stderr`, and the results of a REPL execution are written to. It no longer affects the location of calls to `display()`
|
||||
- Added a `stderr` attribute of `py-repl` tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
|
||||
- Resored the `output-mode` attribute of `py-repl` tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results.
|
||||
- Load code from the attribute src of py-repl and preload it into the corresponding py-repl tag by use the attribute `str` in your `py-repl` tag([#1292](https://github.com/pyscript/pyscript/pull/1292))
|
||||
|
||||
### Plugins
|
||||
- Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a `py-repl` tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
|
||||
|
||||
### Web worker support
|
||||
- introduced the new experimental `execution_thread` config option: if you set `execution_thread = "worker"`, the python interpreter runs inside a web worker
|
||||
- worker support is still **very** experimental: not everything works, use it at your own risk
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fixes [#1280](https://github.com/pyscript/pyscript/issues/1280), which describes the errors on the PyRepl tests related to having auto-gen tags that shouldn't be there.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
- Py-REPL tests now run on both osx and non osx OSs
|
||||
- migrated from *rollup* to *esbuild* to create artifacts
|
||||
- updated `@codemirror` dependency to its latest
|
||||
|
||||
Docs
|
||||
----
|
||||
|
||||
- Add docs for event handlers
|
||||
|
||||
2023.01.1
|
||||
=========
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fixed an issue where `pyscript` would not be available when using the minified version of PyScript. ([#1054](https://github.com/pyscript/pyscript/pull/1054))
|
||||
- Fixed missing closing tag when rendering an image with `display`. ([#1058](https://github.com/pyscript/pyscript/pull/1058))
|
||||
- Fixed a bug where Python plugins methods were being executed twice. ([#1064](https://github.com/pyscript/pyscript/pull/1064))
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
- When adding a `py-` attribute to an element but didn't added an `id` attribute, PyScript will now generate a random ID for the element instead of throwing an error which caused the splash screen to not shutdown. ([#1122](https://github.com/pyscript/pyscript/pull/1122))
|
||||
- You can now disable the splashscreen by setting `enabled = false` in your `py-config` under the `[splashscreen]` configuration section. ([#1138](https://github.com/pyscript/pyscript/pull/1138))
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
- Fixed 'Direct usage of document is deprecated' warning in the getting started guide. ([#1052](https://github.com/pyscript/pyscript/pull/1052))
|
||||
- Added reference documentation for the `py-splashscreen` plugin ([#1138](https://github.com/pyscript/pyscript/pull/1138))
|
||||
- Adds doc for installing tests ([#1156](https://github.com/pyscript/pyscript/pull/1156))
|
||||
- Adds docs for custom Pyscript attributes (`py-*`) that allow you to add event listeners to an element ([#1125](https://github.com/pyscript/pyscript/pull/1125))
|
||||
|
||||
Deprecations and Removals
|
||||
-------------------------
|
||||
|
||||
- The py-config `runtimes` to specify an interpreter has been deprecated. The `interpreters` config should be used instead. ([#1082](https://github.com/pyscript/pyscript/pull/1082))
|
||||
- The attributes `pys-onClick` and `pys-onKeyDown` have been deprecated, but the warning was only shown in the console. An alert banner will now be shown on the page if the attributes are used. They will be removed in the next release. ([#1084](https://github.com/pyscript/pyscript/pull/1084))
|
||||
- The pyscript elements `py-button`, `py-inputbox`, `py-box` and `py-title` have now completed their deprecation cycle and have been removed. ([#1084](https://github.com/pyscript/pyscript/pull/1084))
|
||||
- The attributes `pys-onClick` and `pys-onKeyDown` have been removed. Use `py-click` and `py-keydown` instead ([#1361](https://github.com/pyscript/pyscript/pull/1361))
|
||||
1
docs/concepts/governance/maintainers.md
Symbolic link
1
docs/concepts/governance/maintainers.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../MAINTAINERS.md
|
||||
1
docs/concepts/governance/policy.md
Symbolic link
1
docs/concepts/governance/policy.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../GOVERNANCE.md
|
||||
12
docs/concepts/index.md
Normal file
12
docs/concepts/index.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Concepts
|
||||
|
||||
This section contains various topics that are higher-level and useful to know.
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 2
|
||||
glob:
|
||||
---
|
||||
what-is-pyscript
|
||||
governance/*
|
||||
```
|
||||
33
docs/concepts/what-is-pyscript.md
Normal file
33
docs/concepts/what-is-pyscript.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# What is PyScript?
|
||||
|
||||
The PyScript library provides HTML tags for embedding and executing Python code in your browser. PyScript is built using [Pyodide](https://pyodide.org/en/stable/), the WebAssembly port of CPython, which is compiled using [Emscripten](https://emscripten.org/).
|
||||
|
||||
PyScript turns the browser into a code deployment tool that anyone can learn to use.
|
||||
|
||||
## Example
|
||||
|
||||
In this example, we are using the `<py-script>` HTML tag to generate a Matplotlib figure and display it as an image.
|
||||
Click **Preview** to see the rendered HTML.
|
||||
|
||||
To try it in your browser, copy the code below into an online HTML editor like W3School's [Tryit Editor](https://www.w3schools.com/html/tryit.asp?filename=tryhtml_default_default), which allows you to modify, run, and even save your code. Watch the video below to see it in action!
|
||||
|
||||
```{youtube} ZtC7TCt_LhU
|
||||
```
|
||||
|
||||
::::{tab-set}
|
||||
:::{tab-item} HTML Source
|
||||
|
||||
```{literalinclude} ../_static/examples/what-is-pyscript.html
|
||||
---
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} Preview
|
||||
|
||||
```{raw} html
|
||||
<iframe height="600px" width="100%" scrolling="auto" frameborder="0" src="../_static/examples/what-is-pyscript.html"></iframe>
|
||||
```
|
||||
|
||||
:::
|
||||
::::
|
||||
108
docs/conf.py
Normal file
108
docs/conf.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = "PyScript"
|
||||
copyright = "(c) 2023, Anaconda, Inc."
|
||||
author = "Anaconda, Inc."
|
||||
language = "en"
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"myst_parser",
|
||||
"sphinx_copybutton",
|
||||
"sphinx_design",
|
||||
"sphinx_togglebutton",
|
||||
"sphinx_sitemap",
|
||||
"sphinxemoji.sphinxemoji",
|
||||
"sphinxcontrib.youtube",
|
||||
"sphinx.ext.intersphinx",
|
||||
]
|
||||
|
||||
intersphinx_mapping = {
|
||||
"python": ("https://docs.python.org/3.10", None),
|
||||
}
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "_env", "README.md"]
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = "pydata_sphinx_theme"
|
||||
|
||||
html_logo = "_static/images/pyscript.svg"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
# html_css_files = ["styles/custom.css"]
|
||||
|
||||
html_baseurl = os.environ.get("SPHINX_HTML_BASE_URL", "http://127.0.0.1:8000/")
|
||||
sitemap_locales = [None]
|
||||
sitemap_url_scheme = "{link}"
|
||||
|
||||
html_extra_path = ["robots.txt"]
|
||||
|
||||
html_theme_options = {
|
||||
"github_url": "https://github.com/pyscript/pyscript",
|
||||
"twitter_url": "https://twitter.com/pyscript_dev",
|
||||
"icon_links_label": "Quick Links",
|
||||
# "google_analytics_id": "G-XXXXXXXXXX",
|
||||
"use_edit_page_button": True,
|
||||
"show_nav_level": 2,
|
||||
"external_links": [
|
||||
# {"name": "GitHub repo", "url": "https://github.com/pyscript/pyscript"},
|
||||
],
|
||||
}
|
||||
|
||||
html_context = {
|
||||
"default_mode": "dark",
|
||||
"pygment_light_style": "tango",
|
||||
"pygment_dark_style": "native",
|
||||
"github_user": "pyscript",
|
||||
"github_repo": "pyscript",
|
||||
"github_version": "main",
|
||||
"doc_path": "docs",
|
||||
}
|
||||
|
||||
|
||||
myst_enable_extensions = [
|
||||
"dollarmath",
|
||||
"amsmath",
|
||||
"deflist",
|
||||
"html_admonition",
|
||||
"html_image",
|
||||
"colon_fence",
|
||||
"smartquotes",
|
||||
"replacements",
|
||||
]
|
||||
38
docs/development/deprecation-cycle.md
Normal file
38
docs/development/deprecation-cycle.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Deprecation Cycle
|
||||
|
||||
Pyscript is under heavy development, which means that some things may change, and some features might need to be deprecated so you can use different alternative implementations.
|
||||
|
||||
This page describes the deprecation cycle for pyscript.
|
||||
|
||||
## Deprecation Steps
|
||||
|
||||
1. Remove usage of deprecated features from all examples.
|
||||
2. Add warnings to all elements/features marked for deprecation.
|
||||
3. Release a new version of pyscript with the deprecation warnings.
|
||||
4. Next release, remove the deprecated features.
|
||||
|
||||
## Deprecation Warnings
|
||||
|
||||
Deprecation warnings are added to the codebase using the `showWarning` function from the `pyscriptjs.utils` module.
|
||||
|
||||
This function creates a warning banner on the page if any of the deprecated features was used. You can use HTML to write the message; ideally, you should provide an alternative to the deprecated feature.
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
import {showWarning} from './utils'
|
||||
|
||||
showWarning(`
|
||||
<p>
|
||||
The <code>py-deprecated</code> tag is deprecated. Please use the <code>py-actual</code> tag instead. Please refer to <a href="#">this documentation page</a> for more information.
|
||||
</p>
|
||||
`, "html")
|
||||
```
|
||||
|
||||
## Deprecation History
|
||||
|
||||
This section tracks deprecations of specific features, both for historical record and to help the development team remember to actually remove deprecated features in future releases.
|
||||
|
||||
|Attribute/Object/Functionality|Deprecated In|Removed In|
|
||||
|-|-|-|
|
||||
|`py-mount` attribute | (Release following 2023.03.1) | -|
|
||||
222
docs/development/developing.md
Normal file
222
docs/development/developing.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# Development Process
|
||||
|
||||
This document is intended to help you get started in developing software for the PyScript project. It assumes that you have [a working development environment](setting-up-environment.md). It also assumes you have a remote named `upstream` pointing to PyScript's repository and one named `origin` pointing to your own repository.
|
||||
|
||||
* 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 changes
|
||||
|
||||
**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 test
|
||||
```
|
||||
|
||||
To learn more about tests please refer to the session [Quick guide to pytest](## Quick guide to pytest).
|
||||
|
||||
* 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 the linter you should fix it <u>**before**</u> creating a pull request.
|
||||
|
||||
#### Rebasing changes
|
||||
|
||||
Sometimes you might be asked to rebase the main branch into your local branch. Please refer to this [section on git rebase from GitHub docs](https://docs.github.com/en/get-started/using-git/about-git-rebase).
|
||||
|
||||
If you need help with anything, feel free to reach out and ask for help!
|
||||
|
||||
|
||||
## Updating the changelog
|
||||
|
||||
As you work on your changes, please update the changelog file `changelog.md` with a short description of the changes you made. This will help us keep track of what has changed in each release.
|
||||
|
||||
You can look at the [changelog](../changelog.md) for examples on how to add your changes to the changelog. But here's a quick example:
|
||||
|
||||
```
|
||||
2023.02.01
|
||||
=========
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fixed a bug that was causing the app to crash when you tried to do something #PR_NUMBER
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
- Made awesome new feature #PR_NUMBER
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
- Added a new section to the docs #PR_NUMBER
|
||||
|
||||
```
|
||||
|
||||
## Quick guide to pytest
|
||||
|
||||
We make heavy usage of `pytest`. Here is a quick guide and collection of
|
||||
useful options:
|
||||
|
||||
- To run all tests in the current directory and subdirectories: `pytest`
|
||||
|
||||
- To run tests in a specific directory or file: `pytest path/to/dir/test_foo.py`
|
||||
|
||||
- `-s`: disables output capturing
|
||||
|
||||
- `--pdb`: in case of exception, enter a `(Pdb)` prompt so that you can
|
||||
inspect what went wrong.
|
||||
|
||||
- `-v`: verbose mode
|
||||
|
||||
- `-x`: stop the execution as soon as one test fails
|
||||
|
||||
- `-k foo`: run only the tests whose full name contains `foo`
|
||||
|
||||
- `-k 'foo and bar'`
|
||||
|
||||
- `-k 'foo and not bar'`
|
||||
|
||||
|
||||
### Running integration tests under pytest
|
||||
|
||||
`make test` is useful to run all the tests, but during the development is
|
||||
useful to have more control on how tests are run. The following guide assumes
|
||||
that you are in the directory `pyscriptjs/tests/integration/`.
|
||||
|
||||
#### To run all the integration tests, single or multi core
|
||||
|
||||
```
|
||||
$ pytest -xv
|
||||
...
|
||||
|
||||
test_00_support.py::TestSupport::test_basic[chromium] PASSED [ 0%]
|
||||
test_00_support.py::TestSupport::test_console[chromium] PASSED [ 1%]
|
||||
test_00_support.py::TestSupport::test_check_js_errors_simple[chromium] PASSED [ 2%]
|
||||
test_00_support.py::TestSupport::test_check_js_errors_expected[chromium] PASSED [ 3%]
|
||||
test_00_support.py::TestSupport::test_check_js_errors_expected_but_didnt_raise[chromium] PASSED [ 4%]
|
||||
test_00_support.py::TestSupport::test_check_js_errors_multiple[chromium] PASSED [ 5%]
|
||||
...
|
||||
```
|
||||
|
||||
`-x` means "stop at the first failure". `-v` means "verbose", so that you can
|
||||
see all the test names one by one. We try to keep tests in a reasonable order,
|
||||
from most basic to most complex. This way, if you introduced some bug in very
|
||||
basic things, you will notice immediately.
|
||||
|
||||
If you have the `pytest-xdist` plugin installed, you can run all the
|
||||
integration tests on 4 cores in parallel:
|
||||
```
|
||||
$ pytest -n 4
|
||||
```
|
||||
|
||||
#### To run a single test, headless
|
||||
```
|
||||
$ pytest test_01_basic.py -k test_pyscript_hello -s
|
||||
...
|
||||
[ 0.00 page.goto ] pyscript_hello.html
|
||||
[ 0.01 request ] 200 - fake_server - http://fake_server/pyscript_hello.html
|
||||
...
|
||||
[ 0.17 console.info ] [py-loader] Downloading pyodide-x.y.z...
|
||||
[ 0.18 request ] 200 - CACHED - https://cdn.jsdelivr.net/pyodide/vx.y.z/full/pyodide.js
|
||||
...
|
||||
[ 3.59 console.info ] [pyscript/main] PyScript page fully initialized
|
||||
[ 3.60 console.log ] hello pyscript
|
||||
```
|
||||
|
||||
`-k` selects tests by pattern matching as described above. `-s` instructs
|
||||
`pytest` to show the output to the terminal instead of capturing it. In the
|
||||
output you can see various useful things, including network requests and JS
|
||||
console messages.
|
||||
|
||||
#### To run a single test, headed
|
||||
```
|
||||
$ pytest test_01_basic.py -k test_pyscript_hello -s --headed
|
||||
...
|
||||
```
|
||||
|
||||
Same as above, but with `--headed` the browser is shown in a window, and you
|
||||
can interact with it. The browser uses a fake server, which means that HTTP
|
||||
requests are cached.
|
||||
|
||||
Unfortunately, in this mode source maps does not seem to work, and you cannot
|
||||
debug the original typescript source code. This seems to be a bug in
|
||||
playwright, for which we have a workaround:
|
||||
|
||||
```
|
||||
$ pytest test_01_basic.py -k test_pyscript_hello -s --headed --no-fake-server
|
||||
...
|
||||
```
|
||||
|
||||
As the name implies, `-no-fake-server` disables the fake server: HTTP requests
|
||||
are not cached, but source-level debugging works.
|
||||
|
||||
Finally:
|
||||
|
||||
```
|
||||
$ pytest test_01_basic.py -k test_pyscript_hello -s --dev
|
||||
...
|
||||
```
|
||||
|
||||
`--dev` implies `--headed --no-fake-server`. In addition, it also
|
||||
automatically open chrome dev tools.
|
||||
|
||||
#### To run only main thread or worker tests
|
||||
|
||||
By default, we run each test twice: one with `execution_thread = "main"` and
|
||||
one with `execution_thread = "worker"`. If you want to run only half of them,
|
||||
you can use `-m`:
|
||||
|
||||
```
|
||||
$ pytest -m main # run only the tests in the main thread
|
||||
$ pytest -m worker # ron only the tests in the web worker
|
||||
```
|
||||
|
||||
## Fake server, HTTP cache
|
||||
|
||||
By default, our test machinery uses a playwright router which intercepts and
|
||||
cache HTTP requests, so that for example you don't have to download pyodide
|
||||
again and again. This also enables the possibility of running tests in
|
||||
parallel on multiple cores.
|
||||
|
||||
The cache is stored using the `pytest-cache` plugin, which means that it
|
||||
survives across sessions.
|
||||
|
||||
If you want to temporarily disable the cache, the easiest thing is to use
|
||||
`--no-fake-server`, which bypasses it completely.
|
||||
|
||||
If you want to clear the cache, you can use the special option
|
||||
`--clear-http-cache`:
|
||||
|
||||
**NOTE**: this works only if you are inside `tests/integration`, or if you
|
||||
explicitly specify `tests/integration` from the command line. This is due to
|
||||
how `pytest` decides to search for and load the various `conftest.py`.
|
||||
12
docs/development/index.md
Normal file
12
docs/development/index.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Development
|
||||
|
||||
This section contains various topics related to pyscript development.
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
---
|
||||
setting-up-environment
|
||||
deprecation-cycle
|
||||
developing
|
||||
```
|
||||
285
docs/development/setting-up-environment.md
Normal file
285
docs/development/setting-up-environment.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# Setting up your development environment
|
||||
|
||||
These steps will help you set up your development environment. We suggest completing each step before going to the next step, as some parts have dependencies on previous commands.
|
||||
|
||||
## Prepare your repository
|
||||
|
||||
* Create a fork of the [PyScript github repository](https://github.com/pyscript/pyscript/fork) to your github.
|
||||
|
||||
* In your development machine, clone your fork of PyScript. Use this command in your terminal.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/<your username>/pyscript
|
||||
```
|
||||
|
||||
* With the following command, add the original project as your upstream. This will allow you to pull the latest changes.
|
||||
|
||||
```sh
|
||||
git remote add upstream https://github.com/pyscript/pyscript.git
|
||||
git pull upstream main
|
||||
```
|
||||
|
||||
* If the above fails, try this alternative:
|
||||
|
||||
```sh
|
||||
git remote remove upstream
|
||||
git remote add upstream git@github.com:pyscript/pyscript.git
|
||||
git pull upstream main
|
||||
```
|
||||
|
||||
## Install the dependencies
|
||||
|
||||
* change directory into `pyscriptjs` using this command:
|
||||
|
||||
```sh
|
||||
cd pyscript/pyscriptjs
|
||||
```
|
||||
|
||||
We need to ensure that we have installed `conda`, `nodejs` >= 16 and `make`, before we can continue.
|
||||
|
||||
* Install `conda` by downloading one of the following packages that include it [MiniConda](https://docs.conda.io/en/latest/miniconda.html) or [Anaconda](https://www.anaconda.com/download/).
|
||||
|
||||
* Install `nodejs` with at least version 16. This can be downloaded at [https://nodejs.org](https://nodejs.org)
|
||||
|
||||
* Ensure that `make` is available on your system:
|
||||
|
||||
* *Linux*. `make` is usually installed by default in most Linux distributions. In the case it is not installed, run the terminal command `sudo apt install make`.
|
||||
|
||||
* *OS X*. Install Apple Developer Tools. `make` is included in this package.
|
||||
|
||||
* *Windows*. It is recommended to use either Windows Subsystem for Linux (WSL) or GNUWin32 for installing `make`. Instructions can be found [in this StackOverflow question](https://stackoverflow.com/questions/32127524/how-to-install-and-use-make-in-windows).
|
||||
|
||||
* The following command will download and install the rest of the PyScript dependencies:
|
||||
|
||||
```
|
||||
make setup
|
||||
```
|
||||
|
||||
* **NOTE**: If `make setup` gives an error on an incompatible version for node or npm, please refer to [troubleshooting](https://github.com/pyscript/pyscript/blob/main/TROUBLESHOOTING.md).
|
||||
|
||||
## Activating the environment
|
||||
|
||||
* After the above `make setup` command is completed, it will print out the command for activating the environment using the following format. Use this to work on the development environment:
|
||||
|
||||
```
|
||||
conda activate <environment name>
|
||||
```
|
||||
|
||||
## Deactivating the environment
|
||||
|
||||
* To deactivate the environment, use the following command:
|
||||
```
|
||||
conda deactivate
|
||||
```
|
||||
|
||||
|
||||
# Running PyScript examples server
|
||||
|
||||
The examples server is used to view and edit the example files.
|
||||
|
||||
* change directory into `pyscriptjs` using this command:
|
||||
|
||||
```sh
|
||||
cd pyscript/pyscriptjs
|
||||
```
|
||||
|
||||
* To build the examples, run this command:
|
||||
|
||||
```
|
||||
make examples
|
||||
```
|
||||
|
||||
* To serve the examples, run this command:
|
||||
|
||||
```sh
|
||||
python -m http.server 8080 --directory examples
|
||||
```
|
||||
|
||||
* Alternately, you can also run this command if conda is not activated:
|
||||
|
||||
```sh
|
||||
conda run -p <environment name> python -m http.server 8080 --directory examples
|
||||
```
|
||||
|
||||
* You can access the examples server by visiting the following url in your browser: [http://localhost:8080](http://localhost:8080)
|
||||
|
||||
|
||||
# Running the PyScript development server
|
||||
|
||||
The PyScript development server will regularly check for any changes in the src directory. If any changes were detected, the server will rebuild itself to reflect the changes. This is useful for development with PyScript.
|
||||
|
||||
* change directory into `pyscriptjs` using this command:
|
||||
|
||||
```sh
|
||||
cd pyscript/pyscriptjs
|
||||
```
|
||||
|
||||
* Use the following command to build and run the PyScript dev server.
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
* You can access the PyScript development server by visiting the following url in your browser: [http://localhost:8080](http://localhost:8080)
|
||||
|
||||
# Setting up the test environment
|
||||
|
||||
A key to good development is to perform tests before sending a Pull Request for your changes.
|
||||
|
||||
## Install the dependencies
|
||||
|
||||
* change directory into `pyscriptjs` using this command:
|
||||
|
||||
```sh
|
||||
cd pyscript/pyscriptjs
|
||||
```
|
||||
|
||||
* The following command will download the dependencies needed for running the tests:
|
||||
|
||||
```
|
||||
make setup
|
||||
```
|
||||
|
||||
* If you are not using a conda environment, or wish to install the dependencies manually, here are the packages needed:
|
||||
* `pillow`
|
||||
* `requests`
|
||||
* `numpy`
|
||||
* `playwright`
|
||||
* `pytest-playwright`. Note that this is only available as a `pip` package.
|
||||
|
||||
## Activating the environment
|
||||
|
||||
* After the above `make setup` command is completed, it will print out the command for activating the environment using the following format:
|
||||
|
||||
```
|
||||
conda activate <environment name>
|
||||
```
|
||||
|
||||
## Deactivating the environment
|
||||
|
||||
* To deactivate the environment, use the following command:
|
||||
```
|
||||
conda deactivate
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
* After setting up the test environment and while the environment is activated, you can run the tests with the following command:
|
||||
|
||||
```
|
||||
make test
|
||||
```
|
||||
|
||||
For more information about PyScript's testing framework, head over to the [development process](developing.md) page.
|
||||
|
||||
# Setting up your documentation environment
|
||||
|
||||
The documentation environment is separate from the development environment. It is used for updating and reviewing the documentation before deployment.
|
||||
|
||||
## Installing the dependencies
|
||||
|
||||
* change directory into the `docs` using this command:
|
||||
|
||||
```sh
|
||||
cd pyscript/docs
|
||||
```
|
||||
|
||||
* The following command will download, install the dependencies, and create the environment for you:
|
||||
|
||||
```
|
||||
make setup
|
||||
```
|
||||
|
||||
(activating-documentation-environment)=
|
||||
## Activating the environment
|
||||
|
||||
* After the above `make setup` command is completed, it will print out the command for activating the environment using the following format:
|
||||
|
||||
```
|
||||
conda activate <docs environment name>
|
||||
```
|
||||
|
||||
Note that the docs environment path is different from the developer's environment path.
|
||||
|
||||
## Deactivating the environment
|
||||
|
||||
* To deactivate the environment, use the following command:
|
||||
```
|
||||
conda deactivate
|
||||
```
|
||||
|
||||
## Contributing to the documentation
|
||||
|
||||
* Before sending a pull request, we recommend that your documentation conforms with [PyScript's code of conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md) and with the general principles of [Diataxis](https://diataxis.fr/). 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). This is similar to Markdown but with some addons to create the documentation infrastructure.
|
||||
|
||||
## Reviewing your work
|
||||
|
||||
* Before sending a Pull Request, review your work by starting the documentation server. To do this, use the following command:
|
||||
|
||||
```
|
||||
make livehtml
|
||||
```
|
||||
|
||||
You can visit the documentation server by opening a browser and visiting [http://127.0.0.1:8000](http://127.0.0.1:8000).
|
||||
|
||||
* Alternately, you can open a static documentation server. Unlike the above, this will not automatically update any changes done after running this server. To see the changes done, you will need to manually stop and restart the server. To do this, use the following command:
|
||||
|
||||
```
|
||||
make htmlserve
|
||||
```
|
||||
|
||||
You can visit the documentation server by opening a browser and visiting [http://127.0.0.1:8080](http://127.0.0.1:8080).
|
||||
|
||||
* To stop either server, press `ctrl+C` or `command+C` while the shell running the command is active.
|
||||
|
||||
* Note: If the above make commands failed, you need to activate the documentation environment first before running any of the commands. Refer to [Activating the environment](#activating-documentation-environment) section above.
|
||||
|
||||
# PyScript Demonstrator
|
||||
|
||||
A simple webapp to demonstrate the capabilities of PyScript.
|
||||
|
||||
## Getting started
|
||||
|
||||
1. If you don't already have Node.js, install it. The official installer for the
|
||||
LTS version of Node is available from [nodejs.org](https://nodejs.org/).
|
||||
|
||||
2. If you don't already have Rollup, install it. Rollup can be installed as a
|
||||
global resource using:
|
||||
|
||||
$ npm install --global rollup
|
||||
|
||||
3. Install the demo apps requirements:
|
||||
|
||||
$ npm install
|
||||
|
||||
4. Start the server:
|
||||
|
||||
$ npm run dev
|
||||
|
||||
This will compile the resources for the app, and start the development server.
|
||||
|
||||
5. When the compilation completes, it will display something like:
|
||||
|
||||
Your application is ready~! 🚀
|
||||
|
||||
- Local: http://localhost:8080
|
||||
- Network: Add `--host` to expose
|
||||
|
||||
────────────────── LOGS ──────────────────
|
||||
|
||||
Once this is visible, open a browser at
|
||||
[http://localhost:8080](http://localhost:8080). This will provide a list of
|
||||
demos that you can run.
|
||||
|
||||
## More information
|
||||
|
||||
For more information:
|
||||
|
||||
* [Discussion board](https://community.anaconda.cloud/c/tech-topics/pyscript)
|
||||
* [Home Page](https://pyscript.net/)
|
||||
* [Blog Post](https://engineering.anaconda.com/2022/04/welcome-pyscript.html)
|
||||
* [Discord Channel](https://discord.gg/BYB2kvyFwm)
|
||||
|
||||
We use Discord as the main place for our discussions
|
||||
19
docs/environment.yml
Normal file
19
docs/environment.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
channels:
|
||||
- conda-forge
|
||||
- defaults
|
||||
dependencies:
|
||||
- python=3.9
|
||||
- pip=20.2.2
|
||||
- Sphinx=4.5.0
|
||||
- myst-parser=0.17.2
|
||||
- pydata-sphinx-theme
|
||||
- sphinx-copybutton
|
||||
- sphinx-design
|
||||
- sphinx-togglebutton
|
||||
- nodejs=16
|
||||
|
||||
- pip:
|
||||
- sphinxemoji
|
||||
- sphinx-sitemap
|
||||
- sphinx-autobuild
|
||||
- sphinxcontrib-youtube
|
||||
3
docs/error.md
Normal file
3
docs/error.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Not found!
|
||||
|
||||
The page that you looked for could not be found. If you think this is a mistake, please [open an issue](https://github.com/pyscript/pyscript/issues/new/)
|
||||
35
docs/guides/asyncio.md
Normal file
35
docs/guides/asyncio.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Using Async/Await and Asyncio
|
||||
|
||||
## {bdg-warning-line}`Deprecated` Implicit Coroutine Scheduling / Top-Level Await
|
||||
|
||||
In PyScript versions 2022.09.1 and earlier, \<py-script\> tags could be written in a way that enabled "Implicit Coroutine Scheduling." The keywords `await`, `async for` and `await with` were permitted to be used outside of `async` functions. Any \<py-script\> tags with these keywords at the top level were compiled into coroutines and automatically scheuled to run in the browser's event loop. This functionality was deprecated, and these keywords are no longer allowed outside of `async` functions.
|
||||
|
||||
To transition code from using top-level await statements to the currently-acceptable syntax, wrap the code into a coroutine using `async def()` and schedule it to run in the browser's event looping using `asyncio.ensure_future()` or `asyncio.create_task()`.
|
||||
|
||||
The following two pieces of code are functionally equivalent - the first only works in versions 2022.09.1, the latter is the currently acceptable equivalent.
|
||||
|
||||
```python
|
||||
# This version is deprecated, since
|
||||
# it uses 'await' outside an async function
|
||||
<py-script>
|
||||
import asyncio
|
||||
|
||||
for i in range(3):
|
||||
print(i)
|
||||
await asyncio.sleep(1)
|
||||
</py-script>
|
||||
```
|
||||
|
||||
```python
|
||||
# This version is acceptable
|
||||
<py-script>
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
for i in range(3):
|
||||
print(i)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
asyncio.ensure_future(main())
|
||||
</py-script>
|
||||
```
|
||||
225
docs/guides/custom-plugins.md
Normal file
225
docs/guides/custom-plugins.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Creating custom pyscript plugins
|
||||
|
||||
Pyscript has a few built-in plugins, but you can also create your own ones. This guide will show you how to develop both Javascript and Python plugins.
|
||||
|
||||
```{warning}
|
||||
Pyscript plugins are currently under active development. The API is likely to go through breaking changes between releases.
|
||||
```
|
||||
|
||||
You can add your custom plugins to the `<py-config>` tag on your page. For example:
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
plugins = ["http://example.com/hello-world.py"]
|
||||
</py-config>
|
||||
```
|
||||
|
||||
Currently, only single files with the extension `.py` and `.js` files can be used as plugins.
|
||||
|
||||
## Python plugins
|
||||
|
||||
Python plugins allow you to write plugins in pure Python. We first need to import `Plugin` from `pyscript` and create a new instance of it.
|
||||
|
||||
```python
|
||||
from pyscript import Plugin
|
||||
|
||||
plugin = Plugin("PyHelloWorld")
|
||||
```
|
||||
|
||||
We can now create a new class containing our plugin code to add the text "Hello World" to the page.
|
||||
|
||||
```python
|
||||
from pyscript import Plugin, js
|
||||
|
||||
plugin = Plugin("PyHelloWorld")
|
||||
|
||||
class PyHelloWorld:
|
||||
def __init__(self, element):
|
||||
self.element = element
|
||||
|
||||
|
||||
def connect(self):
|
||||
self.element.innerHTML = "<h1>Hello World!</h1>"
|
||||
```
|
||||
|
||||
Let's now create our `index.html` page and add the plugin.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Python Plugin</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/unstable/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/unstable/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
plugins = ["./hello-world.py"]
|
||||
</py-config>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Now we need to start a live server to serve our page. You can use Python's `http.server` module for this.
|
||||
|
||||
```bash
|
||||
python -m http.server
|
||||
```
|
||||
|
||||
Now you can open your browser and go to `http://localhost:8000` to see the page. You might be surprised that the text "Hello World" is not on the page. This is because we need to do a few more things to make our plugin work.
|
||||
|
||||
First, we must create a custom element that our plugin will use. We can use a decorator in our `PyHelloWorld` class.
|
||||
|
||||
```python
|
||||
from pyscript import Plugin, js
|
||||
|
||||
plugin = Plugin("PyHelloWorld")
|
||||
|
||||
@plugin.register_custom_element("py-hello-world")
|
||||
class PyHelloWorld:
|
||||
def __init__(self, element):
|
||||
self.element = element
|
||||
|
||||
def connect(self):
|
||||
self.element.innerHTML = "<div id='hello'>Hello World!</div>"
|
||||
```
|
||||
|
||||
Now that we have registered our custom element, we can use the custom tag `<py-hello-world>` to add our plugin to the page.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Python Plugin</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/unstable/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/unstable/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
plugins = ["./hello-world.py"]
|
||||
</py-config>
|
||||
|
||||
<py-hello-world></py-hello-world>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Now, if you go to `http://localhost:8000` you should see the text "Hello World" on the page.
|
||||
|
||||
Writing plugins in Python is an excellent way if you want to use PyScript's API's. However, if you want to write plugins in Javascript, you can do that too.
|
||||
|
||||
## Javascript plugins
|
||||
|
||||
Javascript plugins need to have a specific structure to be loaded by PyScript. The plugin export a default class with the following method, which may implement any, all, or none of the [Plugin lifecycle methods](https://github.com/pyscript/pyscript/blob/main/pyscriptjs/src/plugin.ts#L9-L65). These method will be called at the corresponding points in lifecycle of PyScript as it loads, configures itself and its Python interpreter, and executes `<py-script>` and `<py-repl>` tags.
|
||||
|
||||
```{note}
|
||||
You need to specify the file extension `.js` when adding your custom plugin to the `<py-config>` tag.
|
||||
```
|
||||
|
||||
### Creating a Hello World plugin
|
||||
|
||||
Let's create a simple plugin that will add the text "Hello World" to the page. We will create a `hello-world.js` file and write the plugin class.
|
||||
|
||||
```js
|
||||
export default class HelloWorldPlugin {
|
||||
afterStartup(runtime) {
|
||||
// Code goes here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we need to add the code that will add the text to the page.
|
||||
|
||||
```js
|
||||
export default class HelloWorldPlugin {
|
||||
afterStartup(runtime) {
|
||||
const elem = document.createElement("h1");
|
||||
elem.innerText = "Hello World";
|
||||
document.body.appendChild(elem);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally, we need to add the plugin to our page's `<py-config>` tag.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Javascript Plugin</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/unstable/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/unstable/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
plugins = ["./hello-world.js"]
|
||||
</py-config>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Now we need to start a live server to serve our page. You can use Python's `http.server` module for this.
|
||||
|
||||
```bash
|
||||
python -m http.server
|
||||
```
|
||||
|
||||
Now you can open your browser and go to `http://localhost:8000` to see the page. You should see the text "Hello World" on the page.
|
||||
|
||||
```{note}
|
||||
Because we are using a local file, you must start a live server. Otherwise, Pyscript will not be able to fetch the file.
|
||||
```
|
||||
|
||||
### Expanding the Hello World plugin
|
||||
|
||||
As you can see, we could build all our plugin logic inside the `afterStartup` method. You may also want to create a custom html element for your plugin. Let's see how we can do that.
|
||||
|
||||
First, we need to create a custom html element. Let's start by creating our `PyHelloWorld` class that extends the `HTMLElement` class.
|
||||
|
||||
|
||||
```js
|
||||
class PyHelloWorld extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.innerHTML = `<h1>Hello, world!</h1>`;
|
||||
this.mount_name = this.id;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can now register our custom element in the `afterStartup` method of our `HelloWorldPlugin` class. We will also add the custom tag `py-hello-world` to the page.
|
||||
|
||||
```js
|
||||
export default class HelloWorldPlugin {
|
||||
afterStartup(runtime) {
|
||||
// Create a custom element called <py-hello-world>
|
||||
customElements.define("py-hello-world", PyHelloWorld);
|
||||
|
||||
// Add the custom element to the page so we can see it
|
||||
const elem = document.createElement('py-hello-world');
|
||||
document.body.append(elem);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we can open our page and see the custom element on the page.
|
||||
|
||||
By now, you should have a good idea for creating a custom plugin. Also, how powerful it can be to create custom elements that other users could use in their PyScript pages.
|
||||
179
docs/guides/event-handlers.md
Normal file
179
docs/guides/event-handlers.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Event handlers in PyScript
|
||||
|
||||
PyScript offer two ways to subscribe to Javascript event handlers:
|
||||
|
||||
## Subscribe to event with `py-*` attributes
|
||||
|
||||
The value of the attribute contains python code which will be executed when the event is fired. A very common pattern is to call a function which does further work, for example:
|
||||
|
||||
```html
|
||||
<button id="noParam" py-click="say_hello_no_param()">
|
||||
No Event - No Params py-click
|
||||
</button>
|
||||
<button id="withParam" py-click="say_hello_with_param('World')">
|
||||
No Event - With Params py-click
|
||||
</button>
|
||||
```
|
||||
|
||||
```python
|
||||
<py-script>
|
||||
def say_hello_no_param():
|
||||
print("Hello!")
|
||||
|
||||
def say_hello_with_param(name):
|
||||
print("Hello " + name + "!")
|
||||
</py-script>
|
||||
```
|
||||
|
||||
Note that py-\* attributes need a _function call_
|
||||
|
||||
Supported py-\* attributes can be seen in the [PyScript API reference](<[../api-reference.md](https://github.com/pyscript/pyscript/blob/66b57bf812dcc472ed6ffee075ace5ced89bbc7c/pyscriptjs/src/components/pyscript.ts#L119-L260)>).
|
||||
|
||||
## Subscribe to event with `addEventListener`
|
||||
|
||||
You can also subscribe to an event using the `addEventListener` method of the DOM element. This is useful if you want to pass event object to the event handler.
|
||||
|
||||
```html
|
||||
<button id="two">add_event_listener passes event</button>
|
||||
```
|
||||
|
||||
```python
|
||||
<py-script>
|
||||
from js import console, document
|
||||
from pyodide.ffi.wrappers import add_event_listener
|
||||
|
||||
def hello_args(*args):
|
||||
console.log(f"Hi! I got some args! {args}")
|
||||
|
||||
add_event_listener(document.getElementById("two"), "click", hello_args)
|
||||
</py-script>
|
||||
```
|
||||
|
||||
or using the `addEventListener` method of the DOM element:
|
||||
|
||||
```html
|
||||
<button id="three">add_event_listener passes event</button>
|
||||
```
|
||||
|
||||
```python
|
||||
<py-script>
|
||||
from js import console, document
|
||||
from pyodide.ffi import create_proxy
|
||||
|
||||
def hello_args(*args):
|
||||
console.log(f"Hi! I got some args! {args}")
|
||||
|
||||
document.getElementById("three").addEventListener("click", create_proxy(hello_args))
|
||||
</py-script>
|
||||
```
|
||||
|
||||
or using the PyScript Element class:
|
||||
|
||||
```html
|
||||
<button id="four">add_event_listener passes event</button>
|
||||
```
|
||||
|
||||
```python
|
||||
<py-script>
|
||||
from js import console
|
||||
from pyodide.ffi import create_proxy
|
||||
|
||||
def hello_args(*args):
|
||||
console.log(f"Hi! I got some args! {args}")
|
||||
|
||||
Element("four").element.addEventListener("click", create_proxy(hello_args))
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## JavaScript to PyScript and From PyScript to JavaScript
|
||||
|
||||
If you're wondering about how to pass objects from JavaScript to PyScript and/or the other way around head over to the [Passing Objects](passing-objects.md) page.
|
||||
|
||||
|
||||
### 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>
|
||||
```
|
||||
224
docs/guides/http-requests.md
Normal file
224
docs/guides/http-requests.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# How to make HTTP requests using `PyScript`, in pure Python
|
||||
|
||||
[Pyodide](https://pyodide.org), the interpreter 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`
|
||||
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
|
||||
|
||||
The [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="https://pyscript.net/latest/pyscript.css" />
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["/request.py"]
|
||||
</py-config>
|
||||
</head>
|
||||
|
||||
<body><p>
|
||||
Hello world request example! <br>
|
||||
Here is the output of your request:
|
||||
</p>
|
||||
<py-script>
|
||||
import asyncio
|
||||
import json
|
||||
from request import request # import our request function.
|
||||
|
||||
async def main():
|
||||
baseurl = "https://jsonplaceholder.typicode.com"
|
||||
|
||||
# GET
|
||||
headers = {"Content-type": "application/json"}
|
||||
response = await request(f"{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(f"{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(f"{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(f"{baseurl}/posts/1", method="DELETE", headers=headers)
|
||||
print(f"DELETE request=> status:{new_post.status}, json:{await new_post.json()}")
|
||||
|
||||
asyncio.ensure_future(main())
|
||||
</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-config` tag for importing our Python code
|
||||
The very first thing to notice is the `py-config` 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-config` 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
|
||||
|
||||
### 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
|
||||
|
||||
### 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)
|
||||
22
docs/guides/index.md
Normal file
22
docs/guides/index.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Guides
|
||||
|
||||
Welcome to the how-to documentation section for PyScript. If you've already
|
||||
gained some experience with PyScript before and just need practical guides
|
||||
to get your ideas realized, you can learn step by step how to use PyScript here.
|
||||
|
||||
```{note}
|
||||
Please head over to the [tutorials](../tutorials/index.md) section if you're only getting started.
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 2
|
||||
glob:
|
||||
caption: 'Contents:'
|
||||
---
|
||||
passing-objects
|
||||
http-requests
|
||||
asyncio
|
||||
custom-plugins
|
||||
event-handlers
|
||||
```
|
||||
295
docs/guides/passing-objects.md
Normal file
295
docs/guides/passing-objects.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# How to Pass Objects from PyScript to Javascript (and Vice Versa)
|
||||
|
||||
[Pyodide](https://pyodide.org), the interpreter 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
|
||||
|
||||
### Using Pyodide's globals access
|
||||
|
||||
The [PyScript JavaScript module](../reference/modules/pyscript.md) exposes its underlying Pyodide interpreter as `PyScript.interpreter`, and maintains a reference to the [globals()](https://docs.python.org/3/library/functions.html#globals) dictionary of the Python namespace. Thus, any global variables in python are accessible in JavaScript at `PyScript.interpreter.globals.get('my_variable_name')`
|
||||
|
||||
```html
|
||||
<body>
|
||||
<py-script>x = 42</py-script>
|
||||
|
||||
<button onclick="showX()">Click Me to Get 'x' from Python</button>
|
||||
<script>
|
||||
function showX(){
|
||||
console.log(`In Python right now, x = ${pyscript.interpreter.globals.get('x')}`)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
Since [everything is an object](https://docs.python.org/3/reference/datamodel.html) in Python, this applies not only to user created variables, but also to classes, functions, built-ins, etc. If we want, we can even apply Python functions to JavaScript data and variables:
|
||||
|
||||
```html
|
||||
<body>
|
||||
<!-- Click this button to log 'Apple', 'Banana', 'Candy', 'Donut' by sorting in Python-->
|
||||
<button onclick="sortInPython(['Candy', 'Donut', 'Apple', 'Banana'])">Sort In Python And Log</button>
|
||||
<script>
|
||||
function sortInPython(data){
|
||||
js_sorted = pyscript.interpreter.globals.get('sorted') //grab python's 'sorted' function
|
||||
const sorted_data = js_sorted(data) //apply the function to the 'data' argument
|
||||
for (const item of sorted_data){
|
||||
console.log(item)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
### Using JavaScript's eval()
|
||||
|
||||
There may be some situations where it isn't possible or ideal to use `PyScript.interpreter.globals.get()` to retrieve a variable from the Pyodide global dictionary. For example, some JavaScript frameworks may take a function/Callable as an html attribute in a context where code execution isn't allowed (i.e. `get()` fails). In these cases, you can create JavaScript proxies of Python objects more or less "manually" 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(self):
|
||||
return symbols['pi'] * self.radius**2
|
||||
</py-script>
|
||||
```
|
||||
|
||||
```html
|
||||
<input type="button" value="Log Python Variables" id="do-math">
|
||||
<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>
|
||||
```
|
||||
|
||||
#### Full example
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Exporting all Global Python Objects</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>
|
||||
<input type="button" value="Log Python Variables" id="do-math">
|
||||
<py-script>
|
||||
from js import createObject
|
||||
from pyodide.ffi import create_proxy
|
||||
|
||||
createObject(create_proxy(globals()), "pyodideGlobals")
|
||||
|
||||
# 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(self):
|
||||
return symbols['pi'] * self.radius**2
|
||||
</py-script>
|
||||
<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)
|
||||
}
|
||||
|
||||
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>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
### 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>
|
||||
```
|
||||
|
||||
#### Full example
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Exporting Individual Python Objects</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>
|
||||
<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>
|
||||
|
||||
<input type="button" value="Log Python Variables" id="log-python-variables">
|
||||
<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)
|
||||
}
|
||||
|
||||
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>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
BIN
docs/img/diataxis.png
Normal file
BIN
docs/img/diataxis.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
BIN
docs/img/pyodide-pyscript.png
Normal file
BIN
docs/img/pyodide-pyscript.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
58
docs/index.md
Normal file
58
docs/index.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# PyScript
|
||||
|
||||
Welcome to the PyScript documentation!
|
||||
|
||||
PyScript provides a way for you to run Python code directly in your browser, giving
|
||||
anyone the ability to program without infrastructure barriers. Add an interactive
|
||||
Python REPL directly to your website, share an interactive dashboard with a colleague
|
||||
as an HTML file, or create a client-side Python-powered web application. This documentation
|
||||
will show you how.
|
||||
|
||||
::::{grid} 2
|
||||
:gutter: 3
|
||||
|
||||
:::{grid-item-card} [Tutorials](tutorials/index.md)
|
||||
|
||||
Just getting started with PyScript?
|
||||
|
||||
Check out our [getting started guide](tutorials/getting-started.md)!
|
||||
:::
|
||||
:::{grid-item-card} [Guides](guides/index.md)
|
||||
|
||||
You already know the basics and want to learn specifics!
|
||||
|
||||
[Passing Objects between JavaScript and Python](guides/passing-objects.md)
|
||||
|
||||
[Making async HTTP requests in pure Python](guides/http-requests.md)
|
||||
|
||||
[Async/Await and Asyncio](guides/asyncio.md)
|
||||
|
||||
:::
|
||||
:::{grid-item-card} [Concepts](concepts/index.md)
|
||||
|
||||
[What is PyScript?](concepts/what-is-pyscript.md)
|
||||
|
||||
:::
|
||||
:::{grid-item-card} [Reference](reference/index.md)
|
||||
|
||||
[Frequently asked questions](reference/faq.md)
|
||||
|
||||
[The PyScript JS Module](reference/modules/pyscript.md)
|
||||
|
||||
:::{toctree}
|
||||
:maxdepth: 1
|
||||
|
||||
:::
|
||||
::::
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
hidden:
|
||||
---
|
||||
tutorials/index
|
||||
guides/index
|
||||
concepts/index
|
||||
reference/index
|
||||
changelog
|
||||
```
|
||||
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
8
docs/reference/API/__version__.md
Normal file
8
docs/reference/API/__version__.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# `__version__`
|
||||
|
||||
`PyScript.__version__` is a `str` representing the current version of PyScript in a human-readable form. For a structured version more suitable to comparisons, and for details of what each part of the version number represents, see [`version_info`](version_info.md)
|
||||
|
||||
```shell
|
||||
>>> pyscript.__version__
|
||||
'2023.02.1.dev'
|
||||
```
|
||||
85
docs/reference/API/attr_to_event.md
Normal file
85
docs/reference/API/attr_to_event.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# List of PyScript Attributes to Events:
|
||||
|
||||
PyScript provides a convenient syntax for mapping JavaScript events to PyScript events, making it easy to connect events to HTML tags.
|
||||
|
||||
For example, you can use the following code to connect the click event to a button:
|
||||
|
||||
```
|
||||
<button id="py-click" py-click="foo()">Click me</button>
|
||||
```
|
||||
|
||||
Here is a list of all the available event mappings:
|
||||
|
||||
| PyScript Event Name | DOM Event Name |
|
||||
|-------------------|----------------|
|
||||
| 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 |
|
||||
| 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 |
|
||||
| py-keydown | keydown |
|
||||
| py-keypress | keypress |
|
||||
| py-keyup | keyup |
|
||||
| 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 |
|
||||
| py-drag | drag |
|
||||
| py-dragend | dragend |
|
||||
| py-dragenter | dragenter |
|
||||
| py-dragleave | dragleave |
|
||||
| py-dragover | dragover |
|
||||
| py-dragstart | dragstart |
|
||||
| py-drop | drop |
|
||||
| py-scroll | scroll |
|
||||
| py-copy | copy |
|
||||
| py-cut | cut |
|
||||
| py-paste | paste |
|
||||
| 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 |
|
||||
| py-toggle | toggle |
|
||||
87
docs/reference/API/display.md
Normal file
87
docs/reference/API/display.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# `display(*values, target=None, append=True)`
|
||||
|
||||
## Parameters
|
||||
|
||||
`*values` - the objects to be displayed. String objects are output as-written. For non-string objects, the default content to display is the the object's {py:func}`repr`. Objects may implement the following methods to indicate that they should be displayed as a different MIME type. MIME types with a * indicate that the content will be wrapped in the appropriate html tags and attributes before output:
|
||||
|
||||
|
||||
| Method | Inferred MIME type |
|
||||
|---------------------|------------------------|
|
||||
| `__repr__` | text/plain |
|
||||
| `_repr_html_` | text/html |
|
||||
| `_repr_svg_` | image/svg+xml |
|
||||
| `_repr_png_` | image/png* |
|
||||
| `_repr_pdf_` | application/pdf |
|
||||
| `_repr_jpeg_` | image/jpeg* |
|
||||
| `_repr_json_` | application/json |
|
||||
| `_repr_javascript_` | application/javascript*|
|
||||
| `savefig` | image/png |
|
||||
| | |
|
||||
|
||||
`target` - Element's ID. The default value for `target` is the current `py-script` tag ID, it's possible to specify different IDs for this parameter
|
||||
|
||||
`append` - `boolean` if the output is going to be appended or not to the `target`ed element. It creates a `<div>` tag if `True` and a `<py-script>` tag with a random ID if `False`. The default value for `append` is `True`.
|
||||
|
||||
### Description
|
||||
|
||||
Display is the default function to display objects on the screen. Functions like the Python `print()` or JavaScript `console.log()` are now defaulted to only appear on the terminal.
|
||||
|
||||
Display will throw an exception if the target is not clear. E.g. the following code is invalid:
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
def display_hello():
|
||||
# this fails because we don't have any implicit target
|
||||
# from event handlers
|
||||
display('hello')
|
||||
</py-script>
|
||||
<button id="my-button" py-click="display_hello()">Click me</button>
|
||||
```
|
||||
|
||||
Because it's considered unclear if the `hello` string should be displayed underneath the `<py-script>` tag or the `<button>` tag.
|
||||
|
||||
To write compliant code, make sure to specify the target using the `target` parameter, for example:
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
def display_hello():
|
||||
# this works because we give an explicit target
|
||||
display('hello', target="helloDiv")
|
||||
</py-script>
|
||||
<div id="helloDiv"></div>
|
||||
<button id="my-button" py-click="display_hello()">Click me</button>
|
||||
```
|
||||
|
||||
#### Using matplotlib with display
|
||||
|
||||
`matplotlib` has two ways of plotting things as mentioned [here](https://matplotlib.org/matplotblog/posts/pyplot-vs-object-oriented-interface/)
|
||||
|
||||
- In case of using the `pyplot` interface, the graph can be shown using `display(plt)`.
|
||||
|
||||
```python
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
# Data for plotting
|
||||
t = np.arange(0.0, 2.0, 0.01)
|
||||
s = 1 + np.sin(2 * np.pi * t)
|
||||
plt.plot(t,s)
|
||||
|
||||
display(plt)
|
||||
```
|
||||
|
||||
- In case of using the `object oriented` interface, the graph can be shown using `display(fig)` or `display(plt)` both.
|
||||
|
||||
```python
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
# Data for plotting
|
||||
t = np.arange(0.0, 2.0, 0.01)
|
||||
s = 1 + np.sin(2 * np.pi * t)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(t, s)
|
||||
|
||||
display(fig) # but even display(plt) would have worked!
|
||||
```
|
||||
309
docs/reference/API/element.md
Normal file
309
docs/reference/API/element.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# `Element`
|
||||
|
||||
The `Element` API is a helpful way to create and manipulate elements in the DOM. It is a wrapper around the native DOM API, and is designed to be as intuitive as possible.
|
||||
|
||||
## Methods and Properties
|
||||
|
||||
| Property | Description |
|
||||
|----------|-----------------------------------------|
|
||||
| `element` | Returns the element with the given ID. |
|
||||
| `id` | Returns the element's ID. |
|
||||
| `value` | Returns the element's value. |
|
||||
| `innerHtml` | Returns the element's inner HTML. |
|
||||
|
||||
|
||||
|
||||
| Method | Description |
|
||||
|----------------------|--------------------------------------------------------------|
|
||||
| `write` | Writes `value` to element and handles various mime types. `append` defaults to `False`, if set to true, it will create a child element. |
|
||||
| `clear` | Clears the element's value or content. |
|
||||
| `select` | Select element from `query` which uses [Document.querySelector()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector). |
|
||||
| `clone` | Clones the with `new_id` if provided and `to` element if provided. |
|
||||
| `remove_class` | Removes one or more class name from the element. |
|
||||
| `add_class` | Adds one or more class name to the element. |
|
||||
|
||||
## Element.element
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-----------|---------|------|
|
||||
| | | |
|
||||
|
||||
The `element` property returns the DOM element with the given ID.
|
||||
|
||||
```html
|
||||
from pyscript import Element
|
||||
|
||||
my_div = Element('my-div')
|
||||
print(my_div.element)
|
||||
```
|
||||
|
||||
## Element.id
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-----------|---------|------|
|
||||
| | | |
|
||||
|
||||
Return the element's ID.
|
||||
|
||||
```html
|
||||
|
||||
<div id="my-div"></div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
my_div = Element('my-div')
|
||||
print(my_div.id) # prints 'my-div'
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.value
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-----------|---------|------|
|
||||
| | | |
|
||||
|
||||
Return the element's value.
|
||||
|
||||
```html
|
||||
<input id="my-input" value="hello world"></input>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
my_input = Element('my-input')
|
||||
print(my_input.value) # prints 'hello world'
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.innerHtml
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-----------|---------|------|
|
||||
| | | |
|
||||
|
||||
Return the element's inner HTML.
|
||||
|
||||
```html
|
||||
<div id="my-innerHtml">
|
||||
<b>hello world</b>
|
||||
</div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
my_innerHtml = Element('my-innerHtml')
|
||||
print(my_innerHtml.innerHtml) # prints <b> hello world </b>
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.write
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-------------|---------|-----------------------------|
|
||||
| `value` | | `str` or `__mime_type__` |
|
||||
| `append` | False | `bool` |
|
||||
|
||||
Writes `value` to element and handles various mime types. This method also contains a `append` parameter, which defaults to `False`.
|
||||
|
||||
Currently, these are the MIME types that are supported when rendering content using this method
|
||||
|
||||
| Method | Inferred MIME type |
|
||||
|---------------------|------------------------|
|
||||
| `__repr__` | text/plain |
|
||||
| `_repr_html_` | text/html |
|
||||
| `_repr_svg_` | image/svg+xml |
|
||||
| `_repr_png_` | image/png* |
|
||||
| `_repr_pdf_` | application/pdf |
|
||||
| `_repr_jpeg_` | image/jpeg* |
|
||||
| `_repr_json_` | application/json |
|
||||
| `_repr_javascript_` | application/javascript*|
|
||||
| `savefig` | image/png |
|
||||
|
||||
```html
|
||||
<div id="foo"></div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
el.write("Hello!")
|
||||
el.write("World!") # will replace the previous content
|
||||
</py-script>
|
||||
```
|
||||
|
||||
If we set `append` to `True`, it will create a child element using a `div`.
|
||||
|
||||
```html
|
||||
<div id="foo"></div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
el.write("Hello!", append=True)
|
||||
|
||||
# This will create a child div with the id "foo-1"
|
||||
el.write("World!", append=True)
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.clear
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-----------|---------|------|
|
||||
| | | |
|
||||
|
||||
Clears the element's value or content. For example, we can clear the value of an input element.
|
||||
|
||||
```html
|
||||
<input id="foo" value="Hello!"></input>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
el.clear() # Removes value from input
|
||||
</py-script>
|
||||
```
|
||||
|
||||
Or we can clear the content of a div element.
|
||||
|
||||
```html
|
||||
<div id="foo">Hello!</div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
el.clear() # Removes Hello from div content
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.select
|
||||
|
||||
Select element from `query`, it will look into the main Element if `from_content` is `True`. This method is a wrapper of [Document.querySelector()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector).
|
||||
|
||||
```html
|
||||
<div id="foo">
|
||||
<div id="bar"></div>
|
||||
</div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
bar = el.select("#bar")
|
||||
print(bar.id) # prints 'bar'
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.clone
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-------------|---------|-----------|
|
||||
| `new_id` | None | `str` |
|
||||
| `to` | None | `Element` |
|
||||
|
||||
Clones the element to a new element. You can provide `new_id` to set a different id to the cloned element. You can also use a `to` element to append the cloned element to.
|
||||
|
||||
```html
|
||||
<div id="foo">
|
||||
HI!
|
||||
</div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
el = Element("foo")
|
||||
# Creates two divs with the id "foo" and content "HI!"
|
||||
el.clone()
|
||||
</py-script>
|
||||
```
|
||||
|
||||
It's always a good idea to pass a new id to the element you are cloning to avoid confusion if you need to reference the element by id again.
|
||||
|
||||
```html
|
||||
<div id="foo">Hello!</div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
# Clones foo and its contents, but uses the id 'bar'
|
||||
el.clone(new_id="bar")
|
||||
</py-script>
|
||||
```
|
||||
|
||||
You can also clone an element into another element.
|
||||
|
||||
```html
|
||||
<div id="bond">
|
||||
Bond
|
||||
</div>
|
||||
<div id="james">
|
||||
James
|
||||
</div>
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
bond_div = Element("bond")
|
||||
james_div = Element("james")
|
||||
|
||||
bond_div.clone(new_id="bond-2", to=james_div)
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.remove_class
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-------------|---------|-----------------------|
|
||||
| `classname` | None | `str` or `List[str]` |
|
||||
|
||||
Removes one or more class names from the element.
|
||||
|
||||
```html
|
||||
<div id="foo" class="bar baz"></div>
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
el = Element("foo")
|
||||
el.remove_class("bar")
|
||||
</py-script>
|
||||
```
|
||||
|
||||
You can also remove multiple classes by passing a list of strings.
|
||||
|
||||
```html
|
||||
<div id="foo" class="bar baz"></div>
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
el = Element("foo")
|
||||
el.remove_class(["bar", "baz"]) # Remove all classes from element
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.add_class
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-------------|---------|-----------------------|
|
||||
| `classname` | None | `str` or `List[str]` |
|
||||
|
||||
Adds one or more class names to the element.
|
||||
|
||||
```html
|
||||
<style> .red { color: red; } </style>
|
||||
<div id="foo">Hi!</div>
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
el.add_class("red")
|
||||
</py-script>
|
||||
```
|
||||
|
||||
You can also add multiple classes at once by passing a list of strings.
|
||||
|
||||
```html
|
||||
<style> .red { color: red; } .bold { font-weight: bold; } </style>
|
||||
<div id="foo">Hi!</div>
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
el.add_class(["red", "bold"])
|
||||
</py-script>
|
||||
```
|
||||
16
docs/reference/API/version_info.md
Normal file
16
docs/reference/API/version_info.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# `version_info`
|
||||
|
||||
`PyScript.version_info` is a `namedtuple` representing the current version of PyScript. It can be used to compare whether current version precedes or follows a desired version. For a human-readable version of the same info, see [`__version__`](__version__.md)
|
||||
|
||||
```sh
|
||||
>>> pyscript.version_info
|
||||
version_info(year=2023, month=2, minor=1, releaselevel='dev')
|
||||
```
|
||||
|
||||
## Version Fields
|
||||
| **parameter** | **CalVer equivalent field** | **example value** | **description** |
|
||||
|-----------------|-----------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `year` | Full year (YYYY) | 2023 | The year of the release; when printed or represented as a string, always written with 4 digits |
|
||||
| `month` | Short Month (MM) | 2 | The month of the release; when printed or represented as a string, written with 1 or 2 digits as necessary |
|
||||
| `minor` | | 1 | The incremental number of the release for this month; when printed or represented as a string, written with 1 or two digits as necessary |
|
||||
| `releaselevel` | | 'dev' | A string representing the qualifications of this build |
|
||||
51
docs/reference/API/when.md
Normal file
51
docs/reference/API/when.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# `@when`
|
||||
|
||||
`@when(event_type:str = None, selector:str = None)`
|
||||
|
||||
The `@when` decorator attaches the decorated function or Callable as an event handler for selected objects on the page. That is, when the named event is emitted by the selected DOM elements, the decorated Python function will be called.
|
||||
|
||||
If the decorated function takes a single (non-self) argument, it will be passed the [Event object](https://developer.mozilla.org/en-US/docs/Web/API/Event) corresponding to the triggered event. If the function takes no (non-self) argument, it will be called with no arguments.
|
||||
|
||||
## Parameters
|
||||
|
||||
`event_type` - A string representing the event type to match against. This can be any of the [https://developer.mozilla.org/en-US/docs/Web/Events#event_listing](https://developer.mozilla.org/en-US/docs/Web/Events) that HTML elements may emit, as appropriate to their element type.
|
||||
|
||||
`selector` = A string containing one or more [CSS selectors](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors). The selected DOM elements will have the decorated function attacehed as an event handler.
|
||||
|
||||
## Examples:
|
||||
|
||||
The following example prints "Hello, world!" whenever the button is clicked. It demonstrates using the `@when` decorator on a Callable which takes no arguments:
|
||||
|
||||
```html
|
||||
<button id="my_btn">Click Me to Say Hi</button>
|
||||
<py-script>
|
||||
from pyscript import when
|
||||
@when("click", selector="#my_btn")
|
||||
def say_hello():
|
||||
print(f"Hello, world!")
|
||||
</py-script>
|
||||
```
|
||||
|
||||
The following example includes three buttons - when any of the buttons is clicked, that button turns green, and the remaining two buttons turn red. This demonstrates using the `@when` decorator on a Callable which takes one argument, which is then passed the Event object from the associated event. When combined with the ability to look at other elements in on the page, this is quite a powerful feature.
|
||||
|
||||
```html
|
||||
<div id="container">
|
||||
<button>First</button>
|
||||
<button>Second</button>
|
||||
<button>Third</button>
|
||||
</div>
|
||||
<py-script>
|
||||
from pyscript import when
|
||||
import js
|
||||
|
||||
@when("click", selector="#container button")
|
||||
def highlight(evt):
|
||||
#Set the clicked button's background to green
|
||||
evt.target.style.backgroundColor = 'green'
|
||||
|
||||
#Set the background of all buttons to red
|
||||
other_buttons = (button for button in js.document.querySelectorAll('button') if button != evt.target)
|
||||
for button in other_buttons:
|
||||
button.style.backgroundColor = 'red'
|
||||
</py-script>
|
||||
```
|
||||
493
docs/reference/elements/py-config.md
Normal file
493
docs/reference/elements/py-config.md
Normal file
@@ -0,0 +1,493 @@
|
||||
# <py-config>
|
||||
|
||||
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](https://toml.io/)(default) or [JSON](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON) format.
|
||||
|
||||
If you are unfamiliar with TOML, consider [reading about it](https://learnxinyminutes.com/docs/toml/) or 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.
|
||||
|
||||
The `<py-config>` element should be placed within the `<body>` element.
|
||||
|
||||
## Attributes
|
||||
|
||||
| attribute | type | default | description |
|
||||
|-----------|--------|---------|----------------------------------------------------------------------------------------------------------|
|
||||
| **type** | string | "toml" | Syntax type of the `<py-config>`. Value can be `json` or `toml`. Default: "toml" if type is unspecified. |
|
||||
| **src** | url | | Source url to an external configuration file. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Defining an inline config
|
||||
|
||||
- `<py-config>` using TOML (default)
|
||||
|
||||
```{note}
|
||||
Reminder: when using TOML, any Arrays of Tables defined with double-brackets (like `[[interpreters]]` and `[[fetch]]` must come after individual keys (like `plugins = ...` and `packages=...`)
|
||||
```
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[splashscreen]
|
||||
autoclose = true
|
||||
|
||||
[[interpreters]]
|
||||
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
|
||||
name = "pyodide-0.21.2"
|
||||
lang = "python"
|
||||
</py-config>
|
||||
```
|
||||
|
||||
- `<py-config>` using JSON via `type` attribute
|
||||
|
||||
```html
|
||||
<py-config type="json">
|
||||
{
|
||||
"splashscreen": {
|
||||
"autoclose": true
|
||||
},
|
||||
"interpreters": [{
|
||||
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
|
||||
"name": "pyodide-0.21.2",
|
||||
"lang": "python"
|
||||
}]
|
||||
}
|
||||
</py-config>
|
||||
```
|
||||
|
||||
### Defining a file based config
|
||||
|
||||
- Use of the `src` attribute
|
||||
|
||||
```html
|
||||
<py-config src="./custom.toml"></py-config>
|
||||
```
|
||||
where `custom.toml` contains
|
||||
|
||||
```toml
|
||||
[splashscreen]
|
||||
autoclose = true
|
||||
|
||||
[[interpreters]]
|
||||
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
|
||||
name = "pyodide-0.21.2"
|
||||
lang = "python"
|
||||
```
|
||||
|
||||
- JSON using the `type` and `src` attribute
|
||||
|
||||
```html
|
||||
<py-config type="json" src="./custom.json"></py-config>
|
||||
```
|
||||
where `custom.json` contains
|
||||
|
||||
```json
|
||||
{
|
||||
"splashscreen": {
|
||||
"autoclose": true,
|
||||
},
|
||||
"interpreters": [{
|
||||
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
|
||||
"name": "pyodide-0.21.2",
|
||||
"lang": "python"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### Mixing inline and file based configs
|
||||
|
||||
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:
|
||||
|
||||
```html
|
||||
<py-config src="./custom.toml">
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
</py-config>
|
||||
```
|
||||
|
||||
This can also be done via JSON using the `type` attribute.
|
||||
|
||||
```html
|
||||
<py-config type="json" src="./custom.json">
|
||||
{
|
||||
"fetch": [{
|
||||
"files": ["./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`.
|
||||
|
||||
## Dependencies and Packages
|
||||
|
||||
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).
|
||||
|
||||
Package dependencies in the `<py-config>` can be declared by using the direct link to the package URL (whl or any other format supported by the chosen interpreter) or by just providing the package name [and version]. If only the name [and version] are provided, packages will be installed directly from what's provided by your interpreter or from PyPI.
|
||||
|
||||
NOTICE that only pure python packages from PyPI will work and packages with C dependencies will not. These need to be built specifically for WASM (please, consult the Pyodide project for more information about what's supported and on how to build packages with C dependencies)
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
packages = ["./static/wheels/travertino-0.1.3-py3-none-any.whl"]
|
||||
</py-config>
|
||||
```
|
||||
|
||||
OR in JSON like
|
||||
|
||||
```html
|
||||
<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 `display(fig, target="plot")`, which takes the graph and displays it in the element with the id `plot`.
|
||||
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Let's plot random numbers</h1>
|
||||
<div id="plot"></div>
|
||||
<py-config type="json">
|
||||
{
|
||||
"packages": ["numpy", "matplotlib"]
|
||||
}
|
||||
</py-config>
|
||||
<py-script>
|
||||
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)
|
||||
display(fig, target="plot")
|
||||
</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
|
||||
`files` key within the `fetch` section. Refer to the [fetch](#fetch) section for
|
||||
more details.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Let's plot random numbers</h1>
|
||||
<div id="plot"></div>
|
||||
<py-config type="toml">
|
||||
packages = ["numpy", "matplotlib"]
|
||||
|
||||
[[fetch]]
|
||||
files = ["./data.py"]
|
||||
</py-config>
|
||||
<py-script>
|
||||
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)
|
||||
display(fig, target="plot")
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Supported configuration values
|
||||
|
||||
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. |
|
||||
| `fetch` | List of Stuff to fetch | Local Python modules OR resources from the internet are to be specified here using a Fetch Configuration, described below. 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. |
|
||||
| `interpreters` | List of Interpreters| List of Interpreter configurations, described below. The default value contains a single Pyodide based interpreter. **Note:** Currently, only a single interpreter is supported. |
|
||||
| `runtimes` {bdg-warning-line}`Deprecated` | List of Runtimes | This value is deprecated, please use `interpreters`. List of runtime configurations, described below. The default value contains a single Pyodide based interpreter. |
|
||||
|
||||
### <a name="fetch">Fetch</a>
|
||||
|
||||
A fetch configuration consists of the following:
|
||||
|
||||
| Value | Type | Description |
|
||||
|--------------|-----------------|-------------------------------------------------|
|
||||
| `from` | string | Base URL for the resource to be fetched. |
|
||||
| `to_folder` | string | Name of the folder to create in the filesystem. |
|
||||
| `to_file` | string | Name of the target to create in the filesystem. |
|
||||
| `files` | List of strings | List of files to be downloaded. |
|
||||
|
||||
The parameters `to_file` and `files` shouldn't be supplied together.
|
||||
|
||||
#### Mechanism
|
||||
|
||||
The `fetch` mechanism works in the following manner:
|
||||
|
||||
- If both `files` and `to_file` parameters are supplied: Error!
|
||||
- `from` defaults to an empty string i.e. `""` to denote relative URLs of the serving directory
|
||||
- `to_folder` defaults to `.` i.e. the current working directory of the filesystem
|
||||
- If `files` is specified
|
||||
- for each `file` present in the `files` array
|
||||
- the `sourcePath` is calculated as `from + file`
|
||||
- the `destination` is calculated as `to_folder + file`
|
||||
- thus, the object is downloaded from `sourcePath` to `destination`
|
||||
- Else i.e. `files` is NOT specified
|
||||
- If `to_file` is specified
|
||||
- the object is downloaded from `from` to `to_folder + to_file`
|
||||
- Otherwise, calculate the `filename` at the end of `from` i.e. the part after last `/`
|
||||
- the object is downloaded from `from` to `to_folder + filename at the end of 'from'`
|
||||
|
||||
Learn more about `fetch` on PyScript [here](https://jeff.glass/post/whats-new-pyscript-2022-12-1)
|
||||
|
||||
#### Use-Cases
|
||||
|
||||
Assumptions:
|
||||
|
||||
The directory being served has the following tree structure:
|
||||
|
||||
```
|
||||
content/
|
||||
├─ index.html <<< File with <py-config>
|
||||
├─ info.txt
|
||||
├─ data/
|
||||
│ ├─ sensordata.csv
|
||||
├─ packages/
|
||||
│ ├─ my_package/
|
||||
│ │ ├─ __init__.py
|
||||
│ │ ├─ helloworld/
|
||||
│ │ │ ├─ __init__.py
|
||||
│ │ │ ├─ greetings.py
|
||||
```
|
||||
|
||||
1. Fetching a single file
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ['info.txt']
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
with open('info.txt', 'r') as fp:
|
||||
print(fp.read())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
2. Single File with Renaming
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = 'info.txt'
|
||||
to_file = 'info_loaded_from_web.txt'
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
with open('info_loaded_from_web.txt', 'r') as fp:
|
||||
print(fp.read())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
3. Single File to another Directory
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ['info.txt']
|
||||
to_folder = 'infofiles/loaded_info'
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
with open('infofiles/loaded_info/info.txt', 'r') as fp:
|
||||
print(fp.read())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
4. Single File to another Directory with Renaming
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = 'info.txt'
|
||||
to_folder = 'infofiles/loaded_info'
|
||||
to_file = 'info_loaded_from_web.txt'
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
with open('infofiles/loaded_info/info_loaded_from_web.txt', 'r') as fp:
|
||||
print(fp.read())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
5. Single file from a folder to the current working directory
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = 'data/'
|
||||
files = ['sensordata.csv']
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
with open('./sensordata.csv', 'r') as fp:
|
||||
print(fp.read())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
6. Single file from a folder to another folder (i.e. not the current working directory)
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = 'data/'
|
||||
to_folder = './local_data'
|
||||
files = ['sensordata.csv']
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
with open('./local_data/sensordata.csv', 'r') as fp:
|
||||
print(fp.read())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
7. Multiple files preserving directory structure
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = 'packages/my_package/'
|
||||
files = ['__init__.py', 'helloworld/greetings.py', 'helloworld/__init__.py']
|
||||
to_folder = 'custom_pkg'
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
from custom_pkg.helloworld.greetings import say_hi
|
||||
print(say_hi())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
8. From an API endpoint which doesn't end in a filename
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = 'https://catfact.ninja/fact'
|
||||
to_file = './cat_fact.json'
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
import json
|
||||
with open("cat_fact.json", "r") as fp:
|
||||
data = json.load(fp)
|
||||
</py-script>
|
||||
```
|
||||
|
||||
### Interpreter
|
||||
|
||||
An interpreter configuration consists of the following:
|
||||
|
||||
| Value | Type | Description |
|
||||
|--------|-------------------|-------------|
|
||||
| `src` | string (Required) | URL to the interpreter source. |
|
||||
| `name` | string | Name of the interpreter. 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 interpreter. This field can be used by the application author to provide clarification. It currently has no implications on how PyScript behaves. |
|
||||
|
||||
#### Example
|
||||
|
||||
- The default interpreter is `pyodide`, another version of which can be specified as following
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[interpreters]]
|
||||
src = "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"
|
||||
name = "pyodide-0.20.0"
|
||||
lang = "python"
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```{note}
|
||||
Currently, PyScript supports a single interpreter, this may change in the future.
|
||||
```
|
||||
|
||||
## Supplying extra information (or metadata)
|
||||
|
||||
Besides the above schema, 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:
|
||||
|
||||
```html
|
||||
<py-config type="toml">
|
||||
magic = "unicorn"
|
||||
</py-config>
|
||||
```
|
||||
|
||||
OR in JSON like
|
||||
|
||||
```html
|
||||
<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.
|
||||
62
docs/reference/elements/py-repl.md
Normal file
62
docs/reference/elements/py-repl.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# <py-repl>
|
||||
|
||||
The `<py-repl>` element provides a REPL(Read Eval Print Loop) to evaluate multi-line Python and display output.
|
||||
|
||||
## Attributes
|
||||
|
||||
| attribute | type | default | description |
|
||||
|-------------------|---------|---------|--------------------------------------|
|
||||
| **auto-generate** | boolean | | Auto-generates REPL after evaluation |
|
||||
| **output-mode** | string | "" | Determines whether the output element is cleared prior to writing output |
|
||||
| **output** | string | | The id of the element to write `stdout` and `stderr` to |
|
||||
| **stderr** | string | | The id of the element to write `stderr` to |
|
||||
| **src** | string | | Resource to be preloaded into the REPL |
|
||||
|
||||
|
||||
### `auto-generate`
|
||||
If a \<py-repl\> tag has the `auto-generate` attribute, upon execution, another \<pr-repl\> tag will be created and added to the DOM as a sibling of the current tag.
|
||||
|
||||
### `output-mode`
|
||||
By default, the element which displays the output from a REPL is cleared (`innerHTML` set to "") prior to each new execution of the REPL. If `output-mode` == "append", that element is not cleared, and the output is appended instead.
|
||||
|
||||
### `output`
|
||||
The ID of an element in the DOM that `stdout` (e.g. `print()`), `stderr`, and the results of executing the repl are written to. Defaults to an automatically-generated \<div\> as the next sibling of the REPL itself.
|
||||
|
||||
### `stderr`
|
||||
The ID of an element in the DOM that `stderr` will be written to. Defaults to None, though writes to `stderr` will still appear in the location specified by `output`.
|
||||
|
||||
### `src`
|
||||
If a \<py-repl\> tag has the `src` attribute, during page initialization, resource in the `src` will be preloaded into the REPL. Please note that this will not run in advance. If there is content in the \<py-repl\> tag, it will be cleared and replaced with preloaded resource.
|
||||
|
||||
## Examples
|
||||
|
||||
### `<py-repl>` element set to auto-generate
|
||||
|
||||
```html
|
||||
<py-repl auto-generate="true"> </py-repl>
|
||||
```
|
||||
|
||||
### `<py-repl>` element with output
|
||||
|
||||
The following will write "Hello! World!" to the div with id `replOutput`.
|
||||
|
||||
```html
|
||||
<div id="replOutput"></div>
|
||||
<py-repl output="replOutput">
|
||||
print("Hello!")
|
||||
hello = "World!"
|
||||
hello
|
||||
</py-repl>
|
||||
```
|
||||
|
||||
Note that if we `print` from the REPL (or otherwise write to `sys.stdout`), the output will be printed in the [`py-terminal`](../plugins/py-terminal.md) if is enabled.
|
||||
|
||||
### `<py-repl>` element with src
|
||||
Preload resource from src into the REPL
|
||||
```html
|
||||
<py-repl id="py-repl" output="replOutput" src="./src/py/py_code.py">
|
||||
If a py-repl tag has the src attribute,
|
||||
the content here will be cleared and replaced.
|
||||
</py-repl>
|
||||
<div id="replOutput"></div>
|
||||
```
|
||||
126
docs/reference/elements/py-script.md
Normal file
126
docs/reference/elements/py-script.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# <py-script>
|
||||
|
||||
The `<py-script>` element, also available as `<script type="py-script">`, lets you execute multi-line Python scripts both inline and via a src attribute.
|
||||
|
||||
## Attributes
|
||||
|
||||
| attribute | type | default | description |
|
||||
|-----------|--------|---------|------------------------------|
|
||||
| **src** | url | | You don't need to add long python code in py-script, you can provide url of the python source file in the py-script tag with the `src` attribute. When a Python file is referred with the `src` attribute it is executed, and then added to the namespace where it was referred. |
|
||||
| **output**| string | | The id of a DOM element to route `sys.stdout` and `stderr` to, in addition to sending it to the `<py-terminal>`|
|
||||
| **stderr**| string | | The id of a DOM element to route just `sys.stderr` to, in addition to sending it to the `<py-terminal>`|
|
||||
|
||||
### output
|
||||
|
||||
If the `output` attribute is provided, any output to [sys.stdout](https://docs.python.org/3/library/sys.html#sys.stdout) or [sys.stderr](https://docs.python.org/3/library/sys.html#sys.stderr) is written to the DOM element with the ID matching the attribute. If no DOM element is found with a matching ID, a warning is shown. The msg is output to the `innerHTML` of the HTML Element, with newlines (`\n'`) converted to breaks (`<br/>`).
|
||||
|
||||
This output is in addition to the output being written to the developer console and the `<py-terminal>` if it is being used.
|
||||
|
||||
### stderr
|
||||
|
||||
If the `stderr` attribute is provided, any output to [sys.stderr](https://docs.python.org/3/library/sys.html#sys.stderr) is written to the DOM element with the ID matching the attribute. If no DOM element is found with a matching ID, a warning is shown. The msg is output to the `innerHTML` of the HTML Element, with newlines (`\n'`) converted to breaks (`<br/>`).
|
||||
|
||||
This output is in addition to the output being written to the developer console and the `<py-terminal>` if it is being used.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### Inline `<py-script>` element
|
||||
|
||||
Let's execute this multi-line Python script to compute π and print it back onto the page
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<py-script>
|
||||
print("Let's compute π:")
|
||||
def compute_pi(n):
|
||||
pi = 2
|
||||
for i in range(1,n):
|
||||
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
|
||||
return pi
|
||||
|
||||
pi = compute_pi(100000)
|
||||
s = f"π is approximately {pi:.3f}"
|
||||
print(s)
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Using `<py-script>` element with `src` attribute
|
||||
|
||||
we can also move our python code to its own file and reference it via the `src` attribute.
|
||||
|
||||
|
||||
```python
|
||||
# compute_pi.py
|
||||
print("Let's compute π:")
|
||||
def compute_pi(n):
|
||||
pi = 2
|
||||
for i in range(1,n):
|
||||
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
|
||||
return pi
|
||||
|
||||
pi = compute_pi(100000)
|
||||
s = f"π is approximately {pi:.3f}"
|
||||
print(s)
|
||||
```
|
||||
|
||||
Since both compute_pi.py and index.html are in the same directory, we can reference the python file with a relative path.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<py-script src="compute_pi.py"></py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Writing into labeled elements
|
||||
|
||||
In the example above, we had a single `<py-script>` tag printing
|
||||
one or more lines onto the page in order. Within the `<py-script>`, you can
|
||||
use the `Element` class to create a python object for interacting with
|
||||
page elements. Objects created from the `Element` class provide the `.write()` method
|
||||
which enables you to send strings into the page elements referenced by those objects.
|
||||
|
||||
For example, we'll add some style elements and provide placeholders for
|
||||
the `<py-script>` tag to write to.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<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">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<b><p>Today is <u><label id='today'></label></u></p></b>
|
||||
<br>
|
||||
<div id="pi" class="alert alert-primary"></div>
|
||||
<py-script>
|
||||
import datetime as dt
|
||||
Element('today').write(dt.date.today().strftime('%A %B %d, %Y'))
|
||||
|
||||
def compute_pi(n):
|
||||
pi = 2
|
||||
for i in range(1,n):
|
||||
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
|
||||
return pi
|
||||
|
||||
pi = compute_pi(100000)
|
||||
Element('pi').write(f'π is approximately {pi:.3f}')
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
52
docs/reference/exceptions.md
Normal file
52
docs/reference/exceptions.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Exceptions and error codes
|
||||
|
||||
When creating pages with PyScript, you may encounter exceptions. Each handled exception will contain a specific code which will give you more information about it.
|
||||
This reference guide contains the error codes you might find and a description of each of them.
|
||||
|
||||
## User Errors
|
||||
|
||||
| Error code | Description | Recommendation |
|
||||
|------------|--------------------------------|--------------------|
|
||||
| PY1000 | Invalid configuration supplied | Confirm that your `py-config` tag is using a valid `TOML` or `JSON` syntax and is using the correct configuration type. |
|
||||
| PY1001 | Unable to install package(s) | Confirm that the package contains a pure Python 3 wheel or the name of the package is correct. |
|
||||
| PY2000 | Invalid plugin file extension | Only `.js` and `.py` files can be used when loading user plugins. Please confirm your path contains the file extension. |
|
||||
| PY2001 | Plugin doesn't contain a default export | Please add `export default` to the main plugin class. |
|
||||
| PY9000 | Top level await is deprecated | Create a coroutine with your code and schedule it with `asyncio.ensure_future` or similar |
|
||||
|
||||
|
||||
|
||||
## Fetch Errors
|
||||
|
||||
These error codes are related to any exception raised when trying to fetch a resource. If, while trying to fetch a resource, we encounter a status code that is not 200, the error code will contain the HTTP status code and the `PY0` prefix. For example, if we encounter a 404 error, the error code will be `P02404`.
|
||||
|
||||
|
||||
| Error Code | Description |
|
||||
|------------|--------------------------------------------------------------|
|
||||
| PY0001 | Generic fetch error, failed to fetch page from the server |
|
||||
| PY0002 | Name supplied when trying to fetch resource is invalid |
|
||||
| PY0401 | You are not authorized to access this resource. |
|
||||
| PY0403 | You are not allowed to access this resource. |
|
||||
| PY0404 | The page you are trying to fetch does not exist. |
|
||||
| PY0500 | The server encountered an internal error. |
|
||||
| PY0503 | The server is currently unavailable. |
|
||||
|
||||
## PY1001
|
||||
|
||||
Pyscript cannot install the package(s) you specified in your `py-config` tag. This can happen for a few reasons:
|
||||
|
||||
- The package does not exist
|
||||
- The package does not contain a pure Python 3 wheel
|
||||
- An error occurred while trying to install the package
|
||||
|
||||
An error banner should appear on your page with the error code and a description of the error or a traceback. You can also check the developer console for more information.
|
||||
|
||||
## PY2001
|
||||
|
||||
Javascript plugins must export a default class. This is required for PyScript to be able to load the plugin. Please add `export default` to the main plugin class. For example:
|
||||
|
||||
```js
|
||||
export default class HelloWorldPlugin {
|
||||
afterStartup(runtime) {
|
||||
console.log("Hello World from the plugin!");
|
||||
}
|
||||
```
|
||||
160
docs/reference/faq.md
Normal file
160
docs/reference/faq.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Frequently asked questions
|
||||
|
||||
As the world’s most popular programming language, Python is powerful in its capabilities and comparatively easy to learn, yet the limitations cannot be ignored: it’s hard to install Python and all necessary dependencies; it’s glued to the backend without the ability to make apps or websites; and it’s difficult to share your work.
|
||||
|
||||
What if we could remove those limitations, making the power of Python accessible to the masses? The makers of PyScript set out to do just that by enabling Python in the browser for frontend web and application development. No more complicated installs. Projects can be shared with anyone, anywhere—all you need is a browser.
|
||||
|
||||
We are fully leaning into the idea that the browser is the most ubiquitous VM by using Python to build a graphical, programmable OS on top to make and share applications. Harness the full power of canvas, webGL, WASI, and even in-browser support for P2P and CRDTs for serverless data sharing and collaboration.
|
||||
|
||||
> “This is the exciting beginning for supporting new ways of programming, building, sharing, and deploying applications. Ultimately, we should be spending our time thinking and writing applications to solve the real problems we have, not dealing with mundane, hardware-induced challenges. Let's make programming more fun and simple.” - Fabio Pliger
|
||||
|
||||
## Why PyScript?
|
||||
|
||||
Exponentially expand accessibility and use cases for Python by enabling Python in the browser for building UIs. Reproduce environments without having to download Python or conda or install other packages. Projects can be shared with anyone and deployed anywhere—all you need is a browser and a web-accessible device (computer, tablet, or mobile).
|
||||
|
||||
We are fully leaning into the idea that the browser is the most ubiquitous VM by using Python to build a graphical, programmable operating system on top of the browser to make and share applications. Harness the full power of canvas, webGL, WASI, and even in-browser support for P2P and CRDTs for serverless data sharing and collaboration.
|
||||
|
||||
- [PyCon US 2022 Keynote with Peter Wang](https://anaconda.cloud/pyscript-pycon2022-peter-wang-keynote)
|
||||
- [Example demos from Keynote](https://pyscript.net/examples/index.html)
|
||||
|
||||
## What is PyScript?
|
||||
|
||||
PyScript is a framework that allows users to create rich Python applications in the browser using HTML's interface and the power of [Pyodide — Version 0.20.0](https://pyodide.org/en/stable/), [WebAssembly](https://webassembly.org/), and modern web technologies. The PyScript framework provides users at every experience level with access to an expressive, easy-to-learn programming language with countless applications.
|
||||
|
||||
What is PyScript? Well, here are some of the core components:
|
||||
|
||||
- Python in the browser: Enable drop-in content, external file hosting, and application hosting without the reliance on server-side configuration
|
||||
|
||||
- Python ecosystem: Run many popular packages of Python and the scientific stack (such as numpy, pandas, scikit-learn, and more)
|
||||
|
||||
- Python with JavaScript: Bi-directional communication between Python and Javascript objects and namespaces
|
||||
|
||||
- Environment management: Allow users to define what packages and files to include for the page code to run
|
||||
|
||||
- Visual application development: Use readily available curated UI components, such as buttons, containers, text boxes, and more
|
||||
|
||||
- Flexible framework: A flexible framework that can be leveraged to create and share new pluggable and extensible components directly in Python
|
||||
|
||||
All that to say... PyScript is just HTML, only a bit (okay, maybe a lot) more powerful, thanks to the rich and accessible ecosystem of Python libraries.
|
||||
|
||||
In short, our mission is to enable programming for the 99%.
|
||||
|
||||
## How can a user get started?
|
||||
|
||||
Leveraging Python in HTML is literally as simple as adding a few lines of code to your page. The best place to learn more, get started, and stay updated on all things PyScript is [Pyscript.net](http://pyscript.net/). Additional shareable resources below:
|
||||
|
||||
- [PyScript Repo](https://github.com/pyscript/pyscript)
|
||||
- [PyScript Announcement Blog](https://www.anaconda.com/blog/pyscript-python-in-the-browser)
|
||||
- [PyScript Technical Blog](https://engineering.anaconda.com/2022/04/welcome-pyscript.html)
|
||||
- [PyScript Nucleus Project](https://anaconda.cloud/s/pyscript)
|
||||
- [PyScript Documentation](https://docs.pyscript.net/)
|
||||
|
||||
## Why isn't this going to be as terrible as PHP?
|
||||
|
||||
This comparison is based on both PHP and PyScript having a similar way of declaring things: put a tag on the page and the interpreter handles it. PHP works on the server side and is itself a whole different programming language that has its own directives and semantics.
|
||||
|
||||
The choice of using tags to allow people to execute Python is explicit; even if this functionality is similar to PHP, it works differently. First of all, everything runs in the browser itself rather than going to the server side. Secondly, PyScript lives close to the text and allows changes to be made to the visual elements. The PyScript REPL can generate output, plus it provides additional visual elements like buttons, titles, and input boxes. PyScript functions as a framework that generates UIs that makes sense as a tag in the html code.
|
||||
|
||||
Currently, both PHP and PyScript operate with only one namespace. However, PyScript will soon support multiple namespaces and different types of isolation of code (scope), along with support for languages beyond Python.
|
||||
|
||||
## Why not just learn JavaScript?
|
||||
|
||||
JavaScript is not only a different language from python, but a challenging language at that. With PyScript, you now have two languages to choose from, with even more on the roadmap. PyScript allows you to use both libraries and makes JavaScript and Python compatible with one another.
|
||||
|
||||
Python is incredibly powerful, super intuitive, and easy to learn. By adding Python to your toolkit, you can use Numpy, SciPy, Pandas, and more, seamlessly. One tradeoff is longer download times, so it isn’t the right tool for everything—but where it is the right tool, it’s almost irreplaceable.
|
||||
|
||||
Ultimately, PyScript will enable the use of a variety of languages, offer a standard set of components that is well linked to the REPLs, and allow you to do an introspection on the code base—you can have, for example, a *modifiable* chart as a python object.
|
||||
|
||||
## Will PyScript replace JavaScript?
|
||||
|
||||
No. PyScript allows Python to leverage HTML, CSS, and JavaScript conventions to build elegant UIs and address general web application building, packaging, distribution, and deployment challenges (a huge pain). We expect the popularity and adoption of HTML, CSS, and JavaScript to rise alongside Python, ultimately making the web a more friendly and hackable place for everyone. That said, we do believe:
|
||||
|
||||
- PyScript will displace some use cases that people have to rely on Javascript for now
|
||||
- PyScript will heavily leverage and interface with existing powerful, feature-full JS libraries, as appropriate
|
||||
- PyScript will open up web programming to tens of millions of people who would otherwise not have touched JavaScript; so in this way, it will outpace JavaScript
|
||||
|
||||
But none of these above scenarios lead to a situation where "PyScript replaces all existing JavaScript." Just as Python itself didn't replace C, C++, or Java. But, it did take a LOT of market share for new use cases where those languages would otherwise have been used.
|
||||
|
||||
## What is the difference between PyScript and Pyodide?
|
||||
|
||||

|
||||
|
||||
PyScript provides HTML tags for embedding and executing Python code in your browser. As a ‘glue’ framework, it sits on top of a variety of tools and provides users access to Pyodide, the WebAssembly port of CPython, which is compiled using Emscripten. In other words, Pyodide enables PyScript users to take advantage of real CPython on the browser.
|
||||
|
||||
Together, PyScript and Pyodide allow users to author applications by turning the browser into a code deployment tool that anyone can learn to use.
|
||||
|
||||
With respect to the UI, PyScript is opinionated and purposeful, while Pyodide is agnostic, unopinionated, and intended for more general use.
|
||||
|
||||
## What packages can I use? Can I use anything from PyPI?
|
||||
|
||||
You can use anything within the Pyodide library, and pure python packages from PyPi that do not contain C dependencies should be supported by Pyodide.
|
||||
|
||||
There is a list of packages built in Pyodide in [Packages built in Pyodide — Version 0.20.0](https://pyodide.org/en/stable/usage/packages-in-pyodide.html) (these are mostly packages with C extensions). You can also install pure Python packages from PyPI or custom URLs, assuming they have a wheel.
|
||||
|
||||
In general, Pyodide doesn’t support all Python features—not because of Pyodide itself, but because some concepts just work differently on the browser (think of sockets/websockets, IO, threading, etc.). If it’s a pure python package that doesn’t have any non-supported features, you should expect it to work. If it has C dependencies, etc., don’t expect it to work unless someone builds/ports them. A lot of the features that don’t work can be made to work, but it will take human power to fix. For example, the PyTorch community wanted those features to work, so they rallied around it to make it happen. Expect the set of libraries that work to grow quickly given the volume of package builds coming from the community.
|
||||
|
||||
## This package XXX is not supported because it has C dependencies. How can I make it work?
|
||||
|
||||
See [Creating a Pyodide package — Version 0.20.0](https://pyodide.org/en/stable/development/new-packages.html).
|
||||
|
||||
## Why is PyScript loading so slowly? Why can’t we put things behind a CDN?
|
||||
|
||||
Packages are already served from the JsDelivr CDN. This is not a downloading speed problem—it's WASM assembly time. PyScript loads slowly because the Python standard library and packages are large and WebAssembly code needs to be compiled and run by the browser after they are loaded for the first time.
|
||||
|
||||
Currently, there are efforts to mitigate the problem, and Pyodide is currently working on a bundler, for instance.
|
||||
|
||||
## Is PyScript owned by Anaconda?
|
||||
|
||||
Anaconda doesn’t own PyScript. It is an open source project developed by Anaconda internally, and Anaconda team members are currently the main contributors, but the repo itself is public. We are working on a steering council to ensure the project stays public and owned by the community.
|
||||
|
||||
See [Maintainers](../concepts/governance/maintainers.md).
|
||||
|
||||
See [Governance Policy](../concepts/governance/policy.md).
|
||||
|
||||
## What is the governing model for PyScript?
|
||||
|
||||
See [Governance Policy](../concepts/governance/policy.md).
|
||||
|
||||
## What is the license?
|
||||
|
||||
PyScript uses the Apache-2.0 license.
|
||||
|
||||
Pyodide uses MPL-2.0 license. Various packages are distributed under their corresponding license.
|
||||
|
||||
## Is Pyodide a replacement for CPython? How does PyScript and Pyodide compare to CPython on WASM?
|
||||
|
||||
No. They have different elements that do different things, both of which are additive.
|
||||
|
||||
PyScript sits on top of everything. Pyodide came before the work on CPython and WASM. Patches were created in order for it to work, but now that CPython/WASM are progressing, Pyodide is able to remove a few of those patches. Additionally, CPython doesn’t deal with building Python packages for WASM. WASM related work on upstream CPython will integrate into Pyodide in the near future.
|
||||
|
||||
For a list of differences from “standard” CPython, see [Pyodide Python compatibility — Version 0.20.0](https://pyodide.org/en/stable/usage/wasm-constraints.html).
|
||||
|
||||
## Hasn’t this already been done before by Brython/skulpt?
|
||||
|
||||
No. Brython and Skulpt accomplish different things than PyScript and Pyodide.
|
||||
|
||||
Brython is client-side and functions as syntax on top of Javascript—it is a reimplementation of Python on top of Javascript, without support for packages or a file system. The extraction of a package in normal Python has been replaced completely by something else. You should be able to run code with minimal changes; however, that isn’t possible with Brython.
|
||||
|
||||
Skulpt is a cross compiler from Python to Javascript, leveraged for compatibility with the Python ecosystem. If you want to do any more Python, you would have to send it over.
|
||||
|
||||
Fairly similar syntax to normal Python, but not exactly the same. If Brython was an interpreter and a full Python implementation, it could be used with PyScript to leverage packages like Numpy, but that isn’t possible as it stands today. They do have the Python script tag, but it is a smaller API and not as feature rich—which is why PyScript is built on Pyodide, Emscripten, and WebAssembly.
|
||||
|
||||
## How can I contribute/help?
|
||||
|
||||
**PyScript** - we are currently working on building documentation and a contributing guide. In the meantime, just ask to help on the PyScript [discussions page](https://anaconda.cloud/s/pyscript) or in the [repo](http://github.com/pyscript/pyscript).
|
||||
|
||||
**Pyodide** - refer to [Pyodide docs](https://pyodide.org/en/stable/development/contributing.html).
|
||||
|
||||
## WebAssembly Security
|
||||
|
||||
See [WebAssembly docs](https://webassembly.org/docs/security/).
|
||||
|
||||
## Why don’t Requests and Black work?
|
||||
|
||||
Requests and Black do not work out of the box because they weren’t meant for the browser. On the browser, sockets multiprocessing works differently, so there is work to be done to actually match things.
|
||||
|
||||
For Black, it’s a design choice that can be patched. This is currently being addressed by the team at Pyodide.
|
||||
|
||||
Requests do not work because of the sockets issue (sockets and websockets are two different things) and requests are blocking—which you don’t want in the browser. It’ll require putting the interpreter on a webworker and utilizing an assistant, but on the main thread it’s unlikely that it’ll work.
|
||||
|
||||
There are options as a path forward. For example, Requests can be leveraged using javascript libraries, or building a python async version of Requests API or a python wrapper for fetch (pyfetch), etc. The websockets library has a client side that could be made to work—given that it has all asynchronous APIs, there’s nothing fundamentally difficult about getting it to work.
|
||||
54
docs/reference/index.md
Normal file
54
docs/reference/index.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Reference
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
glob:
|
||||
caption: Elements
|
||||
---
|
||||
elements/*
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
glob:
|
||||
caption: Modules
|
||||
---
|
||||
modules/*
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
glob:
|
||||
caption: Plugins
|
||||
---
|
||||
plugins/*
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
glob:
|
||||
caption: API
|
||||
---
|
||||
API/*
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 2
|
||||
glob:
|
||||
caption: Exceptions
|
||||
---
|
||||
exceptions
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
caption: Miscellaneous
|
||||
---
|
||||
faq
|
||||
```
|
||||
77
docs/reference/modules/pyscript.md
Normal file
77
docs/reference/modules/pyscript.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# pyscript
|
||||
|
||||
The code underlying PyScript is a JavaScript module, which is loaded and executed by the browser. This is what loads when you include, for example, `<script defer src="https://pyscript.net/latest/pyscript.js">` in your HTML.
|
||||
|
||||
The module is exported to the browser as `pyscript`. The exports from this module are:
|
||||
|
||||
## pyscript.version
|
||||
|
||||
Once `pyscript.js` has loaded, the version of PyScript that is currently running can be accessed via `pyscript.version`.
|
||||
```html
|
||||
<script defer onload="console.log(`${pyscript.version}`)" src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
```
|
||||
```js
|
||||
//example result
|
||||
Object { year: 2022, month: 11, patch: 1, releaselevel: "dev" }
|
||||
```
|
||||
|
||||
## pyscript.interpreter
|
||||
|
||||
The Interpreter object which is responsible for executing Python code in the Browser. Currently, all interpreters are assumed to be Pyodide interpreters, but there is flexibility to expand this to other web-based Python interpreters in future versions.
|
||||
|
||||
The Interpreter object has the following attributes
|
||||
|
||||
| attribute | type | description |
|
||||
|---------------------|-----------------------|---------------------------------------------------------------------------------|
|
||||
| **src** | string | The URL from which the current interpreter was fetched |
|
||||
| **interface** | InterpreterInterface | A reference to the interpreter object itself |
|
||||
| **globals** | any | The globals dictionary of the interpreter, if applicable/accessible |
|
||||
| **name (optional)** | string | A user-designated name for the interpreter |
|
||||
| **lang (optional)** | string | A user-designation for the language the interpreter runs ('Python', 'C++', etc) |
|
||||
|
||||
### pyscript.interpreter.src
|
||||
|
||||
The URL from which the current interpreter was fetched.
|
||||
|
||||
### pyscript.interpreter.interface
|
||||
|
||||
A reference to the Interpreter wrapper that PyScript uses to execute code. object itself. This allows other frameworks, modules etc to interact with the same [(Pyodide) interpreter instance](https://pyodide.org/en/stable/usage/api/js-api.html) that PyScript uses.
|
||||
|
||||
For example, assuming we've loaded Pyodide, we can access the methods of the Pyodide interpreter as follows:
|
||||
|
||||
```html
|
||||
<button onclick="logFromPython()">Click Me to Run Some Python</button>
|
||||
<script>
|
||||
function logFromPython(){
|
||||
pyscript.interpreter.interface.runPython(`
|
||||
animal = "Python"
|
||||
sound = "sss"
|
||||
console.warn(f"{animal}s go " + sound * 5)
|
||||
`)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### pyscript.interpreter.globals
|
||||
|
||||
A proxy for the interpreter's `globals()` dictionary. For example:
|
||||
|
||||
```html
|
||||
<body>
|
||||
<py-script>x = 42</py-script>
|
||||
|
||||
<button onclick="showX()">Click Me to Get 'x' from Python</button>
|
||||
<script>
|
||||
function showX(){
|
||||
console.log(`In Python right now, x = ${pyscript.interpreter.globals.get('x')}`)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
### pyscript.interpreter.name
|
||||
|
||||
A user-supplied string for the interpreter given at its creation. For user reference only - does not affect the operation of the interpreter or PyScript.
|
||||
|
||||
### PyScript.interpreter.lang
|
||||
|
||||
A user-supplied string for the language the interpreter uses given at its creation. For user reference only - does not affect the operation of the interpreter or PyScript.
|
||||
65
docs/reference/plugins/py-splashscreen.md
Normal file
65
docs/reference/plugins/py-splashscreen.md
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
# <py-splashscreen>
|
||||
|
||||
This is one of the core plugins in PyScript, which is active by default. The splashscreen is the first thing you see when you open a page with Pyscript while it is loading itself and all the necessary resources.
|
||||
|
||||
## Configuration
|
||||
|
||||
You can control how `<py-splashscreen>` behaves by setting the value of the `splashscreen` configuration in your `<py-config>`.
|
||||
|
||||
|
||||
| parameter | default | description |
|
||||
|-------------|-----------|-------------|
|
||||
| `autoclose` | `true` | Whether to close the splashscreen automatically when the page is ready or not |
|
||||
| `enabled` | `true` | Whether to show the splashscreen or not |
|
||||
|
||||
### Examples
|
||||
|
||||
#### Disabling the splashscreen
|
||||
|
||||
If you don't want the splashscreen to show and log any loading messages, you can disable it by setting the splashscreen option `enabled` to `false`.
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[splashscreen]
|
||||
enabled = false
|
||||
</py-config>
|
||||
```
|
||||
|
||||
#### Disabling autoclose
|
||||
|
||||
If you want to keep the splashscreen open even after the page is ready, you can disable autoclose by setting `autoclose` to `false`.
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[splashscreen]
|
||||
autoclose = false
|
||||
</py-config>
|
||||
```
|
||||
|
||||
## Adding custom messages
|
||||
|
||||
You can add custom messages to the splashscreen. This is useful if you want to show the user that something is happening in the background for your PyScript app.
|
||||
|
||||
There are two ways to add your custom messages to the splashscreen, either by dispatching a new custom event, `py-status-message` to the document:
|
||||
|
||||
|
||||
```js
|
||||
document.dispatchEvent(new CustomEvent("py-status-message", {detail: "Hello, world!"}))
|
||||
```
|
||||
|
||||
Or by using the `log` method of the `py-splashscreen` tag directly:
|
||||
|
||||
```js
|
||||
const splashscreen = document.querySelector("py-splashscreen")
|
||||
splashscreen.log("Hello, world!")
|
||||
```
|
||||
|
||||
If you wish, you can also send messages directly to the splashscreen from your python code:
|
||||
|
||||
```python
|
||||
from js import document
|
||||
|
||||
splashscreen = document.querySelector("py-splashscreen")
|
||||
splashscreen.log("Hello, world!")
|
||||
```
|
||||
82
docs/reference/plugins/py-terminal.md
Normal file
82
docs/reference/plugins/py-terminal.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# <py-terminal>
|
||||
|
||||
This is one of the core plugins in PyScript, which is active by default. With it, you can print to `stdout` and `stderr` from your python code, and the output will be displayed on the page in `<py-terminal>`.
|
||||
|
||||
## Configuration
|
||||
|
||||
You can control how `<py-terminal>` behaves by setting the values of the `terminal`, `docked`, and `xterm` fields in your configuration in your `<py-config>`.
|
||||
|
||||
For the **terminal** field, these are the values:
|
||||
|
||||
| value | description |
|
||||
|-------|-------------|
|
||||
| `false` | Don't add `<py-terminal>` to the page |
|
||||
| `true` | Automatically add a `<py-terminal>` to the page |
|
||||
| `"auto"` | This is the default. Automatically add a `<py-terminal auto>`, to the page. The terminal is initially hidden and automatically shown as soon as something writes to `stdout` and/or `stderr` |
|
||||
|
||||
For the **docked** field, these are the values:
|
||||
|
||||
| value | description |
|
||||
|-------|-------------|
|
||||
| `false` | Don't dock `<py-terminal>` to the page |
|
||||
| `true` | Automatically dock a `<py-terminal>` to the page |
|
||||
| `"docked"` | This is the default. Automatically add a `<py-terminal docked>`, to the page. The terminal, once visible, is automatically shown at the bottom of the page, covering the width of such page |
|
||||
|
||||
Please note that **docked** mode is currently used as default only when `terminal="auto"`, or *terminal* default, is used.
|
||||
|
||||
In all other cases it's up to the user decide if a terminal should be docked or not.
|
||||
|
||||
For the **xterm** field, these are the values:
|
||||
|
||||
| value | description |
|
||||
|-------|-------------|
|
||||
| `false` | This is the default. The `<py-terminal>` is a simple `<pre>` tag with some CSS styling. |
|
||||
| `true` or `xterm` | The [xtermjs](http://xtermjs.org/) library is loaded and its Terminal object is used as the `<py-terminal>`. It's visibility and position are determined by the `docked` and `auto` keys in the same way as the default `<py-terminal>` |
|
||||
|
||||
The xterm.js [Terminal object](http://xtermjs.org/docs/api/terminal/classes/terminal/) can be accessed directly if you want to adjust its properties, add [custom parser hooks](http://xtermjs.org/docs/guides/hooks/), introduce [xterm.js addons](http://xtermjs.org/docs/guides/using-addons/), etc. Access is best achieved by awaiting the `xtermReady` attribute of the `<py-terminal>` HTML element itself:
|
||||
|
||||
```python
|
||||
import js
|
||||
import asyncio
|
||||
|
||||
async def adjust_term_size(columns, rows):
|
||||
xterm = await js.document.querySelector('py-terminal').xtermReady
|
||||
xterm.resize(columns, rows)
|
||||
|
||||
asyncio.ensure_future(adjust_term_size(40,10))
|
||||
```
|
||||
|
||||
Some terminal-formatting packages read from specific environment variables to determine whether they should emit formatted output; PyScript does not set these variables explicitly - you may need to set them yourself, or force your terminal-formatting package into a state where it outputs correctly formatted output.
|
||||
|
||||
A couple of specific examples:
|
||||
- the [rich](https://github.com/Textualize/rich) will not, by default, output colorful text, but passing `256` or `truecolor` as an argument as the `color_system` parameter to the [Console constructor](https://rich.readthedocs.io/en/stable/reference/console.html#rich.console.Console) will force it to do so. (As of rich v13)
|
||||
- [termcolor](https://github.com/termcolor/termcolor) will not, by default, output colorful text, but setting `os.environ["FORCE_COLOR"] = "True"` or by passing `force_color=True` to the `colored()` function will force it to do so. (As of termcolor v2.3)
|
||||
|
||||
### Examples
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
terminal = true
|
||||
docked = false
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
print("Hello, world!")
|
||||
</py-script>
|
||||
```
|
||||
|
||||
This example will create a new `<py-terminal>`, the value "Hello, world!" that was printed will show in it.
|
||||
|
||||
You can also add one (or more) `<py-terminal>` to the page manually.
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
print("Hello, world!")
|
||||
</py-script>
|
||||
|
||||
<py-terminal></py-terminal>
|
||||
```
|
||||
|
||||
```{note}
|
||||
If you include a `<py-terminal>` in the page, you can skip `terminal` from your `<py-config>`.
|
||||
```
|
||||
5
docs/robots.txt
Normal file
5
docs/robots.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
User-agent: *
|
||||
Disallow: /review/
|
||||
|
||||
Sitemap: https://docs.pyscript.net/sitemap.xml
|
||||
Host: docs.pyscript.net
|
||||
275
docs/tutorials/getting-started.md
Normal file
275
docs/tutorials/getting-started.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# Getting started with PyScript
|
||||
|
||||
This page will guide you through getting started with PyScript.
|
||||
|
||||
## Development setup
|
||||
|
||||
PyScript does not require any development environment other
|
||||
than a web browser (we recommend using [Chrome](https://www.google.com/chrome/)) and a text editor, even though using your [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) of choice might be convenient.
|
||||
|
||||
If you're using [VSCode](https://code.visualstudio.com/), the
|
||||
[Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
|
||||
can be used to reload the page as you edit the HTML file.
|
||||
|
||||
## Installation
|
||||
|
||||
There is no installation required. In this document, we'll use
|
||||
the PyScript assets served on [https://pyscript.net](https://pyscript.net).
|
||||
|
||||
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.
|
||||
|
||||
## Your first PyScript HTML file
|
||||
|
||||
Here's a "Hello, world!" example using PyScript.
|
||||
|
||||
Using your favorite editor, create a new file called `hello.html` in
|
||||
the same directory as your PyScript, JavaScript, and CSS files with the
|
||||
following content, and open the file in your web browser. You can typically
|
||||
open an HTML by double-clicking it in your file explorer.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<py-script>
|
||||
print('Hello, World!')
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Using a Local Server
|
||||
|
||||
In some situations, your browser may forbid loading remote resources like `pyscript.js` and `pyscript.css` when you open an HTML file directly. When this is the case, you may see your Python code in the text of the webpage, and the [browser developer console](https://balsamiq.com/support/faqs/browserconsole/) may show an error like *"Cross origin requests are only supported for HTTP."* The fix for this is to use a [simple local server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/set_up_a_local_testing_server) to make your html file available to the browser.
|
||||
|
||||
If you have python installed on your system, you can use it's basic built-in server for this purpose via the command line. Change the current working directory of your terminal or command line to the folder where your HTML file is stored. From this folder, run `python -m http.server 8080 --bind 127.0.0.1` in your terminal or command line. With the server program running, point your browser to `http://localhost:8080` to view the contents of that folder. (If a file in that folder is called `index.html`, it will be displayed by default.)
|
||||
|
||||
## A more complex example
|
||||
|
||||
Now that we know how you can create a simple 'Hello, World!' example, let's see a more complex example. This example will use the Demo created by [Cheuk Ting Ho](https://github.com/Cheukting). In this example, we will use more features from PyScript.
|
||||
|
||||
### Setting up the base index file
|
||||
|
||||
Let's create a new file called `index.html` and add the following content:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Ice Cream Picker</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
In this first step, we have created the index file, imported `pyscript.css` and `pyscript.js`. We are ready to start adding the elements we need for our application.
|
||||
|
||||
### Importing the needed libraries
|
||||
|
||||
For this example, we will need to install `pandas` and `matplotlib`. We can install libraries using the `<py-config>` tag so we can import them later. Please refer to the [`<py-config>`](../reference/elements/py-config.md) documentation for more information.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Ice Cream Picker</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<py-config>
|
||||
packages = ["matplotlib", "pandas"]
|
||||
</py-config>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Importing the data and exploring
|
||||
|
||||
Now that we have installed the needed libraries, we can import and explore the data. In this step, we need to create a `<py-script>` tag to import our dependencies, read the data with pandas and then use `py-repl` to explore the data.
|
||||
|
||||
You may want to read the [`<py-script>`](../reference/elements/py-script.md) and [`<py-repl>`](../reference/elements/py-repl.md) documentation for more information about these elements.
|
||||
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Ice Cream Picker</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<py-config>
|
||||
packages = ["matplotlib", "pandas"]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import pandas as pd
|
||||
|
||||
from pyodide.http import open_url
|
||||
|
||||
url = (
|
||||
"https://raw.githubusercontent.com/Cheukting/pyscript-ice-cream/main/bj-products.csv"
|
||||
)
|
||||
ice_data = pd.read_csv(open_url(url))
|
||||
</py-script>
|
||||
|
||||
<py-repl>
|
||||
ice_data
|
||||
</py-repl>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Note that we are adding `ice_data` to `py-repl` to pre-populate the REPL with this variable, so you don't have to type it yourself.
|
||||
|
||||
### Creating the plot
|
||||
|
||||
Now that we have the data, we can create the plot. We will use the `matplotlib` library to make the plot. We will use the `display` API to display the plot on the page. You may want to read the [`display`](../reference/API/display.md) documentation for more information.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Ice Cream Picker</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<py-config>
|
||||
packages = ["matplotlib", "pandas"]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from pyodide.http import open_url
|
||||
|
||||
url = (
|
||||
"https://raw.githubusercontent.com/Cheukting/pyscript-ice-cream/main/bj-products.csv"
|
||||
)
|
||||
ice_data = pd.read_csv(open_url(url))
|
||||
|
||||
def plot(data):
|
||||
plt.rcParams["figure.figsize"] = (22,20)
|
||||
fig, ax = plt.subplots()
|
||||
bars = ax.barh(data["name"], data["rating"], height=0.7)
|
||||
ax.bar_label(bars)
|
||||
plt.title("Rating of ice cream flavours of your choice")
|
||||
display(fig, target="graph-area", append=False)
|
||||
|
||||
plot(ice_data)
|
||||
</py-script>
|
||||
|
||||
<py-repl>
|
||||
ice_data
|
||||
</py-repl>
|
||||
|
||||
<div id="graph-area"></div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Select specific flavours
|
||||
|
||||
Now that we have a way to explore the data using `py-repl` and a way to create the plot using all of the data, it's time for us to add a way to select specific flavours.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Ice Cream Picker</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<py-config>
|
||||
packages = ["matplotlib", "pandas"]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import js
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from pyodide.http import open_url
|
||||
from pyodide.ffi import create_proxy
|
||||
|
||||
url = (
|
||||
"https://raw.githubusercontent.com/Cheukting/pyscript-ice-cream/main/bj-products.csv"
|
||||
)
|
||||
ice_data = pd.read_csv(open_url(url))
|
||||
|
||||
current_selected = []
|
||||
flavour_elements = js.document.getElementsByName("flavour")
|
||||
|
||||
def plot(data):
|
||||
plt.rcParams["figure.figsize"] = (22,20)
|
||||
fig, ax = plt.subplots()
|
||||
bars = ax.barh(data["name"], data["rating"], height=0.7)
|
||||
ax.bar_label(bars)
|
||||
plt.title("Rating of ice cream flavours of your choice")
|
||||
display(fig, target="graph-area", append=False)
|
||||
|
||||
def select_flavour(event):
|
||||
for ele in flavour_elements:
|
||||
if ele.checked:
|
||||
current_selected = ele.value
|
||||
break
|
||||
if current_selected == "ALL":
|
||||
plot(ice_data)
|
||||
else:
|
||||
filter = ice_data.apply(lambda x: ele.value in x["ingredients"], axis=1)
|
||||
plot(ice_data[filter])
|
||||
|
||||
ele_proxy = create_proxy(select_flavour)
|
||||
|
||||
for ele in flavour_elements:
|
||||
if ele.value == "ALL":
|
||||
ele.checked = True
|
||||
current_selected = ele.value
|
||||
ele.addEventListener("change", ele_proxy)
|
||||
|
||||
plot(ice_data)
|
||||
|
||||
</py-script>
|
||||
|
||||
<div id="input" style="margin: 20px;">
|
||||
Select your 🍨 flavour: <br/>
|
||||
<input type="radio" id="all" name="flavour" value="ALL">
|
||||
<label for="all"> All 🍧</label>
|
||||
<input type="radio" id="chocolate" name="flavour" value="COCOA">
|
||||
<label for="chocolate"> Chocolate 🍫</label>
|
||||
<input type="radio" id="cherry" name="flavour" value="CHERRIES">
|
||||
<label for="cherry"> Cherries 🍒</label>
|
||||
<input type="radio" id="berries" name="flavour" value="BERRY">
|
||||
<label for="berries"> Berries 🍓</label>
|
||||
<input type="radio" id="cheese" name="flavour" value="CHEESE">
|
||||
<label for="cheese"> Cheese 🧀</label>
|
||||
<input type="radio" id="peanut" name="flavour" value="PEANUT">
|
||||
<label for="peanut"> Peanut 🥜</label>
|
||||
</div>
|
||||
|
||||
<py-repl>
|
||||
ice_data
|
||||
</py-repl>
|
||||
|
||||
<div id="graph-area"></div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
38
docs/tutorials/index.md
Normal file
38
docs/tutorials/index.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Tutorials
|
||||
|
||||
This section contains pyscript tutorials. Each tutorial is a self-contained document that will guide you through a specific topic.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This tutorial will guide you through getting started with PyScript, from installation to writing your first PyScript application. The getting started will show you how to specify dependencies, read a csv file from the web, use `pandas` and `matplotlib` and how to handle user input.
|
||||
|
||||
[Read the get started tutorial](getting-started.md)
|
||||
|
||||
|
||||
## Basics
|
||||
|
||||
This section contains tutorials about the basics of PyScript.
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
glob:
|
||||
---
|
||||
writing-to-page
|
||||
py-click
|
||||
requests
|
||||
```
|
||||
|
||||
## PyScript Configuration
|
||||
|
||||
This section contains tutorials about the PyScript configuration using the `<py-config>` tag.
|
||||
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
glob:
|
||||
---
|
||||
py-config-fetch
|
||||
py-config-interpreter
|
||||
```
|
||||
127
docs/tutorials/py-click.md
Normal file
127
docs/tutorials/py-click.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Handling click events
|
||||
|
||||
This tutorial will show you how to use the `py-click` attribute to handle mouse clicks on elements on your page. The `py-click` attribute is a special attribute that allows you to specify a Python function that will be called when the element is clicked. There are many other events such as py-mouseover, py-focus, py-input, py-keyress etc, which can be used as well. They are listed here [Attr-to-Event](../reference/API/attr_to_event.html)
|
||||
|
||||
## Development setup
|
||||
|
||||
Let's start by building the base HTML page. We will create an HTML page with a button and a paragraph. When the button is clicked, the paragraph will show the current time.
|
||||
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Current Time</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Adding elements to the page
|
||||
|
||||
Let's add a button and a paragraph to the page. The button will have the `py-click` attribute, and the paragraph will have the `id` attribute. The `id` attribute is used to identify the element on the page, and the `py-click` attribute will be used to specify the function that will be called when the button is clicked.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Current Time</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<button py-click="current_time()" id="get-time" class="py-button">Get current time</button>
|
||||
<p id="current-time"></p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
There are two things to note here:
|
||||
|
||||
- You must specify an id for an element that uses any `py-*` attribute
|
||||
- We used the `py-button` class to style the button, this is optional, and these rules are coming from the pyscript.css that we added in the `<head>` section.
|
||||
|
||||
## Creating the Python function
|
||||
|
||||
In this step, we will create the Python function that will be called when the button is clicked. This function will get the current time and update the paragraph with the current time. We will use a `<py-script>` tag to specify the Python code that will be executed when the page is loaded.
|
||||
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Current Time</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<button py-click="current_time()" id="get-time" class="py-button">Get current time</button>
|
||||
<p id="current-time"></p>
|
||||
|
||||
<py-script>
|
||||
import datetime
|
||||
|
||||
def current_time():
|
||||
now = datetime.datetime.now()
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Writing the time to the page
|
||||
|
||||
If you run the example, you will notice that nothing happened. This is because we still need to update the paragraph with the current time. We can do this by using the [`Element` API](../reference/API/element.md) to get the paragraph element and then update it with the current time with the `write` method.
|
||||
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Current Time</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<button py-click="current_time()" id="get-time" class="py-button">Get current time</button>
|
||||
<p id="current-time"></p>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
import datetime
|
||||
|
||||
def current_time():
|
||||
now = datetime.datetime.now()
|
||||
|
||||
# Get paragraph element by id
|
||||
paragraph = Element("current-time")
|
||||
|
||||
# Add current time to the paragraph element
|
||||
paragraph.write(now.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Now, if you refresh the page and click the button, the paragraph will be updated with the current time.
|
||||
188
docs/tutorials/py-config-fetch.md
Normal file
188
docs/tutorials/py-config-fetch.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# Using the fetch from py-config
|
||||
|
||||
This tutorial shows how to use the fetch configuration from `py-config` to fetch two files from a remote server, store them in a local directory, and verify their contents.
|
||||
|
||||
## Development setup
|
||||
|
||||
We will create a todo list application similar to the one in the [examples](https://pyscript.net/examples/todo.html). To do this, we need three things:
|
||||
|
||||
* An `index.html` file containing the HTML for the application.
|
||||
* A `todo.py` file containing the Python code for the application.
|
||||
* A `utils.py` file containing some utility functions for the application.
|
||||
|
||||
|
||||
We will use the `fetch` configuration from `py-config` to fetch these files from a remote server and store them in a local directory.
|
||||
|
||||
### Creating the html file
|
||||
|
||||
In this first step, we will create the `index.html` file and import both `pyscript.css` and `pyscript.js`. These are needed to run our Python code in the browser and style the application.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>My Todo</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Fetching the files
|
||||
|
||||
### Using `fetch` to get the python files
|
||||
|
||||
Now we will use the `fetch` configuration from `py-config` to fetch the `todo.py` and `utils.py` files from a remote server and store them in a local directory called `todo`. Here we will fetch files from different URLs, using a `fetch` per item.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>My Todo</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = "https://pyscript.net/examples/"
|
||||
files = ["utils.py"]
|
||||
[[fetch]]
|
||||
from = "https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/"
|
||||
files = ["todo.py"]
|
||||
</py-config>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Creating a todo application
|
||||
|
||||
### Creating the todo elements
|
||||
|
||||
Now we will create the todo elements in the `body` of the `index.html` file.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>My Todo</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = "https://pyscript.net/examples/"
|
||||
files = ["utils.py"]
|
||||
[[fetch]]
|
||||
from = "https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/"
|
||||
files = ["todo.py"]
|
||||
</py-config>
|
||||
<section>
|
||||
<div class="text-center w-full mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">To Do List</h1>
|
||||
</div>
|
||||
<div>
|
||||
<input id="new-task-content" class="py-input" type="text">
|
||||
<button id="new-task-btn" class="py-button" type="submit" py-click="add_task()">
|
||||
Add task
|
||||
</button>
|
||||
</div>
|
||||
<div id="list-tasks-container" class="flex flex-col-reverse mt-4"></div>
|
||||
<template id="task-template">
|
||||
<section class="task py-li-element">
|
||||
<label for="flex items-center p-2 ">
|
||||
<input class="mr-2" type="checkbox">
|
||||
<p class="m-0 inline"></p>
|
||||
</label>
|
||||
</section>
|
||||
</template>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Our todo application is starting to shape up, although if you try to add any tasks, you will notice that nothing happens. This is because we have not yet imported the `todo.py` file.
|
||||
|
||||
### Importing the needed functions from `todo.py`
|
||||
|
||||
This is where the magic happens. We can import the `todo.py` file by adding it as a source to the `py-script` tag. By specifying the file, pyscript will automatically import the file and run the code in it.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>My Todo</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = "https://pyscript.net/examples/"
|
||||
files = ["utils.py"]
|
||||
[[fetch]]
|
||||
from = "https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/"
|
||||
files = ["todo.py"]
|
||||
</py-config>
|
||||
<py-script>
|
||||
from todo import add_task, add_task_event
|
||||
</py-script>
|
||||
<section>
|
||||
<div class="text-center w-full mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">To Do List</h1>
|
||||
</div>
|
||||
<div>
|
||||
<input id="new-task-content" class="py-input" type="text">
|
||||
<button id="new-task-btn" class="py-button" type="submit" py-click="add_task()">
|
||||
Add task
|
||||
</button>
|
||||
</div>
|
||||
<div id="list-tasks-container" class="flex flex-col-reverse mt-4"></div>
|
||||
<template id="task-template">
|
||||
<section class="task py-li-element">
|
||||
<label for="flex items-center p-2 ">
|
||||
<input class="mr-2" type="checkbox">
|
||||
<p class="m-0 inline"></p>
|
||||
</label>
|
||||
</section>
|
||||
</template>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
You can now save the file and refresh the page. You should now be able to add tasks to your todo list.
|
||||
|
||||
## That's it!
|
||||
|
||||
You have now created a todo application using pyscript. You can add tasks and mark them as done. Let's take a recap of what we have achieved:
|
||||
|
||||
* We have imported three separate files into our `index.html` file using the `py-config` tag.
|
||||
* We have created the necessary HTML code to create our todo's
|
||||
* We have imported functions from the `todo.py` file, using the `py-script` tag.
|
||||
|
||||
For reference, the code from [the gist](https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/todo.py) is the same code that our [todo example](https://pyscript.net/examples/todo.html) uses with a slight change of importing `Element` from `pyscript`.
|
||||
88
docs/tutorials/py-config-interpreter.md
Normal file
88
docs/tutorials/py-config-interpreter.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Setting a pyodide interpreter
|
||||
|
||||
Pyscript will automatically set the interpreter for you, but you can also set it manually. This is useful if you want to use a different version than the one set by default.
|
||||
|
||||
## Development setup
|
||||
|
||||
To get started, let's create a new `index.html` file and import `pyscript.js`.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Interpreter</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
We are using the pyodide CDN to setup our interpreter, but you can also download the files from [the pyodide GitHub releases](https://github.com/pyodide/pyodide/releases/), unzip them and use the `pyodide.js` file as your interpreter.
|
||||
|
||||
## Setting the interpreter
|
||||
|
||||
To set the interpreter, you can use the `interpreter` configuration in the `py-config` element. In this tutorial, we will use the default `TOML` format, but know that you can also use `json` if you prefer by changing the `type` attribute of the `py-config` element.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Interpreter</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
[[interpreters]]
|
||||
src = "https://cdn.jsdelivr.net/pyodide/v0.23.0/full/pyodide.js"
|
||||
name = "pyodide-0.23.0"
|
||||
lang = "python"
|
||||
</py-config>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Confirming the interpreter version
|
||||
|
||||
To confirm that the interpreter is set correctly, you can open the DevTools and check the version from the console. But for the sake of this tutorial, let's create a `py-script` tag and print pyodide's version.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Interpreter</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
[[interpreters]]
|
||||
src = "https://cdn.jsdelivr.net/pyodide/v0.23.0/full/pyodide.js"
|
||||
name = "pyodide-0.23.0"
|
||||
lang = "python"
|
||||
</py-config>
|
||||
<py-script>
|
||||
import pyodide
|
||||
print(pyodide.__version__)
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
123
docs/tutorials/requests.md
Normal file
123
docs/tutorials/requests.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Calling an API using Requests
|
||||
|
||||
This tutorial will show you how to interact with an API using the [Requests](https://requests.readthedocs.io/en/master/) library. Requests is a popular library, but it doesn't work out of the box with Pyscript. We will use the [pyodide-http](https://github.com/koenvo/pyodide-http) library to patch the Requests library, so it works with Pyscript.
|
||||
|
||||
We will use the [JSON Placeholder API](https://jsonplaceholder.typicode.com/), a free fake API that returns fake data.
|
||||
|
||||
## Development Setup
|
||||
|
||||
Let's build the base HTML page to add our `py-config` and `py-script` tags in the next steps.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Requests Tutorial</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Installing the dependencies
|
||||
|
||||
In this step, we will install the dependencies we need to use the Requests library. We will use the `py-config` tag to specify the dependencies we need to install.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Requests Tutorial</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<py-config>
|
||||
packages = ["requests", "pyodide-http"]
|
||||
</py-config>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Patching the Requests library
|
||||
|
||||
Now that we have installed the dependencies, we need to patch the Requests library to work with Pyscript. We will use the `py-script` tag to specify the code that will be executed when the page is loaded.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Requests Tutorial</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<py-config>
|
||||
packages = ["requests", "pyodide-http"]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import pyodide_http
|
||||
pyodide_http.patch_all()
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Making a request
|
||||
|
||||
Finally, let's make a request to the JSON Placeholder API to confirm that everything is working.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Requests Tutorial</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<py-config>
|
||||
packages = ["requests", "pyodide-http"]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import requests
|
||||
import pyodide_http
|
||||
|
||||
# Patch the Requests library so it works with Pyscript
|
||||
pyodide_http.patch_all()
|
||||
|
||||
# Make a request to the JSON Placeholder API
|
||||
response = requests.get("https://jsonplaceholder.typicode.com/todos")
|
||||
print(response.json())
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
In this tutorial, we learned how to use the Requests library to make requests to an API. We also learned how to use the `py-config` and `py-script` tags to install dependencies and execute code when the page is loaded.
|
||||
|
||||
Depending on the API you use, you may need to add additional headers to your request. You can read the [Requests documentation](https://requests.readthedocs.io/en/master/user/quickstart/#custom-headers) to learn more about how to do this.
|
||||
|
||||
You may also be interested in creating your module to make requests. You can read the in-depth guide on [How to make HTTP requests using `PyScript`, in pure Python](../guides/http-requests.md) to learn more about how to do this.
|
||||
212
docs/tutorials/writing-to-page.md
Normal file
212
docs/tutorials/writing-to-page.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# How to write content to the page
|
||||
|
||||
When creating your PyScript application, you will want to write content to the page. This tutorial will explore the different methods you can use to write content to the page and their differences.
|
||||
|
||||
## Development setup
|
||||
|
||||
To get started, we will create an `index.html` file, import PyScript and start building on top of it.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Writing to the page</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Writing content to an element
|
||||
|
||||
Let's first see how we can write content to an element on the page. We will start by creating a `div` element with an `id` of `manual-write`, then create a `py-script` tag that, upon a click of a button, will write 'Hello World' to the `div` element.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Writing to the page</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="manual-write"></div>
|
||||
<button py-click="write_to_page()" id="manual">Say Hello</button>
|
||||
|
||||
<py-script>
|
||||
def write_to_page():
|
||||
manual_div = Element("manual-write")
|
||||
manual_div.element.innerText = "Hello World"
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```{note}
|
||||
When using `py-click` you must supply an `id` to the element you want to use as the trigger.
|
||||
```
|
||||
|
||||
We can now open our `index.html` file and click the button. You will see that "Hello World" will appear in the `div` element. You could also write HTML using `manual_div.element.innerHTML` instead of `innerText`. For example:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Writing to the page</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="manual-write"></div>
|
||||
<button py-click="write_to_page()" id="manual">Say Hello</button>
|
||||
|
||||
<py-script>
|
||||
def write_to_page():
|
||||
manual_div = Element("manual-write")
|
||||
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Writing content with the `display` API
|
||||
|
||||
The `display` API is a simple way to write content to the page. Not only does it allow you to write content to the page, but it also allows you to display a range of different content types such as images, markdown, svgs, json, etc.
|
||||
|
||||
Using the' display' API, let's reuse our previous example and write "Hello World" to the page.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Writing to the page</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="manual-write"></div>
|
||||
<button py-click="write_to_page()" id="manual">Say Hello</button>
|
||||
<div id="display-write"></div>
|
||||
<button py-click="display_to_div()" id="display">Say Things!</button>
|
||||
|
||||
<py-script>
|
||||
def write_to_page():
|
||||
manual_div = Element("manual-write")
|
||||
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
|
||||
|
||||
def display_to_div():
|
||||
display("I display things!", target="display-write")
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```{note}
|
||||
When using the `display` API, you must specify the `target` parameter to tell PyScript where to write the content. If you do not use this parameter, an error will be thrown.
|
||||
```
|
||||
|
||||
You may be interested in reading more about the `display` API in the [Display API](../reference/api/display) section of the documentation.
|
||||
|
||||
## Printing to the page
|
||||
|
||||
We couldn't have a tutorial on writing to the page without mentioning the `print` function. The `print` function is a simple way to write content to the page, that any Python developer will be familiar with. When you use the `print` function, the content will be written to the page in a `py-terminal` element.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Writing to the page</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="manual-write"></div>
|
||||
<button py-click="write_to_page()" id="manual">Say Hello</button>
|
||||
<div id="display-write"></div>
|
||||
<button py-click="display_to_div()" id="display">Say Things!</button>
|
||||
<button py-click="print_to_page()" id="print">Print Things!</button>
|
||||
|
||||
<py-script>
|
||||
def write_to_page():
|
||||
manual_div = Element("manual-write")
|
||||
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
|
||||
|
||||
def display_to_div():
|
||||
display("I display things!", target="display-write")
|
||||
|
||||
def print_to_page():
|
||||
print("I print things!")
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
You may be surprised to see that when you click the "Print Things!" button, the content is written below the rest of the elements on the page in a black canvas. This is because the `print` function writes content to the page in a `py-terminal` element. You can read more about the `py-terminal` element in the [Terminal Element](../reference/plugins/py-terminal) section of the documentation.
|
||||
|
||||
PyScript comes with the `py-terminal` plugin by default and any `stdout` or `stderr` content will be shown in this element. We can be explicit about where we want the terminal to be shown by adding the `<py-terminal>` tag to our HTML.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Writing to the page</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="manual-write"></div>
|
||||
<button py-click="write_to_page()" id="manual">Say Hello</button>
|
||||
<div id="display-write"></div>
|
||||
<button py-click="display_to_div()" id="display">Say Things!</button>
|
||||
<div>
|
||||
<py-terminal>
|
||||
</div>
|
||||
<button py-click="print_to_page()" id="print">Print Things!</button>
|
||||
|
||||
<py-script>
|
||||
def write_to_page():
|
||||
manual_div = Element("manual-write")
|
||||
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
|
||||
|
||||
def display_to_div():
|
||||
display("I display things!", target="display-write")
|
||||
|
||||
def print_to_page():
|
||||
print("I print things!")
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -11,7 +11,8 @@
|
||||
rel="stylesheet"
|
||||
href="https://pyscript.net/latest/pyscript.css"
|
||||
/>
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<script defer src="../../pyscriptjs/build/pyscript.js"></script>
|
||||
<!-- <script defer src="https://pyscript.net/latest/pyscript.js"></script> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -7,7 +7,32 @@ dynamic = ["version"]
|
||||
|
||||
[tool.codespell]
|
||||
ignore-words-list = "afterall"
|
||||
skip = "*.js,*.json"
|
||||
skip = "pyscriptjs/node_modules/*,*.js,*.json"
|
||||
|
||||
[tool.ruff]
|
||||
builtins = [
|
||||
"Element",
|
||||
"pyscript",
|
||||
]
|
||||
ignore = [
|
||||
"S101",
|
||||
"S113",
|
||||
]
|
||||
line-length = 100
|
||||
select = [
|
||||
"B",
|
||||
"C9",
|
||||
"E",
|
||||
"F",
|
||||
"I",
|
||||
"S",
|
||||
"UP",
|
||||
"W",
|
||||
]
|
||||
target-version = "py310"
|
||||
|
||||
[tool.ruff.mccabe]
|
||||
max-complexity = 10
|
||||
|
||||
[tool.setuptools]
|
||||
include-package-data = false
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
extends: "eslint:recommended",
|
||||
overrides: [
|
||||
{
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
files: [".eslintrc.{js,cjs}"],
|
||||
parserOptions: {
|
||||
sourceType: "script",
|
||||
},
|
||||
},
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
ignorePatterns: ["3rd-party"],
|
||||
rules: {
|
||||
"no-implicit-globals": ["error"],
|
||||
},
|
||||
};
|
||||
@@ -1,10 +1,5 @@
|
||||
.eslintrc.cjs
|
||||
.pytest_cache/
|
||||
node_modules/
|
||||
rollup/
|
||||
test/
|
||||
tests/
|
||||
src/stdlib/_pyscript
|
||||
src/stdlib/pyscript.py
|
||||
package-lock.json
|
||||
tsconfig.json
|
||||
|
||||
@@ -1,61 +1,3 @@
|
||||
# @pyscript/core
|
||||
|
||||
We have moved and renamed previous _core_ module as [polyscript](https://github.com/pyscript/polyscript/#readme), which is the base module used in here to build up _PyScript Next_, now hosted in this folder.
|
||||
|
||||
## Documentation
|
||||
|
||||
Please read [core documentation](./docs/README.md) to know more about this project.
|
||||
|
||||
## Development
|
||||
|
||||
Clone this repository then run `npm install` within its folder.
|
||||
|
||||
Use `npm run build` to create all artifacts and _dist_ files.
|
||||
|
||||
Use `npm run server` to test locally, via the `http://localhost:8080/test/` url, smoke tests or to test manually anything you'd like to check.
|
||||
|
||||
### Artifacts
|
||||
|
||||
There are two main artifacts in this project:
|
||||
|
||||
- **stdlib** and its content, where `src/stdlib/pyscript.js` exposes as object literal all the _Python_ content within the folder (recursively)
|
||||
- **plugins** and its content, where `src/plugins.js` exposes all available _dynamic imports_, able to instrument the bundler to create files a part within the _dist/_ folder, so that by default _core_ remains as small as possible
|
||||
|
||||
Accordingly, whenever a file contains this warning at its first line, please do not change such file directly before submitting a merge request, as that file will be overwritten at the next `npm run build` command, either here or in _CI_:
|
||||
|
||||
```js
|
||||
// ⚠️ This file is an artifact: DO NOT MODIFY
|
||||
```
|
||||
|
||||
### Running tests
|
||||
|
||||
Before running the tests, we need to create a tests environment first. To do so run the following command from the root folder of the project:
|
||||
|
||||
```
|
||||
make setup
|
||||
```
|
||||
|
||||
This will create a tests environment [in the root of the project, named `./env`]and install all the dependencies needed to run the tests.
|
||||
|
||||
After the command has completed and the tests environment has been created, you can run the **integration tests** with
|
||||
the following command:
|
||||
|
||||
```
|
||||
make test-integration
|
||||
```
|
||||
|
||||
## `pyscript` python package
|
||||
|
||||
The `pyscript` package available in _Python_ lives in the folder `src/stdlib/pyscript/`.
|
||||
|
||||
All _Python_ files will be embedded automatically whenever `npm run build` happens and reflected into the `src/stdlib/pyscript.js` file.
|
||||
|
||||
It is _core_ responsibility to ensure those files will be available through the Filesystem in either the _main_ thread, or any _worker_.
|
||||
|
||||
## JS plugins
|
||||
|
||||
While community or third party plugins don't need to be part of this repository and can be added just importing `@pyscript/core` as module, there are a few plugins that we would like to make available by default and these are considered _core plugins_.
|
||||
|
||||
To add a _core plugin_ to this project you can define your plugin entry-point and name in the `src/plugins` folder (see the `error.js` example) and create, if necessary, a folder with the same name where extra files or dependencies can be added.
|
||||
|
||||
The _build_ command will bring plugins by name as artifact so that the bundler can create ad-hoc files within the `dist/` folder.
|
||||
|
||||
1
pyscript.core/core.css
Normal file
1
pyscript.core/core.css
Normal file
@@ -0,0 +1 @@
|
||||
py-config,py-script{display:none}
|
||||
3
pyscript.core/core.js
Normal file
3
pyscript.core/core.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1,31 +0,0 @@
|
||||
let queue = Promise.resolve();
|
||||
|
||||
const { exec } = require("node:child_process");
|
||||
|
||||
const build = (fileName) => {
|
||||
if (fileName) console.log(fileName, "changed");
|
||||
else console.log("building without optimizations");
|
||||
queue = queue.then(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
exec(
|
||||
"npm run build:stdlib && npm run build:plugins && npm run build:core",
|
||||
{ cwd: __dirname, env: { ...process.env, NO_MIN: true } },
|
||||
(error) => {
|
||||
if (error) console.error(error);
|
||||
else console.log(fileName || "", "build completed");
|
||||
resolve();
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const options = {
|
||||
ignored: /\/(?:toml|plugins|pyscript)\.[mc]?js$/,
|
||||
persistent: true,
|
||||
};
|
||||
|
||||
require("chokidar").watch("./src", options).on("change", build);
|
||||
|
||||
build();
|
||||
@@ -14,7 +14,7 @@ Accordingly, this is the bare minimum required output to bootstrap *PyScript Nex
|
||||
|
||||
```html
|
||||
<!-- Option 1: based on esm.sh which in turns is jsdlvr -->
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@pyscript/core"></script>
|
||||
<script type="module" src="https://esm.sh/@pyscript/core@latest/core.js"></script>
|
||||
|
||||
<!-- Option 2: based on unpkg.com -->
|
||||
<script type="module" src="https://unpkg.com/@pyscript/core"></script>
|
||||
@@ -28,24 +28,22 @@ If no `<script type="py">` or `<py-script>` tag is present, it is still possible
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { PyWorker } from "https://cdn.jsdelivr.net/npm/@pyscript/core";
|
||||
import { PyWorker } from "https://unpkg.com/@pyscript/core";
|
||||
|
||||
const worker = PyWorker("./code.py", { config: "./config.toml" /* optional */ });
|
||||
</script>
|
||||
```
|
||||
|
||||
Alternatively, it is possible to specify a `worker` attribute to either run embedded code or the provided `src` file.
|
||||
|
||||
#### CSS
|
||||
|
||||
If you are planning to use either `<py-config>` or `<py-script>` tags on the page, where latter case is usually better off with `<script type="py">` instead, you can also use CDNs to land our custom CSS:
|
||||
|
||||
```html
|
||||
<!-- Option 1: based on esm.sh which in turns is jsdlvr -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@pyscript/core/dist/core.css">
|
||||
<link rel="stylesheet" href="https://esm.sh/@pyscript/core@latest/core.css">
|
||||
|
||||
<!-- Option 2: based on unpkg.com -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/@pyscript/core/dist/core.css">
|
||||
<link rel="stylesheet" href="https://unpkg.com/@pyscript/core/css">
|
||||
|
||||
<!-- Option X: any CDN that uses npmjs registry should work -->
|
||||
```
|
||||
@@ -62,30 +60,6 @@ Once again, if you use `<script type="py">` instead, you won't need CSS unless y
|
||||
</script>
|
||||
```
|
||||
|
||||
#### HTML Example
|
||||
|
||||
This is a complete reference to bootstrap *PyScript* in a HTML document.
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>PyScript Next</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@pyscript/core/dist/core.css">
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@pyscript/core"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py">
|
||||
from pyscript import document
|
||||
|
||||
document.body.textContent = "PyScript Next"
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
## Tag attributes API
|
||||
|
||||
@@ -107,11 +81,11 @@ The module itself is currently exporting the following utilities:
|
||||
* **hooks**, which allows plugins to define *ASAP* callbacks or strings that should be executed either in the main thread or the worker before, or after, the code has been executed.
|
||||
|
||||
```js
|
||||
import { hooks } from "https://cdn.jsdelivr.net/npm/@pyscript/core";
|
||||
import { hooks } from "https://unpkg.com/@pyscript/core";
|
||||
|
||||
// example
|
||||
hooks.onInterpreterReady.add((utils, element) => {
|
||||
console.log(element, 'found', 'pyscript is ready');
|
||||
console.lot(element, 'found', 'pyscript is ready');
|
||||
});
|
||||
|
||||
// the hooks namespace
|
||||
@@ -148,9 +122,9 @@ Please note that a *worker* is a completely different environment and it's not p
|
||||
|
||||
However, each worker string can use `from pyscript import x, y, z` as that will be available out of the box.
|
||||
|
||||
## PyScript Python API
|
||||
## PyScript Module API
|
||||
|
||||
The `pyscript` python package offers various utilities in either the main thread or the worker.
|
||||
The python module offers various utilities in either the main thread or the worker.
|
||||
|
||||
The commonly shared utilities are:
|
||||
|
||||
@@ -159,7 +133,7 @@ The commonly shared utilities are:
|
||||
* **display** in both main and worker, refers to the good old `display` utility except:
|
||||
* in the *main* it automatically uses the current script `target` to display content
|
||||
* in the *worker* it still needs to know *where* to display content using the `target="dom-id"` named argument, as workers don't get a default target attached
|
||||
* in both main and worker, the `append=True` is the *default* behavior, which is inherited from the classic PyScript.
|
||||
* in both main and worker, the `append=Flase` is the *default* behavior, which is a breaking change compared to classic PyScript, but because there is no `Element` with its `write(content)` utility, which would have used that `append=False` behind the scene, we've decided that `false` as append default is more desired, specially after porting most examples in *PyScript Next*, where `append=True` is the exception, not the norm.
|
||||
|
||||
#### Extra main-only features
|
||||
|
||||
@@ -172,7 +146,7 @@ The commonly shared utilities are:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { PyWorker } from "https://cdn.jsdelivr.net/npm/@pyscript/core";
|
||||
import { PyWorker } from "https://unpkg.com/@pyscript/core";
|
||||
|
||||
const worker = PyWorker("./worker.py");
|
||||
|
||||
@@ -257,6 +231,15 @@ We might decide to allow TOML too in the future, but the direct config as attrib
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>why worker attribute needs an external file?</strong></summary>
|
||||
<div markdown=1>
|
||||
|
||||
It would create confusion to have worker code embedded directly in the page and let *PyScript* forward the content to be executed as worker, but the separation of concerns felt more aligned with the meaning of bootstrapping a worker: it inevitably happens elsewhere and with little caveats or features here and there, so it's OK for now to keep that separation explicit.
|
||||
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>what are the worker's caveats?</strong></summary>
|
||||
<div markdown=1>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./dist/core.js";
|
||||
@@ -1,2 +0,0 @@
|
||||
// @see https://github.com/jsdelivr/jsdelivr/issues/18528
|
||||
export * from "./core/dist/core.js";
|
||||
1765
pyscript.core/package-lock.json
generated
1765
pyscript.core/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,36 +1,28 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.3.4",
|
||||
"version": "0.1.5",
|
||||
"type": "module",
|
||||
"description": "PyScript",
|
||||
"module": "./index.js",
|
||||
"unpkg": "./index.js",
|
||||
"jsdelivr": "./jsdelivr.js",
|
||||
"browser": "./index.js",
|
||||
"main": "./index.js",
|
||||
"main": "./core.js",
|
||||
"module": "./core.js",
|
||||
"unpkg": "./core.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/core.d.ts",
|
||||
"import": "./src/core.js"
|
||||
"types": "./types/esm/core.d.ts",
|
||||
"import": "./esm/core.js"
|
||||
},
|
||||
"./js": {
|
||||
"import": "./core.js"
|
||||
},
|
||||
"./css": {
|
||||
"import": "./dist/core.css"
|
||||
"import": "./core.css"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"server": "npx static-handler --coi .",
|
||||
"build": "npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && eslint src/ && npm run ts && npm run test:mpy",
|
||||
"build:core": "rm -rf dist && rollup --config rollup/core.config.js && cp src/3rd-party/*.css dist/",
|
||||
"build:plugins": "node rollup/plugins.cjs",
|
||||
"build:stdlib": "node rollup/stdlib.cjs",
|
||||
"build:3rd-party": "node rollup/3rd-party.cjs",
|
||||
"test:mpy": "static-handler --coi . 2>/dev/null & SH_PID=$!; EXIT_CODE=0; playwright test --fully-parallel test/ || EXIT_CODE=$?; kill $SH_PID 2>/dev/null; exit $EXIT_CODE",
|
||||
"dev": "node dev.cjs",
|
||||
"release": "npm run build && npm run zip",
|
||||
"size": "echo -e \"\\033[1mdist/*.js file size\\033[0m\"; for js in $(ls dist/*.js); do echo -e \"\\033[2m$js:\\033[0m $(cat $js | brotli | wc -c) bytes\"; done",
|
||||
"ts": "tsc -p .",
|
||||
"zip": "zip -r dist.zip ./dist"
|
||||
"server": "npx static-handler --cors --coep --coop --corp .",
|
||||
"build": "rollup --config rollup/core.config.js && rollup --config rollup/core-css.config.js && npm run ts",
|
||||
"ts": "tsc -p ."
|
||||
},
|
||||
"keywords": [
|
||||
"pyscript",
|
||||
@@ -41,26 +33,16 @@
|
||||
"dependencies": {
|
||||
"@ungap/with-resolvers": "^0.1.0",
|
||||
"basic-devtools": "^0.1.6",
|
||||
"polyscript": "^0.5.11",
|
||||
"sticky-module": "^0.1.1",
|
||||
"to-json-callback": "^0.1.1",
|
||||
"type-checked-collections": "^0.1.7"
|
||||
"polyscript": "^0.1.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.39.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@webreflection/toml-j0.4": "^1.1.3",
|
||||
"chokidar": "^3.5.3",
|
||||
"eslint": "^8.53.0",
|
||||
"rollup": "^4.3.0",
|
||||
"@rollup/plugin-node-resolve": "^15.1.0",
|
||||
"@rollup/plugin-terser": "^0.4.3",
|
||||
"rollup": "^3.28.0",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
"static-handler": "^0.4.3",
|
||||
"typescript": "^5.2.2",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-readline": "^1.1.1"
|
||||
"static-handler": "^0.4.2",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
const { copyFileSync, writeFileSync } = require("node:fs");
|
||||
const { join } = require("node:path");
|
||||
|
||||
const CDN = "https://cdn.jsdelivr.net/npm";
|
||||
|
||||
const targets = join(__dirname, "..", "src", "3rd-party");
|
||||
const node_modules = join(__dirname, "..", "node_modules");
|
||||
|
||||
const { devDependencies } = require(join(__dirname, "..", "package.json"));
|
||||
|
||||
const v = (name) => devDependencies[name].replace(/[^\d.]/g, "");
|
||||
|
||||
// Fetch a module via jsdelivr CDN `/+esm` orchestration
|
||||
// then sanitize the resulting outcome to avoid importing
|
||||
// anything via `/npm/...` through Rollup
|
||||
const resolve = (name) => {
|
||||
const cdn = `${CDN}/${name}@${v(name)}/+esm`;
|
||||
console.debug("fetching", cdn);
|
||||
return fetch(cdn)
|
||||
.then((b) => b.text())
|
||||
.then((text) =>
|
||||
text.replace(
|
||||
/("|')\/npm\/(.+)?\+esm\1/g,
|
||||
// normalize `/npm/module@version/+esm` as
|
||||
// just `module` so that rollup can do the rest
|
||||
(_, quote, module) => {
|
||||
const i = module.lastIndexOf("@");
|
||||
return `${quote}${module.slice(0, i)}${quote}`;
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
// key/value pairs as:
|
||||
// "3rd-party/file-name.js"
|
||||
// string as content or
|
||||
// Promise<string> as resolved content
|
||||
const modules = {
|
||||
"toml.js": join(node_modules, "@webreflection", "toml-j0.4", "toml.js"),
|
||||
"xterm.js": resolve("xterm"),
|
||||
"xterm.css": fetch(`${CDN}/xterm@${v("xterm")}/css/xterm.min.css`).then(
|
||||
(b) => b.text(),
|
||||
),
|
||||
"xterm-readline.js": resolve("xterm-readline"),
|
||||
};
|
||||
|
||||
for (const [target, source] of Object.entries(modules)) {
|
||||
if (typeof source === "string") copyFileSync(source, join(targets, target));
|
||||
else {
|
||||
source.then((text) => writeFileSync(join(targets, target), text));
|
||||
}
|
||||
}
|
||||
20
pyscript.core/rollup/core-css.config.js
Normal file
20
pyscript.core/rollup/core-css.config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import postcss from "rollup-plugin-postcss";
|
||||
|
||||
export default {
|
||||
input: "./src/core.css",
|
||||
plugins: [
|
||||
postcss({
|
||||
extract: true,
|
||||
sourceMap: false,
|
||||
minimize: !process.env.NO_MIN,
|
||||
plugins: [],
|
||||
}),
|
||||
],
|
||||
output: {
|
||||
file: "./core.css",
|
||||
},
|
||||
onwarn(warning, warn) {
|
||||
if (warning.code === "FILE_NAME_CONFLICT") return;
|
||||
warn(warning);
|
||||
},
|
||||
};
|
||||
@@ -2,42 +2,24 @@
|
||||
// the default exported as npm entry.
|
||||
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import terser from "@rollup/plugin-terser";
|
||||
import postcss from "rollup-plugin-postcss";
|
||||
import { string } from "rollup-plugin-string";
|
||||
|
||||
const plugins = [];
|
||||
const plugins = [
|
||||
string({
|
||||
// Required to be specified
|
||||
include: "**/*.py",
|
||||
}),
|
||||
];
|
||||
|
||||
export default [
|
||||
{
|
||||
export default {
|
||||
input: "./src/core.js",
|
||||
plugins: plugins.concat(
|
||||
process.env.NO_MIN
|
||||
? [nodeResolve(), commonjs()]
|
||||
: [nodeResolve(), commonjs(), terser()],
|
||||
process.env.NO_MIN ? [nodeResolve()] : [nodeResolve(), terser()],
|
||||
),
|
||||
output: {
|
||||
esModule: true,
|
||||
dir: "./dist",
|
||||
file: "./core.js",
|
||||
sourcemap: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "./src/core.css",
|
||||
plugins: [
|
||||
postcss({
|
||||
extract: true,
|
||||
sourceMap: false,
|
||||
minimize: !process.env.NO_MIN,
|
||||
plugins: [],
|
||||
}),
|
||||
],
|
||||
output: {
|
||||
file: "./dist/core.css",
|
||||
},
|
||||
onwarn(warning, warn) {
|
||||
if (warning.code === "FILE_NAME_CONFLICT") return;
|
||||
warn(warning);
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
const { readdirSync, writeFileSync } = require("node:fs");
|
||||
const { join } = require("node:path");
|
||||
|
||||
const plugins = [""];
|
||||
|
||||
for (const file of readdirSync(join(__dirname, "..", "src", "plugins"))) {
|
||||
if (/\.js$/.test(file)) {
|
||||
const name = file.slice(0, -3);
|
||||
const key = /^[a-zA-Z0-9$_]+$/.test(name)
|
||||
? name
|
||||
: `[${JSON.stringify(name)}]`;
|
||||
const value = JSON.stringify(`./plugins/${file}`);
|
||||
plugins.push(
|
||||
// this comment is needed to avoid bundlers eagerly embedding lazy
|
||||
// dependencies, causing all sort of issues once in production
|
||||
` ${key}: () => import(/* webpackIgnore: true */ ${value}),`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
plugins.push("");
|
||||
|
||||
writeFileSync(
|
||||
join(__dirname, "..", "src", "plugins.js"),
|
||||
`// ⚠️ This file is an artifact: DO NOT MODIFY\nexport default {${plugins.join(
|
||||
"\n",
|
||||
)}};\n`,
|
||||
);
|
||||
@@ -1,29 +0,0 @@
|
||||
const {
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
statSync,
|
||||
writeFileSync,
|
||||
} = require("node:fs");
|
||||
const { join } = require("node:path");
|
||||
|
||||
const crawl = (path, json) => {
|
||||
for (const file of readdirSync(path)) {
|
||||
const full = join(path, file);
|
||||
if (/\.py$/.test(file)) json[file] = readFileSync(full).toString();
|
||||
else if (statSync(full).isDirectory() && !file.endsWith("_"))
|
||||
crawl(full, (json[file] = {}));
|
||||
}
|
||||
};
|
||||
|
||||
const json = {};
|
||||
|
||||
crawl(join(__dirname, "..", "src", "stdlib"), json);
|
||||
|
||||
writeFileSync(
|
||||
join(__dirname, "..", "src", "stdlib", "pyscript.js"),
|
||||
`// ⚠️ This file is an artifact: DO NOT MODIFY\nexport default ${JSON.stringify(
|
||||
json,
|
||||
null,
|
||||
" ",
|
||||
)};\n`,
|
||||
);
|
||||
7
pyscript.core/src/3rd-party/README.md
vendored
7
pyscript.core/src/3rd-party/README.md
vendored
@@ -1,7 +0,0 @@
|
||||
# PyScript 3rd Party
|
||||
|
||||
This folder contains artifacts created via [3rd-party.cjs](../../rollup/3rd-party.cjs).
|
||||
|
||||
As we would like to offer a way to run PyScript offline, and we already offer a `dist` folder with all the necessary scripts, we have created a foreign dependencies resolver that allow to lazy-load CDN dependencies out of the box.
|
||||
|
||||
Please **note** these dependencies are **not interpreters**, because interpreters have their own mechanism, folders structure, WASM files, and whatnot, to work locally, but at least XTerm or the TOML parser, among other lazy dependencies, should be available within the dist folder.
|
||||
@@ -1,17 +0,0 @@
|
||||
import TYPES from "./types.js";
|
||||
|
||||
const waitForIt = [];
|
||||
|
||||
for (const [TYPE] of TYPES) {
|
||||
const selectors = [`script[type="${TYPE}"]`, `${TYPE}-script`];
|
||||
for (const element of document.querySelectorAll(selectors.join(","))) {
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
waitForIt.push(promise);
|
||||
element.addEventListener(`${TYPE}:done`, resolve, { once: true });
|
||||
}
|
||||
}
|
||||
|
||||
// wait for all the things then cleanup
|
||||
Promise.all(waitForIt).then(() => {
|
||||
dispatchEvent(new Event("py:all-done"));
|
||||
});
|
||||
@@ -1,126 +0,0 @@
|
||||
/**
|
||||
* This file parses a generic <py-config> or config attribute
|
||||
* to use as base config for all py-script elements, importing
|
||||
* also a queue of plugins *before* the interpreter (if any) resolves.
|
||||
*/
|
||||
import { $ } from "basic-devtools";
|
||||
|
||||
import TYPES from "./types.js";
|
||||
import allPlugins from "./plugins.js";
|
||||
import { robustFetch as fetch, getText } from "./fetch.js";
|
||||
import { ErrorCode } from "./exceptions.js";
|
||||
|
||||
const badURL = (url, expected = "") => {
|
||||
let message = `(${ErrorCode.BAD_CONFIG}): Invalid URL: ${url}`;
|
||||
if (expected) message += `\nexpected ${expected} content`;
|
||||
throw new Error(message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a string, returns its trimmed content as text,
|
||||
* fetching it from a file if the content is a URL.
|
||||
* @param {string} config either JSON, TOML, or a file to fetch
|
||||
* @param {string?} type the optional type to enforce
|
||||
* @returns {{json: boolean, toml: boolean, text: string}}
|
||||
*/
|
||||
const configDetails = async (config, type) => {
|
||||
let text = config?.trim();
|
||||
// we only support an object as root config
|
||||
let url = "",
|
||||
toml = false,
|
||||
json = /^{/.test(text) && /}$/.test(text);
|
||||
// handle files by extension (relaxing urls parts after)
|
||||
if (!json && /\.(\w+)(?:\?\S*)?$/.test(text)) {
|
||||
const ext = RegExp.$1;
|
||||
if (ext === "json" && type !== "toml") json = true;
|
||||
else if (ext === "toml" && type !== "json") toml = true;
|
||||
else badURL(text, type);
|
||||
url = text;
|
||||
text = (await fetch(url).then(getText)).trim();
|
||||
}
|
||||
return { json, toml: toml || (!json && !!text), text, url };
|
||||
};
|
||||
|
||||
const syntaxError = (type, url, { message }) => {
|
||||
let str = `(${ErrorCode.BAD_CONFIG}): Invalid ${type}`;
|
||||
if (url) str += ` @ ${url}`;
|
||||
return new SyntaxError(`${str}\n${message}`);
|
||||
};
|
||||
|
||||
const configs = new Map();
|
||||
|
||||
for (const [TYPE] of TYPES) {
|
||||
/** @type {Promise<[...any]>} A Promise wrapping any plugins which should be loaded. */
|
||||
let plugins;
|
||||
|
||||
/** @type {any} The PyScript configuration parsed from the JSON or TOML object*. May be any of the return types of JSON.parse() or toml-j0.4's parse() ( {number | string | boolean | null | object | Array} ) */
|
||||
let parsed;
|
||||
|
||||
/** @type {SyntaxError | undefined} The error thrown when parsing the PyScript config, if any.*/
|
||||
let error;
|
||||
|
||||
let config,
|
||||
type,
|
||||
pyConfig = $(`${TYPE}-config`);
|
||||
if (pyConfig) {
|
||||
config = pyConfig.getAttribute("src") || pyConfig.textContent;
|
||||
type = pyConfig.getAttribute("type");
|
||||
} else {
|
||||
pyConfig = $(
|
||||
[
|
||||
`script[type="${TYPE}"][config]:not([worker])`,
|
||||
`${TYPE}-script[config]:not([worker])`,
|
||||
].join(","),
|
||||
);
|
||||
if (pyConfig) config = pyConfig.getAttribute("config");
|
||||
}
|
||||
|
||||
// catch possible fetch errors
|
||||
if (config) {
|
||||
try {
|
||||
const { json, toml, text, url } = await configDetails(config, type);
|
||||
config = text;
|
||||
if (json || type === "json") {
|
||||
try {
|
||||
parsed = JSON.parse(text);
|
||||
} catch (e) {
|
||||
error = syntaxError("JSON", url, e);
|
||||
}
|
||||
} else if (toml || type === "toml") {
|
||||
try {
|
||||
const { parse } = await import(
|
||||
/* webpackIgnore: true */ "./3rd-party/toml.js"
|
||||
);
|
||||
parsed = parse(text);
|
||||
} catch (e) {
|
||||
error = syntaxError("TOML", url, e);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all plugins and optionally ignore only
|
||||
// those flagged as "undesired" via `!` prefix
|
||||
const toBeAwaited = [];
|
||||
for (const [key, value] of Object.entries(allPlugins)) {
|
||||
if (error) {
|
||||
if (key === "error") {
|
||||
// show on page the config is broken, meaning that
|
||||
// it was not possible to disable error plugin neither
|
||||
// as that part wasn't correctly parsed anyway
|
||||
value().then(({ notify }) => notify(error.message));
|
||||
}
|
||||
} else if (!parsed?.plugins?.includes(`!${key}`)) {
|
||||
toBeAwaited.push(value().then(({ default: p }) => p));
|
||||
}
|
||||
}
|
||||
|
||||
// assign plugins as Promise.all only if needed
|
||||
plugins = Promise.all(toBeAwaited);
|
||||
|
||||
configs.set(TYPE, { config: parsed, plugins, error });
|
||||
}
|
||||
|
||||
export default configs;
|
||||
@@ -1,6 +1,4 @@
|
||||
py-script,
|
||||
py-config,
|
||||
mpy-script,
|
||||
mpy-config {
|
||||
py-config {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,38 +1,85 @@
|
||||
/*! (c) PyScript Development Team */
|
||||
|
||||
import stickyModule from "sticky-module";
|
||||
import "@ungap/with-resolvers";
|
||||
import { $ } from "basic-devtools";
|
||||
import { define, XWorker } from "polyscript";
|
||||
|
||||
import {
|
||||
INVALID_CONTENT,
|
||||
Hook,
|
||||
XWorker,
|
||||
assign,
|
||||
dedent,
|
||||
define,
|
||||
defineProperty,
|
||||
dispatch,
|
||||
queryTarget,
|
||||
unescape,
|
||||
whenDefined,
|
||||
} from "polyscript/exports";
|
||||
// TODO: this is not strictly polyscript related but handy ... not sure
|
||||
// we should factor this utility out a part but this works anyway.
|
||||
import { queryTarget } from "../node_modules/polyscript/esm/script-handler.js";
|
||||
import { Hook } from "../node_modules/polyscript/esm/worker/hooks.js";
|
||||
|
||||
import "./all-done.js";
|
||||
import TYPES from "./types.js";
|
||||
import configs from "./config.js";
|
||||
import sync from "./sync.js";
|
||||
import bootstrapNodeAndPlugins from "./plugins-helper.js";
|
||||
import { ErrorCode } from "./exceptions.js";
|
||||
import { robustFetch as fetch, getText } from "./fetch.js";
|
||||
import { hooks, main, worker, codeFor, createFunction } from "./hooks.js";
|
||||
import { robustFetch as fetch } from "./fetch.js";
|
||||
|
||||
const { defineProperty } = Object;
|
||||
|
||||
const getText = (body) => body.text();
|
||||
|
||||
// allows lazy element features on code evaluation
|
||||
let currentElement;
|
||||
|
||||
// generic helper to disambiguate between custom element and script
|
||||
const isScript = ({ tagName }) => tagName === "SCRIPT";
|
||||
// create a unique identifier when/if needed
|
||||
let id = 0;
|
||||
const getID = (prefix = "py") => `${prefix}-${id++}`;
|
||||
|
||||
// find the shared config for all py-script elements
|
||||
let config;
|
||||
let pyConfig = $("py-config");
|
||||
if (pyConfig) config = pyConfig.getAttribute("src") || pyConfig.textContent;
|
||||
else {
|
||||
pyConfig = $('script[type="py"]');
|
||||
config = pyConfig?.getAttribute("config");
|
||||
}
|
||||
|
||||
if (/^https?:\/\//.test(config)) config = await fetch(config).then(getText);
|
||||
|
||||
// generic helper to disambiguate between custom element and script
|
||||
const isScript = (element) => element.tagName === "SCRIPT";
|
||||
|
||||
// helper for all script[type="py"] out there
|
||||
const before = (script) => {
|
||||
defineProperty(document, "currentScript", {
|
||||
configurable: true,
|
||||
get: () => script,
|
||||
});
|
||||
};
|
||||
|
||||
const after = () => {
|
||||
delete document.currentScript;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a generic DOM Element, tries to fetch the 'src' attribute, if present.
|
||||
* It either throws an error if the 'src' can't be fetched or it returns a fallback
|
||||
* content as source.
|
||||
*/
|
||||
const fetchSource = async (tag, io) => {
|
||||
if (tag.hasAttribute("src")) {
|
||||
try {
|
||||
return await fetch(tag.getAttribute("src")).then(getText);
|
||||
} catch (error) {
|
||||
io.stderr(error);
|
||||
}
|
||||
}
|
||||
return tag.textContent;
|
||||
};
|
||||
|
||||
// common life-cycle handlers for any node
|
||||
const bootstrapNodeAndPlugins = (pyodide, element, callback, hook) => {
|
||||
if (isScript(element)) callback(element);
|
||||
for (const fn of hooks[hook]) fn(pyodide, element);
|
||||
};
|
||||
|
||||
// these are imported as string (via rollup)
|
||||
import init_py from "./stdlib/_pyscript/__init__.py";
|
||||
import display_py from "./stdlib/_pyscript/_display.py";
|
||||
|
||||
const writeStdlib = (pyodide, element) => {
|
||||
console.log("writeStdlib!");
|
||||
const FS = pyodide.interpreter.FS;
|
||||
FS.mkdirTree("/home/pyodide/_pyscript");
|
||||
FS.writeFile("_pyscript/__init__.py", init_py, { encoding: "utf8" });
|
||||
FS.writeFile("_pyscript/_display.py", display_py, { encoding: "utf8" });
|
||||
};
|
||||
|
||||
let shouldRegister = true;
|
||||
const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
|
||||
// automatically use the pyscript stderr (when/if defined)
|
||||
// this defaults to console.error
|
||||
@@ -41,275 +88,171 @@ const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
|
||||
worker.onerror = ({ error }) => io.stderr(error);
|
||||
return worker;
|
||||
}
|
||||
// trap once the python `display` utility (borrowed from "classic PyScript")
|
||||
// provide the regular Pyodide globals instead of those from xworker
|
||||
// const pyDisplay = interpreter.runPython(
|
||||
// [
|
||||
// "import js",
|
||||
// "document=js.document",
|
||||
// "window=js",
|
||||
// display,
|
||||
// "display",
|
||||
// ].join("\n"),
|
||||
// );
|
||||
const pyDisplay = interpreter.runPython(`
|
||||
from _pyscript import display
|
||||
display
|
||||
`);
|
||||
|
||||
// enrich the Python env with some JS utility for main
|
||||
interpreter.registerJsModule("_pyscript", {
|
||||
interpreter.registerJsModule("pyscript", {
|
||||
PyWorker,
|
||||
get target() {
|
||||
return isScript(currentElement)
|
||||
document,
|
||||
window,
|
||||
// a getter to ensure if multiple scripts with same
|
||||
// env (py) runs, their execution code will have the correct
|
||||
// display reference with automatic target
|
||||
get display() {
|
||||
const id = isScript(currentElement)
|
||||
? currentElement.target.id
|
||||
: currentElement.id;
|
||||
|
||||
return (...args) => {
|
||||
const last = args.at(-1);
|
||||
let kw = { target: id, append: false };
|
||||
if (
|
||||
typeof last === "object" &&
|
||||
last &&
|
||||
("target" in last || "append" in last)
|
||||
)
|
||||
kw = { ...kw, ...args.pop() };
|
||||
pyDisplay.callKwargs(...args, kw);
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// avoid multiple initialization of the same library
|
||||
const [
|
||||
{
|
||||
PyWorker: exportedPyWorker,
|
||||
hooks: exportedHooks,
|
||||
config: exportedConfig,
|
||||
whenDefined: exportedWhenDefined,
|
||||
},
|
||||
alreadyLive,
|
||||
] = stickyModule("@pyscript/core", {
|
||||
PyWorker,
|
||||
hooks,
|
||||
config: {},
|
||||
whenDefined,
|
||||
});
|
||||
export const hooks = {
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRun: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRunAync: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRun: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRunAsync: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onInterpreterReady: new Set(),
|
||||
|
||||
export {
|
||||
TYPES,
|
||||
exportedPyWorker as PyWorker,
|
||||
exportedHooks as hooks,
|
||||
exportedConfig as config,
|
||||
exportedWhenDefined as whenDefined,
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunWorker: new Set(),
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunWorkerAsync: new Set(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunWorker: new Set(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunWorkerAsync: new Set(),
|
||||
};
|
||||
|
||||
const hooked = new Map();
|
||||
// XXX antocuni: I think this is broken, because now _display.py imports
|
||||
// window and document directly from js
|
||||
const workerPyScriptModule = [
|
||||
"from pyodide_js import FS",
|
||||
`FS.writeFile('./pyscript.py', ${JSON.stringify(
|
||||
[
|
||||
"import polyscript",
|
||||
"document=polyscript.xworker.window.document",
|
||||
"window=polyscript.xworker.window",
|
||||
"sync=polyscript.xworker.sync",
|
||||
display_py,
|
||||
].join("\n"),
|
||||
)})`,
|
||||
].join("\n");
|
||||
|
||||
for (const [TYPE, interpreter] of TYPES) {
|
||||
// avoid any dance if the module already landed
|
||||
if (alreadyLive) break;
|
||||
|
||||
const dispatchDone = (element, isAsync, result) => {
|
||||
if (isAsync) result.then(() => dispatch(element, TYPE, "done"));
|
||||
else dispatch(element, TYPE, "done");
|
||||
};
|
||||
|
||||
const { config, plugins, error } = configs.get(TYPE);
|
||||
|
||||
// create a unique identifier when/if needed
|
||||
let id = 0;
|
||||
const getID = (prefix = TYPE) => `${prefix}-${id++}`;
|
||||
|
||||
/**
|
||||
* Given a generic DOM Element, tries to fetch the 'src' attribute, if present.
|
||||
* It either throws an error if the 'src' can't be fetched or it returns a fallback
|
||||
* content as source.
|
||||
*/
|
||||
const fetchSource = async (tag, io, asText) => {
|
||||
if (tag.hasAttribute("src")) {
|
||||
try {
|
||||
return await fetch(tag.getAttribute("src")).then(getText);
|
||||
} catch (error) {
|
||||
io.stderr(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (asText) return dedent(tag.textContent);
|
||||
|
||||
const code = dedent(unescape(tag.innerHTML));
|
||||
console.warn(
|
||||
`Deprecated: use <script type="${TYPE}"> for an always safe content parsing:\n`,
|
||||
code,
|
||||
);
|
||||
return code;
|
||||
const workerHooks = {
|
||||
codeBeforeRunWorker: () =>
|
||||
[workerPyScriptModule, ...hooks.codeBeforeRunWorker].join("\n"),
|
||||
codeBeforeRunWorkerAsync: () =>
|
||||
[workerPyScriptModule, ...hooks.codeBeforeRunWorkerAsync].join("\n"),
|
||||
codeAfterRunWorker: () => [...hooks.codeAfterRunWorker].join("\n"),
|
||||
codeAfterRunWorkerAsync: () =>
|
||||
[...hooks.codeAfterRunWorkerAsync].join("\n"),
|
||||
};
|
||||
|
||||
// define the module as both `<script type="py">` and `<py-script>`
|
||||
// but only if the config didn't throw an error
|
||||
if (!error) {
|
||||
// ensure plugins are bootstrapped already before custom type definition
|
||||
// NOTE: we cannot top-level await in here as plugins import other utilities
|
||||
// from core.js itself so that custom definition should not be blocking.
|
||||
plugins.then(() => {
|
||||
// possible early errors sent by polyscript
|
||||
const errors = new Map();
|
||||
|
||||
// specific main and worker hooks
|
||||
const hooks = {
|
||||
main: {
|
||||
...codeFor(main),
|
||||
async onReady(wrap, element) {
|
||||
if (shouldRegister) {
|
||||
shouldRegister = false;
|
||||
registerModule(wrap);
|
||||
}
|
||||
|
||||
define("py", {
|
||||
config,
|
||||
env: "py-script",
|
||||
interpreter: "pyodide",
|
||||
...workerHooks,
|
||||
onBeforeRun(pyodide, element) {
|
||||
currentElement = element;
|
||||
bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRun");
|
||||
},
|
||||
onBeforeRunAync(pyodide, element) {
|
||||
currentElement = element;
|
||||
bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRunAync");
|
||||
},
|
||||
onAfterRun(pyodide, element) {
|
||||
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRun");
|
||||
},
|
||||
onAfterRunAsync(pyodide, element) {
|
||||
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRunAsync");
|
||||
},
|
||||
async onInterpreterReady(pyodide, element) {
|
||||
console.log("onInterpreterReady");
|
||||
writeStdlib(pyodide, element);
|
||||
console.log("after writeStdlib");
|
||||
registerModule(pyodide, element);
|
||||
// allows plugins to do whatever they want with the element
|
||||
// before regular stuff happens in here
|
||||
for (const callback of main("onReady"))
|
||||
await callback(wrap, element);
|
||||
|
||||
// now that all possible plugins are configured,
|
||||
// bail out if polyscript encountered an error
|
||||
if (errors.has(element)) {
|
||||
let { message } = errors.get(element);
|
||||
errors.delete(element);
|
||||
const clone = message === INVALID_CONTENT;
|
||||
message = `(${ErrorCode.CONFLICTING_CODE}) ${message} for `;
|
||||
message += element.cloneNode(clone).outerHTML;
|
||||
wrap.io.stderr(message);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const callback of hooks.onInterpreterReady)
|
||||
callback(pyodide, element);
|
||||
if (isScript(element)) {
|
||||
const {
|
||||
attributes: { async: isAsync, target },
|
||||
} = element;
|
||||
const hasTarget = !!target?.value;
|
||||
const show = hasTarget
|
||||
? queryTarget(element, target.value)
|
||||
? queryTarget(target.value)
|
||||
: document.createElement("script-py");
|
||||
|
||||
if (!hasTarget) {
|
||||
const { head, body } = document;
|
||||
if (head.contains(element)) body.append(show);
|
||||
else element.after(show);
|
||||
}
|
||||
if (!hasTarget) element.after(show);
|
||||
if (!show.id) show.id = getID();
|
||||
|
||||
// allows the code to retrieve the target element via
|
||||
// document.currentScript.target if needed
|
||||
defineProperty(element, "target", { value: show });
|
||||
|
||||
// notify before the code runs
|
||||
dispatch(element, TYPE, "ready");
|
||||
dispatchDone(
|
||||
element,
|
||||
isAsync,
|
||||
wrap[`run${isAsync ? "Async" : ""}`](
|
||||
await fetchSource(element, wrap.io, true),
|
||||
),
|
||||
pyodide[`run${isAsync ? "Async" : ""}`](
|
||||
await fetchSource(element, pyodide.io),
|
||||
);
|
||||
} else {
|
||||
// resolve PyScriptElement to allow connectedCallback
|
||||
element._wrap.resolve(wrap);
|
||||
element._pyodide.resolve(pyodide);
|
||||
}
|
||||
console.debug("[pyscript/main] PyScript Ready");
|
||||
},
|
||||
onWorker(_, xworker) {
|
||||
assign(xworker.sync, sync);
|
||||
for (const callback of main("onWorker"))
|
||||
callback(_, xworker);
|
||||
},
|
||||
onBeforeRun(wrap, element) {
|
||||
currentElement = element;
|
||||
bootstrapNodeAndPlugins(
|
||||
main,
|
||||
wrap,
|
||||
element,
|
||||
"onBeforeRun",
|
||||
);
|
||||
},
|
||||
onBeforeRunAsync(wrap, element) {
|
||||
currentElement = element;
|
||||
return bootstrapNodeAndPlugins(
|
||||
main,
|
||||
wrap,
|
||||
element,
|
||||
"onBeforeRunAsync",
|
||||
);
|
||||
},
|
||||
onAfterRun(wrap, element) {
|
||||
bootstrapNodeAndPlugins(
|
||||
main,
|
||||
wrap,
|
||||
element,
|
||||
"onAfterRun",
|
||||
);
|
||||
},
|
||||
onAfterRunAsync(wrap, element) {
|
||||
return bootstrapNodeAndPlugins(
|
||||
main,
|
||||
wrap,
|
||||
element,
|
||||
"onAfterRunAsync",
|
||||
);
|
||||
},
|
||||
},
|
||||
worker: {
|
||||
...codeFor(worker),
|
||||
// these are lazy getters that returns a composition
|
||||
// of the current hooks or undefined, if no hook is present
|
||||
get onReady() {
|
||||
return createFunction(this, "onReady", true);
|
||||
},
|
||||
get onBeforeRun() {
|
||||
return createFunction(this, "onBeforeRun", false);
|
||||
},
|
||||
get onBeforeRunAsync() {
|
||||
return createFunction(this, "onBeforeRunAsync", true);
|
||||
},
|
||||
get onAfterRun() {
|
||||
return createFunction(this, "onAfterRun", false);
|
||||
},
|
||||
get onAfterRunAsync() {
|
||||
return createFunction(this, "onAfterRunAsync", true);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
hooked.set(TYPE, hooks);
|
||||
|
||||
define(TYPE, {
|
||||
config,
|
||||
interpreter,
|
||||
hooks,
|
||||
env: `${TYPE}-script`,
|
||||
version: config?.interpreter,
|
||||
onerror(error, element) {
|
||||
errors.set(element, error);
|
||||
},
|
||||
});
|
||||
|
||||
customElements.define(
|
||||
`${TYPE}-script`,
|
||||
class extends HTMLElement {
|
||||
class PyScriptElement extends HTMLElement {
|
||||
constructor() {
|
||||
assign(super(), {
|
||||
_wrap: Promise.withResolvers(),
|
||||
srcCode: "",
|
||||
executed: false,
|
||||
});
|
||||
}
|
||||
get id() {
|
||||
return super.id || (super.id = getID());
|
||||
}
|
||||
set id(value) {
|
||||
super.id = value;
|
||||
if (!super().id) this.id = getID();
|
||||
this._pyodide = Promise.withResolvers();
|
||||
this.srcCode = "";
|
||||
this.executed = false;
|
||||
}
|
||||
async connectedCallback() {
|
||||
if (!this.executed) {
|
||||
this.executed = true;
|
||||
const isAsync = this.hasAttribute("async");
|
||||
const { io, run, runAsync } = await this._wrap
|
||||
.promise;
|
||||
this.srcCode = await fetchSource(
|
||||
this,
|
||||
io,
|
||||
!this.childElementCount,
|
||||
);
|
||||
this.replaceChildren();
|
||||
const { io, run } = await this._pyodide.promise;
|
||||
this.srcCode = await fetchSource(this, io);
|
||||
this.textContent = "";
|
||||
run(this.srcCode);
|
||||
this.style.display = "block";
|
||||
dispatch(this, TYPE, "ready");
|
||||
dispatchDone(
|
||||
this,
|
||||
isAsync,
|
||||
(isAsync ? runAsync : run)(this.srcCode),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// export the used config without allowing leaks through it
|
||||
exportedConfig[TYPE] = structuredClone(config);
|
||||
}
|
||||
customElements.define("py-script", PyScriptElement);
|
||||
|
||||
/**
|
||||
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
||||
@@ -317,17 +260,14 @@ for (const [TYPE, interpreter] of TYPES) {
|
||||
* @param {{config?: string | object, async?: boolean}} [options] optional configuration for the worker.
|
||||
* @returns {Worker & {sync: ProxyHandler<object>}}
|
||||
*/
|
||||
function PyWorker(file, options) {
|
||||
const hooks = hooked.get("py");
|
||||
export function PyWorker(file, options) {
|
||||
// this propagates pyscript worker hooks without needing a pyscript
|
||||
// bootstrap + it passes arguments and enforces `pyodide`
|
||||
// as the interpreter to use in the worker, as all hooks assume that
|
||||
// and as `pyodide` is the only default interpreter that can deal with
|
||||
// all the features we need to deliver pyscript out there.
|
||||
const xworker = XWorker.call(new Hook(null, hooks), file, {
|
||||
type: "pyodide",
|
||||
return XWorker.call(new Hook(null, workerHooks), file, {
|
||||
...options,
|
||||
type: "pyodide",
|
||||
});
|
||||
assign(xworker.sync, sync);
|
||||
return xworker;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { assign } from "polyscript/exports";
|
||||
|
||||
const CLOSEBUTTON =
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='currentColor' width='12px'><path d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/></svg>";
|
||||
|
||||
@@ -9,33 +7,22 @@ const CLOSEBUTTON =
|
||||
*/
|
||||
export const ErrorCode = {
|
||||
GENERIC: "PY0000", // Use this only for development then change to a more specific error code
|
||||
CONFLICTING_CODE: "PY0409",
|
||||
BAD_CONFIG: "PY1000",
|
||||
MICROPIP_INSTALL_ERROR: "PY1001",
|
||||
BAD_PLUGIN_FILE_EXTENSION: "PY2000",
|
||||
NO_DEFAULT_EXPORT: "PY2001",
|
||||
TOP_LEVEL_AWAIT: "PY9000",
|
||||
// Currently these are created depending on error code received from fetching
|
||||
FETCH_ERROR: "PY0001",
|
||||
FETCH_NAME_ERROR: "PY0002",
|
||||
// Currently these are created depending on error code received from fetching
|
||||
FETCH_UNAUTHORIZED_ERROR: "PY0401",
|
||||
FETCH_FORBIDDEN_ERROR: "PY0403",
|
||||
FETCH_NOT_FOUND_ERROR: "PY0404",
|
||||
FETCH_SERVER_ERROR: "PY0500",
|
||||
FETCH_UNAVAILABLE_ERROR: "PY0503",
|
||||
BAD_CONFIG: "PY1000",
|
||||
MICROPIP_INSTALL_ERROR: "PY1001",
|
||||
BAD_PLUGIN_FILE_EXTENSION: "PY2000",
|
||||
NO_DEFAULT_EXPORT: "PY2001",
|
||||
TOP_LEVEL_AWAIT: "PY9000",
|
||||
};
|
||||
|
||||
/**
|
||||
* Keys of the ErrorCode object
|
||||
* @typedef {keyof ErrorCode} ErrorCodes
|
||||
* */
|
||||
|
||||
export class UserError extends Error {
|
||||
/**
|
||||
* @param {ErrorCodes} errorCode
|
||||
* @param {string} message
|
||||
* @param {string} messageType
|
||||
* */
|
||||
constructor(errorCode, message = "", messageType = "text") {
|
||||
super(`(${errorCode}): ${message}`);
|
||||
this.errorCode = errorCode;
|
||||
@@ -45,10 +32,6 @@ export class UserError extends Error {
|
||||
}
|
||||
|
||||
export class FetchError extends UserError {
|
||||
/**
|
||||
* @param {ErrorCodes} errorCode
|
||||
* @param {string} message
|
||||
* */
|
||||
constructor(errorCode, message) {
|
||||
super(errorCode, message);
|
||||
this.name = "FetchError";
|
||||
@@ -56,23 +39,12 @@ export class FetchError extends UserError {
|
||||
}
|
||||
|
||||
export class InstallError extends UserError {
|
||||
/**
|
||||
* @param {ErrorCodes} errorCode
|
||||
* @param {string} message
|
||||
* */
|
||||
constructor(errorCode, message) {
|
||||
super(errorCode, message);
|
||||
this.name = "InstallError";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function for creating alert banners on the page
|
||||
* @param {string} message The message to be displayed to the user
|
||||
* @param {string} level The alert level of the message. Can be any string; 'error' or 'warning' cause matching messages to be emitted to the console
|
||||
* @param {string} [messageType="text"] If set to "html", the message content will be assigned to the banner's innerHTML directly, instead of its textContent
|
||||
* @param {any} [logMessage=true] An additional flag for whether the message should be sent to the console log.
|
||||
*/
|
||||
export function _createAlertBanner(
|
||||
message,
|
||||
level,
|
||||
@@ -89,13 +61,13 @@ export function _createAlertBanner(
|
||||
}
|
||||
|
||||
const content = messageType === "html" ? "innerHTML" : "textContent";
|
||||
const banner = assign(document.createElement("div"), {
|
||||
const banner = Object.assign(document.createElement("div"), {
|
||||
className: `alert-banner py-${level}`,
|
||||
[content]: message,
|
||||
});
|
||||
|
||||
if (level === "warning") {
|
||||
const closeButton = assign(document.createElement("button"), {
|
||||
const closeButton = Object.assign(document.createElement("button"), {
|
||||
id: "alert-close-button",
|
||||
innerHTML: CLOSEBUTTON,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { FetchError, ErrorCode } from "./exceptions.js";
|
||||
import { getText } from "polyscript/exports";
|
||||
|
||||
export { getText };
|
||||
|
||||
/**
|
||||
* This is a fetch wrapper that handles any non 200 responses and throws a
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import { typedSet } from "type-checked-collections";
|
||||
import { dedent } from "polyscript/exports";
|
||||
import toJSONCallback from "to-json-callback";
|
||||
|
||||
import stdlib from "./stdlib.js";
|
||||
|
||||
export const main = (name) => hooks.main[name];
|
||||
export const worker = (name) => hooks.worker[name];
|
||||
|
||||
const code = (hooks, branch, key, lib) => {
|
||||
hooks[key] = () => {
|
||||
const arr = lib ? [lib] : [];
|
||||
arr.push(...branch(key));
|
||||
return arr.map(dedent).join("\n");
|
||||
};
|
||||
};
|
||||
|
||||
export const codeFor = (branch) => {
|
||||
const hooks = {};
|
||||
code(hooks, branch, `codeBeforeRun`, stdlib);
|
||||
code(hooks, branch, `codeBeforeRunAsync`, stdlib);
|
||||
code(hooks, branch, `codeAfterRun`);
|
||||
code(hooks, branch, `codeAfterRunAsync`);
|
||||
return hooks;
|
||||
};
|
||||
|
||||
export const createFunction = (self, name) => {
|
||||
const cbs = [...worker(name)];
|
||||
if (cbs.length) {
|
||||
const cb = toJSONCallback(
|
||||
self[`_${name}`] ||
|
||||
(name.endsWith("Async")
|
||||
? async (wrap, xworker, ...cbs) => {
|
||||
for (const cb of cbs) await cb(wrap, xworker);
|
||||
}
|
||||
: (wrap, xworker, ...cbs) => {
|
||||
for (const cb of cbs) cb(wrap, xworker);
|
||||
}),
|
||||
);
|
||||
const a = cbs.map(toJSONCallback).join(", ");
|
||||
return Function(`return(w,x)=>(${cb})(w,x,...[${a}])`)();
|
||||
}
|
||||
};
|
||||
|
||||
const SetFunction = typedSet({ typeof: "function" });
|
||||
const SetString = typedSet({ typeof: "string" });
|
||||
|
||||
const inputFailure = `
|
||||
import builtins
|
||||
def input(prompt=""):
|
||||
raise Exception("\\n ".join([
|
||||
"input() doesn't work when PyScript runs in the main thread.",
|
||||
"Consider using the worker attribute: https://docs.pyscript.net/2023.11.1/user-guide/workers/"
|
||||
]))
|
||||
|
||||
builtins.input = input
|
||||
del builtins
|
||||
del input
|
||||
`;
|
||||
|
||||
export const hooks = {
|
||||
main: {
|
||||
/** @type {Set<function>} */
|
||||
onWorker: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onReady: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRun: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRunAsync: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRun: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRunAsync: new SetFunction(),
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRun: new SetString([inputFailure]),
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunAsync: new SetString(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRun: new SetString(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunAsync: new SetString(),
|
||||
},
|
||||
worker: {
|
||||
/** @type {Set<function>} */
|
||||
onReady: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRun: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRunAsync: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRun: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRunAsync: new SetFunction(),
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRun: new SetString(),
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunAsync: new SetString(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRun: new SetString(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunAsync: new SetString(),
|
||||
},
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import { defineProperty } from "polyscript/exports";
|
||||
|
||||
// helper for all script[type="py"] out there
|
||||
const before = (script) => {
|
||||
defineProperty(document, "currentScript", {
|
||||
configurable: true,
|
||||
get: () => script,
|
||||
});
|
||||
};
|
||||
|
||||
const after = () => {
|
||||
delete document.currentScript;
|
||||
};
|
||||
|
||||
// common life-cycle handlers for any node
|
||||
export default async (main, wrap, element, hook) => {
|
||||
const isAsync = hook.endsWith("Async");
|
||||
const isBefore = hook.startsWith("onBefore");
|
||||
// make it possible to reach the current target node via Python
|
||||
// or clean up for other scripts executing around this one
|
||||
(isBefore ? before : after)(element);
|
||||
for (const fn of main(hook)) {
|
||||
if (isAsync) await fn(wrap, element);
|
||||
else fn(wrap, element);
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user