A generic stream interceptor for sys.stdout or sys.stderr. Allows teeing output to pluggable handlers.
Project description
pyio_intercept
A generic, transparent stream interceptor for Python's standard I/O (sys.stdout, sys.stderr, sys.stdin) and file operations (builtins.open).
pyio_intercept allows you to tee, block, modify, or audit terminal and file data using a powerful action middleware chain without disrupting the original streams or crashing your application.
Features
- Middleware Action Chains: Pass data through multiple action handlers. Actions can pass, modify, or completely block the payload.
- Comprehensive Intercepts: Supports
sys.stdout,sys.stderr,sys.stdin, andbuiltins.open(files). - Context Manager: Supports scoped interception (e.g.,
with StdoutIntercept():). - Long-lived Mode: Persistent overriding via
.install()and.uninstall(). - Thread Safe: Middleware chains are executed using isolated snapshot copies, ensuring safe concurrent writes from multiple threads.
- Exception Isolation: Middleware errors are caught and logged directly to
sys.__stderr__without crashing the main application flow. - Context Aware: Middleware actions receive a
contextdictionary containing the active stream or file object being manipulated.
Installation
You can install this module directly using pip:
pip install .
For development (editable mode):
pip install -e .
The Middleware Pattern
All intercepts in pyio_intercept use an Action Chain. An action is simply a function that receives the current payload, a next_action callable to pass data down the chain, and a context dictionary containing metadata about the stream/file.
Calling Convention & Flow
payload: The data being read or written. For.write(), it's the string chunk. For.read(), it starts asNone.next_action(payload): The callback to the next middleware in the chain. You must return the result ofnext_action()if you want the data to continue flowing. If you returnNone(or omit it), the chain stops and the data is dropped.context: A dictionary containing contextual metadata (like{'stream': '<stdout>'}or{'file': <file_object>}).- End of the Chain: The final
next_actionis automatically bound to the original, intercepted function (e.g., the real OS-levelsys.__stdout__.writeor the real file's.read()).
ASCII Diagram: How Data Flows
Write Operations (e.g., print() or file.write()):
Data flows forward through the chain, transforming the payload before it hits the disk/terminal.
Application call: print("hello")
│
▼ (payload = "hello")
[ Action 1: uppercase ] ───(modifies payload to "HELLO")──┐
│
┌──────────────────────────────────────────────────┘
▼ (payload = "HELLO")
[ Action 2: prefix ] ──────(modifies payload to "[LOG] HELLO")──┐
│
┌────────────────────────────────────────────────────────┘
▼ (payload = "[LOG] HELLO")
[ Original Function ] (e.g. sys.__stdout__.write)
│
▼
Terminal Output: "[LOG] HELLO"
Read Operations (e.g., file.read() or sys.stdin.read()):
For reads, the call starts with a None payload and travels down the chain. The actual file/input content is retrieved at the bottom, and transformations are applied as the data flows back up the call stack to the application.
Request Flow (Call Stack) Response Flow (Data Returning)
========================= ==============================
Application: file.read() Application gets: "HELLO"
│ ▲
▼ (starts with payload=None) │ (returns "HELLO")
[ Action 1: uppercase ] [ Action 1: uppercase ]
│ ▲
▼ (calls next_action) │ (returns "hello")
[ Original Function ] [ Original Function ]
│ ▲
▼ (reads from disk) │ (retrieves)
Disk Disk (contains "hello")
Examples
1. Basic Mirroring (Stdout)
Tee output to a file while still printing to the terminal.
from pyio_intercept import StdoutIntercept
def logger_action(payload, next_action, context):
with open("app.log", "a") as f:
f.write(f"LOG: {payload}\n")
return next_action(payload)
with StdoutIntercept(actions=[logger_action]):
print("This goes to the terminal AND app.log")
2. Content Blocking (Stdout)
Block specific sensitive data from ever reaching the console.
from pyio_intercept import StdoutIntercept
def block_secrets(payload, next_action, context):
if "PASSWORD=" in payload:
return None # Blocks the payload entirely
return next_action(payload)
with StdoutIntercept(actions=[block_secrets]):
print("Connecting to DB...")
print("PASSWORD=super_secret_123") # Will not be printed!
3. Modifying Input Dynamically (Stdin)
Force all user input to be uppercase as it is read by the application.
from pyio_intercept import StdinIntercept
import sys
def force_uppercase(payload, next_action, context):
# For reads, the payload travels BACK up the chain
result = next_action(payload)
return result.upper() if result else result
with StdinIntercept(actions=[force_uppercase]):
# Any input typed here by the user is returned as uppercase
user_input = sys.stdin.readline()
4. File I/O Audit Tracking (FileIntercept)
Track exactly when files are opened and how many lines are written to them.
import time
from collections import defaultdict
from pyio_intercept import FileIntercept
audit_log = defaultdict(lambda: {"open_time": None, "lines_written": 0})
def on_file_open(file_obj, *args, **kwargs):
filename = getattr(file_obj, 'name', str(file_obj))
audit_log[filename]["open_time"] = time.time()
def track_lines(payload, next_action, context):
file_obj = context.get('file')
if file_obj and isinstance(payload, str) and payload:
lines = payload.count('\n')
if lines > 0:
audit_log[file_obj.name]["lines_written"] += lines
return next_action(payload)
with FileIntercept(actions=[track_lines], on_open=on_file_open):
with open("data.txt", "w") as f:
f.write("Line 1\nLine 2\n")
# audit_log now contains the open time and the fact that 2 lines were written!
5. On-the-fly Read Modification (File IO)
Capitalize text automatically as it is read from the disk.
from pyio_intercept import FileIntercept
def capitalize_read(payload, next_action, context):
result = next_action(payload)
return result.capitalize() if isinstance(result, str) else result
with FileIntercept(actions=[capitalize_read]):
with open("data.txt", "r") as f:
content = f.read() # Data is loaded from disk, then capitalized
Author
Jon Allen © 2026
License
This project is licensed under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyio_intercept-0.1.0.tar.gz.
File metadata
- Download URL: pyio_intercept-0.1.0.tar.gz
- Upload date:
- Size: 6.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c8f758fa0a76955d9bc8cb97edc48d2219081c5774395225531d94603c3b2c4
|
|
| MD5 |
722d9a38d38c9e811cd471d4cbc9d95a
|
|
| BLAKE2b-256 |
b0905d9919b7427e22c41091e0e87266c244d8ea122b38ce5e19c60b6256c9ce
|
File details
Details for the file pyio_intercept-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyio_intercept-0.1.0-py3-none-any.whl
- Upload date:
- Size: 7.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
77f72107653919c073a2e6d361371aae65777a870b4d0a519ab90676ece63f93
|
|
| MD5 |
f81248b021889e8426ab3461c1689db9
|
|
| BLAKE2b-256 |
f99c729481244fb26cb838728c98ae2a11617ae888e89aaad6c4d4ae1aba473a
|