From 6333c6678a5240925f632de6e1fffdd6382df0c2 Mon Sep 17 00:00:00 2001 From: Piotr Oleszczyk Date: Fri, 27 Feb 2026 11:20:13 +0100 Subject: [PATCH] refactor: remove personal_rating, DRY get_or_404, fix ty errors - Drop Product.personal_rating from model, API schemas, and all frontend views (list table, detail view, quick-edit form, new-product form) - Extract get_or_404 into backend/innercontext/api/utils.py; remove five duplicate copies from individual API modules - Fix all ty type errors: generic get_or_404 with TypeVar, cast() in coerce_effect_profile validator, col() for ilike on SQLModel column, dict[str, Any] annotation in test helper, ty: ignore for CORSMiddleware Co-Authored-By: Claude Sonnet 4.6 --- backend/innercontext/api/health.py | 12 +++--------- backend/innercontext/api/inventory.py | 8 +------- backend/innercontext/api/products.py | 10 +--------- backend/innercontext/api/routines.py | 8 +------- backend/innercontext/api/skincare.py | 8 +------- backend/innercontext/api/utils.py | 13 +++++++++++++ backend/innercontext/models/product.py | 7 ++----- backend/main.py | 2 +- backend/tests/test_product_model.py | 6 ++++-- frontend/src/lib/types.ts | 1 - frontend/src/routes/products/+page.svelte | 10 +--------- frontend/src/routes/products/[id]/+page.server.ts | 3 +-- frontend/src/routes/products/[id]/+page.svelte | 15 +-------------- frontend/src/routes/products/new/+page.server.ts | 3 --- frontend/src/routes/products/new/+page.svelte | 5 ----- 15 files changed, 30 insertions(+), 81 deletions(-) create mode 100644 backend/innercontext/api/utils.py diff --git a/backend/innercontext/api/health.py b/backend/innercontext/api/health.py index 0bf7fc2..5f04434 100644 --- a/backend/innercontext/api/health.py +++ b/backend/innercontext/api/health.py @@ -5,9 +5,10 @@ from uuid import UUID, uuid4 from fastapi import APIRouter, Depends, HTTPException from pydantic import field_validator -from sqlmodel import Session, SQLModel, select +from sqlmodel import Session, SQLModel, col, select from db import get_session +from innercontext.api.utils import get_or_404 from innercontext.models import LabResult, MedicationEntry, MedicationUsage from innercontext.models.enums import MedicationKind, ResultFlag @@ -121,13 +122,6 @@ class LabResultUpdate(SQLModel): # --------------------------------------------------------------------------- -def get_or_404(session: Session, model, record_id) -> object: - obj = session.get(model, record_id) - if obj is None: - raise HTTPException(status_code=404, detail=f"{model.__name__} not found") - return obj - - # --------------------------------------------------------------------------- # Medication routes # --------------------------------------------------------------------------- @@ -143,7 +137,7 @@ def list_medications( if kind is not None: stmt = stmt.where(MedicationEntry.kind == kind) if product_name is not None: - stmt = stmt.where(MedicationEntry.product_name.ilike(f"%{product_name}%")) + stmt = stmt.where(col(MedicationEntry.product_name).ilike(f"%{product_name}%")) return session.exec(stmt).all() diff --git a/backend/innercontext/api/inventory.py b/backend/innercontext/api/inventory.py index 64a2efa..85fdce3 100644 --- a/backend/innercontext/api/inventory.py +++ b/backend/innercontext/api/inventory.py @@ -4,19 +4,13 @@ from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session from db import get_session +from innercontext.api.utils import get_or_404 from innercontext.models import ProductInventory from innercontext.api.products import InventoryUpdate router = APIRouter() -def get_or_404(session: Session, model, record_id) -> object: - obj = session.get(model, record_id) - if obj is None: - raise HTTPException(status_code=404, detail=f"{model.__name__} not found") - return obj - - @router.get("/{inventory_id}", response_model=ProductInventory) def get_inventory(inventory_id: UUID, session: Session = Depends(get_session)): return get_or_404(session, ProductInventory, inventory_id) diff --git a/backend/innercontext/api/products.py b/backend/innercontext/api/products.py index fda0a79..d7fc443 100644 --- a/backend/innercontext/api/products.py +++ b/backend/innercontext/api/products.py @@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query from sqlmodel import Session, SQLModel, select from db import get_session +from innercontext.api.utils import get_or_404 from innercontext.models import ( Product, ProductCategory, @@ -83,7 +84,6 @@ class ProductCreate(SQLModel): is_tool: bool = False needle_length_mm: Optional[float] = None - personal_rating: Optional[int] = None personal_tolerance_notes: Optional[str] = None personal_repurchase_intent: Optional[bool] = None @@ -137,7 +137,6 @@ class ProductUpdate(SQLModel): is_tool: Optional[bool] = None needle_length_mm: Optional[float] = None - personal_rating: Optional[int] = None personal_tolerance_notes: Optional[str] = None personal_repurchase_intent: Optional[bool] = None @@ -165,13 +164,6 @@ class InventoryUpdate(SQLModel): # --------------------------------------------------------------------------- -def get_or_404(session: Session, model, record_id) -> object: - obj = session.get(model, record_id) - if obj is None: - raise HTTPException(status_code=404, detail=f"{model.__name__} not found") - return obj - - # --------------------------------------------------------------------------- # Product routes # --------------------------------------------------------------------------- diff --git a/backend/innercontext/api/routines.py b/backend/innercontext/api/routines.py index 51bc70a..ad8b25c 100644 --- a/backend/innercontext/api/routines.py +++ b/backend/innercontext/api/routines.py @@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session, SQLModel, select from db import get_session +from innercontext.api.utils import get_or_404 from innercontext.models import GroomingSchedule, Routine, RoutineStep from innercontext.models.enums import GroomingAction, PartOfDay @@ -64,13 +65,6 @@ class GroomingScheduleUpdate(SQLModel): # --------------------------------------------------------------------------- -def get_or_404(session: Session, model, record_id) -> object: - obj = session.get(model, record_id) - if obj is None: - raise HTTPException(status_code=404, detail=f"{model.__name__} not found") - return obj - - # --------------------------------------------------------------------------- # Routine routes # --------------------------------------------------------------------------- diff --git a/backend/innercontext/api/skincare.py b/backend/innercontext/api/skincare.py index 35b5bda..19979bc 100644 --- a/backend/innercontext/api/skincare.py +++ b/backend/innercontext/api/skincare.py @@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session, SQLModel, select from db import get_session +from innercontext.api.utils import get_or_404 from innercontext.models import SkinConditionSnapshot from innercontext.models.enums import ( BarrierState, @@ -66,13 +67,6 @@ class SnapshotUpdate(SQLModel): # --------------------------------------------------------------------------- -def get_or_404(session: Session, model, record_id) -> object: - obj = session.get(model, record_id) - if obj is None: - raise HTTPException(status_code=404, detail=f"{model.__name__} not found") - return obj - - # --------------------------------------------------------------------------- # Routes # --------------------------------------------------------------------------- diff --git a/backend/innercontext/api/utils.py b/backend/innercontext/api/utils.py new file mode 100644 index 0000000..6321f07 --- /dev/null +++ b/backend/innercontext/api/utils.py @@ -0,0 +1,13 @@ +from typing import TypeVar + +from fastapi import HTTPException +from sqlmodel import Session + +_T = TypeVar("_T") + + +def get_or_404(session: Session, model: type[_T], record_id: object) -> _T: + obj = session.get(model, record_id) + if obj is None: + raise HTTPException(status_code=404, detail=f"{model.__name__} not found") + return obj diff --git a/backend/innercontext/models/product.py b/backend/innercontext/models/product.py index 961f9a8..88d9cf1 100644 --- a/backend/innercontext/models/product.py +++ b/backend/innercontext/models/product.py @@ -1,5 +1,5 @@ from datetime import date, datetime -from typing import ClassVar, Optional +from typing import Any, ClassVar, Optional, cast from uuid import UUID, uuid4 from pydantic import field_validator, model_validator @@ -160,7 +160,6 @@ class Product(SQLModel, table=True): is_tool: bool = Field(default=False) needle_length_mm: float | None = Field(default=None, gt=0) - personal_rating: int | None = Field(default=None, ge=1, le=10) personal_tolerance_notes: str | None = None personal_repurchase_intent: bool | None = None @@ -181,7 +180,7 @@ class Product(SQLModel, table=True): @classmethod def coerce_effect_profile(cls, v: object) -> object: if isinstance(v, dict): - return ProductEffectProfile(**v) + return ProductEffectProfile(**cast(dict[str, Any], v)) return v @model_validator(mode="after") @@ -324,8 +323,6 @@ class Product(SQLModel, table=True): if self.usage_notes: ctx["usage_notes"] = self.usage_notes - if self.personal_rating is not None: - ctx["personal_rating"] = self.personal_rating if self.personal_tolerance_notes: ctx["personal_tolerance_notes"] = self.personal_tolerance_notes if self.personal_repurchase_intent is not None: diff --git a/backend/main.py b/backend/main.py index 0bd80c4..993ca93 100644 --- a/backend/main.py +++ b/backend/main.py @@ -20,7 +20,7 @@ async def lifespan(app: FastAPI): app = FastAPI(title="innercontext API", lifespan=lifespan, redirect_slashes=False) app.add_middleware( - CORSMiddleware, + CORSMiddleware, # ty: ignore[invalid-argument-type] allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], diff --git a/backend/tests/test_product_model.py b/backend/tests/test_product_model.py index 36660f1..735b1a7 100644 --- a/backend/tests/test_product_model.py +++ b/backend/tests/test_product_model.py @@ -1,6 +1,8 @@ """Unit tests for Product.to_llm_context() — no database required.""" from uuid import uuid4 +from typing import Any + import pytest from innercontext.models import Product @@ -18,8 +20,8 @@ from innercontext.models.product import ( ) -def _make(**kwargs): - defaults = dict( +def _make(**kwargs: Any) -> Product: + defaults: dict[str, Any] = dict( id=uuid4(), name="Test", brand="B", diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index affc1ea..bb20ca3 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -149,7 +149,6 @@ export interface Product { is_medication: boolean; is_tool: boolean; needle_length_mm?: number; - personal_rating?: number; personal_tolerance_notes?: string; personal_repurchase_intent?: boolean; created_at: string; diff --git a/frontend/src/routes/products/+page.svelte b/frontend/src/routes/products/+page.svelte index ed524c7..243ce20 100644 --- a/frontend/src/routes/products/+page.svelte +++ b/frontend/src/routes/products/+page.svelte @@ -67,7 +67,6 @@ Category Targets Time - Rating @@ -93,17 +92,10 @@ {product.recommended_time} - - {#if product.personal_rating} - {product.personal_rating}/10 - {:else} - - {/if} - {:else} - + No products found. diff --git a/frontend/src/routes/products/[id]/+page.server.ts b/frontend/src/routes/products/[id]/+page.server.ts index f4bf922..578f604 100644 --- a/frontend/src/routes/products/[id]/+page.server.ts +++ b/frontend/src/routes/products/[id]/+page.server.ts @@ -19,8 +19,7 @@ export const actions: Actions = { if (value !== '') body[key] = value; } if ('leave_on' in body) body.leave_on = body.leave_on === 'true'; - if ('personal_rating' in body) body.personal_rating = Number(body.personal_rating); - try { +try { const product = await updateProduct(params.id, body); return { success: true, product }; } catch (e) { diff --git a/frontend/src/routes/products/[id]/+page.svelte b/frontend/src/routes/products/[id]/+page.svelte index 9954d97..646f0b9 100644 --- a/frontend/src/routes/products/[id]/+page.svelte +++ b/frontend/src/routes/products/[id]/+page.svelte @@ -48,9 +48,7 @@ — {/if} - Rating - {product.personal_rating != null ? `${product.personal_rating}/10` : '—'} - + {#if product.targets.length}
Targets: @@ -80,17 +78,6 @@ Quick edit
-
- - -
Personal notes
-
- - -
-