Skip to content

Commit b30121c

Browse files
authored
docs: make code examples run and fix factual errors (#260)
- context-resources: add default_scope to the quick-start container, pass a context item where container_context/DIContextMiddleware had no arguments, use the real sync-context error message, list only global_context and scope as middleware arguments - scopes: ContextScopes.APP, return val, nested-scope example no longer re-enters p in REQUEST, default_scope caveat - fastapi: scoped middleware example uses MyScopedContainer with scope=REQUEST, sync override helpers, missing imports - factories: call provider/provider_sync, import ContextScopes from that_depends, context example uses a Factory, drop closing sentence - injection, generator-injection, ioc-container, object, index, lazy: syntax and name errors - multiple-containers, faststream, litestar: inline definitions instead of importing the repo's tests package; Litestar lifespan type - README, resources, provider-overriding, contributing: wording fixes
1 parent 1f485a0 commit b30121c

17 files changed

Lines changed: 114 additions & 68 deletions

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121

2222
Simple, typed dependency injection framework for Python.
2323

24-
It is production-ready and gives you the following:
24+
Features:
2525
- Simple async-first DI framework with IOC-container.
2626
- Python 3.10+ support.
2727
- Full coverage by types annotations (mypy in strict mode, pyrefly).
28-
- Inbuilt FastAPI, FastStream and LiteStar compatibility.
28+
- Built-in FastAPI, FastStream and Litestar integrations.
2929
- Dependency context management with scopes.
3030
- Overriding dependencies for tests.
3131
- Injecting dependencies in functions and coroutines without wiring.

‎docs/dev/contributing.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Contributing
2-
`that-depends` is an opensource project, and we are opened to new contributors.
2+
`that-depends` is an opensource project, and we are open to new contributors.
33

44
## Getting started
55
1. Make sure that you have [uv](https://docs.astral.sh/uv/) and [just](https://just.systems/) installed.
66
2. Clone project:
77
```
8-
git@github.com:modern-python/that-depends.git
8+
git clone git@github.com:modern-python/that-depends.git
99
cd that-depends
1010
```
1111
3. Install dependencies running `just install`

‎docs/experimental/lazy.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ You can use the lazy provider in exactly the same way as you would use the refer
2929

3030
```python
3131
# first_container.py
32+
import typing
33+
3234
from that_depends import BaseContainer, providers, ContextScopes
3335

34-
def my_creator() -> int:
36+
def my_creator() -> typing.Iterator[int]:
3537
yield 42
3638

3739
class FirstContainer(BaseContainer):

‎docs/index.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ supports the following:
3636

3737
### Define a creator
3838
```python
39+
import logging
40+
41+
logger = logging.getLogger(__name__)
42+
43+
3944
async def create_async_resource():
4045
logger.debug("Async resource initiated")
4146
try:

‎docs/integrations/fastapi.md‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ This will enable you to use dependency injection in your `FastAPI` endpoints:
9393
If you wish to initialize the container context you can simply pass arguments to `create_fastapi_route_class()`:
9494

9595
```python
96-
my_route_class = create_fastapi_route_class(Container, global_context={"key": "value"}, scope=ContextScopes.REQUEST)
96+
from that_depends import ContextScopes
97+
98+
my_route_class = create_fastapi_route_class(MyContainer, global_context={"key": "value"}, scope=ContextScopes.REQUEST)
9799
```
98100

99101
In the above example, all `ContextResources` in our container that are `REQUEST` scoped will be initialized
@@ -220,13 +222,14 @@ Then in your FastAPI app:
220222
```python
221223
# main.py
222224
from fastapi import FastAPI, Depends
223-
from that_depends.providers.context_resources import DIContextMiddleware
225+
from that_depends.providers.context_resources import ContextScopes, DIContextMiddleware
224226
from mycontainer import MyScopedContainer
225227

226228
app = FastAPI()
227229
app.add_middleware(
228230
DIContextMiddleware,
229-
MyContainer,
231+
MyScopedContainer,
232+
scope=ContextScopes.REQUEST,
230233
)
231234

232235
@app.get("/")
@@ -269,9 +272,9 @@ def test_read_db(client: TestClient):
269272

270273
1. **Global vs. Request Context**: Decide whether your container’s dependencies should be globally shared (e.g., singletons) or created anew per request (e.g., database or session).
271274
2. **Combining with FastAPI’s `Depends`**: Generally, you can pass `Depends(MyContainer.some_provider)` to route handlers. Under the hood, that-depends will be invoked.
272-
3. **Overriding**: You can override a provider in tests by calling `MyContainer.some_provider.override(...)` or using the context manager `with MyContainer.some_provider.override_context(...):`.
275+
3. **Overriding**: You can override a provider in tests by calling `MyContainer.some_provider.override_sync(...)` or using the context manager `with MyContainer.some_provider.override_context_sync(...):`.
273276
4. **Performance**: If you have expensive creation logic (like a DB engine that can be reused globally), prefer using a `Singleton` or `Object` provider. If you need ephemeral resources, use `ContextResource` with the `DIContextMiddleware`.
274-
5. **Custom Context**: If you do not want to rely on the middleware, you can manually create a context in any async function by calling `async with container_context():`.
277+
5. **Custom Context**: If you do not want to rely on the middleware, you can manually create a context in any async function by calling `async with container_context(MyContainer):`.
275278
6. **Multiple Containers**: You can define multiple containers and connect them (e.g., `ContainerA.connect_containers(ContainerB)`), or add them all to the `DIContextMiddleware`. For advanced usage, see the that-depends documentation on “container connection.”
276279

277280

@@ -285,7 +288,7 @@ Sometimes you want to pass the `fastapi.Request` (or other request-scoped data)
285288
# request_deps.py
286289
from fastapi import Request
287290
from typing import AsyncIterator
288-
from that_depends import container_context, fetch_context_item
291+
from that_depends import container_context
289292

290293
async def init_di_context(request: Request) -> AsyncIterator[None]:
291294
# We store the request in a that_depends global context
@@ -299,6 +302,7 @@ Then in your `FastAPI` route:
299302
# main.py (extended)
300303
from fastapi import FastAPI, Request, Depends
301304
from starlette.responses import JSONResponse
305+
from that_depends import fetch_context_item
302306

303307
from request_deps import init_di_context
304308
from mycontainer import MyContainer

‎docs/integrations/faststream.md‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,22 +56,37 @@ broker = RabbitBroker(middlewares=[DIContextMiddleware(Container, scope=ContextS
5656
Here is an example that includes life-cycle events:
5757

5858
```python
59-
import datetime
6059
import contextlib
60+
import dataclasses
61+
import datetime
6162
import typing
6263

6364
from faststream import FastStream, Depends, Logger
6465
from faststream.rabbit import RabbitBroker
6566

66-
from tests import container
67+
from that_depends import BaseContainer, providers
68+
69+
70+
async def create_async_resource() -> typing.AsyncIterator[datetime.datetime]:
71+
yield datetime.datetime.now(tz=datetime.timezone.utc)
72+
73+
74+
@dataclasses.dataclass(kw_only=True, slots=True)
75+
class DependentFactory:
76+
async_resource: datetime.datetime
77+
78+
79+
class DIContainer(BaseContainer):
80+
async_resource = providers.Resource(create_async_resource)
81+
dependent_factory = providers.Factory(DependentFactory, async_resource=async_resource.cast)
6782

6883

6984
@contextlib.asynccontextmanager
7085
async def lifespan_manager() -> typing.AsyncIterator[None]:
7186
try:
7287
yield
7388
finally:
74-
await container.DIContainer.tear_down()
89+
await DIContainer.tear_down()
7590

7691

7792
broker = RabbitBroker()
@@ -82,8 +97,8 @@ app = FastStream(broker, lifespan=lifespan_manager)
8297
async def read_root(
8398
logger: Logger,
8499
some_dependency: typing.Annotated[
85-
container.DependentFactory,
86-
Depends(container.DIContainer.dependent_factory)
100+
DependentFactory,
101+
Depends(DIContainer.dependent_factory)
87102
],
88103
) -> datetime.datetime:
89104
startup_time = some_dependency.async_resource

‎docs/integrations/litestar.md‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,23 @@
66
sibling DI framework.
77

88
```python
9-
import typing
10-
import fastapi
119
import contextlib
10+
import typing
11+
1212
from litestar import Litestar, get
1313
from litestar.di import Provide
1414
from litestar.status_codes import HTTP_200_OK
1515
from litestar.testing import TestClient
1616

17-
from tests import container
17+
from that_depends import BaseContainer, providers
18+
19+
20+
async def create_async_resource() -> typing.AsyncIterator[str]:
21+
yield "async resource"
22+
23+
24+
class DIContainer(BaseContainer):
25+
async_resource = providers.Resource(create_async_resource)
1826

1927

2028
@get("/")
@@ -23,16 +31,16 @@ async def index(injected: str) -> str:
2331

2432

2533
@contextlib.asynccontextmanager
26-
async def lifespan_manager(_: fastapi.FastAPI) -> typing.AsyncIterator[None]:
34+
async def lifespan_manager(_: Litestar) -> typing.AsyncIterator[None]:
2735
try:
2836
yield
2937
finally:
30-
await container.DIContainer.tear_down()
38+
await DIContainer.tear_down()
3139

3240

3341
app = Litestar(
3442
route_handlers=[index],
35-
dependencies={"injected": Provide(container.DIContainer.async_resource)},
43+
dependencies={"injected": Provide(DIContainer.async_resource)},
3644
lifespan=[lifespan_manager],
3745
)
3846

‎docs/introduction/generator-injection.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def injected(val: float = Provide[Container.dependent_provider]) -> typing.Gener
109109
yield val
110110

111111
# This will raise a `ContextProviderError`!
112-
next(_injected())
112+
next(injected())
113113
```
114114

115115
1. Matches context scope of `sync_provider` provider, which is a dependency of the `dependent_provider` provider.
@@ -136,7 +136,7 @@ def injected(val: float = Provide[Container.dependent_provider]) -> typing.Gener
136136

137137
with container_context(scope=ContextScopes.REQUEST):
138138
# This will resolve as expected
139-
next(_injected())
139+
next(injected())
140140
```
141141

142142
Since no context initialization was needed, the generator will work as expected.

‎docs/introduction/injection.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def greet_user_direct(
8484
return f"Greeting: {greeting}"
8585
```
8686

87-
1. Notice that although `greeting` is a `str`, `mypy` and you IDE will not complain.
87+
1. Notice that although `greeting` is a `str`, `mypy` and your IDE will not complain.
8888

8989
---
9090

@@ -123,7 +123,7 @@ For more details regarding scopes and context management, see the [Context Resou
123123

124124
## Overriding Providers
125125

126-
In tests or specialized scenarios, you may want to override a provider’s value temporarily. You can do so with the container’s `override_providers()` method or the provider’s own `override_context()`:
126+
In tests or specialized scenarios, you may want to override a provider’s value temporarily. You can do so with the container’s `override_providers_sync()` method or the provider’s own `override_context_sync()`:
127127

128128
```python
129129
def test_greet_override():
@@ -135,7 +135,7 @@ def test_greet_override():
135135

136136
This is especially helpful for unit tests where you want to substitute real dependencies (e.g., database connections) with mocks or stubs.
137137

138-
For more details on overring providers, see the [Overriding Providers](../testing/provider-overriding.md) documentation.
138+
For more details on overriding providers, see the [Overriding Providers](../testing/provider-overriding.md) documentation.
139139

140140
---
141141

‎docs/introduction/ioc-container.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ from that_depends import BaseContainer
1515

1616
class Container(BaseContainer):
1717
# define your providers here
18+
...
1819
```
1920

2021
Then you can build your dependency graph within the container:
@@ -27,9 +28,9 @@ class Container(BaseContainer):
2728
session = providers.Factory(create_db_session, config=config.db) # (1)!
2829

2930
user_repository = providers.Factory(
30-
UserRepository,
31+
UserRepository,
32+
config.users,
3133
session=session.cast, # (3)!
32-
config.users
3334
) # (2)!
3435

3536

0 commit comments

Comments
 (0)