Skip to content
Merged
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
2 changes: 1 addition & 1 deletion app/api/stuff.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,5 @@ async def update_stuff(
db_session: AsyncSession = Depends(get_db),
):
stuff = await Stuff.find(db_session, name)
await stuff.update(db_session, **payload.model_dump())
await stuff.update(**payload.model_dump())
return stuff
13 changes: 10 additions & 3 deletions app/database.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections.abc import AsyncGenerator

from rotoger import AppStructLogger
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine

from app.config import settings as global_settings
Expand Down Expand Up @@ -28,6 +29,12 @@ async def get_db() -> AsyncGenerator:
# logger.debug(f"ASYNC Pool: {engine.pool.status()}")
try:
yield session
except Exception as e:
await logger.aerror(f"Error getting database session: {e}")
raise
await session.commit()
except Exception as ex:
if isinstance(ex, SQLAlchemyError):
# Re-raise SQLAlchemyError directly without handling
raise
else:
# Handle other exceptions
await logger.aerror(f"NonSQLAlchemyError: {repr(ex)}")
raise # Re-raise after logging
35 changes: 35 additions & 0 deletions app/exception_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import orjson
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from rotoger import AppStructLogger
from sqlalchemy.exc import SQLAlchemyError

logger = AppStructLogger().get_logger()


async def sqlalchemy_exception_handler(
request: Request, exc: SQLAlchemyError
) -> JSONResponse:
request_path = request.url.path
try:
raw_body = await request.body()
request_body = orjson.loads(raw_body) if raw_body else None
except orjson.JSONDecodeError:
request_body = None

await logger.aerror(
"Database error occurred",
sql_error=repr(exc),
request_url=request_path,
request_body=request_body,
)

return JSONResponse(
status_code=500,
content={"message": "A database error occurred. Please try again later."},
)


def register_exception_handlers(app: FastAPI) -> None:
"""Register all exception handlers with the FastAPI app."""
app.add_exception_handler(SQLAlchemyError, sqlalchemy_exception_handler)
59 changes: 0 additions & 59 deletions app/exceptions.py

This file was deleted.

4 changes: 4 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from app.api.stuff import router as stuff_router
from app.api.user import router as user_router
from app.config import settings as global_settings
from app.exception_handlers import register_exception_handlers
from app.redis import get_redis
from app.services.auth import AuthBearer

Expand Down Expand Up @@ -61,6 +62,9 @@ def create_app() -> FastAPI:
dependencies=[Depends(AuthBearer())],
)

# Register exception handlers
register_exception_handlers(app)

@app.get("/index", response_class=HTMLResponse)
def get_index(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
Expand Down
46 changes: 11 additions & 35 deletions app/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,64 +20,40 @@ def __tablename__(self) -> str:
return self.__name__.lower()

async def save(self, db_session: AsyncSession):
"""

:param db_session:
:return:
"""
try:
db_session.add(self)
await db_session.commit()
await db_session.refresh(self)
return self
except SQLAlchemyError as ex:
await logger.aerror(f"Error inserting instance of {self}: {repr(ex)}")
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)
) from ex
db_session.add(self)
await db_session.flush()
await db_session.refresh(self)
return self

async def delete(self, db_session: AsyncSession):
"""

:param db_session:
:return:
"""
try:
await db_session.delete(self)
await db_session.commit()
return True
except SQLAlchemyError as ex:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)
) from ex

async def update(self, db: AsyncSession, **kwargs):
"""

:param db:
:param kwargs
:return:
"""
async def update(self, **kwargs):
try:
for k, v in kwargs.items():
setattr(self, k, v)
return await db.commit()
return True
except SQLAlchemyError as ex:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)
) from ex

async def save_or_update(self, db: AsyncSession):
async def save_or_update(self, db_session: AsyncSession):
try:
db.add(self)
return await db.commit()
db_session.add(self)
await db_session.flush()
return True
except IntegrityError as exception:
if isinstance(exception.orig, UniqueViolationError):
return await db.merge(self)
return await db_session.merge(self)
else:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=repr(exception),
) from exception
finally:
await db.close()
4 changes: 3 additions & 1 deletion app/schemas/stuff.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@


class RandomStuff(BaseModel):
chaos: dict[str, Any] = Field(..., description="JSON data for chaos field")
chaos: dict[str, Any] = Field(
..., description="Pretty chaotic JSON data can be added here..."
)


class StuffSchema(BaseModel):
Expand Down
Empty file added tests/api/test_chaotic_stuff.py
Empty file.