Python distribution for the browser and Node.js based on WebAssembly that enables running Python code with full JavaScript interoperability
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pyodide's Foreign Function Interface provides seamless interoperability between JavaScript and Python, enabling automatic type conversion and proxy objects for cross-language operation.
The FFI module exports type classes and utilities for working with Python objects from JavaScript.
import { ffi } from "pyodide/ffi";
const ffi: {
PyProxy: typeof PyProxy;
PyProxyWithLength: typeof PyProxyWithLength;
PyProxyWithGet: typeof PyProxyWithGet;
PyProxyWithSet: typeof PyProxyWithSet;
PyProxyWithHas: typeof PyProxyWithHas;
PyDict: typeof PyDict;
PyIterable: typeof PyIterable;
PyAsyncIterable: typeof PyAsyncIterable;
PyIterator: typeof PyIterator;
PyAsyncIterator: typeof PyAsyncIterator;
PyGenerator: typeof PyGenerator;
PyAsyncGenerator: typeof PyAsyncGenerator;
PyAwaitable: typeof PyAwaitable;
PyCallable: typeof PyCallable;
PyBuffer: typeof PyBuffer;
PyBufferView: typeof PyBufferView;
PySequence: typeof PySequence;
PyMutableSequence: typeof PyMutableSequence;
PythonError: typeof PythonError;
};Base proxy type for all Python objects accessible from JavaScript.
interface PyProxy {
/** Check if object has been destroyed */
readonly destroyed: boolean;
/** Destroy the proxy and release Python reference */
destroy(): void;
/** Convert to JavaScript object */
toJs(options?: ConversionOptions): any;
/** Create a copy with new conversion options */
copy(): PyProxy;
/** Get string representation (str() in Python) */
toString(): string;
/** Get detailed representation (repr() in Python) */
supportsLength(): this is PyProxyWithLength;
supportsGet(): this is PyProxyWithGet;
supportsSet(): this is PyProxyWithSet;
supportsHas(): this is PyProxyWithHas;
}Proxy for Python objects that support len().
interface PyProxyWithLength extends PyProxy {
readonly length: number;
}Proxy for Python objects that support item access (obj[key]).
interface PyProxyWithGet extends PyProxy {
get(key: any): any;
}Proxy for Python objects that support item assignment (obj[key] = value).
interface PyProxyWithSet extends PyProxy {
set(key: any, value: any): void;
delete(key: any): void;
}Proxy for Python objects that support membership testing (key in obj).
interface PyProxyWithHas extends PyProxy {
has(key: any): boolean;
}Proxy for Python dictionaries with JavaScript Map-like interface.
interface PyDict extends PyProxy {
get(key: any): any;
set(key: any, value: any): void;
delete(key: any): boolean;
has(key: any): boolean;
keys(): PyProxy;
values(): PyProxy;
items(): PyProxy;
}Usage Example:
// Create Python dict and work with it
const pythonDict = pyodide.runPython(`
{'name': 'Alice', 'age': 30, 'city': 'New York'}
`);
console.log(pythonDict.get('name')); // 'Alice'
pythonDict.set('age', 31);
console.log(pythonDict.has('city')); // true
pythonDict.delete('city');
// Convert to JavaScript object
const jsObject = pythonDict.toJs();
console.log(jsObject); // {name: 'Alice', age: 31}Proxy for Python sequences (lists, tuples, strings) with array-like interface.
interface PySequence extends PyProxy {
readonly length: number;
get(index: number): any;
slice(start?: number, stop?: number): PySequence;
}Proxy for mutable Python sequences (lists) with modification capabilities.
interface PyMutableSequence extends PySequence {
set(index: number, value: any): void;
delete(index: number): void;
append(value: any): void;
extend(iterable: any): void;
insert(index: number, value: any): void;
pop(index?: number): any;
reverse(): void;
sort(compare?: (a: any, b: any) => number): void;
}Usage Example:
// Create Python list and manipulate it
const pythonList = pyodide.runPython('[1, 2, 3, 4, 5]');
console.log(pythonList.length); // 5
console.log(pythonList.get(0)); // 1
pythonList.append(6);
pythonList.insert(0, 0);
console.log(pythonList.toJs()); // [0, 1, 2, 3, 4, 5, 6]
const sliced = pythonList.slice(1, 4);
console.log(sliced.toJs()); // [1, 2, 3]Proxy for Python iterable objects.
interface PyIterable extends PyProxy {
[Symbol.iterator](): Iterator<any>;
}Proxy for Python async iterable objects.
interface PyAsyncIterable extends PyProxy {
[Symbol.asyncIterator](): AsyncIterator<any>;
}Proxy for Python iterator objects.
interface PyIterator extends PyProxy {
next(): IteratorResult<any>;
[Symbol.iterator](): PyIterator;
}Proxy for Python async iterator objects.
interface PyAsyncIterator extends PyProxy {
next(): Promise<IteratorResult<any>>;
[Symbol.asyncIterator](): PyAsyncIterator;
}Proxy for Python callable objects (functions, methods, classes).
interface PyCallable extends PyProxy {
(...args: any[]): any;
callKwargs(...args: any[], kwargs?: { [key: string]: any }): any;
apply(thisArg: any, args: any[]): any;
call(thisArg: any, ...args: any[]): any;
}Usage Example:
// Call Python functions from JavaScript
const mathFunc = pyodide.runPython(`
def calculate(x, y, operation='add'):
if operation == 'add':
return x + y
elif operation == 'multiply':
return x * y
return 0
calculate
`);
// Call with positional arguments
const result1 = mathFunc(5, 3);
console.log(result1); // 8
// Call with keyword arguments
const result2 = mathFunc.callKwargs(5, 3, { operation: 'multiply' });
console.log(result2); // 15Proxy for Python generator objects.
interface PyGenerator extends PyProxy {
next(value?: any): IteratorResult<any>;
return(value?: any): IteratorResult<any>;
throw(error?: any): IteratorResult<any>;
[Symbol.iterator](): PyGenerator;
}Proxy for Python async generator objects.
interface PyAsyncGenerator extends PyProxy {
next(value?: any): Promise<IteratorResult<any>>;
return(value?: any): Promise<IteratorResult<any>>;
throw(error?: any): Promise<IteratorResult<any>>;
[Symbol.asyncIterator](): PyAsyncGenerator;
}Proxy for Python awaitable objects (coroutines, futures).
interface PyAwaitable extends PyProxy {
then<TResult1 = any, TResult2 = never>(
onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>,
onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>
): Promise<TResult1 | TResult2>;
}Usage Example:
// Work with async Python functions
const asyncFunc = pyodide.runPython(`
import asyncio
async def fetch_data():
await asyncio.sleep(0.1)
return "Data fetched!"
fetch_data
`);
// Await Python coroutine
const result = await asyncFunc();
console.log(result); // "Data fetched!"Proxy for Python buffer protocol objects.
interface PyBuffer extends PyProxy {
readonly byteLength: number;
readonly format: string;
readonly itemsize: number;
readonly ndim: number;
readonly readonly: boolean;
readonly shape: number[] | null;
readonly strides: number[] | null;
getBuffer(order?: 'C' | 'F'): ArrayBuffer;
}A view into a Python buffer as a typed array.
interface PyBufferView extends PyProxy {
readonly buffer: ArrayBuffer;
readonly byteLength: number;
readonly byteOffset: number;
readonly length: number;
readonly BYTES_PER_ELEMENT: number;
}Usage Example:
// Work with NumPy arrays through buffer interface
await pyodide.loadPackage('numpy');
const npArray = pyodide.runPython(`
import numpy as np
np.array([1, 2, 3, 4, 5], dtype=np.float32)
`);
console.log('Array shape:', npArray.shape);
console.log('Array dtype:', npArray.format);
// Get buffer view
const buffer = npArray.getBuffer();
const float32View = new Float32Array(buffer);
console.log('JavaScript view:', float32View); // [1, 2, 3, 4, 5]JavaScript representation of Python exceptions.
interface PythonError extends Error {
readonly type: string;
readonly value: PyProxy;
readonly traceback: PyProxy;
}Usage Example:
import { ffi } from "pyodide/ffi";
try {
pyodide.runPython(`
raise ValueError("Something went wrong!")
`);
} catch (error) {
if (error instanceof ffi.PythonError) {
console.log('Python error type:', error.type);
console.log('Python error message:', error.message);
console.log('Python traceback:', error.traceback.toString());
}
}interface ConversionOptions {
/** Maximum depth for recursive conversion */
depth?: number;
/** Custom converter for objects without default conversion */
defaultConverter?: (
value: any,
converter: (value: any) => any,
cacheConversion: (input: any, output: any) => void
) => any;
/** Convert Python dictionaries to JavaScript Maps */
dictConverter?: (
items: Iterable<[key: string, value: any]>
) => Map<string, any> | object;
}Use FFI classes for runtime type checking:
import { ffi } from "pyodide/ffi";
function processData(pythonObject) {
if (pythonObject instanceof ffi.PyDict) {
console.log('Processing Python dictionary...');
for (const [key, value] of pythonObject.items()) {
console.log(`${key}: ${value}`);
}
} else if (pythonObject instanceof ffi.PySequence) {
console.log('Processing Python sequence...');
for (let i = 0; i < pythonObject.length; i++) {
console.log(`Item ${i}: ${pythonObject.get(i)}`);
}
} else if (pythonObject instanceof ffi.PyCallable) {
console.log('Processing Python function...');
const result = pythonObject();
console.log('Function result:', result);
}
}Install with Tessl CLI
npx tessl i tessl/npm-pyodide