Protocol-agnostic interaction interposition with lifecycle hooks for record, replay, and control.
Project description
interposition
Protocol-agnostic interaction interposition with lifecycle hooks for record, replay, and control.
Overview
Interposition is a Python library for replaying recorded interactions. Unlike VCRpy or other HTTP-specific tools, Interposition does not automatically hook into network libraries.
Instead, it provides a pure logic engine for storage, matching, and replay. You write the adapter for your specific target (HTTP client, database driver, IoT message handler), and Interposition handles the rest.
Key Features:
- Protocol-agnostic: Works with any protocol (HTTP, gRPC, SQL, Pub/Sub, etc.)
- Type-safe: Full mypy strict mode support with Pydantic v2
- Immutable: All data structures are frozen Pydantic models
- Serializable: Built-in JSON/YAML serialization for cassette persistence
- Memory-efficient: O(1) lookup with fingerprint indexing
- Streaming: Generator-based response delivery
Architecture
Interposition sits behind your application's data access layer. You provide the "Adapter" that captures live traffic or requests replay from the Broker.
+-------------+ +------------------+ +---------------+
| Application | <--> | Your Adapter | <--> | Interposition |
+-------------+ +------------------+ +---------------+
| |
(Traps calls) (Manages)
|
[Cassette]
Installation
pip install interposition
Practical Integration (Pytest Recipe)
The most common use case is using Interposition as a test fixture. Here is a production-ready recipe for pytest:
import pytest
from interposition import Broker, Cassette, InteractionRequest
@pytest.fixture
def cassette_broker():
# Load cassette from a JSON file (or create one programmatically)
with open("tests/fixtures/my_cassette.json", "rb") as f:
cassette = Cassette.model_validate_json(f.read())
return Broker(cassette)
def test_user_service(cassette_broker, monkeypatch):
# 1. Create your adapter (mocking your actual client)
def mock_fetch(url):
request = InteractionRequest(
protocol="http",
action="GET",
target=url,
headers=(),
body=b"",
)
# Delegate to Interposition
chunks = list(cassette_broker.replay(request))
return chunks[0].data
# 2. Inject the adapter
monkeypatch.setattr("my_app.client.fetch", mock_fetch)
# 3. Run your test
from my_app import get_user_name
assert get_user_name(42) == "Alice"
Protocol-Agnostic Examples
Interposition shines where HTTP-only tools fail.
SQL Database Query
request = InteractionRequest(
protocol="postgres",
action="SELECT",
target="users_table",
headers=(),
body=b"SELECT id, name FROM users WHERE id = 42",
)
# Replay returns: b'[(42, "Alice")]'
MQTT / PubSub Message
request = InteractionRequest(
protocol="mqtt",
action="subscribe",
target="sensors/temp/room1",
headers=(("qos", "1"),),
body=b"",
)
# Replay returns stream of messages: b'24.5', b'24.6', ...
Usage Guide
Manual Construction (Quick Start)
If you need to build interactions programmatically (e.g., for seeding tests):
from interposition import (
Broker,
Cassette,
Interaction,
InteractionRequest,
ResponseChunk,
)
# 1. Define the Request
request = InteractionRequest(
protocol="api",
action="query",
target="users/42",
headers=(),
body=b"",
)
# 2. Define the Response
chunks = (
ResponseChunk(data=b'{"id": 42, "name": "Alice"}', sequence=0),
)
# 3. Create Interaction & Cassette
interaction = Interaction(
request=request,
fingerprint=request.fingerprint(),
response_chunks=chunks,
)
cassette = Cassette(interactions=(interaction,))
# 4. Replay
broker = Broker(cassette=cassette)
response = list(broker.replay(request))
Persistence & Serialization
Interposition models are Pydantic v2 models, making serialization trivial.
# Save to JSON
with open("cassette.json", "w") as f:
f.write(cassette.model_dump_json(indent=2))
# Load from JSON
with open("cassette.json") as f:
cassette = Cassette.model_validate_json(f.read())
# Generate JSON Schema
schema = Cassette.model_json_schema()
Streaming Responses
For large files or streaming protocols, responses are yielded lazily:
# The broker returns a generator
for chunk in broker.replay(request):
print(f"Received chunk: {len(chunk.data)} bytes")
Error Handling
If a matching interaction is not found, the broker raises InteractionNotFoundError:
from interposition import InteractionNotFoundError
try:
broker.replay(unknown_request)
except InteractionNotFoundError as e:
print(f"Not recorded: {e.request.target}")
Development
Prerequisites
- Python 3.10+
- uv (recommended)
Setup & Testing
# Clone and install
git clone https://github.com/osoekawaitlab/interposition.git
cd interposition
uv pip install -e . --group=dev
# Run tests
nox -s tests
License
MIT
Project details
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 interposition-0.2.0.tar.gz.
File metadata
- Download URL: interposition-0.2.0.tar.gz
- Upload date:
- Size: 11.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3eff5d267d8fdb7c5bb6c1ceb50f13c4498612cd7827b52c57d6105bff8d6d0f
|
|
| MD5 |
2e74a20d6513a7ca473e5b0301bce91b
|
|
| BLAKE2b-256 |
d87b0374f3a18aad471731dd7e0410efee11a1cd3f01e024d50267cf759d7817
|
Provenance
The following attestation bundles were made for interposition-0.2.0.tar.gz:
Publisher:
release.yml on osoekawaitlab/interposition
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
interposition-0.2.0.tar.gz -
Subject digest:
3eff5d267d8fdb7c5bb6c1ceb50f13c4498612cd7827b52c57d6105bff8d6d0f - Sigstore transparency entry: 853812916
- Sigstore integration time:
-
Permalink:
osoekawaitlab/interposition@6d2a46b1e589344e2a083778d930e6ea58cda20d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/osoekawaitlab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6d2a46b1e589344e2a083778d930e6ea58cda20d -
Trigger Event:
push
-
Statement type:
File details
Details for the file interposition-0.2.0-py3-none-any.whl.
File metadata
- Download URL: interposition-0.2.0-py3-none-any.whl
- Upload date:
- Size: 9.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f7c960fe23abc02fb1b165fe344119a2637fa438a6abd663468b5ac124f6fb17
|
|
| MD5 |
8ca6b15e086e7049828e18b68110d20f
|
|
| BLAKE2b-256 |
e486cac47db550b0ee88e483f5804a39f0ba29e74e6c25350b934de14b236f0b
|
Provenance
The following attestation bundles were made for interposition-0.2.0-py3-none-any.whl:
Publisher:
release.yml on osoekawaitlab/interposition
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
interposition-0.2.0-py3-none-any.whl -
Subject digest:
f7c960fe23abc02fb1b165fe344119a2637fa438a6abd663468b5ac124f6fb17 - Sigstore transparency entry: 853812926
- Sigstore integration time:
-
Permalink:
osoekawaitlab/interposition@6d2a46b1e589344e2a083778d930e6ea58cda20d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/osoekawaitlab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6d2a46b1e589344e2a083778d930e6ea58cda20d -
Trigger Event:
push
-
Statement type: