Metadata-Version: 2.4
Name: canteen-di
Version: 0.3.0
Summary: Lightweight dependency injection for Python
Keywords: dependency-injection,di,ioc,async,typing
Author: Marc Ammann
Author-email: Marc Ammann <marc@mattersupply.co>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Framework :: AsyncIO
Classifier: Typing :: Typed
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/easy-days/canteen
Project-URL: Repository, https://github.com/easy-days/canteen
Project-URL: Issues, https://github.com/easy-days/canteen/issues
Project-URL: Changelog, https://github.com/easy-days/canteen/blob/main/CHANGELOG.md
Description-Content-Type: text/markdown

# Canteen

Canteen is a dependency injection library for Python 3.11 and later. It has no
runtime dependencies and supports both synchronous and asynchronous providers.

Enter a container with `with` or `async with` to open its resources, then call
resource attributes to obtain their values. `dependency(T)` accepts externally
owned values, including an active parent container.
See the [FastAPI and HTTPX2 example](examples/fastapi_app/README.md) for the
two-container pattern with a real HTTP connection pool and a local upstream.

Use a singleton to reuse a value, a factory to create one on each call, or a
resource to manage something that needs closing. Dependencies are declared with
`Depends()`, using a syntax familiar to FastAPI users.

## Install

```sh
uv add canteen-di
```

The package name is `canteen-di`; the Python import is `canteen`.

## A first example

```python
from dataclasses import dataclass

from canteen import Depends, factory, singleton

@dataclass
class Config:
    greeting: str = "Hello"

@dataclass
class Greeter:
    config: Config

    def greet(self, name: str) -> str:
        return f"{self.config.greeting}, {name}"

@singleton
def get_config() -> Config:
    return Config()

@factory
def get_greeter(config: Config = Depends(get_config)) -> Greeter:
    return Greeter(config)

assert get_greeter().greet("Sam") == "Hello, Sam"
assert get_config() is get_config()
assert get_greeter() is not get_greeter()
```

Calling `get_greeter()` resolves `get_config()` and passes its result to the
function. The configuration is cached; the greeter is created on each call.

## Resources

Resource functions yield once. Put cleanup in `finally` so it also runs when
the caller raises an exception.

```python
from collections.abc import Iterator
from sqlite3 import Connection, connect

from canteen import resource

@resource
def database() -> Iterator[Connection]:
    connection = connect(":memory:")
    try:
        yield connection
    finally:
        connection.close()

with database() as connection:
    assert connection.execute("SELECT 1").fetchone() == (1,)
```

A resource can depend on another resource. Dependencies are entered first and
closed last. Singletons and factories cannot depend on resources because they
have no context-manager lifetime in which to close them.

## Containers and tests

A container groups providers and gives each instance its own singleton caches.
Required parameter names are matched to provider attributes.

Continuing the greeter example:

```python
from canteen import Container

def create_greeter(config: Config) -> Greeter:
    return Greeter(config)

class App(Container):
    config = singleton(Config)
    greeter = factory(create_greeter)

app = App()
test_app = App(config=singleton(lambda: Config(greeting="Hi")))

assert app.greeter().greet("Sam") == "Hello, Sam"
assert test_app.greeter().greet("Sam") == "Hi, Sam"
```

Constructor replacements must be providers. For standalone providers, a scoped
override takes a callable:

```python
from canteen import override

with override(get_config, lambda: Config(greeting="Hi")):
    assert get_greeter().greet("Sam") == "Hi, Sam"
```

A singleton cannot resolve while one of its dependencies has a scoped override,
even if its value is already cached. Override that singleton directly or use a
fresh container with constructor replacements.

## Async usage

Decorate an `async def` function with `@singleton` or `@factory` and await the
provider. Use `@resource` on an async generator and enter it with `async with`.

Async providers can call sync dependencies, but sync providers cannot call async
dependencies. Sync work runs on the calling thread; Canteen does not move it to
a worker thread.

An async singleton belongs to the event loop of its first non-overridden
resolution. Create a fresh container for each application or test loop.

## Documentation

The [guides](docs/index.mdx) cover providers, dependency binding, containers,
async usage, and testing. See the [changelog](CHANGELOG.md) for release notes and
migration instructions.

## Development

```sh
uv sync
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv run basedpyright
```

## License

[MIT](LICENSE).
