Skip to content

Commit

Permalink
Merge pull request #32 from reagento/feature/integrations
Browse files Browse the repository at this point in the history
coverage and flask
  • Loading branch information
Tishka17 authored Feb 8, 2024
2 parents 0d9a218 + 89e95da commit d76caf0
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 7 deletions.
66 changes: 66 additions & 0 deletions examples/flask_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from abc import abstractmethod
from typing import Annotated, Protocol

from flask import Flask

from dishka import (
Provider, Scope, provide,
)
from dishka.integrations.flask import (
Depends, inject, setup_dishka,
)


# app core
class DbGateway(Protocol):
@abstractmethod
def get(self) -> str:
raise NotImplementedError


class FakeDbGateway(DbGateway):
def get(self) -> str:
return "Hello"


class Interactor:
def __init__(self, db: DbGateway):
self.db = db

def __call__(self) -> str:
return self.db.get()


# app dependency logic
class AdaptersProvider(Provider):
@provide(scope=Scope.REQUEST)
def get_db(self) -> DbGateway:
return FakeDbGateway()


class InteractorProvider(Provider):
i1 = provide(Interactor, scope=Scope.REQUEST)


# presentation layer
app = Flask(__name__)


@app.get("/")
@inject
def index(
*,
interactor: Annotated[Interactor, Depends()],
) -> str:
result = interactor()
return result


container = setup_dishka(
providers=[AdaptersProvider(), InteractorProvider()],
app=app,
)
try:
app.run()
finally:
container.close()
1 change: 1 addition & 0 deletions examples/real_world/requirements_test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pytest==7.*
pytest-asyncio==0.23.*
pytest-repeat==0.9.*
pytest-cov==4.1.0

httpx==0.26.*
asgi_lifespan==2.1.*
2 changes: 2 additions & 0 deletions requirements/flask-302.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-r test.txt
Flask==3.0.2
2 changes: 2 additions & 0 deletions requirements/flask-latest.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-r test.txt
Flask
3 changes: 2 additions & 1 deletion requirements/test.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pytest==7.*
pytest-asyncio==0.23.*
pytest-repeat==0.9.*
pytest-repeat==0.9.*
pytest-cov==4.1.0
41 changes: 41 additions & 0 deletions src/dishka/integrations/flask.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
__all__ = [
"inject", "setup_dishka", "Depends",
]

from typing import Sequence

from flask import Flask, Request, g, request

from dishka import Container, Provider, make_container
from .base import Depends, wrap_injection


def inject(func):
return wrap_injection(
func=func,
remove_depends=True,
container_getter=lambda _, p: g.dishka_container,
additional_params=[],
is_async=False,
)


class ContainerMiddleware:
def __init__(self, container):
self.container = container

def enter_request(self):
g.dishka_container_wrapper = self.container({Request: request})
g.dishka_container = g.dishka_container_wrapper.__enter__()

def exit_request(self, *_args, **_kwargs):
g.dishka_container_wrapper.__exit__(None, None, None)


def setup_dishka(providers: Sequence[Provider], app: Flask) -> Container:
container_wrapper = make_container(*providers)
container = container_wrapper.__enter__()
middleware = ContainerMiddleware(container)
app.before_request(middleware.enter_request)
app.teardown_appcontext(middleware.exit_request)
return container
3 changes: 3 additions & 0 deletions tests/integrations/flask/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import pytest

pytest.importorskip("flask")
69 changes: 69 additions & 0 deletions tests/integrations/flask/test_flask.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from contextlib import contextmanager
from typing import Annotated
from unittest.mock import Mock

from flask import Flask

from dishka.integrations.flask import Depends, inject, setup_dishka
from ..common import (
APP_DEP_VALUE,
REQUEST_DEP_VALUE,
AppDep,
AppProvider,
RequestDep,
)


@contextmanager
def dishka_app(view, provider):
app = Flask(__name__)
app.get("/")(inject(view))
container = setup_dishka(
providers=[provider],
app=app,
)
yield app
container.close()


def handle_with_app(
a: Annotated[AppDep, Depends()],
mock: Annotated[Mock, Depends()],
) -> None:
mock(a)


def test_app_dependency(app_provider: AppProvider):
with dishka_app(handle_with_app, app_provider) as app:
app.test_client().get("/")
app_provider.mock.assert_called_with(APP_DEP_VALUE)
app_provider.app_released.assert_not_called()
app_provider.app_released.assert_called()


def handle_with_request(
a: Annotated[RequestDep, Depends()],
mock: Annotated[Mock, Depends()],
) -> None:
mock(a)


def test_request_dependency(app_provider: AppProvider):
with dishka_app(handle_with_request, app_provider) as app:
app.test_client().get("/")
app_provider.mock.assert_called_with(REQUEST_DEP_VALUE)
app_provider.request_released.assert_called_once()


def test_request_dependency2(app_provider: AppProvider):
with dishka_app(handle_with_request, app_provider) as app:
app.test_client().get("/")
app_provider.mock.assert_called_with(REQUEST_DEP_VALUE)
app_provider.request_released.assert_called_once()
app_provider.mock.assert_called_with(REQUEST_DEP_VALUE)
app_provider.mock.reset_mock()
app_provider.request_released.assert_called_once()
app_provider.request_released.reset_mock()
app.test_client().get("/")
app_provider.mock.assert_called_with(REQUEST_DEP_VALUE)
app_provider.request_released.assert_called_once()
27 changes: 21 additions & 6 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
[tox]
requires =
tox>=4
env_list = unit,fastapi-{0092,0109},aiogram-330,real_world_example,telebot-415
env_list =
unit,
real_world_example,
fastapi-{0092,0109},
flask-302,
aiogram-330,
telebot-415,

[pytest]
addopts = --cov=dishka --cov-append -v

[testenv]
deps =
Expand All @@ -12,19 +21,25 @@ deps =
aiogram-330: -r requirements/aiogram-330.txt
telebot-latest: -r requirements/telebot-latest.txt
telebot-415: -r requirements/telebot-415.txt
flask-latest: -r requirements/flask-latest.txt
flask-302: -r requirements/flask-302.txt

commands =
fastapi: pytest -v tests/integrations/fastapi
aiogram: pytest -v tests/integrations/aiogram
telebot: pytest -v tests/integrations/telebot
fastapi: pytest tests/integrations/fastapi
aiogram: pytest tests/integrations/aiogram
telebot: pytest tests/integrations/telebot
flask: pytest tests/integrations/flask

package = editable


[testenv:unit]
deps = -r requirements/test.txt
commands = pytest -v tests/unit
commands = pytest tests/unit

[testenv:latest]
install_commands = python -m pip install -U {opts} {packages}

[testenv:real_world_example]
deps = -r examples/real_world/requirements_test.txt
commands = pytest -v examples/real_world/tests/
commands = pytest examples/real_world/tests/

0 comments on commit d76caf0

Please sign in to comment.