CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-pyodide

Python distribution for the browser and Node.js based on WebAssembly that enables running Python code with full JavaScript interoperability

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

interoperability.mddocs/

Interoperability

Seamless bidirectional conversion and communication between JavaScript and Python objects, modules, and functions.

Type Conversion

toPy

Convert JavaScript objects to Python equivalents.

function toPy(
  obj: any,
  options?: {
    depth?: number;
    defaultConverter?: (
      value: any,
      converter: (value: any) => any,
      cacheConversion: (input: any, output: any) => void
    ) => any;
  }
): any;

Parameters:

  • obj - JavaScript object to convert
  • options.depth - Maximum conversion depth (default: -1 for unlimited)
  • options.defaultConverter - Custom converter for objects with no default conversion

Returns: Python object (may be a PyProxy)

Module System

pyimport

Import Python modules and make them available in JavaScript.

function pyimport(moduleName: string): any;

Parameters:

  • moduleName - Python module name to import (supports dot notation)

Returns: PyProxy wrapping the imported Python module

registerJsModule

Register JavaScript objects as importable Python modules.

function registerJsModule(name: string, module: object): void;

Parameters:

  • name - Module name for Python imports
  • module - JavaScript object to expose as module

unregisterJsModule

Remove a previously registered JavaScript module.

function unregisterJsModule(name: string): void;

Parameters:

  • name - Module name to unregister

Global Access

globals

Access the Python global namespace.

const globals: PyProxy;

pyodide_py

Access the Python-side pyodide module.

const pyodide_py: PyProxy;

Usage Examples

Basic Type Conversion

// JavaScript to Python conversion
const jsArray = [1, 2, 3, 4, 5];
const pyList = pyodide.toPy(jsArray);

pyodide.globals.set("my_list", pyList);
pyodide.runPython(`
    print(type(my_list))  # <class 'list'>
    print(sum(my_list))   # 15
`);

// JavaScript objects become Python dictionaries
const jsObject = { name: "Alice", age: 30, active: true };
const pyDict = pyodide.toPy(jsObject);

pyodide.globals.set("user", pyDict);
pyodide.runPython(`
    print(user["name"])   # Alice
    print(user["age"])    # 30
    print(type(user))     # <class 'dict'>
`);

Advanced Type Conversion

// Nested objects with depth limit
const nestedObj = {
  level1: {
    level2: {
      level3: {
        data: "deep"
      }
    }
  }
};

const shallowConversion = pyodide.toPy(nestedObj, { depth: 2 });
// level3 remains as JavaScript proxy

// Custom converter for special objects
class CustomClass {
  constructor(value) {
    this.value = value;
  }
}

const customObj = new CustomClass(42);
const converted = pyodide.toPy(customObj, {
  defaultConverter: (obj, convert, cache) => {
    if (obj instanceof CustomClass) {
      return convert({ custom_value: obj.value });
    }
    return obj;
  }
});

Python Module Imports

// Import standard library modules
const math = pyodide.pyimport("math");
console.log(math.pi); // 3.141592653589793
console.log(math.sqrt(16)); // 4

const sys = pyodide.pyimport("sys");
console.log(sys.version);

// Import installed packages
await pyodide.loadPackage("numpy");
const np = pyodide.pyimport("numpy");

const array = np.array([1, 2, 3, 4, 5]);
console.log(array.shape); // [5]
console.log(array.sum()); // 15

// Import submodules
const pyplot = pyodide.pyimport("matplotlib.pyplot");
pyplot.figure();
pyplot.plot([1, 2, 3], [1, 4, 9]);

JavaScript Module Registration

// Register utility functions
const jsUtils = {
  formatNumber: (num, decimals = 2) => num.toFixed(decimals),
  getCurrentTime: () => new Date().toISOString(),
  logMessage: (msg) => console.log(`[JS] ${msg}`),
  
  // Nested module structure
  string: {
    reverse: (str) => str.split('').reverse().join(''),
    capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1)
  }
};

pyodide.registerJsModule("jsutils", jsUtils);

pyodide.runPython(`
    import jsutils
    
    # Use top-level functions
    print(jsutils.formatNumber(3.14159, 3))  # 3.142
    print(jsutils.getCurrentTime())
    jsutils.logMessage("Hello from Python!")
    
    # Use nested modules
    from jsutils.string import reverse, capitalize
    print(reverse("hello"))     # olleh
    print(capitalize("world"))  # World
`);

Working with JavaScript APIs

// Register browser APIs for Python use
pyodide.registerJsModule("browser", {
  fetch: fetch,
  localStorage: {
    getItem: (key) => localStorage.getItem(key),
    setItem: (key, value) => localStorage.setItem(key, value),
    removeItem: (key) => localStorage.removeItem(key)
  },
  document: {
    getElementById: (id) => document.getElementById(id),
    createElement: (tag) => document.createElement(tag)
  }
});

pyodide.runPython(`
    from browser import fetch, localStorage, document
    
    # Use fetch API
    response = await fetch("https://api.example.com/data")
    data = await response.json()
    
    # Use localStorage
    localStorage.setItem("user_data", "some_value")
    stored = localStorage.getItem("user_data")
    
    # Use DOM APIs
    element = document.getElementById("output")
    if element:
        element.innerHTML = "Updated from Python!"
`);

Bidirectional Communication

// Python functions callable from JavaScript
pyodide.runPython(`
    def python_processor(data):
        # Process data in Python
        return [x * 2 for x in data if x > 0]
    
    def async_python_function():
        import asyncio
        await asyncio.sleep(0.1)
        return {"processed": True, "timestamp": "2024-01-01"}
`);

const pythonProcessor = pyodide.globals.get("python_processor");
const result = pythonProcessor([1, -2, 3, -4, 5]);
console.log(result); // [2, 6, 10]

const asyncPythonFunc = pyodide.globals.get("async_python_function");
const asyncResult = await asyncPythonFunc();
console.log(asyncResult); // {processed: true, timestamp: "2024-01-01"}

Global Namespace Management

// Set values in Python global namespace
pyodide.globals.set("api_key", "your-secret-key");
pyodide.globals.set("config", pyodide.toPy({
  debug: true,
  timeout: 5000,
  endpoints: ["api1", "api2"]
}));

// Get values from Python global namespace
pyodide.runPython(`
    import os
    os.environ["API_KEY"] = api_key
    
    processed_config = {
        "debug_mode": config["debug"],
        "timeout_ms": config["timeout"],
        "endpoint_count": len(config["endpoints"])
    }
`);

const processedConfig = pyodide.globals.get("processed_config");
console.log(processedConfig.toJs()); // Convert PyProxy to JavaScript

Class and Object Interoperability

// JavaScript class available in Python
class JavaScriptCalculator {
  constructor() {
    this.history = [];
  }
  
  add(a, b) {
    const result = a + b;
    this.history.push(`${a} + ${b} = ${result}`);
    return result;
  }
  
  getHistory() {
    return this.history;
  }
}

pyodide.registerJsModule("calculator", {
  Calculator: JavaScriptCalculator
});

pyodide.runPython(`
    from calculator import Calculator
    
    calc = Calculator()
    result1 = calc.add(5, 3)
    result2 = calc.add(10, 7)
    
    print("Results:", result1, result2)
    print("History:", calc.getHistory())
`);

// Python class instances in JavaScript
pyodide.runPython(`
    class PythonCounter:
        def __init__(self, start=0):
            self.value = start
        
        def increment(self, step=1):
            self.value += step
            return self.value
        
        def get_value(self):
            return self.value
    
    counter = PythonCounter(10)
`);

const pythonCounter = pyodide.globals.get("counter");
console.log(pythonCounter.increment(5)); // 15
console.log(pythonCounter.get_value()); // 15

Error Handling Across Languages

// JavaScript errors in Python
const faultyJsFunction = () => {
  throw new Error("JavaScript error occurred");
};

pyodide.registerJsModule("faulty", { faultyFunction: faultyJsFunction });

try {
  pyodide.runPython(`
      from faulty import faultyFunction
      faultyFunction()  # This will propagate the JS error
  `);
} catch (error) {
  console.log("Caught JS error in Python context:", error.message);
}

// Python errors in JavaScript
pyodide.runPython(`
    def failing_python_function():
        raise ValueError("Python error occurred")
`);

const failingPyFunction = pyodide.globals.get("failing_python_function");

try {
  failingPyFunction();
} catch (error) {
  console.log("Caught Python error in JS context:", error.message);
}

Memory Management

// Proper cleanup of PyProxy objects
const largePythonObject = pyodide.runPython(`
    # Create large object
    large_data = list(range(1000000))
    {"data": large_data, "size": len(large_data)}
`);

// Use the object
console.log(largePythonObject.get("size"));

// Clean up when done
largePythonObject.destroy();

// Automatic cleanup with context managers
function withPyObject(obj, callback) {
  try {
    return callback(obj);
  } finally {
    if (obj && obj.destroy) {
      obj.destroy();
    }
  }
}

const result = withPyObject(
  pyodide.runPython("{'result': 42}"),
  (obj) => obj.get("result")
);
console.log(result); // 42

Install with Tessl CLI

npx tessl i tessl/npm-pyodide

docs

advanced-features.md

code-execution.md

ffi.md

file-system.md

index.md

interoperability.md

io-streams.md

package-management.md

runtime-loading.md

tile.json