0
# Advanced Features
1
2
Advanced functionality including canvas integration, worker support, interrupt handling, and archive operations.
3
4
## Canvas Integration
5
6
### canvas
7
8
HTML5 canvas integration for matplotlib and graphics libraries.
9
10
```javascript { .api }
11
const canvas: {
12
setCanvas2D(canvas: HTMLCanvasElement): void;
13
getCanvas2D(): HTMLCanvasElement | undefined;
14
setCanvas3D(canvas: HTMLCanvasElement): void;
15
getCanvas3D(): HTMLCanvasElement | undefined;
16
};
17
```
18
19
## Interrupt Handling
20
21
### setInterruptBuffer
22
23
Set interrupt signal buffer for webworker communication.
24
25
```javascript { .api }
26
function setInterruptBuffer(buffer: TypedArray): void;
27
```
28
29
**Parameters:**
30
- `buffer` - SharedArrayBuffer for interrupt signals between threads
31
32
### checkInterrupt
33
34
Manually check for pending interrupt signals.
35
36
```javascript { .api }
37
function checkInterrupt(): void;
38
```
39
40
## Worker Communication
41
42
### registerComlink
43
44
Register Comlink for seamless worker proxy communication.
45
46
```javascript { .api }
47
function registerComlink(Comlink: any): void;
48
```
49
50
**Parameters:**
51
- `Comlink` - Comlink library instance
52
53
## Archive Operations
54
55
### unpackArchive
56
57
Extract archive files (tar, zip, wheel) into the file system.
58
59
```javascript { .api }
60
function unpackArchive(
61
buffer: TypedArray | ArrayBuffer,
62
format: string,
63
options?: {
64
extractDir?: string;
65
}
66
): void;
67
```
68
69
**Parameters:**
70
- `buffer` - Archive file data
71
- `format` - Archive format ('tar', 'zip', 'wheel', 'gztar', etc.)
72
- `options.extractDir` - Directory to extract to (default: current directory)
73
74
## Error Constants
75
76
### ERRNO_CODES
77
78
POSIX error code constants for file system operations.
79
80
```javascript { .api }
81
const ERRNO_CODES: {
82
[code: string]: number;
83
};
84
```
85
86
## Usage Examples
87
88
### Canvas Integration for Matplotlib
89
90
```javascript
91
// Set up canvas for matplotlib
92
const canvas = document.getElementById('matplotlib-canvas');
93
pyodide.canvas.setCanvas2D(canvas);
94
95
await pyodide.loadPackage(['matplotlib']);
96
97
pyodide.runPython(`
98
import matplotlib.pyplot as plt
99
import numpy as np
100
101
# Create sample plot
102
x = np.linspace(0, 2 * np.pi, 100)
103
y = np.sin(x)
104
105
plt.figure(figsize=(8, 6))
106
plt.plot(x, y, label='sin(x)')
107
plt.plot(x, np.cos(x), label='cos(x)')
108
plt.legend()
109
plt.title('Trigonometric Functions')
110
plt.xlabel('x')
111
plt.ylabel('y')
112
plt.grid(True)
113
plt.show() # This will render to the canvas
114
`);
115
```
116
117
### 3D Graphics with Three.js Integration
118
119
```javascript
120
// Set up 3D canvas
121
const canvas3d = document.getElementById('threejs-canvas');
122
pyodide.canvas.setCanvas3D(canvas3d);
123
124
// Register Three.js as a module
125
pyodide.registerJsModule('threejs', {
126
Scene: THREE.Scene,
127
PerspectiveCamera: THREE.PerspectiveCamera,
128
WebGLRenderer: THREE.WebGLRenderer,
129
BoxGeometry: THREE.BoxGeometry,
130
MeshBasicMaterial: THREE.MeshBasicMaterial,
131
Mesh: THREE.Mesh
132
});
133
134
pyodide.runPython(`
135
from threejs import Scene, PerspectiveCamera, WebGLRenderer
136
from threejs import BoxGeometry, MeshBasicMaterial, Mesh
137
138
# Create 3D scene
139
scene = Scene()
140
camera = PerspectiveCamera(75, 800/600, 0.1, 1000)
141
renderer = WebGLRenderer()
142
143
# Create geometry
144
geometry = BoxGeometry(1, 1, 1)
145
material = MeshBasicMaterial({'color': 0x00ff00})
146
cube = Mesh(geometry, material)
147
148
scene.add(cube)
149
camera.position.z = 5
150
151
# Render scene
152
renderer.render(scene, camera)
153
`);
154
```
155
156
### Interrupt Handling in Web Workers
157
158
```javascript
159
// Main thread
160
const sharedBuffer = new SharedArrayBuffer(4);
161
const interruptArray = new Int32Array(sharedBuffer);
162
163
// Send buffer to worker
164
worker.postMessage({ sharedBuffer });
165
166
// Worker code
167
self.onmessage = async function(e) {
168
if (e.data.sharedBuffer) {
169
const pyodide = await loadPyodide();
170
const interruptArray = new Int32Array(e.data.sharedBuffer);
171
172
// Set interrupt buffer
173
pyodide.setInterruptBuffer(interruptArray);
174
175
// Long running Python code
176
pyodide.runPython(`
177
import time
178
import signal
179
180
def signal_handler(signum, frame):
181
print(f"Received signal {signum}")
182
raise KeyboardInterrupt("Interrupted by signal")
183
184
# Install signal handler
185
signal.signal(signal.SIGINT, signal_handler)
186
187
# Long computation
188
for i in range(1000000):
189
# Check for interrupts periodically
190
if i % 10000 == 0:
191
print(f"Progress: {i}")
192
time.sleep(0.001)
193
`);
194
}
195
};
196
197
// Main thread - send interrupt
198
setTimeout(() => {
199
interruptArray[0] = 2; // SIGINT
200
}, 5000);
201
```
202
203
### Manual Interrupt Checking
204
205
```javascript
206
// JavaScript-controlled interrupt checking
207
let shouldStop = false;
208
209
pyodide.registerJsModule('jscontrol', {
210
checkStop: () => shouldStop
211
});
212
213
// Start long-running Python process
214
const runLongTask = async () => {
215
pyodide.runPythonAsync(`
216
from jscontrol import checkStop
217
import time
218
219
for i in range(1000000):
220
if i % 1000 == 0:
221
if checkStop():
222
print(f"Task interrupted at iteration {i}")
223
break
224
print(f"Processing: {i}")
225
226
time.sleep(0.001)
227
`);
228
};
229
230
// Stop button handler
231
document.getElementById('stop-button').onclick = () => {
232
shouldStop = true;
233
};
234
235
// Start button handler
236
document.getElementById('start-button').onclick = () => {
237
shouldStop = false;
238
runLongTask();
239
};
240
```
241
242
### Comlink Integration for Seamless Worker Communication
243
244
```javascript
245
// Main thread
246
import * as Comlink from 'comlink';
247
248
const worker = new Worker('pyodide-worker.js');
249
const PyodideWorker = Comlink.wrap(worker);
250
251
// Worker (pyodide-worker.js)
252
import * as Comlink from 'comlink';
253
import { loadPyodide } from 'pyodide';
254
255
class PyodideService {
256
constructor() {
257
this.pyodide = null;
258
}
259
260
async init() {
261
this.pyodide = await loadPyodide();
262
this.pyodide.registerComlink(Comlink);
263
return 'Pyodide loaded in worker';
264
}
265
266
runCode(code) {
267
return this.pyodide.runPython(code);
268
}
269
270
async runCodeAsync(code) {
271
return await this.pyodide.runPythonAsync(code);
272
}
273
274
// Return Python functions as Comlink proxies
275
getPythonFunction(code) {
276
this.pyodide.runPython(code);
277
return this.pyodide.globals.get('exported_function');
278
}
279
}
280
281
Comlink.expose(new PyodideService());
282
283
// Usage in main thread
284
const service = await new PyodideWorker();
285
await service.init();
286
287
const result = await service.runCode('2 + 2');
288
console.log(result); // 4
289
290
// Get Python function proxy
291
const pythonFunc = await service.getPythonFunction(`
292
def exported_function(x, y):
293
return x ** 2 + y ** 2
294
`);
295
296
const funcResult = await pythonFunc(3, 4);
297
console.log(funcResult); // 25
298
```
299
300
### Archive Extraction
301
302
```javascript
303
// Extract Python package archives
304
async function installCustomPackage(archiveUrl, format) {
305
try {
306
// Download archive
307
const response = await fetch(archiveUrl);
308
const buffer = await response.arrayBuffer();
309
310
// Extract to temporary directory
311
pyodide.FS.mkdir('/tmp/extract');
312
pyodide.unpackArchive(buffer, format, {
313
extractDir: '/tmp/extract'
314
});
315
316
// List extracted contents
317
const files = pyodide.FS.readdir('/tmp/extract');
318
console.log('Extracted files:', files);
319
320
// Move to site-packages if it's a Python package
321
if (files.includes('setup.py') || files.includes('pyproject.toml')) {
322
pyodide.runPython(`
323
import sys
324
import os
325
sys.path.insert(0, '/tmp/extract')
326
327
# Install package
328
os.chdir('/tmp/extract')
329
import subprocess
330
subprocess.run([sys.executable, 'setup.py', 'install'])
331
`);
332
}
333
334
} catch (error) {
335
console.error('Failed to install custom package:', error);
336
}
337
}
338
339
// Install from wheel file
340
await installCustomPackage('https://example.com/package.whl', 'wheel');
341
342
// Install from tar.gz
343
await installCustomPackage('https://example.com/package.tar.gz', 'gztar');
344
```
345
346
### Data Archive Processing
347
348
```javascript
349
// Process data archives
350
async function processDataArchive(archiveData, format) {
351
// Extract data archive
352
pyodide.unpackArchive(archiveData, format, {
353
extractDir: '/data'
354
});
355
356
// Process extracted data with Python
357
const results = pyodide.runPython(`
358
import os
359
import pandas as pd
360
361
data_files = []
362
for root, dirs, files in os.walk('/data'):
363
for file in files:
364
if file.endswith('.csv'):
365
file_path = os.path.join(root, file)
366
data_files.append(file_path)
367
368
# Load and combine CSV files
369
dataframes = []
370
for file_path in data_files:
371
df = pd.read_csv(file_path)
372
df['source_file'] = file_path
373
dataframes.append(df)
374
375
if dataframes:
376
combined_df = pd.concat(dataframes, ignore_index=True)
377
{
378
'total_rows': len(combined_df),
379
'columns': list(combined_df.columns),
380
'files_processed': len(data_files)
381
}
382
else:
383
{'error': 'No CSV files found'}
384
`);
385
386
return results;
387
}
388
389
// Usage with ZIP file containing CSV data
390
const zipData = await fetch('/data-archive.zip').then(r => r.arrayBuffer());
391
const results = await processDataArchive(zipData, 'zip');
392
console.log('Processing results:', results);
393
```
394
395
### Error Code Handling
396
397
```javascript
398
// Use ERRNO codes for file system error handling
399
function safeFileOperation(operation) {
400
try {
401
return operation();
402
} catch (error) {
403
const errno = error.errno;
404
405
if (errno === pyodide.ERRNO_CODES.ENOENT) {
406
console.error('File or directory does not exist');
407
} else if (errno === pyodide.ERRNO_CODES.EACCES) {
408
console.error('Permission denied');
409
} else if (errno === pyodide.ERRNO_CODES.EEXIST) {
410
console.error('File already exists');
411
} else if (errno === pyodide.ERRNO_CODES.ENOSPC) {
412
console.error('No space left on device');
413
} else {
414
console.error(`File system error (${errno}):`, error.message);
415
}
416
417
return null;
418
}
419
}
420
421
// Safe file operations
422
const content = safeFileOperation(() =>
423
pyodide.FS.readFile('/nonexistent/file.txt', { encoding: 'utf8' })
424
);
425
426
const success = safeFileOperation(() =>
427
pyodide.FS.writeFile('/protected/file.txt', 'data')
428
);
429
```
430
431
### Performance Monitoring
432
433
```javascript
434
// Monitor Pyodide performance
435
class PyodideMonitor {
436
constructor() {
437
this.metrics = {
438
executionTimes: [],
439
memoryUsage: [],
440
packageLoadTimes: []
441
};
442
}
443
444
async timeExecution(code, description = 'Execution') {
445
const startTime = performance.now();
446
const startMemory = this.getMemoryUsage();
447
448
try {
449
const result = await pyodide.runPython(code);
450
const endTime = performance.now();
451
const endMemory = this.getMemoryUsage();
452
453
const executionTime = endTime - startTime;
454
const memoryDelta = endMemory - startMemory;
455
456
this.metrics.executionTimes.push({
457
description,
458
time: executionTime,
459
timestamp: new Date().toISOString()
460
});
461
462
this.metrics.memoryUsage.push({
463
description,
464
memoryDelta,
465
totalMemory: endMemory,
466
timestamp: new Date().toISOString()
467
});
468
469
console.log(`${description}: ${executionTime.toFixed(2)}ms, Memory: ${memoryDelta}KB`);
470
return result;
471
472
} catch (error) {
473
console.error(`${description} failed:`, error);
474
throw error;
475
}
476
}
477
478
getMemoryUsage() {
479
// Approximation - actual memory usage varies
480
return performance.memory?.usedJSHeapSize / 1024 || 0;
481
}
482
483
getReport() {
484
return {
485
averageExecutionTime: this.metrics.executionTimes.reduce((sum, entry) =>
486
sum + entry.time, 0) / this.metrics.executionTimes.length,
487
totalExecutions: this.metrics.executionTimes.length,
488
memoryMetrics: this.metrics.memoryUsage,
489
packageMetrics: this.metrics.packageLoadTimes
490
};
491
}
492
}
493
494
// Usage
495
const monitor = new PyodideMonitor();
496
497
await monitor.timeExecution(`
498
import numpy as np
499
arr = np.random.random((1000, 1000))
500
result = np.sum(arr)
501
result
502
`, 'NumPy Random Sum');
503
504
await monitor.timeExecution(`
505
import pandas as pd
506
df = pd.DataFrame({'A': range(10000), 'B': range(10000, 20000)})
507
result = df.groupby('A').sum()
508
len(result)
509
`, 'Pandas GroupBy');
510
511
console.log('Performance Report:', monitor.getReport());
512
```
513
514
## Debug Control
515
516
### setDebug
517
518
Enable or disable debug mode for enhanced error messages at a performance cost.
519
520
```javascript { .api }
521
function setDebug(debug: boolean): boolean;
522
```
523
524
**Parameters:**
525
- `debug` - If true, enable debug mode; if false, disable debug mode
526
527
**Returns:** The previous value of the debug flag
528
529
**Usage Example:**
530
531
```javascript
532
// Enable debug mode for development
533
const previousDebugState = pyodide.setDebug(true);
534
console.log('Debug mode enabled, previous state was:', previousDebugState);
535
536
try {
537
// Your code that might produce complex errors
538
pyodide.runPython('some_complex_python_code()');
539
} catch (error) {
540
console.error('Enhanced error info:', error);
541
} finally {
542
// Restore previous debug state
543
pyodide.setDebug(previousDebugState);
544
}
545
```
546
547
## Package Information Access
548
549
### lockfile
550
551
Access the complete lockfile used to load the current Pyodide instance, containing package metadata and dependency information.
552
553
```javascript { .api }
554
const lockfile: Lockfile;
555
```
556
557
**Usage Example:**
558
559
```javascript
560
// Access lockfile information
561
const lockfile = pyodide.lockfile;
562
console.log('Pyodide version:', lockfile.info.version);
563
console.log('Python version:', lockfile.info.python);
564
console.log('Available packages:', Object.keys(lockfile.packages));
565
566
// Get information about a specific package
567
const numpyInfo = lockfile.packages['numpy'];
568
if (numpyInfo) {
569
console.log('NumPy version:', numpyInfo.version);
570
console.log('NumPy dependencies:', numpyInfo.depends);
571
}
572
```
573
574
### lockfileBaseUrl
575
576
Get the base URL used for resolving relative package paths in the lockfile.
577
578
```javascript { .api }
579
const lockfileBaseUrl: string | undefined;
580
```
581
582
**Usage Example:**
583
584
```javascript
585
const baseUrl = pyodide.lockfileBaseUrl;
586
console.log('Package base URL:', baseUrl);
587
588
// This is useful for understanding where packages are loaded from
589
if (baseUrl) {
590
console.log('Packages are loaded from:', baseUrl);
591
} else {
592
console.log('Using default package locations');
593
}
594
```