Dependency Injection Adapters
Project description
io-adapters
A small utility library for decoupling I/O from business logic by combining dependency injection with lightweight, automatically generated fakes.
Install
uv add io-adapters
API Reference
Testing use cases that involve I/O is inherently difficult because they depend on:
-
external state (filesystems, databases, services)
-
side effects that are hard to observe directly
-
slow or flaky infrastructure
A common mitigation is to combine:
-
Dependency Injection (DI)
-
The Repository / Adapter pattern
This allows business logic to depend on an abstract interface rather than concrete I/O.
However, in practice this usually requires:
-
writing and maintaining bespoke fake implementations
-
keeping fake behaviour in sync with real implementations
-
duplicating boilerplate across domains
For small or medium-sized projects, this overhead can outweigh the benefits.
Simply register each I/O function with one of the register decorators and the functionality will be added to the RealAdapter object, on top of that a stub will be added to the FakeAdapter object too so you can pass in either to your usecase and the functionality will work.
Example
from enum import Enum
from pathlib import Path
from io_adapters import (
IoAdapter,
RealAdapter,
add_domain,
get_fake_adapter,
get_real_adapter,
register_domain_read_fn,
register_domain_write_fn,
)
# you can use any hashable object to register an I/O function
class FileFormat(Enum):
JSON = "json"
add_domain("orders")
add_domain("payment")
@register_domain_read_fn("orders", "str")
def read_str(path: str | Path, **kwargs: dict) -> str: ...
# stack decorators to register the same function to multiple domains
@register_domain_read_fn("orders", FileFormat.JSON)
@register_domain_read_fn("payment", FileFormat.JSON)
def read_json(path: str | Path, **kwargs: dict) -> dict: ...
@register_domain_write_fn("orders", "str")
def write_str(data: dict, path: str | Path, **kwargs: dict) -> None: ...
@register_domain_write_fn("orders", FileFormat.JSON)
@register_domain_write_fn("payment", FileFormat.JSON)
def write_json(data: dict, path: str | Path, **kwargs: dict) -> None: ...
def some_usecase(adapter: IoAdapter, path: str) -> None:
adapter.read(path, "str")
# Some business logic
new_path = f"{path}_new.json"
adapter.write({"a": 1}, new_path, FileFormat.JSON)
# in production inject the real adapter
orders_adapter: RealAdapter = get_real_adapter("orders")
some_usecase(orders_adapter, "some/path/to/file.json")
# in testing inject the fake which has all the same funcitonality as the
# `RealAdapter` and assert that the fakes end state is as expected
fake = get_fake_adapter("orders")
some_usecase(fake, "some/path/to/file.json")
assert fake.files["some/path/to/file.json"] == {"a": 1}
Repo map
├── .github
│ └── workflows
│ ├── ci_tests.yaml
│ └── publish.yaml
├── .pytest_cache
│ └── README.md
├── docs
│ └── source
│ └── conf.py
├── src
│ └── io_adapters
│ ├── __init__.py
│ ├── _adapters.py
│ ├── _clock.py
│ ├── _container.py
│ ├── _io_funcs.py
│ └── _registries.py
├── tests
│ ├── __init__.py
│ ├── test_adapters.py
│ ├── test_adapters_apis.py
│ └── test_container.py
├── .pre-commit-config.yaml
├── README.md
├── pyproject.toml
├── ruff.toml
└── uv.lock
::
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 io_adapters-0.2.1.tar.gz.
File metadata
- Download URL: io_adapters-0.2.1.tar.gz
- Upload date:
- Size: 7.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c7df474211a69093061d9ee0616c94baeb4ed39c54a5ec7d5141f840bec5774
|
|
| MD5 |
9ef5649ba12cda9db18cac6d7e721005
|
|
| BLAKE2b-256 |
ddb71194ee6a8985a6b0b484a4865be5b6dbcfc5932745269b0981c940690b4b
|
Provenance
The following attestation bundles were made for io_adapters-0.2.1.tar.gz:
Publisher:
publish.yaml on second-ed/io-adapters
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
io_adapters-0.2.1.tar.gz -
Subject digest:
8c7df474211a69093061d9ee0616c94baeb4ed39c54a5ec7d5141f840bec5774 - Sigstore transparency entry: 803369089
- Sigstore integration time:
-
Permalink:
second-ed/io-adapters@2cfcec79678c55e4b12395f0544994af137ee91c -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/second-ed
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@2cfcec79678c55e4b12395f0544994af137ee91c -
Trigger Event:
release
-
Statement type:
File details
Details for the file io_adapters-0.2.1-py3-none-any.whl.
File metadata
- Download URL: io_adapters-0.2.1-py3-none-any.whl
- Upload date:
- Size: 10.4 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 |
2825adb9ee8e6f74a80633197799aae4d6ffcf91402b80ffc177395e44d001ce
|
|
| MD5 |
5cfe1aca055bce82c157dd62d1f22f69
|
|
| BLAKE2b-256 |
971c6549dab373aec5b1774572cff06293a25d5681601c83f372c72ef071c01d
|
Provenance
The following attestation bundles were made for io_adapters-0.2.1-py3-none-any.whl:
Publisher:
publish.yaml on second-ed/io-adapters
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
io_adapters-0.2.1-py3-none-any.whl -
Subject digest:
2825adb9ee8e6f74a80633197799aae4d6ffcf91402b80ffc177395e44d001ce - Sigstore transparency entry: 803369096
- Sigstore integration time:
-
Permalink:
second-ed/io-adapters@2cfcec79678c55e4b12395f0544994af137ee91c -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/second-ed
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@2cfcec79678c55e4b12395f0544994af137ee91c -
Trigger Event:
release
-
Statement type: