Make shipment fields optional and refactor test scripts.

Updated the `number_of_pucks` and `number_of_samples` fields in the `schemas.py` to be optional for greater flexibility. Simplified the test Jupyter Notebook by restructuring imports and consolidating function calls for better readability and maintainability.
This commit is contained in:
GotthardG
2025-01-17 09:36:16 +01:00
parent 481068603b
commit 9739b8cfe9
6 changed files with 485 additions and 309 deletions

View File

@ -8,7 +8,6 @@ from sqlalchemy.orm import Session, joinedload
from typing import List
import logging
from sqlalchemy.exc import SQLAlchemyError
from pydantic import ValidationError, BaseModel
from app.schemas import (
Dewar as DewarSchema,
DewarCreate,
@ -65,11 +64,114 @@ def generate_unique_id(db: Session, length: int = 16) -> str:
@router.post("/", response_model=DewarSchema, status_code=status.HTTP_201_CREATED)
async def create_dewar(
dewar: DewarCreate, db: Session = Depends(get_db)
async def create_or_update_dewar(
shipment_id: int,
dewar: DewarCreate,
db: Session = Depends(get_db),
) -> DewarSchema:
try:
db_dewar = DewarModel(
# Query existing dewar by name within the shipment
existing_dewar = (
db.query(DewarModel)
.filter(
DewarModel.dewar_name == dewar.dewar_name,
DewarModel.shipment_id == shipment_id,
)
.first()
)
if existing_dewar:
logging.debug(
f"Updating existing dewar with name "
f"{dewar.dewar_name} in shipment {shipment_id}"
)
# Check for associated puck events
for puck in existing_dewar.pucks:
puck_event_exists = (
db.query(PuckEvent).filter(PuckEvent.puck_id == puck.id).first()
)
if puck_event_exists:
raise HTTPException(
status_code=400,
detail=f"Puck {puck.id} "
f"associated with Dewar {existing_dewar.id}"
f" has events. "
f"Update not allowed.",
)
# Check for associated sample events within each puck
for sample in puck.samples:
sample_event_exists = (
db.query(SampleEvent)
.filter(SampleEvent.sample_id == sample.id)
.first()
)
if sample_event_exists:
raise HTTPException(
status_code=400,
detail=f"Sample {sample.id} "
f"associated with Puck "
f"{puck.id} in Dewar "
f"{existing_dewar.id} "
f"has events. Update not allowed.",
)
# Delete associated pucks and samples if no events are found
for puck in existing_dewar.pucks:
for sample in puck.samples:
db.delete(sample)
db.delete(puck)
db.commit()
# Update dewar metadata
for key, value in dewar.dict(
exclude={"pucks", "number_of_pucks", "number_of_samples"}
).items():
if (
hasattr(existing_dewar, key)
and hasattr(type(existing_dewar), key)
and isinstance(getattr(type(existing_dewar), key), property)
and getattr(type(existing_dewar), key).fset
):
setattr(existing_dewar, key, value)
# Commit updates to existing dewar
db.commit()
# Add new pucks and samples
for puck_data in dewar.pucks:
puck = PuckModel(
dewar_id=existing_dewar.id,
puck_name=puck_data.puck_name,
puck_type=puck_data.puck_type,
puck_location_in_dewar=puck_data.puck_location_in_dewar,
)
db.add(puck)
db.commit()
db.refresh(puck)
for sample_data in puck_data.samples:
sample = SampleModel(
puck_id=puck.id,
sample_name=sample_data.sample_name,
proteinname=sample_data.proteinname,
position=sample_data.position,
priority=sample_data.priority,
comments=sample_data.comments,
data_collection_parameters=dict(
sample_data.data_collection_parameters or {}
),
)
db.add(sample)
db.commit()
db.refresh(sample)
db.refresh(existing_dewar)
return existing_dewar
# Create a completely new dewar if none exists
dewar_obj = DewarModel(
dewar_name=dewar.dewar_name,
tracking_number=dewar.tracking_number,
status=dewar.status,
@ -79,14 +181,16 @@ async def create_dewar(
returning_date=dewar.returning_date,
contact_person_id=dewar.contact_person_id,
return_address_id=dewar.return_address_id,
shipment_id=shipment_id, # Associate with the shipment
)
db.add(db_dewar)
db.add(dewar_obj)
db.commit()
db.refresh(db_dewar)
db.refresh(dewar_obj)
# Add pucks and samples for the new dewar
for puck_data in dewar.pucks:
puck = PuckModel(
dewar_id=db_dewar.id,
dewar_id=dewar_obj.id,
puck_name=puck_data.puck_name,
puck_type=puck_data.puck_type,
puck_location_in_dewar=puck_data.puck_location_in_dewar,
@ -96,26 +200,6 @@ async def create_dewar(
db.refresh(puck)
for sample_data in puck_data.samples:
logging.debug(
f"data_collection_parameters: "
f"{sample_data.data_collection_parameters}"
)
if sample_data.data_collection_parameters is None:
serialized_params = {}
elif hasattr(sample_data.data_collection_parameters, "to_dict"):
serialized_params = sample_data.data_collection_parameters.to_dict()
elif isinstance(sample_data.data_collection_parameters, BaseModel):
serialized_params = sample_data.data_collection_parameters.dict(
exclude_unset=True
)
elif isinstance(sample_data.data_collection_parameters, dict):
serialized_params = sample_data.data_collection_parameters
else:
raise ValueError(
"data_collection_parameters must be a dictionary,"
"have a to_dict method, or be None"
)
sample = SampleModel(
puck_id=puck.id,
sample_name=sample_data.sample_name,
@ -123,20 +207,19 @@ async def create_dewar(
position=sample_data.position,
priority=sample_data.priority,
comments=sample_data.comments,
data_collection_parameters=serialized_params,
data_collection_parameters=dict(
sample_data.data_collection_parameters or {}
),
)
db.add(sample)
db.commit()
db.refresh(sample)
return db_dewar
return dewar_obj
except SQLAlchemyError as e:
logging.error(f"Database error occurred: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
except ValidationError as e:
logging.error(f"Validation error occurred: {e}")
raise HTTPException(status_code=400, detail="Validation error")
@router.post("/{dewar_id}/generate-qrcode")

View File

@ -2,9 +2,7 @@ from fastapi import APIRouter, HTTPException, status, Query, Depends
from sqlalchemy.orm import Session
from typing import List, Optional
import logging
from pydantic import ValidationError
from datetime import date
from sqlalchemy.exc import SQLAlchemyError
import json
from app.models import (
@ -13,8 +11,6 @@ from app.models import (
Address as AddressModel,
Proposal as ProposalModel,
Dewar as DewarModel,
Puck as PuckModel,
Sample as SampleModel,
LogisticsEvent,
SampleEvent,
PuckEvent,
@ -25,7 +21,6 @@ from app.schemas import (
Shipment as ShipmentSchema,
ContactPerson as ContactPersonSchema,
Sample as SampleSchema,
DewarCreate,
DewarSchema,
)
from app.database import get_db
@ -334,8 +329,13 @@ async def remove_dewar_from_shipment(
f"has associated events. Removal not allowed.",
)
# Unlink the dewar from the shipment
shipment.dewars = [dw for dw in shipment.dewars if dw.id != dewar_id]
# Perform cascade deletion: Delete samples, pucks, and the dewar
for puck in dewar.pucks:
for sample in puck.samples:
db.delete(sample) # Delete associated samples
db.delete(puck) # Delete associated puck
db.delete(dewar) # Finally, delete the dewar itself
db.commit()
db.refresh(shipment)
@ -399,68 +399,3 @@ async def update_shipment_comments(
db.commit()
db.refresh(shipment)
return shipment
@router.post(
"/{shipment_id}/add_dewar_puck_sample",
response_model=ShipmentSchema,
status_code=status.HTTP_201_CREATED,
)
def add_dewar_puck_sample_to_shipment(
shipment_id: int, payload: DewarCreate, db: Session = Depends(get_db)
):
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
if not shipment:
raise HTTPException(status_code=404, detail="Shipment not found")
try:
for dewar_data in payload.dewars:
dewar = (
db.query(DewarModel)
.filter(DewarModel.dewar_name == dewar_data.dewar_name)
.first()
)
if dewar:
# Update existing dewar
dewar.tracking_number = dewar_data.tracking_number
dewar.status = dewar_data.status
db.commit()
else:
dewar = DewarModel(
shipment_id=shipment_id,
dewar_name=dewar_data.dewar_name,
tracking_number=dewar_data.tracking_number,
status=dewar_data.status,
)
db.add(dewar)
db.commit()
db.refresh(dewar)
for puck_data in dewar_data.pucks:
puck = PuckModel(
dewar_id=dewar.id,
puck_name=puck_data.puck_name,
puck_type=puck_data.puck_type,
puck_location_in_dewar=puck_data.puck_location_in_dewar,
)
db.add(puck)
db.commit()
db.refresh(puck)
for sample_data in puck_data.samples:
sample = SampleModel(
puck_id=puck.id,
sample_name=sample_data.sample_name,
position=sample_data.position,
)
db.add(sample)
db.commit()
db.refresh(sample)
db.refresh(shipment)
except SQLAlchemyError as e:
raise HTTPException(status_code=500, detail=f"Database error: {e}")
except ValidationError as e:
raise HTTPException(status_code=400, detail=f"Validation error: {e}")
return shipment

View File

@ -493,8 +493,8 @@ class DewarBase(BaseModel):
dewar_serial_number_id: Optional[int] = None
unique_id: Optional[str] = None
tracking_number: str
number_of_pucks: int
number_of_samples: int
number_of_pucks: Optional[int] = None
number_of_samples: Optional[int] = None
status: str
ready_date: Optional[date]
shipping_date: Optional[date]