ProvenPin Install

injector Modules, @provider, singleton scope and ClassAssistedBuilder: working versions (tested 2026-10-18)

Wire a small app with injector Modules, a @provider method, singleton-scoped services and ClassAssistedBuilder assisted construction, then override bindings with a test module.

passed6 passed · 0 failed2026-10-18 03:56 UTC
Status
passed
Last passed
2026-10-18 03:56 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions2
injector@0.24.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 39095370682

The code

Exactly the files that passed. Your agent can pull them with get_recipe("injector-di-modules") (install), or see them on GitHub.

requirements.txt
injector==0.24.0
pytest==9.1.1
app.py
from __future__ import annotations

import sqlite3
from dataclasses import dataclass

from injector import (
    Binder,
    ClassAssistedBuilder,
    Injector,
    Module,
    NoInject,
    inject,
    provider,
    singleton,
)


@dataclass(frozen=True)
class Settings:
    dsn: str = ":memory:"
    prefix: str = "evt"


class Database:
    def __init__(self, dsn: str) -> None:
        self._conn = sqlite3.connect(dsn)
        self._conn.execute(
            "CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY, kind TEXT)"
        )

    def add(self, kind: str) -> int:
        cur = self._conn.execute("INSERT INTO events(kind) VALUES (?)", (kind,))
        self._conn.commit()
        return int(cur.lastrowid)

    def count(self) -> int:
        return int(self._conn.execute("SELECT COUNT(*) FROM events").fetchone()[0])


@singleton
class EventStore:
    @inject
    def __init__(self, db: Database) -> None:
        self._db = db

    def record(self, kind: str) -> int:
        return self._db.add(kind)

    def total(self) -> int:
        return self._db.count()


class Sink:
    def __init__(self, prefix: str) -> None:
        self.prefix = prefix
        self.lines: list[str] = []

    def write(self, message: str) -> None:
        self.lines.append(f"{self.prefix}: {message}")


class Report:
    @inject
    def __init__(self, store: EventStore, label: NoInject[str]) -> None:
        self.store = store
        self.label = label

    def render(self) -> str:
        return f"{self.label} ({self.store.total()})"


class AppModule(Module):
    def configure(self, binder: Binder) -> None:
        settings = Settings()
        binder.bind(Settings, to=settings)
        binder.bind(Database, to=Database(settings.dsn))
        binder.bind(EventStore, to=EventStore)

    @provider
    @singleton
    def provide_sink(self, settings: Settings) -> Sink:
        return Sink(prefix=settings.prefix)
tests/test_app.py
import injector
import pytest
from injector import Binder, ClassAssistedBuilder, Injector, Module, singleton

from app import AppModule, EventStore, Report, Sink


class InMemoryStore:
    def __init__(self) -> None:
        self.kinds: list[str] = []

    def record(self, kind: str) -> int:
        self.kinds.append(kind)
        return len(self.kinds)

    def total(self) -> int:
        return len(self.kinds)


class TestModule(Module):
    def configure(self, binder: Binder) -> None:
        binder.bind(EventStore, to=InMemoryStore, scope=singleton)


def test_singleton_scope_is_per_injector():
    inj = Injector([AppModule()])
    store = inj.get(EventStore)
    store.record("login")
    assert inj.get(EventStore) is store
    assert Injector([AppModule()]).get(EventStore) is not store


def test_provider_builds_singleton_sink_from_settings():
    inj = Injector([AppModule()])
    sink = inj.get(Sink)
    sink.write("boot")
    assert inj.get(Sink) is sink
    assert sink.lines == ["evt: boot"]


def test_class_assisted_builder_injects_deps_and_takes_runtime_arg():
    inj = Injector([AppModule()])
    inj.get(EventStore).record("login")
    report = inj.get(ClassAssistedBuilder[Report]).build(label="weekly")
    assert report.render() == "weekly (1)"


def test_assisted_builder_requires_the_noinject_argument():
    inj = Injector([AppModule()])
    with pytest.raises(injector.CallError):
        inj.get(ClassAssistedBuilder[Report]).build()


def test_override_binding_with_a_module():
    inj = Injector([AppModule(), TestModule()])
    store = inj.get(EventStore)
    store.record("signup")
    assert isinstance(store, InMemoryStore)
    assert inj.get(EventStore) is store


def test_constructor_injection_needs_the_inject_decorator():
    class NeedsStore:
        def __init__(self, store: EventStore) -> None:
            self.store = store

    with pytest.raises(injector.CallError):
        Injector([AppModule()]).get(NeedsStore)
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.