Metadata-Version: 2.4
Name: canteen-di
Version: 0.1.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

Lightweight dependency injection for Python. Zero dependencies, full type safety, async/sync compatible.

Canteen handles three common injection patterns:

- **Singleton** — created once, cached forever
- **Factory** — new instance every call
- **Resource** — context manager with setup/teardown

The API is inspired by FastAPI's `Depends`, but works anywhere — no framework required.

## Install

Requires Python 3.11+.

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

The distribution is named `canteen-di` (the `canteen` name on PyPI belongs to an
unrelated project abandoned in 2015), but you import it as `canteen`:

```python
import canteen
```

## Quick start

Decorate any function to turn it into a provider. Use `Depends()` in default parameter values to declare dependencies between them.

```python
from canteen import singleton, factory, resource, Depends

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

@factory
def get_user_service(config: Config = Depends(get_config)) -> UserService:
    return UserService(config)

@resource
def get_db(config: Config = Depends(get_config)) -> Iterator[Session]:
    session = Session(config.db_url)
    try:
        yield session
    finally:
        session.close()
```

Call them like normal functions — dependencies resolve automatically:

```python
config = get_config()        # cached after first call
service = get_user_service() # new instance, config injected

with get_db() as session:    # context manager, config injected
    session.query(...)
```

Async providers work identically; the decorator preserves the sync/async nature of the function, so you `await` them and use `async with`.

Group related providers into a `Container` to get auto-wiring by parameter name and per-instance singleton caches:

```python
from canteen import Container

class AppContainer(Container):
    config = singleton(create_config)
    db = resource(create_db)
    user_service = factory(create_user_service)

app = AppContainer()
app.user_service()           # config auto-injected
```

## Testing

Swap any provider with `override()`, or pass replacements to a container constructor:

```python
with override(get_config, lambda: Config(env="test")):
    service = get_user_service()   # receives test config

app = AppContainer(config=singleton(lambda: Config(env="test")))
```

Overrides nest, and are safe across threads and asyncio tasks (backed by `contextvars`). Each container instance has independent state, so test isolation needs no cleanup.

## Rules

- **Resources** can depend on singletons, factories, and other resources. Nested resource lifetimes are managed automatically via `ExitStack`.
- **Singletons and factories** cannot depend on resources — use a resource provider instead.
- **Sync providers cannot depend on async providers.** Canteen raises rather than injecting an un-awaited coroutine.
- **`@resource`** requires a generator function that yields exactly once.
- **Dependency cycles** are rejected when a container is constructed.

Violations raise a subclass of `CanteenError`: `ProviderError` (also a `TypeError`), `CircularDependencyError`, or `ResourceError` (also a `RuntimeError`).

## Documentation

Full guides — providers, dependencies, containers, async, and testing — live in [`docs/`](docs/).

## Development

```sh
uv sync
uv run pytest          # tests
uv run ruff check .    # lint
uv run mypy            # types
```

## License

MIT — see [LICENSE](LICENSE).
