Compare commits

...

137 Commits

Author SHA1 Message Date
Ted Patrick
25809660ef 2022.12.1 (#1044)
* 2022.12.1

* release level

* fix tests for release
2022-12-13 08:06:38 -06:00
pre-commit-ci[bot]
6b9eff45bb [pre-commit.ci] pre-commit autoupdate (#1042)
updates:
- [github.com/psf/black: 22.10.0 → 22.12.0](https://github.com/psf/black/compare/22.10.0...22.12.0)
- [github.com/pycqa/isort: 5.10.1 → 5.11.1](https://github.com/pycqa/isort/compare/5.10.1...5.11.1)
- [github.com/asottile/pyupgrade: v3.3.0 → v3.3.1](https://github.com/asottile/pyupgrade/compare/v3.3.0...v3.3.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-12-13 10:12:34 +05:30
Madhur Tandon
08e83feaf5 getPySrc should return source code (#1041)
* getPySrc should return source code

* fix lint

* add test

* fix dynamic tag test

* address feedback
2022-12-13 02:41:21 +05:30
Madhur Tandon
4f05b5afc6 remove fetching of pyscript.py from tutorial (#1036)
* remove fetching of pyscript.py from tutorial

* fix indentation
2022-12-10 01:04:04 +05:30
Fabio Pliger
9a5bf9918e Fix py-markdown plugin (#1008)
* fix wrong console method and unescape the tag content before running markdown on it

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

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

* add markdown plugin example test

* add PyMarkdown minimal test

* remove commented code

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

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

* remove import of console from pyscript

* remove unused imports

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

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

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Fabio Pliger <fpliger@anaconda.com>
2022-12-08 10:09:31 -08:00
Jeff Glass
1c7cf0ba7d Bump bokeh, panel versions (#1032)
Bump panel version 13.1 -> 14.1 ; bump bokeh version 2.4.2 -> 2.4.3
2022-12-08 11:40:48 -06:00
Ted Patrick
ce2d1a4513 Release 2022.12.1.RC1 (#1030)
* Release 2022.12.1

* RC1
2022-12-08 07:49:22 -06:00
Madhur Tandon
7d50d7eea0 add docs for using display with matplotlib (#1029) 2022-12-08 07:38:45 -06:00
Fábio Rosado
e9411dc796 Fix the toga example (#1028) 2022-12-08 10:52:09 +00:00
Fábio Rosado
5cf2de16d1 Remove freedom units from examples (#1026) 2022-12-07 12:53:03 +00:00
Ted Patrick
e53bcf15a9 Fix for D3 and 3JS demos (#1025)
* fix for d3 example

* fix webgl +upgrade 3js
2022-12-06 13:36:03 -06:00
Madhur Tandon
bec70b60b8 add reference to Jeff's blog (#1024)
* add reference to Jeff's blog

* fix url
2022-12-06 23:08:12 +05:30
Fábio Rosado
8b7fb89c68 Update docs examples so they don't use deprecated features, styling tweaks (#982)
* Deprecate pyscript output attribute

* Update code blocks so they work

* Small tweaks to styling and use html to warning

* Fix broken test from bad conflict resolution

* lowercase pyodide
2022-12-06 14:36:17 +00:00
Madhur Tandon
b2bbdda73d updated docs for fetch (#1019) 2022-12-06 19:36:23 +05:30
Madhur Tandon
ee2f46cfb9 split py-config into sections (#1022)
* split into sections

* new section about custom keys

* link to fetch section

* add example for using runtime
2022-12-06 19:26:19 +05:30
Fábio Rosado
4337e6833a Rewrite the get started tutorial using the ice cream demo (#985)
* Rewrite the get started tutorial using the ice cream demo

* Update docs/tutorials/getting-started.md

Co-authored-by: Antonio Cuni <anto.cuni@gmail.com>

* Address Antonio's comments

Co-authored-by: Antonio Cuni <anto.cuni@gmail.com>
2022-12-06 13:47:08 +00:00
Jeff Glass
a73c73b814 Fix antigravity (#1017)
* Fix antigravity

* Add test

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

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

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-12-06 19:09:58 +05:30
Antonio Cuni
e8318a98f0 Introduce DeprecatedGlobal and show proper warnings (#1014)
* kill the PyScript class and the weird pyscript instance; from the user point of view its functionalities are still available as pyscript.*, but pyscript is not the module, not the instance of PyScript

* simplify the code in _set_version_info, while I'm at it

* start to implement DeprecatedGlobal

* DeprecatedGlobal.__getattr__

* don't show the same warning twice

* DeprecatedGlobal.__call__

* make it possible to specify a different warning message for every global

* WIP: carefully use DeprecatedGlobal to show reasonable warning messages depending on which name you are accessing to. More names to follow

* deprecate more names

* deprecate private names

* depreacte direct usage of console and document

* deprecate the PyScript class

* use a better error message

* fix test_pyscript.py

* introduce a __repr__ for DeprecatedGlobal

* add an helper to ensure that we don't show any error or warning on the page

* WIP: ensure that examples don't use depreacted features. Many tests are failing

* don't deprecate Element

* don't use the global micropip to install packages, else we trigger a warning

* use a better error message for micropip

* fix test_todo_pylist to avoid using deprecated globals

* fix test_webgl_raycaster

* fix tests

* make HTML globally available

* add MIME_RENDERERS and MIME_METHODS

* fix the typing of Micropip, thanks to @FabioRosado
2022-12-06 19:01:57 +05:30
Ted Patrick
94f2ac6204 Fake server 404 support (#994)
* Fake server 404 support

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

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

* 404 test

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

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

* fix test

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

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

* test fix

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

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

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-12-05 21:26:11 -06:00
pre-commit-ci[bot]
cc6cb4ded0 [pre-commit.ci] pre-commit autoupdate (#1021)
updates:
- [github.com/macisamuele/language-formatters-pre-commit-hooks: v2.4.0 → v2.5.0](https://github.com/macisamuele/language-formatters-pre-commit-hooks/compare/v2.4.0...v2.5.0)
- [github.com/asottile/pyupgrade: v3.2.2 → v3.3.0](https://github.com/asottile/pyupgrade/compare/v3.2.2...v3.3.0)
- [github.com/pre-commit/mirrors-eslint: v8.28.0 → v8.29.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.28.0...v8.29.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-12-05 21:07:16 -06:00
Fábio Rosado
c61d4191c3 Convert image bytes to base64 in pyscript's render_image (#1020)
* Convert bytes to b64 if needed

* Move test and check for pattern
2022-12-05 21:37:03 +00:00
Madhur Tandon
e284da7c09 Fix clashing test names (#1018)
* rename tests

* rename tests for element
2022-12-06 00:43:02 +05:30
Fábio Rosado
9992096654 Add Element to the API reference (#998) 2022-12-05 15:14:34 +00:00
Mariana Meireles
c696d92f40 Rm MIME types that we don't offer support to (#1013) 2022-12-05 16:09:34 +01:00
Fábio Rosado
af60299324 Improve UX when we can't install a package (#1000) 2022-12-05 14:35:15 +00:00
Fábio Rosado
5aa9135a34 Remove comments from github actions (#1011) 2022-12-02 14:11:59 +00:00
Mariana Meireles
72e23ac86f Rm id use (#912)
* rm unecessary code on output rendering on pyscript.py

* Remove all ids from repl

* Fix problems on rendering outside repl

* fixing tests

* Fixes repl and tes_py_repl

* Fix zz tests

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

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

* test 864now works consistently

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-12-02 00:59:21 +01:00
Madhur Tandon
33d49ad87d add docstrings for loadFromFile (#997)
* add docstrings for loadFromFile

* address feedback

* add example usage

* fix typo
2022-11-30 20:54:10 +05:30
pre-commit-ci[bot]
aa2335ca2e [pre-commit.ci] pre-commit autoupdate (#990)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.4.0)
- [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.0.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-11-30 09:05:26 -06:00
Fábio Rosado
b31428006c Use response.ok instead of just status 200 (#999) 2022-11-30 13:17:48 +00:00
Madhur Tandon
dc1d583791 remove unneeded imports (#991) 2022-11-30 15:16:16 +05:30
Jeff Glass
4299a74e40 Invalidate Importlib's Module Cache on Writes to FS (#996)
* Add runtime.invalidate_module_path_cache() to clear importlib's cache of modules

* Clear cache after [[fetch]], after plugin files download, after plugins setup, and prior to py-script tag execution.
2022-11-29 15:36:23 -06:00
Fabio Pliger
3e408b7baa Python Plugins (#961)
* add test and example files

* update config to include python plugins in build

* add markdown plugin

* remove full pyscript execution from pyodide

* move loading of pyscript.py from pyodide loagInterpreter to main setupVirtualEnv and add function to create python CE plugins

* add plugin class to pyscript.py

* add missing import

* fix plugin path

* add fetchPythonPlugins to PyScriptApp

* remove old comments

* fix test

* add support for python plugins beyond custom elements and add app to python namespace in main

* inject reference to PyScript app onto python plugins

* add example hook onto markdown plugin

* change plugin events logs

* remove unused PyPlugin

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

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

* fix type import

* add docstring to fetchPythonPlugins

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

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

* rename addPythonPlugin method

* address PR comment

* call python plugins on hooks after the interpreted is ready

* add test for event hooks and split the test in 2 separate plugins to isolte type of plugins tests

* change python plugins initialization and registration, to inject the app from app itself instead of on the plugins themselves

* handle case when plugin cannot load due to missing plugin attribute

* add test for fail scenario when a plugin module does not have a plugin attribute

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

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

* add deprecation warning for pyscript objects loaded in global namespace

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

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

* remove all from global scope

* remove create_custom_element from global scope

* rename create_custom_element to define_custom_element

* rename attributes in define_custom_element and add docstrings

* better handle connect event output

* add warning to py_markdown plugin

* remove debugging logs

* improve tests

* remove debugging log

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

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

* remove unused import

* add executable shebang

* add pyodide mock module

* fmt and lint

* Update to pyodide.ffi.create_proxy per pyodide v21 api change

* Mock pyodide as package instead of mdoule

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

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

* add __init__ to pyodide package

* Update pyscriptjs/src/plugin.ts

fix logger name

Co-authored-by: Antonio Cuni <anto.cuni@gmail.com>

* fix pyodide import but handling the diff in their API change

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

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

* oops, conflict resolution blooper

* Fix failing integration tests

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Jeff Glass <glass.jeffrey@gmail.com>
Co-authored-by: Antonio Cuni <anto.cuni@gmail.com>
Co-authored-by: FabioRosado <fabiorosado@outlook.com>
2022-11-28 12:39:31 -06:00
Fábio Rosado
446c131ccb Deprecate pyscript output attribute (#981)
* Deprecate pyscript output attribute

* Update pyscriptjs/src/components/pyscript.ts

Co-authored-by: Antonio Cuni <anto.cuni@gmail.com>

Co-authored-by: Antonio Cuni <anto.cuni@gmail.com>
2022-11-25 18:04:27 +00:00
Fábio Rosado
b062efcf17 Add error codes to our custom errors (#959) 2022-11-25 17:04:10 +00:00
Mariana Meireles
30e31a86ef Fix test and add new ones to close 878 (#971)
* Fix test and add new ones to close 887

* lint
2022-11-25 17:40:36 +01:00
Fábio Rosado
182272e8c7 Add tests for changes to examples/ (#980) 2022-11-24 11:17:52 +00:00
Fábio Rosado
06df21e8e3 Add three new tutorials and small tweaks to docs (#972) 2022-11-24 10:53:22 +00:00
Fábio Rosado
cafebd68f2 Fix failing tests (#978) 2022-11-24 09:46:06 +00:00
woxtu
061d4b3f72 Use a more precise type (#976) 2022-11-23 19:16:26 +00:00
Jason Washburn
6586e79d5e remove deprecated ref in getting started doc (#974) 2022-11-23 16:52:26 +00:00
Fábio Rosado
cda6c6bc7e Update docs GitHub actions to deploy to unstable or latest (#977) 2022-11-23 16:51:11 +00:00
Fábio Rosado
a628026838 Restyle examples and add button to show code on all examples (#968) 2022-11-22 15:46:54 +00:00
pre-commit-ci[bot]
6700856b9f [pre-commit.ci] pre-commit autoupdate (#970)
updates:
- [github.com/pre-commit/mirrors-eslint: v8.27.0 → v8.28.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.27.0...v8.28.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-11-22 12:21:36 +05:30
Lukas Schönmann
411aa0bbed Remove hardcoded cursor for <input> (#967) 2022-11-21 21:10:24 +00:00
woxtu
0d79d31b96 Specify the unknown type (#965) 2022-11-21 23:43:57 +05:30
Jeff Glass
cb05a9b067 Export Version from JS Module (#963)
* Export PyScript version from js module

* Add docs and test
2022-11-20 09:58:09 -06:00
woxtu
536f359fb9 Fix a typo (#964) 2022-11-20 13:51:09 +00:00
Jeff Glass
56e888ed33 Fix stray capitalization in file name (#960) 2022-11-19 03:39:54 +05:30
Jeff Glass
687b93d148 Import PyScript Version into Python Runtime (#958)
* Import current version as PyScript.__version__ and PyScript.version_info

* Add integration, python unit tests
 
* Mock pyscript.py with __mocks__/pyscript.js to make jest tests happy
2022-11-18 11:49:45 -06:00
Fábio Rosado
0e1c396d7c Add div id to target in the display so examples get displayed in the right tag (#955)
* Add target to display

* Remove output from examples
2022-11-17 14:51:16 +00:00
Jeff Glass
7e24289703 Unwind async/await chains (#957)
*Cleanup several spots where runtime.run() no longer needs to be awaited, now that #928 is merged.
2022-11-16 13:42:40 -06:00
Jeff Glass
0b23310a06 Remove 'Implicit Async', Don't Await runtime.run() (#928)
* Revert to runPython instead of await runPythonAsync

* "Implicit Coroutines" are no longer permitted in py-script tags

* Tests added for the above

* xfail test_importmap (See #938)
2022-11-16 13:11:40 -06:00
Antonio Cuni
41ebaaf366 More plugins: splashscreen and importmap (#938)
This PR move codes from main.ts into two new plugins:

- splashscreen (formerly known as py-loader)
- importmap

The old setting config.autoclose_loader is still supported but deprecated; the new setting is config.splashscreen.autoclose.

Moreover, it does a small refactoring around UserError: now UserErrors are correctly caught even if they are raised from within afterRuntimeLoad.
2022-11-16 18:08:17 +01:00
Ted Patrick
b79ceea7a8 version number in build (#953) 2022-11-15 15:50:25 -06:00
Fábio Rosado
b990bcb67a Add basic docs for py-terminal (#933)
* Add basic docs for py-terminal

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

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

* Add Antonio's suggestions

* Remove terminal = true

* fix typo

Co-authored-by: Antonio Cuni <anto.cuni@gmail.com>

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Antonio Cuni <anto.cuni@gmail.com>
2022-11-15 15:35:50 +00:00
pre-commit-ci[bot]
3f0f2d9910 [pre-commit.ci] pre-commit autoupdate (#951)
updates:
- [github.com/asottile/pyupgrade: v3.2.0 → v3.2.2](https://github.com/asottile/pyupgrade/compare/v3.2.0...v3.2.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-11-15 18:25:18 +05:30
Fábio Rosado
4333f5f979 Zip the build folder and add it to release (#949) 2022-11-14 20:32:11 +00:00
Madhur Tandon
07e75293b8 use a const for run button svg (#948)
* use a const for run button svg

* remove id

* remove consts.ts file
2022-11-15 01:47:36 +05:30
Fábio Rosado
a9ca7106cb Fix deprecated message (#950) 2022-11-15 01:36:21 +05:30
Fábio Rosado
da2728e6df Add flag to send HTML or plain Text to banner (#947) 2022-11-14 18:36:54 +00:00
Fábio Rosado
adfa9a9b05 Deprecate py-button, py-inputbox, py-box and py-title (#931) 2022-11-14 16:29:28 +00:00
Madhur Tandon
be9b9f66d3 remove one more unneeded import (#946) 2022-11-14 19:09:03 +05:30
Madhur Tandon
9521bc7175 remove unneeded import (#945) 2022-11-14 17:07:33 +05:30
Fábio Rosado
b445f8a834 Handle non-200 code when fetch py-script src (#940) 2022-11-12 10:07:51 +00:00
Fábio Rosado
3c3dffd5ed Raise error if we get a non 200 status from fetch (#935)
* Raise error if we get a non 200 status from fetch

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

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

* Add custom exception

* Add check for TypeError as well

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-11-10 15:00:41 +00:00
Antonio Cuni
4c8443fd00 Make HTTP cache persisten across sessions (#936)
* use pytest-cache to cache HTTP requests, so that it survives across multiple runs

* add an option to clear the HTTP cache

* add some docs about pytest
2022-11-10 15:01:27 +01:00
Fábio Rosado
06a5a54103 Fix banner width (#930) 2022-11-09 16:42:10 +00:00
Antonio Cuni
0d3c3eef4e Introduce a Plugin system, implement <py-terminal> as a plugin (#917)
This PR does two things:

1. introduce the first minimal version of a Plugin system. Plugins are subclasses of the Plugin class, and pyscript will call the relevant methods/hooks at the right time. Currently, I have added only the minimal sets of hooks which were needed for this PR.

2. Implement <py-terminal> as a plugin.
2022-11-09 16:17:21 +01:00
Madhur Tandon
f0a6fb913f add test for not escaping characters in pyconfig (#929) 2022-11-09 12:18:02 +05:30
Madhur Tandon
5f0c508fed use user error for paths calculation (#927) 2022-11-09 03:13:18 +05:30
pre-commit-ci[bot]
16d9657982 [pre-commit.ci] pre-commit autoupdate (#922)
updates:
- [github.com/pre-commit/mirrors-eslint: v8.26.0 → v8.27.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.26.0...v8.27.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-11-09 03:01:19 +05:30
Madhur Tandon
40d098310e decode innerhtml for pyconfig (#926) 2022-11-09 03:00:37 +05:30
Fábio Rosado
1345449d57 Create error or warning level banners and allow users to close it (#909) 2022-11-08 17:19:01 +00:00
Madhur Tandon
515858f313 implement proposal for fetching paths and retaining structure of dirs and packages (#914)
* implement proposal

* update docs and replace py-env

* more docs

* suggested proposal

* update docs

* add to_file parameter

* remove comment from Makefile

* suggested improvements

* move tests from basic to py_config

* retain leading slash from the first path
2022-11-08 17:26:45 +05:30
Fábio Rosado
2f452e9dc7 Add development section to our docs (#920) 2022-11-07 15:36:05 +00:00
Antonio Cuni
66119157a7 display() should escape by default (#915)
- display(some_str) escapes the string by default. This is almost always what you want

- display(some_obj) calls repr(obj) and escapes the result. Again, it's a very sensible default

- if you want to inject some raw HTML in the output, you can use the new HTML class: display(HTML("<p>hello</p>")).
2022-11-03 09:20:10 +01:00
pre-commit-ci[bot]
5b671dd1d0 [pre-commit.ci] pre-commit autoupdate (#913)
updates:
- [github.com/asottile/pyupgrade: v3.1.0 → v3.2.0](https://github.com/asottile/pyupgrade/compare/v3.1.0...v3.2.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-11-01 14:05:10 +05:30
woxtu
1017362eec Use globalThis (#908) 2022-10-31 15:25:36 +01:00
Antonio Cuni
f67b8e0285 Fix py-repl auto-generate=true (#910)
What is not tested is broken man_facepalming
Test&fix <py-repl auto-generate=true> which was broken by PR #884
2022-10-31 12:55:20 +01:00
woxtu
4c635fe84c Add syntax highlighting (#903) 2022-10-31 14:55:11 +05:30
Mariana Meireles
68e463493e Address to Jeff's review on output docs + Ted's remodeling of docs (#866) 2022-10-29 16:24:18 +01:00
Ted Patrick
f1979d60b7 Add Fabio Rosado (#899) 2022-10-28 10:41:34 -05:00
Ted Patrick
9150ebafec Fix py-repl docs (#896) 2022-10-28 07:21:48 -05:00
woxtu
9543019336 Format the TypeScript files (#877) 2022-10-28 14:18:27 +05:30
Fábio Rosado
1c53d91c6b Add more type definitions (#882)
* More typing to base.ts

* Add more types

* More types
2022-10-27 17:48:28 -05:00
Jeff Glass
4850f39b5a Upgrade to Pyodide 0.21.3 (#887)
* Upgrade to Pyodide 21.3

* During the PyodideRuntime test, set the indexURL parameter of loadPyodide to allow Jest to find pyodide-adjacent files.
2022-10-27 15:16:17 -05:00
Madhur Tandon
ab085c2d92 Retain path structure for loading paths (#845)
* update retain dir structure logic to use emscripten FS APIs

* remove unused import

* replace error message
2022-10-27 20:08:58 +05:30
Madhur Tandon
bf4d835948 remove duplicate pytest_configure (#894) 2022-10-27 16:57:12 +05:30
Antonio Cuni
214e39537b Simplify pyrepl.ts and kill base.ts (#884)
Major highlights:

1. Merge&simplify base.ts and pyrepl.ts; kill base.ts
2. improve and extente the py-repl integration tests
3. Reorder the code in pyrepl.ts. This part of the PR doesn't change much of the concrete logic: it's just a sequence of renaming variables, moving code around, group code into functions, killing code which is no longer needed. But the end result is much better and nicer to read, IMHO.

Minor highlights:

1. py-repl now uses the new logic in pyexec.ts to run the code

2. after PR Add display impl, rm outputManage, print and console.log default to browser console #749 py-repl no longer displayed the result of the last evaluated expression (e.g. if you typed 42 and run it, it displayed nothing). This PR re-introduces this behavior, which is what you would expect by a REPL.

3. improve the pytest --dev option: now it implies --no-fake-server so that sourcemaps works automatically

4. improve the names of the CSS classes to be more consistent

5. kill pyrepl.test.ts: the old tests didn't check anything useful,  this style of unit test doesn't really add much value if you have good integration tests (which now we have) and trying to revive them was not worth the hassle
2022-10-27 10:10:57 +02:00
Antonio Cuni
2d33afc195 try to display a reasonable message if you run a bare 'pytest' command (#890) 2022-10-26 18:05:12 +02:00
Ted Patrick
87ea24ebd4 header for pyrepl table (#889) 2022-10-25 19:34:27 -05:00
Ted Patrick
5380f8b9b3 Reference Docs (#888)
* Reference Docs

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

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

* py-repl

* pyrepl

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-25 17:42:30 -05:00
Fábio Rosado
00121ff8ba Remove svelte (#886) 2022-10-25 14:49:53 -05:00
pre-commit-ci[bot]
80e5d20e37 [pre-commit.ci] pre-commit autoupdate (#885)
updates:
- [github.com/pre-commit/mirrors-eslint: v8.25.0 → v8.26.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.25.0...v8.26.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-24 17:48:54 -05:00
Antonio Cuni
58f7c2137d Introduce/improve check_js_errors and improve test_no_implicit_target (#874)
Until now, we didn't have a nice way to check that we expect a specific JS error in the web page.
This PR improves check_js_errors() so that now you can pass a list of error messages that you expect.
It is tricky because we need to handle (and test!) all various combinations of cases:

- errors expected and found / expected but not found
- unexpected errors found / not found

Moreover, JS exceptions now are logged in the special category console.js_error, which means that the printed text is also available using e.g. self.console.js_error.text or self.console.all.text. However, this should never be required and it's preferred to use self.check_js_errors to check for exceptions. This fixes #795 .

Finally, use the new logic to improve test_no_implicit_target.
2022-10-24 16:24:52 +02:00
Antonio Cuni
f9194cc833 Refactor how py-script are executed, kill scriptQueue store, introduce pyExec (#881)
Yet another refactoring to untangle the old mess.
Highlights:

base.ts, pyscript.ts and pyrepl.ts were a tangled mess of code, in which each of them interacted with the others in non-obvious ways. Now PyScript is no longer a subclass of BaseEvalElement and it is much simpler. I removed code for handling the attributes std-out and std-err because they are no longer needed with the new display() logic.

The logic for executing python code is now in pyexec.ts: so we are decoupling the process of "finding" the python code (handled by the py-script web component) and the logic to actually execute it. This has many advantages, including the fact that it will be more easily usable by other components (e.g. pyrepl). Also, note that it's called pyexec and not pyeval: in the vast majority of cases in Python you have statements to execute, and almost never expressions to evaluate.

I killed the last remaining global store, scriptQueue tada. As a bonus effect, now we automatically do the correct thing when a <py-script> tag is dynamically added to the DOM (I added a test for it). I did not remove svelte from packages.json, because I don't fully understand the implications: there are various options which mention svelte in rollup.js and tsconfig.json, so it's probably better to kill it in its own PR.

pyexec.ts is also responsible of handling the default target for display() and correct handling/visualization of exceptions. I fixed/improved/added display/output tests in the process.
I also found a problem though, see issue #878, so I improved the test and marked it as xfail.

I removed BaseEvalElement as the superclass of most components. Now the only class which inherits from it is PyRepl. In a follow-up PR, I plan to merge them into a single class and do more cleanup.

During the refactoring, I killed guidGenerator: now instead of generating random py-* IDs which are very hard to read for humans, we generated py-internal-X IDs, where X is 0, 1, 2, 3, etc. This makes writing tests and debugging much easier.

I improved a lot our test machinery: it turns out that PR #829 broke the ability to use/view sourcemaps inside the playwright browser (at least on my machine).
For some reason chromium is unable to find sourcemaps if you use playwrights internal routing. So I reintroduced the http_server fixture which was removed by that PR, and added a pytest option --no-fake-server to use it instead, useful for debugging. By default we are still using the fakeserver though (which is faster and parallelizable).

Similarly, I added --dev which implies --headed and also automatically open chrome dev tools.
2022-10-23 23:31:50 +02:00
Jeff Glass
d9b8b48972 Export Runtime from PyScript Module (#868)
* export 'pyscript' JS module with runtime attribute, to allow accessing (Pyodide) runtime and globals from JS.
* add docs to explain the js module
* add integration tests for the js module

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-23 13:46:19 -05:00
woxtu
aa85f5f596 Migrate the Jest config (#873)
* Migrate the Jest config

* Run Prettier
2022-10-21 19:35:14 +05:30
woxtu
5341a0be4a Add type annotations (#869)
* Add a type declaration

* Add a missing type annotation
2022-10-21 09:03:54 -05:00
Madhur Tandon
0cfe20ca65 use emscripten virtual FS directly to load paths (#870)
* use emscripten virtual FS directly to load paths

* use more low level APIs

* use await instead of then

* remove try...catch from loadFromFile since it's being handled externally

* add test for an invalid path

* test checks for error shown to the user too

* add comment about a missing case
2022-10-21 18:43:03 +05:30
Ted Patrick
c352b502c4 ts strict - getAttribute (#863)
* ts strict getAttribute

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

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

* mark xfail

* fix to string

* Remove

* use getAttribute for theme

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-21 07:47:44 -05:00
Antonio Cuni
58b4df6b3d skip test_repl as it's flaky (#875) 2022-10-21 14:31:54 +02:00
Jeff Glass
29ba9436c8 Fix 'no unncessary type assertion' (#871) 2022-10-20 16:18:27 +02:00
pre-commit-ci[bot]
2a044e88ad [pre-commit.ci] pre-commit autoupdate (#862)
updates:
- [github.com/codespell-project/codespell: v2.2.1 → v2.2.2](https://github.com/codespell-project/codespell/compare/v2.2.1...v2.2.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-17 19:09:20 -05:00
Ted Patrick
63092f9d72 Add note on [[runtimes]] TOML table in docs (#861)
* Note on pyconfig. Fixes #859

* spell

* grammar

* mor grammar

* goes to go
2022-10-17 17:45:24 -05:00
woxtu
0209324d57 Pin dependencies for pre-commit (#860)
* Remove an unused dependency

* Pin dependencies
2022-10-17 15:12:53 -05:00
Mariana Meireles
1587273868 Add display impl, rm outputManage, print and console.log default to browser console (#749)
* Add display impl, remove outputManage, print and console.log defaults to terminal

* Fixing tests

* Lint

* Erase unecessary code, add cuter CSS formating for errors, fix problems around REPL output

* Add fix to repl2 and lint

* lint

* Allow for list of display, fix elif to else

* Add better global option

* test work

* xfails

* (antocuni, mariana): let's try to start again with TDD methodology: write the minimum test and code for a simple display()

* (antocuni, mariana): this test works out of the box

* WIP: this test is broken, mariana is going to fix it

* add a failing test

* Add ability to deal with targets

* Add append arg and append tests

* Add multiple values to display

* Small adjustments to tests. I noticed I wasn;t running all at some point

* add display test

* Add console tests

* Add async tests

* Fix repl tests

* Fixing merging issues

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

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

* Address antocuni's review

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

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

* Fixing more tests

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

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

* linting

* Improve repl tests

* Change my test so codespell is hapy with it

* Test: change test_runtime_config to use json instead of toml to see if stops failing on CI

* kill this file: it is a merge artifact since it was renamed into test_py_config.py on the main branch

* Change test execution order and add async tests to async test file

Co-authored-by: Antonio Cuni <anto.cuni@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-17 16:28:40 +02:00
Antonio Cuni
beb3aa1574 kill stores.runtimeLoaded and many other stores (#850)
As the title stays, the main goal of the branch is to kill the infamous runtimeLoaded global store and all the complications, problems and bugs caused by the fact that in many places we needed to ensure/wait that the global runtime was properly set before being able to execute code.

The core idea is that runtime is never a global object and that it's passed around explicitly, which means that when a function receives it, it is guaranteed to be initialized&ready.

This caused a bit of complications in pybutton.ts, pyinputbox.ts and pyrepl.ts, because they indirectly want to call runtime.run from connectedCallback, which is the only place where we cannot explicitly pass the runtime because it's automatically called by the browser.
But also, it is also a sign of a bad design, because it were entirely possible that connectedCallback was called before the runtime was ready, which probably caused many bugs, see e.g. #673 and #747.

The solution to is use dependency injection and create the class later on: so instead of having a global PyButton class which relies on a global runtime (whose state is uncertain) we have a make_PyButton function which takes a runtime and make a PyButton class which is tied to that specific runtime (whose state is certainly ready, because we call make_PyButton only when we know that the runtime is ready).
Similar for PyInputBox and PyRepl.

Other highlights: thanks to this, I could kill the also infamous runAfterRuntimeInitialized and a couple of smelly lines which used setTimeout to "wait" for the runtime.

While I was at it, I also called a lot of other stores which were completely unused and where probably leftovers from a past universe.
2022-10-17 10:31:57 +02:00
woxtu
fe708c9fb4 Remove an unused import (#858) 2022-10-17 00:38:52 +05:30
John Franey
e45d8bf973 Fix typo in passing-objects.md (#856)
Fixes typo in HTML example: `do-mmath` -> `do-math`
2022-10-15 14:56:29 -04:00
Jeff Glass
b184c92f01 Use only single rollup-plugin-copy() to avoid race condition (#843)
* Use only single copy() plugin to avoid race condition

* clean production def, better copy_target structure

* Restore '!process.env.ROLLUP_WATCH to production definition

Co-authored-by: Ted Patrick <tpatrick@anaconda.com>
2022-10-12 12:05:14 -05:00
Ted Patrick
6c8afb05a7 Don't hide undefined elements in the CSS (#837)
* Hide py-config element

* display:none for all web components

* docs

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

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

* Add style tests for raw html and pyscript enabled html

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

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

* test naming

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-11 12:27:22 -05:00
pre-commit-ci[bot]
d3dd4573cf [pre-commit.ci] pre-commit autoupdate (#844)
updates:
- [github.com/psf/black: 22.8.0 → 22.10.0](https://github.com/psf/black/compare/22.8.0...22.10.0)
- [github.com/asottile/pyupgrade: v2.38.2 → v3.1.0](https://github.com/asottile/pyupgrade/compare/v2.38.2...v3.1.0)
- [github.com/pre-commit/mirrors-eslint: v8.23.1 → v8.25.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.23.1...v8.25.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-11 08:21:23 -05:00
Ted Patrick
d5cf68391a Upgrade and pin dependencies (#840)
* Upgrade and Pin Dependencies

* Removal all unused deps

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

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

* lock

* remove svelte-fa reference from the svg style.

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-11 07:20:31 -05:00
Ted Patrick
4e54e93450 Correct the url in the example (#841) 2022-10-09 10:24:00 +05:30
Ted Patrick
e4f6387f18 Remove fa reference in py-repl 2022-10-08 20:13:25 -05:00
Ted Patrick
54cb35b60a Add directly listing for releases (fix error) (#839)
* move copy index.html out of !production to run every time

* format

* dir syntax

* file order in dir listing

* Example title
2022-10-09 05:04:28 +05:30
Ted Patrick
18ede2b729 Add directly listing for releases (#838)
* Add directly listing for releases

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

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

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-08 16:05:03 -05:00
Ted Patrick
f138b5a4f4 Add cache, fetch, retry logic to tests (#829)
* Add cache, fetch, retry logic to tests

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

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

* run in parallel

* add pytest-xdist

* undo parallelism. Need to remove http server to enable.

* woops a extra space

* Pass flake8

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

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

* spell fulfill

* use decorator for fetch if not in cache

* Fix --headed and limit to PlaywrightRequestError

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

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

* docs on cache

* CICD caching of conda on unstable builds

* fix config issues

* empty commit to trigger gh-actions

* restore build-unstable

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

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

* Remove http server, add parallel

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

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

* temp: Bypass zip runtime test and point to v0.21.3 on CDN

* suport for files in zip under /pyodide

* remove test-one

* self.http_server and remove content_type

* domcontentloaded w no timeout on base url + longer timeout on wait_for_pyscript

* Fixed #678

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

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

* set default timeout to 60000

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

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

* seamless --headed support

* add test-integration-parallel and default for GHActions

* simplify the code. Use http://fakeserver instead of localhost:8080 so that it's clearer that the browser is NOT hitting a real server, and use urllib to parse the url. Moreover, the special case for pyodide is no longer needed, it's automatically handled by the normal 'fakeserver' logic

* The page-routing logic is becoming too much complicated to stay as an inner function. Move it to its own class, and add some logic to workaround a limitation of playwright which just hangs if a Python exception is raised inside it

* no need to use a hash, we can use the url as the key

* re-implement the retry logic. The old @retry decorator was nice but a bit too over-engineered and most importantly failed silently in case of exceptions. This new approach is less powerful but since we want to retry only two times, simple is better than complex -- and in case of exception, the exception is actually raised

* improve logging

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Madhur Tandon <madhurtandon23@gmail.com>
Co-authored-by: Antonio Cuni <anto.cuni@gmail.com>
2022-10-07 10:31:26 -05:00
Antonio Cuni
11a517bba4 Make the life-cycle more linear and kill some svelte stores (#830)
This is a follow-up of PR #806 and it's a big step forward towards solving issue #763.

The basic idea is that at this stage I want to streamline the execution logic
and make it as linear and easy to read/understand as possible. The diff is
relatively big, but for the most part is just "shuffling code around".


Svelte stores:
the idea is to eventually kill of them, so that we can remove the dependency
on svelte but also to avoid relying on so much global state, which makes
things more complicated and error-prone (e.g., we have several issues related
to using runtime when it's not ready yet).

I killed addInitializer, addPostInitializer and the corresponding svelte
stores tada. They are no longer needed since the relevant code is called
directly from main.ts.

I started to kill the usage of the runtimeLoaded svelte store: instead of
relying on a global variable, I want to arrive at the point in which the
runtime is passed as a parameter in all places where it's needed: pyscript.ts
is now free of global state, but I couldn't kill it yet because it's used
heavily by base.ts and pyrepl.ts. I will do it in another PR.

Other misc changes:
I added sanity checks (and corresponding tests!) which complain if you specify
0 or multiple runtimes. Currently we support having one and only one, so there
is no point to pretend otherwise

I modified the messages displayed by the loader, to be more informative from
the user point of view.
2022-10-07 17:13:12 +02:00
Jeff Glass
66b57bf812 Remove fortawesome/free-solid-svg-icons (#831) 2022-10-06 22:54:10 +05:30
Ted Patrick
a9357bd97e SVG Docs logo (#832)
* SVG Docs logo. Fixes https://github.com/pyscript/pyscript/issues/818

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

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

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-06 20:48:08 +05:30
Madhur Tandon
d7c6d42c3d remove warning (#828) 2022-10-05 01:24:51 +05:30
Madhur Tandon
4dd1dc28b1 move py-config between body tags (#827) 2022-10-05 01:13:34 +05:30
Antonio Cuni
1e05ff7c95 detect the case in which multiple <py-config> are listed
Ideally I would like it to be a fatal error, but it's too hard to do it with the current state of the code, will refactor it later (#826)
2022-10-04 19:59:59 +02:00
Mariana Meireles
e8e2e65584 Update CONTRIBUTING.md (#822)
To add that we need to have a test for every feature :)
closes https://github.com/pyscript/pyscript/issues/615
2022-10-04 17:16:48 +02:00
Ted Patrick
3727e60152 Maintainers Update (#823) 2022-10-04 09:23:47 -05:00
Ted Patrick
0254012db6 Add epic to exempt labels (#821) 2022-10-04 08:55:55 -05:00
Jeff Glass
8b97e4757f Update to Codemirror 6 (py-repl) (#814)
* Upgrade to codemirror v6

* Increase indentation to 4 spaces by default
2022-10-04 08:49:42 -05:00
Madhur Tandon
c70e121078 eliminate py-env altogether (#775)
* examples use toml now

* use 0.21.2 for now

* change config in docs

* fix pre-commit
2022-10-04 19:00:39 +05:30
Ted Patrick
a4f97e6e46 Add Github Stale to PyScript repo (#791)
* Add stale to pyscript repo

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

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

* Add tags: backlog, needs-triage, needs-work

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-04 08:08:26 -05:00
Antonio Cuni
800145a83c Add a link to docs in the README (#817) 2022-10-04 18:14:12 +05:30
Antonio Cuni
c75f885cb4 Refactor py-config and the general initialization logic of the page (#806)
This PR is the first step to improve and rationalize the life-cycle of a pyscript app along the lines of what I described in #763 .
It is not a complete solution, more PRs will follow.
Highlights:

- py-config is no longer a web component: the old code relied on PyConfig.connectedCallback to do some logic, but then if no <py-config> tag was present, we had to introduce a dummy one with the sole goal of activating the callback. Now the logic is much more linear.

- the new pyconfig.ts only contains the code which is needed to parse the config; I also moved some relevant code from utils.ts because it didn't really belong to it

- the old PyConfig class did much more than dealing with the config: in particular, it contained the code to initialize the env and the runtime. Now this logic has been moved directly into main.ts, inside the new PyScriptApp class. I plan to refactor the initialization code in further PRs

- the current code relies too much on global state and global variables, they are everywhere. This PR is a first step to solve the problem by introducing a PyScriptApp class, which will hold all the mutable state of the page. Currently only config is stored there, but eventually I will migrate more state to it, until we will have only one global singleton, globalApp

- thanks to what I described above, I could kill the appConfig svelte store: one less store to kill :).
2022-10-04 14:26:12 +02:00
Ted Patrick
4011a51013 add tsc as a build step with noEmit (#816) 2022-10-04 04:51:56 -05:00
Ted Patrick
f4165dabcf TOML Error Cases (#815)
* TOML Error Cases

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

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

* remove traceback

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

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

* any is a ts feature

* full eslint

* format and disable eslint for toml.js

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Madhur Tandon <madhurtandon23@gmail.com>
2022-10-04 13:13:24 +05:30
djchou
de6c26eb05 Add discord channel (#811)
from Ted Patrick --
tedpatrick — 09/19/2022
Hello and Good Morning (CST in Austin TX)
Key: We want the dialog about Pyscript OSS to be transparent and centered in the community. Rather than add another communication platform, using this Discord meets the community where they are.
2022-10-01 20:44:58 +05:30
170 changed files with 15155 additions and 6565 deletions

26
.github/stale.yaml vendored Normal file
View File

@@ -0,0 +1,26 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- backlog
- needs-triage
- needs-work
- epic
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
This issue has been automatically closed because it has not had
recent activity. Thank you for your contributions.

View File

@@ -1,18 +1,20 @@
name: '[CI] Build Unstable'
on:
push: # Only run on merges into main that modify files under pyscriptjs/
push: # Only run on merges into main that modify files under pyscriptjs/ and examples/
branches:
- main
paths:
- pyscriptjs/**
- examples/**
- .github/workflows/build-latest.yml # Test that workflow works when changed
pull_request: # Run on any PR that modifies files under pyscriptjs/
pull_request: # Run on any PR that modifies files under pyscriptjs/ and examples/
branches:
- main
paths:
- pyscriptjs/**
- examples/**
- .github/workflows/build-unstable.yml # Test that workflow works when changed
workflow_dispatch:
@@ -64,7 +66,7 @@ jobs:
run: make test-py
- name: Integration Tests
run: make test-integration
run: make test-integration-parallel
- uses: actions/upload-artifact@v3
with:

View File

@@ -1,18 +0,0 @@
name: Push issue to Github Project dashboard
on:
issues:
types:
- opened
pull_request_target:
types:
- opened
jobs:
add_to_project:
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.0.3
with:
project-url: https://github.com/orgs/pyscript/projects/4/
github-token: ${{ secrets.PROJECT_TOKEN }}

View File

@@ -55,3 +55,12 @@ jobs:
- name: Sync to S3
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/${{ github.ref_name }}/
# Make sure to remove the latest folder so we sync the full docs upon release
- name: Delete latest directory
run: aws s3 rm --recursive s3://docs.pyscript.net/latest/
# Note that the files are the same as above, but we want to have folders with
# /<tag name>/ AND /latest/ which latest will always point to the latest release
- name: Sync to /latest
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/latest/

View File

@@ -63,9 +63,6 @@ jobs:
- name: Copy redirect file
run: aws s3 cp --quiet ./docs/_build/html/_static/redirect.html s3://docs.pyscript.net/index.html
# - name: Delete review directory
# run: aws s3 rm --recursive s3://docs.pyscript.net/review/${{ github.event.number }}/
- name: Sync to S3
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/review/${{ github.event.number }}/

View File

@@ -50,11 +50,6 @@ jobs:
aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
- name: Copy redirect file
run: aws s3 cp --quiet ./docs/_build/html/_static/redirect.html s3://docs.pyscript.net/index.html
# - name: Delete latest directory
# run: aws s3 rm --recursive s3://docs.pyscript.net/latest/
# Sync will only copy changed files
- name: Sync to S3
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/latest/
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/unstable/

View File

@@ -48,9 +48,13 @@ jobs:
- name: Build and Test
run: make test
- name: Zip build folder
run: zip -r -q ./build.zip ./build
- name: Prepare Release
uses: softprops/action-gh-release@v1
with:
draft: true
prerelease: true
generate_release_notes: true
files: ./build.zip

View File

@@ -4,7 +4,7 @@
default_stages: [commit]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.4.0
hooks:
- id: check-builtin-literals
- id: check-case-conflict
@@ -28,55 +28,54 @@ repos:
- --skip=B101,B201
- repo: https://github.com/psf/black
rev: 22.8.0
rev: 22.12.0
hooks:
- id: black
- repo: https://github.com/codespell-project/codespell
rev: v2.2.1
rev: v2.2.2
hooks:
- id: codespell # See 'setup.cfg' for args
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
rev: 6.0.0
hooks:
- id: flake8 # See 'setup.cfg' for args
additional_dependencies: [flake8-bugbear, flake8-comprehensions]
- repo: https://github.com/pycqa/isort
rev: 5.10.1
rev: 5.11.1
hooks:
- id: isort
name: isort (python)
args: [--profile, black]
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.4.0
rev: v2.5.0
hooks:
- id: pretty-format-yaml
args: [--autofix, --indent, '4']
exclude: .github/ISSUE_TEMPLATE/.*\.yml$
- repo: https://github.com/asottile/pyupgrade
rev: v2.38.2
rev: v3.3.1
hooks:
- id: pyupgrade
args:
- --py310-plus
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.24.0
rev: v8.29.0
hooks:
- id: eslint
files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
types: [file]
additional_dependencies:
- eslint
- eslint-plugin-svelte3
- typescript
- '@typescript-eslint/eslint-plugin'
- '@typescript-eslint/parser'
- eslint@8.25.0
- typescript@4.8.4
- '@typescript-eslint/eslint-plugin@5.39.0'
- '@typescript-eslint/parser@5.39.0'
# Commented out until mdformat-myst supports custom extensions
# See https://github.com/executablebooks/mdformat-myst/pull/9

View File

@@ -9,7 +9,7 @@ Thank you for wanting to contribute to the PyScript project!
* [Reporting bugs](#reporting-bugs)
* [Reporting security issues](#reporting-security-issues)
* [Asking questions](#asking-questions)
* [Setting up your local environment](#setting-up-your-local-environment)
* [Setting up your local environment and developing](#setting-up-your-local-environment-and-developing)
* [Places to start](#places-to-start)
* [Submitting a change](#submitting-a-change)
* [License terms for contributions](#license-terms-for-contributions)
@@ -41,149 +41,6 @@ If you aren't confident that it is appropriate to submit a security issue using
If you have questions about the project, using PyScript, or anything else, please ask in the [PyScript forum](https://community.anaconda.cloud/c/tech-topics/pyscript).
## Setting up your local environment
* Fork the repository - [quicklink](https://github.com/pyscript/pyscript/fork)
* Clone your fork of the project
```
git clone https://github.com/<your username>/pyscript
```
* Add the original project as your upstream (this will allow you to pull the latest changes)
```
git remote add upstream git@github.com:pyscript/pyscript.git
```
* cd into the `pyscriptjs` folder using the line below in your terminal (if your terminal is already in pyscript then use **cd pyscriptjs** instead)
```
cd pyscript/pyscriptjs
```
* Install the dependencies with the command below
```
make setup
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **NOTE**: If `make setup` gives a node/npm version required error then go to [troubleshooting](https://github.com/pyscript/pyscript/blob/main/TROUBLESHOOTING.md)
* You can also run the examples locally by running the command below in your terminal
```
make examples
```
* Run ***npm run dev*** to build and run the dev server. This will also watch for changes and rebuild when a file is saved.
```
npm run dev
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **NOTE**: To access your local build paste `http://localhost:8080` into your browser
Now that node and npm have both been updated `make setup` should work, and you can continue [setting up your local environment](#setting-up-your-local-environment) without problems (hopefully).
### Developing
* First, make sure you are using the latest version of the pyscript main branch
```
git pull upstream main
```
* Update your fork with the latest changes
```
git push origin main
```
* Activate the conda environment (this environment will contain all the necessary dependencies)
```
conda activate pyscriptjs/env/
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **NOTE**: We are assuming you are in the root folder. If you are in the pyscriptjs you can run `conda activate env/` instead.
* Install pre-commit (you only need to do this once)
```
pre-commit install
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **NOTE**: On first run, pre-commit installs a bunch of hooks that will be run when you commit changes to your branch - this will make sure that your code is following our style (it will also lint your code automatically).
* Create a branch for the issue that you want to work on
```
git checkout -b <your branch name>
```
* Work on your change
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **NOTE**: If you are working on a python file, you may encounter linting issues when pre-commit runs. Pyscript uses [black](https://black.readthedocs.io/en/stable/) to fix any linting problems automatically. All you need to do is add the changes again and commit using your previous commit message (the previous one that failed didn't complete due to black formatting files).
* Run tests before pushing the changes
```
make tests
```
* When you make changes locally, double check that your contribution follows the PyScript formatting rules by running `npm run lint`. Note that in this case you're looking for the errors, <u>**NOT**</u> the warnings (Unless the warning is created by a local change). If an error is found by lint you should fix it <u>**before**</u> creating a pull request
#### Rebasing changes
Sometimes you might be asked to rebase main into your branch. You can do such by:
* Checkout into your main branch and pull the upstream changes
```
git checkout main
git pull upstream main
```
* Checkout your branch and rebase on main
```
git rebase -i main
```
If you have conflicts, you must fix them by comparing yours and incoming changes. Your editor can probably help you with this, but do ask for help if you need it!
* Once all conflicts have been fixed
```
git rebase --continue
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **NOTE**: You may see more conflicts that you need to address until all are resolved.
* Force push the fixed conflicts
```
git push -f origin <your branch name>
```
### Building the docs
To build the documentation locally first make sure you are in the `docs` directory.
You'll need `make` and `conda` installed in your machine. The rest of the environment should be automatically download and created for you once you use the command:
```
make setup
```
Use `conda activate $environment_name` to activate your environment.
To add new information to the documentation make sure you conform with PyScript's code of conduct and with the general principles of Diataxis. Don't worry about reading too much on it, just do your best to keep your contributions on the correct axis.
Write your documentation files using [Markedly Structured Text](https://myst-parser.readthedocs.io/en/latest/syntax/optional.html), which is very similar to vanilla Markdown but with some addons to create the documentation infrastructure.
Once done, initialize a server to check your work:
```
make livehtml
```
Visible here: http:///127.0.0.1:8000
## Places to start
If you would like to contribute to PyScript, but you aren't sure where to begin, here are some suggestions.
@@ -193,21 +50,16 @@ If you would like to contribute to PyScript, but you aren't sure where to begin,
* **Look over the open pull requests.** Do you have comments or suggestions for the proposed changes? Add them.
* **Check out the examples.** Is there a use case that would be good to have sample code for? Create an example for it.
# Submitting a change
## Setting up your local environment and developing
All contributions must be licensed Apache 2.0, and all files must have a copy of the boilerplate license comment (can be copied from an existing file).
If you would like to contribute to PyScript, you will need to set up a local development environment. The [following instructions](https://docs.pyscript.net/latest/development/setting-up-environment.html) will help you get started.
To create a change for PyScript, you can follow the process described [here](https://docs.github.com/en/get-started/quickstart/contributing-to-projects).
* Follow the steps in [setting your local environment](#setting-up-your-local-environment) and [developing](#developing)
* Make the changes you would like (don't forget to test them with `make test`)
* Open a pull request back to the PyScript project and address any comments/questions from the maintainers and other contributors.
You can also read the [developing process](https://docs.pyscript.net/latest/development/developing.html) and how to rebase your branch with the latest changes.
## License terms for contributions
This Project welcomes contributions, suggestions, and feedback. All contributions, suggestions, and feedback you submitted are accepted under the [Apache 2.0](./LICENSE) license. You represent that if you do not own copyright in the code that you have the authority to submit it under the [Apache 2.0](./LICENSE) license. All feedback, suggestions, or contributions are not confidential.
## Becoming a maintainer
Contributors are invited to be maintainers of the project by demonstrating good decision making in their contributions, a commitment to the goals of the project, and consistent adherence to the [code of conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md). New maintainers are invited by a 3/4 vote of the existing maintainers.

View File

@@ -14,7 +14,8 @@ This document lists the Maintainers of the Project. Maintainers may be added onc
| Madhur Tandon | Anaconda, Inc |
| Ted Patrick | Anaconda, Inc |
| Jeff Glass | --- |
| --- | --- |
| Paul Everitt | --- |
| Fabio Rosado | --- |
______________________________________________________________________

View File

@@ -35,9 +35,11 @@ Read the [contributing guide](CONTRIBUTING.md) to learn about our development pr
## Resources
* [Official docs](https://docs.pyscript.net)
* [Discussion board](https://community.anaconda.cloud/c/tech-topics/pyscript)
* [Home Page](https://pyscript.net/)
* [Blog Post](https://engineering.anaconda.com/2022/04/welcome-pyscript.html)
* [Discord Channel](https://discord.gg/BYB2kvyFwm)
## Notes

View File

@@ -16,10 +16,6 @@
}
}
</style>
<py-env>
- numpy
- matplotlib
</py-env>
</head>
<body>
@@ -29,6 +25,14 @@
<p style='font-family: monospace sans-serif;'><big><big><big><big>&#10096;py&#10097;</big></big></big></big></p>
</div>
</div>
<py-config>
packages = [
"numpy",
"matplotlib"
]
</py-config>
<py-script>
import matplotlib.pyplot as plt
import numpy as np

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

16
docs/_static/images/pyscript.svg vendored Normal file
View File

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

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -54,7 +54,7 @@ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "_env", "README.md"]
#
html_theme = "pydata_sphinx_theme"
html_logo = "_static/images/avatar.jpg"
html_logo = "_static/images/pyscript.svg"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,

View File

@@ -0,0 +1,30 @@
# Deprecation Cycle
Pyscript is under heavy development, which means that some things may change, and some features might need to be deprecated so you can use different alternative implementations.
This page describes the deprecation cycle for pyscript.
## Deprecation Steps
1. Remove usage of deprecated features from all examples.
2. Add warnings to all elements/features marked for deprecation.
3. Release a new version of pyscript with the deprecation warnings.
4. Next release, remove the deprecated features.
## Deprecation Warnings
Deprecation warnings are added to the codebase using the `showWarning` function from the `pyscriptjs.utils` module.
This function creates a warning banner on the page if any of the deprecated features was used. You can use HTML to write the message; ideally, you should provide an alternative to the deprecated feature.
### Example
```js
import {showWarning} from './utils'
showWarning(`
<p>
The <code>py-deprecated</code> tag is deprecated. Please use the <code>py-actual</code> tag instead. Please refer to <a href="#">this documentation page</a> for more information.
</p>
`, "html")
```

View File

@@ -0,0 +1,203 @@
# Developing Process
This document is intended to help you get started developing for pyscript, it assumes that you have [setup your development environment](setting-up-environment.md).
* First, make sure you are using the latest version of the pyscript main branch
```
git pull upstream main
```
* Update your fork with the latest changes
```
git push origin main
```
* Activate the conda environment (this environment will contain all the necessary dependencies)
```
conda activate pyscriptjs/env/
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **NOTE**: We are assuming you are in the root folder. If you are in the pyscriptjs you can run `conda activate env/` instead.
* Install pre-commit (you only need to do this once)
```
pre-commit install
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **NOTE**: On first run, pre-commit installs a bunch of hooks that will be run when you commit changes to your branch - this will make sure that your code is following our style (it will also lint your code automatically).
* Create a branch for the issue that you want to work on
```
git checkout -b <your branch name>
```
* Work on your change
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **NOTE**: If you are working on a python file, you may encounter linting issues when pre-commit runs. Pyscript uses [black](https://black.readthedocs.io/en/stable/) to fix any linting problems automatically. All you need to do is add the changes again and commit using your previous commit message (the previous one that failed didn't complete due to black formatting files).
* Run tests before pushing the changes
```
make tests
```
* When you make changes locally, double check that your contribution follows the PyScript formatting rules by running `npm run lint`. Note that in this case you're looking for the errors, <u>**NOT**</u> the warnings (Unless the warning is created by a local change). If an error is found by lint you should fix it <u>**before**</u> creating a pull request
## Rebasing changes
Sometimes you might be asked to rebase main into your branch. Please refer to this [section on git rebase from GitHub docs](https://docs.github.com/en/get-started/using-git/about-git-rebase).
If you need help with anything, feel free to reach out and ask for help!
## pytest quick guide
We make a heavy usage of `pytest`. Here is a quick guide and collection of
useful options:
- To run all tests in the current directory and subdirectories: `pytest`
- To run tests in a specific directory or file: `pytest path/to/dir/test_foo.py`
- `-s`: disables output capturing
- `--pdb`: in case of exception, enter a `(Pdb)` prompt so that you can
inspect what went wrong.
- `-v`: verbose mode
- `-x`: stop the execution as soon as one test fails
- `-k foo`: run only the tests whose full name contains `foo`
- `-k 'foo and bar'`
- `-k 'foo and not bar'`
## Running integration tests under pytest
`make test` is useful to run all the tests, but during the development is
useful to have more control on how tests are run. The following guide assumes
that you are in the directory `pyscriptjs/tests/integration/`.
#### To run all the integration tests, single or multi core
```
$ pytest -xv
...
test_00_support.py::TestSupport::test_basic[chromium] PASSED [ 0%]
test_00_support.py::TestSupport::test_console[chromium] PASSED [ 1%]
test_00_support.py::TestSupport::test_check_js_errors_simple[chromium] PASSED [ 2%]
test_00_support.py::TestSupport::test_check_js_errors_expected[chromium] PASSED [ 3%]
test_00_support.py::TestSupport::test_check_js_errors_expected_but_didnt_raise[chromium] PASSED [ 4%]
test_00_support.py::TestSupport::test_check_js_errors_multiple[chromium] PASSED [ 5%]
...
```
`-x` means "stop at the first failure". `-v` means "verbose", so that you can
see all the test names one by one. We try to keep tests in a reasonable order,
from most basic to most complex. This way, if you introduced some bug in very
basic things, you will notice immediately.
If you have the `pytest-xdist` plugin installed, you can run all the
integration tests on 4 cores in parallel:
```
$ pytest -n 4
```
#### To run a single test, headless
```
$ pytest test_01_basic.py -k test_pyscript_hello -s
...
[ 0.00 page.goto ] pyscript_hello.html
[ 0.01 request ] 200 - fake_server - http://fake_server/pyscript_hello.html
...
[ 0.17 console.info ] [py-loader] Downloading pyodide-0.21.3...
[ 0.18 request ] 200 - CACHED - https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js
...
[ 3.59 console.info ] [pyscript/main] PyScript page fully initialized
[ 3.60 console.log ] hello pyscript
```
`-k` selects tests by pattern matching as described above. `-s` instructs
`pytest` to show the output to the terminal instead of capturing it. In the
output you can see various useful things, including network requests and JS
console messages.
#### To run a single test, headed
```
$ pytest test_01_basic.py -k test_pyscript_hello -s --headed
...
```
Same as above, but with `--headed` the browser is shown in a window, and you
can interact with it. The browser uses a fake server, which means that HTTP
requests are cached.
Unfortunately, in this mode source maps does not seem to work, and you cannot
debug the original typescript source code. This seems to be a bug in
playwright, for which we have a workaround:
```
$ pytest test_01_basic.py -k test_pyscript_hello -s --headed --no-fake-server
...
```
As the name implies, `-no-fake-server` disables the fake server: HTTP requests
are not cached, but source-level debugging works.
Finally:
```
$ pytest test_01_basic.py -k test_pyscript_hello -s --dev
...
```
`--dev` implies `--headed --no-fake-server`. In addition, it also
automatically open chrome dev tools.
#### Fake server, HTTP cache
By default, our test machinery uses a playwright router which intercepts and
cache HTTP requests, so that for example you don't have to download pyodide
again and again. This also enables the possibility of running tests in
parallel on multiple cores.
The cache is stored using the `pytest-cache` plugin, which means that it
survives across sessions.
If you want to temporarily disable the cache, the easiest thing is to use
`--no-fake-server`, which bypasses it completely.
If you want to clear the cache, you can use the special option
`--clear-http-cache`:
```
$ pytest --clear-http-cache
...
-------------------- SmartRouter HTTP cache --------------------
Requests found in the cache:
https://raw.githubusercontent.com/pyscript/pyscript/main/README.md
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/repodata.json
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.asm.js
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/micropip-0.1-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.asm.data
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.asm.wasm
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide_py.tar
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyparsing-3.0.9-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/distutils.tar
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/packaging-21.3-py3-none-any.whl
Cache cleared
```
**NOTE**: this works only if you are inside `tests/integration`, or if you
explicitly specify `tests/integration` from the command line. This is due to
how `pytest` decides to search for and load the various `conftest.py`.

12
docs/development/index.md Normal file
View File

@@ -0,0 +1,12 @@
# Development
This section contains various topics related to pyscript development.
```{toctree}
---
maxdepth: 1
---
setting-up-environment
deprecation-cycle
developing
```

View File

@@ -0,0 +1,69 @@
# Setting up your development environment
* Fork the repository - [quicklink](https://github.com/pyscript/pyscript/fork)
* Clone your fork of the project
```
git clone https://github.com/<your username>/pyscript
```
* Add the original project as your upstream (this will allow you to pull the latest changes)
```sh
git remote add upstream git@github.com:pyscript/pyscript.git
```
* cd into the `pyscriptjs` folder using the line below in your terminal (if your terminal is already in pyscript then use **cd pyscriptjs** instead)
```
cd pyscript/pyscriptjs
```
* Install the dependencies with the command below (you must have node >=16)
```
make setup
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **NOTE**: If `make setup` gives a node/npm version required error then go to [troubleshooting](https://github.com/pyscript/pyscript/blob/main/TROUBLESHOOTING.md)
* You can also run the examples locally by running the command below in your terminal
```
make examples
```
* Run ***npm run dev*** to build and run the dev server. This will also watch for changes and rebuild when a file is saved.
```
npm run dev
```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **NOTE**: To access your local build paste `http://localhost:8080` into your browser
Now that node and npm have both been updated `make setup` should work, and you can continue [setting up your local environment](setting-up-environment.md) without problems (hopefully).
## Setting up and building the docs
To build the documentation locally first make sure you are in the `docs` directory.
You'll need `make` and `conda` installed in your machine. The rest of the environment should be automatically download and created for you once you use the command:
```
make setup
```
Use `conda activate $environment_name` to activate your environment.
To add new information to the documentation make sure you conform with PyScript's code of conduct and with the general principles of Diataxis. Don't worry about reading too much on it, just do your best to keep your contributions on the correct axis.
Write your documentation files using [Markedly Structured Text](https://myst-parser.readthedocs.io/en/latest/syntax/optional.html), which is very similar to vanilla Markdown but with some addons to create the documentation infrastructure.
Once done, initialize a server to check your work:
```
make livehtml
```
Visible here: [http://127.0.0.1:8000](http://127.0.0.1:8000)

3
docs/error.md Normal file
View File

@@ -0,0 +1,3 @@
# Not found!
The page that you looked for could not be found. If you think this is a mistake, please [open an issue](https://github.com/pyscript/pyscript/issues/new/)

35
docs/guides/asyncio.md Normal file
View File

@@ -0,0 +1,35 @@
# Using Async/Await and Asyncio
## {bdg-warning-line}`Deprecated` Implicit Coroutine Scheduling / Top-Level Await
In PyScript versions 2022.09.1 and earlier, \<py-script\> tags could be written in a way that enabled "Implicit Coroutine Scheduling." The keywords `await`, `async for` and `await with` were permitted to be used outside of `async` functions. Any \<py-script\> tags with these keywords at the top level were compiled into coroutines and automatically scheuled to run in the browser's event loop. This functionality was deprecated, and these keywords are no longer allowed outside of `async` functions.
To transition code from using top-level await statements to the currently-acceptable syntax, wrap the code into a coroutine using `async def()` and schedule it to run in the browser's event looping using `asyncio.ensure_future()` or `asyncio.create_task()`.
The following two pieces of code are functionally equivalent - the first only works in versions 2022.09.1, the latter is the currently acceptable equivalent.
```python
# This version is deprecated, since
# it uses 'await' outside an async function
<py-script>
import asyncio
for i in range(3):
print(i)
await asyncio.sleep(1)
</py-script>
```
```python
# This version is acceptable
<py-script>
import asyncio
async def main():
for i in range(3):
print(i)
await asyncio.sleep(1)
asyncio.ensure_future(main())
</py-script>
```

View File

@@ -14,14 +14,18 @@ from `PyScript` using Python, since currently, the common tools such as `request
The `fetch` API is a modern way to make HTTP requests. It is available in all modern browsers, and in Pyodide.
Although there are two ways to use `fetch`, 1) using `JavaScript` from `PyScript`, and 2) using Pyodide's Python wrapper,
`Pyodide.http.pyfetch`, this example will only show how to use the Python wrapper. Still, the
Although there are two ways to use `fetch`:
1) using `JavaScript` from `PyScript`
2) using Pyodide's Python wrapper,
`pyodide.http.pyfetch`
This example will only show how to use the Python wrapper. Still, the
[fetch documentation](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters) is a useful reference, as its
parameters can be called from Python using the `pyfetch` wrapper.
## Pyodide.http, pyfetch, and FetchResponse
[Pyodide.http module](https://pyodide.org/en/stable/usage/api/python-api/http.html#module-pyodide.http) is a Python API
The [pyodide.http module](https://pyodide.org/en/stable/usage/api/python-api/http.html#module-pyodide.http) is a Python API
for dealing with HTTP requests. It provides the `pyfetch` function as a wrapper for the `fetch` API,
which returns a `FetchResponse` object whenever a request is made. Extra keyword arguments can be passed to `pyfetch`
which will be passed to the `fetch` API.
@@ -92,13 +96,13 @@ concluding html code.
<title>GET, POST, PUT, DELETE example</title>
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="../build/pyscript.css" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="../build/pyscript.js"></script>
<py-env>
- paths:
- /request.py
</py-env>
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-config>
[[fetch]]
files = ["/request.py"]
</py-config>
</head>
<body><p>
@@ -106,34 +110,33 @@ concluding html code.
Here is the output of your request:
</p>
<py-script>
```
```python
import asyncio # important!!
import json
from request import request # import our request function.
import asyncio
import json
from request import request # import our request function.
baseurl = "https://jsonplaceholder.typicode.com/"
async def main():
baseurl = "https://jsonplaceholder.typicode.com"
# GET
headers = {"Content-type": "application/json"}
response = await request(baseurl+"posts/2", method="GET", headers=headers)
print(f"GET request=> status:{response.status}, json:{await response.json()}")
# GET
headers = {"Content-type": "application/json"}
response = await request(f"{baseurl}/posts/2", method="GET", headers=headers)
print(f"GET request=> status:{response.status}, json:{await response.json()}")
# POST
body = json.dumps({"title": "test_title", "body": "test body", "userId": 1})
new_post = await request(baseurl+"posts", body=body, method="POST", headers=headers)
print(f"POST request=> status:{new_post.status}, json:{await new_post.json()}")
# POST
body = json.dumps({"title": "test_title", "body": "test body", "userId": 1})
new_post = await request(f"{baseurl}/posts", body=body, method="POST", headers=headers)
print(f"POST request=> status:{new_post.status}, json:{await new_post.json()}")
# PUT
body = json.dumps({"id": 1, "title": "test_title", "body": "test body", "userId": 2})
new_post = await request(baseurl+"posts/1", body=body, method="PUT", headers=headers)
print(f"PUT request=> status:{new_post.status}, json:{await new_post.json()}")
# PUT
body = json.dumps({"id": 1, "title": "test_title", "body": "test body", "userId": 2})
new_post = await request(f"{baseurl}/posts/1", body=body, method="PUT", headers=headers)
print(f"PUT request=> status:{new_post.status}, json:{await new_post.json()}")
# DELETE
new_post = await request(baseurl+"posts/1", method="DELETE", headers=headers)
print(f"DELETE request=> status:{new_post.status}, json:{await new_post.json()}")
```
```html
# DELETE
new_post = await request(f"{baseurl}/posts/1", method="DELETE", headers=headers)
print(f"DELETE request=> status:{new_post.status}, json:{await new_post.json()}")
asyncio.ensure_future(main())
</py-script>
<div>
@@ -153,8 +156,8 @@ print(f"DELETE request=> status:{new_post.status}, json:{await new_post.json()}"
```
## Explanation
### `py-env` tag for importing our Python code
The very first thing to notice is the `py-env` tag. This tag is used to import Python files into the `PyScript`.
### `py-config` tag for importing our Python code
The very first thing to notice is the `py-config` tag. This tag is used to import Python files into the `PyScript`.
In this case, we are importing the `request.py` file, which contains the `request` function we wrote above.
### `py-script` tag for making async HTTP requests.
@@ -181,7 +184,7 @@ HTTP requests are defined by standards-setting bodies in [RFC 1945](https://www.
# Conclusion
This tutorial demonstrates how to make HTTP requests using `pyfetch` and the `FetchResponse` objects. Importing Python
code/files into the `PyScript` using the `py-env` tag is also covered.
code/files into the `PyScript` using the `py-config` tag is also covered.
Although a simple example, the principals here can be used to create complex web applications inside of `PyScript`,
or load data into `PyScript` for use by an application, all served as a static HTML page, which is pretty amazing!
@@ -195,7 +198,7 @@ await pyodide.http.pyfetch(url: str, **kwargs: Any) -> FetchResponse
```
Use `pyfetch` to make HTTP requests in `PyScript`. This is a wrapper around the `fetch` API. Returns a `FetchResponse`.
### [`pyfetch` Docs.](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.pyfetch)
- [`pyfetch` Docs.](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.pyfetch)
## pyodide.http.FetchResponse
### Usage
@@ -207,4 +210,4 @@ json = await response.json()
Class for handling HTTP responses. This is a wrapper around the `JavaScript` fetch `Response`. Contains common (async)
methods and properties for handling HTTP responses, such as `json()`, `url`, `status`, `headers`, etc.
### [`FetchResponse` Docs.](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.FetchResponse)
- [`FetchResponse` Docs.](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.FetchResponse)

View File

@@ -1,4 +1,4 @@
# How-to guides
# Guides
Welcome to the how-to documentation section for PyScript. If you've already
gained some experience with PyScript before and just need practical guides
@@ -16,4 +16,5 @@ caption: 'Contents:'
---
passing-objects
http-requests
asyncio
```

View File

@@ -0,0 +1,295 @@
# How to Pass Objects from PyScript to Javascript (and Vice Versa)
[Pyodide](https://pyodide.org), the runtime that underlies PyScript, does a lot of work under the hood to translate objects between Python and JavaScript. This allows code in one language to access objects defined in the other.
This guide discusses how to pass objects between JavaScript and Python within PyScript. For more details on how Pyodide handles translating and proxying objects between the two languages, see the [Pyodide Type Translations Page](https://pyodide.org/en/stable/usage/type-conversions.html).
For our purposes, an 'object' is anything that can be bound to a variable (a number, string, object, [function](https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function), etc).
## JavaScript to PyScript
We can use the syntax `from js import ...` to import JavaScript objects directly into PyScript. Simple JavaScript objects are converted to equivalent Python types; these are called [implicit conversions](https://pyodide.org/en/stable/usage/type-conversions.html#implicit-conversions). More complicated objects are wrapped in [JSProxy](https://pyodide.org/en/stable/usage/type-conversions.html) objects to make them behave like Python objects.
`import js` and `from js import ...` [in Pyodide](https://pyodide.org/en/stable/usage/type-conversions.html#type-translations-using-js-obj-from-py) get objects from the [JavaScript globalThis scope](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis), so keep the[ rules of JavaScript variable scoping](https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/) in mind.
```html
<script>
name = "Guido" //A JS variable
// Define a JS Function
function addTwoNumbers(x, y){
return x + y;
}
</script>
```
```python
<py-script>
# Import and use JS function and variable into Python
from js import name, addTwoNumbers
print(f"Hello {name}")
print("Adding 1 and 2 in Javascript: " + str(addTwoNumbers(1, 2)))
</py-script>
```
## PyScript to JavaScript
### Using Pyodide's globals access
The [PyScript JavaScript module](../reference/modules/pyscript.md) exposes its underlying Pyodide runtime as `PyScript.runtime`, and maintains a reference to the [globals()](https://docs.python.org/3/library/functions.html#globals) dictionary of the Python namespace. Thus, any global variables in python are accessible in JavaScript at `PyScript.runtime.globals.get('my_variable_name')`
```html
<body>
<py-script>x = 42</py-script>
<button onclick="showX()">Click Me to Get 'x' from Python</button>
<script>
function showX(){
console.log(`In Python right now, x = ${pyscript.runtime.globals.get('x')}`)
}
</script>
</body>
```
Since [everything is an object](https://docs.python.org/3/reference/datamodel.html) in Python, this applies not only to user created variables, but also to classes, functions, built-ins, etc. If we want, we can even apply Python functions to JavaScript data and variables:
```html
<body>
<!-- Click this button to log 'Apple', 'Banana', 'Candy', 'Donut' by sorting in Python-->
<button onclick="sortInPython(['Candy', 'Donut', 'Apple', 'Banana'])">Sort In Python And Log</button>
<script>
function sortInPython(data){
js_sorted = pyscript.runtime.globals.get('sorted') //grab python's 'sorted' function
const sorted_data = js_sorted(data) //apply the function to the 'data' argument
for (const item of sorted_data){
console.log(item)
}
}
</script>
</body>
```
### Using JavaScript's eval()
There may be some situations where it isn't possible or ideal to use `PyScript.runtime.globals.get()` to retrieve a variable from the Pyodide global dictionary. For example, some JavaScript frameworks may take a function/Callable as an html attribute in a context where code execution isn't allowed (i.e. `get()` fails). In these cases, you can create JavaScript proxies of Python objects more or less "manually" using [JavaScript's eval() function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval), which executes a string as code much like [Python's eval()](https://docs.python.org/3/library/functions.html#eval).
First, we create a JS function `createObject` which takes an object and a string, then uses `eval()` to create a variable named after the string and bind it to that object. By calling this function from PyScript (where we have access to the Pyodide global namespace), we can bind JavaScript variables to Python objects without having direct access to that global namespace.
Include the following script tag anywhere in your html document:
```html
<script>
function createObject(object, variableName){
//Bind a variable whose name is the string variableName
// to the object called 'object'
let execString = variableName + " = object"
console.log("Running '" + execString + "'");
eval(execString)
}
</script>
```
This function takes a Python Object and creates a variable pointing to it in the JavaScript global scope.
### Exporting all Global Python Objects
We can use our new `createObject` function to "export" the entire Python global object dictionary as a JavaScript object:
```python
<py-script>
from js import createObject
from pyodide.ffi import create_proxy
createObject(create_proxy(globals()), "pyodideGlobals")
</py-script>
```
This will make all Python global variables available in JavaScript with `pyodideGlobals.get('my_variable_name')`.
(Since PyScript tags evaluate _after_ all JavaScript on the page, we can't just dump a `console.log(...)` into a `<script>` tag, since that tag will evaluate before any PyScript has a chance to. We need to delay accessing the Python variable in JavaScript until after the Python code has a chance to run. The following example uses a button with `id="do-math"` to achieve this, but any method would be valid.)
```python
<py-script>
# create some Python objects:
symbols = {'pi': 3.1415926, 'e': 2.7182818}
def rough_exponential(x):
return symbols['e']**x
class Circle():
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return symbols['pi'] * self.radius**2
</py-script>
```
```html
<input type="button" value="Log Python Variables" id="do-math">
<script>
document.getElementById("do-math").addEventListener("click", () => {
const exp = pyodideGlobals.get('rough_exponential');
console.log(`e squared is about ${exp(2)}`);
const c = pyodideGlobals.get('Circle')(4);
console.log(`The area of c is ${c.area}`);
});
</script>
```
#### Full example
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Exporting all Global Python Objects</title>
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<input type="button" value="Log Python Variables" id="do-math">
<py-script>
from js import createObject
from pyodide.ffi import create_proxy
createObject(create_proxy(globals()), "pyodideGlobals")
# create some Python objects:
symbols = {'pi': 3.1415926, 'e': 2.7182818}
def rough_exponential(x):
return symbols['e']**x
class Circle():
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return symbols['pi'] * self.radius**2
</py-script>
<script>
function createObject(object, variableName){
//Bind a variable whose name is the string variableName
// to the object called 'object'
let execString = variableName + " = object"
console.log("Running '" + execString + "'");
eval(execString)
}
document.getElementById("do-math").addEventListener("click", () => {
const exp = pyodideGlobals.get('rough_exponential');
console.log(`e squared is about ${exp(2)}`);
const c = pyodideGlobals.get('Circle')(4);
console.log(`The area of c is ${c.area}`);
});
</script>
</body>
</html>
```
### Exporting Individual Python Objects
We can also export individual Python objects to the JavaScript global scope if we wish.
(As above, the following example uses a button to delay the execution of the `<script>` until after the PyScript has run.)
```python
<py-script>
import js
from pyodide.ffi import create_proxy
# Create 3 python objects
language = "Python 3"
animals = ['dog', 'cat', 'bird']
multiply3 = lambda a, b, c: a * b * c
# js object can be named the same as Python objects...
js.createObject(language, "language")
# ...but don't have to be
js.createObject(create_proxy(animals), "animals_from_py")
# functions are objects too, in both Python and Javascript
js.createObject(create_proxy(multiply3), "multiply")
</py-script>
```
```html
<input type="button" value="Log Python Variables" id="log-python-variables">
<script>
document.getElementById("log-python-variables").addEventListener("click", () => {
console.log(`Nice job using ${language}`);
for (const animal of animals_from_py){
console.log(`Do you like ${animal}s? `);
}
console.log(`2 times 3 times 4 is ${multiply(2,3,4)}`);
});
</script>
```
#### Full example
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Exporting Individual Python Objects</title>
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-script>
import js
from pyodide.ffi import create_proxy
# Create 3 python objects
language = "Python 3"
animals = ['dog', 'cat', 'bird']
multiply3 = lambda a, b, c: a * b * c
# js object can be named the same as Python objects...
js.createObject(language, "language")
# ...but don't have to be
js.createObject(create_proxy(animals), "animals_from_py")
# functions are objects too, in both Python and Javascript
js.createObject(create_proxy(multiply3), "multiply")
</py-script>
<input type="button" value="Log Python Variables" id="log-python-variables">
<script>
function createObject(object, variableName){
//Bind a variable whose name is the string variableName
// to the object called 'object'
let execString = variableName + " = object"
console.log("Running '" + execString + "'");
eval(execString)
}
document.getElementById("log-python-variables").addEventListener("click", () => {
console.log(`Nice job using ${language}`);
for (const animal of animals_from_py){
console.log(`Do you like ${animal}s? `);
}
console.log(`2 times 3 times 4 is ${multiply(2,3,4)}`);
});
</script>
</body>
</html>
```

View File

@@ -1,140 +0,0 @@
# How to Pass Objects from PyScript to Javascript (and Vice Versa)
[Pyodide](https://pyodide.org), the runtime that underlies PyScript, does a lot of work under the hood to translate objects between Python and JavaScript. This allows code in one language to access objects defined in the other.
This guide discusses how to pass objects between JavaScript and Python within PyScript. For more details on how Pyodide handles translating and proxying objects between the two languages, see the [Pyodide Type Translations Page](https://pyodide.org/en/stable/usage/type-conversions.html).
For our purposes, an 'object' is anything that can be bound to a variable (a number, string, object, [function](https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function), etc).
## JavaScript to PyScript
We can use the syntax `from js import ...` to import JavaScript objects directly into PyScript. Simple JavaScript objects are converted to equivalent Python types; these are called [implicit conversions](https://pyodide.org/en/stable/usage/type-conversions.html#implicit-conversions). More complicated objects are wrapped in [JSProxy](https://pyodide.org/en/stable/usage/type-conversions.html) objects to make them behave like Python objects.
`import js` and `from js import ...` [in Pyodide](https://pyodide.org/en/stable/usage/type-conversions.html#type-translations-using-js-obj-from-py) get objects from the [JavaScript globalThis scope](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis), so keep the[ rules of JavaScript variable scoping](https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/) in mind.
```html
<script>
name = "Guido" //A JS variable
// Define a JS Function
function addTwoNumbers(x, y){
return x + y;
}
</script>
```
```python
<py-script>
# Import and use JS function and variable into Python
from js import name, addTwoNumbers
print(f"Hello {name}")
print("Adding 1 and 2 in Javascript: " + str(addTwoNumbers(1, 2)))
</py-script>
```
## PyScript to JavaScript
Since [PyScript doesn't export its instance of Pyodide](https://github.com/pyscript/pyscript/issues/494) and only one instance of Pyodide can be running in a browser window at a time, there isn't currently a way for Javascript to access Objects defined inside PyScript tags "directly".
We can work around this limitation using [JavaScript's eval() function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval), which executes a string as code much like [Python's eval()](https://docs.python.org/3/library/functions.html#eval). First, we create a JS function `createObject` which takes an object and a string, then uses `eval()` to create a variable named after the string and bind it to that object. By calling this function from PyScript (where we have access to the Pyodide global namespace), we can bind JavaScript variables to Python objects without having direct access to that global namespace.
Include the following script tag anywhere in your html document:
```html
<script>
function createObject(object, variableName){
//Bind a variable whose name is the string variableName
// to the object called 'object'
let execString = variableName + " = object"
console.log("Running '" + execString + "'");
eval(execString)
}
</script>
```
This function takes a Python Object and creates a variable pointing to it in the JavaScript global scope.
### Exporting all Global Python Objects
We can use our new `createObject` function to "export" the entire Python global object dictionary as a JavaScript object:
```python
<py-script>
from js import createObject
from pyodide.ffi import create_proxy
createObject(create_proxy(globals()), "pyodideGlobals")
</py-script>
```
This will make all Python global variables available in JavaScript with `pyodideGlobals.get('my_variable_name')`.
(Since PyScript tags evaluate _after_ all JavaScript on the page, we can't just dump a `console.log(...)` into a `<script>` tag, since that tag will evaluate before any PyScript has a chance to. We need to delay accessing the Python variable in JavaScript until after the Python code has a chance to run. The following example uses a button with `id="do-math"` to achieve this, but any method would be valid.)
```python
<py-script>
# create some Python objects:
symbols = {'pi': 3.1415926, 'e': 2.7182818}
def rough_exponential(x):
return symbols['e']**x
class Circle():
def __init__(self, radius):
self.radius = radius
@property
def area:
return symbols['pi'] * self.radius**2
</py-script>
```
```html
<input type="button" value="Log Python Variables" id="do-mmath">
<script>
document.getElementById("do-math").addEventListener("click", () => {
const exp = pyodideGlobals.get('rough_exponential');
console.log("e squared is about ${exp(2)}");
const c = pyodideGlobals.get('Circle')(4);
console.log("The area of c is ${c.area}");
});
</script>
```
### Exporting Individual Python Objects
We can also export individual Python objects to the JavaScript global scope if we wish.
(As above, the following example uses a button to delay the execution of the `<script>` until after the PyScript has run.)
```python
<py-script>
import js
from pyodide.ffi import create_proxy
# Create 3 python objects
language = "Python 3"
animals = ['dog', 'cat', 'bird']
multiply3 = lambda a, b, c: a * b * c
# js object can be named the same as Python objects...
js.createObject(language, "language")
# ...but don't have to be
js.createObject(create_proxy(animals), "animals_from_py")
# functions are objects too, in both Python and Javascript
js.createObject(create_proxy(multiply3), "multiply")
</py-script>
```
```html
<input type="button" value="Log Python Variables" id="log-python-variables">
<script>
document.getElementById("log-python-variables").addEventListener("click", () => {
console.log(`Nice job using ${language}`);
for (const animal of animals_from_py){
console.log(`Do you like ${animal}s? `);
}
console.log(`2 times 3 times 4 is ${multiply(2,3,4)}`);
});
</script>
```

View File

@@ -1,10 +1,5 @@
# PyScript
```{warning}
Please note, this documentation is just a placeholder and **should not be used
in reference material**. Thank you!
```
Welcome to the PyScript documentation!
PyScript provides a way for you to run Python code directly in your browser, giving
@@ -22,12 +17,15 @@ Just getting started with PyScript?
Check out our [getting started guide](tutorials/getting-started.md)!
:::
:::{grid-item-card} [How-to guides](howtos/index.md)
:::{grid-item-card} [Guides](guides/index.md)
You already know the basics and want to learn specifics!
[Passing Objects between JavaScript and Python](howtos/passing-objects.md)
[Making async HTTP requests in pure Python](howtos/http-requests.md)
[Passing Objects between JavaScript and Python](guides/passing-objects.md)
[Making async HTTP requests in pure Python](guides/http-requests.md)
[Async/Await and Asyncio](guides/asyncio.md)
:::
:::{grid-item-card} [Concepts](concepts/index.md)
@@ -39,6 +37,8 @@ You already know the basics and want to learn specifics!
[Frequently asked questions](reference/faq.md)
[The PyScript JS Module](reference/modules/pyscript.md)
:::{toctree}
:maxdepth: 1
@@ -51,7 +51,7 @@ maxdepth: 1
hidden:
---
tutorials/index
howtos/index
guides/index
concepts/index
reference/index
```

View File

@@ -0,0 +1,8 @@
# `__version__`
`PyScript.__version__` is a `str` representing the current version of PyScript in a human-readable form. For a structured version more suitable to comparisons, and for details of what each part of the version number represents, see [`version_info`](version_info.md)
```shell
>>> pyscript.__version__
'2023.02.1.dev'
```

View File

@@ -0,0 +1,87 @@
# `display(*values, target=None, append=True)`
## Parameters
`*values` - the objects to be displayed. String objects are output as-written. For non-string objects, the default content to display is the the object's `repr()`. Objects may implement the following methods to indicate that they should be displayed as a different MIME type. MIME types with a * indicate that the content will be wrapped in the appropriate html tags and attributes before output:
| Method | Inferred MIME type |
|---------------------|------------------------|
| `__repr__` | text/plain |
| `_repr_html_` | text/html |
| `_repr_svg_` | image/svg+xml |
| `_repr_png_` | image/png* |
| `_repr_pdf_` | application/pdf |
| `_repr_jpeg_` | image/jpeg* |
| `_repr_json_` | application/json |
| `_repr_javascript_` | application/javascript*|
| `savefig` | image/png |
| | |
`target` - Element's ID. The default value for `target` is the current `py-script` tag ID, it's possible to specify different IDs for this parameter
`append` - `boolean` if the output is going to be appended or not to the `target`ed element. It creates a `<div>` tag if `True` and a `<py-script>` tag with a random ID if `False`. The default value for `append` is `True`.
### Description
Display is the default function to display objects on the screen. Functions like the Python `print()` or JavaScript `console.log()` are now defaulted to only appear on the terminal.
Display will throw an exception if the target is not clear. E.g. the following code is invalid:
```html
<py-script>
def display_hello():
# this fails because we don't have any implicit target
# from event handlers
display('hello')
</py-script>
<button id="my-button" py-onClick="display_hello()">Click me</button>
```
Because it's considered unclear if the `hello` string should be displayed underneath the `<py-script>` tag or the `<button>` tag.
To write compliant code, make sure to specify the target using the `target` parameter, for example:
```html
<py-script>
def display_hello():
# this fails because we don't have any implicit target
# from event handlers
display('hello', target="helloDiv")
</py-script>
<div id="helloDiv"></div>
<button id="my-button" py-onClick="display_hello()">Click me</button>
```
#### Using matplotlib with display
`matplotlib` has two ways of plotting things as mentioned [here](https://matplotlib.org/matplotblog/posts/pyplot-vs-object-oriented-interface/)
- In case of using the `pyplot` interface, the graph can be shown using `display(plt)`.
```python
import matplotlib.pyplot as plt
import numpy as np
# Data for plotting
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
plt.plot(t,s)
display(plt)
```
- In case of using the `object oriented` interface, the graph can be shown using `display(fig)` or `display(plt)` both.
```python
import matplotlib.pyplot as plt
import numpy as np
# Data for plotting
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
fig, ax = plt.subplots()
ax.plot(t, s)
display(fig) # but even display(plt) would have worked!
```

View File

@@ -0,0 +1,309 @@
# `Element`
The `Element` API is a helpful way to create and manipulate elements in the DOM. It is a wrapper around the native DOM API, and is designed to be as intuitive as possible.
## Methods and Properties
| Property | Description |
|----------|-----------------------------------------|
| `element` | Returns the element with the given ID. |
| `id` | Returns the element's ID. |
| `value` | Returns the element's value. |
| `innerHtml` | Returns the element's inner HTML. |
| Method | Description |
|----------------------|--------------------------------------------------------------|
| `write` | Writes `value` to element and handles various mime types. `append` defaults to `False`, if set to true, it will create a child element. |
| `clear` | Clears the element's value or content. |
| `select` | Select element from `query` which uses [Document.querySelector()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector). |
| `clone` | Clones the with `new_id` if provided and `to` element if provided. |
| `remove_class` | Removes one or more class name from the element. |
| `add_class` | Adds one or more class name to the element. |
## Element.element
| Parameter | Default | Type |
|-----------|---------|------|
| | | |
The `element` property returns the DOM element with the given ID.
```html
from pyscript import Element
my_div = Element('my-div')
print(my_div.element)
```
## Element.id
| Parameter | Default | Type |
|-----------|---------|------|
| | | |
Return the element's ID.
```html
<div id="my-div"></div>
<py-script>
from pyscript import Element
my_div = Element('my-div')
print(my_div.id) # prints 'my-div'
</py-script>
```
## Element.value
| Parameter | Default | Type |
|-----------|---------|------|
| | | |
Return the element's value.
```html
<input id="my-input" value="hello world"></input>
<py-script>
from pyscript import Element
my_input = Element('my-input')
print(my_input.value) # prints 'hello world'
</py-script>
```
## Element.innerHtml
| Parameter | Default | Type |
|-----------|---------|------|
| | | |
Return the element's inner HTML.
```html
<div id="my-innerHtml">
<b>hello world</b>
</div>
<py-script>
from pyscript import Element
my_innerHtml = Element('my-innerHtml')
print(my_innerHtml.innerHtml) # prints <b> hello world </b>
</py-script>
```
## Element.write
| Parameter | Default | Type |
|-------------|---------|-----------------------------|
| `value` | | `str` or `__mime_type__` |
| `append` | False | `bool` |
Writes `value` to element and handles various mime types. This method also contains a `append` parameter, which defaults to `False`.
Currently, these are the MIME types that are supported when rendering content using this method
| Method | Inferred MIME type |
|---------------------|------------------------|
| `__repr__` | text/plain |
| `_repr_html_` | text/html |
| `_repr_svg_` | image/svg+xml |
| `_repr_png_` | image/png* |
| `_repr_pdf_` | application/pdf |
| `_repr_jpeg_` | image/jpeg* |
| `_repr_json_` | application/json |
| `_repr_javascript_` | application/javascript*|
| `savefig` | image/png |
```html
<div id="foo"></div>
<py-script>
from pyscript import Element
el = Element("foo")
el.write("Hello!")
el.write("World!") # will replace the previous content
</py-script>
```
If we set `append` to `True`, it will create a child element using a `div`.
```html
<div id="foo"></div>
<py-script>
from pyscript import Element
el = Element("foo")
el.write("Hello!", append=True)
# This will create a child div with the id "foo-1"
el.write("World!", append=True)
</py-script>
```
## Element.clear
| Parameter | Default | Type |
|-----------|---------|------|
| | | |
Clears the element's value or content. For example, we can clear the value of an input element.
```html
<input id="foo" value="Hello!"></input>
<py-script>
from pyscript import Element
el = Element("foo")
el.clear() # Removes value from input
</py-script>
```
Or we can clear the content of a div element.
```html
<div id="foo">Hello!</div>
<py-script>
from pyscript import Element
el = Element("foo")
el.clear() # Removes Hello from div content
</py-script>
```
## Element.select
Select element from `query`, it will look into the main Element if `from_content` is `True`. This method is a wrapper of [Document.querySelector()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector).
```html
<div id="foo">
<div id="bar"></div>
</div>
<py-script>
from pyscript import Element
el = Element("foo")
bar = el.select("#bar")
print(bar.id) # prints 'bar'
</py-script>
```
## Element.clone
| Parameter | Default | Type |
|-------------|---------|-----------|
| `new_id` | None | `str` |
| `to` | None | `Element` |
Clones the element to a new element. You can provide `new_id` to set a different id to the cloned element. You can also use a `to` element to append the cloned element to.
```html
<div id="foo">
HI!
</div>
<py-script>
from pyscript import Element
el = Element("foo")
# Creates two divs with the id "foo" and content "HI!"
el.clone()
</py-script>
```
It's always a good idea to pass a new id to the element you are cloning to avoid confusion if you need to reference the element by id again.
```html
<div id="foo">Hello!</div>
<py-script>
from pyscript import Element
el = Element("foo")
# Clones foo and its contents, but uses the id 'bar'
el.clone(new_id="bar")
</py-script>
```
You can also clone an element into another element.
```html
<div id="bond">
Bond
</div>
<div id="james">
James
</div>
<py-script>
from pyscript import Element
bond_div = Element("bond")
james_div = Element("james")
bond_div.clone(new_id="bond-2", to=james_div)
</py-script>
```
## Element.remove_class
| Parameter | Default | Type |
|-------------|---------|-----------------------|
| `classname` | None | `str` or `List[str]` |
Removes one or more class names from the element.
```html
<div id="foo" class="bar baz"></div>
<py-script>
from pyscript import Element
el = Element("foo")
el.remove_class("bar")
</py-script>
```
You can also remove multiple classes by passing a list of strings.
```html
<div id="foo" class="bar baz"></div>
<py-script>
from pyscript import Element
el = Element("foo")
el.remove_class(["bar", "baz"]) # Remove all classes from element
</py-script>
```
## Element.add_class
| Parameter | Default | Type |
|-------------|---------|-----------------------|
| `classname` | None | `str` or `List[str]` |
Adds one or more class names to the element.
```html
<style> .red { color: red; } </style>
<div id="foo">Hi!</div>
<py-script>
from pyscript import Element
el = Element("foo")
el.add_class("red")
</py-script>
```
You can also add multiple classes at once by passing a list of strings.
```html
<style> .red { color: red; } .bold { font-weight: bold; } </style>
<div id="foo">Hi!</div>
<py-script>
from pyscript import Element
el = Element("foo")
el.add_class(["red", "bold"])
</py-script>
```

View File

@@ -0,0 +1,16 @@
# `version_info`
`PyScript.version_info` is a `namedtuple` representing the current version of PyScript. It can be used to compare whether current version precedes or follows a desired version. For a human-readable version of the same info, see [`__version__`](__version__.md)
```sh
>>> pyscript.version_info
version_info(year=2023, month=2, minor=1, releaselevel='dev')
```
## Version Fields
| **parameter** | **CalVer equivalent field** | **example value** | **description** |
|-----------------|-----------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------|
| `year` | Full year (YYYY) | 2023 | The year of the release; when printed or represented as a string, always written with 4 digits |
| `month` | Short Month (MM) | 2 | The month of the release; when printed or represented as a string, written with 1 or 2 digits as necessary |
| `minor` | | 1 | The incremental number of the release for this month; when printed or represented as a string, written with 1 or two digits as necessary |
| `releaselevel` | | 'dev' | A string representing the qualifications of this build |

View File

@@ -0,0 +1,487 @@
# &lt;py-config&gt;
Use the `<py-config>` tag to set and configure general metadata along with declaring dependencies for your PyScript application. The configuration has to be set in either [TOML](https://toml.io/)(default) or [JSON](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON) format.
If you are unfamiliar with TOML, consider [reading about it](https://learnxinyminutes.com/docs/toml/) or if you are unfamiliar with JSON, consider reading [freecodecamp's JSON for beginners](https://www.freecodecamp.org/news/what-is-json-a-json-file-example/) guide for more information.
The `<py-config>` element should be placed within the `<body>` element.
## Attributes
| attribute | type | default | description |
|-----------|--------|---------|---------------------------------------------------------------------------------------------------------|
| **type** | string | "toml" | Syntax type of the `<py-config>`. Value can be `json` or `toml`. Default: "toml" if type is unspecifed. |
| **src** | url | | Source url to an external configuration file. |
## Examples
### Defining an inline config
- `<py-config>` using TOML (default)
```{note}
Reminder: when using TOML, any Arrays of Tables defined with double-brackets (like `[[runtimes]]` and `[[fetch]]` must come after individual keys (like `plugins = ...` and `packages=...`)
```
```html
<py-config>
[splashscreen]
autoclose = true
[[runtimes]]
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
name = "pyodide-0.21.2"
lang = "python"
</py-config>
```
- `<py-config>` using JSON via `type` attribute
```html
<py-config type="json">
{
"splashscreen": {
"autoclose": true
},
"runtimes": [{
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
"name": "pyodide-0.21.2",
"lang": "python"
}]
}
</py-config>
```
### Defining a file based config
- Use of the `src` attribute
```html
<py-config src="./custom.toml"></py-config>
```
where `custom.toml` contains
```toml
[splashscreen]
autoclose = true
[[runtimes]]
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
name = "pyodide-0.21.2"
lang = "python"
```
- JSON using the `type` and `src` attribute
```html
<py-config type="json" src="./custom.json"></py-config>
```
where `custom.json` contains
```json
{
"splashscreen": {
"autoclose": true,
},
"runtimes": [{
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
"name": "pyodide-0.21.2",
"lang": "python"
}]
}
```
### Mixing inline and file based configs
One can also use both i.e pass the config from `src` attribute as well as specify it as `inline`. So the following snippet is also valid:
```html
<py-config src="./custom.toml">
[[fetch]]
files = ["./utils.py"]
</py-config>
```
This can also be done via JSON using the `type` attribute.
```html
<py-config type="json" src="./custom.json">
{
"fetch": [{
"files": ["./utils.py"]
}]
}
</py-config>
```
Note: While the `<py-config>` tag supports both TOML and JSON, one cannot mix the type of config passed from 2 different sources i.e. the case when inline config is in TOML format while config from src is in JSON format is NOT allowed. Similarly for the opposite case.
---
This is helpful in cases where a number of applications share a common configuration (which can be supplied via `src`), but their specific keys need to be customised and overridden.
The keys supplied through `inline` override the values present in config supplied via `src`.
## Dependencies and Packages
One can also declare dependencies so as to get access to many 3rd party OSS packages that are supported by PyScript.
You can also link to `.whl` files directly on disk like in our [toga example](https://github.com/pyscript/pyscript/blob/main/examples/toga/freedom.html).
Package dependencies in the `<py-config>` can be declared by using the direct link to the package URL (whl or any other format supported by the chosen runtime) or by just providing the package name [and version]. If only the name [and version] are provided, packages will be installed directly from what's provided by your runtime or from PyPI.
NOTICE that only pure python packages from PyPI will work and packages with C dependencies will not. These need to be built specifically for WASM (please, consult the Pyodide project for more information about what's supported and on how to build packages with C dependencies)
```html
<py-config>
packages = ["./static/wheels/travertino-0.1.3-py3-none-any.whl"]
</py-config>
```
OR in JSON like
```html
<py-config type="json">
{
"packages": ["./static/wheels/travertino-0.1.3-py3-none-any.whl"]
}
</py-config>
```
If your `.whl` is not a pure Python wheel, then open a PR or issue with [pyodide](https://github.com/pyodide/pyodide) to get it added [here](https://github.com/pyodide/pyodide/tree/main/packages).
If there's enough popular demand, the pyodide team will likely work on supporting your package. Regardless, things will likely move faster if you make the PR and consult with the team to get unblocked.
For example, NumPy and Matplotlib are available. Notice here we're using `display(fig, target="plot")`, which takes the graph and displays it in the element with the id `plot`.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<h1>Let's plot random numbers</h1>
<div id="plot"></div>
<py-config type="json">
{
"packages": ["numpy", "matplotlib"]
}
</py-config>
<py-script>
import matplotlib.pyplot as plt
import numpy as np
x = np.random.randn(1000)
y = np.random.randn(1000)
fig, ax = plt.subplots()
ax.scatter(x, y)
display(fig, target="plot")
</py-script>
</body>
</html>
```
## Local modules
In addition to packages, you can declare local Python modules that will
be imported in the `<py-script>` tag. For example, we can place the random
number generation steps in a function in the file `data.py`.
```python
# data.py
import numpy as np
def make_x_and_y(n):
x = np.random.randn(n)
y = np.random.randn(n)
return x, y
```
In the HTML tag `<py-config>`, paths to local modules are provided in the
`files` key within the `fetch` section. Refer to the [fetch](#fetch) section for
more details.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<h1>Let's plot random numbers</h1>
<div id="plot"></div>
<py-config type="toml">
packages = ["numpy", "matplotlib"]
[[fetch]]
files = ["./data.py"]
</py-config>
<py-script>
import matplotlib.pyplot as plt
from data import make_x_and_y
x, y = make_x_and_y(n=1000)
fig, ax = plt.subplots()
ax.scatter(x, y)
display(fig, target="plot")
</py-script>
</body>
</html>
```
## Supported configuration values
The following optional values are supported by `<py-config>`:
| Value | Type | Description |
| ------ | ---- | ----------- |
| `name` | string | Name of the user application. This field can be any string and is to be used by the application author for their own customization purposes. |
| `description` | string | Description of the user application. This field can be any string and is to be used by the application author for their own customization purposes. |
| `version` | string | Version of the user application. This field can be any string and is to be used by the application author for their own customization purposes. It is not related to the PyScript version. |
| `schema_version` | number | The version of the config schema which determines what all keys are supported. This can be supplied by the user so PyScript knows what to expect in the config. If not supplied, the latest version for the schema is automatically used. |
| `type` | string | Type of the project. The default is an "app" i.e. a user application |
| `author_name` | string | Name of the author. |
| `author_email` | string | Email of the author. |
| `license` | string | License to be used for the user application. |
| `autoclose_loader` | boolean | If false, PyScript will not close the loading splash screen when the startup operations finish. |
| `packages` | List of Packages | Dependencies on 3rd party OSS packages are specified here. The default value is an empty list. |
| `fetch` | List of Stuff to fetch | Local Python modules OR resources from the internet are to be specified here using a Fetch Configuration, described below. The default value is an empty list. |
| `plugins` | List of Plugins | List of Plugins are to be specified here. The default value is an empty list. |
| `runtimes` | List of Runtimes | List of runtime configurations, described below. The default value contains a single Pyodide based runtime. |
### <a name="fetch">Fetch</a>
A fetch configuration consists of the following:
| Value | Type | Description |
|--------------|-----------------|-------------------------------------------------|
| `from` | string | Base URL for the resource to be fetched. |
| `to_folder` | string | Name of the folder to create in the filesystem. |
| `to_file` | string | Name of the target to create in the filesystem. |
| `files` | List of strings | List of files to be downloaded. |
The parameters `to_file` and `files` shouldn't be supplied together.
#### Mechanism
The `fetch` mechanism works in the following manner:
- If both `files` and `to_file` parameters are supplied: Error!
- `from` defaults to an empty string i.e. `""` to denote relative URLs of the serving directory
- `to_folder` defaults to `.` i.e. the current working directory of the filesystem
- If `files` is specified
- for each `file` present in the `files` array
- the `sourcePath` is calculated as `from + file`
- the `destination` is calculated as `to_folder + file`
- thus, the object is downloaded from `sourcePath` to `destination`
- Else i.e. `files` is NOT specified
- If `to_file` is specified
- the object is downloaded from `from` to `to_folder + to_file`
- Otherwise, calculate the `filename` at the end of `from` i.e. the part after last `/`
- the object is downloaded from `from` to `to_folder + filename at the end of 'from'`
Learn more about `fetch` on PyScript [here](https://jeff.glass/post/whats-new-pyscript-2022-12-1)
#### Use-Cases
Assumptions:
The directory being served has the following tree structure:
```
content/
├─ index.html <<< File with <py-config>
├─ info.txt
├─ data/
│ ├─ sensordata.csv
├─ packages/
│ ├─ my_package/
│ │ ├─ __init__.py
│ │ ├─ helloworld/
│ │ │ ├─ __init__.py
│ │ │ ├─ greetings.py
```
1. Fetching a single file
```html
<py-config>
[[fetch]]
files = ['info.txt']
</py-config>
```
```html
<py-script>
with open('info.txt', 'r') as fp:
print(fp.read())
</py-script>
```
2. Single File with Renaming
```html
<py-config>
[[fetch]]
from = 'info.txt'
to_file = 'info_loaded_from_web.txt'
</py-config>
```
```html
<py-script>
with open('info_loaded_from_web.txt', 'r') as fp:
print(fp.read())
</py-script>
```
3. Single File to another Directory
```html
<py-config>
[[fetch]]
files = ['info.txt']
to_folder = 'infofiles/loaded_info'
</py-config>
```
```html
<py-script>
with open('infofiles/loaded_info/info.txt', 'r') as fp:
print(fp.read())
</py-script>
```
4. Single File to another Directory with Renaming
```html
<py-config>
[[fetch]]
from = 'info.txt'
to_folder = 'infofiles/loaded_info'
to_file = 'info_loaded_from_web.txt'
</py-config>
```
```html
<py-script>
with open('infofiles/loaded_info/info_loaded_from_web.txt', 'r') as fp:
print(fp.read())
</py-script>
```
5. Single file from a folder to the current working directory
```html
<py-config>
[[fetch]]
from = 'data/'
files = ['sensordata.csv']
</py-config>
```
```html
<py-script>
with open('./sensordata.csv', 'r') as fp:
print(fp.read())
</py-script>
```
6. Single file from a folder to another folder (i.e. not the current working directory)
```html
<py-config>
[[fetch]]
from = 'data/'
to_folder = './local_data'
files = ['sensordata.csv']
</py-config>
```
```html
<py-script>
with open('./local_data/sensordata.csv', 'r') as fp:
print(fp.read())
</py-script>
```
7. Multiple files preserving directory structure
```html
<py-config>
[[fetch]]
from = 'packages/my_package/'
files = ['__init__.py', 'helloworld/greetings.py', 'helloworld/__init__.py']
to_folder = 'custom_pkg'
</py-config>
```
```html
<py-script>
from custom_pkg.helloworld.greetings import say_hi
print(say_hi())
</py-script>
```
8. From an API endpoint which doesn't end in a filename
```html
<py-config>
[[fetch]]
from = 'https://catfact.ninja/fact'
to_file = './cat_fact.json'
</py-config>
```
```html
<py-script>
import json
with open("cat_fact.json", "r") as fp:
data = json.load(fp)
</py-script>
```
### Runtime
A runtime configuration consists of the following:
| Value | Type | Description |
|--------|-------------------|-------------|
| `src` | string (Required) | URL to the runtime source. |
| `name` | string | Name of the runtime. This field can be any string and is to be used by the application author for their own customization purposes |
| `lang` | string | Programming language supported by the runtime. This field can be used by the application author to provide clarification. It currently has no implications on how PyScript behaves. |
#### Example
- The default runtime is `pyodide`, another version of which can be specified as following
```html
<py-config>
[[runtimes]]
src = "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"
name = "pyodide-0.20.0"
lang = "python"
</py-config>
```
## Supplying extra information (or metadata)
Besides the above schema, a user can also supply any extra keys and values that are relevant as metadata information or perhaps are being used within the application.
For example, a valid config could also be with the snippet below:
```html
<py-config type="toml">
magic = "unicorn"
</py-config>
```
OR in JSON like
```html
<py-config type="json">
{
"magic": "unicorn"
}
</py-config>
```
If this `"magic"` key is present in config supplied via `src` and also present in config supplied via `inline`, then the value in the inline config is given priority i.e. the overriding process also works for custom keys.

View File

@@ -0,0 +1,30 @@
# &lt;py-repl&gt;
The `<py-repl>` element provides a REPL(Read Eval Print Loop) to evaluate multi-line Python and display output.
## Attributes
| attribute | type | default | description |
|-------------------|---------|---------|---------------------------------------|
| **auto-generate** | boolean | | Auto-generates REPL after evaluation |
| **output** | string | | The element to write output into |
### Examples
#### `<py-repl>` element set to auto-generate
```html
<py-repl auto-generate="true"> </py-repl>
```
#### `<py-repl>` element with output
```html
<div id="replOutput"></div>
<py-repl output="replOutput">
hello = "Hello world!"
hello
</py-repl>
```
Note that if we `print` any element in the repl, the output will be printed in the [`py-terminal`](../plugins/py-terminal.md) if is enabled.

View File

@@ -0,0 +1,111 @@
# &lt;py-script&gt;
The `<py-script>` element lets you execute multi-line Python scripts both inline and via a src attribute.
## Attributes
| attribute | type | default | description |
|-----------|------|---------|------------------------------|
| **src** | url | | Url of a python source file. |
## Examples
### Inline `<py-script>` element
Let's execute this multi-line Python script to compute π and print it back onto the page
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-script>
print("Let's compute π:")
def compute_pi(n):
pi = 2
for i in range(1,n):
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
return pi
pi = compute_pi(100000)
s = f"π is approximately {pi:.3f}"
print(s)
</py-script>
</body>
</html>
```
### Using `<py-script>` element with `src` attribute
we can also move our python code to its own file and reference it via the `src` attribute.
```python
# compute_pi.py
print("Let's compute π:")
def compute_pi(n):
pi = 2
for i in range(1,n):
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
return pi
pi = compute_pi(100000)
s = f"π is approximately {pi:.3f}"
print(s)
```
Since both compute_pi.py and index.html are in the same directory, we can reference the python file with a relative path.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-script src="compute_pi.py"></py-script>
</body>
</html>
```
### Writing into labeled elements
In the example above, we had a single `<py-script>` tag printing
one or more lines onto the page in order. Within the `<py-script>`, you can
use the `Element` class to create a python object for interacting with
page elements. Objects created from the `Element` class provide the `.write()` method
which enables you to send strings into the page elements referenced by those objects.
For example, we'll add some style elements and provide placeholders for
the `<py-script>` tag to write to.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
</head>
<body>
<b><p>Today is <u><label id='today'></label></u></p></b>
<br>
<div id="pi" class="alert alert-primary"></div>
<py-script>
import datetime as dt
Element('today').write(dt.date.today().strftime('%A %B %d, %Y'))
def compute_pi(n):
pi = 2
for i in range(1,n):
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
return pi
pi = compute_pi(100000)
Element('pi').write(f'π is approximately {pi:.3f}')
</py-script>
</body>
</html>
```

View File

@@ -0,0 +1,39 @@
# Exceptions and error codes
When creating pages with PyScript, you may encounter exceptions. Each handled exception will contain a specific code which will give you more information about it.
This reference guide contains the error codes you might find and a description of each of them.
## User Errors
| Error code | Description | Recommendation |
|------------|--------------------------------|--------------------|
| PY1000 | Invalid configuration supplied | Confirm that your `py-config` tag is using a valid `TOML` or `JSON` syntax and is using the correct configuration type. |
| PY1001 | Unable to install package(s) | Confirm that the package contains a pure Python 3 wheel or the name of the package is correct. |
| PY9000 | Top level await is deprecated | Create a coroutine with your code and schedule it with `asyncio.ensure_future` or similar |
## Fetch Errors
These error codes are related to any exception raised when trying to fetch a resource. If, while trying to fetch a resource, we encounter a status code that is not 200, the error code will contain the HTTP status code and the `PY0` prefix. For example, if we encounter a 404 error, the error code will be `P02404`.
| Error Code | Description |
|------------|--------------------------------------------------------------|
| PY0001 | Generic fetch error, failed to fetch page from the server |
| PY0002 | Name supplied when trying to fetch resource is invalid |
| PY0401 | You are not authorized to access this resource. |
| PY0403 | You are not allowed to access this resource. |
| PY0404 | The page you are trying to fetch does not exist. |
| PY0500 | The server encountered an internal error. |
| PY0503 | The server is currently unavailable. |
## PY1001
Pyscript cannot install the package(s) you specified in your `py-config` tag. This can happen for a few reasons:
- The package does not exist
- The package does not contain a pure Python 3 wheel
- An error occurred while trying to install the package
An error banner should appear on your page with the error code and a description of the error or a traceback. You can also check the developer console for more information.

View File

@@ -1,11 +1,54 @@
# Reference
This reference section will have manually documented or fully
automated code documentation. **Coming soon!**
```{toctree}
---
maxdepth: 1
glob:
caption: Elements
---
elements/*
```
```{toctree}
---
maxdepth: 1
glob:
caption: Modules
---
modules/*
```
```{toctree}
---
maxdepth: 1
glob:
caption: Plugins
---
plugins/*
```
```{toctree}
---
maxdepth: 1
glob:
caption: API
---
API/*
```
```{toctree}
---
maxdepth: 2
glob:
caption: Exceptions
---
exceptions
```
```{toctree}
---
maxdepth: 1
caption: Miscellaneous
---
faq
```

View File

@@ -0,0 +1,77 @@
# pyscript
The code underlying PyScript is a TypeScript/JavaScript module, which is loaded and executed by the browser. This is what loads when you include, for example, `<script defer src="https://pyscript.net/latest/pyscript.js">` in your HTML.
The module is exported to the browser as `pyscript`. The exports from this module are:
## pyscript.version
Once `pyscript.js` has loaded, the version of PyScript that is currently running can be accessed via `pyscript.version`.
```html
<script defer onload="console.log(`${pyscript.version}`)" src="https://pyscript.net/latest/pyscript.js"></script>
```
```js
//example result
Object { year: 2022, month: 11, patch: 1, releaselevel: "dev" }
```
## pyscript.runtime
The RunTime object which is responsible for executing Python code in the Browser. Currently, all runtimes are assumed to be Pyodide runtimes, but there is flexibility to expand this to other web-based Python runtimes in future versions.
The RunTime object has the following attributes
| attribute | type | description |
|---------------------|---------------------|-----------------------------------------------------------------------------|
| **src** | string | The URL from which the current runtime was fetched |
| **interpreter** | RuntimeInterpreter | A reference to the runtime object itself |
| **globals** | any | The globals dictionary of the runtime, if applicable/accessible |
| **name (optional)** | string | A user-designated name for the runtime |
| **lang (optional)** | string | A user-designation for the language the runtime runs ('Python', 'C++', etc) |
### pyscript.runtime.src
The URL from which the current runtime was fetched.
### pyscript.runtime.interpreter
A reference to the Runtime wrapper that PyScript uses to execute code. object itself. This allows other frameworks, modules etc to interact with the same [(Pyodide) runtime instance](https://pyodide.org/en/stable/usage/api/js-api.html) that PyScript uses.
For example, assuming we've loaded Pyodide, we can access the methods of the Pyodide runtime as follows:
```html
<button onclick="logFromPython()">Click Me to Run Some Python</button>
<script>
function logFromPython(){
pyscript.runtime.interpreter.runPython(`
animal = "Python"
sound = "sss"
console.warn(f"{animal}s go " + sound * 5)
`)
}
</script>
```
### pyscript.runtime.globals
A proxy for the runtime's `globals()` dictionary. For example:
```html
<body>
<py-script>x = 42</py-script>
<button onclick="showX()">Click Me to Get 'x' from Python</button>
<script>
function showX(){
console.log(`In Python right now, x = ${pyscript.runtime.globals.get('x')}`)
}
</script>
</body>
```
### pyscript.runtime.name
A user-supplied string for the runtime given at its creation. For user reference only - does not affect the operation of the runtime or PyScript.
### PyScript.runtime.lang
A user-supplied string for the language the runtime uses given at its creation. For user reference only - does not affect the operation of the runtime or PyScript.

View File

@@ -0,0 +1,41 @@
# &lt;py-terminal&gt;
This is one of the core plugins in PyScript, which is active by default. With it, you can print to `stdout` and `stderr` from your python code, and the output will be displayed on the page in `<py-terminal>`.
## Configuration
You can control how `<py-terminal>` behaves by setting the value of the `terminal` configuration in your `<py-config>`.
| value | description |
|-------|-------------|
| `false` | Don't add `<py-terminal>` to the page |
| `true` | Automatically add a `<py-terminal>` to the page |
| `"auto"` | This is the default. Automatically add a `<py-terminal auto>`, to the page. The terminal is initially hidden and automatically shown as soon as something writes to `stdout` and/or `stderr` |
### Examples
```html
<py-config>
terminal = true
</py-config>
<py-script>
print("Hello, world!")
</py-script>
```
This example will create a new `<py-terminal>`, the value "Hello, world!" that was printed will show in it.
You can also add one (or more) `<py-terminal>` to the page manually.
```html
<py-script>
print("Hello, world!")
</py-script>
<py-terminal></py-terminal>
```
```{note}
If you include a `<py-terminal>` in the page, you can skip `terminal` from your `<py-config>`.
```

View File

@@ -1,3 +0,0 @@
# Deployment
**Coming soon!**

View File

@@ -5,46 +5,12 @@ This page will guide you through getting started with PyScript.
## Development setup
PyScript does not require any development environment other
than a web browser (we recommend using [Chrome](https://www.google.com/chrome/)) and a text editor, even though using your [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) of choice might be convenient.
then a web browser (we recommend using [Chrome](https://www.google.com/chrome/)) and a text editor, even though using your [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) of choice might be convenient.
If you're using [VSCode](https://code.visualstudio.com/), the
[Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
can be used to reload the page as you edit the HTML file.
## Trying before installing
If you're new to programming and know nothing about HTML or just want to try some of PyScript features, we recommend using the [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) element in the [PyScript REPL example](https://pyscript.net/examples/repl.html) instead so you can have a programming experience in a REPL that doesn't require any setup. This REPL can be used to have an interactive experience using Python directly.
Alternatively, you can also use an online editor like W3School's [TryIt Editor](https://www.w3schools.com/html/tryit.asp?filename=tryhtml_default_default) and just plug the code below into it, as shown in the [example](https://docs.pyscript.net/latest/concepts/what-is-pyscript.html#example) page and click the run button.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>REPL</title>
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
Hello world! <br>
This is the current date and time, as computed by Python:
<py-script>
from datetime import datetime
now = datetime.now()
now.strftime("%m/%d/%Y, %H:%M:%S")
</py-script>
</body>
</html>
```
You could try changing the code above to explore and play with pyscript yourself.
## Installation
There is no installation required. In this document, we'll use
@@ -62,25 +28,6 @@ the same directory as your PyScript, JavaScript, and CSS files with the
following content, and open the file in your web browser. You can typically
open an HTML by double-clicking it in your file explorer.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body> <py-script> print('Hello, World!') </py-script> </body>
</html>
```
Notice the use of the `<py-script>` tag in the HTML body. This
is where you'll write your Python code. In the following sections, we'll
introduce the eight tags provided by PyScript.
## The py-script tag
The `<py-script>` tag lets you execute multi-line Python scripts and
print back onto the page. For example, we can compute π.
```html
<html>
<head>
@@ -88,417 +35,234 @@ print back onto the page. For example, we can compute π.
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-script>
print("Let's compute π:")
def compute_pi(n):
pi = 2
for i in range(1,n):
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
return pi
pi = compute_pi(100000)
s = f"π is approximately {pi:.3f}"
print(s)
</py-script>
</body>
</html>
```
### Writing into labeled elements
In the example above, we had a single `<py-script>` tag printing
one or more lines onto the page in order. Within the `<py-script>`, you
have access to the `pyscript` module, which provides a `.write()` method
to send strings into labeled elements on the page.
For example, we'll add some style elements and provide placeholders for
the `<py-script>` tag to write to.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
</head>
<body>
<b><p>Today is <u><label id='today'></label></u></p></b>
<br>
<div id="pi" class="alert alert-primary"></div>
<py-script>
import datetime as dt
pyscript.write('today', dt.date.today().strftime('%A %B %d, %Y'))
def compute_pi(n):
pi = 2
for i in range(1,n):
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
return pi
pi = compute_pi(100000)
pyscript.write('pi', f'π is approximately {pi:.3f}')
print('Hello, World!')
</py-script>
</body>
</html>
```
## The py-config tag
## A more complex example
Use the `<py-config>` tag to set and configure general metadata along with declaring dependencies for your PyScript application. The configuration has to be set in either TOML or JSON format. If you are unfamiliar with JSON, consider reading [freecodecamp's JSON for beginners](https://www.freecodecamp.org/news/what-is-json-a-json-file-example/) guide for more information. And for TOML, consider reading about it [here](https://learnxinyminutes.com/docs/toml/).
Now that we know how you can create a simple 'Hello, World!' example, let's see a more complex example. This example will use the Demo created by [Cheuk Ting Ho](https://github.com/Cheukting). In this example, we will use more features from PyScript.
The ideal place to use `<py-config>` in between the `<head>...</head>` tags.
### Setting up the base index file
The `<py-config>` tag can be used as follows:
Let's create a new file called `index.html` and add the following content:
```html
<py-config>
autoclose_loader = true
[[runtimes]]
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
name = "pyodide-0.21.2"
lang = "python"
</py-config>
```
Alternatively, a JSON config can be passed using the `type` attribute.
```html
<py-config type="json">
{
"autoclose_loader": true,
"runtimes": [{
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
"name": "pyodide-0.21.2",
"lang": "python"
}]
}
</py-config>
```
Besides passing the config as inline (as shown above), one can also pass it with the `src` attribute. This is demonstrated below:
```
<py-config src="./custom.toml"></py-config>
```
where `custom.toml` contains
```
autoclose_loader = true
[[runtimes]]
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
name = "pyodide-0.21.2"
lang = "python"
```
This can also be done via JSON using the `type` attribute. By default, `type` is set to `"toml"` if not supplied.
```
<py-config type="json" src="./custom.json"></py-config>
```
where `custom.json` contains
```
{
"autoclose_loader": true,
"runtimes": [{
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
"name": "pyodide-0.21.2",
"lang": "python"
}]
}
```
One can also use both i.e pass the config from `src` attribute as well as specify it as `inline`. So the following snippet is also valid:
```
<py-config src="./custom.toml">
paths = ["./utils.py"]
</py-config>
```
This can also be done via JSON using the `type` attribute.
```
<py-config type="json" src="./custom.json">
{
"paths": ["./utils.py"]
}
</py-config>
```
Note: While the `<py-config>` tag supports both TOML and JSON, one cannot mix the type of config passed from 2 different sources i.e. the case when inline config is in TOML format while config from src is in JSON format is NOT allowed. Similarly for the opposite case.
---
This is helpful in cases where a number of applications share a common configuration (which can be supplied via `src`), but their specific keys need to be customised and overridden.
The keys supplied through `inline` override the values present in config supplied via `src`.
One can also declare dependencies so as to get access to many 3rd party OSS packages that are supported by PyScript.
You can also link to `.whl` files directly on disk like in our [toga example](https://github.com/pyscript/pyscript/blob/main/examples/toga/freedom.html).
```
<py-config>
packages = ["./static/wheels/travertino-0.1.3-py3-none-any.whl"]
</py-config>
```
OR in JSON like
```
<py-config type="json">
{
"packages": ["./static/wheels/travertino-0.1.3-py3-none-any.whl"]
}
</py-config>
```
If your `.whl` is not a pure Python wheel, then open a PR or issue with [pyodide](https://github.com/pyodide/pyodide) to get it added [here](https://github.com/pyodide/pyodide/tree/main/packages).
If there's enough popular demand, the pyodide team will likely work on supporting your package. Regardless, things will likely move faster if you make the PR and consult with the team to get unblocked.
For example, NumPy and Matplotlib are available. Notice here we're using `<py-script output="plot">`
as a shortcut, which takes the expression on the last line of the script and runs `pyscript.write('plot', fig)`.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-config type="json">
{
"packages": ["numpy", "matplotlib"]
}
</py-config>
</head>
<body>
<h1>Let's plot random numbers</h1>
<div id="plot"></div>
<py-script output="plot">
import matplotlib.pyplot as plt
import numpy as np
x = np.random.randn(1000)
y = np.random.randn(1000)
fig, ax = plt.subplots()
ax.scatter(x, y)
fig
</py-script>
</body>
</html>
```
### Local modules
In addition to packages, you can declare local Python modules that will
be imported in the `<py-script>` tag. For example, we can place the random
number generation steps in a function in the file `data.py`.
```python
# data.py
import numpy as np
def make_x_and_y(n):
x = np.random.randn(n)
y = np.random.randn(n)
return x, y
```
In the HTML tag `<py-config>`, paths to local modules are provided in the
`paths:` key.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-config type="toml">
packages = ["numpy", "matplotlib"]
paths = ["./data.py"]
</py-config>
</head>
<body>
<h1>Let's plot random numbers</h1>
<div id="plot"></div>
<py-script output="plot">
import matplotlib.pyplot as plt
from data import make_x_and_y
x, y = make_x_and_y(n=1000)
fig, ax = plt.subplots()
ax.scatter(x, y)
fig
</py-script>
</body>
</html>
```
The following optional values are supported by `<py-config>`:
| Value | Type | Description |
| ------ | ---- | ----------- |
| `name` | string | Name of the user application. This field can be any string and is to be used by the application author for their own customization purposes. |
| `description` | string | Description of the user application. This field can be any string and is to be used by the application author for their own customization purposes. |
| `version` | string | Version of the user application. This field can be any string and is to be used by the application author for their own customization purposes. It is not related to the PyScript version. |
| `schema_version` | number | The version of the config schema which determines what all keys are supported. This can be supplied by the user so PyScript knows what to expect in the config. If not supplied, the latest version for the schema is automatically used. |
| `type` | string | Type of the project. The default is an "app" i.e. a user application |
| `author_name` | string | Name of the author. |
| `author_email` | string | Email of the author. |
| `license` | string | License to be used for the user application. |
| `autoclose_loader` | boolean | If false, PyScript will not close the loading splash screen when the startup operations finish. |
| `packages` | List of Packages | Dependencies on 3rd party OSS packages are specified here. The default value is an empty list. |
| `paths` | List of Paths | Local Python modules are to be specified here. The default value is an empty list. |
| `plugins` | List of Plugins | List of Plugins are to be specified here. The default value is an empty list. |
| `runtimes` | List of Runtimes | List of runtime configurations, described below. The default value contains a single Pyodide based runtime. |
A runtime configuration consists of the following:
| Value | Type | Description |
| ----- | ---- | ----------- |
| `src` | string (Required) | URL to the runtime source. |
| `name` | string | Name of the runtime. This field can be any string and is to be used by the application author for their own customization purposes |
| `lang` | string | Programming language supported by the runtime. This field can be used by the application author to provide clarification. It currently has no implications on how PyScript behaves. |
Besides the above format, a user can also supply any extra keys and values that are relevant as metadata information or perhaps are being used within the application.
For example, a valid config could also be with the snippet below:
```
<py-config type="toml">
magic = "unicorn"
</py-config>
```
OR in JSON like
```
<py-config type="json">
{
"magic": "unicorn"
}
</py-config>
```
If this `"magic"` key is present in config supplied via `src` and also present in config supplied via `inline`, then the value in the inline config is given priority i.e. the overriding process also works for custom keys.
## The py-env tag (Deprecated)
**The <py-env> tag is deprecated as of `2022.09.1` release but you can still use the functionality explained below. It will be removed in the next release. To specify packages in the recommended way, please see the <py-config> section.**
In addition to the [Python Standard Library](https://docs.python.org/3/library/) and
the `pyscript` module, many 3rd-party OSS packages will work out-of-the-box with PyScript.
In order to use them, you will need to declare the dependencies using the `<py-env>` tag in the
HTML head. You can also link to `.whl` files directly on disk like in our [toga example](https://github.com/pyscript/pyscript/blob/main/examples/toga/freedom.html).
```
<py-env>
- './static/wheels/travertino-0.1.3-py3-none-any.whl'
</py-env>
```
If your `.whl` is not a pure Python wheel, then open a PR or issue with [pyodide](https://github.com/pyodide/pyodide) to get it added [here](https://github.com/pyodide/pyodide/tree/main/packages).
If there's enough popular demand, the pyodide team will likely work on supporting your package. Regardless, things will likely move faster if you make the PR and consult with the team to get unblocked.
For example, NumPy and Matplotlib are available. Notice here we're using `<py-script output="plot">`
as a shortcut, which takes the expression on the last line of the script and runs `pyscript.write('plot', fig)`.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-env>
- numpy
- matplotlib
</py-env>
</head>
<body>
<h1>Let's plot random numbers</h1>
<div id="plot"></div>
<py-script output="plot">
import matplotlib.pyplot as plt
import numpy as np
x = np.random.randn(1000)
y = np.random.randn(1000)
fig, ax = plt.subplots()
ax.scatter(x, y)
fig
</py-script>
</body>
</html>
```
### Local modules with py-env (Deprecated)
**The <py-env> tag is deprecated as of `2022.09.1` release but you can still use the functionality explained below. It will be removed in the next release. To specify local modules in the recommended way, please see the <py-config> section.**
In addition to packages, you can declare local Python modules that will
be imported in the `<py-script>` tag. For example, we can place the random
number generation steps in a function in the file `data.py`.
```python
# data.py
import numpy as np
def make_x_and_y(n):
x = np.random.randn(n)
y = np.random.randn(n)
return x, y
```
In the HTML tag `<py-env>`, paths to local modules are provided in the
`paths:` key.
```html
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-env>
- numpy
- matplotlib
- paths:
- ./data.py
</py-env>
</head>
<body>
<h1>Let's plot random numbers</h1>
<div id="plot"></div>
<py-script output="plot">
import matplotlib.pyplot as plt
from data import make_x_and_y
x, y = make_x_and_y(n=1000)
fig, ax = plt.subplots()
ax.scatter(x, y)
fig
</py-script>
</body>
</html>
```
## The py-repl tag
The `<py-repl>` tag creates a REPL component that is rendered to the page as a code editor, allowing you to write executable code inline.
```html
<html>
<head>
<title>Ice Cream Picker</title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<py-repl></py-repl>
<body>
</body>
</html>
```
## Visual component tags
In this first step, we have created the index file, imported `pyscript.css` and `pyscript.js`. We are ready to start adding the elements we need for our application.
The following tags can be used to add visual attributes to your HTML page.
### Importing the needed libraries
| Tag | Description |
| --- | ----------- |
| `<py-inputbox>` | Adds an input box that can be used to prompt users to enter input values. |
| `<py-box>` | Creates a container object that can be used to host one or more visual components that define how elements of `<py-box>` should align and show on the page. |
| `<py-button>` | Adds a button to which authors can add labels and event handlers for actions on the button, such as `on_focus` or `on_click`. |
| `<py-title>` | Adds a static text title component that styles the text inside the tag as a page title. |
For this example, we will need to install `pandas` and `matplotlib`. We can install libraries using the `<py-config>` tag so we can import them later. Please refer to the [`<py-config>`](../reference/elements/py-config.md) documentation for more information.
```{note}
All the elements above are experimental and not implemented at their full functionality. Use them with the understanding that the APIs or full support might change or be removed until the visual components are more mature.
```html
<html>
<head>
<title>Ice Cream Picker</title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-config>
packages = ["matplotlib", "pandas"]
</py-config>
</body>
</html>
```
### Importing the data and exploring
Now that we have installed the needed libraries, we can import and explore the data. In this step, we need to create a `<py-script>` tag to import our dependencies, read the data with pandas and then use `py-repl` to explore the data.
You may want to read the [`<py-script>`](../reference/elements/py-script.md) and [`<py-repl>`](../reference/elements/py-repl.md) documentation for more information about these elements.
```html
<html>
<head>
<title>Ice Cream Picker</title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-config>
packages = ["matplotlib", "pandas"]
</py-config>
<py-script>
import pandas as pd
from pyodide.http import open_url
url = (
"https://raw.githubusercontent.com/Cheukting/pyscript-ice-cream/main/bj-products.csv"
)
ice_data = pd.read_csv(open_url(url))
</py-script>
<py-repl>
ice_data
</py-repl>
</body>
</html>
```
Note that we are adding `ice_data` to `py-repl` to pre-populate the REPL with this variable, so you don't have to type it yourself.
### Creating the plot
Now that we have the data, we can create the plot. We will use the `matplotlib` library to make the plot. We will use the `display` API to display the plot on the page. You may want to read the [`display`](../reference/API/display.md) documentation for more information.
```html
<html>
<head>
<title>Ice Cream Picker</title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-config>
packages = ["matplotlib", "pandas"]
</py-config>
<py-script>
import pandas as pd
import matplotlib.pyplot as plt
from pyodide.http import open_url
url = (
"https://raw.githubusercontent.com/Cheukting/pyscript-ice-cream/main/bj-products.csv"
)
ice_data = pd.read_csv(open_url(url))
def plot(data):
plt.rcParams["figure.figsize"] = (22,20)
fig, ax = plt.subplots()
bars = ax.barh(data["name"], data["rating"], height=0.7)
ax.bar_label(bars)
plt.title("Rating of ice cream flavours of your choice")
display(fig, target="graph-area", append=False)
plot(ice_data)
</py-script>
<py-repl>
ice_data
</py-repl>
<div id="graph-area"></div>
</body>
</html>
```
### Select specific flavours
Now that we have a way to explore the data using `py-repl` and a way to create the plot using all of the data, it's time for us to add a way to select specific flavours.
```html
<html>
<head>
<title>Ice Cream Picker</title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-config>
packages = ["matplotlib", "pandas"]
</py-config>
<py-script>
import pandas as pd
import matplotlib.pyplot as plt
from pyodide.http import open_url
from pyodide.ffi import create_proxy
url = (
"https://raw.githubusercontent.com/Cheukting/pyscript-ice-cream/main/bj-products.csv"
)
ice_data = pd.read_csv(open_url(url))
current_selected = []
flavour_elements = document.getElementsByName("flavour")
def plot(data):
plt.rcParams["figure.figsize"] = (22,20)
fig, ax = plt.subplots()
bars = ax.barh(data["name"], data["rating"], height=0.7)
ax.bar_label(bars)
plt.title("Rating of ice cream flavours of your choice")
display(fig, target="graph-area", append=False)
def select_flavour(event):
for ele in flavour_elements:
if ele.checked:
current_selected = ele.value
break
if current_selected == "ALL":
plot(ice_data)
else:
filter = ice_data.apply(lambda x: ele.value in x["ingredients"], axis=1)
plot(ice_data[filter])
ele_proxy = create_proxy(select_flavour)
for ele in flavour_elements:
if ele.value == "ALL":
ele.checked = True
current_selected = ele.value
ele.addEventListener("change", ele_proxy)
plot(ice_data)
</py-script>
<div id="input" style="margin: 20px;">
Select your 🍨 flavour: <br/>
<input type="radio" id="all" name="flavour" value="ALL">
<label for="all"> All 🍧</label>
<input type="radio" id="chocolate" name="flavour" value="COCOA">
<label for="chocolate"> Chocolate 🍫</label>
<input type="radio" id="cherrie" name="flavour" value="CHERRIES">
<label for="cherrie"> Cherries 🍒</label>
<input type="radio" id="berries" name="flavour" value="BERRY">
<label for="berries"> Berries 🍓</label>
<input type="radio" id="cheese" name="flavour" value="CHEESE">
<label for="cheese"> Cheese 🧀</label>
<input type="radio" id="peanut" name="flavour" value="PEANUT">
<label for="peanut"> Peanut 🥜</label>
</div>
<py-repl>
ice_data
</py-repl>
<div id="graph-area"></div>
</body>
</html>
```

View File

@@ -7,6 +7,7 @@ This is the tutorials section for beginners.
maxdepth: 2
---
getting-started
deployment
setup
py-config-fetch
py-config-runtime
writing-to-page
```

View File

@@ -0,0 +1,188 @@
# Using the fetch from py-config
This tutorial shows how to use the fetch configuration from `py-config` to fetch two files from a remote server, store them in a local directory, and verify their contents.
## Development setup
We will create a todo list application similar to the one in the [examples](https://pyscript.net/examples/todo.html). To do this, we need three things:
* An `index.html` file containing the HTML for the application.
* A `todo.py` file containing the Python code for the application.
* A `utils.py` file containing some utility functions for the application.
We will use the `fetch` configuration from `py-config` to fetch these files from a remote server and store them in a local directory.
### Creating the html file
In this first step, we will create the `index.html` file and import both `pyscript.css` and `pyscript.js`. These are needed to run our Python code in the browser and style the application.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>My Todo</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
</body>
</html>
```
## Fetching the files
### Using `fetch` to get the python files
Now we will use the `fetch` configuration from `py-config` to fetch the `todo.py` and `utils.py` files from a remote server and store them in a local directory called `todo`. Here we will fetch files from different URLs, using a `fetch` per item.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>My Todo</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-config>
[[fetch]]
from = "https://pyscript.net/examples/"
files = ["utils.py"]
[[fetch]]
from = "https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/"
files = ["todo.py"]
</py-config>
</body>
</html>
```
## Creating a todo application
### Creating the todo elements
Now we will create the todo elements in the `body` of the `index.html` file.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>My Todo</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-config>
[[fetch]]
from = "https://pyscript.net/examples/"
files = ["utils.py"]
[[fetch]]
from = "https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/"
files = ["todo.py"]
</py-config>
<section>
<div class="text-center w-full mb-8">
<h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">To Do List</h1>
</div>
<div>
<input id="new-task-content" class="py-input" type="text">
<button id="new-task-btn" class="py-button" type="submit" py-click="add_task()">
Add task
</button>
</div>
<div id="list-tasks-container" class="flex flex-col-reverse mt-4"></div>
<template id="task-template">
<section class="task py-li-element">
<label for="flex items-center p-2 ">
<input class="mr-2" type="checkbox">
<p class="m-0 inline"></p>
</label>
</section>
</template>
</section>
</body>
</html>
```
Our todo application is starting to shape up, although if you try to add any tasks, you will notice that nothing happens. This is because we have not yet imported the `todo.py` file.
### Importing the needed functions from `todo.py`
This is where the magic happens. We can import the `todo.py` file by adding it as a source to the `py-script` tag. By specifying the file, pyscript will automatically import the file and run the code in it.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>My Todo</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-config>
[[fetch]]
from = "https://pyscript.net/examples/"
files = ["utils.py"]
[[fetch]]
from = "https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/"
files = ["todo.py"]
</py-config>
<py-script>
from todo import add_task, add_task_event
</py-script>
<section>
<div class="text-center w-full mb-8">
<h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">To Do List</h1>
</div>
<div>
<input id="new-task-content" class="py-input" type="text">
<button id="new-task-btn" class="py-button" type="submit" py-click="add_task()">
Add task
</button>
</div>
<div id="list-tasks-container" class="flex flex-col-reverse mt-4"></div>
<template id="task-template">
<section class="task py-li-element">
<label for="flex items-center p-2 ">
<input class="mr-2" type="checkbox">
<p class="m-0 inline"></p>
</label>
</section>
</template>
</section>
</body>
</html>
```
You can now save the file and refresh the page. You should now be able to add tasks to your todo list.
## That's it!
You have now created a todo application using pyscript. You can add tasks and mark them as done. Let's take a recap of what we have achieved:
* We have imported three separate files into our `index.html` file using the `py-config` tag.
* We have created the necessary HTML code to create our todo's
* We have imported functions from the `todo.py` file, using the `py-script` tag.
For reference, the code from [the gist](https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/todo.py) is the same code that our [todo example](https://pyscript.net/examples/todo.html) uses with a slight change of importing `Element` from `pyscript`.

View File

@@ -0,0 +1,88 @@
# Setting a pyodide runtime
Pyscript will automatically set the runtime for you, but you can also set it manually. This is useful if you want to use a different version than the one set by default.
## Development setup
To get started, let's create a new `index.html` file and import `pyscript.js`.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Runtime</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
</body>
</html>
```
We are using the pyodide CDN to setup our runtime, but you can also download the files from [the pyodide GitHub release](https://github.com/pyodide/pyodide/releases/tag/0.22.0a3), unzip them and use the `pyodide.js` file as your runtime.
## Setting the runtime
To set the runtime, you can use the `runtime` configuration in the `py-config` element. In this tutorial, we will use the default `TOML` format, but know that you can also use `json` if you prefer by changing the `type` attribute of the `py-config` element.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Runtime</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-config>
[[runtimes]]
src = "https://cdn.jsdelivr.net/pyodide/v0.22.0a3/full/pyodide.js"
name = "pyodide-0.22.0a3"
lang = "python"
</py-config>
</body>
</html>
```
## Confirming the runtime version
To confirm that the runtime is set correctly, you can open the DevTools and check the version from the console. But for the sake of this tutorial, let's create a `py-script` tag and print pyodide's version.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Runtime</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-config>
[[runtimes]]
src = "https://cdn.jsdelivr.net/pyodide/v0.22.0a3/full/pyodide.js"
name = "pyodide-0.22.0a3"
lang = "python"
</py-config>
<py-script>
import pyodide
print(pyodide.__version__)
</py-script>
</body>
</html>
```

View File

@@ -1,3 +0,0 @@
# Setup
**Coming soon!**

View File

@@ -0,0 +1,212 @@
# How to write content to the page
When creating your PyScript application, you will want to write content to the page. This tutorial will explore the different methods you can use to write content to the page and their differences.
## Development setup
To get started, we will create an `index.html` file, import PyScript and start building on top of it.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Writing to the page</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
</body>
</html>
```
## Writing content to an element
Let's first see how we can write content to an element on the page. We will start by creating a `div` element with an `id` of `manual-write`, then create a `py-script` tag that, upon a click of a button, will write 'Hello World' to the `div` element.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Writing to the page</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<div id="manual-write"></div>
<button py-click="write_to_page()" id="manual">Say Hello</button>
<py-script>
def write_to_page():
manual_div = Element("manual-write")
manual_div.element.innerText = "Hello World"
</py-script>
</body>
</html>
```
```{note}
When using `py-click` you must supply an `id` to the element you want to use as the trigger.
```
We can now open our `index.html` file and click the button. You will see that "Hello World" will appear in the `div` element. You could also write HTML using `manual_div.element.innerHTML` instead of `innerText`. For example:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Writing to the page</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<div id="manual-write"></div>
<button py-click="write_to_page()" id="manual">Say Hello</button>
<py-script>
def write_to_page():
manual_div = Element("manual-write")
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
</py-script>
</body>
</html>
```
## Writing content with the `display` API
The `display` API is a simple way to write content to the page. Not only does it allow you to write content to the page, but it also allows you to display a range of different content types such as images, markdown, svgs, json, etc.
Using the' display' API, let's reuse our previous example and write "Hello World" to the page.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Writing to the page</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<div id="manual-write"></div>
<button py-click="write_to_page()" id="manual">Say Hello</button>
<div id="display-write"></div>
<button py-click="display_to_div()" id="display">Say Things!</button>
<py-script>
def write_to_page():
manual_div = Element("manual-write")
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
def display_to_div():
display("I display things!", target="display-write")
</py-script>
</body>
</html>
```
```{note}
When using the `display` API, you must specify the `target` parameter to tell PyScript where to write the content. If you do not use this parameter, an error will be thrown.
```
You may be interested in reading more about the `display` API in the [Display API](../reference/api/display) section of the documentation.
## Printing to the page
We couldn't have a tutorial on writing to the page without mentioning the `print` function. The `print` function is a simple way to write content to the page, that any Python developer will be familiar with. When you use the `print` function, the content will be written to the page in a `py-terminal` element.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Writing to the page</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<div id="manual-write"></div>
<button py-click="write_to_page()" id="manual">Say Hello</button>
<div id="display-write"></div>
<button py-click="display_to_div()" id="display">Say Things!</button>
<button py-click="print_to_page()" id="print">Print Things!</button>
<py-script>
def write_to_page():
manual_div = Element("manual-write")
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
def display_to_div():
display("I display things!", target="display-write")
def print_to_page():
print("I print things!")
</py-script>
</body>
</html>
```
You may be surprised to see that when you click the "Print Things!" button, the content is written below the rest of the elements on the page in a black canvas. This is because the `print` function writes content to the page in a `py-terminal` element. You can read more about the `py-terminal` element in the [Terminal Element](../reference/plugins/py-terminal) section of the documentation.
PyScript comes with the `py-terminal` plugin by default and any `stdout` or `stderr` content will be shown in this element. We can be explicit about where we want the terminal to be shown by adding the `<py-terminal>` tag to our HTML.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Writing to the page</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<div id="manual-write"></div>
<button py-click="write_to_page()" id="manual">Say Hello</button>
<div id="display-write"></div>
<button py-click="display_to_div()" id="display">Say Things!</button>
<div>
<py-terminal>
</div>
<button py-click="print_to_page()" id="print">Print Things!</button>
<py-script>
def write_to_page():
manual_div = Element("manual-write")
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
def display_to_div():
display("I display things!", target="display-write")
def print_to_page():
print("I print things!")
</py-script>
</body>
</html>
```

View File

@@ -4,59 +4,151 @@
<meta charset="utf-8">
<link rel="icon" type="image/x-icon" href="./favicon.png">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-config>
packages = [
"altair",
"pandas",
"vega_datasets"
]
</py-config>
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<div id="altair" style="width: 100%; height: 100%"></div>
<py-script output="altair">
import altair as alt
from vega_datasets import data
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Altair</a>
</div>
</nav>
<section class="pyscript">
<div id="altair"></div>
<py-config>
packages = [
"altair",
"pandas",
"vega_datasets"
]
</py-config>
<py-script>
import altair as alt
from vega_datasets import data
source = data.movies.url
source = data.movies.url
pts = alt.selection(type="single", encodings=['x'])
pts = alt.selection(type="single", encodings=['x'])
rect = alt.Chart(data.movies.url).mark_rect().encode(
alt.X('IMDB_Rating:Q', bin=True),
alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
alt.Color('count()',
scale=alt.Scale(scheme='greenblue'),
legend=alt.Legend(title='Total Records')
)
)
rect = alt.Chart(data.movies.url).mark_rect().encode(
alt.X('IMDB_Rating:Q', bin=True),
alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
alt.Color('count()',
scale=alt.Scale(scheme='greenblue'),
legend=alt.Legend(title='Total Records')
)
)
circ = rect.mark_point().encode(
alt.ColorValue('grey'),
alt.Size('count()',
legend=alt.Legend(title='Records in Selection')
)
).transform_filter(
pts
)
circ = rect.mark_point().encode(
alt.ColorValue('grey'),
alt.Size('count()',
legend=alt.Legend(title='Records in Selection')
)
).transform_filter(
pts
)
bar = alt.Chart(source).mark_bar().encode(
x='Major_Genre:N',
y='count()',
color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
).properties(
width=550,
height=200
).add_selection(pts)
bar = alt.Chart(source).mark_bar().encode(
x='Major_Genre:N',
y='count()',
color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
).properties(
width=550,
height=200
).add_selection(pts)
alt.vconcat(
rect + circ,
bar
).resolve_legend(
color="independent",
size="independent"
)
</py-script>
</body>
display(alt.vconcat(
rect + circ,
bar
).resolve_legend(
color="independent",
size="independent"
), target="altair")
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"altair",
"pandas",
"vega_datasets"
]
&lt;/py-config&gt;
&lt;py-script&gt;
import altair as alt
from vega_datasets import data
source = data.movies.url
pts = alt.selection(type="single", encodings=['x'])
rect = alt.Chart(data.movies.url).mark_rect().encode(
alt.X('IMDB_Rating:Q', bin=True),
alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
alt.Color('count()',
scale=alt.Scale(scheme='greenblue'),
legend=alt.Legend(title='Total Records')
)
)
circ = rect.mark_point().encode(
alt.ColorValue('grey'),
alt.Size('count()',
legend=alt.Legend(title='Records in Selection')
)
).transform_filter(
pts
)
bar = alt.Chart(source).mark_bar().encode(
x='Major_Genre:N',
y='count()',
color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
).properties(
width=550,
height=200
).add_selection(pts)
display(alt.vconcat(
rect + circ,
bar
).resolve_legend(
color="independent",
size="independent"
), target="altair")
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -4,18 +4,115 @@
<meta charset="utf-8">
<link rel="icon" type="image/x-icon" href="./favicon.png">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<script defer src="./assets/prism/prism.js"></script>
</head>
<py-config>
paths = [
"./antigravity.py"
]
</py-config>
<body>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Antigravity</a>
</div>
</nav>
<section class="pyscript">
<py-config>
[[fetch]]
files = ["./antigravity.py"]
</py-config>
<b>Based on xkcd: antigravity https://xkcd.com/353/.</b>
<py-script>
import antigravity
antigravity.fly()
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
[[fetch]]
files = ["./antigravity.py"]
&lt;/py-config&gt;
&lt;py-script&gt;
import antigravity
antigravity.fly()
&lt;/py-script&gt;
</code>
</pre>
<p>antigravity.py</p>
<pre class="prism-code language-python">
<code class="language-python">
import random
import sys
from js import DOMParser, document, setInterval
from pyodide.ffi import create_proxy
from pyodide.http import open_url
class Antigravity:
url = "./antigravity.svg"
def __init__(self, target=None, interval=10, append=True, fly=False):
target = target or sys.stdout._out
self.target = (
document.getElementById(target) if isinstance(target, str) else target
)
doc = DOMParser.new().parseFromString(
open_url(self.url).read(), "image/svg+xml"
)
self.node = doc.documentElement
if append:
self.target.append(self.node)
else:
self.target.replaceChildren(self.node)
self.xoffset, self.yoffset = 0, 0
self.interval = interval
if fly:
self.fly()
def fly(self):
setInterval(create_proxy(self.move), self.interval)
def move(self):
char = self.node.getElementsByTagName("g")[1]
char.setAttribute("transform", f"translate({self.xoffset}, {-self.yoffset})")
self.xoffset += random.normalvariate(0, 1) / 20
if self.yoffset < 50:
self.yoffset += 0.1
else:
self.yoffset += random.normalvariate(0, 1) / 20
_auto = Antigravity(append=True)
fly = _auto.fly
</code>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -1,5 +1,4 @@
import random
import sys
from js import DOMParser, document, setInterval
from pyodide.ffi import create_proxy
@@ -11,9 +10,10 @@ class Antigravity:
url = "./antigravity.svg"
def __init__(self, target=None, interval=10, append=True, fly=False):
target = target or sys.stdout._out
self.target = (
document.getElementById(target) if isinstance(target, str) else target
document.getElementById(target)
if isinstance(target, str)
else document.body
)
doc = DOMParser.new().parseFromString(
open_url(self.url).read(), "image/svg+xml"

View File

@@ -0,0 +1,90 @@
body {
margin: 0;
}
.pyscript {
margin: 0.5rem;
}
.code {
display: flex;
position: absolute;
right: 0px;
z-index: 9998;
top: 7rem;
}
@media (max-width: 1300px) {
.code:has(> .code-section-visible) {
width: 90%;
/* Absolute position is messing up the layout on small screens */
right: 70px;
}
}
.code-section-hidden {
width: 0px;
display: none;
}
.code-section-visible {
display: flex;
flex-direction: column;
width: 100%;
background-color: rgb(45 46 53 / 90%);
padding: 1rem;
border-radius: 10px 0px 0px 10px;
color: #c6c6c8;
}
.code-section-visible p{
margin: 0;
font-style: italic;
font-size: small;
}
.language-html, .language-python {
float: left;
}
#view-code-button {
writing-mode: tb-rl;
text-orientation: sideways-right;
background-color: #1D1D22;
color: white;
padding: 0.5rem;
border-radius: 5px;
cursor: pointer;
height: 81px;
}
nav {
position: sticky;
width: 100%;
top: 0;
left: 0;
z-index: 9999;
}
.logo {
padding-right: 10px;
font-size: 28px;
height: 30px;
max-width: inherit;
}
.title {
text-decoration: none;
text-decoration-line: none;
text-decoration-style: initial;
text-decoration-color: initial;
font-weight: 400;
font-size: 1.5em;
line-height: 2em;
white-space: nowrap;
}
.app-header {
display: flex;
align-items: center;
padding: 0.5rem 1rem;
}

View File

@@ -3,7 +3,7 @@
}
.example h2{
color: #000000;
/* color: #000000; */
font-family: "Inconsolata", monospace;
font-size: 2.25rem;
margin-bottom: 1rem;
@@ -11,10 +11,14 @@
.card {
height: 15rem;
background-color: #FFFFFF;
background-color: var(--color-secondary);
padding: 1rem;
-webkit-box-shadow: var(--card-shadow);
box-shadow: var(--card-shadow);
border-radius: 10px;
}
.card:hover, .card:hover a, .card:hover a:visited, .card:hover h2 {
background-color: var(--color-primary);
color: #1D1D22
}
.card a h2 {
@@ -24,6 +28,17 @@
font-size: 2.25rem;
}
.card a p {
color: var(--text-color);
}
a .card {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.card-content {
height: 100%;
display: flex;
@@ -31,6 +46,10 @@
justify-content: space-between;
}
.card-content a, .card-content a:visited {
color: var(--color-primary);
}
.container-card {
max-width: 1500px;
margin: 0 auto;

View File

@@ -2,6 +2,14 @@
@import "./variables.css";
@import "./reset.css";
body {
background: #2D2E35 url('https://assets.anaconda.com/production/Content/1650828148240.png?w=3240&auto=compress%2Cformat&fit=crop&dm=1650828161&s=c558dc55e0ed1f8419a892e842a5728f') repeat-x center bottom / 250px;
background-attachment: fixed;
overflow-x: hidden;
color: var(--text-color)
}
.container {
max-width: 1510px;
margin: auto;
@@ -9,7 +17,6 @@
}
.title-main {
color: #000000;
font-size: 4.25rem;
font-family: "Inconsolata", monospace;
text-align: center;

View File

@@ -1,4 +1,6 @@
:root {
--color-primary: #2563eb;
--color-primary: #FDA703;
--color-secondary: #1D1D22;
--text-color: white;
--card-shadow: 0px 5px 11px 0px rgb(0 0 0 / 15%);
}

View File

@@ -0,0 +1,3 @@
/* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+clike+javascript+python */
code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}

File diff suppressed because one or more lines are too long

View File

@@ -14,18 +14,29 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<py-config>
packages = [
"bokeh",
"numpy"
]
</py-config>
<h1>Bokeh Example</h1>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a>
</div>
</nav>
<section class="pyscript">
<div id="myplot"></div>
<py-config>
packages = [
"bokeh",
"numpy"
]
</py-config>
<py-script id="main">
import json
import pyodide
@@ -45,6 +56,60 @@ p_json = json.dumps(json_item(p, "myplot"))
Bokeh.embed.embed_item(JSON.parse(p_json))
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"bokeh",
"numpy"
]
&lt;/py-config&gt;
&lt;py-script id="main"&gt;
import json
import pyodide
</body>
from js import Bokeh, console, JSON
from bokeh.embed import json_item
from bokeh.plotting import figure
from bokeh.resources import CDN
# create a new plot with default tools, using figure
p = figure(plot_width=400, plot_height=400)
# add a circle renderer with x and y coordinates, size, color, and alpha
p.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=15, line_color="navy", fill_color="orange", fill_alpha=0.5)
p_json = json.dumps(json_item(p, "myplot"))
Bokeh.embed.embed_item(JSON.parse(p_json))
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -7,6 +7,9 @@
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.2.min.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
<script type="text/javascript">
Bokeh.set_log_level("info");
@@ -14,18 +17,27 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<py-config>
packages = [
"bokeh",
"numpy"
]
</py-config>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a>
</div>
</nav>
<section class="pyscript">
<h1>Bokeh Example</h1>
<div id="myplot"></div>
<py-config>
packages = [
"bokeh",
"numpy"
]
</py-config>
<py-script id="main">
import asyncio
import json
@@ -93,8 +105,112 @@ async def show(plot, target):
jsdoc = views[0].model.document
_link_docs(pydoc, jsdoc)
await show(row, 'myplot')
asyncio.ensure_future(show(row, 'myplot'))
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"bokeh",
"numpy"
]
&lt;/py-config&gt;
&lt;py-script id="main"&gt;
import asyncio
import json
import pyodide
</body>
from js import Bokeh, console, JSON
from bokeh import __version__
from bokeh.document import Document
from bokeh.embed.util import OutputDocumentFor, standalone_docs_json_and_render_items
from bokeh.models import Slider, Div
from bokeh.layouts import Row
from bokeh.protocol.messages.patch_doc import process_document_events
# create a new plot with default tools, using figure
p = Slider(start=0.1, end=10, value=1, step=.1, title="Amplitude")
div = Div(text=f'Amplitude is: {p.value}')
def callback(attr, old, new):
div.text = f'Amplitude is: {new}'
p.on_change('value', callback)
row = Row(children=[p, div])
def doc_json(model, target):
with OutputDocumentFor([model]) as doc:
doc.title = ""
docs_json, _ = standalone_docs_json_and_render_items(
[model], suppress_callback_warning=True
)
doc_json = list(docs_json.values())[0]
root_id = doc_json['roots']['root_ids'][0]
return doc, json.dumps(dict(
target_id = target,
root_id = root_id,
doc = doc_json,
version = __version__,
))
def _link_docs(pydoc, jsdoc):
def jssync(event):
if getattr(event, 'setter_id', None) is not None:
return
events = [event]
json_patch = jsdoc.create_json_patch_string(pyodide.ffi.to_js(events))
pydoc.apply_json_patch(json.loads(json_patch))
jsdoc.on_change(pyodide.ffi.create_proxy(jssync), pyodide.ffi.to_js(False))
def pysync(event):
json_patch, buffers = process_document_events([event], use_buffers=True)
buffer_map = {}
for (ref, buffer) in buffers:
buffer_map[ref['id']] = buffer
jsdoc.apply_json_patch(JSON.parse(json_patch), pyodide.ffi.to_js(buffer_map), setter_id='js')
pydoc.on_change(pysync)
async def show(plot, target):
pydoc, model_json = doc_json(plot, target)
views = await Bokeh.embed.embed_item(JSON.parse(model_json))
jsdoc = views[0].model.document
_link_docs(pydoc, jsdoc)
asyncio.ensure_future(show(row, 'myplot'))
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -6,7 +6,10 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.loading {
display: inline-block;
@@ -27,6 +30,15 @@
</head>
<body>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Simple d3 visualization</a>
</div>
</nav>
<section class="pyscript">
<b>
Based on <i><a href="https://observablehq.com/@d3/learn-d3-shapes?collection=@d3/learn-d3>">Learn D3: Shapes</a></i> tutorial.
</b>
@@ -45,16 +57,7 @@
</div>
</div>
<script type="importmap">
{
"imports": {
"d3": "https://cdn.skypack.dev/pin/d3@v7.6.1-1Q0NZ0WZnbYeSjDusJT3/mode=imports,min/optimized/d3.js"
}
}
</script>
<script type="module">
import * as d3 from "https://cdn.skypack.dev/pin/d3@v7.6.1-1Q0NZ0WZnbYeSjDusJT3/mode=imports,min/optimized/d3.js";
const fruits = [
{name: "🍊", count: 21},
@@ -109,8 +112,9 @@ for (const d of data) {
</script>
<py-script>
import js
from pyodide.ffi import create_proxy, to_js
import d3
d3 = js.d3
fruits = [
dict(name="🍊", count=21),
@@ -165,6 +169,146 @@ for d in data:
.attr("dy", "1.3em")
.text(d_py["value"]))
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;script&gt;
import * as d3 from "https://cdn.skypack.dev/pin/d3@v7.6.1-1Q0NZ0WZnbYeSjDusJT3/mode=imports,min/optimized/d3.js";
</body>
const fruits = [
{name: "🍊", count: 21},
{name: "🍇", count: 13},
{name: "🍏", count: 8},
{name: "🍌", count: 5},
{name: "🍐", count: 3},
{name: "🍋", count: 2},
{name: "🍎", count: 1},
{name: "🍉", count: 1},
]
const fn = (d) => d.count
const data = d3.pie().value(fn)(fruits)
const arc = d3.arc()
.innerRadius(210)
.outerRadius(310)
.padRadius(300)
.padAngle(2 / 300)
.cornerRadius(8)
const js = d3.select("#js")
js.select(".loading").remove()
const svg = js
.append("svg")
.attr("viewBox", "-320 -320 640 640")
.attr("width", "400")
.attr("height", "400")
for (const d of data) {
svg.append("path")
.style("fill", "steelblue")
.attr("d", arc(d))
const text = svg.append("text")
.style("fill", "white")
.attr("transform", `translate(${arc.centroid(d).join(",")})`)
.attr("text-anchor", "middle")
text.append("tspan")
.style("font-size", "24")
.attr("x", "0").text(d.data.name)
text.append("tspan")
.style("font-size", "18")
.attr("x", "0")
.attr("dy", "1.3em")
.text(d.value)
}
&lt;/script&gt;
&lt;py-script&gt;
from pyodide.ffi import create_proxy, to_js
import d3
fruits = [
dict(name="🍊", count=21),
dict(name="🍇", count=13),
dict(name="🍏", count=8),
dict(name="🍌", count=5),
dict(name="🍐", count=3),
dict(name="🍋", count=2),
dict(name="🍎", count=1),
dict(name="🍉", count=1),
]
fn = create_proxy(lambda d, *_: d["count"])
data = d3.pie().value(fn)(to_js(fruits))
arc = (d3.arc()
.innerRadius(210)
.outerRadius(310)
.padRadius(300)
.padAngle(2 / 300)
.cornerRadius(8))
py = d3.select("#py")
py.select(".loading").remove()
svg = (py
.append("svg")
.attr("viewBox", "-320 -320 640 640")
.attr("width", "400")
.attr("height", "400"))
for d in data:
d_py = d.to_py()
(svg.append("path")
.style("fill", "steelblue")
.attr("d", arc(d)))
text = (svg.append("text")
.style("fill", "white")
.attr("transform", f"translate({arc.centroid(d).join(',')})")
.attr("text-anchor", "middle"))
(text.append("tspan")
.style("font-size", "24")
.attr("x", "0")
.text(d_py["data"]["name"]))
(text.append("tspan")
.style("font-size", "18")
.attr("x", "0")
.attr("dy", "1.3em")
.text(d_py["value"]))
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -5,16 +5,30 @@
<link rel="icon" type="image/x-icon" href="./favicon.png">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-config>
packages = [
"folium",
"pandas"
]
</py-config>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<div id="folium" style="width: 100%; height: 100%"></div>
<py-script output="folium">
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Folium</a>
</div>
</nav>
<section class="pyscript">
<div id="folium"></div>
<py-config>
packages = [
"folium",
"pandas"
]
</py-config>
<py-script>
import folium
import json
import pandas as pd
@@ -45,7 +59,78 @@ folium.Choropleth(
folium.LayerControl().add_to(m)
m
display(m, target="folium")
</py-script>
</body>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;div id="folium"&gt;&lt;/div&gt;
&lt;py-config&gt;
packages = [
"folium",
"pandas"
]
&lt;/py-config&gt;
&lt;py-script&gt;
import folium
import json
import pandas as pd
from pyodide.http import open_url
url = (
"https://raw.githubusercontent.com/python-visualization/folium/master/examples/data"
)
state_geo = f"{url}/us-states.json"
state_unemployment = f"{url}/US_Unemployment_Oct2012.csv"
state_data = pd.read_csv(open_url(state_unemployment))
geo_json = json.loads(open_url(state_geo).read())
m = folium.Map(location=[48, -102], zoom_start=3)
folium.Choropleth(
geo_data=geo_json,
name="choropleth",
data=state_data,
columns=["State", "Unemployment"],
key_on="feature.id",
fill_color="YlGn",
fill_opacity=0.7,
line_opacity=0.2,
legend_name="Unemployment Rate (%)",
).add_to(m)
folium.LayerControl().add_to(m)
display(m, target="folium")
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Svelte app</title>
<title>Say Hello</title>
<link rel="icon" type="image/png" href="../favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />

View File

@@ -8,17 +8,64 @@
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
Hello world! <br>
This is the current date and time, as computed by Python:
<py-script>
from datetime import datetime
now = datetime.now()
now.strftime("%m/%d/%Y, %H:%M:%S")
</py-script>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Hello World</a>
</div>
</nav>
<section class="pyscript">
Hello world! <br>
This is the current date and time, as computed by Python:
<py-script>
from datetime import datetime
now = datetime.now()
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-script&gt;
from datetime import datetime
now = datetime.now()
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -254,20 +254,6 @@
</div>
</div>
<div class="card">
<div class="card-content">
<a href="./toga/freedom.html" target="_blank">
<h2 class="text-2xl font-bold text-blue-600">
Freedom Units!
</h2>
</a>
<p>
A <a href="https://beeware.org/project/projects/libraries/toga/" target="_blank">Toga</a>
application (a Fahrenheit to Celsius converter), rendered as a Single Page App
</p>
</div>
</div>
<div class="card">
<div class="card-content">
<a href="./numpy_canvas_fractals.html" target="_blank">

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Svelte app</title>
<title>Mario</title>
<link rel="icon" type="image/png" href="../favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>PyMarkdown</title>
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="../build/pyscript.css" />
<script defer src="../build/pyscript.js"></script>
<py-config>
packages = [
"markdown"
]
plugins = [
"../build/plugins/python/py_markdown.py"
]
</py-config>
</head>
<body>
<py-md>#Hello world!</py-md>
</body>
</html>

View File

@@ -5,15 +5,29 @@
<link rel="icon" type="image/x-icon" href="./favicon.png">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-config>
packages = [
"matplotlib"
]
</py-config>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Matplotlib</a>
</div>
</nav>
<section class="pyscript">
<div id="mpl"></div>
<py-script output="mpl">
<py-config>
packages = [
"matplotlib"
]
</py-config>
<py-script>
import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np
@@ -46,7 +60,79 @@ tpc = ax1.tripcolor(triang, z, shading='flat')
fig1.colorbar(tpc)
ax1.set_title('tripcolor of Delaunay triangulation, flat shading')
fig1
display(fig1, target="mpl")
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;div id="mpl"&gt;&lt;/div&gt;
&lt;py-config&gt;
packages = [
"matplotlib",
]
&lt;/py-config&gt;
&lt;py-script&gt;
import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np
# First create the x and y coordinates of the points.
n_angles = 36
n_radii = 8
min_radius = 0.25
radii = np.linspace(min_radius, 0.95, n_radii)
angles = np.linspace(0, 2 * np.pi, n_angles, endpoint=False)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
angles[:, 1::2] += np.pi / n_angles
x = (radii * np.cos(angles)).flatten()
y = (radii * np.sin(angles)).flatten()
z = (np.cos(radii) * np.cos(3 * angles)).flatten()
# Create the Triangulation; no triangles so Delaunay triangulation created.
triang = tri.Triangulation(x, y)
# Mask off unwanted triangles.
triang.set_mask(np.hypot(x[triang.triangles].mean(axis=1),
y[triang.triangles].mean(axis=1))
< min_radius)
fig1, ax1 = plt.subplots()
ax1.set_aspect('equal')
tpc = ax1.tripcolor(triang, z, shading='flat')
fig1.colorbar(tpc)
ax1.set_title('tripcolor of Delaunay triangulation, flat shading')
display(fig1, target="mpl")
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</body>
</html>

View File

@@ -7,13 +7,15 @@
</head>
<body>
<py-config>
packages = [
"numpy",
"networkx",
"matplotlib"
]
packages = [
"numpy",
"networkx",
"matplotlib"
]
</py-config>
<py-script>
import numpy as np
import networkx as nx

View File

@@ -9,19 +9,20 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-config>
packages = [
"micrograd",
"numpy",
"matplotlib"
]
</py-config>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
</head>
<body style="padding-top: 20px; padding-right: 20px; padding-bottom: 20px; padding-left: 20px">
<h1>Micrograd - A tiny Autograd engine (with a bite! :))</h1><br>
<py-config>
packages = [
"micrograd",
"numpy",
"matplotlib"
]
</py-config>
<div>
<p>
<a href="https://github.com/karpathy/micrograd">Micrograd</a> is a tiny Autograd engine created

View File

@@ -5,7 +5,9 @@
<link rel="icon" type="image/x-icon" href="./favicon.png">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
<style>
.loading {
display: inline-block;
@@ -30,8 +32,15 @@
</head>
<body>
<b>
</b>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Fractals with NumPy and canvas</a>
</div>
</nav>
<section class="pyscript">
<div style="display: flex; flex-direction: column; gap: 1em; width: 600px">
<div id="mandelbrot">
<div style="text-align: center">Mandelbrot set</div>
@@ -78,18 +87,22 @@
</div>
</div>
<py-config type="json">
{
"packages": [
"numpy",
"sympy"
],
"paths": [
"./palettes.py",
"./fractals.py"
]
}
</py-config>
<py-config type="json">
{
"packages": [
"numpy",
"sympy"
],
"fetch": [
{
"files": [
"./palettes.py",
"./fractals.py"
]
}
]
}
</py-config>
<py-script>
from pyodide.ffi import to_js, create_proxy
@@ -312,12 +325,290 @@ canvas.addEventListener("mousemove", create_proxy(mousemove))
import asyncio
_ = await asyncio.gather(
draw_mandelbrot(),
draw_julia(),
draw_newton(),
)
</py-script>
async def main():
_ = await asyncio.gather(
draw_mandelbrot(),
draw_julia(),
draw_newton(),
)
</body>
asyncio.ensure_future(main())
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config type="json"&gt;
{
"packages": [
"numpy",
"sympy"
],
"fetch": [
{
"files": [
"./palettes.py",
"./fractals.py"
]
}
]
}
&lt;/py-config&gt;
&lt;py-script&gt;
from pyodide.ffi import to_js, create_proxy
import numpy as np
import sympy
from palettes import Magma256
from fractals import mandelbrot, julia, newton
from js import (
console,
document,
devicePixelRatio,
ImageData,
Uint8ClampedArray,
CanvasRenderingContext2D as Context2d,
requestAnimationFrame,
)
def prepare_canvas(width: int, height: int, canvas: Element) -> Context2d:
ctx = canvas.getContext("2d")
canvas.style.width = f"{width}px"
canvas.style.height = f"{height}px"
canvas.width = width
canvas.height = height
ctx.clearRect(0, 0, width, height)
return ctx
def color_map(array: np.array, palette: np.array) -> np.array:
size, _ = palette.shape
index = (array/array.max()*(size - 1)).round().astype("uint8")
width, height = array.shape
image = np.full((width, height, 4), 0xff, dtype=np.uint8)
image[:, :, :3] = palette[index]
return image
def draw_image(ctx: Context2d, image: np.array) -> None:
data = Uint8ClampedArray.new(to_js(image.tobytes()))
width, height, _ = image.shape
image_data = ImageData.new(data, width, height)
ctx.putImageData(image_data, 0, 0)
width, height = 600, 600
async def draw_mandelbrot() -> None:
spinner = document.querySelector("#mandelbrot .loading")
canvas = document.querySelector("#mandelbrot canvas")
spinner.style.display = ""
canvas.style.display = "none"
ctx = prepare_canvas(width, height, canvas)
console.log("Computing Mandelbrot set ...")
console.time("mandelbrot")
iters = mandelbrot(width, height)
console.timeEnd("mandelbrot")
image = color_map(iters, Magma256)
draw_image(ctx, image)
spinner.style.display = "none"
canvas.style.display = "block"
async def draw_julia() -> None:
spinner = document.querySelector("#julia .loading")
canvas = document.querySelector("#julia canvas")
spinner.style.display = ""
canvas.style.display = "none"
ctx = prepare_canvas(width, height, canvas)
console.log("Computing Julia set ...")
console.time("julia")
iters = julia(width, height)
console.timeEnd("julia")
image = color_map(iters, Magma256)
draw_image(ctx, image)
spinner.style.display = "none"
canvas.style.display = "block"
def ranges():
x0_in = document.querySelector("#x0")
x1_in = document.querySelector("#x1")
y0_in = document.querySelector("#y0")
y1_in = document.querySelector("#y1")
xr = (float(x0_in.value), float(x1_in.value))
yr = (float(y0_in.value), float(y1_in.value))
return xr, yr
current_image = None
async def draw_newton() -> None:
spinner = document.querySelector("#newton .loading")
canvas = document.querySelector("#newton canvas")
spinner.style.display = ""
canvas.style.display = "none"
ctx = prepare_canvas(width, height, canvas)
console.log("Computing Newton set ...")
poly_in = document.querySelector("#poly")
coef_in = document.querySelector("#coef")
conv_in = document.querySelector("#conv")
iter_in = document.querySelector("#iter")
xr, yr = ranges()
# z**3 - 1
# z**8 + 15*z**4 - 16
# z**3 - 2*z + 2
expr = sympy.parse_expr(poly_in.value)
coeffs = [ complex(c) for c in reversed(sympy.Poly(expr, sympy.Symbol("z")).all_coeffs()) ]
poly = np.polynomial.Polynomial(coeffs)
coef = complex(sympy.parse_expr(coef_in.value))
console.time("newton")
iters, roots = newton(width, height, p=poly, a=coef, xr=xr, yr=yr)
console.timeEnd("newton")
if conv_in.checked:
n = poly.degree() + 1
k = int(len(Magma256)/n)
colors = Magma256[::k, :][:n]
colors[0, :] = [255, 0, 0] # red: no convergence
image = color_map(roots, colors)
else:
image = color_map(iters, Magma256)
global current_image
current_image = image
draw_image(ctx, image)
spinner.style.display = "none"
canvas.style.display = "block"
handler = create_proxy(lambda _event: draw_newton())
document.querySelector("#newton fieldset").addEventListener("change", handler)
canvas = document.querySelector("#newton canvas")
is_selecting = False
init_sx, init_sy = None, None
sx, sy = None, None
async def mousemove(event):
global is_selecting
global init_sx
global init_sy
global sx
global sy
def invert(sx, source_range, target_range):
source_start, source_end = source_range
target_start, target_end = target_range
factor = (target_end - target_start)/(source_end - source_start)
offset = -(factor * source_start) + target_start
return (sx - offset) / factor
bds = canvas.getBoundingClientRect()
event_sx, event_sy = event.clientX - bds.x, event.clientY - bds.y
ctx = canvas.getContext("2d")
pressed = event.buttons == 1
if is_selecting:
if not pressed:
xr, yr = ranges()
x0 = invert(init_sx, xr, (0, width))
x1 = invert(sx, xr, (0, width))
y0 = invert(init_sy, yr, (0, height))
y1 = invert(sy, yr, (0, height))
document.querySelector("#x0").value = x0
document.querySelector("#x1").value = x1
document.querySelector("#y0").value = y0
document.querySelector("#y1").value = y1
is_selecting = False
init_sx, init_sy = None, None
sx, sy = init_sx, init_sy
await draw_newton()
else:
ctx.save()
ctx.clearRect(0, 0, width, height)
draw_image(ctx, current_image)
sx, sy = event_sx, event_sy
ctx.beginPath()
ctx.rect(init_sx, init_sy, sx - init_sx, sy - init_sy)
ctx.fillStyle = "rgba(255, 255, 255, 0.4)"
ctx.strokeStyle = "rgba(255, 255, 255, 1.0)"
ctx.fill()
ctx.stroke()
ctx.restore()
else:
if pressed:
is_selecting = True
init_sx, init_sy = event_sx, event_sy
sx, sy = init_sx, init_sy
canvas.addEventListener("mousemove", create_proxy(mousemove))
import asyncio
async def main():
_ = await asyncio.gather(
draw_mandelbrot(),
draw_julia(),
draw_newton(),
)
asyncio.ensure_future(main())
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -3,23 +3,36 @@
<title>Panel Example</title>
<meta charset="iso-8859-1">
<link rel="icon" type="image/x-icon" href="./favicon.png">
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@holoviz/panel@0.13.1/dist/panel.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@holoviz/panel@0.14.1/dist/panel.min.js"></script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<py-config>
packages = [
"bokeh",
"numpy",
"panel==0.13.1"
]
</py-config>
<body>
<h1>Panel Example</h1>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Panel Example</a>
</div>
</nav>
<section class="pyscript">
<div id="simple_app"></div>
<py-config>
packages = [
"bokeh",
"numpy",
"panel==0.14.1"
]
</py-config>
<py-script>
import panel as pn
@@ -30,5 +43,52 @@ packages = [
pn.Row(slider, pn.bind(callback, slider)).servable(target='simple_app');
</py-script>
</body>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"bokeh",
"numpy",
"panel==0.13.1"
]
&lt;/py-config&gt;
&lt;py-script&gt;
import panel as pn
slider = pn.widgets.FloatSlider(start=0, end=10, name='Amplitude')
def callback(new):
return f'Amplitude is: {new}'
pn.Row(slider, pn.bind(callback, slider)).servable(target='simple_app');
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -40,44 +40,43 @@
</style>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<py-config>
packages = [
"bokeh",
"numpy",
"pandas",
"panel==0.13.1"
]
</py-config>
<body>
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
<nav class="navbar navbar-expand-md navbar-dark sticky-top shadow" id="header" style="background-color: #000000;">
<button type="button" class="navbar-toggle collapsed" id="sidebarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="app-header">
<a class="navbar-brand app-logo" href="/">
<img src="./logo.png" class="app-logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Panel DeckGL NYC Taxi Demo</a>
</div>
</nav>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Panel DeckGL NYC Taxi Demo</a>
</div>
</nav>
<section class="pyscript">
<div class="d-flex flex-nowrap" id="content">
<div class="sidenav" id="sidebar">
<ul class="nav flex-column" >
<div class="bk-root" id="widgets"></div>
<py-repl id="my-repl" auto-generate="true"> </py-repl>
</ul>
</div>
<div class="col mh-100" style="padding: 0">
<div class="bk-root" id="plot"></div>
</div>
<div class="sidenav" id="sidebar">
<ul class="nav flex-column" >
<div class="bk-root" id="widgets"></div>
<py-repl id="my-repl" auto-generate="true"> </py-repl>
</ul>
</div>
<div class="col mh-100" style="padding: 0">
<div class="bk-root" id="plot"></div>
</div>
</div>
</div>
<py-config>
packages = [
"bokeh",
"numpy",
"pandas",
"panel==0.13.1"
]
</py-config>
<py-script>
import panel as pn
import pandas as pd
@@ -219,5 +218,185 @@ packages = [
app.deck_gl.servable(target='plot')
controls.servable(target='widgets');
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0" style="height: 95px;">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"bokeh",
"numpy",
"pandas",
"panel==0.13.1"
]
&lt;/py-config&gt;
&lt;py-script&gt;
import panel as pn
import pandas as pd
import param
from pyodide.http import open_url
MAPBOX_KEY = "pk.eyJ1IjoicGFuZWxvcmciLCJhIjoiY2s1enA3ejhyMWhmZjNobjM1NXhtbWRrMyJ9.B_frQsAVepGIe-HiOJeqvQ"
class App(param.Parameterized):
data = param.DataFrame(precedence=-1)
view = param.DataFrame(precedence=-1)
arc_view = param.DataFrame(precedence=-1)
radius = param.Integer(default=50, bounds=(20, 1000))
elevation = param.Integer(default=10, bounds=(0, 50))
hour = param.Integer(default=0, bounds=(0, 23))
speed = param.Integer(default=1, bounds=(0, 10), precedence=-1)
play = param.Event(label='▷')
def __init__(self, **params):
self.deck_gl = None
super().__init__(**params)
self.deck_gl = pn.pane.DeckGL(
dict(self.spec),
mapbox_api_key=MAPBOX_KEY,
throttle={'click': 10},
sizing_mode='stretch_both',
margin=0
)
self.deck_gl.param.watch(self._update_arc_view, 'click_state')
self._playing = False
self._cb = pn.state.add_periodic_callback(
self._update_hour, 1000//self.speed, start=False
)
@property
def spec(self):
return {
"initialViewState": {
"bearing": 0,
"latitude": 40.7,
"longitude": -73.9,
"maxZoom": 15,
"minZoom": 5,
"pitch": 40.5,
"zoom": 11
},
"layers": [self.hex_layer, self.arc_layer],
"mapStyle": "mapbox://styles/mapbox/dark-v9",
"views": [
{"@@type": "MapView", "controller": True}
]
}
@property
def hex_layer(self):
return {
"@@type": "HexagonLayer",
"autoHighlight": True,
"coverage": 1,
"data": self.data if self.view is None else self.view,
"elevationRange": [0, 100],
"elevationScale": self.elevation,
"radius": self.radius,
"extruded": True,
"getPosition": "@@=[pickup_x, pickup_y]",
"id": "8a553b25-ef3a-489c-bbe2-e102d18a3211"
}
@property
def arc_layer(self):
return {
"@@type": "ArcLayer",
"id": 'arc-layer',
"data": self.arc_view,
"pickable": True,
"getWidth": 1,
"getSourcePosition": "@@=[pickup_x, pickup_y]",
"getTargetPosition": "@@=[dropoff_x, dropoff_y]",
"getSourceColor": [0, 255, 0, 180],
"getTargetColor": [240, 100, 0, 180]
}
def _update_hour(self):
self.hour = (self.hour+1) % 24
@param.depends('view', watch=True)
def _update_arc_view(self, event=None):
data = self.data if self.view is None else self.view
if not self.deck_gl or not self.deck_gl.click_state:
self.arc_view = data.iloc[:0]
return
lon, lat = self.deck_gl.click_state['coordinate']
tol = 0.001
self.arc_view = data[
(df.pickup_x>=float(lon-tol)) &
(df.pickup_x<=float(lon+tol)) &
(df.pickup_y>=float(lat-tol)) &
(df.pickup_y<=float(lat+tol))
]
@param.depends('hour', watch=True, on_init=True)
def _update_hourly_view(self):
self.view = self.data[self.data.hour==self.hour]
@param.depends('speed', watch=True)
def _update_speed(self):
self._cb.period = 1000//self.speed
@param.depends('play', watch=True)
def _play_pause(self):
if self._playing:
self._cb.stop()
self.param.play.label = '▷'
self.param.speed.precedence = -1
else:
self._cb.start()
self.param.play.label = '❚❚'
self.param.speed.precedence = 1
self._playing = not self._playing
@param.depends('view', 'radius', 'elevation', 'arc_view', watch=True)
def update_spec(self):
self.deck_gl.object = dict(self.spec)
url = 'https://s3.eu-west-1.amazonaws.com/assets.holoviews.org/data/nyc_taxi_wide.csv'
df = pd.read_csv(open_url(url))
app = App(data=df)
controls = pn.Param(app.param, sizing_mode='stretch_width', show_name=False)
app.deck_gl.servable(target='plot')
controls.servable(target='widgets')
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</body>
</html>

View File

@@ -41,48 +41,47 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<py-config>
packages = [
"altair",
"numpy",
"pandas",
"scikit-learn",
"panel==0.13.1"
]
</py-config>
<body>
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
<nav class="navbar navbar-expand-md navbar-dark sticky-top shadow" id="header" style="background-color: #000000;">
<button type="button" class="navbar-toggle collapsed" id="sidebarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="app-header">
<a class="navbar-brand app-logo" href="/">
<img src="./logo.png" class="app-logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Panel KMeans Clustering Demo</a>
</div>
</nav>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Panel KMeans Clustering Demo</a>
</div>
</nav>
<section class="pyscript">
<div class="row overflow-hidden" id="content">
<div class="sidenav" id="sidebar">
<ul class="nav flex-column">
<div class="bk-root" id="x-widget"></div>
<div class="bk-root" id="y-widget"></div>
<div class="bk-root" id="n-widget"></div>
</ul>
</div>
<div class="col mh-100 float-left" id="main">
<div class="bk-root" id="intro"></div>
<div class="bk-root" id="cluster-plot"></div>
<div class="bk-root" id="table"></div>
</div>
<div class="sidenav" id="sidebar">
<ul class="nav flex-column">
<div class="bk-root" id="x-widget"></div>
<div class="bk-root" id="y-widget"></div>
<div class="bk-root" id="n-widget"></div>
</ul>
</div>
<div class="col mh-100 float-left" id="main">
<div class="bk-root" id="intro"></div>
<div class="bk-root" id="cluster-plot"></div>
<div class="bk-root" id="table"></div>
</div>
</div>
</div>
<py-config>
packages = [
"altair",
"numpy",
"pandas",
"scikit-learn",
"panel==0.13.1"
]
</py-config>
<py-script>
import altair as alt
import panel as pn
@@ -159,6 +158,122 @@ packages = [
update_table()
update_chart()
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0" style="height: 95px;">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"altair",
"numpy",
"pandas",
"scikit-learn",
"panel==0.13.1"
]
&lt;/py-config&gt;
&lt;py-script&gt;
import altair as alt
import panel as pn
import pandas as pd
from sklearn.cluster import KMeans
from pyodide.http import open_url
pn.config.sizing_mode = 'stretch_width'
url = 'https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv'
penguins = pd.read_csv(open_url(url)).dropna()
cols = list(penguins.columns)[2:6]
x = pn.widgets.Select(name='x', options=cols, value='bill_depth_mm').servable(target='x-widget')
y = pn.widgets.Select(name='y', options=cols, value='bill_length_mm').servable(target='y-widget')
n_clusters = pn.widgets.IntSlider(name='n_clusters', start=1, end=5, value=3).servable(target='n-widget')
brush = alt.selection_interval(name='brush') # selection of type "interval"
def get_clusters(n_clusters):
kmeans = KMeans(n_clusters=n_clusters)
est = kmeans.fit(penguins[cols].values)
df = penguins.copy()
df['labels'] = est.labels_.astype('str')
return df
def get_chart(x, y, df):
centers = df.groupby('labels').mean()
return (
alt.Chart(df)
.mark_point(size=100)
.encode(
x=alt.X(x, scale=alt.Scale(zero=False)),
y=alt.Y(y, scale=alt.Scale(zero=False)),
shape='labels',
color='species'
).add_selection(brush).properties(width=800) +
alt.Chart(centers)
.mark_point(size=250, shape='cross', color='black')
.encode(x=x+':Q', y=y+':Q')
)
intro = pn.pane.Markdown("""
This app provides an example of **building a simple dashboard using
Panel**.\n\nIt demonstrates how to take the output of **k-means
clustering on the Penguins dataset** using scikit-learn,
parameterizing the number of clusters and the variables to
plot.\n\nThe plot and the table are linked, i.e. selecting on the plot
will filter the data in the table.\n\n The **`x` marks the center** of
the cluster.
""").servable(target='intro')
chart = pn.pane.Vega().servable(target='cluster-plot')
table = pn.widgets.Tabulator(pagination='remote', page_size=10).servable(target='table')
def update_table(event=None):
table.value = get_clusters(n_clusters.value)
n_clusters.param.watch(update_table, 'value')
@pn.depends(x, y, n_clusters, watch=True)
def update_chart(*events):
chart.object = get_chart(x.value, y.value, table.value)
chart.selection.param.watch(update_filters, 'brush')
def update_filters(event=None):
filters = []
for k, v in (getattr(event, 'new') or {}).items():
filters.append(dict(field=k, type='>=', value=v[0]))
filters.append(dict(field=k, type='<=', value=v[1]))
table.filters = filters
update_table()
update_chart()
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
<script>
$(document).ready(function () {

View File

@@ -32,31 +32,21 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<py-config>
packages = [
"bokeh",
"numpy",
"pandas",
"panel==0.13.1"
]
</py-config>
<body>
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
<nav class="navbar navbar-expand-md navbar-dark sticky-top shadow" id="header" style="background-color: #000000;">
<button type="button" class="navbar-toggle collapsed" id="sidebarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="app-header">
<a class="navbar-brand app-logo" href="/">
<img src="./logo.png" class="app-logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Panel Streaming Demo</a>
</div>
</nav>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Panel Streaming Demo</a>
</div>
</nav>
<section class="pyscript">
<div class="row overflow-hidden" id="content">
<div class="col mh-100 float-left" id="main">
<div class="bk-root" id="controls"></div>
@@ -68,6 +58,15 @@ packages = [
</div>
</div>
<py-config>
packages = [
"bokeh",
"numpy",
"pandas",
"panel==0.13.1"
]
</py-config>
<py-script>
import panel as pn
import numpy as np
@@ -115,5 +114,90 @@ packages = [
pn.pane.Bokeh(p).servable(target='plot')
pn.Row(cb.param.period, rollover, follow, width=400).servable(target='controls')
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0" style="height: 95px;">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"bokeh",
"numpy",
"pandas",
"panel==0.13.1"
]
&lt;/py-config&gt;
&lt;py-script&gt;
import panel as pn
import numpy as np
import pandas as pd
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
df = pd.DataFrame(np.random.randn(10, 4), columns=list('ABCD')).cumsum()
rollover = pn.widgets.IntInput(name='Rollover', value=15)
follow = pn.widgets.Checkbox(name='Follow', value=True, align='end')
tabulator = pn.widgets.Tabulator(df, height=450, width=400).servable(target='table')
def color_negative_red(val):
"""
Takes a scalar and returns a string with
the css property `'color: red'` for negative
strings, black otherwise.
"""
color = 'red' if val < 0 else 'green'
return 'color: %s' % color
tabulator.style.applymap(color_negative_red)
p = figure(height=450, width=600)
cds = ColumnDataSource(data=ColumnDataSource.from_df(df))
p.line('index', 'A', source=cds, line_color='red')
p.line('index', 'B', source=cds, line_color='green')
p.line('index', 'C', source=cds, line_color='blue')
p.line('index', 'D', source=cds, line_color='purple')
def stream():
data = df.iloc[-1] + np.random.randn(4)
tabulator.stream(data, rollover=rollover.value, follow=follow.value)
value = {k: [v] for k, v in tabulator.value.iloc[-1].to_dict().items()}
value['index'] = [tabulator.value.index[-1]]
cds.stream(value)
cb = pn.state.add_periodic_callback(stream, 200)
pn.pane.Bokeh(p).servable(target='plot')
pn.Row(cb.param.period, rollover, follow, width=400).servable(target='controls')
&lt;/py-script&gt;
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -1,7 +1,9 @@
from datetime import datetime as dt
import pyscript
class PyItem(PyItemTemplate):
class PyItem(pyscript.PyItemTemplate):
def on_click(self, evt=None):
self.data["done"] = not self.data["done"]
self.strike(self.data["done"])
@@ -9,7 +11,7 @@ class PyItem(PyItemTemplate):
self.select("input").element.checked = self.data["done"]
class PyList(PyListTemplate):
class PyList(pyscript.PyListTemplate):
item_class = PyItem
def add(self, item):

View File

@@ -9,20 +9,115 @@
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<py-config>
paths = [
"./antigravity.py"
]
</py-config>
<body>
<h1><b>pyscript REPL</b></h1>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">PyScript REPL</a>
</div>
</nav>
<section class="pyscript">
<h1><b>PyScript REPL</b></h1>
Tip: press Shift-ENTER to evaluate a cell
<br>
<div>
<py-config>
[[fetch]]
files = ["./antigravity.py"]
</py-config>
<div style="margin-right: 3rem;">
<py-repl id="my-repl" auto-generate="true"> </py-repl>
</div>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
[[fetch]]
files = [&quot;./antigravity.py&quot;]
&lt;/py-config&gt;
&lt;div&gt;
&lt;py-repl id=&quot;my-repl&quot; auto-generate=&quot;true&quot;&gt; &lt;/py-repl&gt;
&lt;/div&gt;
</code>
</pre>
<p>antigravity.py</p>
<pre class="prism-code language-python">
<code class="language-python">
import random
import sys
from js import DOMParser, document, setInterval
from pyodide.ffi import create_proxy
from pyodide.http import open_url
class Antigravity:
url = "./antigravity.svg"
def __init__(self, target=None, interval=10, append=True, fly=False):
target = target or sys.stdout._out
self.target = (
document.getElementById(target) if isinstance(target, str) else target
)
doc = DOMParser.new().parseFromString(
open_url(self.url).read(), "image/svg+xml"
)
self.node = doc.documentElement
if append:
self.target.append(self.node)
else:
self.target.replaceChildren(self.node)
self.xoffset, self.yoffset = 0, 0
self.interval = interval
if fly:
self.fly()
def fly(self):
setInterval(create_proxy(self.move), self.interval)
def move(self):
char = self.node.getElementsByTagName("g")[1]
char.setAttribute("transform", f"translate({self.xoffset}, {-self.yoffset})")
self.xoffset += random.normalvariate(0, 1) / 20
if self.yoffset < 50:
self.yoffset += 0.1
else:
self.yoffset += random.normalvariate(0, 1) / 20
_auto = Antigravity(append=True)
fly = _auto.fly
</code>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -9,28 +9,140 @@
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<link rel="stylesheet" href="repl.css" />
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<py-config>
packages = [
"bokeh",
"numpy"
]
paths = [
"./utils.py",
"./antigravity.py"
]
</py-config>
<body>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Custom REPL</a>
</div>
</nav>
<section class="pyscript">
<h1 class="font-semibold text-2xl ml-5">Custom REPL</h1>
<py-box widths="2/3;1/3">
<py-repl id="my-repl" auto-generate="true" std-out="output" std-err="err-div"> </py-repl>
<py-config>
packages = [
"bokeh",
"numpy"
]
[[fetch]]
files = ["./utils.py", "./antigravity.py"]
</py-config>
<div style="margin-right: 3rem;">
<py-repl id="my-repl" auto-generate="true"> </py-repl>
<div id="output" class="p-4"></div>
</py-box>
<footer id="err-div" class="bg-red-700 text-white text-center border-t-4 border-green-500 fixed inset-x-0 bottom-0 p-4 hidden">
</footer>
</div>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;div&gt;
&lt;py-repl id=&quot;my-repl&quot; auto-generate=&quot;true&quot;&gt; &lt;/py-repl&gt;
&lt;div id=&quot;output&quot; class=&quot;p-4&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
</code>
</pre>
<p>utils.py</p>
<pre class="prism-code language-python">
<code class="language-python">
from datetime import datetime as dt
def format_date(dt_, fmt="%m/%d/%Y, %H:%M:%S"):
return f"{dt_:{fmt}}"
def now(fmt="%m/%d/%Y, %H:%M:%S"):
return format_date(dt.now(), fmt)
def remove_class(element, class_name):
element.element.classList.remove(class_name)
def add_class(element, class_name):
element.element.classList.add(class_name)
</code>
</pre>
<p>antigravity.py</p>
<pre class="prism-code language-python">
<code class="language-python">
import random
import sys
from js import DOMParser, document, setInterval
from pyodide.ffi import create_proxy
from pyodide.http import open_url
class Antigravity:
url = "./antigravity.svg"
def __init__(self, target=None, interval=10, append=True, fly=False):
target = target or sys.stdout._out
self.target = (
document.getElementById(target) if isinstance(target, str) else target
)
doc = DOMParser.new().parseFromString(
open_url(self.url).read(), "image/svg+xml"
)
self.node = doc.documentElement
if append:
self.target.append(self.node)
else:
self.target.replaceChildren(self.node)
self.xoffset, self.yoffset = 0, 0
self.interval = interval
if fly:
self.fly()
def fly(self):
setInterval(create_proxy(self.move), self.interval)
def move(self):
char = self.node.getElementsByTagName("g")[1]
char.setAttribute("transform", f"translate({self.xoffset}, {-self.yoffset})")
self.xoffset += random.normalvariate(0, 1) / 20
if self.yoffset < 50:
self.yoffset += 0.1
else:
self.yoffset += random.normalvariate(0, 1) / 20
_auto = Antigravity(append=True)
fly = _auto.fly
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -10,22 +10,31 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-config>
paths = [
"./utils.py"
]
</py-config>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Simple Clock</a>
</div>
</nav>
<section class="pyscript">
<div class="font-mono">start time: <label id="outputDiv"></label></div>
<div id="outputDiv2" class="font-mono"></div>
<div id="outputDiv3" class="font-mono"></div>
<py-script output="outputDiv">
# demonstrates how use the global PyScript pyscript_loader
# to send operation log messages to it
<py-config>
[[fetch]]
files = ["./utils.py"]
</py-config>
<py-script>
import utils
utils.now()
display(utils.now())
</py-script>
<py-script>
@@ -44,9 +53,80 @@ async def foo():
else:
out3.clear()
# close the global PyScript pyscript_loader
pyscript_loader.close()
pyscript.run_until_complete(foo())
</py-script>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;div class=&quot;font-mono&quot;&gt;start time: &lt;label id=&quot;outputDiv&quot;&gt;&lt;/label&gt;&lt;/div&gt;
&lt;div id=&quot;outputDiv2&quot; class=&quot;font-mono&quot;&gt;&lt;/div&gt;
&lt;div id=&quot;outputDiv3&quot; class=&quot;font-mono&quot;&gt;&lt;/div&gt;
&lt;py-config&gt;
[[fetch]]
files = ["./utils.py"]
&lt;/py-config&gt;
&lt;py-script&gt;
from utils import now
import asyncio
async def foo():
while True:
await asyncio.sleep(1)
output = now()
Element("outputDiv2").write(output)
out3 = Element("outputDiv3")
if output[-1] in ["0", "4", "8"]:
out3.write("It's espresso time!")
else:
out3.clear()
pyscript.run_until_complete(foo())
&lt;/py-script&gt;
</code>
</pre>
<p>utils.py</p>
<pre class="prism-code language-python">
<code class="language-python">
from datetime import datetime as dt
def format_date(dt_, fmt="%m/%d/%Y, %H:%M:%S"):
return f"{dt_:{fmt}}"
def now(fmt="%m/%d/%Y, %H:%M:%S"):
return format_date(dt.now(), fmt)
def remove_class(element, class_name):
element.element.classList.remove(class_name)
def add_class(element, class_name):
element.element.classList.add(class_name)
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -9,16 +9,36 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-config>
paths = [
"./utils.py"
]
</py-config>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Pyscript Native TODO App</a>
</div>
</nav>
<section class="pyscript">
<h1>To Do List</h1>
<py-register-widget src="./pylist.py" name="py-list" klass="PyList"></py-register-widget>
<py-config>
[[fetch]]
files = ["./utils.py"]
</py-config>
<py-script>
from js import document
from pyodide.ffi.wrappers import add_event_listener
def add_task(*ags, **kws):
# create a new dictionary representing the new task
new_task_content = Element("new-task-content")
task = { "content": new_task_content.value, "done": False, "created_at": dt.now() }
# add a new task to the list and tell it to use the `content` key to show in the UI
@@ -28,24 +48,123 @@ paths = [
# clear the inputbox element used to create the new task
new_task_content.clear()
</py-script>
</head>
def on_click(evt):
add_task()
<body>
<py-title>To Do List</py-title>
<py-box widths="4/5;1/5">
<py-inputbox id="new-task-content">
def on_keypress(e):
if (e.code == "Enter"):
add_task()
</py-inputbox>
<py-button id="new-task-btn" label="Add Task!">
def on_click(evt):
add_task()
</py-button>
</py-box>
def handle_keypress(evt):
if evt.key == "Enter":
add_task()
add_event_listener(
document.getElementById("new-task-content"),
"keypress",
handle_keypress
)
</py-script>
<div class="py-box">
<input id="new-task-content" />
<button py-click="add_task()" id="new-task-btn" class="py-button">Add Task!</button>
</div>
<py-list id="myList"></py-list>
<py-repl id="my-repl" auto-generate="true"> </py-repl>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-register-widget src=&quot;./pylist.py&quot; name=&quot;py-list&quot; klass=&quot;PyList&quot;&gt;&lt;/py-register-widget&gt;
&lt;py-config&gt;
[[fetch]]
files = ["./utils.py"]
&lt;/py-config&gt;
&lt;py-script&gt;
from js import document
from pyodide.ffi.wrappers import add_event_listener
def add_task(*ags, **kws):
# create a new dictionary representing the new task
new_task_content = Element("new-task-content")
task = { "content": new_task_content.value, "done": False, "created_at": dt.now() }
# add a new task to the list and tell it to use the `content` key to show in the UI
# and to use the key `done` to sync the task status with a checkbox element in the UI
myList.add(task)
# clear the inputbox element used to create the new task
new_task_content.clear()
def on_click(evt):
add_task()
def handle_keypress(evt):
if evt.key == "Enter":
add_task()
add_event_listener(
document.getElementById("new-task-content"),
"keypress",
handle_keypress
)
&lt;/py-script&gt;
&lt;div class=&quot;py-box&quot;&gt;
&lt;input id=&quot;new-task-content&quot; /&gt;
&lt;button py-click=&quot;add_task()&quot; id=&quot;new-task-btn&quot; class=&quot;py-button&quot;&gt;Add Task!&lt;/button&gt;
&lt;/div&gt;
&lt;py-list id=&quot;myList&quot;&gt;&lt;/py-list&gt;
&lt;py-repl id=&quot;my-repl&quot; auto-generate=&quot;true&quot;&gt; &lt;/py-repl&gt;
</code>
</pre>
<p>pylist.py</p>
<pre class="prism-code language-python">
<code class="language-python">
import pyscript
from datetime import datetime as dt
class PyItem(pyscript.PyItemTemplate):
def on_click(self, evt=None):
self.data["done"] = not self.data["done"]
self.strike(self.data["done"])
self.select("input").element.checked = self.data["done"]
class PyList(pyscript.PyListTemplate):
item_class = PyItem
def add(self, item):
if isinstance(item, str):
item = {"content": item, "done": False, "created_at": dt.now()}
super().add(item, labels=["content"], state_key="done")
</code>
</pre>
</div>
</section>
</body>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</html>

View File

@@ -10,15 +10,26 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<py-config>
paths = [
"./utils.py"
]
</py-config>
<link rel="stylesheet" href="./assets/css/examples.css" />
<link rel="stylesheet" href="./assets/prism/prism.css" />
<script defer src="./assets/prism/prism.js"></script>
</head>
<body class="container">
<!-- <py-repl id="my-repl" auto-generate="true"> </py-repl> -->
<body>
<nav class="navbar" style="background-color: #000000;">
<div class="app-header">
<a href="/">
<img src="./logo.png" class="logo">
</a>
<a class="title" href="" style="color: #f0ab3c;">Todo App</a>
</div>
</nav>
<section class="pyscript">
<py-config>
[[fetch]]
files = ["./utils.py"]
</py-config>
<py-script src="./todo.py"> </py-script>
<main>
@@ -49,5 +60,149 @@ paths = [
</section>
</main>
</section>
<section class="code">
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
[[fetch]]
files = ["./utils.py"]
&lt;/py-config&gt;
&lt;py-script src="./todo.py"&gt;
&lt;/py-script&gt;
&lt;main&gt;
&lt;section&gt;
&lt;div class=&quot;text-center w-full mb-8&quot;&gt;
&lt;h1 class=&quot;text-3xl font-bold text-gray-800 uppercase tracking-tight&quot;&gt;To Do List&lt;/h1&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;input id=&quot;new-task-content&quot; class=&quot;py-input&quot; type=&quot;text&quot;&gt;
&lt;button id=&quot;new-task-btn&quot; class=&quot;py-button&quot; type=&quot;submit&quot; py-click=&quot;add_task()&quot;&gt;
Add task
&lt;/button&gt;
&lt;/div&gt;
&lt;py-list id=&quot;myList&quot;&gt;&lt;/py-list&gt;
&lt;div id=&quot;list-tasks-container&quot; class=&quot;flex flex-col-reverse mt-4&quot;&gt;
&lt;/div&gt;
&lt;template id=&quot;task-template&quot;&gt;
&lt;section class=&quot;task py-li-element&quot;&gt;
&lt;label for=&quot;flex items-center p-2 &quot;&gt;
&lt;input class=&quot;mr-2&quot; type=&quot;checkbox&quot;&gt;
&lt;p class=&quot;m-0 inline&quot;&gt;&lt;/p&gt;
&lt;/label&gt;
&lt;/section&gt;
&lt;/template&gt;
&lt;/section&gt;
&lt;/main&gt;
</code>
</pre>
<p>utils.py</p>
<pre class="prism-code language-python">
<code class="language-python">
from datetime import datetime as dt
def format_date(dt_, fmt="%m/%d/%Y, %H:%M:%S"):
return f"{dt_:{fmt}}"
def now(fmt="%m/%d/%Y, %H:%M:%S"):
return format_date(dt.now(), fmt)
def remove_class(element, class_name):
element.element.classList.remove(class_name)
def add_class(element, class_name):
element.element.classList.add(class_name)
</code>
</pre>
<p>todo.py</p>
<pre class="prism-code language-python">
<code class="language-python">
from datetime import datetime as dt
from utils import add_class, remove_class
tasks = []
# define the task template that will be use to render new templates to the page
task_template = Element("task-template").select(".task", from_content=True)
task_list = Element("list-tasks-container")
new_task_content = Element("new-task-content")
def add_task(*ags, **kws):
# ignore empty task
if not new_task_content.element.value:
return None
# create task
task_id = f"task-{len(tasks)}"
task = {
"id": task_id,
"content": new_task_content.element.value,
"done": False,
"created_at": dt.now(),
}
tasks.append(task)
# add the task element to the page as new node in the list by cloning from a
# template
task_html = task_template.clone(task_id)
task_html_content = task_html.select("p")
task_html_content.element.innerText = task["content"]
task_html_check = task_html.select("input")
task_list.element.appendChild(task_html.element)
def check_task(evt=None):
task["done"] = not task["done"]
if task["done"]:
add_class(task_html_content, "line-through")
else:
remove_class(task_html_content, "line-through")
new_task_content.clear()
task_html_check.element.onclick = check_task
def add_task_event(e):
if e.key == "Enter":
add_task()
new_task_content.element.onkeypress = add_task_event
</code>
</pre>
</div>
</section>
<script>
const viewCodeButton = document.getElementById("view-code-button");
const codeSection = document.getElementById("code-section");
const handleClick = () => {
if (codeSection.classList.contains("code-section-hidden")) {
codeSection.classList.remove("code-section-hidden");
codeSection.classList.add("code-section-visible");
} else {
codeSection.classList.remove("code-section-visible");
codeSection.classList.add("code-section-hidden");
}
}
viewCodeButton.addEventListener("click", handleClick)
viewCodeButton.addEventListener("keydown", (e) => {
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
handleClick();
}
})
</script>
</body>
</html>

View File

@@ -16,10 +16,6 @@
<title>Loading...</title>
<link rel="icon" type="image/png" href="../favicon.png" />
</head>
<body>
<div id="toga-placeholder">Loading...</div>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
@@ -33,20 +29,27 @@
integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
crossorigin="anonymous">
</script>
</head>
<body>
<py-config>
packages = [
"./static/wheels/travertino-0.1.3-py3-none-any.whl",
"./static/wheels/toga_core-0.3.0.dev39-py3-none-any.whl",
"./static/wheels/toga_web-0.3.0.dev39-py3-none-any.whl",
"./static/wheels/freedom-0.0.1-py3-none-any.whl"
]
terminal = false
</py-config>
<div id="app-placeholder">Loading...</div>
<py-script>
from pyscript import Element
from freedom.__main__ import main
# Remove text from placeholder
Element('app-placeholder').write('', append=False)
app = main().main_loop()
</py-script>
</body>
<py-config>
packages = [
"./static/wheels/travertino-0.1.3-py3-none-any.whl",
"./static/wheels/toga_core-0.3.0.dev33-py3-none-any.whl",
"./static/wheels/toga_web-0.3.0.dev33-py3-none-any.whl",
"./static/wheels/freedom-0.0.1-py3-none-any.whl"
]
</py-config>
<py-script>
from toga_web.dom import handle as dom_handle
from freedom.__main__ import main
app = main().main_loop(spa=True)
</py-script>
</html>

View File

@@ -20,7 +20,7 @@
<div class="col"></div>
</div>
</div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/89/three.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/0.147.0/three.min.js'></script>
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
@@ -31,10 +31,9 @@ from js import Math
from js import THREE
from js import performance
from js import Object
from js import document
import asyncio
mouse = THREE.Vector2.new();
renderer = THREE.WebGLRenderer.new({"antialias":True})
@@ -145,49 +144,50 @@ scene.add(light);
scene.add(lightBack);
rectSize = 2
intensity = 100
intensity = 14
rectLight = THREE.RectAreaLight.new( 0x0FFFFF, intensity, rectSize, rectSize )
rectLight.position.set( 0, 0, 1 )
rectLight.lookAt( 0, 0, 0 )
scene.add( rectLight )
rectLightHelper = THREE.RectAreaLightHelper.new( rectLight );
raycaster = THREE.Raycaster.new();
uSpeed = 0.1
time = 0.0003;
camera.lookAt(scene.position)
while True:
time = performance.now() * 0.0003;
i = 0
while i < particularGroup.children.length:
newObject = particularGroup.children[i];
newObject.rotation.x += newObject.speedValue/10;
newObject.rotation.y += newObject.speedValue/10;
newObject.rotation.z += newObject.speedValue/10;
i += 1
async def main():
while True:
time = performance.now() * 0.0003;
i = 0
while i < particularGroup.children.length:
newObject = particularGroup.children[i];
newObject.rotation.x += newObject.speedValue/10;
newObject.rotation.y += newObject.speedValue/10;
newObject.rotation.z += newObject.speedValue/10;
i += 1
i = 0
while i < modularGroup.children.length:
newCubes = modularGroup.children[i];
newCubes.rotation.x += 0.008;
newCubes.rotation.y += 0.005;
newCubes.rotation.z += 0.003;
i = 0
while i < modularGroup.children.length:
newCubes = modularGroup.children[i];
newCubes.rotation.x += 0.008;
newCubes.rotation.y += 0.005;
newCubes.rotation.z += 0.003;
newCubes.position.x = Math.sin(time * newCubes.positionZ) * newCubes.positionY;
newCubes.position.y = Math.cos(time * newCubes.positionX) * newCubes.positionZ;
newCubes.position.z = Math.sin(time * newCubes.positionY) * newCubes.positionX;
i += 1
newCubes.position.x = Math.sin(time * newCubes.positionZ) * newCubes.positionY;
newCubes.position.y = Math.cos(time * newCubes.positionX) * newCubes.positionZ;
newCubes.position.z = Math.sin(time * newCubes.positionY) * newCubes.positionX;
i += 1
particularGroup.rotation.y += 0.005;
particularGroup.rotation.y += 0.005;
modularGroup.rotation.y -= ((mouse.x * 4) + modularGroup.rotation.y) * uSpeed;
modularGroup.rotation.x -= ((-mouse.y * 4) + modularGroup.rotation.x) * uSpeed;
modularGroup.rotation.y -= ((mouse.x * 4) + modularGroup.rotation.y) * uSpeed;
modularGroup.rotation.x -= ((-mouse.y * 4) + modularGroup.rotation.x) * uSpeed;
renderer.render( scene, camera )
await asyncio.sleep(0.02)
renderer.render( scene, camera )
await asyncio.sleep(0.02)
asyncio.ensure_future(main())
</py-script>
</body>

View File

@@ -86,12 +86,15 @@ test-integration:
make examples
$(PYTEST_EXE) -vv $(ARGS) tests/integration/ --log-cli-level=warning
test-integration-parallel:
make examples
$(PYTEST_EXE) --numprocesses auto -vv $(ARGS) tests/integration/ --log-cli-level=warning
test-py:
@echo "Tests from $(src_dir)"
$(PYTEST_EXE) -vv $(ARGS) tests/py-unit/ --log-cli-level=warning
test-ts:
@echo "Tests are coming :( this is a placeholder and it's meant to fail!"
npm run test
fmt: fmt-py fmt-ts

View File

@@ -0,0 +1,14 @@
/**
* this file mocks the `src/python/pyscript.py` file
* since importing of `.py` files isn't usually supported
* inside JS/TS files.
*
* It sets the value of whatever is imported from
* `src/python/pyscript.py` the contents of that file
*
* This is needed since the imported object is further
* passed to a function which only accepts a string.
*/
const fs = require('fs');
module.exports = fs.readFileSync('./src/python/pyscript.py', 'utf8');

View File

@@ -0,0 +1 @@
module.exports = "";

View File

@@ -1,12 +1,16 @@
/**
* this file mocks the `src/python/pyscript.py` file
* since importing of `.py` files isn't usually supported
* inside JS/TS files.
*
* It sets the value of whatever is imported from
* `src/python/pyscript.py` to be an empty string i.e. ""
* this file mocks python files that are not explicitly
* matched by a regex in jest.config.js, since importing of
* `.py` files isn't usually supported inside JS/TS files.
*
* This is needed since the imported object is further
* passed to a function which only accepts a string.
*
* The mocked contents of the `.py` file will be "", e.g.
* nothing.
*/
module.exports = "";
console.warn(`.py files that are not explicitly mocked in \
jest.config.js and /__mocks__/ are mocked as empty strings`);
module.exports = "";

View File

@@ -13,7 +13,8 @@ dependencies:
- pre-commit
- pillow
- numpy
- markdown
- pip:
- playwright
- pytest-playwright
- pytest-xdist

View File

@@ -3,17 +3,22 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'jest-environment-jsdom',
extensionsToTreatAsEsm: ['.ts'],
globals: {
'ts-jest': {
tsconfig: 'tsconfig.json',
useESM: true
}
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
tsconfig: 'tsconfig.json',
useESM: true,
},
],
},
verbose: true,
testEnvironmentOptions: {
url: "http://localhost"
url: 'http://localhost',
},
moduleNameMapper: {
"^[./a-zA-Z0-9$_-]+\\.py$": "<rootDir>/__mocks__/fileMock.js",
}
};
'^.*?pyscript\.py$': '<rootDir>/__mocks__/_pyscript.js',
'^[./a-zA-Z0-9$_-]+\\.py$': '<rootDir>/__mocks__/fileMock.js',
'\\.(css)$': '<rootDir>/__mocks__/cssMock.js',
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -5,8 +5,7 @@
"build-min": "NODE_ENV=production rollup -c",
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public --no-clear --port 8080",
"validate": "svelte-check",
"tsc": "tsc --noEmit",
"format:check": "prettier --check './src/**/*.{js,html,ts}'",
"format": "prettier --write './src/**/*.{js,html,ts}'",
"lint": "eslint './src/**/*.{js,html,ts}'",
@@ -16,45 +15,40 @@
"test:watch": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"@rollup/plugin-typescript": "^8.4.0",
"@tsconfig/svelte": "^1.0.0",
"@types/jest": "^28.1.6",
"@types/js-yaml": "^4.0.5",
"@types/node": "^18.7.11",
"@typescript-eslint/eslint-plugin": "^5.36.0",
"@typescript-eslint/parser": "^5.36.0",
"autoprefixer": "^10.4.7",
"cross-env": "^7.0.3",
"eslint": "^8.14.0",
"jest": "^28.1.3",
"jest-environment-jsdom": "^28.1.3",
"prettier": "^2.6.2",
"pyodide": "0.21.2",
"rollup": "^2.71.1",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-serve": "^1.1.0",
"rollup-plugin-string": "^3.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.48.0",
"svelte-check": "^1.0.0",
"ts-jest": "^28.0.7",
"tslib": "^2.4.0",
"typescript": "^4.8.2"
"@jest/globals": "29.1.2",
"@rollup/plugin-commonjs": "22.0.2",
"@rollup/plugin-legacy": "2.2.0",
"@rollup/plugin-node-resolve": "14.1.0",
"@rollup/plugin-typescript": "8.5.0",
"@types/codemirror": "^5.60.5",
"@types/jest": "29.1.2",
"@types/node": "18.8.3",
"@typescript-eslint/eslint-plugin": "5.39.0",
"@typescript-eslint/parser": "5.39.0",
"cross-env": "7.0.3",
"eslint": "8.25.0",
"jest": "29.1.2",
"jest-environment-jsdom": "29.1.2",
"prettier": "2.7.1",
"pyodide": "0.21.3",
"rollup": "2.79.1",
"rollup-plugin-copy": "3.4.0",
"rollup-plugin-css-only": "3.1.0",
"rollup-plugin-livereload": "2.0.5",
"rollup-plugin-serve": "2.0.1",
"rollup-plugin-string": "3.0.0",
"rollup-plugin-terser": "7.0.2",
"ts-jest": "29.0.3",
"tslib": "2.4.0",
"typescript": "4.8.4"
},
"dependencies": {
"@codemirror/basic-setup": "^0.19.1",
"@codemirror/lang-python": "^0.19.5",
"@codemirror/state": "^0.19.9",
"@codemirror/theme-one-dark": "^0.19.1",
"@fortawesome/free-solid-svg-icons": "^6.0.0",
"codemirror": "^5.65.3",
"js-yaml": "^4.1.0",
"sirv-cli": "^1.0.0",
"svelte-fa": "^2.4.0",
"svelte-promisable-stores": "^0.1.3"
"@codemirror/commands": "6.1.1",
"@codemirror/lang-python": "6.0.2",
"@codemirror/language": "6.2.1",
"@codemirror/state": "6.1.2",
"@codemirror/theme-one-dark": "6.1.0",
"@codemirror/view": "6.3.0",
"codemirror": "6.0.1"
}
}

View File

@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/mvp.css@1.12/mvp.css">
<link rel="stylesheet" href="pyscript.css">
<script defer src="pyscript.min.js"></script>
<title>PyScript</title>
</head>
<body>
<main>
<h1>&lt;py-script&gt;</h1>
<ul>
<li><a href="pyscript.js">pyscript.js</a></li>
<li><a href="pyscript.min.js">pyscript.min.js</a></li>
<li><a href="pyscript.css">pyscript.css</a></li>
<li><a href="pyscript.min.js.map">pyscript.min.js.map</a></li>
<li><a href="pyscript.js.map">pyscript.js.map</a></li>
</ul>
<div id="out"></div>
<py-script std-out="out">
import sys
print(sys.version)
</py-script>
<h2>Example</h2>
<pre style="padding:1em;border:1px solid #000000;">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot; /&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1&quot; /&gt;
&lt;title&gt;PyScript Hello World&lt;/title&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://pyscript.net/latest/pyscript.css&quot; /&gt;
&lt;script defer src=&quot;https://pyscript.net/latest/pyscript.js&quot;&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
Hello world! &lt;br&gt;
This is the current date and time, as computed by Python:
&lt;py-script&gt;
from datetime import datetime
now = datetime.now()
now.strftime(&quot;%m/%d/%Y, %H:%M:%S&quot;)
&lt;/py-script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</main>
</body>
</html>

View File

@@ -1,7 +1,7 @@
import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import livereload from "rollup-plugin-livereload";
import { terser } from "rollup-plugin-terser";
import legacy from '@rollup/plugin-legacy';
import typescript from "@rollup/plugin-typescript";
import css from "rollup-plugin-css-only";
import serve from "rollup-plugin-serve";
@@ -10,6 +10,17 @@ import copy from 'rollup-plugin-copy'
const production = !process.env.ROLLUP_WATCH || (process.env.NODE_ENV === "production");
const copy_targets = {
targets: [
{ src: 'public/index.html', dest: 'build' },
{ src: 'src/plugins/*', dest: 'build/plugins' }
]
}
if( !production ){
copy_targets.targets.push({ src: 'build/*', dest: 'examples/build' })
}
export default {
input: "src/main.ts",
output:[
@@ -17,7 +28,7 @@ export default {
sourcemap: true,
format: "iife",
inlineDynamicImports: true,
name: "app",
name: "pyscript",
file: "build/pyscript.js",
},
{
@@ -34,9 +45,9 @@ export default {
string({
include: "./src/**/*.py",
}),
legacy({ 'src/toml.js': 'toml' }),
resolve({
browser: true,
dedupe: ["svelte"],
}),
commonjs(),
typescript({
@@ -44,13 +55,7 @@ export default {
inlineSources: !production,
}),
// This will make sure that examples will always get the latest build folder
!production && copy({
targets: [
{ src: 'build/*', dest: 'examples/build' }
]
}),
!production && serve(),
!production && livereload("public"),
copy(copy_targets),
// production && terser(),
!production && serve({
port: 8080,

View File

@@ -1,370 +0,0 @@
import { runtimeLoaded } from '../stores';
import { guidGenerator, addClasses, removeClasses } from '../utils';
import type { Runtime } from '../runtime';
import { getLogger } from '../logger';
const logger = getLogger('pyscript/base');
// Global `Runtime` that implements the generic runtimes API
let runtime: Runtime;
let Element;
runtimeLoaded.subscribe(value => {
runtime = value;
});
export class BaseEvalElement extends HTMLElement {
shadow: ShadowRoot;
wrapper: HTMLElement;
code: string;
source: string;
btnConfig: HTMLElement;
btnRun: HTMLElement;
outputElement: HTMLElement;
errorElement: HTMLElement;
theme: string;
appendOutput: boolean;
constructor() {
super();
// attach shadow so we can preserve the element original innerHtml content
this.shadow = this.attachShadow({ mode: 'open' });
this.wrapper = document.createElement('slot');
this.shadow.appendChild(this.wrapper);
this.setOutputMode("append");
}
addToOutput(s: string) {
this.outputElement.innerHTML += '<div>' + s + '</div>';
this.outputElement.hidden = false;
}
setOutputMode(defaultMode = "append") {
const mode = this.hasAttribute('output-mode') ? this.getAttribute('output-mode') : defaultMode;
switch (mode) {
case "append":
this.appendOutput = true;
break;
case "replace":
this.appendOutput = false;
break;
default:
logger.warn(`${this.id}: custom output-modes are currently not implemented`);
}
}
// subclasses should overwrite this method to define custom logic
// before code gets evaluated
preEvaluate(): void {
return null;
}
// subclasses should overwrite this method to define custom logic
// after code has been evaluated
postEvaluate(): void {
return null;
}
checkId() {
if (!this.id) this.id = 'py-' + guidGenerator();
}
getSourceFromElement(): string {
return '';
}
async getSourceFromFile(s: string): Promise<string> {
const response = await fetch(s);
this.code = await response.text();
return this.code;
}
protected async _register_esm(runtime: Runtime): Promise<void> {
const imports: { [key: string]: unknown } = {};
const nodes = document.querySelectorAll("script[type='importmap']");
const importmaps: Array<any> = [];
nodes.forEach( node =>
{
let importmap;
try {
importmap = JSON.parse(node.textContent);
if (importmap?.imports == null) return;
importmaps.push(importmap);
} catch {
return;
}
}
)
for (const importmap of importmaps){
for (const [name, url] of Object.entries(importmap.imports)) {
if (typeof name != 'string' || typeof url != 'string') continue;
try {
// XXX: pyodide doesn't like Module(), failing with
// "can't read 'name' of undefined" at import time
imports[name] = { ...(await import(url)) };
} catch {
logger.error(`failed to fetch '${url}' for '${name}'`);
}
}
}
runtime.registerJsModule('esm', imports);
}
async evaluate(): Promise<void> {
this.preEvaluate();
let source: string;
let output: string;
try {
source = this.source ? await this.getSourceFromFile(this.source)
: this.getSourceFromElement();
this._register_esm(runtime);
<string>await runtime.run(
`output_manager.change(out="${this.outputElement.id}", err="${this.errorElement.id}", append=${this.appendOutput ? 'True' : 'False'})`,
);
output = <string>await runtime.run(source);
if (output !== undefined) {
if (Element === undefined) {
Element = <Element>runtime.globals.get('Element');
}
const out = Element(this.outputElement.id);
out.write.callKwargs(output, { append: this.appendOutput });
this.outputElement.hidden = false;
this.outputElement.style.display = 'block';
}
await runtime.run(`output_manager.revert()`);
// check if this REPL contains errors, delete them and remove error classes
const errorElements = document.querySelectorAll(`div[id^='${this.errorElement.id}'][error]`);
if (errorElements.length > 0) {
errorElements.forEach( errorElement =>
{
errorElement.classList.add('hidden');
if (this.hasAttribute('std-err')) {
this.errorElement.hidden = true;
this.errorElement.style.removeProperty('display');
}
}
)
}
removeClasses(this.errorElement, ['bg-red-200', 'p-2']);
this.postEvaluate();
} catch (err) {
logger.error(err);
try{
if (Element === undefined) {
Element = <Element>runtime.globals.get('Element');
}
const out = Element(this.errorElement.id);
addClasses(this.errorElement, ['bg-red-200', 'p-2']);
out.write.callKwargs(err.toString(), { append: this.appendOutput });
if (this.errorElement.children.length === 0){
this.errorElement.setAttribute('error', '');
}else{
this.errorElement.children[this.errorElement.children.length - 1].setAttribute('error', '');
}
this.errorElement.hidden = false;
this.errorElement.style.display = 'block';
this.errorElement.style.visibility = 'visible';
} catch (internalErr){
logger.error("Unnable to write error to error element in page.")
}
}
} // end evaluate
async eval(source: string): Promise<void> {
try {
const output = await runtime.run(source);
if (output !== undefined) {
logger.info(output);
}
} catch (err) {
logger.error(err);
}
} // end eval
runAfterRuntimeInitialized(callback: () => Promise<void>) {
runtimeLoaded.subscribe(value => {
if ('run' in value) {
setTimeout(() => {
void callback();
}, 100);
}
});
}
}
function createWidget(name: string, code: string, klass: string) {
class CustomWidget extends HTMLElement {
shadow: ShadowRoot;
wrapper: HTMLElement;
name: string = name;
klass: string = klass;
code: string = code;
proxy: any;
proxyClass: any;
constructor() {
super();
// attach shadow so we can preserve the element original innerHtml content
this.shadow = this.attachShadow({ mode: 'open' });
this.wrapper = document.createElement('slot');
this.shadow.appendChild(this.wrapper);
}
connectedCallback() {
// TODO: we are calling with a 2secs delay to allow pyodide to load
// ideally we can just wait for it to load and then run. To do
// so we need to replace using the promise and actually using
// the interpreter after it loads completely
// setTimeout(() => {
// void (async () => {
// await this.eval(this.code);
// this.proxy = this.proxyClass(this);
// console.log('proxy', this.proxy);
// this.proxy.connect();
// this.registerWidget();
// })();
// }, 2000);
runtimeLoaded.subscribe(value => {
if ('run' in value) {
runtime = value;
setTimeout(() => {
void (async () => {
await this.eval(this.code);
this.proxy = this.proxyClass(this);
this.proxy.connect();
this.registerWidget();
})();
}, 1000);
}
});
}
registerWidget() {
logger.info('new widget registered:', this.name);
runtime.globals.set(this.id, this.proxy);
}
async eval(source: string): Promise<void> {
try {
const output = await runtime.run(source);
this.proxyClass = runtime.globals.get(this.klass);
if (output !== undefined) {
logger.info('CustomWidget.eval: ', output);
}
} catch (err) {
logger.error('CustomWidget.eval: ', err);
}
}
}
const xPyWidget = customElements.define(name, CustomWidget);
}
export class PyWidget extends HTMLElement {
shadow: ShadowRoot;
name: string;
klass: string;
outputElement: HTMLElement;
errorElement: HTMLElement;
wrapper: HTMLElement;
theme: string;
source: string;
code: string;
constructor() {
super();
// attach shadow so we can preserve the element original innerHtml content
this.shadow = this.attachShadow({ mode: 'open' });
this.wrapper = document.createElement('slot');
this.shadow.appendChild(this.wrapper);
this.addAttributes('src','name','klass');
}
addAttributes(...attrs:string[]){
for (const each of attrs){
const property = each === "src" ? "source" : each;
if (this.hasAttribute(each)) {
this[property]=this.getAttribute(each);
}
}
}
async connectedCallback() {
if (this.id === undefined) {
throw new ReferenceError(
`No id specified for component. Components must have an explicit id. Please use id="" to specify your component id.`,
);
}
const mainDiv = document.createElement('div');
mainDiv.id = this.id + '-main';
this.appendChild(mainDiv);
logger.debug('PyWidget: reading source', this.source);
this.code = await this.getSourceFromFile(this.source);
createWidget(this.name, this.code, this.klass);
}
initOutErr(): void {
if (this.hasAttribute('output')) {
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
// in this case, the default output-mode is append, if hasn't been specified
if (!this.hasAttribute('output-mode')) {
this.setAttribute('output-mode', 'append');
}
} else {
if (this.hasAttribute('std-out')) {
this.outputElement = document.getElementById(this.getAttribute('std-out'));
} else {
// In this case neither output or std-out have been provided so we need
// to create a new output div to output to
this.outputElement = document.createElement('div');
this.outputElement.classList.add('output');
this.outputElement.hidden = true;
this.outputElement.id = this.id + '-' + this.getAttribute('exec-id');
}
if (this.hasAttribute('std-err')) {
this.errorElement = document.getElementById(this.getAttribute('std-err'));
} else {
this.errorElement = this.outputElement;
}
}
}
async getSourceFromFile(s: string): Promise<string> {
const response = await fetch(s);
return await response.text();
}
async eval(source: string): Promise<void> {
try {
const output = await runtime.run(source);
if (output !== undefined) {
logger.info('PyWidget.eval: ', output);
}
} catch (err) {
logger.error('PyWidget.eval: ', err);
}
}
}

View File

@@ -1,27 +1,17 @@
import { PyRepl } from './pyrepl';
import type { Runtime } from '../runtime';
import { make_PyRepl } from './pyrepl';
import { PyBox } from './pybox';
import { PyButton } from './pybutton';
import { make_PyButton } from './pybutton';
import { PyTitle } from './pytitle';
import { PyInputBox } from './pyinputbox';
import { PyWidget } from './base';
import { make_PyInputBox } from './pyinputbox';
import { make_PyWidget } from './pywidget';
/*
These were taken from main.js because some of our components call
runAfterRuntimeInitialized immediately when we are creating the custom
element, this was causing tests to fail since runAfterRuntimeInitialized
expects the runtime to have been loaded before being called.
function createCustomElements(runtime: Runtime) {
const PyInputBox = make_PyInputBox(runtime);
const PyButton = make_PyButton(runtime);
const PyWidget = make_PyWidget(runtime);
const PyRepl = make_PyRepl(runtime);
This function is now called from within the `runtime.initialize`. Once
the runtime finished initializing, then we will create the custom elements
so they are rendered in the page and we will always have a runtime available.
Ideally, this would live under utils.js, but importing all the components in
the utils.js file was causing jest to fail with weird errors such as:
"ReferenceError: Cannot access 'BaseEvalElement' before initialization" coming
from the PyScript class.
*/
function createCustomElements() {
/* eslint-disable @typescript-eslint/no-unused-vars */
const xPyRepl = customElements.define('py-repl', PyRepl);
const xPyBox = customElements.define('py-box', PyBox);

View File

@@ -1,4 +1,4 @@
import { addClasses } from '../utils';
import { getAttribute, addClasses, createDeprecationWarning } from '../utils';
import { getLogger } from '../logger';
const logger = getLogger('py-box');
@@ -7,7 +7,7 @@ export class PyBox extends HTMLElement {
shadow: ShadowRoot;
wrapper: HTMLElement;
theme: string;
widths: Array<string>;
widths: string[];
constructor() {
super();
@@ -20,6 +20,10 @@ export class PyBox extends HTMLElement {
}
connectedCallback() {
const deprecationMessage =
'The element <py-box> is deprecated, you should create a ' +
'div with "py-box" class name instead. For example: <div class="py-box">';
createDeprecationWarning(deprecationMessage, 'py-box');
const mainDiv = document.createElement('div');
addClasses(mainDiv, ['py-box']);
@@ -47,10 +51,14 @@ export class PyBox extends HTMLElement {
// now we need to set widths
this.widths = [];
if (this.hasAttribute('widths')) {
for (const w of this.getAttribute('widths').split(';')) {
if (w.includes('/')) this.widths.push(w.split('/')[0])
else this.widths.push(w)
const widthsAttr = getAttribute(this, 'widths');
if (widthsAttr) {
for (const w of widthsAttr.split(';')) {
if (w.includes('/')) {
this.widths.push(w.split('/')[0]);
} else {
this.widths.push(w);
}
}
} else {
this.widths = Array<string>(mainDiv.children.length).fill('1 1 0');
@@ -59,7 +67,7 @@ export class PyBox extends HTMLElement {
this.widths.forEach((width, index) => {
const node: ChildNode = mainDiv.childNodes[index];
(<HTMLElement>node).style.flex = width;
addClasses((<HTMLElement>node), ['py-box-child']);
addClasses(<HTMLElement>node, ['py-box-child']);
});
this.appendChild(mainDiv);

View File

@@ -1,77 +1,85 @@
import { BaseEvalElement } from './base';
import { addClasses, htmlDecode } from '../utils';
import { getLogger } from '../logger'
import { getAttribute, addClasses, htmlDecode, ensureUniqueId, createDeprecationWarning } from '../utils';
import { getLogger } from '../logger';
import type { Runtime } from '../runtime';
const logger = getLogger('py-button');
export function make_PyButton(runtime: Runtime) {
class PyButton extends HTMLElement {
widths: string[] = [];
label: string | undefined = undefined;
class: string[];
defaultClass: string[];
mount_name: string | undefined = undefined;
code: string;
export class PyButton extends BaseEvalElement {
widths: Array<string>;
label: string;
class: Array<string>;
defaultClass: Array<string>;
mount_name: string;
constructor() {
super();
constructor() {
super();
this.defaultClass = ['py-button'];
this.defaultClass = ['py-button'];
if (this.hasAttribute('label')) {
this.label = this.getAttribute('label');
}
// Styling does the same thing as class in normal HTML. Using the name "class" makes the style to malfunction
if (this.hasAttribute('styling')) {
const klass = this.getAttribute('styling').trim();
if (klass === '') {
this.class = this.defaultClass;
} else {
// trim each element to remove unnecessary spaces which makes the button style to malfunction
this.class = klass
.split(' ')
.map(x => x.trim())
.filter(x => x !== '');
const label = getAttribute(this, 'label');
if (label) {
this.label = label;
}
} else {
this.class = this.defaultClass;
// Styling does the same thing as class in normal HTML. Using the name "class" makes the style to malfunction
const styling = getAttribute(this, 'styling');
if (styling) {
const klass = styling.trim();
if (klass === '') {
this.class = this.defaultClass;
} else {
// trim each element to remove unnecessary spaces which makes the button style to malfunction
this.class = klass
.split(' ')
.map(x => x.trim())
.filter(x => x !== '');
}
} else {
this.class = this.defaultClass;
}
}
connectedCallback() {
const deprecationMessage =
'The element <py-button> is deprecated, create a function with your ' +
'inline code and use <button py-click="function()" class="py-button"> instead.';
createDeprecationWarning(deprecationMessage, 'py-button');
ensureUniqueId(this);
this.code = htmlDecode(this.innerHTML) || '';
this.mount_name = this.id.split('-').join('_');
this.innerHTML = '';
const mainDiv = document.createElement('button');
mainDiv.innerHTML = this.label;
addClasses(mainDiv, this.class);
mainDiv.id = this.id;
this.id = `${this.id}-container`;
this.appendChild(mainDiv);
this.code = this.code.split('self').join(this.mount_name);
let registrationCode = `from pyodide.ffi import create_proxy`;
registrationCode += `\n${this.mount_name} = Element("${mainDiv.id}")`;
if (this.code.includes('def on_focus')) {
this.code = this.code.replace('def on_focus', `def on_focus_${this.mount_name}`);
registrationCode += `\n${this.mount_name}.element.addEventListener('focus', create_proxy(on_focus_${this.mount_name}))`;
}
if (this.code.includes('def on_click')) {
this.code = this.code.replace('def on_click', `def on_click_${this.mount_name}`);
registrationCode += `\n${this.mount_name}.element.addEventListener('click', create_proxy(on_click_${this.mount_name}))`;
}
// now that we appended and the element is attached, lets connect with the event handlers
// defined for this widget
runtime.runButDontRaise(this.code);
runtime.runButDontRaise(registrationCode);
logger.debug('py-button connected');
}
}
connectedCallback() {
this.checkId();
this.code = htmlDecode(this.innerHTML);
this.mount_name = this.id.split('-').join('_');
this.innerHTML = '';
const mainDiv = document.createElement('button');
mainDiv.innerHTML = this.label;
addClasses(mainDiv, this.class);
mainDiv.id = this.id;
this.id = `${this.id}-container`;
this.appendChild(mainDiv);
this.code = this.code.split('self').join(this.mount_name);
let registrationCode = `from pyodide.ffi import create_proxy`;
registrationCode += `\n${this.mount_name} = Element("${mainDiv.id}")`;
if (this.code.includes('def on_focus')) {
this.code = this.code.replace('def on_focus', `def on_focus_${this.mount_name}`);
registrationCode += `\n${this.mount_name}.element.addEventListener('focus', create_proxy(on_focus_${this.mount_name}))`;
}
if (this.code.includes('def on_click')) {
this.code = this.code.replace('def on_click', `def on_click_${this.mount_name}`);
registrationCode += `\n${this.mount_name}.element.addEventListener('click', create_proxy(on_click_${this.mount_name}))`;
}
// now that we appended and the element is attached, lets connect with the event handlers
// defined for this widget
this.runAfterRuntimeInitialized(async () => {
await this.eval(this.code);
await this.eval(registrationCode);
logger.debug('registered handlers');
});
logger.debug('py-button connected');
}
return PyButton;
}

View File

@@ -1,130 +0,0 @@
import { BaseEvalElement } from './base';
import { appConfig, addInitializer, runtimeLoaded } from '../stores';
import type { AppConfig, Runtime } from '../runtime';
import { version } from '../runtime';
import { PyodideRuntime } from '../pyodide';
import { getLogger } from '../logger';
import { readTextFromPath, handleFetchError, mergeConfig, validateConfig, defaultConfig } from '../utils'
// Subscriber used to connect to the first available runtime (can be pyodide or others)
let runtimeSpec: Runtime;
runtimeLoaded.subscribe(value => {
runtimeSpec = value;
});
let appConfig_: AppConfig;
appConfig.subscribe(value => {
appConfig_ = value;
});
const logger = getLogger('py-config');
/**
* Configures general metadata about the PyScript application such
* as a list of runtimes, name, version, closing the loader
* automatically, etc.
*
* Also initializes the different runtimes passed. If no runtime is passed,
* the default runtime based on Pyodide is used.
*/
export class PyConfig extends BaseEvalElement {
widths: Array<string>;
label: string;
mount_name: string;
details: HTMLElement;
operation: HTMLElement;
values: AppConfig;
constructor() {
super();
}
extractFromSrc(configType: string) {
if (this.hasAttribute('src'))
{
logger.info('config set from src attribute');
return validateConfig(readTextFromPath(this.getAttribute('src')), configType);
}
return {};
}
extractFromInline(configType: string) {
if (this.innerHTML!=='')
{
this.code = this.innerHTML;
this.innerHTML = '';
logger.info('config set from inline');
return validateConfig(this.code, configType);
}
return {};
}
injectMetadata() {
this.values.pyscript = {
"version": version,
"time": new Date().toISOString()
};
}
connectedCallback() {
const configType: string = this.hasAttribute("type") ? this.getAttribute("type") : "toml";
let srcConfig = this.extractFromSrc(configType);
const inlineConfig = this.extractFromInline(configType);
// first make config from src whole if it is partial
srcConfig = mergeConfig(srcConfig, defaultConfig);
// then merge inline config and config from src
this.values = mergeConfig(inlineConfig, srcConfig);
this.injectMetadata();
appConfig.set(this.values);
logger.info('config set:', this.values);
addInitializer(this.loadPackages);
addInitializer(this.loadPaths);
this.loadRuntimes();
}
log(msg: string) {
const newLog = document.createElement('p');
newLog.innerText = msg;
this.details.appendChild(newLog);
}
close() {
this.remove();
}
loadPackages = async () => {
const env = appConfig_.packages;
logger.info("Loading env: ", env);
await runtimeSpec.installPackage(env);
}
loadPaths = async () => {
const paths = appConfig_.paths;
logger.info("Paths to load: ", paths)
for (const singleFile of paths) {
logger.info(` loading path: ${singleFile}`);
try {
await runtimeSpec.loadFromFile(singleFile);
} catch (e) {
//Should we still export full error contents to console?
handleFetchError(<Error>e, singleFile);
}
}
logger.info("All paths loaded");
}
loadRuntimes() {
logger.info('Initializing runtimes');
for (const runtime of this.values.runtimes) {
const runtimeObj: Runtime = new PyodideRuntime(runtime.src, runtime.name, runtime.lang);
const script = document.createElement('script'); // create a script DOM node
script.src = runtimeObj.src; // set its src to the provided URL
script.addEventListener('load', () => {
void runtimeObj.initialize();
});
document.head.appendChild(script);
}
}
}

View File

@@ -1,82 +0,0 @@
import * as jsyaml from 'js-yaml';
import { runtimeLoaded, addInitializer } from '../stores';
import { handleFetchError } from '../utils';
import type { Runtime } from '../runtime';
import { getLogger } from '../logger';
const logger = getLogger('py-env');
// Premise used to connect to the first available runtime (can be pyodide or others)
let runtime: Runtime;
runtimeLoaded.subscribe(value => {
runtime = value;
});
export class PyEnv extends HTMLElement {
shadow: ShadowRoot;
wrapper: HTMLElement;
code: string;
environment: unknown;
runtime: Runtime;
env: string[];
paths: string[];
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
this.wrapper = document.createElement('slot');
}
connectedCallback() {
logger.info("The <py-env> tag is deprecated, please use <py-config> instead.")
this.code = this.innerHTML;
this.innerHTML = '';
const env: string[] = [];
const paths: string[] = [];
this.environment = jsyaml.load(this.code);
if (this.environment === undefined) return;
for (const entry of Array.isArray(this.environment) ? this.environment : []) {
if (typeof entry == 'string') {
env.push(entry);
} else if (entry && typeof entry === 'object') {
const obj = <Record<string, unknown>>entry;
for (const path of Array.isArray(obj.paths) ? obj.paths : []) {
if (typeof path === 'string') {
paths.push(path);
}
}
}
}
this.env = env;
this.paths = paths;
async function loadEnv() {
logger.info("Loading env: ", env);
await runtime.installPackage(env);
}
async function loadPaths() {
logger.info("Paths to load: ", paths)
for (const singleFile of paths) {
logger.info(` loading path: ${singleFile}`);
try {
await runtime.loadFromFile(singleFile);
} catch (e) {
//Should we still export full error contents to console?
handleFetchError(<Error>e, singleFile);
}
}
logger.info("All paths loaded");
}
addInitializer(loadEnv);
addInitializer(loadPaths);
}
}

View File

@@ -1,52 +1,58 @@
import { BaseEvalElement } from './base';
import { addClasses, htmlDecode } from '../utils';
import { getLogger } from '../logger'
import { getAttribute, addClasses, htmlDecode, ensureUniqueId, createDeprecationWarning } from '../utils';
import { getLogger } from '../logger';
import type { Runtime } from '../runtime';
const logger = getLogger('py-inputbox');
export class PyInputBox extends BaseEvalElement {
widths: Array<string>;
label: string;
mount_name: string;
constructor() {
super();
export function make_PyInputBox(runtime: Runtime) {
class PyInputBox extends HTMLElement {
widths: string[] = [];
label: string | undefined = undefined;
mount_name: string | undefined = undefined;
code: string;
if (this.hasAttribute('label')) {
this.label = this.getAttribute('label');
constructor() {
super();
const label = getAttribute(this, 'label');
if (label) {
this.label = label;
}
}
connectedCallback() {
const deprecationMessage =
'The element <py-input> is deprecated, ' + 'use <input class="py-input"> instead.';
createDeprecationWarning(deprecationMessage, 'py-input');
ensureUniqueId(this);
this.code = htmlDecode(this.innerHTML);
this.mount_name = this.id.split('-').join('_');
this.innerHTML = '';
const mainDiv = document.createElement('input');
mainDiv.type = 'text';
addClasses(mainDiv, ['py-input']);
mainDiv.id = this.id;
this.id = `${this.id}-container`;
this.appendChild(mainDiv);
// now that we appended and the element is attached, lets connect with the event handlers
// defined for this widget
this.appendChild(mainDiv);
this.code = this.code.split('self').join(this.mount_name);
let registrationCode = `from pyodide.ffi import create_proxy`;
registrationCode += `\n${this.mount_name} = Element("${mainDiv.id}")`;
if (this.code.includes('def on_keypress')) {
this.code = this.code.replace('def on_keypress', `def on_keypress_${this.mount_name}`);
registrationCode += `\n${this.mount_name}.element.addEventListener('keypress', create_proxy(on_keypress_${this.mount_name}))`;
}
runtime.runButDontRaise(this.code);
runtime.runButDontRaise(registrationCode);
logger.debug('py-inputbox connected');
}
}
connectedCallback() {
this.checkId();
this.code = htmlDecode(this.innerHTML);
this.mount_name = this.id.split('-').join('_');
this.innerHTML = '';
const mainDiv = document.createElement('input');
mainDiv.type = 'text';
addClasses(mainDiv, ['py-input']);
mainDiv.id = this.id;
this.id = `${this.id}-container`;
this.appendChild(mainDiv);
// now that we appended and the element is attached, lets connect with the event handlers
// defined for this widget
this.appendChild(mainDiv);
this.code = this.code.split('self').join(this.mount_name);
let registrationCode = `from pyodide.ffi import create_proxy`;
registrationCode += `\n${this.mount_name} = Element("${mainDiv.id}")`;
if (this.code.includes('def on_keypress')) {
this.code = this.code.replace('def on_keypress', `def on_keypress_${this.mount_name}`);
registrationCode += `\n${this.mount_name}.element.addEventListener('keypress', create_proxy(on_keypress_${this.mount_name}))`;
}
// TODO: For now we delay execution to allow pyodide to load but in the future this
// should really wait for it to load..
this.runAfterRuntimeInitialized(async () => {
await this.eval(this.code);
await this.eval(registrationCode);
logger.debug('registered handlers');
});
}
return PyInputBox;
}

View File

@@ -1,43 +0,0 @@
import { BaseEvalElement } from './base';
import { getLogger } from '../logger';
const logger = getLogger('py-loader');
export class PyLoader extends BaseEvalElement {
widths: Array<string>;
label: string;
mount_name: string;
details: HTMLElement;
operation: HTMLElement;
constructor() {
super();
}
connectedCallback() {
this.innerHTML = `<div id="pyscript_loading_splash" class="py-overlay">
<div class="py-pop-up">
<div class="smooth spinner"></div>
<div id="pyscript-loading-label" class="label">
<div id="pyscript-operation-details">
</div>
</div>
</div>
</div>`;
this.mount_name = this.id.split('-').join('_');
this.operation = document.getElementById('pyscript-operation');
this.details = document.getElementById('pyscript-operation-details');
}
log(msg: string) {
// loader messages are showed both in the HTML and in the console
logger.info(msg);
const newLog = document.createElement('p');
newLog.innerText = msg;
this.details.appendChild(newLog);
}
close() {
logger.info('Closing');
this.remove();
}
}

View File

@@ -1,201 +1,239 @@
import { basicSetup, EditorState, EditorView } from '@codemirror/basic-setup';
import { basicSetup, EditorView } from 'codemirror';
import { python } from '@codemirror/lang-python';
import { Compartment, StateCommand } from '@codemirror/state';
import { indentUnit } from '@codemirror/language';
import { Compartment } from '@codemirror/state';
import { keymap } from '@codemirror/view';
import { defaultKeymap } from '@codemirror/commands';
import { oneDarkTheme } from '@codemirror/theme-one-dark';
import { addClasses, htmlDecode } from '../utils';
import { BaseEvalElement } from './base';
import { getAttribute, ensureUniqueId, htmlDecode } from '../utils';
import type { Runtime } from '../runtime';
import { pyExec, pyDisplay } from '../pyexec';
import { getLogger } from '../logger';
const logger = getLogger('py-repl');
const RUNBUTTON = `<svg style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>`;
export function make_PyRepl(runtime: Runtime) {
/* High level structure of py-repl DOM, and the corresponding JS names.
function createCmdHandler(el: PyRepl): StateCommand {
// Creates a codemirror cmd handler that calls the el.evaluate when an event
// triggers that specific cmd
return () => {
void el.evaluate();
return true;
};
}
this <py-repl>
shadow #shadow-root
<slot></slot>
boxDiv <div class='py-repl-box'>
editorLabel <label>...</label>
editorDiv <div class="py-repl-editor"></div>
outDiv <div class="py-repl-output"></div>
</div>
</py-repl>
*/
class PyRepl extends HTMLElement {
shadow: ShadowRoot;
outDiv: HTMLElement;
editor: EditorView;
let initialTheme: string;
function getEditorTheme(el: BaseEvalElement): string {
return initialTheme || (initialTheme = el.getAttribute('theme'));
}
export class PyRepl extends BaseEvalElement {
editor: EditorView;
editorNode: HTMLElement;
constructor() {
super();
// add an extra div where we can attach the codemirror editor
this.editorNode = document.createElement('div');
addClasses(this.editorNode, ['editor-box']);
this.shadow.appendChild(this.wrapper);
}
connectedCallback() {
this.checkId();
this.code = htmlDecode(this.innerHTML);
this.innerHTML = '';
const languageConf = new Compartment();
const extensions = [
basicSetup,
languageConf.of(python()),
keymap.of([
...defaultKeymap,
{ key: 'Ctrl-Enter', run: createCmdHandler(this) },
{ key: 'Shift-Enter', run: createCmdHandler(this) },
]),
];
if (getEditorTheme(this) === 'dark') {
extensions.push(oneDarkTheme);
constructor() {
super();
}
this.editor = new EditorView({
state: EditorState.create({
doc: this.code.trim(),
connectedCallback() {
ensureUniqueId(this);
this.shadow = this.attachShadow({ mode: 'open' });
const slot = document.createElement('slot');
this.shadow.appendChild(slot);
if (!this.hasAttribute('exec-id')) {
this.setAttribute('exec-id', '1');
}
if (!this.hasAttribute('root')) {
this.setAttribute('root', this.id);
}
const pySrc = htmlDecode(this.innerHTML).trim();
this.innerHTML = '';
this.editor = this.makeEditor(pySrc);
const boxDiv = this.makeBoxDiv();
this.appendChild(boxDiv);
this.editor.focus();
logger.debug(`element ${this.id} successfully connected`);
}
/** Create and configure the codemirror editor
*/
makeEditor(pySrc: string): EditorView {
const languageConf = new Compartment();
const extensions = [
indentUnit.of(' '),
basicSetup,
languageConf.of(python()),
keymap.of([
...defaultKeymap,
{ key: 'Ctrl-Enter', run: this.execute.bind(this) },
{ key: 'Shift-Enter', run: this.execute.bind(this) },
]),
];
if (getAttribute(this, 'theme') === 'dark') {
extensions.push(oneDarkTheme);
}
return new EditorView({
doc: pySrc,
extensions,
}),
parent: this.editorNode,
});
const mainDiv = document.createElement('div');
addClasses(mainDiv, ['py-repl-box']);
// Styles that we use to hide the labels whilst also keeping it accessible for screen readers
const labelStyle = 'overflow:hidden; display:block; width:1px; height:1px';
// Code editor Label
this.editorNode.id = 'code-editor';
const editorLabel = document.createElement('label');
editorLabel.innerHTML = 'Python Script Area';
editorLabel.setAttribute('style', labelStyle);
editorLabel.htmlFor = 'code-editor';
mainDiv.append(editorLabel);
// add Editor to main PyScript div
mainDiv.appendChild(this.editorNode);
// Play Button
this.btnRun = document.createElement('button');
this.btnRun.id = 'btnRun';
this.btnRun.innerHTML =
'<svg id="" class="svelte-fa svelte-ps5qeg" style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>';
addClasses(this.btnRun, ['absolute', 'repl-play-button']);
// Play Button Label
const btnLabel = document.createElement('label');
btnLabel.innerHTML = 'Python Script Run Button';
btnLabel.setAttribute('style', labelStyle);
btnLabel.htmlFor = 'btnRun';
this.editorNode.appendChild(btnLabel);
this.editorNode.appendChild(this.btnRun);
this.btnRun.addEventListener('click', () => {
void this.evaluate();
});
if (!this.id) {
logger.warn(
"WARNING: <py-repl> defined without an id. <py-repl> should always have an id, otherwise multiple <py-repl> in the same page will not work!"
);
});
}
if (!this.hasAttribute('exec-id')) {
this.setAttribute('exec-id', '1');
// ******** main entry point for py-repl DOM building **********
//
// The following functions are written in a top-down, depth-first
// order (so that the order of code roughly matches the order of
// execution)
makeBoxDiv(): HTMLElement {
const boxDiv = document.createElement('div');
boxDiv.className = 'py-repl-box';
const editorDiv = this.makeEditorDiv();
const editorLabel = this.makeLabel('Python Script Area', editorDiv);
this.outDiv = this.makeOutDiv();
boxDiv.append(editorLabel);
boxDiv.appendChild(editorDiv);
boxDiv.appendChild(this.outDiv);
return boxDiv;
}
if (!this.hasAttribute('root')) {
this.setAttribute('root', this.id);
makeEditorDiv(): HTMLElement {
const editorDiv = document.createElement('div');
editorDiv.id = 'code-editor';
editorDiv.className = 'py-repl-editor';
editorDiv.appendChild(this.editor.dom);
const runButton = this.makeRunButton();
const runLabel = this.makeLabel('Python Script Run Button', runButton);
editorDiv.appendChild(runLabel);
editorDiv.appendChild(runButton);
return editorDiv;
}
if (this.hasAttribute('output')) {
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
} else {
if (this.hasAttribute('std-out')) {
this.outputElement = document.getElementById(this.getAttribute('std-out'));
} else {
// In this case neither output or std-out have been provided so we need
// to create a new output div to output to
this.outputElement = document.createElement('div');
this.outputElement.classList.add('output');
this.outputElement.hidden = true;
this.outputElement.id = this.id + '-' + this.getAttribute('exec-id');
makeLabel(text: string, elementFor: HTMLElement): HTMLElement {
ensureUniqueId(elementFor);
const lbl = document.createElement('label');
lbl.innerHTML = text;
lbl.htmlFor = elementFor.id;
// XXX this should be a CSS class
// Styles that we use to hide the labels whilst also keeping it accessible for screen readers
const labelStyle = 'overflow:hidden; display:block; width:1px; height:1px';
lbl.setAttribute('style', labelStyle);
return lbl;
}
// add the output div id if there's not output pre-defined
mainDiv.appendChild(this.outputElement);
makeRunButton(): HTMLElement {
const runButton = document.createElement('button');
runButton.id = 'runButton';
runButton.className = 'absolute py-repl-run-button';
runButton.innerHTML = RUNBUTTON;
runButton.addEventListener('click', this.execute.bind(this));
return runButton;
}
makeOutDiv(): HTMLElement {
const outDiv = document.createElement('div');
outDiv.className = 'py-repl-output';
outDiv.id = this.id + '-' + this.getAttribute('exec-id');
return outDiv;
}
// ********************* execution logic *********************
/** Execute the python code written in the editor, and automatically
* display() the last evaluated expression
*/
execute(): void {
const pySrc = this.getPySrc();
// determine the output element
const outEl = this.getOutputElement();
if (outEl === undefined) {
// this happens if we specified output="..." but we couldn't
// find the ID. We already displayed an error message inside
// getOutputElement, stop the execution.
return;
}
this.errorElement = this.hasAttribute('std-err')
? document.getElementById(this.getAttribute('std-err'))
: this.outputElement;
}
// clear the old output before executing the new code
outEl.innerHTML = '';
this.appendChild(mainDiv);
this.editor.focus();
logger.debug(`element ${this.id} successfully connected`);
}
// execute the python code
const pyResult = pyExec(runtime, pySrc, outEl);
addToOutput(s: string): void {
this.outputElement.innerHTML += '<div>' + s + '</div>';
this.outputElement.hidden = false;
}
preEvaluate(): void {
this.setOutputMode("replace");
if(!this.appendOutput) {
this.outputElement.innerHTML = '';
}
}
postEvaluate(): void {
this.outputElement.hidden = false;
this.outputElement.style.display = 'block';
if (this.hasAttribute('auto-generate')) {
const allPyRepls = document.querySelectorAll(`py-repl[root='${this.getAttribute('root')}'][exec-id]`);
const lastRepl = allPyRepls[allPyRepls.length - 1];
const lastExecId = lastRepl.getAttribute('exec-id');
const nextExecId = parseInt(lastExecId) + 1;
const newPyRepl = document.createElement('py-repl');
newPyRepl.setAttribute('root', this.getAttribute('root'));
newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString();
if(this.hasAttribute('auto-generate')) {
newPyRepl.setAttribute('auto-generate', '');
this.removeAttribute('auto-generate');
// display the value of the last evaluated expression (REPL-style)
if (pyResult !== undefined) {
pyDisplay(runtime, pyResult, { target: outEl.id });
}
if(this.hasAttribute('output-mode')) {
newPyRepl.setAttribute('output-mode', this.getAttribute('output-mode'));
}
this.autogenerateMaybe();
}
const addReplAttribute = (attribute: string) => {
if (this.hasAttribute(attribute)) {
newPyRepl.setAttribute(attribute, this.getAttribute(attribute));
getPySrc(): string {
return this.editor.state.doc.toString();
}
getOutputElement(): HTMLElement {
const outputID = getAttribute(this, 'output');
if (outputID !== null) {
const el = document.getElementById(outputID);
if (el === null) {
const err = `py-repl ERROR: cannot find the output element #${outputID} in the DOM`;
this.outDiv.innerText = err;
return undefined;
}
};
return el;
} else {
return this.outDiv;
}
}
addReplAttribute('output');
addReplAttribute('std-out');
addReplAttribute('std-err');
// XXX the autogenerate logic is very messy. We should redo it, and it
// should be the default.
autogenerateMaybe(): void {
if (this.hasAttribute('auto-generate')) {
const allPyRepls = document.querySelectorAll(`py-repl[root='${this.getAttribute('root')}'][exec-id]`);
const lastRepl = allPyRepls[allPyRepls.length - 1];
const lastExecId = lastRepl.getAttribute('exec-id');
const nextExecId = parseInt(lastExecId) + 1;
newPyRepl.setAttribute('exec-id', nextExecId.toString());
this.parentElement.appendChild(newPyRepl);
const newPyRepl = document.createElement('py-repl');
newPyRepl.setAttribute('root', this.getAttribute('root'));
newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString();
if (this.hasAttribute('auto-generate')) {
newPyRepl.setAttribute('auto-generate', '');
this.removeAttribute('auto-generate');
}
const outputMode = getAttribute(this, 'output-mode');
if (outputMode) {
newPyRepl.setAttribute('output-mode', outputMode);
}
const addReplAttribute = (attribute: string) => {
const attr = getAttribute(this, attribute);
if (attr) {
newPyRepl.setAttribute(attribute, attr);
}
};
addReplAttribute('output');
newPyRepl.setAttribute('exec-id', nextExecId.toString());
if (this.parentElement) {
this.parentElement.appendChild(newPyRepl);
}
}
}
}
getSourceFromElement(): string {
return this.editor.state.doc.toString();
}
return PyRepl;
}

Some files were not shown because too many files have changed in this diff Show More