diff --git a/docs/examples/faststream.rst b/docs/examples/faststream.rst new file mode 100644 index 00000000..46543009 --- /dev/null +++ b/docs/examples/faststream.rst @@ -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 `_. + +The source code is available on the `Github `_. + +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/consumer.py + :language: python + +Listing of ``producer.py``: + +.. literalinclude:: ../../examples/miniapps/faststream/producer.py + :language: python + +Sources +------- + +Explore the sources on the `Github `_. + +.. include:: ../sponsor.rst + +.. disqus:: + diff --git a/docs/examples/index.rst b/docs/examples/index.rst index b166ceae..40f15470 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -23,5 +23,6 @@ Explore the examples to see the ``Dependency Injector`` in action. fastapi-redis fastapi-sqlalchemy fastdepends + faststream .. disqus:: diff --git a/examples/miniapps/faststream/Dockerfile b/examples/miniapps/faststream/Dockerfile new file mode 100644 index 00000000..a29c4f41 --- /dev/null +++ b/examples/miniapps/faststream/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.13-bookworm + +WORKDIR /app + +COPY requirements.txt ./ +RUN pip install -r requirements.txt + +COPY . ./ + +ENV PYTHONUNBUFFERED=1 diff --git a/examples/miniapps/faststream/README.rst b/examples/miniapps/faststream/README.rst new file mode 100644 index 00000000..aef51b4f --- /dev/null +++ b/examples/miniapps/faststream/README.rst @@ -0,0 +1,41 @@ +FastStream + Dependency Injector Example +======================================== + +This is a `FastStream `_ + +`Dependency Injector `_ 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. + + +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.' + + +Once you've done working with this example you can clean up docker images and containers it produced: + +.. code-block:: bash + + docker compose down --rmi local diff --git a/examples/miniapps/faststream/consumer.py b/examples/miniapps/faststream/consumer.py new file mode 100644 index 00000000..ce40e2b8 --- /dev/null +++ b/examples/miniapps/faststream/consumer.py @@ -0,0 +1,67 @@ +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, RedisRouter +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) + + config = providers.Configuration() + + broker = providers.Singleton(RedisBroker, config.redis_url, logger=None) + app = providers.Factory(FastStream, broker, logger=None) + + +class Message(BaseModel): + user: str + text: str + + +router = RedisRouter() + + +@router.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__]) + + container.config.redis_url.from_env("REDIS_URL") + + broker = container.broker() + broker.include_router(router) + + app = container.app() + await app.run() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/miniapps/faststream/docker-compose.yml b/examples/miniapps/faststream/docker-compose.yml new file mode 100644 index 00000000..b760e1df --- /dev/null +++ b/examples/miniapps/faststream/docker-compose.yml @@ -0,0 +1,24 @@ +name: faststream-example + +services: + + redis: + image: redis + + consumer: + build: . + environment: + REDIS_URL: "redis://redis" + depends_on: + - redis + entrypoint: python3 consumer.py + + producer: + build: . + environment: + REDIS_HOST: "redis" + REDIS_PORT: "6379" + depends_on: + - consumer + entrypoint: python3 producer.py + diff --git a/examples/miniapps/faststream/producer.py b/examples/miniapps/faststream/producer.py new file mode 100644 index 00000000..5ebc59de --- /dev/null +++ b/examples/miniapps/faststream/producer.py @@ -0,0 +1,37 @@ +import json +import time + +from dependency_injector import containers, providers +from redis import Redis + + +class Container(containers.DeclarativeContainer): + config = providers.Configuration() + + redis = providers.Singleton(Redis, config.redis_host, config.redis_port.as_int()) + + +def main(): + container = Container() + container.wire(modules=[__name__]) + + container.config.redis_host.from_env("REDIS_HOST") + container.config.redis_port.from_env("REDIS_PORT") + + redis = container.redis() + + 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} + redis.publish("messages", json.dumps(message)) + + +if __name__ == "__main__": + main() diff --git a/examples/miniapps/faststream/requirements.txt b/examples/miniapps/faststream/requirements.txt new file mode 100644 index 00000000..8d9a0c60 --- /dev/null +++ b/examples/miniapps/faststream/requirements.txt @@ -0,0 +1,4 @@ +dependency_injector +faststream +pydantic +redis diff --git a/examples/miniapps/faststream/run.sh b/examples/miniapps/faststream/run.sh new file mode 100755 index 00000000..f5fd00b9 --- /dev/null +++ b/examples/miniapps/faststream/run.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +docker compose up \ + --no-attach=redis \ + --abort-on-container-exit \ + --exit-code-from producer +