Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/examples/faststream.rst
Comment thread
birthdaysgift marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.. _faststream-example:

FastStream example
=============
Comment on lines +3 to +4
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
FastStream example
=============
FastStream example
==================

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you push the changes?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started commenting along with adding changes so I can track what's done and what is not.

Now I committed everything so you can check

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And thanks for the review btw :)


.. meta::
:keywords: Python,Dependency Injection,FastStream,Example
:description: This example demonstrates a usage of FastStream with Dependency Injector.


This example shows how to use ``Dependency Injector`` with `FastStream <https://dummy.faststream.airt.ai/0.5/faststream/>`_.
Comment thread
birthdaysgift marked this conversation as resolved.
Outdated

The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/faststream>`_.

Despite ``FastStream`` uses ``FastDepends`` library for dependency injection, the integration between
``Dependency injector`` and ``FastStream`` has a small difference from already existing :ref:`fastdepends` example.

Since ``FastStream`` also leverages function signatures to determine input data types you have to use ``Depends()`` function
with ``cast=False`` argument to make ``FastStream`` ignore your injected dependency argument in the function signature.

Example below shows how to inject ``Counter`` class into ``FastStream`` redis handler so that it will distinguish between
message schema (``User``) and injected dependency (``Counter``) and use them both correctly.

Listing of ``consumer.py``:

.. literalinclude:: ../../examples/miniapps/faststream/src/consumer.py
:language: python

Listing of ``producer.py``:

.. literalinclude:: ../../examples/miniapps/faststream/src/producer.py
:language: python

Sources
-------

Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/faststream>`_.

.. include:: ../sponsor.rst

.. disqus::

11 changes: 11 additions & 0 deletions examples/miniapps/faststream/Dockerfile.consumer
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python:3.13-bookworm

WORKDIR /app

COPY requirements.txt ./
RUN pip install -r requirements.txt

COPY ./src ./src

ENV PYTHONUNBUFFERED=1
CMD ["python3", "-m", "src.consumer"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For src layout, you have to do few extra steps in pyproject.toml. Either configure it properly (src should not be an importable package) or just use flat layout (without src/).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this suggestion relates to installable python packages (i.e. libraries). Since my example is just a runnable application I don't see why it should follow this approach.

Does it make a sense for runnable apps?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally prefer src layout for clear code separation. In larger projects your root of the repo tends to be polluted with other stuff (e.g. django's manage.py, tons of tool configs, etc...).

For example app I think it would be better to stick to flat layout, for simplicity. I.e. remove src/.

11 changes: 11 additions & 0 deletions examples/miniapps/faststream/Dockerfile.producer
Comment thread
ZipFile marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python:3.13-bookworm

WORKDIR /app

COPY requirements.txt ./
RUN pip install -r requirements.txt

COPY ./src ./src

ENV PYTHONUNBUFFERED=1
CMD ["python3", "-m", "src.producer"]
40 changes: 40 additions & 0 deletions examples/miniapps/faststream/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
FastStream + Dependency Injector Example
=====================================
Comment thread
birthdaysgift marked this conversation as resolved.
Outdated

This is a `FastStream <https://dummy.faststream.airt.ai/0.5/faststream/>`_ +
`Dependency Injector <https://python-dependency-injector.ets-labs.org/>`_ example application.

The example application is a simple consumer that counts messages sent to redis channel by producer.

Counter is provided to faststream handler as a dependency injected by ``dependency_injector`` library.

Run
---

Everything can be run via docker compose.

A convenient ``run.sh`` script runs consumer, producer and redis services, prints logs from consumer
and shuts down once producer exits.

Ensure that ``run.sh`` has execution permission:

.. code-block:: bash

sudo chmod +x ./run.sh
Comment thread
birthdaysgift marked this conversation as resolved.
Outdated

Run the sciprt:

.. code-block:: bash

./run.sh

The output should be something like:

.. code-block::

faststream-example-consumer | Message #1 from John: 'As you can see'
faststream-example-consumer | Message #2 from John: 'messages are counted correctly'
faststream-example-consumer | Message #3 from John: 'by the counter that is injected'
faststream-example-consumer | Message #4 from John: 'into faststream handler'
faststream-example-consumer | Message #5 from John: 'via awesome dependency_injector library.'

22 changes: 22 additions & 0 deletions examples/miniapps/faststream/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: faststream-example

services:

redis:
container_name: "${COMPOSE_PROJECT_NAME}-redis"
Comment thread
birthdaysgift marked this conversation as resolved.
Outdated
image: redis

consumer:
container_name: "${COMPOSE_PROJECT_NAME}-consumer"
Comment thread
birthdaysgift marked this conversation as resolved.
Outdated
build:
dockerfile: Dockerfile.consumer
depends_on:
- redis

producer:
container_name: "${COMPOSE_PROJECT_NAME}-producer"
Comment thread
birthdaysgift marked this conversation as resolved.
Outdated
build:
dockerfile: Dockerfile.producer
Comment thread
birthdaysgift marked this conversation as resolved.
Outdated
depends_on:
- consumer

42 changes: 42 additions & 0 deletions examples/miniapps/faststream/faststream.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.. _faststream-example:

FastStream example
=============

.. meta::
:keywords: Python,Dependency Injection,FastStream,Example
:description: This example demonstrates a usage of FastStream with Dependency Injector.


This example shows how to use ``Dependency Injector`` with `FastStream <https://dummy.faststream.airt.ai/0.5/faststream/>`_.

The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/faststream>`_.

Despite ``FastStream`` uses ``FastDepends`` library for dependency injection, the integration between
``Dependency injector`` and ``FastStream`` has a small difference from already existing :ref:`fastdepends` example.

Since ``FastStream`` also leverages function signatures to determine input data types you have to use ``Depends()`` function
with ``cast=False`` argument to make ``FastStream`` ignore your injected dependency argument in the function signature.

Example below shows how to inject ``Counter`` class into ``FastStream`` redis handler so that it will distinguish between
message schema (``User``) and injected dependency (``Counter``) and use them both correctly.

Listing of ``consumer.py``:

.. literalinclude:: ../../examples/miniapps/faststream/src/consumer.py
:language: python

Listing of ``producer.py``:

.. literalinclude:: ../../examples/miniapps/faststream/src/producer.py
:language: python

Sources
-------

Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/faststream>`_.

.. include:: ../sponsor.rst

.. disqus::

4 changes: 4 additions & 0 deletions examples/miniapps/faststream/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dependency_injector
faststream
pydantic
redis
15 changes: 15 additions & 0 deletions examples/miniapps/faststream/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash

Comment thread
birthdaysgift marked this conversation as resolved.
docker compose up \
--no-attach=redis \
--abort-on-container-exit \
--exit-code-from producer

docker container rm \
faststream-example-producer \
faststream-example-consumer \
faststream-example-redis

docker image rm \
faststream-example-producer \
faststream-example-consumer
Comment thread
birthdaysgift marked this conversation as resolved.
Outdated
Empty file.
57 changes: 57 additions & 0 deletions examples/miniapps/faststream/src/consumer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import asyncio
from typing import Annotated

from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
from faststream import Depends, FastStream
from faststream.redis import RedisBroker
from pydantic import BaseModel


class Counter:
def __init__(self):
self.count = 0

def next(self) -> int:
self.count += 1
return self.count


class Container(containers.DeclarativeContainer):
counter = providers.Singleton(Counter)


broker = RedisBroker("redis://redis", logger=None)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
counter = providers.Singleton(Counter)
broker = RedisBroker("redis://redis", logger=None)
config = providers.Configuration()
counter = providers.Singleton(Counter)
broker = providers.Resource(RedisBroker, config.redis_url, logger=None)
app = providers.Factory(FastStream, broker, logger=None)

If we're demonstrating integration, it would be better to go all-out.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to keep this example as small as possible to make key idea (adding cast=False to Depends(...)) to be clearly visible. In my opinion going full DI on this example will hide the important thing behind all other stuff that is not related to FastDepends integration.

But anyways I added full DI for consumer.py as a separate commit so you can decide whether it should be included or not.

So if you think that it's better to have full DI in this example I'll leave this decision up to you, I'm totally fine with both approaches.



class Message(BaseModel):
user: str
text: str


@broker.subscriber("messages")
@inject
async def handle_user_message(
message: Message,
counter: Annotated[
Counter,
Depends(
Provide[Container.counter],
cast=False, # <-- this is the key part
),
],
) -> None:
count = counter.next()
print(f"Message #{count} from {message.user}: '{message.text}'")


async def main() -> None:
container = Container()
container.wire(modules=[__name__])

await FastStream(broker, logger=None).run()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see comment above

Suggested change
await FastStream(broker, logger=None).run()
app = await container.app()
await app.run()



if __name__ == "__main__":
asyncio.run(main())

25 changes: 25 additions & 0 deletions examples/miniapps/faststream/src/producer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import json
import time

import redis


def main():
client = redis.Redis(host="redis", port=6379)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No DI container?

Copy link
Copy Markdown
Author

@birthdaysgift birthdaysgift Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, for the sake of simplicity)

since we already have fastapi-redis example do you think this example needs DI container for redis as well?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not exactly familiar with how you usually separate publishers and subscribers in your faststream apps, but I have gut feeling that you want to reuse same configuration/container for both.

In particular, I think it would be nice to reuse the same broker instead of directly sending messages with raw redis client. For the sake of demonstration. https://faststream.ag2.ai/latest/redis/pubsub/publishing/#basic-redis-channel-publishing

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

anyways, I added DI to producer.py in a separate commit, so you can decide whether it should be included or not


for text in (
"As you can see",
"messages are counted correctly",
"by the counter that is injected",
"into faststream handler",
"via awesome dependency_injector library.",
):
time.sleep(2)

message = {"user": "John", "text": text}
client.publish("messages", json.dumps(message))


if __name__ == "__main__":
main()