pyodide/docs/usage/faq.md

4.7 KiB

Frequently Asked Questions (FAQ)

How can I load external Python files in Pyodide?

The two possible solutions are,

  • include these files in a Python package, build a pure Python wheel with python setup.py bdist_wheel and {ref}load it with micropip <micropip-installing-from-arbitrary-urls>.
  • fetch the Python code as a string and evaluate it in Python,
    pyodide.runPython(await fetch('https://some_url/...'))
    

In both cases, files need to be served with a web server and cannot be loaded from local file system.

Why can't I load files from the local file system?

For security reasons Javascript in the browser is not allowed to load local data files. You need to serve them with a web-browser. Recently there is a Native File System API supported in Chrome but not in Firefox. There is a discussion about implementing it for Firefox here.

How can I change the behavior of {any}runPython <pyodide.runPython> and {any}runPythonAsync <pyodide.runPythonAsync>?

You can directly call Python functions from Javascript. For many purposes it makes sense to make your own Python function as an entrypoint and call that instead of using runPython. The definitions of {any}runPython <pyodide.runPython> and {any}runPythonAsync <pyodide.runPythonAsync> are very simple:

function runPython(code){
  pyodide.pyodide_py.eval_code(code, pyodide.globals);
}
async function runPythonAsync(code, messageCallback, errorCallback) {
  await pyodide.loadPackagesFromImports(code, messageCallback, errorCallback);
  return pyodide.runPython(code);
};

To make your own version of {any}runPython <pyodide.runPython>:

pyodide.runPython(`
  import pyodide
  def my_eval_code(code, ns):
    extra_info = None
    result = pyodide.eval_code(code, ns)
    return ns["extra_info"], result]
`)

function myRunPython(code){
  return pyodide.globals.get("my_eval_code")(code, pyodide.globals);
}

function myAsyncRunPython(code){
  await pyodide.loadPackagesFromImports(code, messageCallback, errorCallback);
  return pyodide.myRunPython(code, pyodide.globals);
}

Then pyodide.myRunPython("2+7") returns [None, 9] and pyodide.myRunPython("extra_info='hello' ; 2 + 2") returns ['hello', 4]. If you want to change which packages {any}pyodide.loadPackagesFromImports loads, you can monkey patch {any}pyodide.find_imports which takes code as an argument and returns a list of packages imported.

How can I execute code in a custom namespace?

The second argument to {any}pyodide.eval_code is a global namespace to execute the code in. The namespace is a Python dictionary.

let my_namespace = pyodide.globals.dict();
pyodide.pyodide_py.eval_code(`x = 1 + 1`, my_namespace);
pyodide.pyodide_py.eval_code(`y = x ** x`, my_namespace);
my_namespace.y; // ==> 4

This effectively runs the code in "module scope". Like the Python eval function you can provide a third argument to eval_code to specify a separate locals dict to run code in "function scope".

How to detect that code is run with Pyodide?

At run time, you can detect that a code is running with Pyodide using,

import sys

if "pyodide" in sys.modules:
   # running in Pyodide

More generally you can detect Python built with Emscripten (which includes Pyodide) with,

import platform

if platform.system() == 'Emscripten':
    # running in Pyodide or other Emscripten based build

This however will not work at build time (i.e. in a setup.py) due to the way the Pyodide build system works. It first compiles packages with the host compiler (e.g. gcc) and then re-runs the compilation commands with emsdk. So the setup.py is never run inside the Pyodide environement.

To detect Pyodide, at build time use,

import os

if "PYODIDE" in os.environ:
    # building for Pyodide

We used to use the environment variable PYODIDE_BASE_URL for this purpose, but this usage is deprecated.

How do I create custom Python packages from Javascript?

Put a collection of functions into a Javascript object and use {any}pyodide.registerJsModule: Javascript:

let my_module = {
  f : function(x){
    return x*x + 1;
  },
  g : function(x){
    console.log(`Calling g on argument ${x}`);
    return x;
  },
  submodule : {
    h : function(x) {
      return x*x - 1;
    },
    c  : 2,
  },
};
pyodide.registerJsModule("my_js_module", my_module);

You can import your package like a normal Python package:

import my_js_module
from my_js_module.submodule import h, c
assert my_js_module.f(7) == 50
assert h(9) == 80
assert c == 2