From 9742e501cf8267f0e2116a57d86f844f9ec6dd8b Mon Sep 17 00:00:00 2001 From: DerThorsten Date: Wed, 26 Mar 2025 09:45:41 +0100 Subject: [PATCH 1/3] avoid name clashes --- examples/README.rst | 4 - examples/js_api_tour.js | 152 ------------------------- examples/py_api_tour.py | 107 ----------------- include/pyjs/pre_js/dynload/dynload.js | 24 ++-- 4 files changed, 15 insertions(+), 272 deletions(-) delete mode 100644 examples/README.rst delete mode 100644 examples/js_api_tour.js delete mode 100644 examples/py_api_tour.py diff --git a/examples/README.rst b/examples/README.rst deleted file mode 100644 index a37a556..0000000 --- a/examples/README.rst +++ /dev/null @@ -1,4 +0,0 @@ -Example Code -================== - -Below is a gallery of examples diff --git a/examples/js_api_tour.js b/examples/js_api_tour.js deleted file mode 100644 index 5e5ecc6..0000000 --- a/examples/js_api_tour.js +++ /dev/null @@ -1,152 +0,0 @@ -// %% [markdown] -// # pyjs JavaScript API Tour -// 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. - -// %% [markdown] -// # Loading the pyjs module - -// %% [code] -// load the pyjs runtime by importing the pyjs_runtime_browser.js file -// the url differs depending on the deployment -importScripts("../../../../xeus/bin/pyjs_runtime_browser.js"); - -// the locateFile function is used to locate the wasm file -// which sits next to the pyjs_runtime_browser.js file -// in thism deployment -let locateFile = function(filename){ - if(filename.endsWith('pyjs_runtime_browser.wasm')){ - return `../../../../xeus/bin/pyjs_runtime_browser.wasm`; - } -}; - -// the createModule function in from the pyjs runtime -// is used to create the pyjs module -let pyjs = await createModule({locateFile:locateFile}); - -// load the python packages (includung the python standard library) -// from the empack environment -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 -); - - -// %% [markdown] -// # Evaluating Python expressions: -// From now on, you can use the pyjs module to run python code. -// Here we use "eval" to evaluate a python expression - -// %% [code] -pyjs.eval("print('hello world')"); - -// %% [markdown] -// # Executing Python code -// Here we execute a python code block using "exec" function - -// %% [code] -pyjs.exec(` -import numpy -print(numpy.random.rand(3)) -`); - -// %% [markdown] -// # Executing Python code and returning the last expression -// Here we execute a python code block using "exec" function and return the last expression. - - -// %% [code] -let rand_arr = pyjs.exec_eval(` -import numpy -numpy.random.rand(2,4,3) -`); -rand_arr.delete() - -// %% [markdown] - -// # Using the pyobject class -// When a python object is returned, it is wrapped in a pyobject class. -// This class provides methods to interact with the python object. -// Any created instance of the pyobject class needs to be deleted using the "delete" method. - -// %% [code] -// create a numpy array with [0,1,2,3] as value -let arr = pyjs.exec_eval(` -import numpy -numpy.arange(0,4) -`); - -// get the shape -let arr_shape = arr.shape - -// get the square function -const square_func = pyjs.eval('numpy.square') - -// any function call / __call__ like operator on the python side -// is called via "py_call" -const res = square_func.py_call(arr) - -// print the result -console.log(res) - -// delete all the created pyobjects -res.delete() -square_func.delete() -arr_shape.delete() -arr.delete() - -// %% [markdown] -// # Type Conversion -// pyjs provides methods to convert between JavaScript and Python types. -// ## Explicit conversion - -// %% [code] -// python list to javascript array -const py_list = pyjs.eval("[1,2,3]") -const js_arr = pyjs.to_js(py_list) -py_list.delete() -console.log(js_arr) - -// python dict to js map -const py_dict = pyjs.eval("dict(a=1, b='fobar')") -const js_map = pyjs.to_js(py_dict) -py_dict.delete() - -// values -console.log(Array.from(js_map.keys())) -// keys -console.log(Array.from(js_map.values())) - -// %% [markdown] -// ## Implicit conversion -// Fundamental types are automatically converted between Python and JavaScript. -// This includes numbers, strings, booleans and null. - -// %% [code] -// sum is a plain javascript number -const sum = pyjs.eval("sum([i for i in range(0,101)])") -sum - -// %% [code] -// is_true is a plain javascript boolean -const is_true = pyjs.eval("sum([i for i in range(0,101)]) == 5050") -is_true - -// %% [code] -// none will be undefined -let none = pyjs.eval('None') -console.log(none) - -// %% [markdown] -// # Asynchronous execution -// The pyjs module provides a way to run python code asynchronously using the "exec_async" function. - -// %% [code] -const py_code = ` -import asyncio -await asyncio.sleep(2) -sum([i for i in range(100)]) -` -result = await pyjs.async_exec_eval(py_code) -console.log(result); \ No newline at end of file diff --git a/examples/py_api_tour.py b/examples/py_api_tour.py deleted file mode 100644 index d680b87..0000000 --- a/examples/py_api_tour.py +++ /dev/null @@ -1,107 +0,0 @@ -# %% [markdown] -# # A tour of the Python API - - -# %% [code] -import pyjs - -# %% [markdown] -# # Accessing the the JavaScript global object -# -# The global object in javascript is accessible via `pyjs.js`. -# Since this example runs **not** in the main thread, but only -# in a worker thread, we can not acces the window object, but -# only whats available in the workers global scope / globalThis. -# 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 - -# %% [code] -# define the function -js_function = pyjs.js.Function("a", "b", "return a + b") - -# %% [code] -# call the function -result = js_function(1, 2) -result - -# %% [markdown] -# # Type conversion -# -# Pyjs allows to convert between python and javascript types. -# -# ## Explicit conversion - -# %% [code] -# convert a python list to a javascript array -js_list = pyjs.js.eval("[1,2,3]") -# pylist is a vanilla python list -py_list = pyjs.to_py(js_list) -py_list - -# %% [code] -# convert a nested javascript object to a python dict -js_nested_object = pyjs.js.Function("return{ foo:42,bar:[1,{a:1,b:2}]};")() -py_dict = pyjs.to_py(js_nested_object) -py_dict - -# %% [markdown] -# ### Custom converters -# -# Pyjs allows to register custom converters for specific javascript classes. - -# %% [code] -# Define JavaScript Rectangle class -# and create an instance of it -rectangle = pyjs.js.Function(""" - class Rectangle { - constructor(height, width) { - this.height = height; - this.width = width; - } - } - return new Rectangle(10,20) -""")() - -# A Python Rectangle class -class Rectangle(object): - def __init__(self, height, width): - self.height = height - self.width = width - -# the custom converter -def rectangle_converter(js_val, depth, cache, converter_options): - return Rectangle(js_val.height, js_val.width) - -# Register the custom converter -pyjs.register_converter("Rectangle", rectangle_converter) - -# Convert the JavaScript Rectangle to a Python Rectangle -r = pyjs.to_py(rectangle) -assert isinstance(r, Rectangle) -assert r.height == 10 -assert r.width == 20 - -# %% [markdown] -# ## Implicit conversion -# ## Implicit conversion -# Fundamental types are automatically converted between Javascript and Python. -# This includes numbers, strings, booleans and undefined and null. - -# %% [code] -# this will convert the javascript string to a python string -origin = pyjs.js.location.origin -assert isinstance(origin, str) - -# or results from a javascript function -js_function = pyjs.js.Function("a", "b", "return a + b") -result = js_function("hello", "world") -assert isinstance(js_function("hello", "world"), str) -assert isinstance(js_function(1, 2), int) -assert isinstance(js_function(1.5, 2.0), float) -assert isinstance(js_function(1.5, 2.5), int) # (!) \ No newline at end of file diff --git a/include/pyjs/pre_js/dynload/dynload.js b/include/pyjs/pre_js/dynload/dynload.js index 1629367..8ee2482 100644 --- a/include/pyjs/pre_js/dynload/dynload.js +++ b/include/pyjs/pre_js/dynload/dynload.js @@ -202,13 +202,18 @@ async function loadDynlib(prefix, lib, global, searchDirs, readFileFunc) { const fs = createDynlibFS(prefix, lib, searchDirs, readFileFunc); const libName = Module.PATH.basename(lib); - //console.log(`load ${lib} (${libName}) `) + + // contains cpython-3 and with wasm32-emscripten + const is_cython_lib = libName.includes("cpython-3") && libName.includes("wasm32-emscripten"); + + // load cython library from full path + const load_name = is_cython_lib ? lib : libName; - await Module.loadDynamicLibrary(libName, { + await Module.loadDynamicLibrary(load_name, { loadAsync: true, nodelete: true, allowUndefined: true, - global: global, + global: global && !is_cython_lib, fs: fs }) @@ -219,13 +224,14 @@ async function loadDynlib(prefix, lib, global, searchDirs, readFileFunc) { console.execption(`Failed to load ${libName} from ${lib} LDSO not found`); } - if (!dsoOnlyLibName) { + if(!is_cython_lib){ + if (!dsoOnlyLibName) { + Module.LDSO.loadedLibsByName[libName] = dsoFullLib + } - Module.LDSO.loadedLibsByName[libName] = dsoFullLib - } - - if(!dsoFullLib){ - Module.LDSO.loadedLibsByName[lib] = dsoOnlyLibName; + if(!dsoFullLib){ + Module.LDSO.loadedLibsByName[lib] = dsoOnlyLibName; + } } } finally { releaseDynlibLock(); From 955f206f6f6ff3345632e9a54c59a44aebe2dac1 Mon Sep 17 00:00:00 2001 From: DerThorsten Date: Wed, 26 Mar 2025 09:51:49 +0100 Subject: [PATCH 2/3] playwright --- environment-dev_3.1.45.yml | 2 +- environment-dev_3.1.58.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/environment-dev_3.1.45.yml b/environment-dev_3.1.45.yml index c91225b..2d5d5c8 100644 --- a/environment-dev_3.1.45.yml +++ b/environment-dev_3.1.45.yml @@ -8,7 +8,7 @@ dependencies: - yarn - click - empack >=3.0.1 - - microsoft::playwright + - microsoft::playwright <= 1.50 - ninja - nodejs - pyjs_code_runner >= 2.0.1 diff --git a/environment-dev_3.1.58.yml b/environment-dev_3.1.58.yml index 1525559..6e520e8 100644 --- a/environment-dev_3.1.58.yml +++ b/environment-dev_3.1.58.yml @@ -8,7 +8,7 @@ dependencies: - yarn - click - empack >=3.0.1 - - microsoft::playwright + - microsoft::playwright <= 1.50 - ninja - nodejs - pyjs_code_runner >= 2.0.1 From 8cab69afbb266e9cab604d13b5c94cddfa050f7a Mon Sep 17 00:00:00 2001 From: DerThorsten Date: Wed, 26 Mar 2025 10:02:23 +0100 Subject: [PATCH 3/3] restore --- examples/README.rst | 4 ++ examples/js_api_tour.js | 152 ++++++++++++++++++++++++++++++++++++++++ examples/py_api_tour.py | 107 ++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 examples/README.rst create mode 100644 examples/js_api_tour.js create mode 100644 examples/py_api_tour.py diff --git a/examples/README.rst b/examples/README.rst new file mode 100644 index 0000000..a37a556 --- /dev/null +++ b/examples/README.rst @@ -0,0 +1,4 @@ +Example Code +================== + +Below is a gallery of examples diff --git a/examples/js_api_tour.js b/examples/js_api_tour.js new file mode 100644 index 0000000..5e5ecc6 --- /dev/null +++ b/examples/js_api_tour.js @@ -0,0 +1,152 @@ +// %% [markdown] +// # pyjs JavaScript API Tour +// 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. + +// %% [markdown] +// # Loading the pyjs module + +// %% [code] +// load the pyjs runtime by importing the pyjs_runtime_browser.js file +// the url differs depending on the deployment +importScripts("../../../../xeus/bin/pyjs_runtime_browser.js"); + +// the locateFile function is used to locate the wasm file +// which sits next to the pyjs_runtime_browser.js file +// in thism deployment +let locateFile = function(filename){ + if(filename.endsWith('pyjs_runtime_browser.wasm')){ + return `../../../../xeus/bin/pyjs_runtime_browser.wasm`; + } +}; + +// the createModule function in from the pyjs runtime +// is used to create the pyjs module +let pyjs = await createModule({locateFile:locateFile}); + +// load the python packages (includung the python standard library) +// from the empack environment +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 +); + + +// %% [markdown] +// # Evaluating Python expressions: +// From now on, you can use the pyjs module to run python code. +// Here we use "eval" to evaluate a python expression + +// %% [code] +pyjs.eval("print('hello world')"); + +// %% [markdown] +// # Executing Python code +// Here we execute a python code block using "exec" function + +// %% [code] +pyjs.exec(` +import numpy +print(numpy.random.rand(3)) +`); + +// %% [markdown] +// # Executing Python code and returning the last expression +// Here we execute a python code block using "exec" function and return the last expression. + + +// %% [code] +let rand_arr = pyjs.exec_eval(` +import numpy +numpy.random.rand(2,4,3) +`); +rand_arr.delete() + +// %% [markdown] + +// # Using the pyobject class +// When a python object is returned, it is wrapped in a pyobject class. +// This class provides methods to interact with the python object. +// Any created instance of the pyobject class needs to be deleted using the "delete" method. + +// %% [code] +// create a numpy array with [0,1,2,3] as value +let arr = pyjs.exec_eval(` +import numpy +numpy.arange(0,4) +`); + +// get the shape +let arr_shape = arr.shape + +// get the square function +const square_func = pyjs.eval('numpy.square') + +// any function call / __call__ like operator on the python side +// is called via "py_call" +const res = square_func.py_call(arr) + +// print the result +console.log(res) + +// delete all the created pyobjects +res.delete() +square_func.delete() +arr_shape.delete() +arr.delete() + +// %% [markdown] +// # Type Conversion +// pyjs provides methods to convert between JavaScript and Python types. +// ## Explicit conversion + +// %% [code] +// python list to javascript array +const py_list = pyjs.eval("[1,2,3]") +const js_arr = pyjs.to_js(py_list) +py_list.delete() +console.log(js_arr) + +// python dict to js map +const py_dict = pyjs.eval("dict(a=1, b='fobar')") +const js_map = pyjs.to_js(py_dict) +py_dict.delete() + +// values +console.log(Array.from(js_map.keys())) +// keys +console.log(Array.from(js_map.values())) + +// %% [markdown] +// ## Implicit conversion +// Fundamental types are automatically converted between Python and JavaScript. +// This includes numbers, strings, booleans and null. + +// %% [code] +// sum is a plain javascript number +const sum = pyjs.eval("sum([i for i in range(0,101)])") +sum + +// %% [code] +// is_true is a plain javascript boolean +const is_true = pyjs.eval("sum([i for i in range(0,101)]) == 5050") +is_true + +// %% [code] +// none will be undefined +let none = pyjs.eval('None') +console.log(none) + +// %% [markdown] +// # Asynchronous execution +// The pyjs module provides a way to run python code asynchronously using the "exec_async" function. + +// %% [code] +const py_code = ` +import asyncio +await asyncio.sleep(2) +sum([i for i in range(100)]) +` +result = await pyjs.async_exec_eval(py_code) +console.log(result); \ No newline at end of file diff --git a/examples/py_api_tour.py b/examples/py_api_tour.py new file mode 100644 index 0000000..d680b87 --- /dev/null +++ b/examples/py_api_tour.py @@ -0,0 +1,107 @@ +# %% [markdown] +# # A tour of the Python API + + +# %% [code] +import pyjs + +# %% [markdown] +# # Accessing the the JavaScript global object +# +# The global object in javascript is accessible via `pyjs.js`. +# Since this example runs **not** in the main thread, but only +# in a worker thread, we can not acces the window object, but +# only whats available in the workers global scope / globalThis. +# 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 + +# %% [code] +# define the function +js_function = pyjs.js.Function("a", "b", "return a + b") + +# %% [code] +# call the function +result = js_function(1, 2) +result + +# %% [markdown] +# # Type conversion +# +# Pyjs allows to convert between python and javascript types. +# +# ## Explicit conversion + +# %% [code] +# convert a python list to a javascript array +js_list = pyjs.js.eval("[1,2,3]") +# pylist is a vanilla python list +py_list = pyjs.to_py(js_list) +py_list + +# %% [code] +# convert a nested javascript object to a python dict +js_nested_object = pyjs.js.Function("return{ foo:42,bar:[1,{a:1,b:2}]};")() +py_dict = pyjs.to_py(js_nested_object) +py_dict + +# %% [markdown] +# ### Custom converters +# +# Pyjs allows to register custom converters for specific javascript classes. + +# %% [code] +# Define JavaScript Rectangle class +# and create an instance of it +rectangle = pyjs.js.Function(""" + class Rectangle { + constructor(height, width) { + this.height = height; + this.width = width; + } + } + return new Rectangle(10,20) +""")() + +# A Python Rectangle class +class Rectangle(object): + def __init__(self, height, width): + self.height = height + self.width = width + +# the custom converter +def rectangle_converter(js_val, depth, cache, converter_options): + return Rectangle(js_val.height, js_val.width) + +# Register the custom converter +pyjs.register_converter("Rectangle", rectangle_converter) + +# Convert the JavaScript Rectangle to a Python Rectangle +r = pyjs.to_py(rectangle) +assert isinstance(r, Rectangle) +assert r.height == 10 +assert r.width == 20 + +# %% [markdown] +# ## Implicit conversion +# ## Implicit conversion +# Fundamental types are automatically converted between Javascript and Python. +# This includes numbers, strings, booleans and undefined and null. + +# %% [code] +# this will convert the javascript string to a python string +origin = pyjs.js.location.origin +assert isinstance(origin, str) + +# or results from a javascript function +js_function = pyjs.js.Function("a", "b", "return a + b") +result = js_function("hello", "world") +assert isinstance(js_function("hello", "world"), str) +assert isinstance(js_function(1, 2), int) +assert isinstance(js_function(1.5, 2.0), float) +assert isinstance(js_function(1.5, 2.5), int) # (!) \ No newline at end of file