Skip to content

Commit 7e5c01a

Browse files
Add FastStream example to docs (#958)
1 parent 5f7aa1c commit 7e5c01a

9 files changed

Lines changed: 233 additions & 0 deletions

File tree

docs/examples/faststream.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.. _faststream-example:
2+
3+
FastStream example
4+
==================
5+
6+
.. meta::
7+
:keywords: Python,Dependency Injection,FastStream,Example
8+
:description: This example demonstrates a usage of FastStream with Dependency Injector.
9+
10+
11+
This example shows how to use ``Dependency Injector`` with `FastStream <https://github.com/ag2ai/faststream>`_.
12+
13+
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/faststream>`_.
14+
15+
Despite ``FastStream`` uses ``FastDepends`` library for dependency injection, the integration between
16+
``Dependency injector`` and ``FastStream`` has a small difference from already existing :ref:`fastdepends-example`.
17+
18+
Since ``FastStream`` also leverages function signatures to determine input data types you have to use ``Depends()`` function
19+
with ``cast=False`` argument to make ``FastStream`` ignore your injected dependency argument in the function signature.
20+
21+
Example below shows how to inject ``Counter`` class into ``FastStream`` redis handler so that it will distinguish between
22+
message schema (``User``) and injected dependency (``Counter``) and use them both correctly.
23+
24+
Listing of ``consumer.py``:
25+
26+
.. literalinclude:: ../../examples/miniapps/faststream/consumer.py
27+
:language: python
28+
29+
Listing of ``producer.py``:
30+
31+
.. literalinclude:: ../../examples/miniapps/faststream/producer.py
32+
:language: python
33+
34+
Sources
35+
-------
36+
37+
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/faststream>`_.
38+
39+
.. include:: ../sponsor.rst
40+
41+
.. disqus::
42+

docs/examples/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ Explore the examples to see the ``Dependency Injector`` in action.
2323
fastapi-redis
2424
fastapi-sqlalchemy
2525
fastdepends
26+
faststream
2627

2728
.. disqus::
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM python:3.13-bookworm
2+
3+
WORKDIR /app
4+
5+
COPY requirements.txt ./
6+
RUN pip install -r requirements.txt
7+
8+
COPY . ./
9+
10+
ENV PYTHONUNBUFFERED=1
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
FastStream + Dependency Injector Example
2+
========================================
3+
4+
This is a `FastStream <https://github.com/ag2ai/faststream>`_ +
5+
`Dependency Injector <https://python-dependency-injector.ets-labs.org/>`_ example application.
6+
7+
The example application is a simple consumer that counts messages sent to redis channel by producer.
8+
9+
Counter is provided to faststream handler as a dependency injected by ``dependency_injector`` library.
10+
11+
Run
12+
---
13+
14+
Everything can be run via docker compose.
15+
16+
A convenient ``run.sh`` script runs consumer, producer and redis services, prints logs from consumer
17+
and shuts down once producer exits.
18+
19+
20+
Run the sciprt:
21+
22+
.. code-block:: bash
23+
24+
./run.sh
25+
26+
The output should be something like:
27+
28+
.. code-block::
29+
30+
faststream-example-consumer | Message #1 from John: 'As you can see'
31+
faststream-example-consumer | Message #2 from John: 'messages are counted correctly'
32+
faststream-example-consumer | Message #3 from John: 'by the counter that is injected'
33+
faststream-example-consumer | Message #4 from John: 'into faststream handler'
34+
faststream-example-consumer | Message #5 from John: 'via awesome dependency_injector library.'
35+
36+
37+
Once you've done working with this example you can clean up docker images and containers it produced:
38+
39+
.. code-block:: bash
40+
41+
docker compose down --rmi local
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import asyncio
2+
from typing import Annotated
3+
4+
from dependency_injector import containers, providers
5+
from dependency_injector.wiring import Provide, inject
6+
from faststream import Depends, FastStream
7+
from faststream.redis import RedisBroker, RedisRouter
8+
from pydantic import BaseModel
9+
10+
11+
class Counter:
12+
def __init__(self):
13+
self.count = 0
14+
15+
def next(self) -> int:
16+
self.count += 1
17+
return self.count
18+
19+
20+
class Container(containers.DeclarativeContainer):
21+
counter = providers.Singleton(Counter)
22+
23+
config = providers.Configuration()
24+
25+
broker = providers.Singleton(RedisBroker, config.redis_url, logger=None)
26+
app = providers.Factory(FastStream, broker, logger=None)
27+
28+
29+
class Message(BaseModel):
30+
user: str
31+
text: str
32+
33+
34+
router = RedisRouter()
35+
36+
37+
@router.subscriber("messages")
38+
@inject
39+
async def handle_user_message(
40+
message: Message,
41+
counter: Annotated[
42+
Counter,
43+
Depends(
44+
Provide[Container.counter],
45+
cast=False, # <-- this is the key part
46+
),
47+
],
48+
) -> None:
49+
count = counter.next()
50+
print(f"Message #{count} from {message.user}: '{message.text}'")
51+
52+
53+
async def main() -> None:
54+
container = Container()
55+
container.wire(modules=[__name__])
56+
57+
container.config.redis_url.from_env("REDIS_URL")
58+
59+
broker = container.broker()
60+
broker.include_router(router)
61+
62+
app = container.app()
63+
await app.run()
64+
65+
66+
if __name__ == "__main__":
67+
asyncio.run(main())
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: faststream-example
2+
3+
services:
4+
5+
redis:
6+
image: redis
7+
8+
consumer:
9+
build: .
10+
environment:
11+
REDIS_URL: "redis://redis"
12+
depends_on:
13+
- redis
14+
entrypoint: python3 consumer.py
15+
16+
producer:
17+
build: .
18+
environment:
19+
REDIS_HOST: "redis"
20+
REDIS_PORT: "6379"
21+
depends_on:
22+
- consumer
23+
entrypoint: python3 producer.py
24+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import json
2+
import time
3+
4+
from dependency_injector import containers, providers
5+
from redis import Redis
6+
7+
8+
class Container(containers.DeclarativeContainer):
9+
config = providers.Configuration()
10+
11+
redis = providers.Singleton(Redis, config.redis_host, config.redis_port.as_int())
12+
13+
14+
def main():
15+
container = Container()
16+
container.wire(modules=[__name__])
17+
18+
container.config.redis_host.from_env("REDIS_HOST")
19+
container.config.redis_port.from_env("REDIS_PORT")
20+
21+
redis = container.redis()
22+
23+
for text in (
24+
"As you can see",
25+
"messages are counted correctly",
26+
"by the counter that is injected",
27+
"into faststream handler",
28+
"via awesome dependency_injector library.",
29+
):
30+
time.sleep(2)
31+
32+
message = {"user": "John", "text": text}
33+
redis.publish("messages", json.dumps(message))
34+
35+
36+
if __name__ == "__main__":
37+
main()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dependency_injector
2+
faststream
3+
pydantic
4+
redis
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env bash
2+
3+
docker compose up \
4+
--no-attach=redis \
5+
--abort-on-container-exit \
6+
--exit-code-from producer
7+

0 commit comments

Comments
 (0)