From f14c58638945343cdf08f04886a8ef4347f0bd5f Mon Sep 17 00:00:00 2001 From: grillazz Date: Tue, 29 Jul 2025 17:26:37 +0200 Subject: [PATCH 1/4] add json filed example --- Makefile | 2 +- ...250729_1521_d021bd4763a5_add_json_chaos.py | 37 +++++++++++++++++++ app/api/stuff.py | 13 ++++++- app/models/base.py | 3 +- app/models/stuff.py | 12 ++++++ app/schemas/stuff.py | 7 +++- compose.yml | 1 + 7 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 alembic/versions/20250729_1521_d021bd4763a5_add_json_chaos.py diff --git a/Makefile b/Makefile index 6bafa7d..3d6ef3a 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ docker-apply-db-migrations: ## apply alembic migrations to database/schema docker compose run --rm app alembic upgrade head .PHONY: docker-create-db-migration -docker-create-db-migration: ## Create new alembic database migration aka database revision. +docker-create-db-migration: ## Create new alembic database migration aka database revision. Example: make docker-create-db-migration msg="add users table" docker compose up -d db | true docker compose run --no-deps app alembic revision --autogenerate -m "$(msg)" diff --git a/alembic/versions/20250729_1521_d021bd4763a5_add_json_chaos.py b/alembic/versions/20250729_1521_d021bd4763a5_add_json_chaos.py new file mode 100644 index 0000000..a629c14 --- /dev/null +++ b/alembic/versions/20250729_1521_d021bd4763a5_add_json_chaos.py @@ -0,0 +1,37 @@ +"""add json chaos + +Revision ID: d021bd4763a5 +Revises: 0c69050b5a3e +Create Date: 2025-07-29 15:21:19.415583 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'd021bd4763a5' +down_revision = '0c69050b5a3e' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('random_stuff', + sa.Column('id', sa.UUID(), nullable=False), + sa.Column('chaos', postgresql.JSON(astext_type=sa.Text()), nullable=False), + sa.PrimaryKeyConstraint('id'), + schema='happy_hog' + ) + op.create_unique_constraint(None, 'nonsense', ['name'], schema='happy_hog') + op.create_unique_constraint(None, 'stuff', ['name'], schema='happy_hog') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'stuff', schema='happy_hog', type_='unique') + op.drop_constraint(None, 'nonsense', schema='happy_hog', type_='unique') + op.drop_table('random_stuff', schema='happy_hog') + # ### end Alembic commands ### diff --git a/app/api/stuff.py b/app/api/stuff.py index 379b661..baacee7 100644 --- a/app/api/stuff.py +++ b/app/api/stuff.py @@ -4,14 +4,23 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db -from app.models.stuff import Stuff -from app.schemas.stuff import StuffResponse, StuffSchema +from app.models.stuff import Stuff, RandomStuff +from app.schemas.stuff import StuffResponse, StuffSchema, RandomStuff as RandomStuffSchema logger = AppStructLogger().get_logger() router = APIRouter(prefix="/v1/stuff") +@router.post("/random", status_code=status.HTTP_201_CREATED) +async def create_random_stuff( + payload: RandomStuffSchema, db_session: AsyncSession = Depends(get_db) +) -> dict[str, str]: + random_stuff = RandomStuff(**payload.model_dump()) + await random_stuff.save(db_session) + return {"id": str(random_stuff.id)} + + @router.post("/add_many", status_code=status.HTTP_201_CREATED) async def create_multi_stuff( payload: list[StuffSchema], db_session: AsyncSession = Depends(get_db) diff --git a/app/models/base.py b/app/models/base.py index 5e059df..77575f7 100644 --- a/app/models/base.py +++ b/app/models/base.py @@ -27,7 +27,8 @@ async def save(self, db_session: AsyncSession): """ try: db_session.add(self) - return await db_session.commit() + await db_session.commit() + return await db_session.refresh(self) except SQLAlchemyError as ex: await logger.aerror(f"Error inserting instance of {self}: {repr(ex)}") raise HTTPException( diff --git a/app/models/stuff.py b/app/models/stuff.py index 248d7e3..683732e 100644 --- a/app/models/stuff.py +++ b/app/models/stuff.py @@ -9,6 +9,18 @@ from app.models.nonsense import Nonsense from app.utils.decorators import compile_sql_or_scalar +from sqlalchemy.dialects.postgresql import JSON + + +class RandomStuff(Base): + __tablename__ = "random_stuff" + __table_args__ = ({"schema": "happy_hog"},) + + id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), default=uuid.uuid4, primary_key=True + ) + chaos: Mapped[dict] = mapped_column(JSON) + class Stuff(Base): __tablename__ = "stuff" diff --git a/app/schemas/stuff.py b/app/schemas/stuff.py index d5703b2..258cc87 100644 --- a/app/schemas/stuff.py +++ b/app/schemas/stuff.py @@ -1,10 +1,13 @@ from uuid import UUID -from pydantic import BaseModel, ConfigDict, Field - +from pydantic import BaseModel, ConfigDict, Field, Json +from typing import Any config = ConfigDict(from_attributes=True) +class RandomStuff(BaseModel): + chaos: dict[str, Any] = Field(..., description="JSON data for chaos field") + class StuffSchema(BaseModel): name: str = Field( title="", diff --git a/compose.yml b/compose.yml index 293db32..d9fdc99 100644 --- a/compose.yml +++ b/compose.yml @@ -18,6 +18,7 @@ services: - ./app:/panettone/app - ./tests:/panettone/tests - ./templates:/panettone/templates + - ./alembic:/panettone/alembic ports: - "8080:8080" depends_on: From 7aace85eeb17e545d6708820e9d8a7309eb0ddf8 Mon Sep 17 00:00:00 2001 From: grillazz Date: Tue, 29 Jul 2025 17:26:54 +0200 Subject: [PATCH 2/4] lint --- app/api/stuff.py | 5 +++-- app/models/stuff.py | 4 +--- app/schemas/stuff.py | 5 +++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/api/stuff.py b/app/api/stuff.py index baacee7..7ff8ee6 100644 --- a/app/api/stuff.py +++ b/app/api/stuff.py @@ -4,8 +4,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db -from app.models.stuff import Stuff, RandomStuff -from app.schemas.stuff import StuffResponse, StuffSchema, RandomStuff as RandomStuffSchema +from app.models.stuff import RandomStuff, Stuff +from app.schemas.stuff import RandomStuff as RandomStuffSchema +from app.schemas.stuff import StuffResponse, StuffSchema logger = AppStructLogger().get_logger() diff --git a/app/models/stuff.py b/app/models/stuff.py index 683732e..1edf5c1 100644 --- a/app/models/stuff.py +++ b/app/models/stuff.py @@ -1,7 +1,7 @@ import uuid from sqlalchemy import ForeignKey, String, select -from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.dialects.postgresql import JSON, UUID from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Mapped, joinedload, mapped_column, relationship @@ -9,8 +9,6 @@ from app.models.nonsense import Nonsense from app.utils.decorators import compile_sql_or_scalar -from sqlalchemy.dialects.postgresql import JSON - class RandomStuff(Base): __tablename__ = "random_stuff" diff --git a/app/schemas/stuff.py b/app/schemas/stuff.py index 258cc87..994d266 100644 --- a/app/schemas/stuff.py +++ b/app/schemas/stuff.py @@ -1,7 +1,8 @@ +from typing import Any from uuid import UUID -from pydantic import BaseModel, ConfigDict, Field, Json -from typing import Any +from pydantic import BaseModel, ConfigDict, Field + config = ConfigDict(from_attributes=True) From 060bdb65feb22755d85e0d44eb2ed4ff5ecd4952 Mon Sep 17 00:00:00 2001 From: grillazz Date: Tue, 29 Jul 2025 17:27:56 +0200 Subject: [PATCH 3/4] format code --- app/main.py | 11 +++++++++-- app/schemas/stuff.py | 1 + app/utils/logging.py | 6 +++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/main.py b/app/main.py index 63a016c..dab1176 100644 --- a/app/main.py +++ b/app/main.py @@ -20,6 +20,7 @@ logger = AppStructLogger().get_logger() templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates") + @asynccontextmanager async def lifespan(app: FastAPI): app.redis = await get_redis() @@ -30,12 +31,15 @@ async def lifespan(app: FastAPI): min_size=5, max_size=20, ) - await logger.ainfo("Postgres pool created", idle_size=app.postgres_pool.get_idle_size()) + await logger.ainfo( + "Postgres pool created", idle_size=app.postgres_pool.get_idle_size() + ) yield finally: await app.redis.close() await app.postgres_pool.close() + def create_app() -> FastAPI: app = FastAPI( title="Stuff And Nonsense API", @@ -47,7 +51,9 @@ def create_app() -> FastAPI: app.include_router(shakespeare_router) app.include_router(user_router) app.include_router(ml_router, prefix="/v1/ml", tags=["ML"]) - app.include_router(health_router, prefix="/v1/public/health", tags=["Health, Public"]) + app.include_router( + health_router, prefix="/v1/public/health", tags=["Health, Public"] + ) app.include_router( health_router, prefix="/v1/health", @@ -61,6 +67,7 @@ def get_index(request: Request): return app + app = create_app() # --- Unused/experimental code and TODOs --- diff --git a/app/schemas/stuff.py b/app/schemas/stuff.py index 994d266..18b3700 100644 --- a/app/schemas/stuff.py +++ b/app/schemas/stuff.py @@ -9,6 +9,7 @@ class RandomStuff(BaseModel): chaos: dict[str, Any] = Field(..., description="JSON data for chaos field") + class StuffSchema(BaseModel): name: str = Field( title="", diff --git a/app/utils/logging.py b/app/utils/logging.py index 293efbe..1aece2b 100644 --- a/app/utils/logging.py +++ b/app/utils/logging.py @@ -30,7 +30,7 @@ def msg(self, message): lineno=0, msg=message.rstrip("\n"), args=(), - exc_info=None + exc_info=None, ) # Check if rotation is needed before emitting @@ -78,7 +78,7 @@ def __attrs_post_init__(self): filename=_log_path, maxBytes=10 * 1024 * 1024, # 10MB backupCount=5, - encoding="utf-8" + encoding="utf-8", ) structlog.configure( cache_logger_on_first_use=True, @@ -90,7 +90,7 @@ def __attrs_post_init__(self): structlog.processors.TimeStamper(fmt="iso", utc=True), structlog.processors.JSONRenderer(serializer=orjson.dumps), ], - logger_factory=RotatingBytesLoggerFactory(_handler) + logger_factory=RotatingBytesLoggerFactory(_handler), ) self._logger = structlog.get_logger() From 93f2e66bd040dcebece794f14ecc513801280362 Mon Sep 17 00:00:00 2001 From: grillazz Date: Tue, 29 Jul 2025 17:33:17 +0200 Subject: [PATCH 4/4] format code --- app/models/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/base.py b/app/models/base.py index 77575f7..b8665a1 100644 --- a/app/models/base.py +++ b/app/models/base.py @@ -28,7 +28,8 @@ async def save(self, db_session: AsyncSession): try: db_session.add(self) await db_session.commit() - return await db_session.refresh(self) + await db_session.refresh(self) + return self except SQLAlchemyError as ex: await logger.aerror(f"Error inserting instance of {self}: {repr(ex)}") raise HTTPException(