From 3f68e0b9c8d054b2f11656d2aa2e8780a64cab47 Mon Sep 17 00:00:00 2001 From: DerThorsten Date: Tue, 12 Mar 2024 11:51:10 +0100 Subject: [PATCH] docs --- .github/workflows/docs.yml | 4 +- .gitignore | 7 +- .readthedocs.yaml | 25 --- benchmark_pyjs.py | 97 -------- build_browser.sh | 64 ------ build_docs.sh => build_mkdocs.sh | 70 +++++- build_tests.sh | 62 ----- build_utils/embed_pycpp.py | 32 --- docs/JavaScript_API.md | 12 + docs/Makefile | 23 -- docs/Python_API.md | 42 ++++ docs/_src/faq.md | 14 -- docs/_src/related_projects.md | 16 -- docs/_src/usage.md | 123 ---------- docs/conf.py | 88 -------- docs/index.md | 3 + docs/index.rst | 27 --- docs/make.bat | 35 --- docs/post_build.py | 166 -------------- docs/requirements.txt | 3 - docs/try_from_js.md | 22 ++ docs/try_from_py.md | 7 + environment-dev.yml | 12 +- examples/js_api_tour.js | 38 ++-- examples/py_api_tour.py | 18 +- include/pyjs/pre_js/init.js | 13 -- include/pyjs/pre_js/load_pkg.js | 27 ++- mkdocs.yml | 36 +++ module/pyjs/__init__.py | 6 +- module/pyjs/convert.py | 50 ++--- module/pyjs/extend_js_val.py | 1 - module/pyjs/webloop_LICENCE | 373 +++++++++++++++++++++++++++++++ module/pyjs/webloop_README.md | 2 + src/export_js_proxy.cpp | 7 +- src/export_pyjs_module.cpp | 8 - src/js_timestamp.cpp | 2 +- stubs/pyjs_core/__init__.py | 1 + stubs/pyjs_core/__init__.pyi | 17 ++ 38 files changed, 666 insertions(+), 887 deletions(-) delete mode 100644 .readthedocs.yaml delete mode 100644 benchmark_pyjs.py delete mode 100755 build_browser.sh rename build_docs.sh => build_mkdocs.sh (53%) delete mode 100755 build_tests.sh delete mode 100644 build_utils/embed_pycpp.py create mode 100644 docs/JavaScript_API.md delete mode 100644 docs/Makefile create mode 100644 docs/Python_API.md delete mode 100644 docs/_src/faq.md delete mode 100644 docs/_src/related_projects.md delete mode 100644 docs/_src/usage.md delete mode 100644 docs/conf.py create mode 100644 docs/index.md delete mode 100644 docs/index.rst delete mode 100644 docs/make.bat delete mode 100644 docs/post_build.py delete mode 100644 docs/requirements.txt create mode 100644 docs/try_from_js.md create mode 100644 docs/try_from_py.md create mode 100644 mkdocs.yml create mode 100644 module/pyjs/webloop_LICENCE create mode 100644 module/pyjs/webloop_README.md create mode 100644 stubs/pyjs_core/__init__.py create mode 100644 stubs/pyjs_core/__init__.pyi diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8414bec..de293f6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -38,7 +38,7 @@ jobs: - name: build the docs shell: bash -el {0} run: | - ./build_docs.sh + ./build_mkdocs.sh ################################################################ @@ -47,7 +47,7 @@ jobs: - name: Upload Pages artifact uses: actions/upload-pages-artifact@v2 with: - path: docs/_build/html + path: docs_build/mkdocs deploy: diff --git a/.gitignore b/.gitignore index 4042d7c..23f10b1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,9 @@ node_modules package-lock.json docs/_build *.wasm -nxtgm_javascript_runtime.js \ No newline at end of file +nxtgm_javascript_runtime.js +.DS_Store +xeus-python/ +emsdk_* +docs_build/ +empack_config.yaml \ No newline at end of file diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index c886f17..0000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Set the version of Python and other tools you might need -build: - os: ubuntu-20.04 - tools: - python: "3.10" - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py - -# If using Sphinx, optionally build your docs in additional formats such as PDF -# formats: -# - pdf - -# Optionally declare the Python requirements required to build your docs -python: - install: - - requirements: docs/requirements.txt diff --git a/benchmark_pyjs.py b/benchmark_pyjs.py deleted file mode 100644 index 2f701ea..0000000 --- a/benchmark_pyjs.py +++ /dev/null @@ -1,97 +0,0 @@ -import pytest - -import pyjs - -# os.environ["PYJS_DONT_AUTOSTART_EVENT_LOOP"] = "1" - - -# to benchmark: -# - get global property -# - long chain "foo.bar.foobar.barfoo" -# - implicted converted properties - - -# def test_get_global_property(benchmark): - -# benchmark(lambda : pyjs.js.Object) - - -@pytest.mark.parametrize("n_args", [2]) -def test_function_call(benchmark, n_args): - - args = [f"a{i}" for i in range(n_args)] - body = ["return 1"] - - f = pyjs.js.Function(*(args + body)) - benchmark(f, *args) - - -@pytest.mark.parametrize("n_args", [2]) -def test_function_jcall(benchmark, n_args): - - args = [f"a{i}" for i in range(n_args)] - body = ["return 1"] - - f = pyjs.js.Function(*(args + body)).jcall - benchmark(f, *args) - - -@pytest.mark.parametrize("n_args", [2]) -@pytest.mark.parametrize("jin", [False, True]) -@pytest.mark.parametrize("jout", [False, True]) -def test_function_gcall(benchmark, n_args, jin, jout): - - args = [f"a{i}" for i in range(n_args)] - body = ["return 1"] - - f = pyjs.js.Function(*(args + body)) - - benchmark(lambda: pyjs.gapply(f, args, jin=jin, jout=jout)) - - -# @pytest.mark.benchmark( -# min_rounds=500 -# ) -# def test_real_world_scenario(benchmark): - -# def f(): -# myRequest = js.XMLHttpRequest.new() -# myRequest.open("GET","https://www.w3schools.com/bootstrap/bootstrap_ver.asp",False) -# myRequest.responseType = "arraybuffer" - -# benchmark(f) - - -# @pytest.mark.benchmark( -# min_rounds=500 -# ) -# def test_jcall(benchmark): - -# fopen = js.XMLHttpRequest.new().open -# benchmark(fopen,"GET","https://www.w3schools.com/bootstrap/bootstrap_ver.asp",False) - - -# @pytest.mark.benchmark( -# min_rounds=500 -# ) -# def test_call(benchmark): - -# fopen = js.XMLHttpRequest.new().open.jcall -# benchmark(fopen,"GET","https://www.w3schools.com/bootstrap/bootstrap_ver.asp",False) - - -if __name__ == "__main__": - # start the tests - # os.environ["NO_COLOR"] = "1" - args = [ - "-s", - "/script/benchmark_pyjs.py", - "--benchmark-min-time=0.0005", - "--benchmark-warmup=on", - ] - specific_test = "" # "test_real_world_scenario" - if specific_test is not None and specific_test != "": - args += ["-k", str(specific_test)] - retcode = pytest.main(args) - if retcode != 0: - raise RuntimeError(f"pytest failed with return code: {retcode}") diff --git a/build_browser.sh b/build_browser.sh deleted file mode 100755 index ccbb544..0000000 --- a/build_browser.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash -set -e - - -BUILD_DIR=build_tests -# create build/work dir -mkdir -p $BUILD_DIR -cd $BUILD_DIR - -ENV_NAME=pyjs-browser -ENV_MINIMAL_NAME=pyjs-minimal - - -WORK_DIR=host_work_dir -mkdir -p $WORK_DIR - -cp $(pwd)/../tests/test_data/* $WORK_DIR - - -if false; then - # install wasm env - # rm -rf $MAMBA_ROOT_PREFIX/envs/pyjs-node-testsar - micromamba create -n pyjs-browser \ - --platform=emscripten-32 \ - -c https://repo.mamba.pm/emscripten-forge \ - -c https://repo.mamba.pm/conda-forge \ - --yes \ - python pybind11 nlohmann_json pybind11_json numpy pytest \ - bzip2 sqlite zlib libffi xeus xeus-lite xtl xeus -fi - - -if false; then - # install wasm env - # rm -rf $MAMBA_ROOT_PREFIX/envs/pyjs-node-testsar - micromamba create -n pyjs-minimal \ - --platform=emscripten-32 \ - -c https://repo.mamba.pm/emscripten-forge \ - -c https://repo.mamba.pm/conda-forge \ - --yes \ - python pytest - -fi - -if true; then - - # let cmake know where the env is - export PREFIX=$MAMBA_ROOT_PREFIX/envs/$ENV_NAME - export CMAKE_PREFIX_PATH=$PREFIX - export CMAKE_SYSTEM_PREFIX_PATH=$PREFIX - - # build pyjs - emcmake cmake \ - -DCMAKE_BUILD_TYPE=MinSizeRel \ - -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON \ - -DBUILD_RUNTIME_BROWSER=ON \ - -DBUILD_RUNTIME_NODE=OFF \ - -DCMAKE_INSTALL_PREFIX=$PREFIX \ - .. - - emmake make -j12 install - -fi - diff --git a/build_docs.sh b/build_mkdocs.sh similarity index 53% rename from build_docs.sh rename to build_mkdocs.sh index 0e57516..16a1d82 100755 --- a/build_docs.sh +++ b/build_mkdocs.sh @@ -29,7 +29,7 @@ if [ ! -d "$WASM_ENV_PREFIX" ]; then --yes \ python pybind11 nlohmann_json pybind11_json numpy \ bzip2 sqlite zlib libffi exceptiongroup \ - xeus xeus-lite xeus-python-shell xeus-javascript xtl + xeus xeus-lite xeus-python xeus-javascript xtl "ipython<8.20" else echo "Wasm env $WASM_ENV_NAME already exists" @@ -69,14 +69,14 @@ fi # if there is no xeus-python dir, clone it if [ ! -d "$THIS_DIR/xeus-python" ]; then cd $THIS_DIR - git clone https://github.com/jupyter-xeus/xeus-python/ + git clone -b pyjs_update https://github.com/DerThorsten/xeus-python/ else echo "xeus-python dir already exists" fi -PYJS_PROBE_FILE=$WASM_ENV_PREFIX/share/jupyter/kernels/xpython/kernel.json -if [ ! -f "$PYJS_PROBE_FILE" ]; then +# PYJS_PROBE_FILE=$WASM_ENV_PREFIX/share/jupyter/kernels/xpython/kernel.json +if false; then echo "Building xeus-python" cd $THIS_DIR @@ -105,8 +105,9 @@ if [ ! -f "$PYJS_PROBE_FILE" ]; then emmake make -j8 install - - rm -rf $WASM_ENV_PREFIX/share/jupyter/kernels/xpython-raw + if [ -d "$WASM_ENV_PREFIX/share/jupyter/kernels/xpython-raw" ]; then + rm -rf $WASM_ENV_PREFIX/share/jupyter/kernels/xpython-raw + fi else echo "Skipping build xeus-python" @@ -114,17 +115,64 @@ fi +if true ; then + + rm -rf $THIS_DIR/docs_build + mkdir -p $THIS_DIR/docs_build + + # convert *.py to *.ipynb using jupytext + NOTEBOOK_OUTPUT_DIR=$THIS_DIR/docs_build/notebooks + rm -rf $NOTEBOOK_OUTPUT_DIR + mkdir -p $NOTEBOOK_OUTPUT_DIR + + for f in $THIS_DIR/examples/*.py; do + # get the filename without the extension and path + filename=$(basename -- "$f") + jupytext $f --to ipynb --output $NOTEBOOK_OUTPUT_DIR/${filename%.*}.ipynb --update-metadata '{"kernelspec": {"name": "xpython"}}' + done + for f in $THIS_DIR/examples/*.js; do + # get the filename without the extension and path + filename=$(basename -- "$f") + jupytext $f --to ipynb --output $NOTEBOOK_OUTPUT_DIR/${filename%.*}.ipynb --update-metadata '{"kernelspec": {"name": "xjavascript"}}' + done + + + + # lite + if true; then + cd $THIS_DIR + rm -rf docs_build/_output + rm -rf docs_build/.jupyterlite.doit.db + mkdir -p docs_build + cd docs_build + + jupyter lite build \ + --contents=$NOTEBOOK_OUTPUT_DIR \ + --XeusAddon.prefix=$WASM_ENV_PREFIX \ + --XeusAddon.mounts=$THIS_DIR/module/pyjs:/lib/python3.11/site-packages/pyjs + fi +fi + + +# the docs itself if true ; then export PREFIX=$MAMBA_ROOT_PREFIX/envs/pyjs-wasm echo "Building docs" - cd $THIS_DIR/docs + cd $THIS_DIR + mkdir -p docs_build/mkdocs + export PYTHONPATH=$PYTHONPATH:$THIS_DIR/stubs + export PYTHONPATH=$PYTHONPATH:$THIS_DIR/module + mkdocs build --site-dir=docs_build/mkdocs - WASM_ENV_PREFIX=$WASM_ENV_PREFIX LITE=1 make html - # post-build - - WASM_ENV_PREFIX=$WASM_ENV_PREFIX python ./post_build.py +fi + +if true ; then + # # copy lite _output to docs_build + cp -r $THIS_DIR/docs_build/_output $THIS_DIR/docs_build/mkdocs/lite + # copy pyjs binary to docs_build + cp $WASM_ENV_PREFIX/lib_js/pyjs/* $THIS_DIR/docs_build/mkdocs/lite/xeus/bin/ fi \ No newline at end of file diff --git a/build_tests.sh b/build_tests.sh deleted file mode 100755 index 9912214..0000000 --- a/build_tests.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -set -e - -# create build/work dir -mkdir -p build_tests -cd build_tests - - -if true; then - # install wasm env - # rm -rf $MAMBA_ROOT_PREFIX/envs/pyjs-node-testsar - micromamba create -n pyjs-node-testsar \ - --platform=emscripten-32 \ - -c https://repo.mamba.pm/emscripten-forge \ - -c https://repo.mamba.pm/conda-forge \ - --yes \ - python pybind11 nlohmann_json pybind11_json numpy pytest bzip2 sqlite zlib libffi pyb2d pydantic - - - - # # # donload empack config - # EMPACK_CONFIG=empack_config.yaml - # echo "donwload empack config" - # wget -O $EMPACK_CONFIG https://raw.githubusercontent.com/emscripten-forge/recipes/main/empack_config.yaml - - - - # pack the environment in a js/data file - empack pack env \ - --env-prefix $MAMBA_ROOT_PREFIX/envs/pyjs-node-testsar \ - --outname python_data \ - --config empack_config.yaml \ - --export-name "global.Module" - - - - -fi - - -# let cmake know where the env is -export PREFIX=$MAMBA_ROOT_PREFIX/envs/pyjs-node-testsar -export CMAKE_PREFIX_PATH=$PREFIX -export CMAKE_SYSTEM_PREFIX_PATH=$PREFIX - -# build pyjs -emcmake cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON \ - -DBUILD_RUNTIME_BROWSER=OFF \ - -DBUILD_RUNTIME_NODE=ON \ - -DCMAKE_INSTALL_PREFIX=$PREFIX \ - .. - - -make -j12 node_tests - - -# make install -# cp ../examples/repl.html . - -# python -m http.server diff --git a/build_utils/embed_pycpp.py b/build_utils/embed_pycpp.py deleted file mode 100644 index cda3fa7..0000000 --- a/build_utils/embed_pycpp.py +++ /dev/null @@ -1,32 +0,0 @@ -import sys -from pathlib import Path - -template = """ -#include -#include -namespace py = pybind11; - -void {name}_pseudo_init(py::module_& m){{ - - py::object scope = m.attr("__dict__"); - py::exec(R"( -{content} -)",scope); - -}} -""" - - -def generate_embed(fn_in, fn_out): - fn_in = Path(fn_in) - fn = fn_in.name - modname = "pyjs_" + fn.split(".")[0] - content = fn_in.read_text() - res = template.format(name=modname, content=content) - fn_out = Path(fn_out) - fn_out.write_text(res) - - -if __name__ == "__main__": - print(sys.argv[1], sys.argv[2]) - generate_embed(sys.argv[1], sys.argv[2]) diff --git a/docs/JavaScript_API.md b/docs/JavaScript_API.md new file mode 100644 index 0000000..23d5322 --- /dev/null +++ b/docs/JavaScript_API.md @@ -0,0 +1,12 @@ +# JavaScript API +## `pyjs` +### `exec` +### `exec_eval` +### `eval` +### `async_exec_eval` +### `eval_file` +### `pyobject` +#### `py_call` +#### `py_apply` +#### `get` + diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 503b294..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - - $(SPHINXBUILD) -D plot_gallery=1 -b html $(ALLSPHINXOPTS) $(SOURCEDIR) $(BUILDDIR)/html - python post_build.py diff --git a/docs/Python_API.md b/docs/Python_API.md new file mode 100644 index 0000000..8615f51 --- /dev/null +++ b/docs/Python_API.md @@ -0,0 +1,42 @@ +# Python API + +::: pyjs_core + handler: python + options: + members: + - JsValue + + +::: pyjs + handler: python + options: + show_submodules: true + members: + - to_js + - register_converter + - to_py_json + - JsToPyConverterOptions + - to_py + - error_to_py + - error_to_py_and_raise + - new + - create_callable + - callable_context + - promise + - create_once_callable + - apply + - japply + - gapply + - JsException + - JsGenericError + - JsError + - JsInternalError + - JsRangeError + - JsReferenceError + - JsSyntaxError + - JsTypeError + - JsURIError + - WebLoop + - pyjs_core + + \ No newline at end of file diff --git a/docs/_src/faq.md b/docs/_src/faq.md deleted file mode 100644 index 77f5a92..0000000 --- a/docs/_src/faq.md +++ /dev/null @@ -1,14 +0,0 @@ -# FAQ: - -## What is pyjs? -`pyjs` is C++ library, compiled to wasm, which allows to run Python code in the browser or in node. - -## Why not use the plain python executable compiled to wasm? -While this would be possible, it would be a very limited API. -`pyjs` not only allows you to call Python from JavaScript, but also calling JavaScript from Python. - - -## Why not use pyodide -* The code of `pyodide` is strongly coupled to pyodides packaging system while `pyjs` focus on `emscripten-forge`. -* `pyodide` uses raw Pthon-C-API while `pyjs` uses high level `pybind11` -* `pyjs` uses `embind` instead of emscriptens more raw apis diff --git a/docs/_src/related_projects.md b/docs/_src/related_projects.md deleted file mode 100644 index a1169d9..0000000 --- a/docs/_src/related_projects.md +++ /dev/null @@ -1,16 +0,0 @@ -# Related Projects - - -## Emscripten-forge - -With emscripten forge we provide a mamba/conda channel for `emscripten-32` wasm packages. -The build instructions, also called "recipes", are stored at the -[github.com/emscripten-forge/recipes](https://github.com/emscripten-forge/recipes) repository. -To create new packages for `emscripten-32`, one has to add a new recipe to that repository. - - -## Empack -Empack is responsible for packaging files as `*.js/*.data` st. they can be used to populating Emscripten’s [virtual file system](https://emscripten.org/docs/porting/files/file_systems_overview.html#file-system-overview) when the page is loaded. -Empack is a convenient wrapper around emscripten filepackager with functionality centered around packaging mamba/conda environments. - -[emscripten-forge/empack](https://github.com/emscripten-forge/empack) diff --git a/docs/_src/usage.md b/docs/_src/usage.md deleted file mode 100644 index b33e775..0000000 --- a/docs/_src/usage.md +++ /dev/null @@ -1,123 +0,0 @@ -# Usage - -This describes the overall workflow to setup an `emscripten-32` environment with `micromamba`, packing this environment with `empack` and use this environment with `pyjs`. - -## Install micromamba - -We strongly recommend to use micromamba when using `pyjs`. -Please refer to the [mamba and micromamba installation guide in the documentation.](https://mamba.readthedocs.io/en/latest/installation.html) - -## Create the working environment - -First we create an environment containing all the dev-dependencies. - -```yaml -name: pyjs-dev-env -channels: - - conda-forge -dependencies: - - python - - curl - - empack >= 1.2.0 - - emsdk >=3.1.11 -``` - -Assuming the yaml file above is named `pyjs-dev-env.yaml`, we can create the dev-environment with: - -```bash -micromamba create \ - --yes --file pyjs-dev-env.yaml \ - -c https://repo.mamba.pm/emscripten-forge \ - -c https://repo.mamba.pm/conda-forge -``` - - -## Create the web environment - -This is the environment which will be available from the browser / nodejs. - -```yaml -name: pyjs-wasm-env -channels: - - https://repo.mamba.pm/emscripten-forge - - conda-forge -dependencies: - - pyjs == 0.11.1 - - numpy - - pyb2d -``` -Assuming the yaml file above is named `pyjs-wasm-env.yaml`, we can create the web-environment with: - -```Bash -micromamba create \ - --platform=emscripten-32 \ - --yes --file pyjs-wasm-env.yaml \ - -c https://repo.mamba.pm/emscripten-forge \ - -c https://repo.mamba.pm/conda-forge -``` - - -## Pack the environment - -First we download the empack default configuration [github](https://raw.githubusercontent.com/emscripten-forge/recipes/main/empack_config.yaml) -```bash -curl https://raw.githubusercontent.com/emscripten-forge/recipes/main/empack_config.yaml --output empack_config.yaml -``` - -Next we invoke empack to pack the conda-environment `pyjs-wasm-env` into `*.data/*.js` files which can be fetched and imported from JavaScript. - -```bash -empack pack env \ - --env-prefix $MAMBA_ROOT_PREFIX/envs/pyjs-wasm-env \ - --outname my_sample_application \ - --config empack_config.yaml \ - --config extra_config.yaml \ - --outdir output \ - --export-name globalThis.pyjs \ - --split -```` - - -## Use It -Now that we packed our environment in (multiple) JavaScript files, we can start using `pyjs`: - -### Initialize pyjs - -The initialization of pyjs in JavaScript unfortunately still a bit complicated: - -```JavaScript -// pyjs itself -import {createModule} from '../pyjs_runtime_browser.js'; -let pyjs = await createModule() -globalThis.pyjs = pyjs - -// content of the packaged wasm environment -const { default: load_all } = await import('../my_sample_application.js') -await load_all() -await pyjs.init() - -``` - - -### Use Pyjs - -The initialization of pyjs in JavaScript unfortunately still a bit complicated: - -```JavaScript -// pyjs itself -pysjs.exec(` -import numpy -print(numpy.zeros([10,20])) -`) -``` - - -Within the python code called from JavaScript, one has access to the `pyjs` python module. -This allows access to the JavaScript side from Python: - - -```python -from pyjs.js import console - -console.log("print to browser console") -``` diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index b498c1e..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,88 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information - -project = "pyjs" -copyright = "2022, Thorsten Beier" -author = "Thorsten Beier" - -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration - - - -import os -import sys -import tempfile -import shutil -from pathlib import Path -import json - -this_dir = Path(os.path.abspath(os.path.dirname(__file__))) - - - -# get WASM_ENV_PREFIX from environment -env_location = Path(os.environ["WASM_ENV_PREFIX"]) -if not env_location.exists(): - raise ValueError(f"env_location {env_location} does not exist") - -jupyter_lite_conf_json = { - "LiteBuildConfig": { - "XeusAddon":{ - "prefix": str(env_location) - } - } -} -# write the jupyterlite config -with open(this_dir / "jupyterlite_config.json", "w") as f: - f.write(json.dumps(jupyter_lite_conf_json, indent=4)) -jupyterlite_config = this_dir / "jupyterlite_config.json" - -use_lite = True -if os.environ.get('LITE') and os.environ.get('LITE') == '0': - print('Not using lite') - use_lite = False - - - - -extensions = [ - "myst_parser", - "sphinx_rtd_theme", - 'sphinx_gallery.gen_gallery', -] -if use_lite: - extensions.append('jupyterlite_sphinx') - - - - - - -templates_path = ["_templates"] -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - - -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output - -html_theme = "sphinx_rtd_theme" -html_static_path = ["_static"] - -source_suffix = [".rst", ".md"] - - -# -- Options for sphinx-gallery ---------------------------------------------- -sphinx_gallery_conf = { - 'examples_dirs': '../examples', # path to your example scripts - 'gallery_dirs': 'auto_examples', # path to where to save gallery generated output, - 'example_extensions': {'.py', '.js', '.cpp'}, - 'notebook_extensions': {'.py', '.js', '.cpp'}, -} - - diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..189f481 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,3 @@ +# Welcome to `pyjs` + +Welcome \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 444612f..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,27 +0,0 @@ -Welcome to pyjs's documentation! -================================ - -This page is still under construction and will (hopefully) be extended soon! - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - auto_examples/index.rst - - - - - - - -.. Indices and tables -.. ================== - -.. * :ref:`genindex` -.. * :ref:`modindex` -.. * :ref:`search` - - - - diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 32bb245..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/post_build.py b/docs/post_build.py deleted file mode 100644 index a79a046..0000000 --- a/docs/post_build.py +++ /dev/null @@ -1,166 +0,0 @@ - -from pathlib import Path -import json -import sys -import os -import tempfile -import shutil - -language_info_js = { - "name": "javascript", - "version": "1.0.0", - "mimetype": "application/javascript", - "file_extension": ".js", - "codemirror_mode": "javascript", - "nbconvert_exporter": "javascript", - "pygments_lexer": "javascript", - "jupyterlite": { - "kernel": "xeus-javascript", - "spec": "javascript" - } -} -kernelspec_js = { - "display_name": "xeus-javascript", - "language": "javascript", - "name": "xeus-javascript" -} - -metadata_js = { - "language_info": language_info_js, - "kernelspec": kernelspec_js -} - - -language_info_py = { - "name": "python", - "version": "3.11.0", - "mimetype": "text/x-python", - "file_extension": ".py", - "codemirror_mode": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "python", -} - -kernelspec_py = { - "display_name": "xeus-python", - "language": "python", - "name": "xeus-python" -} - -metadata_py = { - "language_info": language_info_py, - "kernelspec": kernelspec_py -} - - -def fix_metadata(notebook_path): - with open(notebook_path, "r") as f: - notebook = json.load(f) - - notebook_name = notebook_path.stem - if notebook_name.startswith("js_"): - notebook["metadata"] = metadata_js - elif notebook_name.startswith("py_"): - notebook["metadata"] = metadata_py - else: - return - - print("fixing", notebook_path) - with open(notebook_path, "w") as f: - json.dump(notebook, f, indent=4) - -def fix_all_notebooks(directory): - # iterate over all notebooks (with pathlib) - for item in Path(directory).iterdir(): - if item.is_file() and item.suffix == ".ipynb": - fix_metadata(item) - -def recusively_fix_all_notebooks(directory): - if directory.exists(): - # iterate over all notebooks (with pathlib) - for item in Path(directory).rglob("*.ipynb"): - fix_metadata(item) - - -# def fix_rst_links(js_rst_file): - -# # core name without extension -# core_name = js_rst_file.stem -# to_replace = f"../lite/lab/?path=auto_examples/{core_name}.js" -# replacement = f"../lite/lab/?path=auto_examples/{core_name}.ipynb" - - -# # read the file -# with open(js_rst_file, "r") as f: -# lines = f.readlines() - -# # replace the links -# new_lines = [line.replace(to_replace, replacement) for line in lines] - -# # write the file -# with open(js_rst_file, "w") as f: -# f.writelines(new_lines) - -def fix_html_links(js_html_file): - - # core name without extension - core_name = js_html_file.stem - to_replace = f"../lite/lab/?path=auto_examples/{core_name}.js" - replacement = f"../lite/lab/?path=auto_examples/{core_name}.ipynb" - - - # read the file - with open(js_html_file, "r") as f: - lines = f.readlines() - - # replace the links - new_lines = [line.replace(to_replace, replacement) for line in lines] - - # write the file - with open(js_html_file, "w") as f: - f.writelines(new_lines) - -if __name__ == "__main__": - - from pathlib import Path - - this_dir = Path(__file__).parent - auto_examples_dir = this_dir / "auto_examples" - build_dir_html = this_dir / "_build" / "html" - lite_build_dir = build_dir_html / "lite" - - _download_dir = build_dir_html /"_downloads" - recusively_fix_all_notebooks(this_dir) - - lite_auto_examples_dir = lite_build_dir / "files"/ "auto_examples" - - # fix_all_notebooks(lite_auto_examples_dir) - # fix_all_notebooks(auto_examples_dir) - - auto_example_build_dir = build_dir_html / "auto_examples" - - # iterate over all rst files in auto_examples - # _dir - for item in auto_example_build_dir.iterdir(): - print(item) - if item.is_file() and item.suffix == ".html": - # only for js - if item.stem.startswith("js_"): - fix_html_links(item) - - - - lite_build_dir = this_dir / "_build"/ "html" / "lite" - lite_build_dir.mkdir(parents=True, exist_ok=True) - - - # get WASM_ENV_PREFIX from environment - env_location = Path(os.environ["WASM_ENV_PREFIX"]) - if not env_location.exists(): - raise ValueError(f"env_location {env_location} does not exist") - - - pyjs_dir = env_location / "lib_js" / "pyjs" - shutil.copy(pyjs_dir/"pyjs_runtime_browser.js", lite_build_dir / "extensions"/ "@jupyterlite"/"xeus"/"static/") - shutil.copy(pyjs_dir/"pyjs_runtime_browser.wasm", lite_build_dir / "extensions"/ "@jupyterlite"/"xeus"/"static/") - diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 0e475d9..0000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -myst_parser -sphinx_rtd_theme -jupyterlite-sphinx diff --git a/docs/try_from_js.md b/docs/try_from_js.md new file mode 100644 index 0000000..216fe8d --- /dev/null +++ b/docs/try_from_js.md @@ -0,0 +1,22 @@ + + +# Try pyjs from JavaScript + + + diff --git a/docs/try_from_py.md b/docs/try_from_py.md new file mode 100644 index 0000000..f0fc32a --- /dev/null +++ b/docs/try_from_py.md @@ -0,0 +1,7 @@ +# Try pyjs from Python + + diff --git a/environment-dev.yml b/environment-dev.yml index 1b8e967..5f15e0b 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -14,11 +14,8 @@ dependencies: - pyjs_code_runner >= 2.0.1 - exceptiongroup # documentation - - make - - sphinx - - sphinx_rtd_theme - - sphinx-gallery - - myst-parser + - mkdocs + - mkdocstrings - empack >=3.2.0 - jupyter_server # to enable contents - jupyterlite @@ -26,4 +23,7 @@ dependencies: - jupyterlite-sphinx - notebook >=7,<8 # to include the extension to switch between JupyterLab and Notebook # pyjs_code_runner dev deps - - hatchling \ No newline at end of file + - hatchling + - pip: + - mkdocs-sphinxs-dracular + - JLDracula diff --git a/examples/js_api_tour.js b/examples/js_api_tour.js index 3f8966c..4d8d47a 100644 --- a/examples/js_api_tour.js +++ b/examples/js_api_tour.js @@ -1,31 +1,29 @@ -// - -// -// %% Javascript API tour - -// -// %% Instantiate the pyjs wasm module - -importScripts("./pyjs_runtime_browser.js"); -let pyjs = await createModule({}); - -packages_json_url = origin + "/lite/xeus/kernels/xpython/empack_env_meta.json" -package_tarballs_root_url = origin + "/lite/xeus/kernel_packages/" - +// %% [markdown] +// Welcome to the tour of the PYJS JavaScript API. This notebook demonstrates how to use the PYJS JavaScript API to run Python code in the browser. + +// %% [code] + +// the url differs depending on the deployment +importScripts("../../../../xeus/bin/pyjs_runtime_browser.js"); +let locateFile = function(filename){ + if(filename.endsWith('pyjs_runtime_browser.wasm')){ + return `../../../../xeus/bin/pyjs_runtime_browser.wasm`; + } +}; +let pyjs = await createModule({locateFile:locateFile}); +packages_json_url = "../../../../xeus/kernels/xpython/empack_env_meta.json" +package_tarballs_root_url = "../../../../xeus/kernel_packages/" await pyjs.bootstrap_from_empack_packed_environment( packages_json_url, package_tarballs_root_url -) +); -// -// %% Hello world +// %% [code] pyjs.eval("print('hello world')"); -// -// %% Import a module - +// %% [code] pyjs.exec(` import numpy print(numpy.random.rand(3)) diff --git a/examples/py_api_tour.py b/examples/py_api_tour.py index bb85af4..c5d16df 100644 --- a/examples/py_api_tour.py +++ b/examples/py_api_tour.py @@ -1,17 +1,14 @@ -""" -A tour trough pyjs python API -============================== +# %% [markdown] +# A tour of the Python API +# ======================== -Here we will show you some of the features of pyjs. - - -""" +# %% [code] import pyjs -# %% +# %% [markdown] # Accessing the the JavaScript global object # ------------------------------------------ # The global object in javascript is accessible via `pyjs.js`. @@ -21,13 +18,14 @@ # We can for instance print the page origin like this: # +# %% [code] pyjs.js.location.origin # equivalent to the javascript expression `location.origin` / `globalThis.location.origin` -# %% +# %% [markdown] # Create JavaScript functions on the fly # -------------------------------------- # @@ -41,9 +39,11 @@ # # we can do the following: +# %% [code] # define the function js_function = pyjs.js.Function("a", "b", "return a + b") +# %% [code] # call the function result = js_function(1, 2) result \ No newline at end of file diff --git a/include/pyjs/pre_js/init.js b/include/pyjs/pre_js/init.js index 46f437a..5274c89 100644 --- a/include/pyjs/pre_js/init.js +++ b/include/pyjs/pre_js/init.js @@ -4,8 +4,6 @@ Module._is_initialized = false Module['init_phase_1'] = async function(prefix, python_version) { - console.log('init') - let version_str = `${python_version[0]}.${python_version[1]}`; // list of python objects we need to delete when cleaning up @@ -31,9 +29,6 @@ Module['init_phase_1'] = async function(prefix, python_version) { var side_path = `${prefix}/lib/python${version_str}/site-packages`; } - console.log('Module is', Module) - console.log('Module FS is', Module.FS) - if(!Module.FS.isDir(side_path)){ Module.FS.mkdir(side_path); } @@ -135,17 +130,10 @@ Module['init_phase_1'] = async function(prefix, python_version) { return ret['ret'] } }; - console.log("init phase 1 done") } Module['init_phase_2'] = function(prefix, python_version) { let default_scope = Module["default_scope"]; - console.log('init phase 2') - - console.log("import pyjs") - -// raw string - // make the python pyjs module easy available Module.exec(` @@ -157,7 +145,6 @@ except Exception as e: traceback.print_exc() raise e `) - console.log("imported pyjs") Module.py_pyjs = Module.eval("pyjs") Module._py_objects.push(Module.py_pyjs); diff --git a/include/pyjs/pre_js/load_pkg.js b/include/pyjs/pre_js/load_pkg.js index b3aafac..635f064 100644 --- a/include/pyjs/pre_js/load_pkg.js +++ b/include/pyjs/pre_js/load_pkg.js @@ -67,16 +67,19 @@ def _py_untar(tarball_path, target_dir): Module["_untar_from_python"] = untar_from_python -async function bootstrap_python(prefix, package_tarballs_root_url, python_package) { +async function bootstrap_python(prefix, package_tarballs_root_url, python_package, verbose = false) { // fetch python package let python_package_url = `${package_tarballs_root_url}/${python_package.filename}` - console.log(`fetching python package from ${python_package_url}`) + if (verbose) { + console.log(`fetching python package from ${python_package_url}`) + } let byte_array = await fetchByteArray(python_package_url) - const python_tarball_path = `/package_tarballs/${python_package.filename}`; - console.log(`extract ${python_tarball_path} (${byte_array.length} bytes)`) + if(verbose){ + console.log(`extract ${python_tarball_path} (${byte_array.length} bytes)`) + } Module.FS.writeFile(python_tarball_path, byte_array); Module._untar(python_tarball_path, prefix); @@ -98,9 +101,6 @@ Module["bootstrap_from_empack_packed_environment"] = async function skip_loading_shared_libs = false ) { - - - function splitPackages(packages) { // find package with name "python" and remove it from the list let python_package = undefined @@ -129,14 +129,19 @@ Module["bootstrap_from_empack_packed_environment"] = async function ( package_tarballs_root_url, python_is_ready_promise, - pkg + pkg, + verbose = false ) { let package_url = `${package_tarballs_root_url}/${pkg.filename}` - console.log(`fetching pkg ${pkg.name} from ${package_url}`) + if (verbose) { + console.log(`fetching pkg ${pkg.name} from ${package_url}`) + } let byte_array = await fetchByteArray(package_url) const tarball_path = `/package_tarballs/${pkg.filename}`; Module.FS.writeFile(tarball_path, byte_array); - console.log(`extract ${tarball_path} (${byte_array.length} bytes)`) + if(verbose){ + console.log(`extract ${tarball_path} (${byte_array.length} bytes)`) + } await python_is_ready_promise; return untar_from_python(tarball_path); } @@ -162,7 +167,7 @@ Module["bootstrap_from_empack_packed_environment"] = async function let python_is_ready_promise = bootstrap_python(prefix, package_tarballs_root_url, python_package); // create array with size - let shared_libs = await Promise.all(packages.map(pkg => fetchAndUntar(package_tarballs_root_url, python_is_ready_promise, pkg))) + let shared_libs = await Promise.all(packages.map(pkg => fetchAndUntar(package_tarballs_root_url, python_is_ready_promise, pkg, verbose))); Module.init_phase_2(prefix, python_version); diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..e0a3618 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,36 @@ +site_name: Pyjs docs +theme: + # name: material + name: dracula + + palette: + + # Palette toggle for automatic mode + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to system preference + + +plugins: +- mkdocstrings: + handlers: + python: + options: + allow_inspection: true + show_root_heading: true + show_source: false diff --git a/module/pyjs/__init__.py b/module/pyjs/__init__.py index 5c4dc74..68ae2a3 100644 --- a/module/pyjs/__init__.py +++ b/module/pyjs/__init__.py @@ -1,5 +1,6 @@ +import pyjs_core from pyjs_core import * - +from pyjs_core import JsValue from . core import * from . extend_js_val import * @@ -14,4 +15,5 @@ js = sys.modules["pyjs_core.js"] -_module = sys.modules["pyjs_core._module"] \ No newline at end of file +_module = sys.modules["pyjs_core._module"] + diff --git a/module/pyjs/convert.py b/module/pyjs/convert.py index ffc7999..79fa875 100644 --- a/module/pyjs/convert.py +++ b/module/pyjs/convert.py @@ -29,7 +29,7 @@ def get(self, js_val, default_py): self[js_val] = default_py return default_py, False -def array_converter(js_val, depth, cache, converter_options): +def _array_converter(js_val, depth, cache, converter_options): py_list, found_in_cache = cache.get(js_val, []) if found_in_cache: return py_list @@ -44,7 +44,7 @@ def array_converter(js_val, depth, cache, converter_options): py_list.append(py_item) return py_list -def object_converter(js_val, depth, cache, converter_options): +def _object_converter(js_val, depth, cache, converter_options): ret_dict, found_in_cache = cache.get(js_val, {}) if found_in_cache: @@ -68,7 +68,7 @@ def object_converter(js_val, depth, cache, converter_options): return ret_dict -def map_converter(js_val, depth, cache, converter_options): +def _map_converter(js_val, depth, cache, converter_options): ret_dict, found_in_cache = cache.get(js_val, {}) if found_in_cache: @@ -96,7 +96,7 @@ def map_converter(js_val, depth, cache, converter_options): return ret_dict -def set_converter(js_val, depth, cache, converter_options): +def _set_converter(js_val, depth, cache, converter_options): pyset, found_in_cache = cache.get(js_val, set()) if found_in_cache: return pyset @@ -108,20 +108,20 @@ def set_converter(js_val, depth, cache, converter_options): return pyset -def error_converter(js_val, depth, cache, converter_options, error_cls): +def _error_converter(js_val, depth, cache, converter_options, error_cls): return error_cls(err=js_val) -error_to_py_converters = dict( - Error=functools.partial(error_converter, error_cls=JsError), - InternalError=functools.partial(error_converter, error_cls=JsInternalError), - RangeError=functools.partial(error_converter, error_cls=JsRangeError), - ReferenceError=functools.partial(error_converter, error_cls=JsReferenceError), - SyntaxError=functools.partial(error_converter, error_cls=JsSyntaxError), - TypeError=functools.partial(error_converter, error_cls=JsTypeError), - URIError=functools.partial(error_converter, error_cls=JsURIError), +_error_to_py_converters = dict( + Error=functools.partial(_error_converter, error_cls=JsError), + InternalError=functools.partial(_error_converter, error_cls=JsInternalError), + RangeError=functools.partial(_error_converter, error_cls=JsRangeError), + ReferenceError=functools.partial(_error_converter, error_cls=JsReferenceError), + SyntaxError=functools.partial(_error_converter, error_cls=JsSyntaxError), + TypeError=functools.partial(_error_converter, error_cls=JsTypeError), + URIError=functools.partial(_error_converter, error_cls=JsURIError), ) # register converters -basic_to_py_converters = { +_basic_to_py_converters = { "0": lambda x: None, "1": lambda x, d, c, opts: None, "3": lambda x, d, c, opts: internal.as_string(x), @@ -129,11 +129,11 @@ def error_converter(js_val, depth, cache, converter_options, error_cls): "4": lambda x, d, c, opts: internal.as_int(x), "5": lambda x, d, c, opts: internal.as_float(x), "pyobject": lambda x, d, c, opts: internal.as_py_object(x), - "2": object_converter, - "Object": object_converter, - "Array": array_converter, - "Set": set_converter, - "Map": map_converter, + "2": _object_converter, + "Object": _object_converter, + "Array": _array_converter, + "Set": _set_converter, + "Map": _map_converter, "7": lambda x, d, c, opts: x, "Promise": lambda x, d, c, opts: x._to_future(), "ArrayBuffer": lambda x, d, c, opts: to_py(new(js.Uint8Array, x), d, c, opts), @@ -149,10 +149,10 @@ def error_converter(js_val, depth, cache, converter_options, error_cls): "BigUint64Array": lambda x, d, c, opts: internal.as_buffer(x), "Uint8ClampedArray": lambda x, d, c, opts: internal.as_buffer(x), } -basic_to_py_converters = {**basic_to_py_converters, **error_to_py_converters} +_basic_to_py_converters = {**_basic_to_py_converters, **_error_to_py_converters} def register_converter(cls_name, converter): - basic_to_py_converters[cls_name] = converter + _basic_to_py_converters[cls_name] = converter def to_py_json(js_val): @@ -163,9 +163,9 @@ def __init__(self, json=False, converters=None, default_converter=None): self.json = json if converters is None: - converters = basic_to_py_converters + converters = _basic_to_py_converters if default_converter is None: - default_converter = basic_to_py_converters["Object"] + default_converter = _basic_to_py_converters["Object"] self.converters = converters self.default_converter = default_converter @@ -187,9 +187,9 @@ def to_py(js_val, depth=0, cache=None, converter_options=None): def error_to_py(err): - default_converter = functools.partial(error_converter, error_cls=JsGenericError) + default_converter = functools.partial(_error_converter, error_cls=JsGenericError) converter_options = JsToPyConverterOptions( - converters=error_to_py_converters, default_converter=default_converter + converters=_error_to_py_converters, default_converter=default_converter ) return to_py(err, converter_options=converter_options) diff --git a/module/pyjs/extend_js_val.py b/module/pyjs/extend_js_val.py index 908fbd8..4f546b2 100644 --- a/module/pyjs/extend_js_val.py +++ b/module/pyjs/extend_js_val.py @@ -7,7 +7,6 @@ def extend_val(): - print("0") def _to_string(val): if _module._is_undefined(val): return "undefined" diff --git a/module/pyjs/webloop_LICENCE b/module/pyjs/webloop_LICENCE new file mode 100644 index 0000000..fa0086a --- /dev/null +++ b/module/pyjs/webloop_LICENCE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/module/pyjs/webloop_README.md b/module/pyjs/webloop_README.md new file mode 100644 index 0000000..9341d82 --- /dev/null +++ b/module/pyjs/webloop_README.md @@ -0,0 +1,2 @@ +the webloop implementation has been taken from pyodide (https://github.com/pyodide/pyodide/blob/main/src/py/pyodide/webloop.py) +and has been modified to fit pyjs \ No newline at end of file diff --git a/src/export_js_proxy.cpp b/src/export_js_proxy.cpp index 5dad23a..e465d6f 100644 --- a/src/export_js_proxy.cpp +++ b/src/export_js_proxy.cpp @@ -73,11 +73,14 @@ namespace pyjs } const auto type_string = wrapped_return_value["type_string"].as(); - if (type_string == "0") + + + + if(type_string.size() == 1 && type_string[0] == static_cast::type>(JsType::JS_NULL)) { return py::none(); } - else if (type_string == "1") + else if(type_string.size() == 1 && type_string[0] == static_cast::type>(JsType::JS_UNDEFINED)) { std::stringstream ss; ss << "has no attribute/key "; diff --git a/src/export_pyjs_module.cpp b/src/export_pyjs_module.cpp index 74a94e1..64239d8 100644 --- a/src/export_pyjs_module.cpp +++ b/src/export_pyjs_module.cpp @@ -15,14 +15,6 @@ namespace py = pybind11; namespace em = emscripten; -void pyjs_core_pseudo_init(py::module_&); -void pyjs_extend_js_val_pseudo_init(py::module_&); -void pyjs_error_handling_pseudo_init(py::module_&); -void pyjs_convert_pseudo_init(py::module_&); -void pyjs_convert_py_to_js_pseudo_init(py::module_&); -void pyjs_webloop_pseudo_init(py::module_&); -void pyjs_pyodide_polyfill_pseudo_init(py::module_&); - namespace pyjs { void export_pyjs_module(py::module_& pyjs_module) diff --git a/src/js_timestamp.cpp b/src/js_timestamp.cpp index 8537fd5..d0f72b3 100644 --- a/src/js_timestamp.cpp +++ b/src/js_timestamp.cpp @@ -1 +1 @@ -#define PYJS_JS_UTC_TIMESTAMP "2024-03-08 12:02:45.400994" \ No newline at end of file +#define PYJS_JS_UTC_TIMESTAMP "2024-03-12 09:13:17.929082" \ No newline at end of file diff --git a/stubs/pyjs_core/__init__.py b/stubs/pyjs_core/__init__.py new file mode 100644 index 0000000..d1467cb --- /dev/null +++ b/stubs/pyjs_core/__init__.py @@ -0,0 +1 @@ +"""this module is the core of pyjs, it contains the C++ implementation of most of the functions and classes used by pyjs.""" \ No newline at end of file diff --git a/stubs/pyjs_core/__init__.pyi b/stubs/pyjs_core/__init__.pyi new file mode 100644 index 0000000..c802773 --- /dev/null +++ b/stubs/pyjs_core/__init__.pyi @@ -0,0 +1,17 @@ +"""This module does blah blah.""" + +class JsValue: + """ + A class holding a javascript object/value. + """ + def ok_1(self, foo: list[str] = ...) -> None: ... + + def __init__(self, value: Any) -> None: + """ + Create a new JsValue from a python value. + Args: + value: The value to convert to a JsValue. + If the value is a primitive type (int, float, str, bool) it will be converted to the corresponding javascript type. + For any other python object, it will be converted to the javascript class `pyjs.pyobject` which is a wrapper around the python object on the javascript side. + """ + ... \ No newline at end of file