Skip to content

Commit

Permalink
[GH-29] Refactor repository interface and pre-commit GHA (#30)
Browse files Browse the repository at this point in the history
* [#29] refactor: repository interface and pre-commit GHA

* [#29] fix: typo]

* [#29] fix: typo again...

* [#29] fix: -_ -;

* [#29] chore: lint
  • Loading branch information
nickatnight authored Feb 22, 2023
1 parent 84b0b1b commit 0be71b8
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ concurrency:

jobs:
lint:
uses: ./.github/workflows/pre-commit.yml
uses: nickatnight/gha-workflows/.github/workflows/pre-commit.yml@main

tests:
uses: ./.github/workflows/test.yml
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:

jobs:
lint:
uses: ./.github/workflows/pre-commit.yml
uses: nickatnight/gha-workflows/.github/workflows/pre-commit.yml@main

tests:
uses: ./.github/workflows/test.yml
13 changes: 0 additions & 13 deletions .github/workflows/pre-commit.yml

This file was deleted.

1 change: 1 addition & 0 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"%smodels/meme.py" % BASE_BACKEND_SRC_PATH,
"%sschemas/meme.py" % BASE_BACKEND_SRC_PATH,
"%sapi/v1/meme.py" % BASE_BACKEND_SRC_PATH,
"%srepositories/meme.py" % BASE_BACKEND_SRC_PATH,
"%sdb/init_db.py" % BASE_BACKEND_SRC_PATH,
]
DEPLOYMENT_FILES = [
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "cookiecutter-fastapi-backend"
version = "0.2.6"
version = "0.2.7"
description = "Cookiecutter template to build and deploy fastapi backends..batteries included"
authors = ["nickatnight <[email protected]>"]
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion {{ cookiecutter.project_slug }}/.github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ env:

jobs:
lint:
uses: ./.github/workflows/lint.yml
uses: nickatnight/gha-workflows/.github/workflows/pre-commit.yml@main

{%- if cookiecutter.deployments == 'yes' %}
build:
Expand Down
2 changes: 1 addition & 1 deletion {{ cookiecutter.project_slug }}/.github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:

jobs:
lint:
uses: ./.github/workflows/lint.yml
uses: nickatnight/gha-workflows/.github/workflows/pre-commit.yml@main

unit-tests:
needs: [lint]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

from src.core.enums import SortOrder
from src.db.session import get_session
from src.models.meme import Meme
from src.repositories.sqlalchemy import SQLAlchemyRepository
from src.repositories.meme import MemeRepository
from src.schemas.common import IGetResponseBase
from src.schemas.meme import IMemeRead

Expand All @@ -27,7 +26,7 @@ async def memes(
sort_order: Optional[str] = SortOrder.DESC,
session: AsyncSession = Depends(get_session),
) -> IGetResponseBase[List[IMemeRead]]:
meme_repo = SQLAlchemyRepository(model=Meme, db=session)
meme_repo = MemeRepository(db=session)
memes = await meme_repo.all(
skip=skip, limit=limit, sort_field=sort_field, sort_order=sort_order
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
from abc import ABCMeta, abstractmethod
from typing import Generic, List, Optional, TypeVar
from typing import Any, List, Optional


T = TypeVar("T")


class IRepository(Generic[T], metaclass=ABCMeta):
class IRepository(metaclass=ABCMeta):
"""Class representing the repository interface."""

@abstractmethod
async def create(self, obj_in: T, **kwargs: int) -> T:
async def create(self, obj_in: Any, **kwargs: Any) -> Any:
"""Create new entity and returns the saved instance."""
raise NotImplementedError

@abstractmethod
async def update(self, instance: T, obj_in: T) -> T:
async def update(self, obj_current: Any, obj_in: Any) -> Any:
"""Updates an entity and returns the saved instance."""
raise NotImplementedError

@abstractmethod
async def get(self, **kwargs: int) -> T:
async def get(self, **kwargs: Any) -> Optional[Any]:
"""Get and return one instance by filter."""
raise NotImplementedError

@abstractmethod
async def delete(self, **kwargs: int) -> None:
async def delete(self, **kwargs: Any) -> None:
"""Delete one instance by filter."""
raise NotImplementedError

Expand All @@ -35,6 +32,16 @@ async def all(
limit: int = 50,
sort_field: Optional[str] = None,
sort_order: Optional[str] = None,
) -> List[T]:
"""Delete one instance by filter."""
) -> List[Any]:
"""Get all instances."""
raise NotImplementedError

@abstractmethod
async def f(self, **kwargs: Any) -> List[Any]:
"""Filter instances"""
raise NotImplementedError

@abstractmethod
async def get_or_create(self, obj_in: Any, **kwargs: Any) -> Any:
"""Get or create an instance"""
raise NotImplementedError
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from src.models.meme import Meme
from src.repositories.sqlalchemy import BaseSQLAlchemyRepository
from src.schemas.meme import IMemeCreate, IMemeUpdate


class MemeRepository(BaseSQLAlchemyRepository[Meme, IMemeCreate, IMemeUpdate]):
_model = Meme
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import logging
from typing import List, Optional, Type, TypeVar
from typing import Any, Generic, List, Optional, Type, TypeVar

from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import SQLModel, select

from src.core.exceptions import ObjectNotFound
from src.interfaces.repository import IRepository


ModelType = TypeVar("ModelType", bound=SQLModel)
CreateSchemaType = TypeVar("CreateSchemaType", bound=SQLModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=SQLModel)
logger: logging.Logger = logging.getLogger(__name__)


class SQLAlchemyRepository(IRepository[ModelType]):
def __init__(self, model: Type[ModelType], db: AsyncSession) -> None:
self.model = model
class BaseSQLAlchemyRepository(IRepository, Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
_model: Type[ModelType]

def __init__(self, db: AsyncSession) -> None:
self.db = db

async def create(self, obj_in: ModelType, **kwargs: int) -> ModelType:
async def create(self, obj_in: CreateSchemaType, **kwargs: Any) -> ModelType:
logger.info(f"Inserting new object[{obj_in.__class__.__name__}]")

db_obj = self.model.from_orm(obj_in)
db_obj = self._model.from_orm(obj_in)
add = kwargs.get("add", True)
flush = kwargs.get("flush", True)
commit = kwargs.get("commit", True)
Expand All @@ -42,20 +44,20 @@ async def create(self, obj_in: ModelType, **kwargs: int) -> ModelType:

return db_obj

async def get(self, **kwargs: int) -> ModelType:
logger.info(f"Fetching [{self.model}] object by [{kwargs}]")
async def get(self, **kwargs: Any) -> Optional[ModelType]:
logger.info(f"Fetching [{self._model.__class__.__name__}] object by [{kwargs}]")

query = select(self.model).filter_by(**kwargs)
query = select(self._model).filter_by(**kwargs)
response = await self.db.execute(query)
scalar: Optional[ModelType] = response.scalar_one_or_none()

if not scalar:
raise ObjectNotFound(f"Object with [{kwargs}] not found.")
# if not scalar:
# raise ObjectNotFound(f"Object with [{kwargs}] not found.")

return scalar

async def update(self, obj_current: ModelType, obj_in: ModelType) -> ModelType:
logger.info(f"Updating [{self.model}] object with [{obj_in}]")
async def update(self, obj_current: ModelType, obj_in: UpdateSchemaType) -> ModelType:
logger.info(f"Updating [{self._model.__class__.__name__}] object with [{obj_in}]")

update_data = obj_in.dict(
exclude_unset=True
Expand All @@ -70,7 +72,7 @@ async def update(self, obj_current: ModelType, obj_in: ModelType) -> ModelType:

return obj_current

async def delete(self, **kwargs: int) -> None:
async def delete(self, **kwargs: Any) -> None:
obj = self.get(**kwargs)

await self.db.delete(obj)
Expand All @@ -83,7 +85,7 @@ async def all(
sort_field: Optional[str] = None,
sort_order: Optional[str] = None,
) -> List[ModelType]:
columns = self.model.__table__.columns # type: ignore
columns = self._model.__table__.columns # type: ignore

if not sort_field:
sort_field = "created_at"
Expand All @@ -92,7 +94,26 @@ async def all(
sort_order = "desc"

order_by = getattr(columns[sort_field], sort_order)()
query = select(self.model).offset(skip).limit(limit).order_by(order_by)
query = select(self._model).offset(skip).limit(limit).order_by(order_by)

response = await self.db.execute(query)
return response.scalars().all()

async def f(self, **kwargs: Any) -> List[ModelType]:
logger.info(f"Fetching [{self._model.__class__.__name__}] object by [{kwargs}]")

query = select(self._model).filter_by(**kwargs)
response = await self.db.execute(query)
scalars: List[ModelType] = response.scalars().all()

return scalars

async def get_or_create(self, obj_in: CreateSchemaType, **kwargs: Any) -> ModelType:
get_instance: Optional[ModelType] = await self.get(**kwargs)

if get_instance:
return get_instance

instance: ModelType = await self.create(obj_in)

return instance

0 comments on commit 0be71b8

Please sign in to comment.