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

advanced-features.mddocs/

Advanced Features

Advanced functionality including canvas integration, worker support, interrupt handling, and archive operations.

Canvas Integration

canvas

HTML5 canvas integration for matplotlib and graphics libraries.

const canvas: {
  setCanvas2D(canvas: HTMLCanvasElement): void;
  getCanvas2D(): HTMLCanvasElement | undefined;
  setCanvas3D(canvas: HTMLCanvasElement): void;
  getCanvas3D(): HTMLCanvasElement | undefined;
};

Interrupt Handling

setInterruptBuffer

Set interrupt signal buffer for webworker communication.

function setInterruptBuffer(buffer: TypedArray): void;

Parameters:

  • buffer - SharedArrayBuffer for interrupt signals between threads

checkInterrupt

Manually check for pending interrupt signals.

function checkInterrupt(): void;

Worker Communication

registerComlink

Register Comlink for seamless worker proxy communication.

function registerComlink(Comlink: any): void;

Parameters:

  • Comlink - Comlink library instance

Archive Operations

unpackArchive

Extract archive files (tar, zip, wheel) into the file system.

function unpackArchive(
  buffer: TypedArray | ArrayBuffer,
  format: string,
  options?: {
    extractDir?: string;
  }
): void;

Parameters:

  • buffer - Archive file data
  • format - Archive format ('tar', 'zip', 'wheel', 'gztar', etc.)
  • options.extractDir - Directory to extract to (default: current directory)

Error Constants

ERRNO_CODES

POSIX error code constants for file system operations.

const ERRNO_CODES: {
  [code: string]: number;
};

Usage Examples

Canvas Integration for Matplotlib

// Set up canvas for matplotlib
const canvas = document.getElementById('matplotlib-canvas');
pyodide.canvas.setCanvas2D(canvas);

await pyodide.loadPackage(['matplotlib']);

pyodide.runPython(`
    import matplotlib.pyplot as plt
    import numpy as np
    
    # Create sample plot
    x = np.linspace(0, 2 * np.pi, 100)
    y = np.sin(x)
    
    plt.figure(figsize=(8, 6))
    plt.plot(x, y, label='sin(x)')
    plt.plot(x, np.cos(x), label='cos(x)')
    plt.legend()
    plt.title('Trigonometric Functions')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.grid(True)
    plt.show()  # This will render to the canvas
`);

3D Graphics with Three.js Integration

// Set up 3D canvas
const canvas3d = document.getElementById('threejs-canvas');
pyodide.canvas.setCanvas3D(canvas3d);

// Register Three.js as a module
pyodide.registerJsModule('threejs', {
  Scene: THREE.Scene,
  PerspectiveCamera: THREE.PerspectiveCamera,
  WebGLRenderer: THREE.WebGLRenderer,
  BoxGeometry: THREE.BoxGeometry,
  MeshBasicMaterial: THREE.MeshBasicMaterial,
  Mesh: THREE.Mesh
});

pyodide.runPython(`
    from threejs import Scene, PerspectiveCamera, WebGLRenderer
    from threejs import BoxGeometry, MeshBasicMaterial, Mesh
    
    # Create 3D scene
    scene = Scene()
    camera = PerspectiveCamera(75, 800/600, 0.1, 1000)
    renderer = WebGLRenderer()
    
    # Create geometry
    geometry = BoxGeometry(1, 1, 1)
    material = MeshBasicMaterial({'color': 0x00ff00})
    cube = Mesh(geometry, material)
    
    scene.add(cube)
    camera.position.z = 5
    
    # Render scene
    renderer.render(scene, camera)
`);

Interrupt Handling in Web Workers

// Main thread
const sharedBuffer = new SharedArrayBuffer(4);
const interruptArray = new Int32Array(sharedBuffer);

// Send buffer to worker
worker.postMessage({ sharedBuffer });

// Worker code
self.onmessage = async function(e) {
  if (e.data.sharedBuffer) {
    const pyodide = await loadPyodide();
    const interruptArray = new Int32Array(e.data.sharedBuffer);
    
    // Set interrupt buffer
    pyodide.setInterruptBuffer(interruptArray);
    
    // Long running Python code
    pyodide.runPython(`
        import time
        import signal
        
        def signal_handler(signum, frame):
            print(f"Received signal {signum}")
            raise KeyboardInterrupt("Interrupted by signal")
        
        # Install signal handler
        signal.signal(signal.SIGINT, signal_handler)
        
        # Long computation
        for i in range(1000000):
            # Check for interrupts periodically
            if i % 10000 == 0:
                print(f"Progress: {i}")
            time.sleep(0.001)
    `);
  }
};

// Main thread - send interrupt
setTimeout(() => {
  interruptArray[0] = 2; // SIGINT
}, 5000);

Manual Interrupt Checking

// JavaScript-controlled interrupt checking
let shouldStop = false;

pyodide.registerJsModule('jscontrol', {
  checkStop: () => shouldStop
});

// Start long-running Python process
const runLongTask = async () => {
  pyodide.runPythonAsync(`
      from jscontrol import checkStop
      import time
      
      for i in range(1000000):
          if i % 1000 == 0:
              if checkStop():
                  print(f"Task interrupted at iteration {i}")
                  break
              print(f"Processing: {i}")
          
          time.sleep(0.001)
  `);
};

// Stop button handler
document.getElementById('stop-button').onclick = () => {
  shouldStop = true;
};

// Start button handler  
document.getElementById('start-button').onclick = () => {
  shouldStop = false;
  runLongTask();
};

Comlink Integration for Seamless Worker Communication

// Main thread
import * as Comlink from 'comlink';

const worker = new Worker('pyodide-worker.js');
const PyodideWorker = Comlink.wrap(worker);

// Worker (pyodide-worker.js)
import * as Comlink from 'comlink';
import { loadPyodide } from 'pyodide';

class PyodideService {
  constructor() {
    this.pyodide = null;
  }
  
  async init() {
    this.pyodide = await loadPyodide();
    this.pyodide.registerComlink(Comlink);
    return 'Pyodide loaded in worker';
  }
  
  runCode(code) {
    return this.pyodide.runPython(code);
  }
  
  async runCodeAsync(code) {
    return await this.pyodide.runPythonAsync(code);
  }
  
  // Return Python functions as Comlink proxies
  getPythonFunction(code) {
    this.pyodide.runPython(code);
    return this.pyodide.globals.get('exported_function');
  }
}

Comlink.expose(new PyodideService());

// Usage in main thread
const service = await new PyodideWorker();
await service.init();

const result = await service.runCode('2 + 2');
console.log(result); // 4

// Get Python function proxy
const pythonFunc = await service.getPythonFunction(`
    def exported_function(x, y):
        return x ** 2 + y ** 2
`);

const funcResult = await pythonFunc(3, 4);
console.log(funcResult); // 25

Archive Extraction

// Extract Python package archives
async function installCustomPackage(archiveUrl, format) {
  try {
    // Download archive
    const response = await fetch(archiveUrl);
    const buffer = await response.arrayBuffer();
    
    // Extract to temporary directory
    pyodide.FS.mkdir('/tmp/extract');
    pyodide.unpackArchive(buffer, format, {
      extractDir: '/tmp/extract'
    });
    
    // List extracted contents
    const files = pyodide.FS.readdir('/tmp/extract');
    console.log('Extracted files:', files);
    
    // Move to site-packages if it's a Python package
    if (files.includes('setup.py') || files.includes('pyproject.toml')) {
      pyodide.runPython(`
          import sys
          import os
          sys.path.insert(0, '/tmp/extract')
          
          # Install package
          os.chdir('/tmp/extract')
          import subprocess
          subprocess.run([sys.executable, 'setup.py', 'install'])
      `);
    }
    
  } catch (error) {
    console.error('Failed to install custom package:', error);
  }
}

// Install from wheel file
await installCustomPackage('https://example.com/package.whl', 'wheel');

// Install from tar.gz
await installCustomPackage('https://example.com/package.tar.gz', 'gztar');

Data Archive Processing

// Process data archives
async function processDataArchive(archiveData, format) {
  // Extract data archive
  pyodide.unpackArchive(archiveData, format, {
    extractDir: '/data'
  });
  
  // Process extracted data with Python
  const results = pyodide.runPython(`
      import os
      import pandas as pd
      
      data_files = []
      for root, dirs, files in os.walk('/data'):
          for file in files:
              if file.endswith('.csv'):
                  file_path = os.path.join(root, file)
                  data_files.append(file_path)
      
      # Load and combine CSV files
      dataframes = []
      for file_path in data_files:
          df = pd.read_csv(file_path)
          df['source_file'] = file_path
          dataframes.append(df)
      
      if dataframes:
          combined_df = pd.concat(dataframes, ignore_index=True)
          {
              'total_rows': len(combined_df),
              'columns': list(combined_df.columns),
              'files_processed': len(data_files)
          }
      else:
          {'error': 'No CSV files found'}
  `);
  
  return results;
}

// Usage with ZIP file containing CSV data
const zipData = await fetch('/data-archive.zip').then(r => r.arrayBuffer());
const results = await processDataArchive(zipData, 'zip');
console.log('Processing results:', results);

Error Code Handling

// Use ERRNO codes for file system error handling
function safeFileOperation(operation) {
  try {
    return operation();
  } catch (error) {
    const errno = error.errno;
    
    if (errno === pyodide.ERRNO_CODES.ENOENT) {
      console.error('File or directory does not exist');
    } else if (errno === pyodide.ERRNO_CODES.EACCES) {
      console.error('Permission denied');
    } else if (errno === pyodide.ERRNO_CODES.EEXIST) {
      console.error('File already exists');
    } else if (errno === pyodide.ERRNO_CODES.ENOSPC) {
      console.error('No space left on device');
    } else {
      console.error(`File system error (${errno}):`, error.message);
    }
    
    return null;
  }
}

// Safe file operations
const content = safeFileOperation(() => 
  pyodide.FS.readFile('/nonexistent/file.txt', { encoding: 'utf8' })
);

const success = safeFileOperation(() => 
  pyodide.FS.writeFile('/protected/file.txt', 'data')
);

Performance Monitoring

// Monitor Pyodide performance
class PyodideMonitor {
  constructor() {
    this.metrics = {
      executionTimes: [],
      memoryUsage: [],
      packageLoadTimes: []
    };
  }
  
  async timeExecution(code, description = 'Execution') {
    const startTime = performance.now();
    const startMemory = this.getMemoryUsage();
    
    try {
      const result = await pyodide.runPython(code);
      const endTime = performance.now();
      const endMemory = this.getMemoryUsage();
      
      const executionTime = endTime - startTime;
      const memoryDelta = endMemory - startMemory;
      
      this.metrics.executionTimes.push({
        description,
        time: executionTime,
        timestamp: new Date().toISOString()
      });
      
      this.metrics.memoryUsage.push({
        description,
        memoryDelta,
        totalMemory: endMemory,
        timestamp: new Date().toISOString()
      });
      
      console.log(`${description}: ${executionTime.toFixed(2)}ms, Memory: ${memoryDelta}KB`);
      return result;
      
    } catch (error) {
      console.error(`${description} failed:`, error);
      throw error;
    }
  }
  
  getMemoryUsage() {
    // Approximation - actual memory usage varies
    return performance.memory?.usedJSHeapSize / 1024 || 0;
  }
  
  getReport() {
    return {
      averageExecutionTime: this.metrics.executionTimes.reduce((sum, entry) => 
        sum + entry.time, 0) / this.metrics.executionTimes.length,
      totalExecutions: this.metrics.executionTimes.length,
      memoryMetrics: this.metrics.memoryUsage,
      packageMetrics: this.metrics.packageLoadTimes
    };
  }
}

// Usage
const monitor = new PyodideMonitor();

await monitor.timeExecution(`
    import numpy as np
    arr = np.random.random((1000, 1000))
    result = np.sum(arr)
    result
`, 'NumPy Random Sum');

await monitor.timeExecution(`
    import pandas as pd
    df = pd.DataFrame({'A': range(10000), 'B': range(10000, 20000)})
    result = df.groupby('A').sum()
    len(result)
`, 'Pandas GroupBy');

console.log('Performance Report:', monitor.getReport());

Debug Control

setDebug

Enable or disable debug mode for enhanced error messages at a performance cost.

function setDebug(debug: boolean): boolean;

Parameters:

  • debug - If true, enable debug mode; if false, disable debug mode

Returns: The previous value of the debug flag

Usage Example:

// Enable debug mode for development
const previousDebugState = pyodide.setDebug(true);
console.log('Debug mode enabled, previous state was:', previousDebugState);

try {
  // Your code that might produce complex errors
  pyodide.runPython('some_complex_python_code()');
} catch (error) {
  console.error('Enhanced error info:', error);
} finally {
  // Restore previous debug state
  pyodide.setDebug(previousDebugState);
}

Package Information Access

lockfile

Access the complete lockfile used to load the current Pyodide instance, containing package metadata and dependency information.

const lockfile: Lockfile;

Usage Example:

// Access lockfile information
const lockfile = pyodide.lockfile;
console.log('Pyodide version:', lockfile.info.version);
console.log('Python version:', lockfile.info.python);
console.log('Available packages:', Object.keys(lockfile.packages));

// Get information about a specific package
const numpyInfo = lockfile.packages['numpy'];
if (numpyInfo) {
  console.log('NumPy version:', numpyInfo.version);
  console.log('NumPy dependencies:', numpyInfo.depends);
}

lockfileBaseUrl

Get the base URL used for resolving relative package paths in the lockfile.

const lockfileBaseUrl: string | undefined;

Usage Example:

const baseUrl = pyodide.lockfileBaseUrl;
console.log('Package base URL:', baseUrl);

// This is useful for understanding where packages are loaded from
if (baseUrl) {
  console.log('Packages are loaded from:', baseUrl);
} else {
  console.log('Using default package locations');
}

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