0
# Foreign Function Interface (FFI)
1
2
Pyodide's Foreign Function Interface provides seamless interoperability between JavaScript and Python, enabling automatic type conversion and proxy objects for cross-language operation.
3
4
## FFI Module
5
6
The FFI module exports type classes and utilities for working with Python objects from JavaScript.
7
8
```javascript { .api }
9
import { ffi } from "pyodide/ffi";
10
11
const ffi: {
12
PyProxy: typeof PyProxy;
13
PyProxyWithLength: typeof PyProxyWithLength;
14
PyProxyWithGet: typeof PyProxyWithGet;
15
PyProxyWithSet: typeof PyProxyWithSet;
16
PyProxyWithHas: typeof PyProxyWithHas;
17
PyDict: typeof PyDict;
18
PyIterable: typeof PyIterable;
19
PyAsyncIterable: typeof PyAsyncIterable;
20
PyIterator: typeof PyIterator;
21
PyAsyncIterator: typeof PyAsyncIterator;
22
PyGenerator: typeof PyGenerator;
23
PyAsyncGenerator: typeof PyAsyncGenerator;
24
PyAwaitable: typeof PyAwaitable;
25
PyCallable: typeof PyCallable;
26
PyBuffer: typeof PyBuffer;
27
PyBufferView: typeof PyBufferView;
28
PySequence: typeof PySequence;
29
PyMutableSequence: typeof PyMutableSequence;
30
PythonError: typeof PythonError;
31
};
32
```
33
34
## Core FFI Types
35
36
### PyProxy
37
38
Base proxy type for all Python objects accessible from JavaScript.
39
40
```javascript { .api }
41
interface PyProxy {
42
/** Check if object has been destroyed */
43
readonly destroyed: boolean;
44
/** Destroy the proxy and release Python reference */
45
destroy(): void;
46
/** Convert to JavaScript object */
47
toJs(options?: ConversionOptions): any;
48
/** Create a copy with new conversion options */
49
copy(): PyProxy;
50
/** Get string representation (str() in Python) */
51
toString(): string;
52
/** Get detailed representation (repr() in Python) */
53
supportsLength(): this is PyProxyWithLength;
54
supportsGet(): this is PyProxyWithGet;
55
supportsSet(): this is PyProxyWithSet;
56
supportsHas(): this is PyProxyWithHas;
57
}
58
```
59
60
### PyProxyWithLength
61
62
Proxy for Python objects that support `len()`.
63
64
```javascript { .api }
65
interface PyProxyWithLength extends PyProxy {
66
readonly length: number;
67
}
68
```
69
70
### PyProxyWithGet
71
72
Proxy for Python objects that support item access (`obj[key]`).
73
74
```javascript { .api }
75
interface PyProxyWithGet extends PyProxy {
76
get(key: any): any;
77
}
78
```
79
80
### PyProxyWithSet
81
82
Proxy for Python objects that support item assignment (`obj[key] = value`).
83
84
```javascript { .api }
85
interface PyProxyWithSet extends PyProxy {
86
set(key: any, value: any): void;
87
delete(key: any): void;
88
}
89
```
90
91
### PyProxyWithHas
92
93
Proxy for Python objects that support membership testing (`key in obj`).
94
95
```javascript { .api }
96
interface PyProxyWithHas extends PyProxy {
97
has(key: any): boolean;
98
}
99
```
100
101
## Collection Types
102
103
### PyDict
104
105
Proxy for Python dictionaries with JavaScript Map-like interface.
106
107
```javascript { .api }
108
interface PyDict extends PyProxy {
109
get(key: any): any;
110
set(key: any, value: any): void;
111
delete(key: any): boolean;
112
has(key: any): boolean;
113
keys(): PyProxy;
114
values(): PyProxy;
115
items(): PyProxy;
116
}
117
```
118
119
**Usage Example:**
120
121
```javascript
122
// Create Python dict and work with it
123
const pythonDict = pyodide.runPython(`
124
{'name': 'Alice', 'age': 30, 'city': 'New York'}
125
`);
126
127
console.log(pythonDict.get('name')); // 'Alice'
128
pythonDict.set('age', 31);
129
console.log(pythonDict.has('city')); // true
130
pythonDict.delete('city');
131
132
// Convert to JavaScript object
133
const jsObject = pythonDict.toJs();
134
console.log(jsObject); // {name: 'Alice', age: 31}
135
```
136
137
### PySequence
138
139
Proxy for Python sequences (lists, tuples, strings) with array-like interface.
140
141
```javascript { .api }
142
interface PySequence extends PyProxy {
143
readonly length: number;
144
get(index: number): any;
145
slice(start?: number, stop?: number): PySequence;
146
}
147
```
148
149
### PyMutableSequence
150
151
Proxy for mutable Python sequences (lists) with modification capabilities.
152
153
```javascript { .api }
154
interface PyMutableSequence extends PySequence {
155
set(index: number, value: any): void;
156
delete(index: number): void;
157
append(value: any): void;
158
extend(iterable: any): void;
159
insert(index: number, value: any): void;
160
pop(index?: number): any;
161
reverse(): void;
162
sort(compare?: (a: any, b: any) => number): void;
163
}
164
```
165
166
**Usage Example:**
167
168
```javascript
169
// Create Python list and manipulate it
170
const pythonList = pyodide.runPython('[1, 2, 3, 4, 5]');
171
172
console.log(pythonList.length); // 5
173
console.log(pythonList.get(0)); // 1
174
175
pythonList.append(6);
176
pythonList.insert(0, 0);
177
console.log(pythonList.toJs()); // [0, 1, 2, 3, 4, 5, 6]
178
179
const sliced = pythonList.slice(1, 4);
180
console.log(sliced.toJs()); // [1, 2, 3]
181
```
182
183
## Async and Iterator Types
184
185
### PyIterable
186
187
Proxy for Python iterable objects.
188
189
```javascript { .api }
190
interface PyIterable extends PyProxy {
191
[Symbol.iterator](): Iterator<any>;
192
}
193
```
194
195
### PyAsyncIterable
196
197
Proxy for Python async iterable objects.
198
199
```javascript { .api }
200
interface PyAsyncIterable extends PyProxy {
201
[Symbol.asyncIterator](): AsyncIterator<any>;
202
}
203
```
204
205
### PyIterator
206
207
Proxy for Python iterator objects.
208
209
```javascript { .api }
210
interface PyIterator extends PyProxy {
211
next(): IteratorResult<any>;
212
[Symbol.iterator](): PyIterator;
213
}
214
```
215
216
### PyAsyncIterator
217
218
Proxy for Python async iterator objects.
219
220
```javascript { .api }
221
interface PyAsyncIterator extends PyProxy {
222
next(): Promise<IteratorResult<any>>;
223
[Symbol.asyncIterator](): PyAsyncIterator;
224
}
225
```
226
227
## Function and Callable Types
228
229
### PyCallable
230
231
Proxy for Python callable objects (functions, methods, classes).
232
233
```javascript { .api }
234
interface PyCallable extends PyProxy {
235
(...args: any[]): any;
236
callKwargs(...args: any[], kwargs?: { [key: string]: any }): any;
237
apply(thisArg: any, args: any[]): any;
238
call(thisArg: any, ...args: any[]): any;
239
}
240
```
241
242
**Usage Example:**
243
244
```javascript
245
// Call Python functions from JavaScript
246
const mathFunc = pyodide.runPython(`
247
def calculate(x, y, operation='add'):
248
if operation == 'add':
249
return x + y
250
elif operation == 'multiply':
251
return x * y
252
return 0
253
calculate
254
`);
255
256
// Call with positional arguments
257
const result1 = mathFunc(5, 3);
258
console.log(result1); // 8
259
260
// Call with keyword arguments
261
const result2 = mathFunc.callKwargs(5, 3, { operation: 'multiply' });
262
console.log(result2); // 15
263
```
264
265
### PyGenerator
266
267
Proxy for Python generator objects.
268
269
```javascript { .api }
270
interface PyGenerator extends PyProxy {
271
next(value?: any): IteratorResult<any>;
272
return(value?: any): IteratorResult<any>;
273
throw(error?: any): IteratorResult<any>;
274
[Symbol.iterator](): PyGenerator;
275
}
276
```
277
278
### PyAsyncGenerator
279
280
Proxy for Python async generator objects.
281
282
```javascript { .api }
283
interface PyAsyncGenerator extends PyProxy {
284
next(value?: any): Promise<IteratorResult<any>>;
285
return(value?: any): Promise<IteratorResult<any>>;
286
throw(error?: any): Promise<IteratorResult<any>>;
287
[Symbol.asyncIterator](): PyAsyncGenerator;
288
}
289
```
290
291
### PyAwaitable
292
293
Proxy for Python awaitable objects (coroutines, futures).
294
295
```javascript { .api }
296
interface PyAwaitable extends PyProxy {
297
then<TResult1 = any, TResult2 = never>(
298
onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>,
299
onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>
300
): Promise<TResult1 | TResult2>;
301
}
302
```
303
304
**Usage Example:**
305
306
```javascript
307
// Work with async Python functions
308
const asyncFunc = pyodide.runPython(`
309
import asyncio
310
311
async def fetch_data():
312
await asyncio.sleep(0.1)
313
return "Data fetched!"
314
315
fetch_data
316
`);
317
318
// Await Python coroutine
319
const result = await asyncFunc();
320
console.log(result); // "Data fetched!"
321
```
322
323
## Buffer Types
324
325
### PyBuffer
326
327
Proxy for Python buffer protocol objects.
328
329
```javascript { .api }
330
interface PyBuffer extends PyProxy {
331
readonly byteLength: number;
332
readonly format: string;
333
readonly itemsize: number;
334
readonly ndim: number;
335
readonly readonly: boolean;
336
readonly shape: number[] | null;
337
readonly strides: number[] | null;
338
getBuffer(order?: 'C' | 'F'): ArrayBuffer;
339
}
340
```
341
342
### PyBufferView
343
344
A view into a Python buffer as a typed array.
345
346
```javascript { .api }
347
interface PyBufferView extends PyProxy {
348
readonly buffer: ArrayBuffer;
349
readonly byteLength: number;
350
readonly byteOffset: number;
351
readonly length: number;
352
readonly BYTES_PER_ELEMENT: number;
353
}
354
```
355
356
**Usage Example:**
357
358
```javascript
359
// Work with NumPy arrays through buffer interface
360
await pyodide.loadPackage('numpy');
361
const npArray = pyodide.runPython(`
362
import numpy as np
363
np.array([1, 2, 3, 4, 5], dtype=np.float32)
364
`);
365
366
console.log('Array shape:', npArray.shape);
367
console.log('Array dtype:', npArray.format);
368
369
// Get buffer view
370
const buffer = npArray.getBuffer();
371
const float32View = new Float32Array(buffer);
372
console.log('JavaScript view:', float32View); // [1, 2, 3, 4, 5]
373
```
374
375
## Error Handling
376
377
### PythonError
378
379
JavaScript representation of Python exceptions.
380
381
```javascript { .api }
382
interface PythonError extends Error {
383
readonly type: string;
384
readonly value: PyProxy;
385
readonly traceback: PyProxy;
386
}
387
```
388
389
**Usage Example:**
390
391
```javascript
392
import { ffi } from "pyodide/ffi";
393
394
try {
395
pyodide.runPython(`
396
raise ValueError("Something went wrong!")
397
`);
398
} catch (error) {
399
if (error instanceof ffi.PythonError) {
400
console.log('Python error type:', error.type);
401
console.log('Python error message:', error.message);
402
console.log('Python traceback:', error.traceback.toString());
403
}
404
}
405
```
406
407
## Type Conversion Options
408
409
```javascript { .api }
410
interface ConversionOptions {
411
/** Maximum depth for recursive conversion */
412
depth?: number;
413
/** Custom converter for objects without default conversion */
414
defaultConverter?: (
415
value: any,
416
converter: (value: any) => any,
417
cacheConversion: (input: any, output: any) => void
418
) => any;
419
/** Convert Python dictionaries to JavaScript Maps */
420
dictConverter?: (
421
items: Iterable<[key: string, value: any]>
422
) => Map<string, any> | object;
423
}
424
```
425
426
## Runtime Type Checking
427
428
Use FFI classes for runtime type checking:
429
430
```javascript
431
import { ffi } from "pyodide/ffi";
432
433
function processData(pythonObject) {
434
if (pythonObject instanceof ffi.PyDict) {
435
console.log('Processing Python dictionary...');
436
for (const [key, value] of pythonObject.items()) {
437
console.log(`${key}: ${value}`);
438
}
439
} else if (pythonObject instanceof ffi.PySequence) {
440
console.log('Processing Python sequence...');
441
for (let i = 0; i < pythonObject.length; i++) {
442
console.log(`Item ${i}: ${pythonObject.get(i)}`);
443
}
444
} else if (pythonObject instanceof ffi.PyCallable) {
445
console.log('Processing Python function...');
446
const result = pythonObject();
447
console.log('Function result:', result);
448
}
449
}
450
```