Implemented find queries

This commit is contained in:
Donkie
2023-04-05 21:29:39 +02:00
parent 6a58caa2ec
commit b51d0a3183
6 changed files with 199 additions and 25 deletions

View File

@@ -1,8 +1,8 @@
"""Filament related endpoints.""" """Filament related endpoints."""
from typing import Annotated, Optional, Union from typing import Annotated, Optional
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends, Query
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -19,7 +19,7 @@ router = APIRouter(
tags=["filament"], tags=["filament"],
) )
# ruff: noqa: D103 # ruff: noqa: D103, B008
class FilamentParameters(BaseModel): class FilamentParameters(BaseModel):
@@ -74,12 +74,43 @@ class FilamentUpdateParameters(FilamentParameters):
response_model_exclude_none=True, response_model_exclude_none=True,
) )
async def find( async def find(
vendor_id: Optional[int] = None, *,
name: Optional[str] = None, db: Annotated[AsyncSession, Depends(get_db_session)],
material: Optional[str] = None, vendor_name: Optional[str] = Query(
article_number: Optional[str] = None, default=None,
title="Vendor Name",
description="Partial case-insensitive search term for the filament vendor name.",
),
vendor_id: Optional[int] = Query(
default=None,
title="Vendor ID",
description="Match an exact vendor ID.",
),
name: Optional[str] = Query(
default=None,
title="Filament Name",
description="Partial case-insensitive search term for the filament name.",
),
material: Optional[str] = Query(
default=None,
title="Filament Material",
description="Partial case-insensitive search term for the filament material.",
),
article_number: Optional[str] = Query(
default=None,
title="Filament Article Number",
description="Partial case-insensitive search term for the filament article number.",
),
) -> list[Filament]: ) -> list[Filament]:
return [] # TODO: Implement db_items = await filament.find(
db=db,
vendor_name=vendor_name,
vendor_id=vendor_id,
name=name,
material=material,
article_number=article_number,
)
return [Filament.from_db(db_item) for db_item in db_items]
@router.get( @router.get(

View File

@@ -1,9 +1,9 @@
"""Spool related endpoints.""" """Spool related endpoints."""
from datetime import datetime from datetime import datetime
from typing import Annotated, Optional, Union from typing import Annotated, Optional
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends, Query
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -20,7 +20,7 @@ router = APIRouter(
tags=["spool"], tags=["spool"],
) )
# ruff: noqa: D103 # ruff: noqa: D103,B008
class SpoolParameters(BaseModel): class SpoolParameters(BaseModel):
@@ -64,14 +64,55 @@ class SpoolUseParameters(BaseModel):
response_model_exclude_none=True, response_model_exclude_none=True,
) )
async def find( async def find(
filament_id: Optional[int] = None, *,
name: Optional[str] = None, db: Annotated[AsyncSession, Depends(get_db_session)],
material: Optional[str] = None, filament_name: Optional[int] = Query(
vendor: Optional[str] = None, default=None,
location: Optional[str] = None, title="Filament Name",
lot_nr: Optional[str] = None, description="Partial case-insensitive search term for the filament name.",
),
filament_id: Optional[int] = Query(
default=None,
title="Filament ID",
description="Match an exact filament ID.",
),
filament_material: Optional[str] = Query(
default=None,
title="Filament Material",
description="Partial case-insensitive search term for the filament material.",
),
vendor_name: Optional[str] = Query(
default=None,
title="Vendor Name",
description="Partial case-insensitive search term for the filament vendor name.",
),
vendor_id: Optional[int] = Query(
default=None,
title="Vendor ID",
description="Match an exact vendor ID.",
),
location: Optional[str] = Query(
default=None,
title="Location",
description="Partial case-insensitive search term for the spool location.",
),
lot_nr: Optional[str] = Query(
default=None,
title="Lot/Batch Number",
description="Partial case-insensitive search term for the spool lot number.",
),
) -> list[Spool]: ) -> list[Spool]:
return [] # TODO: Implement db_items = await spool.find(
db=db,
filament_name=filament_name,
filament_id=filament_id,
filament_material=filament_material,
vendor_name=vendor_name,
vendor_id=vendor_id,
location=location,
lot_nr=lot_nr,
)
return [Spool.from_db(db_item) for db_item in db_items]
@router.get( @router.get(

View File

@@ -1,8 +1,8 @@
"""Vendor related endpoints.""" """Vendor related endpoints."""
from typing import Annotated, Optional, Union from typing import Annotated, Optional
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends, Query
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from pydantic.error_wrappers import ErrorWrapper from pydantic.error_wrappers import ErrorWrapper
@@ -17,7 +17,7 @@ router = APIRouter(
tags=["vendor"], tags=["vendor"],
) )
# ruff: noqa: D103 # ruff: noqa: D103,B008
class VendorParameters(BaseModel): class VendorParameters(BaseModel):
@@ -44,8 +44,19 @@ class VendorUpdateParameters(VendorParameters):
description="Get a list of vendors that matches the search query.", description="Get a list of vendors that matches the search query.",
response_model_exclude_none=True, response_model_exclude_none=True,
) )
async def find(name: Optional[str] = None) -> list[Vendor]: async def find(
return [] # TODO: Implement db: Annotated[AsyncSession, Depends(get_db_session)],
name: Optional[str] = Query(
default=None,
title="Vendor Name",
description="Partial case-insensitive search term for the vendor name.",
),
) -> list[Vendor]:
db_items = await vendor.find(
db=db,
name=name,
)
return [Vendor.from_db(db_item) for db_item in db_items]
@router.get( @router.get(

View File

@@ -2,8 +2,10 @@
from typing import Optional from typing import Optional
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import contains_eager, joinedload
from spoolman.database import models, vendor from spoolman.database import models, vendor
from spoolman.exceptions import ItemDeleteError, ItemNotFoundError from spoolman.exceptions import ItemDeleteError, ItemNotFoundError
@@ -47,12 +49,42 @@ async def create(
async def get_by_id(db: AsyncSession, filament_id: int) -> models.Filament: async def get_by_id(db: AsyncSession, filament_id: int) -> models.Filament:
"""Get a filament object from the database by the unique ID.""" """Get a filament object from the database by the unique ID."""
filament = await db.get(models.Filament, filament_id) filament = await db.get(
models.Filament,
filament_id,
options=[joinedload("*")], # Load all nested objects as well
)
if filament is None: if filament is None:
raise ItemNotFoundError(f"No filament with ID {filament_id} found.") raise ItemNotFoundError(f"No filament with ID {filament_id} found.")
return filament return filament
async def find(
*,
db: AsyncSession,
vendor_name: Optional[str] = None,
vendor_id: Optional[int] = None,
name: Optional[str] = None,
material: Optional[str] = None,
article_number: Optional[str] = None,
) -> list[models.Filament]:
"""Find a list of filament objects by search criteria."""
stmt = select(models.Filament).options(contains_eager(models.Filament.vendor)).join(models.Filament.vendor)
if vendor_name is not None:
stmt = stmt.where(models.Vendor.name.ilike(f"%{vendor_name}%"))
if vendor_id is not None:
stmt = stmt.where(models.Filament.vendor_id == vendor_id)
if name is not None:
stmt = stmt.where(models.Filament.name.ilike(f"%{name}%"))
if material is not None:
stmt = stmt.where(models.Filament.material.ilike(f"%{material}%"))
if article_number is not None:
stmt = stmt.where(models.Filament.article_number.ilike(f"%{article_number}%"))
rows = await db.execute(stmt)
return list(rows.scalars().all())
async def update( async def update(
*, *,
db: AsyncSession, db: AsyncSession,

View File

@@ -3,7 +3,9 @@
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import contains_eager, joinedload
from spoolman.database import filament, models from spoolman.database import filament, models
from spoolman.exceptions import ItemCreateError, ItemNotFoundError from spoolman.exceptions import ItemCreateError, ItemNotFoundError
@@ -44,12 +46,54 @@ async def create(
async def get_by_id(db: AsyncSession, spool_id: int, with_for_update: Optional[bool] = None) -> models.Spool: async def get_by_id(db: AsyncSession, spool_id: int, with_for_update: Optional[bool] = None) -> models.Spool:
"""Get a spool object from the database by the unique ID.""" """Get a spool object from the database by the unique ID."""
spool = await db.get(models.Spool, spool_id, with_for_update=with_for_update) # type: ignore # noqa: PGH003 spool = await db.get(
models.Spool,
spool_id,
with_for_update=with_for_update, # type: ignore # noqa: PGH003
options=[joinedload("*")], # Load all nested objects as well
)
if spool is None: if spool is None:
raise ItemNotFoundError(f"No spool with ID {spool_id} found.") raise ItemNotFoundError(f"No spool with ID {spool_id} found.")
return spool return spool
async def find(
*,
db: AsyncSession,
filament_name: Optional[int] = None,
filament_id: Optional[int] = None,
filament_material: Optional[str] = None,
vendor_name: Optional[str] = None,
vendor_id: Optional[int] = None,
location: Optional[str] = None,
lot_nr: Optional[str] = None,
) -> list[models.Spool]:
"""Find a list of spool objects by search criteria."""
stmt = (
select(models.Spool)
.join(models.Spool.filament)
.join(models.Filament.vendor)
.options(contains_eager(models.Spool.filament).contains_eager(models.Filament.vendor))
)
if filament_name is not None:
stmt = stmt.where(models.Filament.name.ilike(f"%{filament_name}%"))
if filament_id is not None:
stmt = stmt.where(models.Spool.filament_id == filament_id)
if filament_material is not None:
stmt = stmt.where(models.Filament.material.ilike(f"%{filament_material}%"))
if vendor_name is not None:
stmt = stmt.where(models.Vendor.name.ilike(f"%{vendor_name}%"))
if vendor_id is not None:
stmt = stmt.where(models.Filament.vendor_id == vendor_id)
if location is not None:
stmt = stmt.where(models.Spool.location.ilike(f"%{location}%"))
if lot_nr is not None:
stmt = stmt.where(models.Spool.lot_nr.ilike(f"%{lot_nr}%"))
rows = await db.execute(stmt)
return list(rows.scalars().all())
async def update( async def update(
*, *,
db: AsyncSession, db: AsyncSession,

View File

@@ -2,6 +2,7 @@
from typing import Optional from typing import Optional
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from spoolman.database import models from spoolman.database import models
@@ -32,6 +33,20 @@ async def get_by_id(db: AsyncSession, vendor_id: int) -> models.Vendor:
return vendor return vendor
async def find(
*,
db: AsyncSession,
name: Optional[str] = None,
) -> list[models.Vendor]:
"""Find a list of vendor objects by search criteria."""
stmt = select(models.Vendor)
if name is not None:
stmt = stmt.where(models.Vendor.name.ilike(f"%{name}%"))
rows = await db.execute(stmt)
return list(rows.scalars().all())
async def update( async def update(
*, *,
db: AsyncSession, db: AsyncSession,