Skip to content

Commit

Permalink
works tests for litestar (injection and overriding) (#15)
Browse files Browse the repository at this point in the history
* works tests for litestar (injection and overriding)

* upd docs and tests
  • Loading branch information
nightblure authored Nov 17, 2024
1 parent 1bf61fe commit 5dd23fc
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 38 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@

Easy dependency injection for all, works with Python 3.8-3.12. Main features and advantages:
* support **Python 3.8-3.12**;
* works with **FastAPI, Flask** and **Django REST Framework**;
* works with **FastAPI, **Litestar**, Flask** and **Django REST Framework**;
* support dependency injection via `Annotated` in `FastAPI`;
* the code is fully typed and checked with [mypy](https://github.com/python/mypy);
* **no third-party dependencies**;
* no wiring;
* the life cycle of objects (**scope**) is implemented by providers;
* no **wiring**;
* the life cycle of objects (**scope**) is implemented by **providers**;
* **overriding** dependencies for testing;
* **100%** code coverage;
* good [documentation](https://injection.readthedocs.io/latest/);
Expand All @@ -47,12 +47,12 @@ pip install deps-injection
```

## Compatibility between web frameworks and injection features
| Framework | Dependency injection with @inject | Dependency injection with @autoinject (_experimental_) | Overriding providers |
|--------------------------------------------------------------------------|:---------------------------------:|:------------------------------------------------------:|:--------------------:|
| [FastAPI](https://github.com/fastapi/fastapi) | |||
| [Flask](https://github.com/pallets/flask) | |||
| [Django REST Framework](https://github.com/encode/django-rest-framework) | |||
| [Litestar](https://github.com/litestar-org/litestar) | || |
| Framework | Dependency injection with or without @inject | Dependency injection with @autoinject (_experimental_) | Overriding providers |
|--------------------------------------------------------------------------|:--------------------------------------------:|:------------------------------------------------------:|:--------------------:|
| [FastAPI](https://github.com/fastapi/fastapi) | |||
| [Flask](https://github.com/pallets/flask) | |||
| [Django REST Framework](https://github.com/encode/django-rest-framework) | |||
| [Litestar](https://github.com/litestar-org/litestar) | || |


## Using example with FastAPI
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
integration-with-web-frameworks/fastapi
integration-with-web-frameworks/flask
integration-with-web-frameworks/litestart
integration-with-web-frameworks/litestar
integration-with-web-frameworks/drf
.. toctree::
Expand Down
69 changes: 69 additions & 0 deletions docs/integration-with-web-frameworks/litestar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Litestar

By now you should have a container with providers.
Now all you have to do is define request handlers according to the following rules:
1. use **@inject** decorator **before** http-method Litestar decorator;

2. added for each injected parameter to the request handler a typing of the form
`Union[<your type>, Any]` for Python versions below 3.10 or `<your type> | Any`,
as well as the `Provide` marker from the `injection` package indicating the provider

## Example

```python3
from typing import Any, Union

from litestar import Litestar, get
from injection import Provide, inject, DeclarativeContainer, providers


class Redis:
def __init__(self, *, url: str, port: int):
self.uri = url + ":" + str(port)
self.url = url
self.port = port

def get(self, key):
return key


class Container(DeclarativeContainer):
redis = providers.Singleton(
Redis,
port=9873,
url="redis://...",
)
num = providers.Object(9402)


@get(
"/some_resource",
status_code=200,
)
@inject
async def litestar_endpoint(
redis: Union[Redis, Any] = Provide(Container.redis),
num: Union[int, Any] = Provide(Container.num),
) -> dict:
value = redis.get(800)
return {"detail": value, "num2": num}


@get(
"/num_endpoint",
status_code=200,
)
@inject
async def litestar_endpoint_object_provider(
num: Union[int, Any] = Provide(Container.num),
) -> dict:
return {"detail": num}


_handlers = [
litestar_endpoint,
litestar_endpoint_object_provider,
]

app = Litestar(route_handlers=_handlers)
```
4 changes: 3 additions & 1 deletion src/injection/providers/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ def _resolve(self) -> T:
value = cast(T, resolve_value(self._obj))
return value

def __call__(self) -> Union[T, Any]:
def __call__(self, **_: Any) -> Union[T, Any]:
# **_ - workaround for working DI with Litestar
# It's ok because there should be no arguments in the __call__ method
if self._mocks:
return self._mocks[-1]
return self._resolve()
56 changes: 29 additions & 27 deletions tests/integration/test_litestar/test_integration.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,53 @@
import pytest
from typing import Any, Union
from unittest.mock import Mock

from litestar import Litestar, get
from litestar.di import Provide
from litestar.testing import TestClient

from injection import inject
from injection import Provide, inject
from tests.container_objects import Container, Redis


@get(
"/some_resource",
status_code=200,
dependencies={"redis": Provide(Container.redis)},
)
@inject
async def litestar_endpoint_with_direct_provider_injection(redis: Redis) -> dict:
async def litestar_endpoint(
redis: Union[Redis, Any] = Provide(Container.redis), # noqa: B008
num: Union[int, Any] = Provide(Container.num2), # noqa: B008
) -> dict:
value = redis.get(800)
return {"detail": value}
return {"detail": value, "num2": num}


@get(
"/num_endpoint",
status_code=200,
dependencies={"num": Provide(Container.num2)},
)
async def litestar_endpoint_object_provider(num: int) -> dict:
@inject
async def litestar_endpoint_object_provider(
num: Union[int, Any] = Provide(Container.num2), # noqa: B008
) -> dict:
return {"detail": num}


_handlers = [
litestar_endpoint,
litestar_endpoint_object_provider,
litestar_endpoint_with_direct_provider_injection,
]

app_deps = {
# "redis": Provide(Container.redis),
}
app_deps = {}

app = Litestar(route_handlers=_handlers, debug=True, dependencies=app_deps)


@pytest.mark.xfail(
reason="TypeError: __init__() got an unexpected keyword argument 'args'",
)
def test_litestar_endpoint_with_direct_provider_injection():
with TestClient(app=app) as client:
response = client.get("/some_resource")

assert response.status_code == 200
assert response.json() == {"detail": 800}
assert response.json() == {"detail": 800, "num2": 9402}


def test_litestar_object_provider():
Expand All @@ -58,20 +58,22 @@ def test_litestar_object_provider():
assert response.json() == {"detail": 9402}


class _RedisMock:
def get(self, _):
return 192342526


@pytest.mark.xfail(
reason="TypeError: Unsupported type: <class 'tests.integration.test_litestar.test_integration._RedisMock'>",
)
def test_litestar_overriding_direct_provider_endpoint():
mock_instance = _RedisMock()
mock_instance = Mock(get=lambda _: 192342526)
override_providers = {"redis": mock_instance, "num2": -2999999999}

with TestClient(app=app) as client:
with Container.redis.override_context(mock_instance):
with Container.override_providers(override_providers):
response = client.get("/some_resource")

assert response.status_code == 200
assert response.json() == {"detail": 192342526}
assert response.json() == {"detail": 192342526, "num2": -2999999999}


def test_litestar_endpoint_object_provider():
with TestClient(app=app) as client:
with Container.num2.override_context("mock_num2_value"):
response = client.get("/num_endpoint")

assert response.status_code == 200
assert response.json() == {"detail": "mock_num2_value"}

0 comments on commit 5dd23fc

Please sign in to comment.