mirror of
https://github.com/grillazz/fastapi-sqlalchemy-asyncpg.git
synced 2026-04-23 08:20:39 +03:00
Merge pull request #238 from grillazz/rotoger-one-more-time
rotoger-one-more-time
This commit is contained in:
+21
-2
@@ -1,4 +1,8 @@
|
|||||||
|
from collections.abc import Callable
|
||||||
|
from typing import Annotated, Any
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||||
|
from pydantic import ValidationError, WrapValidator
|
||||||
from rotoger import get_logger
|
from rotoger import get_logger
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@@ -22,12 +26,26 @@ async def create_random_stuff(
|
|||||||
return {"id": str(random_stuff.id)}
|
return {"id": str(random_stuff.id)}
|
||||||
|
|
||||||
|
|
||||||
|
failed_items: list[dict] = [] # Global or pass via context
|
||||||
|
|
||||||
|
def catch_invalid(v: Any, handler: Callable[[Any], Any] ) -> Any:
|
||||||
|
try:
|
||||||
|
return handler(v)
|
||||||
|
except ValidationError:
|
||||||
|
failed_items.append(v) # Intercept here!
|
||||||
|
return None # Or raise if needed
|
||||||
|
|
||||||
@router.post("/add_many", status_code=status.HTTP_201_CREATED)
|
@router.post("/add_many", status_code=status.HTTP_201_CREATED)
|
||||||
async def create_multi_stuff(
|
async def create_multi_stuff(
|
||||||
payload: list[StuffSchema], db_session: AsyncSession = Depends(get_db)
|
payload: list[Annotated[StuffSchema, WrapValidator(catch_invalid)]], db_session: AsyncSession = Depends(get_db)
|
||||||
):
|
):
|
||||||
|
await logger.ainfo(f">>>{failed_items}")
|
||||||
try:
|
try:
|
||||||
stuff_instances = [Stuff(**stuff.model_dump()) for stuff in payload]
|
await logger.ainfo(f">>>{failed_items}")
|
||||||
|
await logger.ainfo(f">>>{payload}")
|
||||||
|
stuff_instances = [
|
||||||
|
Stuff(**stuff.model_dump()) for stuff in payload if stuff is not None
|
||||||
|
]
|
||||||
db_session.add_all(stuff_instances)
|
db_session.add_all(stuff_instances)
|
||||||
await db_session.commit()
|
await db_session.commit()
|
||||||
except SQLAlchemyError as ex:
|
except SQLAlchemyError as ex:
|
||||||
@@ -39,6 +57,7 @@ async def create_multi_stuff(
|
|||||||
await logger.ainfo(
|
await logger.ainfo(
|
||||||
f"{len(stuff_instances)} Stuff instances inserted into the database."
|
f"{len(stuff_instances)} Stuff instances inserted into the database."
|
||||||
)
|
)
|
||||||
|
return {"inserted": len(stuff_instances)}
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -21,12 +21,12 @@ from app.middleware.profiler import ProfilingMiddleware
|
|||||||
from app.redis import get_redis
|
from app.redis import get_redis
|
||||||
from app.services.auth import AuthBearer
|
from app.services.auth import AuthBearer
|
||||||
|
|
||||||
logger = get_logger()
|
|
||||||
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
|
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
|
app.logger = get_logger()
|
||||||
app.redis = await get_redis()
|
app.redis = await get_redis()
|
||||||
postgres_dsn = global_settings.postgres_url.unicode_string()
|
postgres_dsn = global_settings.postgres_url.unicode_string()
|
||||||
try:
|
try:
|
||||||
@@ -35,12 +35,12 @@ async def lifespan(app: FastAPI):
|
|||||||
min_size=5,
|
min_size=5,
|
||||||
max_size=20,
|
max_size=20,
|
||||||
)
|
)
|
||||||
await logger.ainfo(
|
await app.logger.ainfo(
|
||||||
"Postgres pool created", idle_size=app.postgres_pool.get_idle_size()
|
"Postgres pool created", idle_size=app.postgres_pool.get_idle_size()
|
||||||
)
|
)
|
||||||
yield
|
yield
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await logger.aerror("Error during app startup", error=repr(e))
|
await app.logger.aerror("Error during app startup", error=repr(e))
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
await app.redis.close()
|
await app.redis.close()
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from pydantic import BaseModel, ConfigDict, Field
|
|||||||
|
|
||||||
config = ConfigDict(from_attributes=True)
|
config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
|
||||||
class RandomStuff(BaseModel):
|
class RandomStuff(BaseModel):
|
||||||
chaos: dict[str, Any] = Field(
|
chaos: dict[str, Any] = Field(
|
||||||
..., description="Pretty chaotic JSON data can be added here..."
|
..., description="Pretty chaotic JSON data can be added here..."
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from granian import Granian
|
||||||
|
|
||||||
|
|
||||||
|
def startup():
|
||||||
|
print("Server starting up...")
|
||||||
|
|
||||||
|
def shutdown():
|
||||||
|
print("Server shutting down...")
|
||||||
|
|
||||||
|
server = Granian(
|
||||||
|
"main:app",
|
||||||
|
host="0.0.0.0", # Bind to all interfaces
|
||||||
|
port=8000,
|
||||||
|
workers=4,
|
||||||
|
interface="asgi",
|
||||||
|
blocking_threads=8 # Optional: threads per worker for blocking ops
|
||||||
|
)
|
||||||
|
server.on_startup(startup)
|
||||||
|
server.on_shutdown(shutdown)
|
||||||
|
server.serve_forever()
|
||||||
+2
-2
@@ -22,14 +22,14 @@ dependencies = [
|
|||||||
"redis==7.1.0",
|
"redis==7.1.0",
|
||||||
"bcrypt==5.0.0",
|
"bcrypt==5.0.0",
|
||||||
"polars[pyarrow]==1.36.1",
|
"polars[pyarrow]==1.36.1",
|
||||||
"python-multipart==0.0.20",
|
"python-multipart==0.0.22",
|
||||||
"fastexcel==0.18.0",
|
"fastexcel==0.18.0",
|
||||||
"inline-snapshot==0.31.1",
|
"inline-snapshot==0.31.1",
|
||||||
"dirty-equals==0.11",
|
"dirty-equals==0.11",
|
||||||
"polyfactory==3.1.0",
|
"polyfactory==3.1.0",
|
||||||
"granian==2.6.0",
|
"granian==2.6.0",
|
||||||
"apscheduler[redis,sqlalchemy]>=4.0.0a6",
|
"apscheduler[redis,sqlalchemy]>=4.0.0a6",
|
||||||
"rotoger==0.2.1",
|
"rotoger==0.3.0",
|
||||||
"pyinstrument>=5.1.2",
|
"pyinstrument>=5.1.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -438,10 +438,10 @@ requires-dist = [
|
|||||||
{ name = "pyjwt", specifier = "==2.10.1" },
|
{ name = "pyjwt", specifier = "==2.10.1" },
|
||||||
{ name = "pytest", specifier = "==9.0.2" },
|
{ name = "pytest", specifier = "==9.0.2" },
|
||||||
{ name = "pytest-cov", specifier = "==7.0.0" },
|
{ name = "pytest-cov", specifier = "==7.0.0" },
|
||||||
{ name = "python-multipart", specifier = "==0.0.20" },
|
{ name = "python-multipart", specifier = "==0.0.22" },
|
||||||
{ name = "redis", specifier = "==7.1.0" },
|
{ name = "redis", specifier = "==7.1.0" },
|
||||||
{ name = "rich", specifier = "==14.2.0" },
|
{ name = "rich", specifier = "==14.2.0" },
|
||||||
{ name = "rotoger", specifier = "==0.2.1" },
|
{ name = "rotoger", specifier = "==0.3.0" },
|
||||||
{ name = "sqlalchemy", specifier = "==2.0.45" },
|
{ name = "sqlalchemy", specifier = "==2.0.45" },
|
||||||
{ name = "uvicorn", extras = ["standard"], specifier = "==0.38.0" },
|
{ name = "uvicorn", extras = ["standard"], specifier = "==0.38.0" },
|
||||||
{ name = "uvloop", specifier = "==0.22.1" },
|
{ name = "uvloop", specifier = "==0.22.1" },
|
||||||
@@ -1075,11 +1075,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-multipart"
|
name = "python-multipart"
|
||||||
version = "0.0.20"
|
version = "0.0.22"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
{ url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1196,7 +1196,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rotoger"
|
name = "rotoger"
|
||||||
version = "0.2.1"
|
version = "0.3.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "attrs" },
|
{ name = "attrs" },
|
||||||
@@ -1204,9 +1204,9 @@ dependencies = [
|
|||||||
{ name = "structlog" },
|
{ name = "structlog" },
|
||||||
{ name = "whenever" },
|
{ name = "whenever" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/ad/75a22ddd259505547fd47c36ea984688e3b56d9cbc49c0f98bb95c84c01b/rotoger-0.2.1.tar.gz", hash = "sha256:823bb39c781d6038d2aae1c2c3f6d74c0abb1e9f07b257c079028d6ae3f2589d", size = 1647, upload-time = "2025-11-13T16:12:27.833Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/06/40/475df92ef562d22489a1b07bc41cf1094dce1d4d03475f59112b97070560/rotoger-0.3.0.tar.gz", hash = "sha256:e407e3f4cf4948886bd26b35bd54e635f58703db406f5a706e4c4579dc273241", size = 2714, upload-time = "2026-03-01T17:08:46.78Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e7/da/9422061c62499eaafcf90c4adf3e13c51031d4b593af899451825fa85a7b/rotoger-0.2.1-py3-none-any.whl", hash = "sha256:849ed131068ab724991c38c32fb63e4904efb79e29bf084f37ec11a31ec0c703", size = 2603, upload-time = "2025-11-13T16:12:26.895Z" },
|
{ url = "https://files.pythonhosted.org/packages/72/85/2164d61cff7594366d5797cc6f33784a05e83a39841701860bbbc41631dc/rotoger-0.3.0-py3-none-any.whl", hash = "sha256:08d3c239f05c0551a9cdb682332f4c1e981844b1b0afa7b1e04c50730bbe2098", size = 3458, upload-time = "2026-03-01T17:08:47.684Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user