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.
Metadata
Release files for pyio-intercept 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pyio_intercept-0.1.0.tar.gz | 6.7 kB | Without provenance | Details for pyio_intercept-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyio_intercept-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for pyio_intercept-0.1.0-py3-none-any.whl |
Total release size: 14.1 kB
Release files / pyio_intercept-0.1.0.tar.gz
| Download URL | pyio_intercept-0.1.0.tar.gz |
|---|---|
| Size | 6.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9c8f758fa0a76955d9bc8cb97edc48d2219081c5774395225531d94603c3b2c4
|
|
BLAKE2b-256 checksum How to use checksums |
b0905d9919b7427e22c41091e0e87266c244d8ea122b38ce5e19c60b6256c9ce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.14
|
Release files / pyio_intercept-0.1.0-py3-none-any.whl
| Download URL | pyio_intercept-0.1.0-py3-none-any.whl |
|---|---|
| Size | 7.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
77f72107653919c073a2e6d361371aae65777a870b4d0a519ab90676ece63f93
|
|
BLAKE2b-256 checksum How to use checksums |
f99c729481244fb26cb838728c98ae2a11617ae888e89aaad6c4d4ae1aba473a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.14
|
Security for pyio-intercept 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.