Skip to content
Draft
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
## Dev

- Blacksheep
- Rodi
- Guardpost
- Mediatr
- Adaptix
Expand All @@ -14,9 +13,11 @@
- Alembic
- orjson
- nats-py
- dishka

## Test

- pytest
- pytest-asyncio
- pytest-mock
- Testcontainers
129 changes: 70 additions & 59 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ python = "^3.11"
blacksheep = "^2.0.7"
uvicorn = "^0.27.1"
pydantic = "^2.6.3"
rodi = "^2.0.6"
pyjwt = "^2.8.0"
structlog = "^24.1.0"
mediatr = "^1.3.2"
adaptix = "^3.0.0b3"
guardpost = "^1.0.2"
orjson = "^3.10.2"
nats-py = "^2.7.2"
dishka = "^1.1.1"

[tool.poetry.group.db.dependencies]
asyncpg = "^0.29.0"
Expand Down
2 changes: 1 addition & 1 deletion requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ guardpost==1.0.2 ; python_version >= "3.11" and python_version < "4.0"
mediatr==1.3.2 ; python_version >= "3.11" and python_version < "4.0"
pydantic==2.7.0 ; python_version >= "3.11" and python_version < "4.0"
pyjwt==2.8.0 ; python_version >= "3.11" and python_version < "4.0"
rodi==2.0.6 ; python_version >= "3.11" and python_version < "4.0"
structlog==24.1.0 ; python_version >= "3.11" and python_version < "4.0"
uvicorn==0.27.1 ; python_version >= "3.11" and python_version < "4.0"
nats-py==2.7.2 ; python_version >= "3.11" and python_version < "4.0"
orjson==3.10.2 ; python_version >= "3.11" and python_version < "4.0"
dishka==1.1.1 ; python_version >= "3.11" and python_version < "4.0"
-r db.txt
Empty file added src/api/di/__init__.py
Empty file.
31 changes: 31 additions & 0 deletions src/api/di/container.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import Protocol, TypeVar

from dishka import AsyncContainer, Container

T = TypeVar("T")


class BlacksheepContainer(Protocol):
"""
Generic interface of DI Container that can register and resolve services,
and tell if a type is configured.
"""

def register(self, obj_type: str | type, *args, **kwargs) -> None:
"""Registers a type in the container, with optional arguments."""

def resolve(self, obj_type: str | type[T], *args, **kwargs) -> T:
"""Activates an instance of the given type, with optional arguments."""

def __contains__(self, item) -> bool:
"""
Returns a value indicating whether a given type is configured in this container.
"""


class DishkaDI(BlacksheepContainer):
def __init__(self, container: AsyncContainer | Container) -> None:
self._container = container

# BUG: Can't register in builded dishka container
def register(self, obj_type: str | type, *args, **kwargs) -> None: ...
16 changes: 3 additions & 13 deletions src/api/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from blacksheep import Application
from nats import NATS

from src.api.auth.handler import BuberDinnerAuthHandler
from src.api.di.container import DishkaDI
from src.api.docs import setup_docs
from src.api.middleware import ErrorHandlingMiddleware
from src.infrastructure.di import build_application_container
Expand All @@ -11,12 +11,10 @@
def build_api() -> Application:
"""Build BlackSheep application"""

app = Application(show_error_details=True)
container = build_application_container()
app = Application(services=DishkaDI(container), show_error_details=True) # type: ignore
setup_app(app)

app.on_start += setup_di
app.on_stop += close_connections

return app


Expand All @@ -30,11 +28,3 @@ def setup_app(app: Application) -> None:

app.use_authentication().add(BuberDinnerAuthHandler())
app.use_authorization()


async def setup_di(app: Application) -> None:
await build_application_container(app.services)


async def close_connections(app: Application) -> None:
await app.services.resolve(NATS).close()
5 changes: 3 additions & 2 deletions src/api/middleware/error_handling.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from logging import Logger
import logging
from typing import Awaitable, Callable

from blacksheep import Request, Response
Expand All @@ -11,13 +11,14 @@
from src.application.common.errors.service_exception import IServiceException
from src.application.common.errors.validation_behavior import ValidationBehaviorError

logger = logging.getLogger(__name__)


class ErrorHandlingMiddleware:
async def __call__(
self,
request: Request,
handler: Callable[[Request], Awaitable[Response]],
logger: Logger,
) -> Response:
try:
response: Response = await handler(request)
Expand Down
14 changes: 0 additions & 14 deletions src/infrastructure/di/config.py

This file was deleted.

41 changes: 16 additions & 25 deletions src/infrastructure/di/main.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
import logging
from functools import lru_cache

from rodi import Container
from dishka import AsyncContainer, make_async_container

from src.infrastructure.converter.retort import setup_retort

from .config import setup_config_di
from .extra import setup_extra_di
from .mapper import setup_mapper_di
from .mediatr import setup_mediatr_di
from .message_queue import setup_message_queue_di
from .persistence import setup_persistence_di
from .providers.broker import BrokerProvider
from .providers.config import ConfigProvider
from .providers.extra import ExtraProvider
from .providers.mapper import MapperProvider
from .providers.mediatr import MediatrProvider
from .providers.persistence import PersistenceProvider


@lru_cache
async def build_application_container(container: Container | None = None) -> Container:
if container is None:
container = Container()

container.add_instance(logging.getLogger(__name__), logging.Logger)
container.add_instance(setup_retort())

config = setup_config_di(container)
setup_extra_di(container)
setup_mapper_di(container)
setup_mediatr_di(container)
await setup_message_queue_di(container, config)
setup_persistence_di(container)

return container
def build_application_container() -> AsyncContainer:
return make_async_container(
BrokerProvider(),
ConfigProvider(),
ExtraProvider(),
MapperProvider(),
MediatrProvider(),
PersistenceProvider(),
)
12 changes: 0 additions & 12 deletions src/infrastructure/di/mapper.py

This file was deleted.

22 changes: 0 additions & 22 deletions src/infrastructure/di/message_queue.py

This file was deleted.

21 changes: 0 additions & 21 deletions src/infrastructure/di/persistence.py

This file was deleted.

Empty file.
23 changes: 23 additions & 0 deletions src/infrastructure/di/providers/broker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from typing import AsyncGenerator

from dishka import Provider, Scope, provide
from nats import NATS

from src.application.common.events.event_bus import EventBus
from src.infrastructure.config.broker import BrokerConfig
from src.infrastructure.event_bus.event_bus import EventBusImpl
from src.infrastructure.message_broker.interface import MessageBroker
from src.infrastructure.message_broker.main import make_broker_connection
from src.infrastructure.message_broker.message_broker import MessageBrokerImpl


class BrokerProvider(Provider):
scope = Scope.REQUEST

event_bus = provide(EventBusImpl, provides=EventBus)
message_broker = provide(MessageBrokerImpl, provides=MessageBroker)

@provide(scope=Scope.APP)
async def nats_conn(self, broker_config: BrokerConfig) -> AsyncGenerator[NATS, None]:
async with make_broker_connection(broker_config.full_url) as conn:
yield conn
26 changes: 26 additions & 0 deletions src/infrastructure/di/providers/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from dishka import Provider, Scope, provide

from src.infrastructure.config.broker import BrokerConfig
from src.infrastructure.config.config import Config, create_config_obj
from src.infrastructure.config.db import DBConfig
from src.infrastructure.config.jwt import JWTConfig


class ConfigProvider(Provider):
scope = Scope.APP

@provide
def config(self) -> Config:
return create_config_obj()

@provide
def jwt_config(self, config: Config) -> JWTConfig:
return config.jwt_config

@provide
def db_config(self, config: Config) -> DBConfig:
return config.db_config

@provide
def broker_config(self, config: Config) -> BrokerConfig:
return config.broker_config
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from rodi import Container
from dishka import Provider, Scope, provide

from src.application.common.interfaces.authentication.i_jwt_token_generator import (
JwtTokenGenerator,
Expand All @@ -8,6 +8,14 @@
from src.infrastructure.services.dt_provider import DateTimeProviderImpl


def setup_extra_di(container: Container) -> None:
container.add_singleton(JwtTokenGenerator, JwtTokenGeneratorImpl)
container.add_singleton(DateTimeProvider, DateTimeProviderImpl)
class ExtraProvider(Provider):
jwt_token_generator = provide(
JwtTokenGeneratorImpl,
provides=JwtTokenGenerator,
scope=Scope.APP,
)
dt_provider = provide(
DateTimeProviderImpl,
provides=DateTimeProvider,
scope=Scope.APP,
)
18 changes: 18 additions & 0 deletions src/infrastructure/di/providers/mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from adaptix import Retort
from dishka import Provider, Scope, provide

from src.application.common.mapper.interface import AuthMapper, MainMapper, MenuMapper
from src.infrastructure.converter.mapper import MainMapperImpl
from src.infrastructure.converter.mapper.auth import AuthMapperImpl
from src.infrastructure.converter.mapper.menu import MenuMapperImpl
from src.infrastructure.converter.retort import setup_retort


class MapperProvider(Provider):
scope = Scope.APP

retort = provide(setup_retort, provides=Retort)

main_mapper = provide(MainMapperImpl, provides=MainMapper)
auth_mapper = provide(AuthMapperImpl, provides=AuthMapper)
menu_mapper = provide(MenuMapperImpl, provides=MenuMapper)
Loading