├── .github └── workflows │ └── update_docs.yml ├── .gitignore ├── README.md ├── docs ├── api.md ├── assets │ └── images │ │ ├── micropython.png │ │ ├── platform.png │ │ ├── py-terminal.gif │ │ ├── pyeditor-stop.gif │ │ ├── pyeditor1.gif │ │ ├── pyodide.png │ │ ├── pyscript-black.svg │ │ ├── pyscript.com.png │ │ ├── pyscript.svg │ │ ├── pyterm1.png │ │ ├── pyterm2.gif │ │ └── pyterm3.gif ├── beginning-pyscript.md ├── conduct.md ├── contributing.md ├── developers.md ├── examples.md ├── faq.md ├── index.md ├── license.md ├── mini-coi.js └── user-guide │ ├── architecture.md │ ├── configuration.md │ ├── dom.md │ ├── editor.md │ ├── features.md │ ├── ffi.md │ ├── filesystem.md │ ├── first-steps.md │ ├── index.md │ ├── media.md │ ├── offline.md │ ├── plugins.md │ ├── pygame-ce.md │ ├── running-offline.md │ ├── terminal.md │ ├── what.md │ └── workers.md ├── environment.yml ├── mkdocs.yml ├── requirements.txt ├── robots.txt ├── version-update.js └── version.json /.github/workflows/update_docs.yml: -------------------------------------------------------------------------------- 1 | name: "Update Docs" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '.github/**' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | update-docs: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup 19 | uses: conda-incubator/setup-miniconda@v2 20 | with: 21 | auto-update-conda: true 22 | activate-environment: docs 23 | environment-file: environment.yml 24 | 25 | - uses: oleksiyrudenko/gha-git-credentials@v2-latest 26 | with: 27 | token: '${{ secrets.DOCS_GITHUB_TOKEN }}' 28 | 29 | - name: Get Version 30 | id: version 31 | if: success() 32 | shell: bash 33 | run: echo "version=$(jq -r '.version' version.json)" >> $GITHUB_OUTPUT 34 | 35 | - name: Update "gh-pages" branch 36 | shell: bash -l {0} 37 | run: | 38 | git config user.name ${{ secrets.DOCS_GITHUB_USER }} 39 | git config user.email ${{ secrets.DOCS_GITHUB_EMAIL }} 40 | git fetch origin gh-pages --depth=1 41 | mike deploy --push --update-aliases ${{ steps.version.outputs.version }} latest 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _env 2 | site 3 | .DS_Store 4 | bin 5 | lib 6 | lib64 7 | pyvenv.cfg 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyScript documentation 2 | 3 | Welcome to the PyScript documentation repository. 4 | 5 | This source code becomes the official PyScript documentation hosted here: 6 | 7 | [https://docs.pyscript.net](https://docs.pyscript.net/) 8 | 9 | Contribute prose and participate in discussions about the written support of 10 | PyScript and related topics. 11 | 12 | ## Getting started 13 | 14 | Before you start contributing to the documentation, it's worthwhile to 15 | take a look at the general contributing guidelines for the PyScript project. 16 | You can find these guidelines here 17 | [Contributing Guidelines](https://github.com/pyscript/pyscript/blob/main/CONTRIBUTING.md) 18 | 19 | ## Setup 20 | 21 | The `docs` directory in the pyscript repository contains a 22 | [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) 23 | documentation project. Material is a system that takes plaintext files 24 | containing documentation written in Markdown, along with static files like 25 | templates and themes, to build the static end result. 26 | 27 | To setup the documentation development environment simply create a new 28 | virtual environment, then `pip install -r requirements.txt` (from in the root 29 | of this repository). 30 | 31 | ```sh 32 | # example of a simple virtual environment 33 | # creation from the root of this project 34 | python -m venv . 35 | ./bin/pip install --upgrade setuptools 36 | ./bin/pip install -r requirements.txt 37 | ``` 38 | 39 | ## Build 40 | 41 | Simply run `mkdocs serve` or `./bin/mkdocs serve`. 42 | 43 | ## Cross-referencing 44 | 45 | Link to other pages in the documentation by using the `{doc}` role. For 46 | example, to link to the `docs/README.md` file, you would use: 47 | 48 | ```markdown 49 | {doc}`docs/README.md` 50 | ``` 51 | 52 | Cross-reference the Python glossary by using the `{term}` role. For example, to 53 | link to the `iterable` term, you would use: 54 | 55 | ```markdown 56 | {term}`iterable` 57 | ``` 58 | 59 | Cross-reference functions, methods or data attributes by using the `{attr}` for 60 | example: 61 | 62 | ```markdown 63 | {py:func}`repr` 64 | ``` 65 | 66 | This would link to the `repr` function in the python builtins. 67 | -------------------------------------------------------------------------------- /docs/assets/images/micropython.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscript/docs/e4f22877fd948eba8ba776b6f95274a48d1858da/docs/assets/images/micropython.png -------------------------------------------------------------------------------- /docs/assets/images/platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscript/docs/e4f22877fd948eba8ba776b6f95274a48d1858da/docs/assets/images/platform.png -------------------------------------------------------------------------------- /docs/assets/images/py-terminal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscript/docs/e4f22877fd948eba8ba776b6f95274a48d1858da/docs/assets/images/py-terminal.gif -------------------------------------------------------------------------------- /docs/assets/images/pyeditor-stop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscript/docs/e4f22877fd948eba8ba776b6f95274a48d1858da/docs/assets/images/pyeditor-stop.gif -------------------------------------------------------------------------------- /docs/assets/images/pyeditor1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscript/docs/e4f22877fd948eba8ba776b6f95274a48d1858da/docs/assets/images/pyeditor1.gif -------------------------------------------------------------------------------- /docs/assets/images/pyodide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscript/docs/e4f22877fd948eba8ba776b6f95274a48d1858da/docs/assets/images/pyodide.png -------------------------------------------------------------------------------- /docs/assets/images/pyscript-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 11 | 12 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/assets/images/pyscript.com.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscript/docs/e4f22877fd948eba8ba776b6f95274a48d1858da/docs/assets/images/pyscript.com.png -------------------------------------------------------------------------------- /docs/assets/images/pyscript.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 11 | 12 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/assets/images/pyterm1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscript/docs/e4f22877fd948eba8ba776b6f95274a48d1858da/docs/assets/images/pyterm1.png -------------------------------------------------------------------------------- /docs/assets/images/pyterm2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscript/docs/e4f22877fd948eba8ba776b6f95274a48d1858da/docs/assets/images/pyterm2.gif -------------------------------------------------------------------------------- /docs/assets/images/pyterm3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscript/docs/e4f22877fd948eba8ba776b6f95274a48d1858da/docs/assets/images/pyterm3.gif -------------------------------------------------------------------------------- /docs/beginning-pyscript.md: -------------------------------------------------------------------------------- 1 | # Beginning PyScript 2 | 3 | PyScript is a platform for running 4 | Python 5 | in modern web browsers. 6 | 7 | Create apps with a 8 | PyScript development environment: 9 | write code, curate the project's assets, and test your application. 10 | 11 | To distribute a PyScript application, host it on the web, then click 12 | on the link to your application. PyScript and the browser do the rest. 13 | 14 | This page covers these core aspects of PyScript in a beginner friendly manner. 15 | We only assume you know how to use a browser and edit text. 16 | 17 | !!! note 18 | 19 | The easiest way to get a PyScript development environment and hosting, is 20 | to use [pyscript.com](https://pyscript.com) in your browser. 21 | 22 | It is a free service that helps you create new projects from templates, and 23 | then edit, preview and deploy your apps with a unique link. 24 | 25 | While the core features of [pyscript.com](https://pyscript.com) will always be 26 | free, additional paid-for capabilities directly support and sustain the 27 | PyScript open source project. Commercial and educational support is also 28 | available. 29 | 30 | ## An application 31 | 32 | All PyScript applications need three things: 33 | 34 | 1. An `index.html` file that is served to your browser. 35 | 2. A description of the Python environment in which your application will run. 36 | This is usually specified by a `pyscript.json` or `pyscript.toml` file. 37 | 3. Python code (usually in a file called something like `main.py`) that defines 38 | how your application works. 39 | 40 | Create these files with your favourite code editor on your local file system. 41 | Alternatively, [pyscript.com](https://pyscript.com) will take away all the pain 42 | of organising, previewing and deploying your application. 43 | 44 | If you're using your local file system, you'll need a way to view your 45 | application in your browser. If you already have Python installed on 46 | your local machine, serve your files with the following command run from your 47 | terminal and in the same directory as your files: 48 | 49 | ```sh 50 | python3 -m http.server 51 | ``` 52 | 53 | Point your browser at [http://localhost:8000](localhost:8000). Remember to 54 | refresh the page (`CTRL-R`) to see any updates you may have made. 55 | 56 | !!! note 57 | If you're using [VSCode](https://code.visualstudio.com/) as your editor, 58 | the 59 | [Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) 60 | can be used to reload the page as you edit your files. 61 | 62 | Alternatively, if you have an account on [GitHub](https://github.com) you 63 | could use VSCode in your browser as a 64 | [PyScript aware "CodeSpace"](https://github.com/ntoll/codespaces-project-template-pyscript/) 65 | (just follow the instructions in the README file). 66 | 67 | If you decide to use [pyscript.com](https://pyscript.com) (recommended for 68 | first steps), once signed in, create a new project by pressing the "+" button 69 | on the left hand side below the site's logo. You'll be presented with a page 70 | containing three columns (listing your files, showing your code and previewing 71 | the app). The "save" and "run" buttons do exactly what you'd expect. 72 | 73 | ![PyScript.com](assets/images/pyscript.com.png) 74 | 75 | Let's build a simple PyScript application that translates English 🇬🇧 into 76 | Pirate 🏴‍☠️ speak. In order to do this we'll make use of the 77 | [arrr](https://arrr.readthedocs.io/en/latest/) library. By building this app 78 | you'll be introduced to all the core concepts of PyScript at an introductory 79 | level. 80 | 81 | You can see this application embedded into the page below (try it out!): 82 | 83 | 84 | 85 | Let's explore each of the three files that make this app work. 86 | 87 | ### pyscript.json 88 | 89 | This file tells PyScript and your browser about various 90 | [configurable aspects](../user-guide/configuration) 91 | of your application. Put simply, it tells PyScript what it needs in order to run 92 | your application. The only thing we need to show is that we require the third 93 | party `arrr` module to do the 94 | [actual translation](https://arrr.readthedocs.io/en/latest/). 95 | 96 | We do this by putting `arrr` as the single entry in a list of required 97 | `packages`, so the content of `pyscript.json` looks like this: 98 | 99 | ``` json title="pyscript.json" 100 | { 101 | "packages": ["arrr"] 102 | } 103 | ``` 104 | 105 | ### index.html 106 | 107 | Next we come to the `index.html` file that is first served to your browser. 108 | 109 | To start out, we need to tell the browser that this HTML document uses 110 | PyScript, and so we create a ` 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | ``` 130 | 131 | Notice that the `` of the document is empty except for the TODO comment. 132 | It's in here that we put standard HTML content to define our user interface, so 133 | the `` now looks like: 134 | 135 | ``` html 136 | 137 |

Polyglot 🦜 💬 🇬🇧 ➡️ 🏴‍☠️

138 |

Translate English into Pirate speak...

139 | 140 | 141 |
142 | 143 | 144 | ``` 145 | 146 | This fragment of HTML contains the application's header (`

`), some 147 | instructions between the `

` tags, an `` box for the English text, and 148 | a ` 179 |

180 | 181 | 182 | 183 | ``` 184 | 185 | But this only defines _how_ the user interface should look. To define its 186 | behaviour we need to write some Python. Specifically, we need to define the 187 | `translate_english` function, used when the button is clicked. 188 | 189 | ### main.py 190 | 191 | The behaviour of the application is defined in `main.py`. It looks like this: 192 | 193 | ``` python linenums="1" title="main.py" 194 | import arrr 195 | from pyscript import document 196 | 197 | 198 | def translate_english(event): 199 | input_text = document.querySelector("#english") 200 | english = input_text.value 201 | output_div = document.querySelector("#output") 202 | output_div.innerText = arrr.translate(english) 203 | ``` 204 | 205 | It's not very complicated Python code. 206 | 207 | On line 1 the `arrr` module is imported so we can do the actual English to 208 | Pirate translation. If we hadn't told PyScript to download the `arrr` module 209 | in our `pyscript.json` configuration file, this line would cause an error. 210 | PyScript has ensured our environment is set up with the expected `arrr` module. 211 | 212 | Line 2 imports the `document` object. The `document` allows us to reach into 213 | the things on the web page defined in `index.html`. 214 | 215 | Finally, on line 5 the `translate_english` function is defined. 216 | 217 | The `translate_english` function takes a single parameter called 218 | `event`. This represents the user's click of the button (but which we don't 219 | actually use). 220 | 221 | Inside the body of the function we first get a reference to the `input` 222 | element with the [`document.querySelector` function](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) 223 | that takes `#english` as its 224 | parameter (indicating we want the element with the id "english"). We assign the 225 | result to `input_text`, then extract the user's `english` from the 226 | `input_text`'s `value`. Next, we get a reference called `output_div` that 227 | points to the `div` element with the id "output". Finally, we assign the 228 | `innerText` of the `output_div` to the result of calling 229 | [`arrr.translate`](https://arrr.readthedocs.io/en/latest/#arrr.translate) 230 | (to actually translate the `english` to something piratical). 231 | 232 | That's it! 233 | 234 | ## Sharing your app 235 | 236 | ### PyScript.com 237 | 238 | If you're using [pyscript.com](https://pyscript.com), you should save all your files 239 | and click the "run" button. Assuming you've copied the code properly, you 240 | should have a fine old time using "Polyglot 🦜" to translate English to 241 | Pirate-ish. 242 | 243 | Alternatively, [click here to see a working example of this app](https://ntoll.pyscriptapps.com/piratical/v5/). 244 | Notice that the bottom right hand corner contains a link to view the code on 245 | [pyscript.com](https://pyscript.com). Why not explore the code, copy it to your own 246 | account and change it to your satisfaction? 247 | 248 | ### From a web server 249 | 250 | Just host the three files (`pyscript.json`, `index.html` 251 | and `main.py`) in the same directory on a static web server somewhere. 252 | 253 | Clearly, we recommend you use [pyscript.com](https://pyscript.com) for this, but any 254 | static web host will do (for example, 255 | [GitHub Pages](https://pages.github.com/), 256 | [Amazon's S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html), 257 | [Google Cloud](https://cloud.google.com/storage/docs/hosting-static-website) or 258 | [Microsoft's Azure](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website)). 259 | 260 | ## Run PyScript Offline 261 | 262 | To run PyScript offline, without the need of a CDN or internet connection, read 263 | the [Run PyScript Offline](user-guide/offline.md) section of the user 264 | guide. 265 | 266 | ## Conclusion 267 | 268 | Congratulations! 269 | 270 | You have just created your first PyScript app. We've explored the core concepts 271 | needed to build yet more interesting things of your own. 272 | 273 | PyScript is extremely powerful, and these beginner steps only just scratch the 274 | surface. To learn about PyScript in more depth, check out 275 | [our user guide](user-guide/index.md) or 276 | [explore our example applications](../examples). 277 | -------------------------------------------------------------------------------- /docs/conduct.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Our code of conduct is based on the 4 | [Contributor Covenant](https://www.contributor-covenant.org/) code of conduct. 5 | 6 | ## Our Pledge 7 | 8 | We as members, contributors, and leaders pledge to make participation in our 9 | community a harassment-free experience for everyone, regardless of age, body 10 | size, visible or invisible disability, ethnicity, sex characteristics, gender 11 | identity and expression, level of experience, education, socio-economic status, 12 | nationality, personal appearance, race, religion, or sexual identity and 13 | orientation. 14 | 15 | We pledge to act and interact in ways that contribute to an open, welcoming, 16 | diverse, inclusive, and healthy community. 17 | 18 | ## Our Standards 19 | 20 | Examples of behavior that contributes to a positive environment for our 21 | community include: 22 | 23 | * Demonstrating empathy and kindness toward other people 24 | * Being respectful of differing opinions, viewpoints, and experiences 25 | * Giving and gracefully accepting constructive feedback 26 | * Accepting responsibility and apologizing to those affected by our mistakes, 27 | and learning from the experience 28 | * Focusing on what is best not just for us as individuals, but for the overall 29 | community 30 | 31 | Examples of unacceptable behavior include: 32 | 33 | * The use of sexualized language or imagery, and sexual attention or 34 | advances of any kind 35 | * Trolling, insulting or derogatory comments, and personal or political attacks 36 | * Public or private harassment 37 | * Publishing others' private information, such as a physical or email 38 | address, without their explicit permission 39 | * Other conduct which could reasonably be considered inappropriate in a 40 | professional setting 41 | 42 | ## Enforcement Responsibilities 43 | 44 | Community leaders are responsible for clarifying and enforcing our standards of 45 | acceptable behavior and will take appropriate and fair corrective action in 46 | response to any behavior that they deem inappropriate, threatening, offensive, 47 | or harmful. 48 | 49 | Community leaders have the right and responsibility to remove, edit, or reject 50 | comments, commits, code, wiki edits, issues, and other contributions that are 51 | not aligned to this Code of Conduct, and will communicate reasons for 52 | moderation decisions when appropriate. 53 | 54 | ## Scope 55 | 56 | This Code of Conduct applies within all community spaces, and also applies when 57 | an individual is officially representing the community in public spaces. 58 | Examples of representing our community include using an official e-mail 59 | address, posting via an official social media account, or acting as an 60 | appointed representative at an online or offline event. 61 | 62 | ## Enforcement 63 | 64 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 65 | reported to the community leaders responsible for enforcement as set forth in 66 | the repository's Notice.md file. All complaints will be reviewed and 67 | investigated promptly and fairly. 68 | 69 | All community leaders are obligated to respect the privacy and security of the 70 | reporter of any incident. 71 | 72 | ## Enforcement Guidelines 73 | 74 | Community leaders will follow these Community Impact Guidelines in determining 75 | the consequences for any action they deem in violation of this Code of Conduct: 76 | 77 | ### 1. Correction 78 | 79 | **Community Impact**: Use of inappropriate language or other behavior deemed 80 | unprofessional or unwelcome in the community. 81 | 82 | **Consequence**: A private, written warning from community leaders, providing 83 | clarity around the nature of the violation and an explanation of why the 84 | behavior was inappropriate. A public apology may be requested. 85 | 86 | ### 2. Warning 87 | 88 | **Community Impact**: A violation through a single incident or series of 89 | actions. 90 | 91 | **Consequence**: A warning with consequences for continued behavior. No 92 | interaction with the people involved, including unsolicited interaction with 93 | those enforcing the Code of Conduct, for a specified period of time. This 94 | includes avoiding interactions in community spaces as well as external channels 95 | like social media. Violating these terms may lead to a temporary or permanent 96 | ban. 97 | 98 | ### 3. Temporary Ban 99 | 100 | **Community Impact**: A serious violation of community standards, including 101 | sustained inappropriate behavior. 102 | 103 | **Consequence**: A temporary ban from any sort of interaction or public 104 | communication with the community for a specified period of time. No public or 105 | private interaction with the people involved, including unsolicited interaction 106 | with those enforcing the Code of Conduct, is allowed during this period. 107 | Violating these terms may lead to a permanent ban. 108 | 109 | ### 4. Permanent Ban 110 | 111 | **Community Impact**: Demonstrating a pattern of violation of community 112 | standards, including sustained inappropriate behavior, harassment of an 113 | individual, or aggression toward or disparagement of classes of individuals. 114 | 115 | **Consequence**: A permanent ban from any sort of public interaction within the 116 | project community. 117 | 118 | ## Reporting a violation 119 | 120 | To report a violation of the Code of Conduct, e-mail conduct@pyscript.net 121 | 122 | ## Attribution 123 | 124 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 125 | version 2.0, 126 | available at 127 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 128 | 129 | Community Impact Guidelines were inspired by 130 | [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 131 | 132 | [homepage]: https://www.contributor-covenant.org 133 | 134 | For answers to common questions about this code of conduct, see the FAQ at 135 | https://www.contributor-covenant.org/faq. Translations are available at 136 | https://www.contributor-covenant.org/translations. 137 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for wanting to contribute to the PyScript project! 4 | 5 | ## Code of conduct 6 | 7 | The [PyScript Code of Conduct](conduct.md) governs the project and everyone 8 | participating in it. By participating, you are expected to uphold this code. 9 | Please report unacceptable behavior to the maintainers or administrators as 10 | described in that document. 11 | 12 | ## Ways to contribute 13 | 14 | ### Report bugs 15 | 16 | Bugs are tracked on the 17 | [project issues page](https://github.com/pyscript/pyscript/issues). 18 | 19 | !!! warning "Check first" 20 | 21 | Please check your bug has not already been reported by someone else by 22 | searching the existing issues before filing a new one. Once your issue is 23 | filed, it will be triaged by another contributor or maintainer. If there 24 | are questions raised about your issue, please respond promptly. 25 | 26 | - Use a clear and descriptive title. 27 | - Describe the starting context and specific steps needed to reproduce the 28 | problem you have observed. Some of these steps may not be obvious, so please 29 | as much detail as possible. 30 | - Explain the behaviour you observed, the behaviour you expected, and why this 31 | difference is problematic. 32 | - Include screenshots if they help make the bug clearer. 33 | - Include commented example code if that will help recreate the bug. 34 | 35 | ### Report security issues 36 | 37 | If it is not appropriate to submit a security issue using the above process, 38 | please e-mail us at security@pyscript.net. 39 | 40 | ### Ask questions 41 | 42 | If you have questions about the project, using PyScript, or anything else, 43 | please ask in the [project's discord server](https://discord.gg/HxvBtukrg2). 44 | 45 | ### Create resources 46 | 47 | Folks make all sorts of wonderful things with PyScript. 48 | 49 | If you have an interesting project, a cool hack, or an innovative way to 50 | implement a new capability or feature, please share your work and tell us about 51 | it so we can celebrate and amplify your contribution. 52 | 53 | - Show off your creation at our fortnightly 54 | [PyScript FUN](https://discord.com/events/972017612454232116/1227275336115556402) 55 | community call on discord. 56 | - Write up your creation in a blog post. 57 | - Share it on social media. 58 | - Create a demo video. 59 | - Propose a talk about it at a community event. 60 | - Demonstrate it as a lightning talk at a conference. 61 | 62 | Please reach out to us if you'd like advice and feedback. 63 | 64 | ### Participate 65 | 66 | As an open source project, PyScript has community at its core. In addition to 67 | the ways to engage already outlined, you could join our live community calls. 68 | 69 | - [PyScript Community Call](https://discord.com/events/972017612454232116/1227274094366556279) 70 | - weekly on Tuesdays. A coordination call for anyone participating in the 71 | PyScript project. 72 | - [Community Engagement Call](https://discord.com/events/972017612454232116/1227275045965922376) 73 | - weekly on Wednesdays. A non-technical meeting where we coordinate how the 74 | PyScript project engages with, nourishes and grows our wonderful community. 75 | - [PyScript FUN](https://discord.com/events/972017612454232116/1227275336115556402) 76 | - held every two weeks on a Thursday, this call is an informal, 77 | supportive and often humorous "show and tell" of community created projects, 78 | hacks, feedback and plugins. 79 | 80 | Announcement of connection details is made via the PyScript 81 | [discord server](https://discord.gg/HxvBtukrg2). 82 | 83 | ## Technical contributions 84 | 85 | In addition to working with PyScript, if you would like to contribute to 86 | PyScript itself, the following advice should be followed. 87 | 88 | ### Places to start 89 | 90 | If you're not sure where to begin, we have some suggestions: 91 | 92 | - **Read over our documentation.** Are there things missing, or could they be 93 | clearer? Make edits/changes/additions to those documents. 94 | - **Review the open issues.** Are they clear? Can you reproduce them? You can 95 | add comments, clarifications, or additions to those issues. If you think you 96 | have an idea of how to address the issue, submit a fix! 97 | - **Look over the open pull requests.** Do you have comments or suggestions for 98 | the proposed changes? Add them. 99 | - **Check out the examples.** Is there a use case that would be good to have 100 | sample code for? Create an example for it. 101 | 102 | ### Set up your development environment 103 | 104 | We assume you are familiar with core development practices such as using a code 105 | editor, writing Python and/or JavaScript and working with tools and services 106 | such as GIT and GitHub. 107 | 108 | * Create your own fork of the main 109 | [PyScript github repository](https://github.com/pyscript/pyscript/fork). 110 | * Clone _your fork_ of PyScript onto your local machine. 111 | 112 | ``` 113 | git clone https://github.com//pyscript 114 | ``` 115 | 116 | * Add our original PyScript repository as your `upstream`. 117 | 118 | ``` 119 | git remote add upstream https://github.com/pyscript/pyscript.git 120 | ``` 121 | 122 | * This allows you to pull the latest changes from the main project. 123 | 124 | ``` 125 | git pull upstream main 126 | ``` 127 | 128 | * Contribute changes using the 129 | [GitHub flow](https://docs.github.com/en/get-started/using-github/github-flow) 130 | model of coding collaboration. 131 | 132 | ### License terms for contributions 133 | 134 | PyScrcipt welcomes contributions, suggestions, and feedback. All contributions, 135 | suggestions, and feedback you submitted are accepted under the 136 | [Apache 2.0](license.md) license. You represent that if you do not own 137 | copyright in the code that you have the authority to submit it under the 138 | [Apache 2.0](license.md) license. All feedback, suggestions, or contributions 139 | are not confidential. 140 | 141 | ### Becoming a maintainer 142 | 143 | Contributors are invited to be maintainers of the project by demonstrating good 144 | decision making in their contributions, a commitment to the goals of the 145 | project, and consistent adherence to the 146 | [code of conduct](conduct.md). New maintainers are invited by a 3/4 vote of 147 | the existing maintainers. 148 | 149 | ## Trademarks 150 | 151 | The Project abides by the Organization's 152 | [trademark policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md). 153 | -------------------------------------------------------------------------------- /docs/developers.md: -------------------------------------------------------------------------------- 1 | # Developer Guide 2 | 3 | This page explains the technical and practical requirements and processes 4 | needed to contribute to PyScript. 5 | 6 | !!! info 7 | 8 | In the following instructions, we assume familiarity with `git`, 9 | [GitHub](https://github.com/pyscript/pyscript), the command line and other 10 | common development concepts, tools and practices. 11 | 12 | For those who come from a non-Pythonic technical background (for example, 13 | you're a JavaScript developer), we will explain Python-isms as we go along 14 | so you're contributing with confidence. 15 | 16 | If you're unsure, or encounter problems, please ask for help on our 17 | [discord server](https://discord.gg/HxvBtukrg2). 18 | 19 | ## Welcome 20 | 21 | We are a diverse, inclusive coding community and welcome contributions from 22 | _anyone_ irrespective of their background. If you're thinking, "but they don't 23 | mean me", _then we especially mean YOU_. Our diversity means _you will meet 24 | folks in our community who are different to yourself_. Therefore, thoughtful 25 | contributions made in good faith, and engagement with respect, care and 26 | compassion wins every time. 27 | 28 | * If you're from a background which isn't well-represented in most geeky 29 | groups, get involved - _we want to help you make a difference_. 30 | * If you're from a background which **is** well-represented in most geeky 31 | groups, get involved - _we want your help making a difference_. 32 | * If you're worried about not being technical enough, get involved - _your 33 | fresh perspective will be invaluable_. 34 | * If you need help with anything, get involved - _we welcome questions asked 35 | in good faith, and will move mountains to help_. 36 | * If you're unsure where to start, get involved - _we have [many ways to 37 | contribute](contributing.md)_. 38 | 39 | All contributors are expected to follow our [code of conduct](conduct.md). 40 | 41 | ## Setup 42 | The following steps create a working development environment for PyScript. It 43 | is through this environment that you contribute to PyScript. You can choose 44 | between two options for setting up your environment. 45 | 46 | !!! danger 47 | 48 | The following commands work on Unix like operating systems (like MacOS or 49 | Linux). **If you are a Microsoft Windows user please use the 50 | [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/about) 51 | with the following instructions.** 52 | 53 | ### Create a virtual environment 54 | 55 | * A Python [virtual environment](https://docs.python.org/3/library/venv.html) 56 | is a computing "sandbox" that safely isolates your work. PyScript's 57 | development makes use of various Python based tools, so both 58 | [Python](https://python.org) and a virtual environment is needed. There are 59 | many tools to help manage these environments, but the standard way to create 60 | a virtual environment is to use this command in your terminal: 61 | 62 | ```sh 63 | python3 -m venv my_pyscript_dev_venv 64 | ``` 65 | 66 | !!! warning 67 | 68 | Replace `my_pyscript_dev_venv` with a meaningful name for the 69 | virtual environment, that works for you. 70 | 71 | * A `my_pyscript_dev_venv` directory containing the virtual environment's 72 | "stuff" is created as a subdirectory of your current directory. Next, 73 | activate the virtual environment to ensure your development activities happen 74 | within the context of the sandbox: 75 | 76 | ```sh 77 | source my_pyscript_dev_venv/bin/activate 78 | ``` 79 | 80 | * The prompt in your terminal will change to include the name of your virtual 81 | environment indicating the sandbox is active. To deactivate the virtual 82 | environment just type the following into your terminal: 83 | 84 | ```sh 85 | deactivate 86 | ``` 87 | 88 | ### Option 2: Create a conda environment 89 | **This option will install Python and NodeJS for you, so you don't need to have them 90 | pre-installed on your system.** 91 | 92 | * If you prefer using [conda](https://docs.conda.io/en/latest/) for environment management, 93 | you can create a conda environment that includes both Python and NodeJS: 94 | * 95 | ```sh 96 | conda create --name pyscript python nodejs 97 | conda activate pyscript 98 | ``` 99 | 100 | !!! warning 101 | 102 | Replace `pyscript` with a meaningful name for the conda environment, that works for you. 103 | 104 | * This creates a new environment with both Python and NodeJS installed. The prompt in your 105 | terminal will change to include the name of your conda environment indicating the sandbox is active. 106 | * To deactivate the conda environment just type the following into your terminal: 107 | 108 | ```sh 109 | conda deactivate 110 | ``` 111 | 112 | * If you don't have conda installed, you can download and install 113 | [Miniconda, Miniforge](https://docs.conda.io/projects/conda), or 114 | [Anaconda](https://www.anaconda.com/download). 115 | 116 | !!! info 117 | 118 | The rest of the instructions on this page assume you are working in **an 119 | activated virtual environment** for developing PyScript. 120 | 121 | ### Prepare your repository 122 | 123 | * Create a [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) 124 | of the 125 | [PyScript github repository](https://github.com/pyscript/pyscript/fork) to 126 | your own GitHub account. 127 | * [Clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) 128 | your newly forked version of the PyScript repository onto your 129 | local development machine. For example, use this command in your terminal: 130 | 131 | ```sh 132 | git clone https://github.com//pyscript 133 | ``` 134 | 135 | !!! warning 136 | 137 | In the URL for the forked PyScript repository, remember to replace 138 | `` with your actual GitHub username. 139 | 140 | !!! tip 141 | 142 | To help explain steps, we will use `git` commands to be typed into your 143 | terminal / command line. 144 | 145 | The equivalent of these commands could be achieved through other means 146 | (such as [GitHub's desktop client](https://desktop.github.com/)). How 147 | these alternatives work is beyond the scope of this document. 148 | 149 | * Change into the root directory of your newly cloned `pyscript` repository: 150 | 151 | ```sh 152 | cd pyscript 153 | ``` 154 | 155 | * Add the original PyScript repository as your `upstream` to allow you to keep 156 | your own fork up-to-date with the latest changes: 157 | 158 | ```sh 159 | git remote add upstream https://github.com/pyscript/pyscript.git 160 | ``` 161 | 162 | * If the above fails, try this alternative: 163 | 164 | ```sh 165 | git remote remove upstream 166 | git remote add upstream git@github.com:pyscript/pyscript.git 167 | ``` 168 | 169 | * Pull in the latest changes from the main `upstream` PyScript repository: 170 | 171 | ```sh 172 | git pull upstream main 173 | ``` 174 | 175 | * Pyscript uses a `Makefile` to automate the most common development tasks. In 176 | your terminal, type `make` to see what it can do. You should see something 177 | like this: 178 | 179 | ```sh 180 | There is no default Makefile target right now. Try: 181 | 182 | make setup - check your environment and install the dependencies. 183 | make clean - clean up auto-generated assets. 184 | make build - build PyScript. 185 | make precommit-check - run the precommit checks (run eslint). 186 | make test-integration - run all integration tests sequentially. 187 | make fmt - format the code. 188 | make fmt-check - check the code formatting. 189 | ``` 190 | 191 | ### Install dependencies 192 | 193 | * To install the required software dependencies for working on PyScript, in 194 | your terminal type: 195 | 196 | ```sh 197 | make setup 198 | ``` 199 | 200 | * Updates from `npm` and then `pip` will scroll past telling you about 201 | their progress installing the required packages. 202 | 203 | !!! warning 204 | 205 | The `setup` process checks the versions of Python, node 206 | and npm. If you encounter a failure at this point, it's probably 207 | because one of these pre-requisits is out of date on your system. 208 | Please update! 209 | 210 | ## Check code 211 | 212 | * To ensure consistency of code layout we use tools to both reformat and check 213 | the code. 214 | 215 | * To ensure your code is formatted correctly: 216 | 217 | ```sh 218 | make fmt 219 | ``` 220 | 221 | * To check your code is formatted correctly: 222 | 223 | ```sh 224 | make fmt-check 225 | ``` 226 | 227 | * Finally, as part of the automated workflow for contributing 228 | [pull requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) 229 | pre-commit checks the source code. If this fails revise your PR. To run 230 | pre-commit checks locally (before creating the PR): 231 | 232 | ```sh 233 | make precommit-check 234 | ``` 235 | 236 | This may also revise your code formatting. Re-run `make precommit-check` to 237 | ensure this is the case. 238 | 239 | ## Build PyScript 240 | 241 | * To turn the JavaScript source code found in the `pyscript.core` directory 242 | into a bundled up module ready for the browser, type: 243 | 244 | ```sh 245 | make build 246 | ``` 247 | 248 | The resulting assets will be in the `pyscript.core/dist` directory. 249 | 250 | ## Run the tests 251 | 252 | * The integration tests for PyScript are started with: 253 | 254 | ```sh 255 | make test 256 | ``` 257 | 258 | (This essentially runs the `npm run test:integration` command in the right 259 | place. This is defined in PyScript's `package.json` file.) 260 | 261 | Tests are found in the `core/tests` directory. These are organised into 262 | three locations: 263 | 264 | 1. `python` - the Python based test suite to exercise Python code 265 | **within** PyScript. 266 | 2. `javascript` - JavaScript tests to exercise PyScript itself, in the 267 | browser. 268 | 3. `manual` - containing tests to run manually in a browser, due to the 269 | complex nature of the tests. 270 | 271 | We use [Playwright](https://playwright.dev/) to automate the running of the 272 | Python and JavaScript test suites. We use 273 | [uPyTest](https://github.com/ntoll/upytest) as a test framework for the 274 | Python test suite. uPyTest is a "PyTest inspired" framework for running 275 | tests in the browser on both MicroPython and Pyodide. 276 | 277 | The automated (Playwright) tests are specified in the 278 | `tests/integration.spec.js` file. 279 | 280 | ## Documentation 281 | 282 | * Documentation for PyScript (i.e. what you're reading right now), is found 283 | in a separate repository: 284 | [https://github.com/pyscript/docs](https://github.com/pyscript/docs) 285 | 286 | * The documentation's `README` file contains instructions for setting up a 287 | development environment and contributing. 288 | 289 | ## Contributing 290 | 291 | * We have [suggestions for how to contribute to PyScript](contributing.md). Take 292 | a read and dive in. 293 | 294 | * Please make sure you discuss potential contributions *before* you put in 295 | work. We don#t want folks to waste their time or re-invent the wheel. 296 | 297 | * Technical discussions happen [on our discord server](https://discord.gg/HxvBtukrg2) 298 | and in the 299 | [discussions section](https://github.com/pyscript/pyscript/discussions) of 300 | our GitHub repository. 301 | 302 | * Every Tuesday is a [technical community video call](https://discord.com/events/972017612454232116/1227274094366556279), 303 | the details of which are posted onto 304 | the discord server. Face to face technical discussions happen here. 305 | 306 | * Every Wednesday is a [non-technical community engagement call](https://discord.com/events/972017612454232116/1227275045965922376), 307 | in which we organise how to engage with, grow and nourish our community. 308 | 309 | * Every two weeks, on a Thursday, is a 310 | [PyScript FUN call](https://discord.com/events/972017612454232116/1227275336115556402), 311 | the details of which 312 | are also posted to discord. Project show-and-tells, cool hacks, new features 313 | and a generally humorous and creative time is had by all. 314 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # Example applications 2 | 3 | A curated list of example applications that demonstrate various features of 4 | PyScript can be found [on PyScript.com](https://pyscript.com/@examples). 5 | 6 | The examples are (links take you to the code): 7 | 8 | ### Simple: 9 | 10 | * [Hello world](https://pyscript.com/@examples/hello-world/latest) 11 | * uses included datetime module. No additional packages. 12 | * [WebGL Icosahedron](https://pyscript.com/@examples/webgl-icosahedron/latest) 13 | * uses [three.js](https://threejs.org/) imported as a module in toml) 14 | * [Pandas dataframe fun](https://pyscript.com/@examples/pandas/latest) 15 | * uses [pandas](https://pandas.pydata.org/) from pypi 16 | * [Matplotlib example](https://pyscript.com/@examples/matplotlib/latest) 17 | * uses [matplotlib](https://matplotlib.org/) 18 | * [Todo](https://pyscript.com/@examples/todo-app/latest) 19 | * uses pyweb. No additional packages. 20 | * [Tic Tac Toe](https://pyscript.com/@examples/tic-tac-toe/latest) 21 | * uses pyweb. No additional packages. 22 | * [Pyscript Jokes](https://pyscript.com/@examples/pyscript-jokes/latest) 23 | * uses pyweb and [pyjokes](https://pyjok.es/) 24 | * [D3 visualization](https://pyscript.com/@examples/d3-visualization/latest) 25 | * uses [d3](https://d3js.org/) 26 | * mixes javascript code with python code. imports d3 from javascript. 27 | * [Import antigravity](https://pyscript.com/@examples/antigravity/latest) 28 | * uses svg simply 29 | * [API proxy tutorial](https://pyscript.com/@examples/api-proxy-tutorial/latest) 30 | * uses fetch 31 | * [API proxy and secrets tutorial](https://pyscript.com/@examples/api-proxy-and-secrets-tutorial/latest) 32 | * uses fetch 33 | 34 | ### More complex: 35 | 36 | * [Numpy fractals](https://pyscript.com/@examples/fractals-with-numpy-and-canvas/latest) 37 | * uses [numpy](https://numpy.org/), [sympy](https://www.sympy.org/en/index.html) from pypi 38 | * [Simple slider panel](https://pyscript.com/@examples/simple-panel/latest) 39 | * uses [Panel](https://panel.holoviz.org/) and [Bokeh](https://bokeh.org/) from pypi and loads in index.html 40 | * [Streaming data panel](https://pyscript.com/@examples/streaming-in-panel/latest) 41 | * uses [Panel](https://panel.holoviz.org/), [Bokeh](https://bokeh.org/) [numpy](https://numpy.org/), [pandas](https://pandas.pydata.org/) from pypi 42 | * loads bokeh, panel, tabulator in index.html 43 | * [KMeans in a panel](https://pyscript.com/@examples/kmeans-in-panel/latest) 44 | * uses [Bokeh](https://bokeh.org/), [altair](https://altair-viz.github.io/), [numpy](https://numpy.org/), [pandas](https://pandas.pydata.org/), [scikit-learn](https://scikit-learn.org/stable/), [Panel](https://panel.holoviz.org/) from pypi 45 | * loads panel, bootstrap, vega, tabulator, bokeh in index.html 46 | * [New York Taxi panel (WebGL)](https://pyscript.com/@examples/nyc-taxi-panel-deckgl/latest) 47 | * uses a mixture of pypi and direct load packages in index.html 48 | * [Bokeh](https://bokeh.org/), [numpy](https://numpy.org/), [pandas](https://pandas.pydata.org/), [Panel](https://panel.holoviz.org/), [deck-gl](https://deck.gl/) 49 | * deckGL, bokeh are loaded directly in index.html 50 | * [Folium geographical data](https://pyscript.com/@examples/folium/latest) 51 | * uses [folium](https://python-visualization.github.io/folium/latest/), [pandas](https://pandas.pydata.org/) from pypi 52 | * [Bokeh data plotting](https://pyscript.com/@examples/bokeh/latest) 53 | * uses [pandas](https://pandas.pydata.org/), [Bokeh](https://bokeh.org/), [xyzservices](https://github.com/geopandas/xyzservices) from pypi 54 | * [Altair data plotting](https://pyscript.com/@examples/altair/latest) 55 | * uses [altair](https://altair-viz.github.io/), [pandas](https://pandas.pydata.org/), [vega_datasets](https://github.com/altair-viz/vega_datasets) from pypi 56 | * [Panel and hyplot](https://pyscript.com/@examples/panel-and-hvplot/latest) 57 | * uses [Bokeh](https://bokeh.org/), [Panel](https://panel.holoviz.org/), [markdown-it-py](https://github.com/executablebooks/markdown-it-py), [numpy](https://numpy.org/), [pandas](https://pandas.pydata.org/), [hvplot](https://hvplot.holoviz.org/), [pyodide-http](https://pyodide.org/en/stable/usage/api/python-api/http.html) a fetch library. 58 | * bokeh and panel are loaded in the index.html 59 | 60 | Notes: 61 | - No micropython examples - all are pyodide 62 | - No worker examples 63 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ![PyScript Logo](assets/images/pyscript.svg) 2 | 3 |

PyScript is an open source platform for Python in the browser.

4 | 5 | ## PyScript is... 6 | 7 | * **Easy**: your apps run in the browser with no complicated installation 8 | required. 9 | * **Expressive**: create apps with a powerful, popular and easy to learn 10 | language like Python. 11 | * **Scalable**: no need for expensive infrastructure ~ your code runs in 12 | your user's browser. 13 | * **Shareable**: applications are just a URL on the web. That's it! 14 | * **Universal**: your code runs anywhere a browser runs... which is 15 | _everywhere_! 16 | * **Secure**: PyScript runs in the world's most battle-tested computing 17 | platform, the browser! 18 | * **Powerful**: the best of the web and Python, together at last. 19 | 20 | ## What's next? 21 | 22 |
23 |
I'm a beginner...
24 |
Welcome! PyScript is designed to be friendly for beginner coders. The 25 | best way to start is to read our 26 | beginning PyScript guide 27 | and then use 28 | pyscript.com 29 | to create your first apps. Problems? Check out our 30 | frequently asked questions.
31 |
I'm already technical...
32 |
The beginner docs will set you up with a simple coding environment. For 33 | more in-depth technical coverage of PyScript, consult the 34 | user guide. The 35 | example applications demonstrate many of the features 36 | of PyScript. The API docs and FAQ 37 | contain lots of technical detail.
38 |
I want to contribute...
39 |
40 |

Welcome, friend! 41 | PyScript is an open source project, we expect 42 | participants to act in the spirit of our 43 | code of conduct and we have many 44 | ways in which you can contribute. 45 | Our developer guide explains how to set 46 | up a working development environment for PyScript.

47 |
48 |
Just show me...
49 |
That's easy! Just take a look around 50 | pyscript.com - our 51 | platform for developing and hosting PyScript applications. By using 52 | using this service you help to support and sustain the development and growth 53 | of the open-source PyScript project.
54 |
I want support...
55 |
56 |

Join the conversation on our 57 | discord server, 58 | for realtime chat with core maintainers and fellow users of PyScript. 59 | Check out our YouTube 60 | channel, full of community calls and show and tells. 61 | Explore 62 | educational 63 | and 64 | commercial 65 | support provided by our open source sponsor 66 | Anaconda Inc (this 67 | helps pay for and sustain PyScript!).

68 |
69 |
70 | -------------------------------------------------------------------------------- /docs/license.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | ============== 3 | 4 | _Version 2.0, January 2004_ 5 | _<>_ 6 | 7 | ### Terms and Conditions for use, reproduction, and distribution 8 | 9 | #### 1. Definitions 10 | 11 | “License” shall mean the terms and conditions for use, reproduction, and 12 | distribution as defined by Sections 1 through 9 of this document. 13 | 14 | “Licensor” shall mean the copyright owner or entity authorized by the copyright 15 | owner that is granting the License. 16 | 17 | “Legal Entity” shall mean the union of the acting entity and all other entities 18 | that control, are controlled by, or are under common control with that entity. 19 | For the purposes of this definition, “control” means **(i)** the power, direct or 20 | indirect, to cause the direction or management of such entity, whether by 21 | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the 22 | outstanding shares, or **(iii)** beneficial ownership of such entity. 23 | 24 | “You” (or “Your”) shall mean an individual or Legal Entity exercising 25 | permissions granted by this License. 26 | 27 | “Source” form shall mean the preferred form for making modifications, including 28 | but not limited to software source code, documentation source, and configuration 29 | files. 30 | 31 | “Object” form shall mean any form resulting from mechanical transformation or 32 | translation of a Source form, including but not limited to compiled object code, 33 | generated documentation, and conversions to other media types. 34 | 35 | “Work” shall mean the work of authorship, whether in Source or Object form, made 36 | available under the License, as indicated by a copyright notice that is included 37 | in or attached to the work (an example is provided in the Appendix below). 38 | 39 | “Derivative Works” shall mean any work, whether in Source or Object form, that 40 | is based on (or derived from) the Work and for which the editorial revisions, 41 | annotations, elaborations, or other modifications represent, as a whole, an 42 | original work of authorship. For the purposes of this License, Derivative Works 43 | shall not include works that remain separable from, or merely link (or bind by 44 | name) to the interfaces of, the Work and Derivative Works thereof. 45 | 46 | “Contribution” shall mean any work of authorship, including the original version 47 | of the Work and any modifications or additions to that Work or Derivative Works 48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 49 | by the copyright owner or by an individual or Legal Entity authorized to submit 50 | on behalf of the copyright owner. For the purposes of this definition, 51 | “submitted” means any form of electronic, verbal, or written communication sent 52 | to the Licensor or its representatives, including but not limited to 53 | communication on electronic mailing lists, source code control systems, and 54 | issue tracking systems that are managed by, or on behalf of, the Licensor for 55 | the purpose of discussing and improving the Work, but excluding communication 56 | that is conspicuously marked or otherwise designated in writing by the copyright 57 | owner as “Not a Contribution.” 58 | 59 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf 60 | of whom a Contribution has been received by Licensor and subsequently 61 | incorporated within the Work. 62 | 63 | #### 2. Grant of Copyright License 64 | 65 | Subject to the terms and conditions of this License, each Contributor hereby 66 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 67 | irrevocable copyright license to reproduce, prepare Derivative Works of, 68 | publicly display, publicly perform, sublicense, and distribute the Work and such 69 | Derivative Works in Source or Object form. 70 | 71 | #### 3. Grant of Patent License 72 | 73 | Subject to the terms and conditions of this License, each Contributor hereby 74 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 75 | irrevocable (except as stated in this section) patent license to make, have 76 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 77 | such license applies only to those patent claims licensable by such Contributor 78 | that are necessarily infringed by their Contribution(s) alone or by combination 79 | of their Contribution(s) with the Work to which such Contribution(s) was 80 | submitted. If You institute patent litigation against any entity (including a 81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 82 | Contribution incorporated within the Work constitutes direct or contributory 83 | patent infringement, then any patent licenses granted to You under this License 84 | for that Work shall terminate as of the date such litigation is filed. 85 | 86 | #### 4. Redistribution 87 | 88 | You may reproduce and distribute copies of the Work or Derivative Works thereof 89 | in any medium, with or without modifications, and in Source or Object form, 90 | provided that You meet the following conditions: 91 | 92 | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of 93 | this License; and 94 | * **(b)** You must cause any modified files to carry prominent notices stating that You 95 | changed the files; and 96 | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, 97 | all copyright, patent, trademark, and attribution notices from the Source form 98 | of the Work, excluding those notices that do not pertain to any part of the 99 | Derivative Works; and 100 | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any 101 | Derivative Works that You distribute must include a readable copy of the 102 | attribution notices contained within such NOTICE file, excluding those notices 103 | that do not pertain to any part of the Derivative Works, in at least one of the 104 | following places: within a NOTICE text file distributed as part of the 105 | Derivative Works; within the Source form or documentation, if provided along 106 | with the Derivative Works; or, within a display generated by the Derivative 107 | Works, if and wherever such third-party notices normally appear. The contents of 108 | the NOTICE file are for informational purposes only and do not modify the 109 | License. You may add Your own attribution notices within Derivative Works that 110 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 111 | provided that such additional attribution notices cannot be construed as 112 | modifying the License. 113 | 114 | You may add Your own copyright statement to Your modifications and may provide 115 | additional or different license terms and conditions for use, reproduction, or 116 | distribution of Your modifications, or for any such Derivative Works as a whole, 117 | provided Your use, reproduction, and distribution of the Work otherwise complies 118 | with the conditions stated in this License. 119 | 120 | #### 5. Submission of Contributions 121 | 122 | Unless You explicitly state otherwise, any Contribution intentionally submitted 123 | for inclusion in the Work by You to the Licensor shall be under the terms and 124 | conditions of this License, without any additional terms or conditions. 125 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 126 | any separate license agreement you may have executed with Licensor regarding 127 | such Contributions. 128 | 129 | #### 6. Trademarks 130 | 131 | This License does not grant permission to use the trade names, trademarks, 132 | service marks, or product names of the Licensor, except as required for 133 | reasonable and customary use in describing the origin of the Work and 134 | reproducing the content of the NOTICE file. 135 | 136 | #### 7. Disclaimer of Warranty 137 | 138 | Unless required by applicable law or agreed to in writing, Licensor provides the 139 | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, 140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 141 | including, without limitation, any warranties or conditions of TITLE, 142 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 143 | solely responsible for determining the appropriateness of using or 144 | redistributing the Work and assume any risks associated with Your exercise of 145 | permissions under this License. 146 | 147 | #### 8. Limitation of Liability 148 | 149 | In no event and under no legal theory, whether in tort (including negligence), 150 | contract, or otherwise, unless required by applicable law (such as deliberate 151 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 152 | liable to You for damages, including any direct, indirect, special, incidental, 153 | or consequential damages of any character arising as a result of this License or 154 | out of the use or inability to use the Work (including but not limited to 155 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 156 | any and all other commercial damages or losses), even if such Contributor has 157 | been advised of the possibility of such damages. 158 | 159 | #### 9. Accepting Warranty or Additional Liability 160 | 161 | While redistributing the Work or Derivative Works thereof, You may choose to 162 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 163 | other liability obligations and/or rights consistent with this License. However, 164 | in accepting such obligations, You may act only on Your own behalf and on Your 165 | sole responsibility, not on behalf of any other Contributor, and only if You 166 | agree to indemnify, defend, and hold each Contributor harmless for any liability 167 | incurred by, or claims asserted against, such Contributor by reason of your 168 | accepting any such warranty or additional liability. 169 | 170 | _END OF TERMS AND CONDITIONS_ 171 | 172 | ### APPENDIX: How to apply the Apache License to your work 173 | 174 | To apply the Apache License to your work, attach the following boilerplate 175 | notice, with the fields enclosed by brackets `[]` replaced with your own 176 | identifying information. (Don't include the brackets!) The text should be 177 | enclosed in the appropriate comment syntax for the file format. We also 178 | recommend that a file or class name and description of purpose be included on 179 | the same “printed page” as the copyright notice for easier identification within 180 | third-party archives. 181 | 182 | Copyright [yyyy] [name of copyright owner] 183 | 184 | Licensed under the Apache License, Version 2.0 (the "License"); 185 | you may not use this file except in compliance with the License. 186 | You may obtain a copy of the License at 187 | 188 | http://www.apache.org/licenses/LICENSE-2.0 189 | 190 | Unless required by applicable law or agreed to in writing, software 191 | distributed under the License is distributed on an "AS IS" BASIS, 192 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 193 | See the License for the specific language governing permissions and 194 | limitations under the License. 195 | 196 | 197 | -------------------------------------------------------------------------------- /docs/mini-coi.js: -------------------------------------------------------------------------------- 1 | /*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */ 2 | /*! mini-coi - Andrea Giammarchi and contributors, licensed under MIT */ 3 | (({ document: d, navigator: { serviceWorker: s } }) => { 4 | if (d) { 5 | const { currentScript: c } = d; 6 | s.register(c.src, { scope: c.getAttribute('scope') || '.' }).then(r => { 7 | r.addEventListener('updatefound', () => location.reload()); 8 | if (r.active && !s.controller) location.reload(); 9 | }); 10 | } 11 | else { 12 | addEventListener('install', () => skipWaiting()); 13 | addEventListener('activate', e => e.waitUntil(clients.claim())); 14 | addEventListener('fetch', e => { 15 | const { request: r } = e; 16 | if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return; 17 | e.respondWith(fetch(r).then(r => { 18 | const { body, status, statusText } = r; 19 | if (!status || status > 399) return r; 20 | const h = new Headers(r.headers); 21 | h.set('Cross-Origin-Opener-Policy', 'same-origin'); 22 | h.set('Cross-Origin-Embedder-Policy', 'require-corp'); 23 | h.set('Cross-Origin-Resource-Policy', 'cross-origin'); 24 | return new Response(body, { status, statusText, headers: h }); 25 | })); 26 | }); 27 | } 28 | })(self); 29 | -------------------------------------------------------------------------------- /docs/user-guide/architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture, Lifecycle & Interpreters 2 | 3 | ## Core concepts 4 | 5 | PyScript's architecture has three core concepts: 6 | 7 | 1. A small, efficient and powerful kernel called 8 | [PolyScript](https://github.com/pyscript/polyscript) is the foundation 9 | upon which PyScript and plugins are built. 10 | 2. A library called [coincident](https://github.com/WebReflection/coincident#readme) 11 | that simplifies and coordinates interactions with web workers. 12 | 3. The PyScript [stack](https://en.wikipedia.org/wiki/Solution_stack) inside 13 | the browser is simple and clearly defined. 14 | 15 | ### PolyScript 16 | 17 | [PolyScript](https://github.com/pyscript/polyscript) is the core of PyScript. 18 | 19 | !!! danger 20 | 21 | Unless you are an advanced user, you only need to know that PolyScript 22 | exists, and it can be safely ignored. 23 | 24 | PolyScript's purpose is to bootstrap the platform and provide all the necessary 25 | core capabilities. Setting aside PyScript for a moment, to use 26 | *just PolyScript* requires a ` 35 | 36 | 37 | 41 | 45 | 46 | 47 | ``` 48 | 49 | !!! warning 50 | 51 | **PolyScript is not PyScript.** 52 | 53 | PyScript enhances the available Python interpreters with convenient 54 | features, helper functions and easy-to-use yet powerful capabilities. 55 | 56 | These enhancements are missing from PolyScript. 57 | 58 | PolyScript's capabilities, upon which PyScript is built, can be summarised as: 59 | 60 | * Evaluation of code via [` 316 | 317 | 321 | 324 | ``` 325 | 326 | When you find yourself in this situation, simply use the `window` object in 327 | your Python code (found in the `pyscript` namespace) to interact with the 328 | resulting JavaScript objects: 329 | 330 | ```python title="Python interaction with the JavaScript global reference" 331 | from pyscript import window, document 332 | 333 | 334 | # The window object is the global context of your web page. 335 | html = window.html 336 | 337 | # Just use the object "as usual"... 338 | # e.g. show escaped HTML in the body: <> 339 | document.body.append(html.escape("<>")) 340 | ``` 341 | 342 | You can find an example of this technique here: 343 | 344 | [https://pyscript.com/@agiammarchi/floral-glade/v1](https://pyscript.com/@agiammarchi/floral-glade/v1) 345 | 346 | ### JavaScript as a non-standard UMD module 347 | 348 | Sadly, these sorts of non-standard JavaScript modules are still quite 349 | prevalent. But the good news is there are strategies you can use to help you 350 | get them to work properly. 351 | 352 | The non-standard UMD approach tries to check for `export` and `module` fields 353 | in the JavaScript module and, if it doesn’t find them, falls back to treating 354 | the module in the same way as a global reference described above. 355 | 356 | If you find you have a UMD JavaScript module, there are services online to 357 | automagically convert it to the modern and standards compliant way to d 358 | o JavaScript modules. A common (and usually reliable) service is provided by 359 | [https://esm.run/your-module-name](https://esm.run/your-module-name), a 360 | service that provides an out of the box way to consume the module in the 361 | correct and standard manner: 362 | 363 | ```html title="Use esm.run to automatically convert a non-standard UMD module" 364 | 365 | 372 | ``` 373 | 374 | If a similar test works for the module you want to use, use the esm.run CDN 375 | service within the `py` or `mpy` configuration file as explained at the start 376 | of this section on JavaScript (i.e. you'll use it via the `pyscript.js_modules` 377 | namespace). 378 | 379 | If this doesn't work, assume the module is not updated nor migrated to a state 380 | that can be automatically translated by services like esm.run. You could try an 381 | alternative (more modern) JavaScript module to achieve you ends or (if it 382 | really must be this module), you can wrap it in a new JavaScript module that 383 | conforms to the modern standards. 384 | 385 | The following four files demonstrate this approach: 386 | 387 | ```html title="index.html - still grab the script so it appears as a global reference." 388 | 389 | ... 390 | 391 | 392 | ... 393 | ``` 394 | 395 | ```js title="wrapper.js - this grabs the JavaScript functionality from the global context and wraps it (exports it) in the modern standards compliant manner." 396 | // get all utilities needed from the global. 397 | const { escape, unescape } = globalThis.html; 398 | 399 | // export utilities like a standards compliant module would do. 400 | export { escape, unescape }; 401 | ``` 402 | 403 | ```toml title="pyscript.toml - configure your JS modules as before, but use your wrapper instead of the original module." 404 | [js_modules.main] 405 | # will simulate a standard JS module 406 | "./wrapper.js" = "html_escaper" 407 | ``` 408 | 409 | ```python title="main.py - just import the module as usual and make use of it." 410 | from pyscript import document 411 | 412 | # import the module either via 413 | from pyscript.js_modules import html_escaper 414 | # or via 415 | from pyscript.js_modules.html_escaper import escape, unescape 416 | 417 | # show on body: <> 418 | document.body.append(html.escape("<>")) 419 | ``` 420 | 421 | You can see this approach in action here: 422 | 423 | [https://pyscript.com/@agiammarchi/floral-glade/v2](https://pyscript.com/@agiammarchi/floral-glade/v2) 424 | 425 | ### A standard JavaScript module 426 | 427 | This is both the easiest and best way to import any standard JS module into 428 | Python. 429 | 430 | You don't need to reference the script in your HTML, just define how the source 431 | JavaScript module maps into the `pyscript.js_modules` namespace in your 432 | configuration file, as explained above. 433 | 434 | That's it! 435 | 436 | Here is an example project that uses this approach: 437 | 438 | [https://pyscript.com/@agiammarchi/floral-glade/v3](https://pyscript.com/@agiammarchi/floral-glade/v3) 439 | 440 | 441 | ### My own JavaScript code 442 | 443 | If you have your own JavaScript work, just remember to write it as a standard 444 | JavaScript module. Put simply, ensure you `export` the things you need to. For 445 | instance, in the following fragment of JavaScript, the two functions are 446 | exported from the module: 447 | 448 | ```js title="code.js - containing two functions exported as capabilities of the module." 449 | /* 450 | Some simple JavaScript functions for example purposes. 451 | */ 452 | 453 | export function hello(name) { 454 | return "Hello " + name; 455 | } 456 | 457 | export function fibonacci(n) { 458 | if (n == 1) return 0; 459 | if (n == 2) return 1; 460 | return fibonacci(n - 1) + fibonacci(n - 2); 461 | } 462 | ``` 463 | 464 | Next, just reference this module in the usual way in your TOML or JSON 465 | configuration file: 466 | 467 | ```TOML title="pyscript.toml - references the code.js module so it will appear as the code module in the pyscript.js_modules namespace." 468 | [js_modules.main] 469 | "code.js" = "code" 470 | ``` 471 | 472 | In your HTML, reference your Python script with this configuration file: 473 | 474 | ```html title="Reference the expected configuration file." 475 | 476 | ``` 477 | 478 | Finally, just use your JavaScript module’s exported functions inside PyScript: 479 | 480 | ```python title="Just call your bespoke JavaScript code from Python." 481 | from pyscript.js_modules import code 482 | 483 | 484 | # Just use the JS code from Python "as usual". 485 | greeting = code.hello("Chris") 486 | print(greeting) 487 | result = code.fibonacci(12) 488 | print(result) 489 | ``` 490 | 491 | You can see this in action in the following example project: 492 | 493 | [https://pyscript.com/@ntoll/howto-javascript/latest](https://pyscript.com/@ntoll/howto-javascript/latest) 494 | -------------------------------------------------------------------------------- /docs/user-guide/editor.md: -------------------------------------------------------------------------------- 1 | # Python editor 2 | 3 | The PyEditor is a core plugin. 4 | 5 | !!! warning 6 | 7 | Work on the Python editor is in its early stages. We have made it available 8 | in this version of PyScript to give the community an opportunity to play, 9 | experiment and provide feedback. 10 | 11 | Future versions of PyScript will include a more refined, robust and perhaps 12 | differently behaving version of the Python editor. 13 | 14 | If you specify the type of a ` 34 | 40 | ``` 41 | 42 | However, different editors can share the same interpreter if they share the 43 | same `env` attribute value. 44 | 45 | ```html title="Two editors sharing the same MicroPython environment." 46 | 53 | 57 | ``` 58 | 59 | The outcome of these code fragments should look something like this: 60 | 61 | 62 | 63 | !!! info 64 | 65 | Notice that the interpreter type, and optional environment name is shown 66 | at the top right above the Python editor. 67 | 68 | Hovering over the Python editor reveals the "run" button. 69 | 70 | ### Stop evaluation 71 | 72 | Sometimes, for whatever reason, the fragment of code in the editor will never 73 | complete. Perhaps it's stuck in an infinite loop and you need to stop the 74 | evaluation of your code so you can fix the problem and start it again. 75 | 76 | When the code is running, hovering over the editor will reveal a stop button 77 | (where the run button was found). Click on it, confirm you want to stop your 78 | code, and then the code will stop and the editor will refresh so you can fix 79 | your code. 80 | 81 | It looks something like this: 82 | 83 | 84 | 85 | ### Setup 86 | 87 | Sometimes you need to create a pre-baked Pythonic context for a shared 88 | environment used by an editor. This need is especially helpful in educational 89 | situations where boilerplate code can be run, with just the important salient 90 | code available in the editor. 91 | 92 | To achieve this end use the `setup` attribute within a `script` tag. The 93 | content of this editor will not be shown, but will bootstrap the referenced 94 | environment automatically before any following editor within the same 95 | environment is evaluated. 96 | 97 | ```html title="Bootstrapping an environment with `setup`" 98 | 103 | 104 | 110 | ``` 111 | 112 | Finally, the `target` attribute allows you to specify a node into which the 113 | editor will be rendered: 114 | 115 | ```html title="Specify a target for the Python editor." 116 | 120 |
121 | ``` 122 | 123 | ## Editor VS Terminal 124 | 125 | The editor and terminal are commonly used to embed interactive Python code into 126 | a website. However, there are differences between the two plugins, of which you 127 | should be aware. 128 | 129 | The main difference is that a `py-editor` or `mpy-editor` is an isolated 130 | environment (from the rest of PyScript that may be running on the page) and 131 | its code always runs in a web worker. We do this to prevent accidental blocking 132 | of the main thread that would freeze your browser's user interface. 133 | 134 | Because an editor is isolated from regular *py* or *mpy* scripts, one should 135 | not expect the same behavior regular *PyScript* elements follow, most notably: 136 | 137 | * The editor's user interface is based on 138 | [CodeMirror](https://codemirror.net/) and not on XTerm.js 139 | [as it is for the terminal](../terminal). 140 | * Code is evaluated all at once and asynchronously when the *Run* button is 141 | pressed (not each line at a time, as in the terminal). 142 | * The editor has listeners for `Ctrl-Enter` or `Cmd-Enter`, and 143 | `Shift-Enter` to shortcut the execution of all the code. These shortcuts 144 | make no sense in the terminal as each line is evaluated separately. 145 | * There is a clear separation between the code and any resulting output. 146 | * You may not use blocking calls (like `input`) with the editor, whereas 147 | these will work if running the terminal via a worker. 148 | * It's an editor! So simple or complex programs can be fully written without 149 | running the code until ready. In the terminal, code is evaluated one line 150 | at a time as it is typed in. 151 | * There is no special reference to the underlying editor instance, while 152 | there is both `script.terminal` or `__terminal__` in the terminal. 153 | 154 | ## Read / Write / Execute 155 | 156 | Sometimes you need to programatically read, write or execute code in an 157 | editor. Once PyScript has started, every py-editor/mpy-editor script tag gets 158 | a `code` accessor attached to it. 159 | 160 | ```python 161 | from pyscript import document 162 | 163 | # Grab the editor script reference. 164 | editor = document.querySelector('#editor') 165 | 166 | # Output the live content of the editor. 167 | print(editor.code) 168 | 169 | # Update the live content of the editor. 170 | editor.code = """ 171 | a = 1 172 | b = 2 173 | print(a + b) 174 | """ 175 | 176 | # Evaluate the live code in the editor. 177 | # This could be any arbitrary code to evaluate in the editor's Python context. 178 | editor.process(editor.code) 179 | ``` 180 | 181 | ## Configuration 182 | 183 | Unlike ` 207 | 208 | 222 | ``` 223 | 224 | This 225 | [live example](https://agiammarchi.pyscriptapps.com/pyeditor-iot-example/latest/) 226 | shows how the editor can be used to execute code via a USB serial connection to 227 | a connected MicroPython microcontroller. 228 | 229 | ## Tab behavior 230 | 231 | We currently trap the `tab` key in a way that reflects what a regular code 232 | editor would do: the code is simply indented, rather than focus moving to 233 | another element. 234 | 235 | We are fully aware of the implications this might have around accessibility so 236 | we followed 237 | [this detailed advice from Codemirror's documentation](https://codemirror.net/examples/tab/) 238 | We have an *escape hatch* to move focus outside the editor. Press `esc` before 239 | `tab` to move focus to the next focusable element. Otherwise `tab` indents 240 | code. 241 | 242 | 243 | ## Still missing 244 | 245 | The PyEditor is currently under active development and refinement, so features 246 | may change (depending on user feedback). For instance, there is currently no 247 | way to stop or kill a web worker that has got into difficulty from the editor 248 | (hint: refreshing the page will reset things). 249 | -------------------------------------------------------------------------------- /docs/user-guide/features.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 |
4 |
All the web
5 |
6 |

Pyscript gives you full access to the DOM and all 7 | the web 8 | APIs implemented by your browser.

9 | 10 |

Thanks to the foreign 11 | function interface (FFI), Python just works with all the browser has to 12 | offer, including any third party JavaScript libraries that may be included 13 | in the page.

14 | 15 |

The FFI is bi-directional ~ it also enables JavaScript to access the 16 | power of Python.

17 | 18 |
All of Python
19 |
20 |

PyScript brings you two Python interpreters:

21 |
    22 |
  1. Pyodide - the original standard 23 | CPython interpreter you know and love, but compiled to WebAssembly. 24 |
  2. 25 |
  3. MicroPython - a lean and 26 | efficient reimplementation of Python3 that includes a comprehensive 27 | subset of the standard library, compiled to WebAssembly.
  4. 28 |
29 |

Because it is just regular CPython, Pyodide puts Python's deep and 30 | diverse ecosystem of libraries, frameworks 31 | and modules at your disposal. No matter the area of computing endeavour, 32 | there's probably a Python library to help. Got a favourite library in 33 | Python? Now you can use it in the browser and share your work with just 34 | a URL.

35 |

MicroPython, because of its small size (170k) and speed, is especially 36 | suited to running on more constrained browsers, such as those on mobile 37 | or tablet devices. It includes a powerful sub-set of the Python standard 38 | library and efficiently exposes the expressiveness of Python to the 39 | browser.

40 |

Both Python interpreters supported by PyScript implement the 41 | same FFI to bridge the gap between the worlds of Python 42 | and the browser.

43 |
44 | 45 |
AI and Data science built in
46 |
Python is famous for its extraordinary usefulness in artificial 47 | intelligence and data science. The Pyodide interpreter comes with many of 48 | the libraries you need for this sort of work already baked in.
49 | 50 |
Mobile friendly MicroPython
51 |
52 |

Thanks to MicroPython in PyScript, there is a compelling story for 53 | Python on mobile.

54 | 55 |

MicroPython is small and fast enough that your app will start quickly 56 | on first load, and almost instantly (due to the cache) on subsequent 57 | runs.

58 | 59 |
Parallel execution
60 |
Thanks to a browser technology called 61 | web workers 62 | expensive and blocking computation can run somewhere other than the main 63 | application thread controlling the user interface. When such work is done 64 | on the main thread, the browser appears frozen; web workers ensure 65 | expensive blocking computation happens elsewhere. 66 | Think of workers as independent subprocesses in your web page.
67 | 68 |
Rich and powerful plugins
69 |
70 |

PyScript has a small, efficient yet powerful core called 71 | PolyScript. Most of 72 | the functionality of PyScript is actually implemented through PolyScript's 73 | plugin system.

74 | 75 |

This approach ensures a clear separation of concerns: PolyScript 76 | can focus on being small, efficient and powerful, whereas the PyScript 77 | related plugins allow us to Pythonically build upon the solid foundations 78 | of PolyScript.

79 | 80 |

Because there is a plugin system, folks 81 | independent of the PyScript core team have a way to create and 82 | contribute to a rich ecosystem of plugins whose functionality reflects the 83 | unique and diverse needs of PyScript's users.

84 |
85 |
86 | 87 | 88 | -------------------------------------------------------------------------------- /docs/user-guide/ffi.md: -------------------------------------------------------------------------------- 1 | # PyScript FFI 2 | 3 | The foreign function interface (FFI) gives Python access to JavaScript, and 4 | JavaScript access to Python. As a result PyScript is able to access all the 5 | standard APIs and capabilities provided by the browser. 6 | 7 | We provide a unified `pyscript.ffi` because 8 | [Pyodide's FFI](https://pyodide.org/en/stable/usage/api/python-api/ffi.html) 9 | is only partially implemented in MicroPython and there are some fundamental 10 | differences. The `pyscript.ffi` namespace smooths out such differences into 11 | a uniform and consistent API. 12 | 13 | Our `pyscript.ffi` offers the following utilities: 14 | 15 | * `ffi.to_js(reference)` converts a Python object into its JavaScript 16 | counterpart. 17 | * `ffi.create_proxy(def_or_lambda)` proxies a generic Python function into a 18 | JavaScript one, without destroying its reference right away. 19 | 20 | Should you require access to Pyodide or MicroPython's specific version of the 21 | FFI you'll find them under the `pyodide.ffi` and `micropython.ffi` namespaces. 22 | Please refer to the documentation for those projects for further information. 23 | 24 | ## to_js 25 | 26 | In the 27 | [Pyodide project](https://pyodide.org/en/stable/usage/api/python-api/ffi.html#pyodide.ffi.to_js), 28 | this utility converts Python dictionaries into 29 | [JavaScript `Map` objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). 30 | Such `Map` objects reflect the `obj.get(field)` semantics native to Python's 31 | way of retrieving a value from a dictionary. 32 | 33 | Unfortunately, this default conversion breaks the vast majority of native and 34 | third party JavaScript APIs. This is because the convention in idiomatic 35 | JavaScript is to use an [object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects) 36 | for such key/value data structures (not a `Map` instance). 37 | 38 | A common complaint has been the repeated need to call `to_js` with the long 39 | winded argument `dict_converter=js.Object.fromEntries`. It turns out, most 40 | people most of the time simply want to map a Python `dict` to a JavaScript 41 | `object` (not a `Map`). 42 | 43 | Furthermore, in MicroPython the default Python `dict` conversion is to the 44 | idiomatic and sensible JavaScript `object`, making the need to specify a 45 | dictionary converter pointless. 46 | 47 | Therefore, if there is no reason to hold a Python reference in a JavaScript 48 | context (which is 99% of the time, for common usage of PyScript) then use the 49 | `pyscript.ffi.to_js` function, on both Pyodide and MicroPython, to always 50 | convert a Python `dict` to a JavaScript `object`. 51 | 52 | ```html title="to_js: pyodide.ffi VS pyscript.ffi" 53 | 54 | 60 | 61 | 62 | 68 | ``` 69 | 70 | !!! Note 71 | 72 | It is still possible to specify a different `dict_converter` or use Pyodide 73 | specific features while converting Python references by simply overriding 74 | the explicit field for `dict_converter`. 75 | 76 | However, we cannot guarantee all fields and features provided by Pyodide 77 | will work in the same way on MicroPython. 78 | 79 | ## create_proxy 80 | 81 | In the 82 | [Pyodide project](https://pyodide.org/en/stable/usage/api/python-api/ffi.html#pyodide.ffi.create_proxy), 83 | this function ensures that a Python callable associated with an event listener, 84 | won't be garbage collected immediately after the function is assigned to the 85 | event. Therefore, in Pyodide, if you do not wrap your Python function, it is 86 | immediately garbage collected after being associated with an event listener. 87 | 88 | This is so common a gotcha (see the FAQ for 89 | [more on this](../../faq#borrowed-proxy)) that the Pyodide project have already 90 | created many work-arounds to address this situation. For example, the 91 | `create_once_callable`, `pyodide.ffi.wrappers.add_event_listener` and 92 | `pyodide.ffi.set_timeout` are all methods whose goal is to automatically manage 93 | the lifetime of the passed in Python callback. 94 | 95 | Add to this situation methods connected to the JavaScript `Promise` object 96 | (`.then` and `.catch` callbacks that are implicitly handled to guarantee no 97 | leaks once executed) and things start to get confusing and overwhelming with 98 | many ways to achieve a common end result. 99 | 100 | Ultimately, user feedback suggests folks simply want to do something like this, 101 | as they write their Python code: 102 | 103 | ```python title="Define a callback without create_proxy." 104 | import js 105 | from pyscript import window 106 | 107 | 108 | def callback(msg): 109 | """ 110 | A Python callable that logs a message. 111 | """ 112 | window.console.log(msg) 113 | 114 | 115 | # Use the callback without having to explicitly create_proxy. 116 | js.setTimeout(callback, 1000, 'success') 117 | ``` 118 | 119 | Therefore, PyScript provides an experimental configuration flag called 120 | `experimental_create_proxy = "auto"`. When set, you should never have to care 121 | about these technical details nor use the `create_proxy` method and all the 122 | JavaScript callback APIs should just work. 123 | 124 | Under the hood, the flag is strictly coupled with the JavaScript garbage 125 | collector that will eventually destroy all proxy objects created via the 126 | [FinalizationRegistry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry) 127 | built into the browser. 128 | 129 | This flag also won't affect MicroPython because it rarely needs a 130 | `create_proxy` at all when Python functions are passed to JavaScript event 131 | handlers. MicroPython automatically handles this situation. However, 132 | there might still be rare and niche cases in MicroPython where such a 133 | conversion might be needed. 134 | 135 | Hence, PyScript retains the `create_proxy` method, even though it does not 136 | change much in the MicroPython world, although it might be still needed with 137 | the Pyodide runtime is you don't use the `experimental_create_proxy = "auto"` 138 | flag. 139 | 140 | At a more fundamental level, MicroPython doesn't provide (yet) a way to 141 | explicitly destroy a proxy reference, whereas Pyodide still expects to 142 | explicitly invoke `proxy.destroy()` when the function is not needed. 143 | 144 | !!! warning 145 | 146 | In MicroPython proxies might leak due to the lack of a `destroy()` method. 147 | 148 | Happily, proxies are usually created explicitly for event listeners or 149 | other utilities that won't need to be destroyed in the future. So the lack 150 | of a `destroy()` method in MicroPython is not a problem in this specific, 151 | and most common, situation. 152 | 153 | Until we have a `destroy()` in MicroPython, we suggest testing the 154 | `experimental_create_proxy` flag with Pyodide so both runtimes handle 155 | possible leaks automatically. 156 | 157 | For completeness, the following examples illustrate the differences in 158 | behaviour between Pyodide and MicroPython: 159 | 160 | ```html title="A classic Pyodide gotcha VS MicroPython" 161 | 165 | 169 | 170 | 171 | 175 | ``` 176 | 177 | To address the difference in Pyodide's behaviour, we can use the experimental 178 | flag: 179 | 180 | ```html title="experimental create_proxy" 181 | 182 | experimental_create_proxy = "auto" 183 | 184 | 185 | 186 | 190 | ``` 191 | 192 | Alternatively, `create_proxy` via the `pyscript.ffi` in both interpreters, but 193 | only in Pyodide can we then destroy such proxy: 194 | 195 | ```html title="pyscript.ffi.create_proxy" 196 | 197 | 211 | ``` 212 | -------------------------------------------------------------------------------- /docs/user-guide/filesystem.md: -------------------------------------------------------------------------------- 1 | # PyScript and Filesystems 2 | 3 | As you know, the filesystem is where you store files. For Python to work there 4 | needs to be a filesystem in which Python packages, modules and data for your 5 | apps can be found. When you `import` a library, or when you `open` a file, it 6 | is on the in-browser virtual filesystem that Python looks. 7 | 8 | However, things are not as they may seem. 9 | 10 | This section clarifies what PyScript means by a filesystem, and the way in 11 | which PyScript interacts with such a concept. 12 | 13 | ## Two filesystems 14 | 15 | PyScript interacts with two filesystems. 16 | 17 | 1. The browser, thanks to 18 | [Emscripten](https://emscripten.org/docs/api_reference/Filesystem-API.html), 19 | provides a virtual in-memory filesystem. **This has nothing to do with your 20 | device's local filesystem**, but is contained within the browser based 21 | sandbox used by PyScript. The [files](../configuration/#files) 22 | configuration API defines what is found on this filesystem. 23 | 2. PyScript provides an easy to use API for accessing your device's local 24 | filesystem. It requires permission from the user to mount a folder from the 25 | local filesystem onto a directory in the browser's virtual filesystem. Think 26 | of it as gate-keeping a bridge to the outside world of the device's local 27 | filesystem. 28 | 29 | !!! danger 30 | 31 | Access to the device's local filesystem **is only available in Chromium 32 | based browsers**. The maximum capacity for files shared in this way is 33 | 4GB. 34 | 35 | Firefox and Safari do not support this capability (yet), and so it is not 36 | available to PyScript running in these browsers. 37 | 38 | ## The in-browser filesystem 39 | 40 | The filesystem that both Pyodide and MicroPython use by default is the 41 | [in-browser virtual filesystem](https://emscripten.org/docs/api_reference/Filesystem-API.html). 42 | Opening files and importing modules takes place in relation to this sandboxed 43 | environment, configured via the [files](../configuration/#files) entry in your 44 | settings. 45 | 46 | ```toml title="Filesystem configuration via TOML." 47 | [files] 48 | "https://example.com/myfile.txt": "" 49 | ``` 50 | 51 | ```python title="Just use the resulting file 'as usual'." 52 | # Interacting with the virtual filesystem, "as usual". 53 | with open("myfile.txt", "r") as myfile: 54 | print(myfile.read()) 55 | ``` 56 | 57 | Currently, each time you re-load the page, the filesystem is recreated afresh, 58 | so any data stored by PyScript to this filesystem will be lost. 59 | 60 | !!! info 61 | 62 | In the future, we may make it possible to configure the in-browser virtual 63 | filesystem as persistent across re-loads. 64 | 65 | [This article](https://emscripten.org/docs/porting/files/file_systems_overview.html) 66 | gives an excellent overview of the browser based virtual filesystem's 67 | implementation and architecture. 68 | 69 | The most important key concepts to remember are: 70 | 71 | * The PyScript filesystem is contained *within* the browser's sandbox. 72 | * Each instance of a Python interpreter used by PyScript runs in a separate 73 | sandbox, and so does NOT share virtual filesystems. 74 | * All Python related filesytem operations work as expected with this 75 | filesystem. 76 | * The virtual filesystem is configured via the 77 | [files](../configuration/#files) entry in your settings. 78 | * The virtual filesystem is (currently) NOT persistent between page re-loads. 79 | * Currently, the filesystem has a maximum capacity of 4GB of data (something 80 | over which we have no control). 81 | 82 | ## The device's local filesystem 83 | 84 | **Access to the device's local filesystem currently only works on Chromium 85 | based browsers**. 86 | 87 | Your device (the laptop, mobile or tablet) that runs your browser has a 88 | filesystem provided by a hard drive. Thanks to the 89 | [`pyscript.fs` namespace in our API](../../api/#pyscriptfs), both MicroPython 90 | and Pyodide (CPython) gain access to this filesystem should the user of 91 | your code allow this to happen. 92 | 93 | This is a [transient activation](https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation) 94 | for the purposes of 95 | [user activation of gated features](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation). 96 | Put simply, before your code gains access to their local filesystem, an 97 | explicit agreement needs to be gathered from the user. Part of this process 98 | involves asking the user to select a target directory on their local 99 | filesystem, to which PyScript will be given access. 100 | 101 | The directory on their local filesystem, selected by the user, is then mounted 102 | to a given directory inside the browser's virtual filesystem. In this way a 103 | mapping is made between the sandboxed world of the browser, and the outside 104 | world of the user's filesystem. 105 | 106 | Your code will then be able to perform all the usual filesystem related 107 | operations provided by Python, within the mounted directory. However, **such 108 | changes will NOT take effect on the local filesystem UNTIL your code 109 | explicitly calls the `sync` function**. At this point, the state of the 110 | in-browser virtual filesystem and the user's local filesystem are synchronised. 111 | 112 | The following code demonstrates the simplest use case: 113 | 114 | ```python title="The core operations of the pyscript.fs API" 115 | from pyscript import fs 116 | 117 | # Ask once for permission to mount any local folder 118 | # into the virtual filesystem handled by Pyodide/MicroPython. 119 | # The folder "/local" refers to the directory on the virtual 120 | # filesystem to which the user-selected directory will be 121 | # mounted. 122 | await fs.mount("/local") 123 | 124 | # ... DO FILE RELATED OPERATIONS HERE ... 125 | 126 | # If changes were made, ensure these are persisted to the local filesystem's 127 | # folder. 128 | await fs.sync("/local") 129 | 130 | # If needed to free RAM or that specific path, sync and unmount 131 | await fs.unmount("/local") 132 | ``` 133 | 134 | It is possible to use multiple different local directories with the same mount 135 | point. This is important if your application provides some generic 136 | functionality on data that might be in different local directories because 137 | while the nature of the data might be similar, the subject is not. For 138 | instance, you may have different models for a PyScript based LLM in different 139 | directories, and may wish to switch between them at runtime using different 140 | handlers (requiring their own transient action). In which case use 141 | the following technique: 142 | 143 | ```python title="Multiple local directories on the same mount point" 144 | # Mount a local folder specifying a different handler. 145 | # This requires a user explicit transient action (once). 146 | await fs.mount("/local", id="v1") 147 | # ... operate on that folder ... 148 | await fs.unmount("/local") 149 | 150 | # Mount a local folder specifying a different handler. 151 | # This also requires a user explicit transient action (once). 152 | await fs.mount("/local", id="v2") 153 | # ... operate on that folder ... 154 | await fs.unmount("/local") 155 | 156 | # Go back to the original handler or a previous one. 157 | # No transient action required now. 158 | await fs.mount("/local", id="v1") 159 | # ... operate again on that folder ... 160 | ``` 161 | 162 | In addition to the mount `path` and handler `id`, the `fs.mount` function can 163 | take two further arguments: 164 | 165 | * `mode` (by default `"readwrite"`) indicates the sort of activity available to 166 | the user. It can also be set to `read` for read-only access to the local 167 | filesystem. This is a part of the 168 | [web-standards](https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker#mode) 169 | for directory selection. 170 | * `root` - (by default, `""`) is a hint to the browser for where to start 171 | picking the path that should be mounted in Python. Valid values are: 172 | `desktop`, `documents`, `downloads`, `music`, `pictures` or `videos` 173 | [as per web standards](https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker#startin). 174 | 175 | The `sync` and `unmount` functions only accept the mount `path` used in the 176 | browser's local filesystem. 177 | -------------------------------------------------------------------------------- /docs/user-guide/first-steps.md: -------------------------------------------------------------------------------- 1 | # First steps 2 | 3 | It's simple: 4 | 5 | * tell your browser to use PyScript, then, 6 | * tell PyScript how to run your Python code. 7 | 8 | That's it! 9 | 10 | For the browser to use PyScript, simply add a ` 26 | 27 | 28 | 29 | 30 | 31 | ``` 32 | 33 | There are two ways to tell PyScript how to find your code. 34 | 35 | * With a standard HTML ` 53 | ``` 54 | 55 | ...and here's a `` tag with inline Python code. 56 | 57 | ```html title="A <py-script> tag with inline code" 58 | 59 | import sys 60 | from pyscript import display 61 | 62 | 63 | display(sys.version) 64 | 65 | ``` 66 | 67 | The ` 159 | 160 | ``` 161 | 162 | Notice how different interpreters can be used with different 163 | configurations. 164 | 165 | 166 | -------------------------------------------------------------------------------- /docs/user-guide/index.md: -------------------------------------------------------------------------------- 1 | # The PyScript User Guide 2 | 3 | !!! info 4 | 5 | This guide provides technical guidance and exploration of the PyScript 6 | platform. 7 | 8 | While we endeavour to write clearly, some of the content in this user guide 9 | will not be suitable for beginners. We assume you already have Python 10 | or web development experience. If you're a beginner start with our 11 | [beginner's guide](../beginning-pyscript.md). 12 | 13 | We [welcome constructive feedback](https://github.com/pyscript/docs/issues). 14 | 15 | Our docs have three aims: 16 | 17 | 1. A [clear overview](what.md) of all things PyScript. 18 | 2. [Exploration of PyScript](architecture.md) in substantial technical detail. 19 | 3. Demonstration of the features of PyScript working together in 20 | [real-world example applications](../examples.md). 21 | 22 | _Read this user guide in full_: it is a short but comprehensive overview of the 23 | PyScript platform. 24 | 25 | Get involved! Join in the PyScript conversation on our 26 | [discord server](https://discord.gg/HxvBtukrg2). There you'll find core 27 | developers, community contributors and a flourishing forum for those creating 28 | projects with PyScript. Should you wish to engage with the development of 29 | PyScript, you are welcome to contribute via 30 | [the project's GitHub organisation](https://github.com/pyscript). 31 | 32 | Finally, the example projects referenced in our docs are all freely available 33 | and copiously commented on [pyscript.com](https://pyscript.com). 34 | 35 | !!! note 36 | 37 | Many of these examples come from contributors in our wonderful 38 | community. We love to recognise, share and celebrate the incredible work 39 | of folks in the PyScript community. If you believe you have a project that 40 | would make a good demonstration, please don't hesitate to 41 | [get in touch](https://discord.gg/HxvBtukrg2). 42 | -------------------------------------------------------------------------------- /docs/user-guide/media.md: -------------------------------------------------------------------------------- 1 | # PyScript and Media Devices 2 | 3 | For web applications to interact with cameras, microphones, and other media 4 | devices, there needs to be a way to access these hardware components through the 5 | browser. PyScript provides a media API that enables your Python code to interact 6 | with media devices directly from the browser environment. 7 | 8 | This section explains how PyScript interacts with media devices and how you can 9 | use these capabilities in your applications. 10 | 11 | ## Media Device Access 12 | 13 | PyScript interacts with media devices through the browser's [MediaDevices 14 | API](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices). This API 15 | provides access to connected media input devices like cameras and microphones, 16 | as well as output devices like speakers. 17 | 18 | When using PyScript's media API, it's important to understand: 19 | 20 | 1. Media access requires **explicit user permission**. The browser will show a 21 | permission dialog when your code attempts to access cameras or microphones. 22 | 2. Media access is only available in **secure contexts** (HTTPS or localhost). 23 | 3. All media interactions happen within the **browser's sandbox**, following the 24 | browser's security policies. 25 | 26 | ## The `pyscript.media` API 27 | 28 | PyScript provides a Pythonic interface to media devices through the 29 | `pyscript.media` namespace. This API includes two main components: 30 | 31 | 1. The `Device` class - represents a media device and provides methods to 32 | interact with it 33 | 2. The `list_devices()` function - discovers available media devices 34 | 35 | ### Listing Available Devices 36 | 37 | To discover what media devices are available, use the `list_devices()` function: 38 | 39 | ```python 40 | from pyscript.media import list_devices 41 | 42 | async def show_available_devices(): 43 | devices = await list_devices() 44 | for device in devices: 45 | print(f"Device: {device.label}, Type: {device.kind}, ID: {device.id}") 46 | 47 | # List all available devices 48 | show_available_devices() 49 | ``` 50 | 51 | This function returns a list of `Device` objects, each representing a media 52 | input or output device. Note that the browser will typically request permission 53 | before providing this information. 54 | 55 | ### Working with the Camera 56 | 57 | The most common use case is accessing the camera to display a video stream: 58 | 59 | ```python 60 | from pyscript import when 61 | from pyscript.media import Device 62 | from pyscript.web import page 63 | 64 | async def start_camera(): 65 | # Get a video stream (defaults to video only, no audio) 66 | stream = await Device.load(video=True) 67 | 68 | # Connect the stream to a video element in your HTML 69 | video_element = page["#camera"][0]._dom_element 70 | video_element.srcObject = stream 71 | 72 | return stream 73 | 74 | # Start the camera 75 | camera_stream = start_camera() 76 | ``` 77 | 78 | The `Device.load()` method is a convenient way to access media devices without 79 | first listing all available devices. You can specify options to control which 80 | camera is used: 81 | 82 | ```python 83 | # Prefer the environment-facing camera (often the back camera on mobile) 84 | stream = await Device.load(video={"facingMode": "environment"}) 85 | 86 | # Prefer the user-facing camera (often the front camera on mobile) 87 | stream = await Device.load(video={"facingMode": "user"}) 88 | 89 | # Request specific resolution 90 | stream = await Device.load(video={ 91 | "width": {"ideal": 1280}, 92 | "height": {"ideal": 720} 93 | }) 94 | ``` 95 | 96 | ### Capturing Images from the Camera 97 | 98 | To capture a still image from the video stream: 99 | 100 | ```python 101 | def capture_image(video_element): 102 | # Get the video dimensions 103 | width = video_element.videoWidth 104 | height = video_element.videoHeight 105 | 106 | # Create a canvas to capture the frame 107 | canvas = document.createElement("canvas") 108 | canvas.width = width 109 | canvas.height = height 110 | 111 | # Draw the current video frame to the canvas 112 | ctx = canvas.getContext("2d") 113 | ctx.drawImage(video_element, 0, 0, width, height) 114 | 115 | # Get the image as a data URL 116 | image_data = canvas.toDataURL("image/png") 117 | 118 | return image_data 119 | ``` 120 | 121 | For applications that need to process images with libraries like OpenCV, you 122 | need to convert the image data to a format these libraries can work with: 123 | 124 | ```python 125 | import numpy as np 126 | import cv2 127 | 128 | def process_frame_with_opencv(video_element): 129 | # Get video dimensions 130 | width = video_element.videoWidth 131 | height = video_element.videoHeight 132 | 133 | # Create a canvas and capture the frame 134 | canvas = document.createElement("canvas") 135 | canvas.width = width 136 | canvas.height = height 137 | ctx = canvas.getContext("2d") 138 | ctx.drawImage(video_element, 0, 0, width, height) 139 | 140 | # Get the raw pixel data 141 | image_data = ctx.getImageData(0, 0, width, height).data 142 | 143 | # Convert to numpy array for OpenCV 144 | frame = np.asarray(image_data, dtype=np.uint8).reshape((height, width, 4)) 145 | 146 | # Convert from RGBA to BGR (OpenCV's default format) 147 | frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGBA2BGR) 148 | 149 | # Process the image with OpenCV 150 | # ... 151 | 152 | return frame_bgr 153 | ``` 154 | 155 | ### Managing Camera Resources 156 | 157 | It's important to properly manage media resources, especially when your 158 | application no longer needs them. Cameras and microphones are shared resources, 159 | and failing to release them can impact other applications or cause unexpected 160 | behavior. 161 | 162 | ### Stopping the Camera 163 | 164 | To stop the camera and release resources: 165 | 166 | ```python 167 | from pyscript.web import page 168 | 169 | def stop_camera(stream): 170 | # Stop all tracks on the stream 171 | if stream: 172 | tracks = stream.getTracks() 173 | for track in tracks: 174 | track.stop() 175 | 176 | # Clear the video element's source 177 | video_element = page["#camera"][0]._dom_element 178 | if video_element: 179 | video_element.srcObject = None 180 | ``` 181 | 182 | ### Switching Between Cameras 183 | 184 | For devices with multiple cameras, you can implement camera switching: 185 | 186 | ```python 187 | from pyscript.media import Device, list_devices 188 | from pyscript.web import page 189 | 190 | class CameraManager: 191 | def __init__(self): 192 | self.cameras = [] 193 | self.current_index = 0 194 | self.active_stream = None 195 | self.video_element = page["#camera"][0]._dom_element 196 | 197 | async def initialize(self): 198 | # Get all video input devices 199 | devices = await list_devices() 200 | self.cameras = [d for d in devices if d.kind == "videoinput"] 201 | 202 | # Start with the first camera 203 | if self.cameras: 204 | await self.start_camera(self.cameras[0].id) 205 | 206 | async def start_camera(self, device_id=None): 207 | # Stop any existing stream 208 | await self.stop_camera() 209 | 210 | # Start a new stream 211 | video_options = ( 212 | {"deviceId": {"exact": device_id}} if device_id 213 | else {"facingMode": "environment"} 214 | ) 215 | self.active_stream = await Device.load(video=video_options) 216 | 217 | # Connect to the video element 218 | if self.video_element: 219 | self.video_element.srcObject = self.active_stream 220 | 221 | async def stop_camera(self): 222 | if self.active_stream: 223 | tracks = self.active_stream.getTracks() 224 | for track in tracks: 225 | track.stop() 226 | self.active_stream = None 227 | 228 | if self.video_element: 229 | self.video_element.srcObject = None 230 | 231 | async def switch_camera(self): 232 | if len(self.cameras) <= 1: 233 | return 234 | 235 | # Move to the next camera 236 | self.current_index = (self.current_index + 1) % len(self.cameras) 237 | await self.start_camera(self.cameras[self.current_index].id) 238 | ``` 239 | 240 | ## Working with Audio 241 | 242 | In addition to video, the PyScript media API can access audio inputs: 243 | 244 | ```python 245 | # Get access to the microphone (audio only) 246 | audio_stream = await Device.load(audio=True, video=False) 247 | 248 | # Get both audio and video 249 | av_stream = await Device.load(audio=True, video=True) 250 | ``` 251 | 252 | ## Best Practices 253 | 254 | When working with media devices in PyScript, follow these best practices: 255 | 256 | ### Permissions and User Experience 257 | 258 | 1. **Request permissions contextually**: 259 | - Only request camera/microphone access when needed 260 | - Explain to users why you need access before requesting it 261 | - Provide fallback options when permissions are denied 262 | 263 | 2. **Clear user feedback**: 264 | - Indicate when the camera is active 265 | - Provide controls to pause/stop the camera 266 | - Show loading states while the camera is initializing 267 | 268 | ### Resource Management 269 | 270 | 1. **Always clean up resources**: 271 | - Stop media tracks when they're not needed 272 | - Clear `srcObject` references from video elements 273 | - Be especially careful in single-page applications 274 | 275 | 2. **Handle errors gracefully**: 276 | - Catch exceptions when requesting media access 277 | - Provide meaningful error messages 278 | - Offer alternatives when media access fails 279 | 280 | ### Performance Optimization 281 | 282 | 1. **Match resolution to needs**: 283 | - Use lower resolutions when possible 284 | - Consider mobile device limitations 285 | - Adjust video constraints based on the device 286 | 287 | 2. **Optimize image processing**: 288 | - Process frames on demand rather than continuously 289 | - Use efficient algorithms 290 | - Consider downsampling for faster processing 291 | 292 | ## Example Application: Simple Camera Capture 293 | 294 | Here's a simplified example that shows how to capture and display images from a 295 | camera: 296 | 297 | ```python 298 | from pyscript import when, window 299 | from pyscript.media import Device 300 | from pyscript.web import page 301 | 302 | class CameraCapture: 303 | def __init__(self): 304 | # Get UI elements 305 | self.video = page["#camera"][0] 306 | self.video_element = self.video._dom_element 307 | self.capture_button = page["#capture-button"] 308 | self.snapshot = page["#snapshot"][0] 309 | 310 | # Start camera 311 | self.initialize_camera() 312 | 313 | async def initialize_camera(self): 314 | # Prefer environment-facing camera on mobile devices 315 | stream = await Device.load(video={"facingMode": "environment"}) 316 | self.video_element.srcObject = stream 317 | 318 | def take_snapshot(self): 319 | """Capture a frame from the camera and display it""" 320 | # Get video dimensions 321 | width = self.video_element.videoWidth 322 | height = self.video_element.videoHeight 323 | 324 | # Create canvas and capture frame 325 | canvas = window.document.createElement("canvas") 326 | canvas.width = width 327 | canvas.height = height 328 | 329 | # Draw the current video frame to the canvas 330 | ctx = canvas.getContext("2d") 331 | ctx.drawImage(self.video_element, 0, 0, width, height) 332 | 333 | # Convert the canvas to a data URL and display it 334 | image_data_url = canvas.toDataURL("image/png") 335 | self.snapshot.setAttribute("src", image_data_url) 336 | 337 | # HTML structure needed: 338 | # 339 | # 340 | # 341 | 342 | # Usage: 343 | # camera = CameraCapture() 344 | # 345 | # @when("click", "#capture-button") 346 | # def handle_capture(event): 347 | # camera.take_snapshot() 348 | ``` 349 | 350 | This example demonstrates: 351 | - Initializing a camera with the PyScript media API 352 | - Accessing the camera stream and displaying it in a video element 353 | - Capturing a still image from the video stream when requested 354 | - Converting the captured frame to an image that can be displayed 355 | 356 | This simple pattern can serve as the foundation for various camera-based 357 | applications and can be extended with image processing libraries as needed for 358 | more complex use cases. 359 | 360 | 361 | ## Conclusion 362 | 363 | The PyScript media API provides a powerful way to access and interact with 364 | cameras and microphones directly from Python code running in the browser. By 365 | following the patterns and practices outlined in this guide, you can build 366 | sophisticated media applications while maintaining good performance and user 367 | experience. 368 | 369 | Remember that media access is a sensitive permission that requires user consent 370 | and should be used responsibly. Always provide clear indications when media 371 | devices are active and ensure proper cleanup of resources when they're no longer 372 | needed. 373 | -------------------------------------------------------------------------------- /docs/user-guide/offline.md: -------------------------------------------------------------------------------- 1 | # Use PyScript Offline 2 | 3 | Sometimes you want to run PyScript applications offline. 4 | 5 | Both PyScript core and the interpreter used to run code need to be served with 6 | the application itself. The two requirements needed to create an offline 7 | version of PyScript are: 8 | 9 | 1. Download and include PyScript core. 10 | 2. Download and include the Python interpreters used in your application. 11 | 12 | ## Get PyScript core 13 | 14 | You have two choices: 15 | 16 | 1. **Build from source**. Clone the repository, install dependencies, then 17 | build and use the content found in the `./dist/` folder. 18 | 2. **Grab the npm package**. For simplicity this is the method we currently 19 | recommend as the easiest to get started. 20 | 21 | In the following instructions, we assume the existence of a folder called 22 | `pyscript-offline`. All the necessary files needed to use PyScript offline will 23 | eventually find their way in there. 24 | 25 | In your computer's command line shell, create the `pyscript-offline` folder 26 | like this: 27 | 28 | ```sh 29 | mkdir -p pyscript-offline 30 | ``` 31 | 32 | Now change into the newly created directory: 33 | 34 | ```sh 35 | cd pyscript-offline 36 | ``` 37 | 38 | ### PyScipt core from source 39 | 40 | Build PyScript core by cloning the project repository and follow the 41 | instructions in our [developer guide](../developers.md) 42 | 43 | Once completed, copy the `dist` folder, that has been created by the build 44 | step, into your `pyscript-offline` folder. 45 | 46 | ### PyScript core from `npm` 47 | 48 | Ensure you are in the `pyscript-offline` folder created earlier. 49 | 50 | Create a `package.json` file. Even an empty one with just `{}` as content will 51 | suffice. This is needed to make sure our folder will include the local 52 | `npm_modules` folder instead of placing assets elsewhere. Our aim is to ensure 53 | everything is in the same place locally. 54 | 55 | ```sh 56 | # only if there is no package.json, create one 57 | echo '{}' > ./package.json 58 | ``` 59 | 60 | Assuming you have 61 | [npm installed on your computer](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm), 62 | issue the following command in the `pyscript-offline` folder to install the 63 | PyScript core package. 64 | 65 | ``` 66 | # install @pyscript/core 67 | npm i @pyscript/core 68 | ``` 69 | 70 | Now the folder should contain a `node_module` folder in it, and we can copy the 71 | `dist` folder found within the `@pyscript/core` package wherever we like. 72 | 73 | ```sh 74 | # create a public folder to serve locally 75 | mkdir -p public 76 | 77 | # move @pyscript/core dist into such folder 78 | cp -R ./node_modules/@pyscript/core/dist ./public/pyscript 79 | ``` 80 | 81 | That's almost it! 82 | 83 | ## Set up your application 84 | 85 | Simply create a `./public/index.html` file that loads the local PyScript: 86 | 87 | ```html 88 | 89 | 90 | 91 | 92 | 93 | PyScript Offline 94 | 95 | 96 | 97 | 98 | 103 | 104 | 105 | ``` 106 | 107 | Run this project directly (after being sure that `index.html` file is saved 108 | into the `public` folder): 109 | 110 | ```sh 111 | python3 -m http.server -d ./public/ 112 | ``` 113 | 114 | If you would like to test `worker` features, try instead: 115 | 116 | ```sh 117 | npx mini-coi ./public/ 118 | ``` 119 | 120 | ## Download a local interpreter 121 | 122 | PyScript officially supports *MicroPython* and *Pyodide* interpreters, so let's 123 | see how to get a local copy for each one of them. 124 | 125 | ### Local MicroPython 126 | 127 | Similar to `@pyscript/core`, we can also install *MicroPython* from *npm*: 128 | 129 | ```sh 130 | npm i @micropython/micropython-webassembly-pyscript 131 | ``` 132 | 133 | Our `node_modules` folder should contain a `@micropython` one and from there we 134 | can move relevant files into our `public` folder. 135 | 136 | Let's be sure we have a target for that: 137 | 138 | ```sh 139 | # create a folder in our public space 140 | mkdir -p ./public/micropython 141 | 142 | # copy related files into such folder 143 | cp ./node_modules/@micropython/micropython-webassembly-pyscript/micropython.* ./public/micropython/ 144 | ``` 145 | 146 | The folder should contain at least both `micropython.mjs` and 147 | `micropython.wasm` files. These are the files to use locally via a dedicated 148 | config. 149 | 150 | ```html 151 | 152 | 153 | 154 | 155 | 156 | PyScript Offline 157 | 158 | 159 | 160 | 161 | 162 | interpreter = "/micropython/micropython.mjs" 163 | 164 | 169 | 170 | 171 | ``` 172 | 173 | ### Local Pyodide 174 | 175 | Remember, Pyodide uses `micropip` to install third party packages. While the 176 | procedure for offline Pyodide is very similar to the one for MicroPython, 177 | if we want to use 3rd party packages we also need to have these available 178 | locally. We'll start simple and cover such packaging issues at the end. 179 | 180 | ```sh 181 | # locally install the pyodide module 182 | npm i pyodide 183 | 184 | # create a folder in our public space 185 | mkdir -p ./public/pyodide 186 | 187 | # move all necessary files into that folder 188 | cp ./node_modules/pyodide/pyodide* ./public/pyodide/ 189 | cp ./node_modules/pyodide/python_stdlib.zip ./public/pyodide/ 190 | ``` 191 | 192 | Please **note** that the `pyodide-lock.json` file is needed, so please don't 193 | change that `cp` operation as all `pyodide*` files need to be moved. 194 | 195 | At this point, all we need to do is to change the configuration on our *HTML* 196 | page to use *pyodide* instead: 197 | 198 | ```html 199 | 200 | 201 | 202 | 203 | 204 | PyScript Offline 205 | 206 | 207 | 208 | 209 | 210 | interpreter = "/pyodide/pyodide.mjs" 211 | 212 | 217 | 218 | 219 | ``` 220 | 221 | ## Wrap up 222 | 223 | That's basically it! 224 | 225 | Disconnect from the internet, run the local server, and the page will still 226 | show that very same `Hello from PyScript` message. 227 | 228 | ## Local Pyodide packages 229 | 230 | Finally, we need the ability to install Python packages from a local source 231 | when using Pyodide. 232 | 233 | Put simply, we use the packages bundle from 234 | [pyodide releases](https://github.com/pyodide/pyodide/releases/tag/0.26.2). 235 | 236 | !!! warning 237 | 238 | This bundle is more than 200MB! 239 | 240 | It contains each package that is required by Pyodide, and Pyodide will only 241 | load packages when needed. 242 | 243 | Once downloaded and extracted (we're using version `0.26.2` in this example), 244 | we can simply copy the files and folders inside the `pyodide-0.26.2/pyodide/*` 245 | directory into our `./public/pyodide/*` folder. 246 | 247 | Feel free to either skip or replace the content, or even directly move the 248 | `pyodide` folder inside our `./public/` one. 249 | 250 | Now use any package available in via the Pyodide bundle. 251 | 252 | For example: 253 | 254 | ```html 255 | 256 | 257 | 258 | 259 | 260 | PyScript Offline 261 | 262 | 263 | 264 | 265 | 266 | interpreter = "/pyodide/pyodide.mjs" 267 | packages = ["pandas"] 268 | 269 | 276 | 277 | 278 | ``` 279 | 280 | We should now be able to read `[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]` on the 281 | page *even* if we disconnect from the Internet. 282 | 283 | That's it! 284 | -------------------------------------------------------------------------------- /docs/user-guide/plugins.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | 3 | PyScript offers a plugin API _so anyone can extend its functionality and 4 | share their modifications_. 5 | 6 | PyScript only supports plugins written in Javascript (although causing the 7 | evaluation of bespoke Python code can be a part of such plugins). The plugin's 8 | JavaScript code should be included on the web page via a 9 | ` 199 | 200 | 201 | 202 | 203 | 204 | 207 | 208 | 209 | ``` 210 | -------------------------------------------------------------------------------- /docs/user-guide/pygame-ce.md: -------------------------------------------------------------------------------- 1 | # PyGame Support 2 | 3 | !!! Danger 4 | 5 | **Support for PyGame-CE is experimental** and its behaviour is likely to 6 | change as we get feedback and bug reports from the community. 7 | 8 | Please bear this in mind as you try PyGame-CE with PyScript, and all 9 | feedback, bug reports and constructive critique is welcome via discord 10 | or GitHub. 11 | 12 | 13 | [PyGameCE](https://pyga.me/) is a Python library for building powerful games 14 | (so says their website). They also say, to get started you just need to 15 | `pip install pygame-ce`. 16 | 17 | Thanks to work in the upstream [Pyodide project](https://pyodide.org/) 18 | PyGame-CE is available in PyScript and to get started all you need to do is: 19 | `` Now you don't even need to 20 | `pip install` the library! It comes with PyScript by default, and you can share 21 | your games via a URL! 22 | 23 | !!! Info 24 | 25 | Please refer to 26 | [PyGame-CE's extensive documentation](https://pyga.me/docs/) for how to 27 | create a game. Some things may not work because we're running in a 28 | browser context, but play around and let us know how you get on. 29 | 30 | ## How it works 31 | 32 | When a `` element is found on the page a 33 | Pyodide instance is bootstrapped with the `pygame-ce` package already included. 34 | Differently from other scripts, `py-game` cannot currently work through a 35 | worker and it uses an optional target attribute to define the `` 36 | element id that will be used to render the game. If no target attribute is 37 | defined, the script assumes there is a `` element already 38 | on the page. 39 | 40 | A config attribute can be specified to add extra packages but right now that's 41 | all it can do. 42 | 43 | !!! Info 44 | 45 | Sometimes you need to gather text based user input when starting a game. 46 | The usual way to do this is via the builtin `input` function. 47 | 48 | Because PyGame-CE **only runs on the main thread**, the only way to block 49 | your code while it waits for user `input` is to use a 50 | [JavaScript prompt](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) 51 | instead of input typed in via a terminal. PyScript handles this 52 | automatically for you if you use the `input` function. 53 | 54 | This is an experiment, but: 55 | 56 | * It is possible to use regular PyScript to load the pygame-ce package and use 57 | all the other features. But there be dragons! This helper simply allows 58 | multiple games on a page and forces game logic to run on the main thread to 59 | reduce confusion around attributes and features when the `pygame-ce` package 60 | is meant to be used. Put simply, we make it relatively safe and easy to use. 61 | * The fact `pygame-ce` is the default "game engine" does not mean in the future 62 | PyScript won't have other engines also available. 63 | * Once again, as this is an experiment, we welcome any kind of feedback, 64 | suggestions, hints on how to improve or reports of what's missing. 65 | 66 | Other than that, please go make and share wonderful games. We can't wait to see 67 | what you come up with. 68 | -------------------------------------------------------------------------------- /docs/user-guide/running-offline.md: -------------------------------------------------------------------------------- 1 | # Running PyScript Offline 2 | 3 | Although users will want to create and share PyScript apps on the internet, there are cases when user want to run PyScript applications offline, in an airgapped fashion. This means that both PyScript core and the interpreter used to run code need to be served with the application itself. In short, the 2 main explicit tasks needed to create an offline PyScript application are: 4 | 5 | * download and include PyScript core (`core.js`) 6 | * download and include the [Python] interpreters you want to use in your Application 7 | 8 | ## Downloading and Including PyScript's `core.js` 9 | 10 | There are at least 2 ways to use PyScript offline: 11 | 12 | * by **cloning the repository**, then building and installing dependencies and then run and then reach the `./dist/` folder 13 | * by **grabbing the npm package** which for simplicity sake will be the method used here at least until we find a better place to *pin* our *dist* folder via our CDN and make the procedure even easier than it is now 14 | 15 | In the examples below, we'll assume we are creating a PyScript Application folder called `pyscript-offline` and we'll add all the necessary files to the folder. 16 | 17 | First of all, we are going to create a `pyscript-offline` folder as reference. 18 | 19 | ```sh 20 | mkdir -p pyscript-offline 21 | cd pyscript-offline 22 | ``` 23 | 24 | ### Adding core by Cloning the Repository 25 | 26 | You can build all the PyScript Core files by cloning the project repository and building them yourself. To do so, build the files by following the instructions in our [developer guide](/developers) 27 | 28 | Once you've run the `build` command, copy the `build` folder that has been created into your `pyscript-offline` folder. 29 | 30 | ### Adding core by Installing `@pyscript/core` Locally 31 | 32 | First of all, ensure you are in the folder you would like to test PyScirpt locally. In this case, the `pyscript-offline` folder we created earlier. 33 | 34 | Once within the folder, be sure there is a `package.json` file. Even an empty one with just `{}` as content would work. 35 | This is needed to be sure the folder will include locally the `npm_modules` folder instead of placing the package in the parent folder, if any. 36 | 37 | ```sh 38 | # only if there is no package.json, create one 39 | echo '{}' > ./package.json 40 | 41 | # install @pyscript/core 42 | npm i @pyscript/core 43 | ``` 44 | 45 | At this point the folder should contain a `node_module` in it and we can actually copy its `dist` folder wherever we like. 46 | 47 | ```sh 48 | # create a public folder to serve locally 49 | mkdir -p public 50 | 51 | # move @pyscript/core dist into such folder 52 | cp -R ./node_modules/@pyscript/core/dist ./public/pyscript 53 | ``` 54 | 55 | ## Setting up your application 56 | 57 | Once you've added PyScript code following one of the methods above, that's almost it! We are half way through our goal but we can already create a `./public/index.html` file that loads the project: 58 | 59 | ```html 60 | 61 | 62 | 63 | 64 | 65 | PyScript Offline 66 | 67 | 68 | 69 | 70 | 75 | 76 | 77 | ``` 78 | 79 | To run this project directly, after being sure that `index.html` file is saved into the `public` folder, you can try: 80 | 81 | ```sh 82 | python3 -m http.server -d ./public/ 83 | ``` 84 | 85 | Alternatively, if you would like to test also `worker` features, you can try instead: 86 | 87 | ```sh 88 | npx static-handler --coi ./public/ 89 | ``` 90 | ## Downloading and Setting up a Local Interpreter 91 | 92 | Good news! We are almost there. Now that we've: 93 | 94 | * downloaded PyScript locally 95 | * created the skeleton of an initial PyScript App 96 | 97 | we need to download and setup up an interpreter. PyScript officially supports *MicroPython* and *Pyodide* interpreters, so let's see how to do that for each one of them. 98 | 99 | ### Download MicroPython locally 100 | 101 | Similarly to what we did for `@pyscript/core`, we can also install *MicroPython* from *npm*: 102 | 103 | ```sh 104 | npm i @micropython/micropython-webassembly-pyscript 105 | ``` 106 | 107 | Our `node_modules` folder now should contain a `@micropython` one and from there we can move relevant files into our `public` folder, but let's be sure we have a target for that: 108 | 109 | ```sh 110 | # create a folder in our public space 111 | mkdir -p ./public/micropython 112 | 113 | # copy related files into such folder 114 | cp ./node_modules/@micropython/micropython-webassembly-pyscript/micropython.* ./public/micropython/ 115 | ``` 116 | 117 | That folder should contain at least both `micropython.mjs` and `micropython.wasm` files and these are the files we are going to use locally via our dedicated config. 118 | 119 | ```html 120 | 121 | 122 | 123 | 124 | 125 | PyScript Offline 126 | 127 | 128 | 129 | 130 | 131 | interpreter = "/micropython/micropython.mjs" 132 | 133 | 138 | 139 | 140 | ``` 141 | 142 | ### Install Pyodide locally 143 | 144 | Currently there is a difference between MicroPython and Pyodide: the former does not have (*yet*) a package manager while the latest does, it's called *micropip*. 145 | 146 | This is important to remember because while the procedure to have *pyodide* offline is very similar to the one we've just seen, if we want to use also 3rd party packages we also need to have these running locally ... but let's start simple: 147 | 148 | ```sh 149 | # install locally the pyodide module 150 | npm i pyodide 151 | 152 | # create a folder in our public space 153 | mkdir -p ./public/pyodide 154 | 155 | # move all necessary files into that folder 156 | cp ./node_modules/pyodide/pyodide* ./public/pyodide/ 157 | cp ./node_modules/pyodide/python_stdlib.zip ./public/pyodide/ 158 | ``` 159 | 160 | Please **note** that also `pyodide-lock.json` file is needed so please don't change that `cp` operation as all `pyodide*` files need to be moved. 161 | 162 | At this point, all we need to do is to change our *HTML* page to use *pyodide* instead: 163 | 164 | ```html 165 | 166 | 167 | 168 | 169 | 170 | PyScript Offline 171 | 172 | 173 | 174 | 175 | 176 | interpreter = "/pyodide/pyodide.mjs" 177 | 178 | 183 | 184 | 185 | ``` 186 | 187 | ## Wrapping it up 188 | 189 | We are basically done! If we try to disconnect from the internet but we still run our local server, the page will still show that very same *Hello from PyScript* message :partying_face: 190 | 191 | We can now drop internet, still keeping the local server running, and everything should be fine :partying_face: 192 | 193 | ## Local Pyodide Packages 194 | 195 | There's one last thing that users are probably going to need: the ability to install Python packages when using Pyodide. 196 | 197 | In order to have also 3rd party packages available, we can use the bundle from [pyodide releases](https://github.com/pyodide/pyodide/releases/tag/0.24.1) that contains also packages. 198 | 199 | Please note this bundle is more than 200MB: it not downloaded all at once, it contains each package that is required and it loads only related packages when needed. 200 | 201 | Once downloaded and extracted, where in this case I am using `0.24.1` as reference bundle, we can literally copy and paste, or even move, all those files and folders inside the `pyodide-0.24.1/pyodide/*` directory into our `./public/pyodide/*` folder. 202 | 203 | As the bundle contains files already present, feel free to either skip or replace the content, or even directly move that *pyodide* folder inside our `./public/` one. 204 | 205 | Once it's done, we can now use any package we like that is available in *pyodide*. Let's see an example: 206 | 207 | ```html 208 | 209 | 210 | 211 | 212 | 213 | PyScript Offline 214 | 215 | 216 | 217 | 218 | 219 | interpreter = "/pyodide/pyodide.mjs" 220 | packages = ["pandas"] 221 | 222 | 229 | 230 | 231 | ``` 232 | 233 | If everything went fine, we should now be able to read `[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]` on the page *even* if we disconnect from the Internet. 234 | 235 | And **that's all folks** :wave: -------------------------------------------------------------------------------- /docs/user-guide/terminal.md: -------------------------------------------------------------------------------- 1 | # Terminal 2 | 3 | In conventional (non-browser based) Python, it is common to run scripts from 4 | the terminal, or to interact directly with the Python interpreter via the 5 | [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop). 6 | It's to the terminal that `print` writes characters (via `stdout`), and it's 7 | from the terminal that the `input` reads characters (via `stdin`). 8 | 9 | It usually looks something like this: 10 | 11 | 12 | 13 | Because of the historic importance of the use of a terminal, PyScript makes one 14 | available in the browser (based upon [XTerm.js](https://xtermjs.org/)). 15 | As [mentioned earlier](first-steps.md), PyScript's built-in terminal is 16 | activated with the `terminal` flag when using the ` 28 | ``` 29 | 30 | The end result will look like this (the rectangular box indicates the current 31 | position of the cursor): 32 | 33 | 34 | 35 | Should you need an interactive terminal, for example because you use the 36 | `input` statement that requires the user to type things into the terminal, you 37 | **must ensure your code is run on a worker**: 38 | 39 | ```html 40 | 44 | ``` 45 | 46 | 47 | To use the interactive Python REPL in the terminal, use Python's 48 | [code](https://docs.python.org/3/library/code.html) module like this: 49 | 50 | ```python 51 | import code 52 | 53 | code.interact() 54 | ``` 55 | 56 | The end result should look something like this: 57 | 58 | 59 | 60 | Finally, it is possible to dynamically pass Python code into the terminal. The 61 | trick is to get a reference to the terminal created by PyScript. Thankfully, 62 | this is very easy. 63 | 64 | Consider this fragment: 65 | 66 | ```html 67 | 68 | ``` 69 | 70 | Get a reference to the element, and just call the `process` method on 71 | that object: 72 | 73 | ```JS 74 | const myterm = document.querySelector("#my_script"); 75 | await myterm.process('print("Hello world!")'); 76 | ``` 77 | 78 | ## XTerm reference 79 | 80 | Each terminal has a reference to the 81 | [Terminal](https://xtermjs.org/docs/api/terminal/classes/terminal/) 82 | instance used to bootstrap the current terminal. 83 | 84 | On the JavaScript side, it's a `script.terminal` property while on the Python 85 | side, it's a `__terminal__` special reference that guarantees to provide the 86 | very same `script.terminal`: 87 | 88 | ```html title="How to reach the XTerm Terminal" 89 | 99 | ``` 100 | 101 | ### Clear the terminal 102 | 103 | It's very simple to clear a PyTerminal: 104 | 105 | ```html title="Clearing the terminal" 106 | 112 | ``` 113 | 114 | ### Resize the terminal 115 | 116 | The terminal takes up a fair amount of room onscreen. It can be resized to use less. 117 | Here it is 10 lines high. 118 | ```python title="Resizing the terminal in python" 119 | if '__terminal__' in locals(): # has a terminal been created 120 | __terminal__.resize(60, 10) # (width, height) 121 | ``` 122 | 123 | ### Terminal colors 124 | 125 | Colors and most special characters work so you can make the text **bold** or 126 | turn it green. You could even use a control 127 | character to `print('\033[2J')` and clear the terminal, instead of using the 128 | exposed `clear()` method: 129 | 130 | ```html title="Terminal colors" 131 | 136 | ``` 137 | 138 | ### Terminal addons 139 | 140 | It's possible [use XTerm.js addons](https://xtermjs.org/docs/guides/using-addons/): 141 | 142 | ```html title="Terminal addons" 143 | 144 | [js_modules.main] 145 | "https://cdn.jsdelivr.net/npm/@xterm/addon-web-links/+esm" = "weblinks" 146 | 147 | 155 | ``` 156 | 157 | By default we enable the `WebLinksAddon` addon (so URLs displayed in the 158 | terminal automatically become links). Behind the scenes is the example code 159 | shown above, and this approach will work for 160 | [any other addon](https://github.com/xtermjs/xterm.js/tree/master/addons/) you 161 | may wish to use. 162 | 163 | ### MicroPython 164 | 165 | MicroPython has a 166 | [very complete REPL](https://docs.micropython.org/en/latest/reference/repl.html) 167 | already built into it. 168 | 169 | * All `Ctrl+X` strokes are handled, including paste mode and kill switches. 170 | * History works out of the box. Access this via the up and down arrows to 171 | view your command history. 172 | * Tab completion works like a charm. Use the `tab` key to see available 173 | variables or objects in `globals`. 174 | * Copy and paste is much improved. This is true for a single terminal entry, 175 | or a 176 | [paste mode](https://docs.micropython.org/en/latest/reference/repl.html#paste-mode) 177 | enabled variant. 178 | 179 | As a bonus, the MicroPython terminal works on both the main thread and in 180 | web workers, with the following caveats: 181 | 182 | * **Main thread:** 183 | * Calls to the blocking `input` function are delegated to the native browser 184 | based 185 | [prompt](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) 186 | utility. 187 | * There are no guards against blocking code (e.g. `while True:` loops). 188 | Such blocking code _could freeze your page_. 189 | * **Web worker:** 190 | * Conventional support for the `input` function, without blocking the main 191 | thread. 192 | * Blocking code (e.g. `while True:` loops) does not block the main thread 193 | and your page will remain responsive. 194 | 195 | We encourage the usage of `worker` attribute to bootstrap a MicroPython 196 | terminal. But now you have an option to run the terminal in the main thread. 197 | Just remember not to block! 198 | -------------------------------------------------------------------------------- /docs/user-guide/what.md: -------------------------------------------------------------------------------- 1 | # What is PyScript? 2 | 3 | [PyScript](https://pyscript.net) is an 4 | [open source](../../license/) platform for 5 | [Python](https://python.org) in the 6 | [browser](https://en.wikipedia.org/wiki/Web_browser). 7 | 8 | PyScript brings together two of the most vibrant technical ecosystems on the 9 | planet. If [the web](https://en.wikipedia.org/wiki/World_Wide_Web) and Python 10 | had a baby, you'd get PyScript. 11 | 12 | PyScript works because modern browsers support 13 | [WebAssembly](https://webassembly.org/) (abbreviated to WASM) - an 14 | [instruction set](https://en.wikipedia.org/wiki/Instruction_set_architecture) 15 | for a [virtual machine](https://en.wikipedia.org/wiki/Virtual_machine) with 16 | an open specification and near native performance. PyScript takes 17 | versions of the Python interpreter compiled to WASM, and makes them easy to use 18 | inside the browser. 19 | 20 | At the core of PyScript is a _philosophy of digital empowerment_. The web is 21 | the world's most ubiquitous computing platform, mature and familiar to billions 22 | of people. Python is one of the 23 | [world's most popular programming languages](https://spectrum.ieee.org/the-top-programming-languages-2023): 24 | it is easy to teach and learn, used in a plethora of existing domains 25 | (such as data science, games, embedded systems, artificial intelligence, 26 | finance, physics and film production - to name but a few), and the Python 27 | ecosystem contains a huge number of popular and powerful libraries to address 28 | its many uses. 29 | 30 | PyScript brings together the ubiquity, familiarity and accessibility of the web 31 | with the power, depth and expressiveness of Python. It means PyScript isn't 32 | just for programming experts but, as we like to say, for the 99% of the rest of 33 | the planet who use computers. 34 | 35 | -------------------------------------------------------------------------------- /docs/user-guide/workers.md: -------------------------------------------------------------------------------- 1 | # Workers 2 | 3 | Workers run code that won't block the "main thread" controlling the user 4 | interface. If you block the main thread, your web page becomes annoyingly 5 | unresponsive. **You should never block the main thread.** 6 | 7 | Happily, PyScript makes it very easy to use workers and uses a feature recently 8 | added to web standards called 9 | [Atomics](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics). 10 | **You don't need to know about Atomics to use web workers**, but it's useful to 11 | know that the underlying [coincident library](architecture.md#coincident) 12 | uses it under the hood. 13 | 14 | !!! info 15 | 16 | Sometimes you only need to `await` in the main thread on a method in a 17 | worker when neither `window` nor `document` are referenced in the code 18 | running on the worker. 19 | 20 | In these cases, you don't need any special header or service worker 21 | as long as **the method exposed from the worker returns a serializable 22 | result**. 23 | 24 | ## HTTP headers 25 | 26 | To use the `window` and `document` objects from within a worker (i.e. use 27 | synchronous Atomics) **you must ensure your web server enables the following 28 | headers** (this is the default behavior for 29 | [pyscript.com](https://pyscript.com)): 30 | 31 | ``` 32 | Access-Control-Allow-Origin: * 33 | Cross-Origin-Opener-Policy: same-origin 34 | Cross-Origin-Embedder-Policy: require-corp 35 | Cross-Origin-Resource-Policy: cross-origin 36 | ``` 37 | 38 | If you're unable to configure your server's headers, you have two options: 39 | 40 | 1. Use the [mini-coi](https://github.com/WebReflection/mini-coi#readme) project 41 | to enforce headers. 42 | 2. Use the `service-worker` attribute with the `script` element. 43 | 44 | ### Option 1: mini-coi 45 | 46 | For performance reasons, this is the preferred option so Atomics works at 47 | native speed. 48 | 49 | The simplest way to use mini-coi is to copy the 50 | [mini-coi.js](https://raw.githubusercontent.com/WebReflection/mini-coi/main/mini-coi.js) 51 | file content and save it in the root of your website (i.e. `/`), and reference 52 | it as the first child tag in the `` of your HTML documents: 53 | 54 | ```html 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ``` 63 | 64 | ### Option 2: `service-worker` attribute 65 | 66 | This allows you to slot in a custom 67 | [service worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) 68 | to handle requirements for synchronous operations. 69 | 70 | Each ` 97 | 98 | 99 | ``` 100 | 101 | !!! warning 102 | 103 | Using sabayon as the fallback for synchronous operations via Atomics 104 | should be **the last solution to consider**. It is inevitably 105 | slower than using native Atomics. 106 | 107 | If you must use sabayon, always reduce the amount of synchronous 108 | operations by caching references from the *main* thread. 109 | 110 | ```python 111 | # ❌ THIS IS UNNECESSARILY SLOWER 112 | from pyscript import document 113 | 114 | # add a data-test="not ideal attribute" 115 | document.body.dataset.test = "not ideal" 116 | # read a data-test attribute 117 | print(document.body.dataset.test) 118 | 119 | # - - - - - - - - - - - - - - - - - - - - - 120 | 121 | # ✔️ THIS IS FINE 122 | from pyscript import document 123 | 124 | # if needed elsewhere, reach it once 125 | body = document.body 126 | dataset = body.dataset 127 | 128 | # add a data-test="not ideal attribute" 129 | dataset.test = "not ideal" 130 | # read a data-test attribute 131 | print(dataset.test) 132 | ``` 133 | 134 | In latter example the number of operations has been reduced from six to just 135 | four. The rule of thumb is: _if you ever need a DOM reference more than once, 136 | cache it_. 👍 137 | 138 | 139 | ## Start working 140 | 141 | To start your code in a worker, simply ensure the ` 147 | ``` 148 | 149 | You may also want to add a `name` attribute to the tag, so you can use 150 | `pyscript.workers` in the main thread to retrieve a reference to the worker: 151 | 152 | ```html 153 | 154 | ``` 155 | 156 | ```python 157 | from pyscript import workers 158 | 159 | my_worker = await workers["my-worker"] 160 | ``` 161 | 162 | Alternatively, to launch a worker from within Python running on the main thread 163 | use the [pyscript.PyWorker](../../api/#pyscriptpyworker) class and you must 164 | reference both the target Python script and interpreter type: 165 | 166 | ```python title="Launch a worker from within Python" 167 | from pyscript import PyWorker 168 | 169 | # The type MUST be given and can be either `micropython` or `pyodide` 170 | my_worker = PyWorker("my-worker-code.py", type="micropython") 171 | ``` 172 | 173 | ## Worker interactions 174 | 175 | Code running in the worker needs to be able to interact with code running in 176 | the main thread and perhaps have access to the web page. This is achieved via 177 | some helpful [builtin APIs](../../api). 178 | 179 | !!! note 180 | 181 | For ease of use, the worker related functionality in PyScript is 182 | a simpler presentation of more sophisticated and powerful behaviour 183 | available via PolyScript. 184 | 185 | **If you are a confident advanced user**, please 186 | [consult the XWorker](https://pyscript.github.io/polyscript/#xworker) 187 | related documentation from the PolyScript project for how to make use of 188 | these features. 189 | 190 | To synchronise serializable data between the worker and the main thread use 191 | [the `sync` function](../../api/#pyscriptsync) in the worker to reference a 192 | function registered on the main thread: 193 | 194 | ```python title="Python code running on the main thread." 195 | from pyscript import PyWorker 196 | 197 | def hello(name="world"): 198 | return(f"Hello, {name}") 199 | 200 | # Create the worker. 201 | worker = PyWorker("./worker.py", type="micropython") 202 | 203 | # Register the hello function as callable from the worker. 204 | worker.sync.hello = hello 205 | ``` 206 | 207 | ```python title="Python code in the resulting worker." 208 | from pyscript import sync, window 209 | 210 | greeting = sync.hello("PyScript") 211 | window.console.log(greeting) 212 | ``` 213 | 214 | Alternatively, for the main thread to call functions in a worker, specify the 215 | functions in a `__export__` list: 216 | 217 | ```python title="Python code on the worker." 218 | import sys 219 | 220 | def version(): 221 | return sys.version 222 | 223 | # Define what to export to the main thread. 224 | __export__ = ["version", ] 225 | ``` 226 | 227 | Then ensure you have a reference to the worker in the main thread (for 228 | instance, by using the `pyscript.workers`): 229 | 230 | ```html title="Creating a named worker in the web page." 231 | 232 | ``` 233 | 234 | ```python title="Referencing and using the worker from the main thread." 235 | from pyscript import workers 236 | 237 | my_worker = await workers["my-worker"] 238 | 239 | print(await my_worker.version()) 240 | ``` 241 | 242 | The values passed between the main thread and the worker **must be 243 | serializable**. Try the example given above via 244 | [this project on PyScript.com](https://pyscript.com/@ntoll/tiny-silence/latest). 245 | 246 | No matter if your code is running on the main thread or in a web worker, 247 | both the [`pyscript.window`](../../api/#pyscriptwindow) (representing the main 248 | thread's global window context) and 249 | [`pyscript.document`](../../api/#pyscriptdocument) (representing the web 250 | page's 251 | [document object](https://developer.mozilla.org/en-US/docs/Web/API/Document)) 252 | will be available and work in the same way. As a result, a worker can reach 253 | into the DOM and access some `window` based APIs. 254 | 255 | !!! warning 256 | 257 | Access to the `window` and `document` objects is a powerful feature. Please 258 | remember that: 259 | 260 | * Arguments to and the results from such calls, when used in a worker, 261 | **must be serializable**, otherwise they won't work. 262 | * If you manipulate the DOM via the `document` object, and other workers or 263 | code on the main thread does so too, **they may interfere with each other 264 | and produce unforeseen problematic results**. Remember, with great power 265 | comes great responsibility... and we've given you a bazooka (so please 266 | remember not to shoot yourself in the foot with it). 267 | 268 | ## Common Use Case 269 | 270 | While it is possible to start a MicroPython or Pyodide worker from either 271 | MicroPython or Pyodide running on the main thread, the most common use case 272 | we have encountered is MicroPython on the main thread starting a Pyodide 273 | worker. 274 | 275 | Here's how: 276 | 277 | **index.html** 278 | ```HTML title="Evaluate main.py via MicroPython on the main thread" 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | PyWorker - mpy bootstrapping pyodide example 289 | 290 | 291 | 292 | ``` 293 | 294 | **main.py** 295 | ```Python title="MicroPython's main.py: bootstrapping a Pyodide worker." 296 | from pyscript import PyWorker, document 297 | 298 | # Bootstrap the Pyodide worker, with optional config too. 299 | # The worker is: 300 | # * Owned by this script, no JS or Pyodide code in the same page can access 301 | # it. 302 | # * It allows pre-sync methods to be exposed. 303 | # * It has a ready Promise to await for when Pyodide is ready in the worker. 304 | # * It allows the use of post-sync (methods exposed by Pyodide in the 305 | # worker). 306 | worker = PyWorker("worker.py", type="pyodide") 307 | 308 | # Expose a utility that can be immediately invoked in the worker. 309 | worker.sync.greetings = lambda: print("Pyodide bootstrapped") 310 | 311 | print("before ready") 312 | # Await until Pyodide has completed its bootstrap, and is ready. 313 | await worker.ready 314 | print("after ready") 315 | 316 | # Await any exposed methods exposed via Pyodide in the worker. 317 | result = await worker.sync.heavy_computation() 318 | print(result) 319 | 320 | # Show the result at the end of the body. 321 | document.body.append(result) 322 | 323 | # Free memory and get rid of everything in the worker. 324 | worker.terminate() 325 | ``` 326 | 327 | **worker.py** 328 | ```Python title="The worker.py script runs in the Pyodide worker." 329 | from pyscript import sync 330 | 331 | # Use any methods from main.py on the main thread. 332 | sync.greetings() 333 | 334 | # Expose any methods meant to be used from main. 335 | sync.heavy_computation = lambda: 6 * 7 336 | ``` 337 | 338 | Save these files in a `tmp` folder, ensure [your headers](#http-headers) (just 339 | use `npx mini-coi ./tmp` to serve via localhost) then see the following 340 | outcome in the browser's devtools. 341 | 342 | ``` 343 | before ready 344 | Pyodide bootstrapped 345 | after ready 346 | 42 347 | ``` 348 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | - defaults 4 | dependencies: 5 | - python=3.11 6 | - pip=23.2.1 7 | - pip: 8 | - mkdocs-material==9.3.1 9 | - mike==1.1.2 10 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: PyScript 2 | 3 | theme: 4 | name: material 5 | 6 | logo: assets/images/pyscript-black.svg 7 | 8 | palette: 9 | # Palette toggle for automatic mode 10 | - media: "(prefers-color-scheme)" 11 | primary: orange 12 | toggle: 13 | icon: material/brightness-auto 14 | name: Switch to light mode 15 | 16 | # Palette toggle for light mode 17 | - media: "(prefers-color-scheme: light)" 18 | primary: orange 19 | scheme: default 20 | toggle: 21 | icon: material/brightness-7 22 | name: Switch to dark mode 23 | 24 | # Palette toggle for dark mode 25 | - media: "(prefers-color-scheme: dark)" 26 | primary: orange 27 | scheme: slate 28 | toggle: 29 | icon: material/brightness-4 30 | name: Switch to system preference 31 | 32 | features: 33 | - content.code.copy 34 | - content.code.annotate 35 | 36 | markdown_extensions: 37 | - attr_list 38 | - md_in_html 39 | - pymdownx.highlight: 40 | anchor_linenums: true 41 | line_spans: __span 42 | pygments_lang_class: true 43 | - pymdownx.inlinehilite 44 | - pymdownx.snippets 45 | - pymdownx.superfences 46 | - admonition 47 | - pymdownx.details 48 | 49 | extra: 50 | version: 51 | provider: mike 52 | 53 | plugins: 54 | - search 55 | - mike: 56 | version_selector: true 57 | css_dir: css 58 | javascript_dir: js 59 | canonical_version: null 60 | 61 | nav: 62 | - Home: index.md 63 | - Beginning PyScript: beginning-pyscript.md 64 | - Example Applications: examples.md 65 | - User guide: 66 | - Introduction: user-guide/index.md 67 | - What is PyScript?: user-guide/what.md 68 | - Features: user-guide/features.md 69 | - First steps: user-guide/first-steps.md 70 | - Architecture: user-guide/architecture.md 71 | - Configure PyScript: user-guide/configuration.md 72 | - The DOM & JavaScript: user-guide/dom.md 73 | - Web Workers: user-guide/workers.md 74 | - The FFI in detail: user-guide/ffi.md 75 | - PyScript and filesystems: user-guide/filesystem.md 76 | - Python terminal: user-guide/terminal.md 77 | - Python editor: user-guide/editor.md 78 | - Media: user-guide/media.md 79 | - PyGame-CE: user-guide/pygame-ce.md 80 | - Plugins: user-guide/plugins.md 81 | - Use Offline: user-guide/offline.md 82 | - Built-in APIs: api.md 83 | - FAQ: faq.md 84 | - Contributing: contributing.md 85 | - Developer Guide: developers.md 86 | - Code of Conduct: conduct.md 87 | - License: license.md 88 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs-material==9.3.1 2 | mike==1.1.2 3 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /review/ 3 | 4 | Sitemap: https://docs.pyscript.net/sitemap.xml 5 | Host: docs.pyscript.net 6 | -------------------------------------------------------------------------------- /version-update.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { join } = require('path'); 4 | const { readdirSync, readFileSync, statSync, writeFileSync } = require('fs'); 5 | 6 | const { version } = require(join(__dirname, 'version.json')); 7 | const calVer = /\/\d{4}\.\d{1,2}\.\d{1,2}\//g; 8 | 9 | const patch = directory => { 10 | for (const file of readdirSync(directory)) { 11 | const path = join(directory, file); 12 | if (file.endsWith('.md')) { 13 | writeFileSync( 14 | path, 15 | readFileSync(path).toString().replace( 16 | calVer, 17 | `/${version}/` 18 | ) 19 | ); 20 | } 21 | else if (statSync(path).isDirectory()) 22 | patch(path); 23 | } 24 | }; 25 | 26 | patch(join(__dirname, 'docs')); 27 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2025.5.1" 3 | } 4 | --------------------------------------------------------------------------------