Skip to main content

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

io-adapters API docs

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

io_adapters-0.3.4.tar.gz (7.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

io_adapters-0.3.4-py3-none-any.whl (11.0 kB view details)

Uploaded Python 3

File details

Details for the file io_adapters-0.3.4.tar.gz.

File metadata

  • Download URL: io_adapters-0.3.4.tar.gz
  • Upload date:
  • Size: 7.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for io_adapters-0.3.4.tar.gz
Algorithm Hash digest
SHA256 2414fab2b341bb7c24fa5400ff1fc2a2f9c53c7aba5a7e41586704b57d79bd67
MD5 bd5127ad64ce85cecab58b6468299266
BLAKE2b-256 a6d7b55d0a418b8bffe605663463ff347c9ad9a79061f7d8d58f08f798c59f22

See more details on using hashes here.

Provenance

The following attestation bundles were made for io_adapters-0.3.4.tar.gz:

Publisher: publish.yaml on second-ed/io-adapters

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file io_adapters-0.3.4-py3-none-any.whl.

File metadata

  • Download URL: io_adapters-0.3.4-py3-none-any.whl
  • Upload date:
  • Size: 11.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for io_adapters-0.3.4-py3-none-any.whl
Algorithm Hash digest
SHA256 763a1ca712015e54e1baf4ac0b6bd19609d38ee36a1fd5bea7905f70cf6da621
MD5 d6cb9daf21baac792cbf36a6bdb5b9d2
BLAKE2b-256 11aa3ed87c6c3078da104cd25aaa06e16f8366dc7e05a7e0df9be75c99abe4e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for io_adapters-0.3.4-py3-none-any.whl:

Publisher: publish.yaml on second-ed/io-adapters

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page