Skip to content

Commit

Permalink
Merge pull request #8 from ArielMAJ/release/0.3.0
Browse files Browse the repository at this point in the history
Release/0.3.0
  • Loading branch information
ArielMAJ authored Jul 16, 2024
2 parents 4962be7 + 061696e commit 0f85f0c
Show file tree
Hide file tree
Showing 37 changed files with 206 additions and 101 deletions.
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"python.testing.pytestArgs": ["tests"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
15 changes: 13 additions & 2 deletions api/app.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
from contextlib import asynccontextmanager
from pathlib import Path

from api.config import Config
from api.entrypoints.router import api_router
from api.entrypoints import router
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi_async_sqlalchemy import SQLAlchemyMiddleware

# from fastapi.responses import JSONResponse
from fastapi_cache import FastAPICache
from fastapi_cache.backends.inmemory import InMemoryBackend
from loguru import logger

APP_ROOT = Path(__file__).parent


@asynccontextmanager
async def lifespan(app: FastAPI):
logger.info("starting up")
yield
logger.info("shutting down")


def get_app() -> FastAPI:
"""
Get FastAPI application.
Expand All @@ -23,6 +32,8 @@ def get_app() -> FastAPI:
"""
_app = FastAPI(
title="fastapi-backend-template",
description="FastAPI backend template.",
lifespan=lifespan,
# default_response_class=JSONResponse,
)

Expand All @@ -39,7 +50,7 @@ def get_app() -> FastAPI:
engine_args=Config.DATABASE.ENGINE_ARGS,
commit_on_exit=True,
)
_app.include_router(router=api_router)
_app.include_router(router=router)

FastAPICache.init(InMemoryBackend(), prefix="fastapi-cache")
return _app
16 changes: 4 additions & 12 deletions api/database/models/model_base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from datetime import datetime
from typing import Any, List
from typing import Any, List, Union

from fastapi_async_sqlalchemy import db
from sqlalchemy import DateTime, MetaData, select
Expand Down Expand Up @@ -30,6 +30,8 @@ class ModelBase(DeclarativeBase):
async def new(cls, **kwargs) -> Self:
obj: Self = cls(**kwargs)
await obj.save()
await db.session.flush()
await db.session.refresh(obj)
return obj

@classmethod
Expand All @@ -55,7 +57,7 @@ async def get(
return result.scalars().first()

@classmethod
async def get_by_id(cls, id: int) -> Self:
async def get_by_id(cls, id: int) -> Union[Self, None]:
return await cls.get(cls.id == id)

async def save(self) -> None:
Expand All @@ -66,15 +68,5 @@ async def update(self, **kwargs):
setattr(self, attr, value)
await self.save()

@classmethod
async def update_by_id(cls, id: int, **kwargs):
obj = await cls.get_by_id(id)
await obj.update(**kwargs)

async def delete(self):
await db.session.delete(self)

@classmethod
async def delete_by_id(cls, id: int):
obj = await cls.get_by_id(id)
await obj.delete()
3 changes: 3 additions & 0 deletions api/entrypoints/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from api.entrypoints.router import router

__all__ = ["router"]
File renamed without changes.
4 changes: 0 additions & 4 deletions api/entrypoints/monitoring/__init__.py

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from api.entrypoints.v1.random_number.schema import RandomResponse
from api.services.random_number_service import RandomNumberService
from api.schemas.random_number import RandomResponse
from api.services.random_number_service import RandomResponseService
from fastapi import APIRouter
from fastapi_cache.decorator import cache

Expand All @@ -14,4 +14,4 @@ async def random_number():
:returns: random number to user.
"""
return await RandomNumberService.get_random_number()
return await RandomResponseService.get_random_number()
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from api.entrypoints.v1.root_response.schema import RootResponse
from api.schemas.root_response import RootResponse
from api.services.root_response_service import RootResponseService
from fastapi import APIRouter
from fastapi_cache.decorator import cache
Expand Down
12 changes: 8 additions & 4 deletions api/entrypoints/router.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from api.entrypoints import monitoring, v1
from api.entrypoints import monitoring, random_response, root_response, user
from fastapi.routing import APIRouter

api_router = APIRouter()
api_router.include_router(monitoring.router)
api_router.include_router(v1.router, prefix="/v1", tags=["v1"])
router = APIRouter()
router.include_router(monitoring.router, tags=["Monitoring"])
router.include_router(
random_response.router, prefix="/random_number", tags=["Random Number"]
)
router.include_router(root_response.router, prefix="", tags=["Root Response"])
router.include_router(user.router, prefix="/user", tags=["User"])
33 changes: 15 additions & 18 deletions api/entrypoints/v1/user/views.py → api/entrypoints/user.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
from typing import List

from api.database.models.users import User
from api.schemas.user import UserCreate
from api.schemas.user import UserCreate, UserOut
from api.services.user_service import UserService
from fastapi import APIRouter
from loguru import logger

router = APIRouter()


@router.get("/", response_model=List[UserCreate])
async def get_users() -> List[UserCreate]:
@router.get("/", response_model=List[UserOut])
async def get_users() -> List[UserOut]:
"""
Retrieve a list of all users.
Returns:
List[User]: A list of User objects.
"""
return await User.get_all()
return await UserService().get_all()


@router.get("/{user_id}", response_model=UserCreate)
async def get_user(user_id: int):
@router.get("/{user_id}", response_model=UserOut)
async def get_user(user_id: int) -> UserOut:
"""
Retrieve a user by ID.
Expand All @@ -30,10 +29,10 @@ async def get_user(user_id: int):
Returns:
User: The User object.
"""
return await User.get_by_id(user_id)
return await UserService().get_user(user_id)


@router.post("/", response_model=UserCreate)
@router.post("/", response_model=UserOut)
async def create_user(user: UserCreate):
"""
Create a new user.
Expand All @@ -44,12 +43,11 @@ async def create_user(user: UserCreate):
Returns:
User: The created User object.
"""
logger.info(user.model_dump())
return await User.new(**user.model_dump())
return await UserService().create_user(user)


@router.put("/{user_id}", response_model=None)
async def update_user(user_id: int, user: UserCreate):
async def update_user(user_id: int, user: UserCreate) -> None:
"""
Update a user by ID.
Expand All @@ -60,16 +58,15 @@ async def update_user(user_id: int, user: UserCreate):
Returns:
User: The updated User object.
"""
return await User.update_by_id(user_id, **user.model_dump())
return await UserService().update_user(user_id, user)


@router.delete("/{user_id}")
async def delete_user(user_id: int):
@router.delete("/{user_id}", response_model=None)
async def delete_user(user_id: int) -> None:
"""
Delete a user by ID.
Args:
user_id (int): The ID of the user.
"""
await User.delete_by_id(user_id)
return {"message": "User deleted successfully"}
return await UserService().delete_user(user_id)
4 changes: 0 additions & 4 deletions api/entrypoints/v1/__init__.py

This file was deleted.

4 changes: 0 additions & 4 deletions api/entrypoints/v1/random_number/__init__.py

This file was deleted.

4 changes: 0 additions & 4 deletions api/entrypoints/v1/root_response/__init__.py

This file was deleted.

9 changes: 0 additions & 9 deletions api/entrypoints/v1/router.py

This file was deleted.

4 changes: 0 additions & 4 deletions api/entrypoints/v1/user/__init__.py

This file was deleted.

10 changes: 10 additions & 0 deletions api/exceptions/http_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from api.database.models.model_base import ModelBase
from fastapi import HTTPException, status


class NotFoundException(HTTPException):
def __init__(self, model: ModelBase):
super().__init__(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"{model.__name__} not found",
)
20 changes: 20 additions & 0 deletions api/schemas/base_db_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from datetime import datetime
from typing import Optional

from pydantic import BaseModel, field_serializer


class BaseDBSchema(BaseModel):
id: int
created_at: datetime
updated_at: datetime
deleted_at: Optional[datetime]

class Config:
orm_mode = True

@field_serializer("created_at", "updated_at", "deleted_at")
def serialize_dt(self, dt: Optional[datetime]):
if not dt:
return dt
return dt.strftime("%d-%m-%Y %H:%M:%S")
File renamed without changes.
File renamed without changes.
6 changes: 6 additions & 0 deletions api/schemas/user.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from api.schemas.base_db_schema import BaseDBSchema
from pydantic import BaseModel


class UserCreate(BaseModel):
name: str
email: str
password: str


class UserOut(BaseDBSchema):
name: str
email: str
4 changes: 2 additions & 2 deletions api/services/random_number_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import asyncio
from random import random

from api.entrypoints.v1.random_number.schema import RandomResponse
from api.schemas.random_number import RandomResponse
from loguru import logger


class RandomNumberService:
class RandomResponseService:
"""
Random number service class.
"""
Expand Down
2 changes: 1 addition & 1 deletion api/services/root_response_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import asyncio
from random import random

from api.entrypoints.v1.root_response.schema import RootResponse
from api.schemas.root_response import RootResponse
from loguru import logger


Expand Down
71 changes: 71 additions & 0 deletions api/services/user_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from typing import List, Union

from api.database.models.users import User
from api.exceptions.http_exceptions import NotFoundException
from api.schemas.user import UserCreate
from loguru import logger


class UserService:
async def get_all(self) -> List[User]:
"""
Retrieve a list of all users.
Returns:
List[User]: A list of User objects.
"""
return await User.get_all()

async def get_user(self, user_id: int) -> User:
"""
Retrieve a user by ID.
Args:
user_id (int): The ID of the user.
Returns:
User: The User object.
"""
user: Union[User, None] = await User.get_by_id(user_id)
if not user:
raise NotFoundException(User)
return user

async def create_user(self, user: UserCreate) -> User:
"""
Create a new user.
Args:
user (UserCreate): The user data.
Returns:
User: The created User object.
"""
logger.info(user.model_dump())
return await User.new(**user.model_dump())

async def update_user(self, user_id: int, updated_user: UserCreate):
"""
Update a user by ID.
Args:
user_id (int): The ID of the user.
user (UserUpdate): The updated user data.
Returns:
User: The updated User object.
"""
user: User = await self.get_user(user_id)
return await user.update(**updated_user.model_dump())

async def delete_user(self, user_id: int):
"""
Delete a user by ID.
Args:
user_id (int): The ID of the user.
"""

user: User = await self.get_user(user_id)
await user.delete()
return {"message": "User deleted successfully"}
Loading

0 comments on commit 0f85f0c

Please # to comment.