Skip to content

Commit

Permalink
fix: refactor filmin to a more clean code flavour (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
abusquets authored Aug 13, 2023
1 parent 6a8e917 commit 7de9103
Show file tree
Hide file tree
Showing 63 changed files with 1,006 additions and 425 deletions.
2 changes: 2 additions & 0 deletions env-api-dev-example
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
APP_ENV=dev
DEBUG=true
# SqlAchemy Verbose
SA_ECHO=true
160 changes: 90 additions & 70 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ pytest-asyncio = "^0.21.0"
poetry-types = "^0.4.0"
beartype = "^0.14.1"
types-redis = "^4.6.0.2"
faker = "^19.3.0"


[build-system]
Expand Down
7 changes: 2 additions & 5 deletions src/app/app_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from core.di.mixins import CoreContainerMixin
from filmin.di.mixins import FilminContainerMixin
from infra.cache.ports import AbstractCacheRepository
from infra.database.sqlalchemy.session import AbstractDatabase
from infra.cache.redis_cache import RedisCache
from infra.database.sqlalchemy.session import AbstractDatabase, Database
from utils.di import DIContainer, di_singleton


Expand All @@ -12,14 +13,10 @@ class AppContainerMixin:
cache_repository: AbstractCacheRepository

def _get_db(self) -> AbstractDatabase:
from infra.database.sqlalchemy.session import Database

return Database()

@di_singleton
def _get_cache_repository(self) -> AbstractCacheRepository:
from infra.cache.redis_cache import RedisCache

return RedisCache(
url=settings.REDIS_URL,
user=settings.REDIS_USER,
Expand Down
2 changes: 1 addition & 1 deletion src/app/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from auth.adapters.api.http.router import router as auth_router
from config import settings
from core.adapters.api.http.router import router as core_router
from filmin.api.router import router as filmin_router
from filmin.adapters.api.http.router import router as filmin_router
from shared.exceptions import APPExceptionError


Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
79 changes: 45 additions & 34 deletions src/filmin/api/genre.py → src/filmin/adapters/api/http/genre.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
from typing import Dict, List, Union

from fastapi import Depends
from fastapi.responses import JSONResponse
from fastapi.routing import APIRouter

from app.app_container import AppContainer
from app.exceptions import EmptyPayloadExceptionError
from app.schemas import Session
from app.session_deps import check_access_token, is_admin_session
from filmin.api.schemas.genre import (
from filmin.adapters.api.http.presenters.genre import GenrePagedListPresenter, GenrePresenter
from filmin.adapters.api.http.schemas.genre import (
CreateGenreRequestDTO,
CreateGenreResponseDTO,
GenreListPagedResponse,
GenreResponse,
UpdateGenreRequestDTO,
UpdatePartialGenreRequestDTO,
)
from filmin.data.repositories.ports.genre import AbstractGenreRepository
from filmin.domain.schemas.genre import Genre
from filmin.domain.entities.genre import Genre
from filmin.domain.services.genre import GenreService
from filmin.domain.use_cases.genre import CreateGenreUseCase, GetGenresUseCase, GetGenreUseCase, UpdateGenreUseCase
from filmin.schemas.genre import CreateGenreInDTO, UpdatePartialGenreInDTO
from shared.api.schemas.page import PagedResponseSchema, PageParams
from shared.api.schemas.page import PageParams


router = APIRouter(prefix='/genre')


def get_genre_repository() -> AbstractGenreRepository:
from app.app_container import AppContainer

return AppContainer().genre_repository


@router.get(
'/{code}',
responses={
Expand All @@ -38,32 +35,33 @@ def get_genre_repository() -> AbstractGenreRepository:
)
async def get_genre(
code: str,
genre_repository: AbstractGenreRepository = Depends(get_genre_repository),
container: AppContainer = Depends(AppContainer),
_: Session = Depends(check_access_token),
) -> Genre:
return await genre_repository.get_by_id(code)
) -> GenreResponse:
service = GenreService(container.genre_repository)
presenter = GenrePresenter()
usecase = GetGenreUseCase(presenter, service)
await usecase.execute(code)
return presenter.result


@router.get(
'',
response_model=PagedResponseSchema[Genre],
responses={
200: {'description': 'Successful Response'},
422: {'description': 'Unprocessable Entity'},
},
)
async def list_genre(
async def list_genres(
page_params: PageParams = Depends(),
genre_repository: AbstractGenreRepository = Depends(get_genre_repository),
container: AppContainer = Depends(AppContainer),
_: Session = Depends(check_access_token),
) -> Dict[str, Union[int, List[Genre]]]:
count, items = await genre_repository.get_xpage(**page_params.model_dump())
return {
'total': count,
'results': items,
'page': page_params.page,
'size': page_params.size,
}
) -> GenreListPagedResponse:
service = GenreService(container.genre_repository)
presenter = GenrePagedListPresenter(page_params)
usecase = GetGenresUseCase(presenter, service)
await usecase.execute(page_params)
return presenter.result


@router.post(
Expand All @@ -78,11 +76,15 @@ async def list_genre(
)
async def create_genre(
request_data: CreateGenreRequestDTO,
genre_repository: AbstractGenreRepository = Depends(get_genre_repository),
container: AppContainer = Depends(AppContainer),
_: Session = Depends(is_admin_session),
) -> Genre:
in_dto = CreateGenreInDTO.model_validate(request_data.model_dump())
return await genre_repository.create(in_dto)
service = GenreService(container.genre_repository)
presenter = GenrePresenter()
usecase = CreateGenreUseCase(presenter, service)
await usecase.execute(in_dto)
return presenter.result


@router.put(
Expand All @@ -96,12 +98,16 @@ async def create_genre(
async def update_genre(
code: str,
request_data: UpdateGenreRequestDTO,
genre_repository: AbstractGenreRepository = Depends(get_genre_repository),
container: AppContainer = Depends(AppContainer),
_: Session = Depends(is_admin_session),
) -> Genre:
) -> GenreResponse:
in_data = request_data.model_dump()
in_dto = UpdatePartialGenreInDTO.model_validate(in_data)
return await genre_repository.update(code, in_dto)
service = GenreService(container.genre_repository)
presenter = GenrePresenter()
usecase = UpdateGenreUseCase(presenter, service)
await usecase.execute(code, in_dto)
return presenter.result


@router.patch(
Expand All @@ -115,11 +121,16 @@ async def update_genre(
async def update_genre_partially(
code: str,
request_data: UpdatePartialGenreRequestDTO,
genre_repository: AbstractGenreRepository = Depends(get_genre_repository),
container: AppContainer = Depends(AppContainer),
_: Session = Depends(is_admin_session),
) -> Genre:
) -> GenreResponse:
in_data = request_data.model_dump(exclude_unset=True)
if not in_data.keys():
raise EmptyPayloadExceptionError()
in_dto = UpdatePartialGenreInDTO.model_validate(in_data)
return await genre_repository.update(code, in_dto)

service = GenreService(container.genre_repository)
presenter = GenrePresenter()
usecase = UpdateGenreUseCase(presenter, service)
await usecase.execute(code, in_dto)
return presenter.result
145 changes: 145 additions & 0 deletions src/filmin/adapters/api/http/movie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
from typing import Annotated
import uuid

from fastapi import Depends
from fastapi.responses import JSONResponse
from fastapi.routing import APIRouter

from app.app_container import AppContainer
from app.exceptions import EmptyPayloadExceptionError
from app.schemas import Session
from app.session_deps import is_admin_session
from filmin.adapters.api.http.presenters.movie import (
MoviePagedListPresenter,
MoviePresenter,
)
from filmin.adapters.api.http.schemas.movie import (
CreateMovieRequestDTO,
CreateMovieResponseDTO,
MovieListPagedResponse,
MovieResponse,
UpdateMovieRequestDTO,
UpdatePartialMovieRequestDTO,
)
from filmin.domain.entities.movie import Movie
from filmin.domain.services.movie import MovieService
from filmin.domain.use_cases.movie import (
CreateMovieUseCase,
GetMoviesUseCase,
GetMovieUseCase,
UpdateMovieUseCase,
)
from filmin.schemas.movie import CreateMovieInDTO, UpdatePartialMovieInDTO
from shared.api.schemas.page import PageParams


router = APIRouter(prefix='/movie')


@router.get(
'/{uuid}',
responses={
200: {'description': 'Successful Response'},
404: {'description': 'Movie not found'},
422: {'description': 'Unprocessable Entity'},
},
)
async def get_movie(
uuid: Annotated[str, uuid.UUID],
container: AppContainer = Depends(AppContainer),
) -> MovieResponse:
service = MovieService(container.movie_repository)
presenter = MoviePresenter()
usecase = GetMovieUseCase(presenter, service)
await usecase.execute(uuid)
return presenter.result


@router.get(
'',
responses={
200: {'description': 'Successful Response'},
422: {'description': 'Unprocessable Entity'},
},
)
async def list_movies(
page_params: PageParams = Depends(),
container: AppContainer = Depends(AppContainer),
) -> MovieListPagedResponse:
service = MovieService(container.movie_repository)
presenter = MoviePagedListPresenter(page_params)
usecase = GetMoviesUseCase(presenter, service)
await usecase.execute(page_params)
return presenter.result


@router.post(
'',
response_class=JSONResponse,
response_model=CreateMovieResponseDTO,
status_code=201,
responses={
201: {'description': 'Item created'},
422: {'description': 'Unprocessable Entity'},
},
)
async def create_movie(
request_data: CreateMovieRequestDTO,
container: AppContainer = Depends(AppContainer),
_: Session = Depends(is_admin_session),
) -> Movie:
in_dto = CreateMovieInDTO.model_validate(request_data.model_dump())
service = MovieService(container.movie_repository)
presenter = MoviePresenter()
usecase = CreateMovieUseCase(presenter, service)
await usecase.execute(in_dto)
return presenter.result


@router.put(
'/{uuid}',
responses={
200: {'description': 'Item updated'},
404: {'description': 'Item not found'},
422: {'description': 'Unprocessable Entity'},
},
)
async def update_movie(
uuid: Annotated[str, uuid.UUID],
request_data: UpdateMovieRequestDTO,
container: AppContainer = Depends(AppContainer),
_: Session = Depends(is_admin_session),
) -> MovieResponse:
in_data = request_data.model_dump()
in_dto = UpdatePartialMovieInDTO.model_validate(in_data)
service = MovieService(container.movie_repository)
presenter = MoviePresenter()
usecase = UpdateMovieUseCase(presenter, service)
await usecase.execute(uuid, in_dto)
return presenter.result


@router.patch(
'/{uuid}',
responses={
200: {'description': 'Item updated'},
404: {'description': 'Item not found'},
422: {'description': 'Unprocessable Entity'},
},
)
async def update_movie_partially(
uuid: Annotated[str, uuid.UUID],
request_data: UpdatePartialMovieRequestDTO,
container: AppContainer = Depends(AppContainer),
_: Session = Depends(is_admin_session),
) -> MovieResponse:
in_data = request_data.model_dump(exclude_unset=True)
if not in_data.keys():
raise EmptyPayloadExceptionError()
in_dto = UpdatePartialMovieInDTO.model_validate(in_data)

service = MovieService(container.movie_repository)
presenter = MoviePresenter()
usecase = UpdateMovieUseCase(presenter, service)
await usecase.execute(uuid, in_dto)
return presenter.result
29 changes: 29 additions & 0 deletions src/filmin/adapters/api/http/presenters/genre.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import List

from filmin.adapters.api.http.schemas.genre import GenreListPagedResponse, GenreResponse
from filmin.domain.entities.genre import Genre
from shared.api.schemas.page import PageParams
from shared.presenter import AbstractPresenter


class GenrePresenter(AbstractPresenter[Genre, GenreResponse]):
result: GenreResponse

async def present(self, data: Genre) -> None:
self.result = GenreResponse.model_validate(data, from_attributes=True)


class GenrePagedListPresenter(AbstractPresenter[List[Genre], List[GenreResponse]]):
result: GenreListPagedResponse

def __init__(self, page_params: PageParams) -> None:
self.page_params = page_params

async def present(self, data: List[Genre]) -> None:
list_items = [GenreResponse.model_validate(item, from_attributes=True) for item in data]
self.result = GenreListPagedResponse(
results=list_items,
total=len(list_items),
page=self.page_params.page,
size=self.page_params.size,
)
Loading

0 comments on commit 7de9103

Please sign in to comment.