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

io-streams.mddocs/

I/O Streams

Customize input/output handling for Python code execution in different environments and use cases.

Stream Control Functions

setStdin

Set custom standard input handler for Python input operations.

function setStdin(options?: {
  stdin?: () => string;
  read?: (buffer: Uint8Array) => number;
  isatty?: boolean;
}): void;

Parameters:

  • options.stdin - Function called when Python requests input, should return a string
  • options.read - Low-level read function for binary input
  • options.isatty - Whether input is from a terminal

setStdout

Set custom standard output handler for Python print statements and output.

function setStdout(options?: {
  batched?: (output: string) => void;
  raw?: (charCode: number) => void;
  write?: (buffer: Uint8Array) => number;
  isatty?: boolean;
}): void;

Parameters:

  • options.batched - Function called with each line of Python output
  • options.raw - Function called with individual character codes
  • options.write - Low-level write function for binary output
  • options.isatty - Whether output is to a terminal

setStderr

Set custom standard error handler for Python error messages and warnings.

function setStderr(options?: {
  batched?: (output: string) => void;
  raw?: (charCode: number) => void;
  write?: (buffer: Uint8Array) => number;
  isatty?: boolean;
}): void;

Parameters:

  • options.batched - Function called with each line of Python error output
  • options.raw - Function called with individual character codes
  • options.write - Low-level write function for binary error output
  • options.isatty - Whether error output is to a terminal

Usage Examples

Basic Stream Redirection

// Redirect Python output to custom handlers
pyodide.setStdout({
  batched: (message) => {
    console.log(`[Python Output] ${message}`);
  }
});

pyodide.setStderr({
  batched: (message) => {
    console.error(`[Python Error] ${message}`);
  }
});

// Test output redirection
pyodide.runPython(`
    print("This goes to stdout")
    import sys
    sys.stderr.write("This goes to stderr\\n")
`);

Interactive Input Handling

// Simple prompt-based input
pyodide.setStdin({
  stdin: () => {
    return prompt("Python input requested:");
  }
});

pyodide.runPython(`
    name = input("What's your name? ")
    print(f"Hello, {name}!")
`);

Advanced Input Queue System

class InputQueue {
  constructor() {
    this.queue = [];
    this.waitingResolvers = [];
  }
  
  addInput(input) {
    if (this.waitingResolvers.length > 0) {
      const resolver = this.waitingResolvers.shift();
      resolver(input);
    } else {
      this.queue.push(input);
    }
  }
  
  async getInput() {
    if (this.queue.length > 0) {
      return this.queue.shift();
    }
    
    return new Promise((resolve) => {
      this.waitingResolvers.push(resolve);
    });
  }
}

const inputQueue = new InputQueue();

// Set up async input handler
pyodide.setStdin(() => {
  // This is a blocking call in the Python context
  // but we can use a synchronous approach with queued inputs
  if (inputQueue.queue.length > 0) {
    return inputQueue.queue.shift();
  }
  return ""; // Return empty if no input available
});

// Add inputs programmatically
inputQueue.addInput("Alice");
inputQueue.addInput("25");
inputQueue.addInput("Engineer");

pyodide.runPython(`
    name = input("Name: ")
    age = input("Age: ")
    job = input("Job: ")
    
    print(f"Hello {name}, you are {age} years old and work as an {job}")
`);

Logging and Analytics

class StreamLogger {
  constructor() {
    this.outputLog = [];
    this.errorLog = [];
    this.inputLog = [];
  }
  
  logOutput(message) {
    const entry = {
      type: 'stdout',
      message,
      timestamp: new Date().toISOString()
    };
    this.outputLog.push(entry);
    console.log(`[OUT] ${message}`);
  }
  
  logError(message) {
    const entry = {
      type: 'stderr', 
      message,
      timestamp: new Date().toISOString()
    };
    this.errorLog.push(entry);
    console.error(`[ERR] ${message}`);
  }
  
  logInput(input) {
    const entry = {
      type: 'stdin',
      input,
      timestamp: new Date().toISOString()
    };
    this.inputLog.push(entry);
    return input;
  }
  
  getFullLog() {
    return [...this.outputLog, ...this.errorLog, ...this.inputLog]
      .sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
  }
  
  exportLog() {
    return JSON.stringify(this.getFullLog(), null, 2);
  }
}

const logger = new StreamLogger();

pyodide.setStdout((msg) => logger.logOutput(msg));
pyodide.setStderr((msg) => logger.logError(msg));
pyodide.setStdin(() => logger.logInput("user_input"));

// Run Python code with full logging
pyodide.runPython(`
    print("Starting calculation...")
    try:
        result = 10 / 2
        print(f"Result: {result}")
    except Exception as e:
        print(f"Error: {e}")
        
    user_data = input("Enter data: ")
    print(f"You entered: {user_data}")
`);

// Export execution log
console.log("Execution log:", logger.exportLog());

Web UI Integration

// HTML elements for I/O
const outputDiv = document.getElementById('python-output');
const errorDiv = document.getElementById('python-errors');
const inputField = document.getElementById('python-input');
const inputButton = document.getElementById('input-submit');

let pendingInputResolver = null;

// Set up output handlers
pyodide.setStdout((message) => {
  const line = document.createElement('div');
  line.className = 'output-line';
  line.textContent = message;
  outputDiv.appendChild(line);
  outputDiv.scrollTop = outputDiv.scrollHeight;
});

pyodide.setStderr((message) => {
  const line = document.createElement('div');
  line.className = 'error-line';
  line.textContent = message;
  errorDiv.appendChild(line);
  errorDiv.scrollTop = errorDiv.scrollHeight;
});

// Set up input handler
pyodide.setStdin(() => {
  // Show input UI
  inputField.style.display = 'block';
  inputButton.style.display = 'block';
  inputField.focus();
  
  // Wait for user input
  return new Promise((resolve) => {
    pendingInputResolver = resolve;
  });
});

inputButton.addEventListener('click', () => {
  if (pendingInputResolver) {
    const input = inputField.value;
    inputField.value = '';
    inputField.style.display = 'none';
    inputButton.style.display = 'none';
    pendingInputResolver(input);
    pendingInputResolver = null;
  }
});

// Handle Enter key
inputField.addEventListener('keypress', (e) => {
  if (e.key === 'Enter') {
    inputButton.click();
  }
});

Buffered Output Handling

class BufferedOutputStream {
  constructor(flushCallback, bufferSize = 1024) {
    this.buffer = '';
    this.flushCallback = flushCallback;
    this.bufferSize = bufferSize;
    this.timer = null;
  }
  
  write(message) {
    this.buffer += message;
    
    // Flush if buffer is full
    if (this.buffer.length >= this.bufferSize) {
      this.flush();
    } else {
      // Schedule flush after short delay
      if (this.timer) {
        clearTimeout(this.timer);
      }
      this.timer = setTimeout(() => this.flush(), 10);
    }
  }
  
  flush() {
    if (this.buffer.length > 0) {
      this.flushCallback(this.buffer);
      this.buffer = '';
    }
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
  }
}

const bufferedOutput = new BufferedOutputStream((content) => {
  console.log('Flushed output:', content);
});

const bufferedError = new BufferedOutputStream((content) => {
  console.error('Flushed errors:', content);
});

pyodide.setStdout((msg) => bufferedOutput.write(msg + '\n'));
pyodide.setStderr((msg) => bufferedError.write(msg + '\n'));

// Test with rapid output
pyodide.runPython(`
    for i in range(100):
        print(f"Line {i}")
`);

// Ensure final flush
setTimeout(() => {
  bufferedOutput.flush();
  bufferedError.flush();
}, 100);

File-based I/O Redirection

// Redirect output to virtual files
pyodide.FS.writeFile('/tmp/output.log', '');
pyodide.FS.writeFile('/tmp/error.log', '');

pyodide.setStdout((message) => {
  const current = pyodide.FS.readFile('/tmp/output.log', { encoding: 'utf8' });
  pyodide.FS.writeFile('/tmp/output.log', current + message + '\n');
});

pyodide.setStderr((message) => {
  const current = pyodide.FS.readFile('/tmp/error.log', { encoding: 'utf8' });
  pyodide.FS.writeFile('/tmp/error.log', current + message + '\n');
});

// Run Python code
pyodide.runPython(`
    print("This goes to output.log")
    import sys
    sys.stderr.write("This goes to error.log\\n")
    
    import warnings
    warnings.warn("This is a warning")
`);

// Read logged output
const outputLog = pyodide.FS.readFile('/tmp/output.log', { encoding: 'utf8' });
const errorLog = pyodide.FS.readFile('/tmp/error.log', { encoding: 'utf8' });

console.log('Output log:', outputLog);
console.log('Error log:', errorLog);

Stream Multiplexing

class StreamMultiplexer {
  constructor() {
    this.handlers = {
      stdout: [],
      stderr: [],
      stdin: []
    };
  }
  
  addOutputHandler(handler) {
    this.handlers.stdout.push(handler);
  }
  
  addErrorHandler(handler) {
    this.handlers.stderr.push(handler);
  }
  
  addInputHandler(handler) {
    this.handlers.stdin.push(handler);
  }
  
  handleOutput(message) {
    this.handlers.stdout.forEach(handler => handler(message));
  }
  
  handleError(message) {
    this.handlers.stderr.forEach(handler => handler(message));
  }
  
  handleInput() {
    // Use first available input handler
    if (this.handlers.stdin.length > 0) {
      return this.handlers.stdin[0]();
    }
    return '';
  }
}

const multiplexer = new StreamMultiplexer();

// Add multiple output handlers
multiplexer.addOutputHandler((msg) => console.log(`[Console] ${msg}`));
multiplexer.addOutputHandler((msg) => {
  // Send to websocket, log file, etc.
  // websocket.send(JSON.stringify({type: 'stdout', message: msg}));
});

multiplexer.addErrorHandler((msg) => console.error(`[Console Error] ${msg}`));
multiplexer.addErrorHandler((msg) => {
  // Send errors to monitoring service
  // monitoringService.reportError(msg);
});

// Set up Pyodide with multiplexer
pyodide.setStdout((msg) => multiplexer.handleOutput(msg));
pyodide.setStderr((msg) => multiplexer.handleError(msg));
pyodide.setStdin(() => multiplexer.handleInput());

Stream Restoration

// Save original stream handlers for restoration
const originalHandlers = {
  stdout: null,
  stderr: null,
  stdin: null
};

function saveStreamHandlers() {
  // Note: Pyodide doesn't expose current handlers directly
  // This is conceptual - you'd need to track them in your app
  originalHandlers.stdout = console.log;
  originalHandlers.stderr = console.error;
  originalHandlers.stdin = () => prompt("Input:");
}

function restoreStreamHandlers() {
  pyodide.setStdout(originalHandlers.stdout);
  pyodide.setStderr(originalHandlers.stderr);
  pyodide.setStdin(originalHandlers.stdin);
}

// Custom temporary handlers
function withCustomStreams(customHandlers, callback) {
  saveStreamHandlers();
  
  if (customHandlers.stdout) pyodide.setStdout(customHandlers.stdout);
  if (customHandlers.stderr) pyodide.setStderr(customHandlers.stderr);
  if (customHandlers.stdin) pyodide.setStdin(customHandlers.stdin);
  
  try {
    return callback();
  } finally {
    restoreStreamHandlers();
  }
}

// Usage
withCustomStreams({
  stdout: (msg) => console.log(`[CUSTOM] ${msg}`),
  stderr: (msg) => console.error(`[CUSTOM ERROR] ${msg}`)
}, () => {
  pyodide.runPython(`
      print("This uses custom handlers")
      import sys
      sys.stderr.write("Custom error handler\\n")
  `);
});

// Original handlers are restored automatically
pyodide.runPython(`print("Back to original handlers")`);

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