Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ lint.ignore = [
"PLW2901",
"RET505",
"PLR0913",
"UP038",
"TCH001",
]

[lint.per-file-ignores]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ from dishka.integrations.fastapi import (

@router.get("/")
@inject
async def index(a: Annotated[A, FromDishka()]) -> str:
async def index(a: FromDishka[A]) -> str:
...

...
Expand All @@ -127,7 +127,7 @@ setup_dishka(container, app)
**Dependency** is what you need for some part of your code to work. They are just object which you do not create in place and probably want to replace some day. At least for tests.
Some of them can live while you application is running, others are destroyed and created on each request. Dependencies can depend on other objects, which are their dependencies.

**Scope** is a lifespan of a dependency. Standard scopes are:
**Scope** is a lifespan of a dependency. Standard scopes are (without skipped ones):

`APP` -> `REQUEST` -> `ACTION` -> `STEP`.

Expand Down
3 changes: 3 additions & 0 deletions docs/advanced/components.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,6 @@ Components can **link to each other**: each provider can add a component name wh

a = alias(int, component="X")


.. note::
In frameworks integrations ``FromDishka[T]`` is used to get an object from default component. To use other component you can use the same syntax with annotated ``Annotated[T, FromComponent("X")]``
82 changes: 82 additions & 0 deletions docs/advanced/scopes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
.. include:: <isonum.txt>

Scope management
*************************

In dishka scope determines a lifespan of dependency. Firstly, when creating provider, your attach dependency to its scope. Then, when you use container you enter scope and dependency is retained once it is requested until you exist that scope.

The set of scopes is defined once per container and providers should use the same scopes. You are not limited to standard scopes and can create custom ones, but it is hardly ever needed.

In most cases you need only 2 scopes. ``APP``-scope is usually entered on container creation and ``REQUEST``-scope is the on you go into during some event processing:

``APP`` |rarr| ``REQUEST``

But standard set is much wider. Dishka supported *skipped* scopes and has just 2 additional just in case:

``[RUNTIME]`` |rarr| ``APP`` |rarr| ``[SESSION]`` |rarr| ``REQUEST`` |rarr| ``ACTION`` |rarr| ``STEP``

Entering container context will bring you to the next non-skipped scope. Or to the target scope if you provided one.

Skipped scopes
======================

Skipped scope in the scope which is implicitly passed through when you are going deeper from parent container.


For example, standard scopes ``RUNTIME`` and ``SESSION`` are marked as ``skip=True``:

When you create a container, you implicitly enter ``APP``-scope. ``RUNTIME`` scope is still used, you can have dependencies attached to it, but it is entered automatically and also closed once you close ``APP``-scoped container.

.. code-block:: python

container = make_container(provider) # APP

In other case you can enter ``RUNTIME`` scope passing ``start_scope=Scope.RUNTIME`` when entering container and you will get that scope. When you go deeper you will enter ``APP`` scope as it is the next appropriate one. In that case, closing ``APP`` scope won't close ``RUNTIME`` as it was entered explicitly.

.. code-block:: python

container = make_container(provider, start_scope=Scope.RUNTIME)
with container() as app_container:
# RUNTIME -> APP

The same thing is about going into when you are in ``APP`` scope. If you just call ``with container()`` you will skip ``SESSION`` scope and go into ``REQUEST`` one. Both will be closed simultaneously. Calling ``with container(scope=Scope.SESSION)`` will bring you to that scope and you can go into ``REQUEST`` with the next call.


.. code-block:: python

container = make_container(provider)
with container() as request_container:
# APP -> Session -> REQUEST
pass

with container(scope=Scope.SESSION) as session_container:
# APP -> Session
with session_container() as request_container:
# Session -> REQUEST
pass

.. note::

* ``RUNTIME`` scope can be useful for adding dependencies which are kept between tests recreating apps.
* ``SESSION`` scope can be useful for connection related objects in websockets, while HTTP-request handler goes straight into ``REQUEST`` scope.

Custom scopes
======================

To create a custom scopes set you need

* inherit from ``BaseScope``
* set scopes using ``new_scope()``
* provide that scope class to ``make_container`` call as a ``scopes=`` argument

.. code-block:: python

from dishka import BaseScope, Provider, make_container, new_scope

class MyScope(BaseScope):
APPLICATION = new_scope("APPLICATION")
SESSION = new_scope("SESSION", skip=True)
EVENT = new_scope("EVENT")

provider = Provider(scope=MyScope.EVENT)
make_container(provider, scopes=MyScope)
2 changes: 1 addition & 1 deletion docs/advanced/testing/app_before.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

@router.get("/")
@inject
async def index(connection: Annotated[Connection, FromDishka()]) -> str:
async def index(connection: FromDishka[Connection]) -> str:
connection.execute("select 1")
return "Ok"

Expand Down
2 changes: 1 addition & 1 deletion docs/advanced/testing/test_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

@router.get("/")
@inject
async def index(connection: Annotated[Connection, FromDishka()]) -> str:
async def index(connection: FromDishka[Connection]) -> str:
connection.execute("select 1")
return "Ok"

Expand Down
2 changes: 1 addition & 1 deletion docs/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Scope
**Scope** is a lifespan of a dependency.
Some dependencies can live while you application is running, others are created and destroyed on each request. In more rare cases you need more short-lived objects. You set a scope for your dependency when you configure how to create it.

Standard scopes are:
Standard scopes are (excluding skipped):

``APP`` |rarr| ``REQUEST`` |rarr| ``ACTION`` |rarr| ``STEP``

Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This library is targeting to provide only an IoC-container but make it really us
container/index
integrations/index
advanced/components
advanced/scopes
advanced/testing/index
alternatives
contributing
Expand Down
10 changes: 5 additions & 5 deletions docs/integrations/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ For several frameworks library contains helper functions so you don't need to co
To use framework integration you mainly need to do 3 things:

* call ``setup_dishka`` on your container and framework entity
* add ``Annotated[YourClass, FromDishka()]`` on you framework handlers (or view-functions)
* add ``FromDishka[YourClass]`` on you framework handlers (or view-functions)
* decorate your handlers with ``@inject`` before registering them in framework. Some integrations do not required it, see :ref:`autoinject`

For FastAPI it will look like:
Expand All @@ -35,7 +35,7 @@ For FastAPI it will look like:

@router.get("/")
@inject
async def index(interactor: Annotated[Interactor, FromDishka()]) -> str:
async def index(interactor: FromDishka[Interactor]) -> str:
result = interactor()
return result

Expand Down Expand Up @@ -64,7 +64,7 @@ With some frameworks we provide an option to inject dependencies in handlers wit
@router.message()
async def start(
message: Message,
user: Annotated[User, FromDishka()],
user: FromDishka[User],
):
await message.answer(f"Hello, {1}, {user.full_name}!")

Expand All @@ -80,7 +80,7 @@ With some frameworks we provide an option to inject dependencies in handlers wit
@app.get("/"
def index(
*,
interactor: Annotated[Interactor, FromDishka()],
interactor: FromDishka[Interactor],
) -> str:
result = interactor()
return result
Expand All @@ -98,7 +98,7 @@ With some frameworks we provide an option to inject dependencies in handlers wit
@router.get("/")
async def index(
*,
interactor: Annotated[Interactor, FromDishka()],
interactor: FromDishka[Interactor],
) -> str:
result = interactor()
return result
Expand Down
2 changes: 1 addition & 1 deletion docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ This can be also rewritten using class:

@router.get("/")
@inject
async def index(a: Annotated[A, FromDishka()]) -> str:
async def index(a: FromDishka[A]) -> str:
...

...
Expand Down
4 changes: 2 additions & 2 deletions examples/integrations/aiogram_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ async def get_name(self, request: TelegramObject) -> User:
@inject # if auto_inject=True is specified in the setup_dishka, then you do not need to specify a decorator
async def start(
message: Message,
user: Annotated[User, FromDishka()],
value: Annotated[int, FromDishka()],
user: FromDishka[User],
value: FromDishka[int],
):
await message.answer(f"Hello, {1}, {user.full_name}!")

Expand Down
2 changes: 1 addition & 1 deletion examples/integrations/aiohttp_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class GatewayProvider(Provider):
@router.get('/')
@inject
async def endpoint(
request: str, gateway: Annotated[Gateway, FromDishka()],
request: str, gateway: FromDishka[Gateway],
) -> Response:
data = await gateway.get()
return Response(text=f'gateway data: {data}')
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class GatewayProvider(Provider):
@inject
async def get_content(
context: dict[Any, Any],
gateway: Annotated[Gateway, FromDishka()],
gateway: FromDishka[Gateway],
):
result = await gateway.get()
logger.info(result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class GatewayProvider(Provider):
@inject
async def get_content(
context: dict[Any, Any],
gateway: Annotated[Gateway, FromDishka()],
gateway: FromDishka[Gateway],
):
result = await gateway.get()
logger.info(result)
Expand Down
4 changes: 2 additions & 2 deletions examples/integrations/fastapi_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class InteractorProvider(Provider):
@inject
async def index(
*,
interactor: Annotated[Interactor, FromDishka()],
interactor: FromDishka[Interactor],
) -> str:
result = interactor()
return result
Expand All @@ -72,7 +72,7 @@ async def index(
@second_router.get("/auto")
async def auto(
*,
interactor: Annotated[Interactor, FromDishka()],
interactor: FromDishka[Interactor],
) -> str:
result = interactor()
return result
Expand Down
4 changes: 2 additions & 2 deletions examples/integrations/faststream_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def get_b(self, a: A) -> B:
@inject
async def handler(
msg: str,
a: Annotated[A, FromDishka()],
b: Annotated[B, FromDishka()],
a: FromDishka[A],
b: FromDishka[B],
):
print(msg, a, b)

Expand Down
4 changes: 2 additions & 2 deletions examples/integrations/flask_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class InteractorProvider(Provider):
@inject
def index(
*,
interactor: Annotated[Interactor, FromDishka()],
interactor: FromDishka[Interactor],
) -> str:
result = interactor()
return result
Expand All @@ -64,7 +64,7 @@ def index(
@app.get("/auto")
def auto(
*,
interactor: Annotated[Interactor, FromDishka()],
interactor: FromDishka[Interactor],
) -> str:
result = interactor()
return result
Expand Down
2 changes: 1 addition & 1 deletion examples/integrations/litestar_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class MainController(Controller):
@get()
@inject
async def index(
self, *, interactor: Annotated[Interactor, FromDishka()],
self, *, interactor: FromDishka[Interactor],
) -> str:
result = interactor()
return result
Expand Down
2 changes: 1 addition & 1 deletion examples/integrations/starlette_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class InteractorProvider(Provider):
# presentation layer
@inject
async def index(
request: Request, *, interactor: Annotated[Interactor, FromDishka()],
request: Request, *, interactor: FromDishka[Interactor],
) -> PlainTextResponse:
result = interactor()
return PlainTextResponse(result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from taskiq import AsyncTaskiqTask, InMemoryBroker

from dishka import FromDishka, Provider, Scope, make_async_container
from dishka.integrations.taskiq import inject, setup_broker
from dishka.integrations.taskiq import inject, setup_dishka

provider = Provider(scope=Scope.REQUEST)
provider.provide(lambda: random.random(), provides=float) # noqa: S311
Expand All @@ -15,13 +15,13 @@

@broker.task
@inject
async def random_task(num: Annotated[float, FromDishka()]) -> float:
async def random_task(num: FromDishka[float]) -> float:
return num


async def main() -> None:
container = make_async_container(provider)
setup_broker(broker, container)
setup_dishka(container, broker)
await broker.startup()

task: AsyncTaskiqTask[float] = await random_task.kiq()
Expand Down
2 changes: 1 addition & 1 deletion examples/integrations/telebot_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def get_int(self) -> Iterable[int]:
@inject
def start(
message: Message,
value: Annotated[int, FromDishka()],
value: FromDishka[int],
):
bot.reply_to(message, f"Hello, {value}!")

Expand Down
2 changes: 1 addition & 1 deletion examples/real_world/myapp/presentation_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
@inject
async def start(
message: Message,
interactor: Annotated[AddProductsInteractor, FromDishka()],
interactor: FromDishka[AddProductsInteractor],
):
interactor(user_id=1)
await message.answer("Products added!")
2 changes: 1 addition & 1 deletion examples/real_world/myapp/presentation_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@inject
async def add_product(
*,
interactor: Annotated[AddProductsInteractor, FromDishka()],
interactor: FromDishka[AddProductsInteractor],
) -> str:
interactor(user_id=1)
return "Ok"
4 changes: 2 additions & 2 deletions src/dishka/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"Provider",
"alias", "decorate", "from_context", "provide", "DependencyKey",
"FromDishka",
"BaseScope", "Scope",
"BaseScope", "Scope", "new_scope",
]

from .async_container import AsyncContainer, make_async_container
Expand All @@ -21,5 +21,5 @@
from .entities.depends_marker import FromDishka
from .entities.key import DependencyKey, FromComponent
from .entities.provides_marker import AnyOf
from .entities.scope import BaseScope, Scope
from .entities.scope import BaseScope, Scope, new_scope
from .provider import Provider
Loading