384 lines
9.9 KiB
Python
384 lines
9.9 KiB
Python
"""
|
|
Veterinary Clinic route handlers bound via OpenAPI operationId.
|
|
|
|
Handlers explicitly control HTTP response status codes to ensure runtime
|
|
behavior matches the OpenAPI contract. Domain models defined using
|
|
Pydantic are used for request and response payloads.
|
|
|
|
No routing decorators, path definitions, or implicit framework behavior
|
|
appear in this module. All routing, HTTP methods, and schemas are defined
|
|
in the OpenAPI specification.
|
|
"""
|
|
|
|
from fastapi import Response, HTTPException, UploadFile
|
|
from fastapi.responses import StreamingResponse
|
|
|
|
from sse import subscribe, unsubscribe
|
|
|
|
from models import (
|
|
ParentCreate,
|
|
VetCreate,
|
|
TreatmentCreate,
|
|
PetCreate,
|
|
AppointmentCreate,
|
|
)
|
|
from data import (
|
|
list_parents as _list_parents,
|
|
get_parent as _get_parent,
|
|
create_parent as _create_parent,
|
|
update_parent as _update_parent,
|
|
delete_parent as _delete_parent,
|
|
list_vets as _list_vets,
|
|
get_vet as _get_vet,
|
|
create_vet as _create_vet,
|
|
update_vet as _update_vet,
|
|
delete_vet as _delete_vet,
|
|
list_treatments as _list_treatments,
|
|
get_treatment as _get_treatment,
|
|
create_treatment as _create_treatment,
|
|
update_treatment as _update_treatment,
|
|
delete_treatment as _delete_treatment,
|
|
list_pets as _list_pets,
|
|
get_pet as _get_pet,
|
|
create_pet as _create_pet,
|
|
update_pet as _update_pet,
|
|
delete_pet as _delete_pet,
|
|
list_appointments as _list_appointments,
|
|
get_appointment as _get_appointment,
|
|
create_appointment as _create_appointment,
|
|
update_appointment as _update_appointment,
|
|
delete_appointment as _delete_appointment,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Parents
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def list_parents(limit: int = 20, offset: int = 0):
|
|
"""List parents (paginated).
|
|
|
|
Parameters
|
|
----------
|
|
limit : int
|
|
Maximum number of records to return.
|
|
offset : int
|
|
Number of records to skip.
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
Paginated response with ``total`` and ``items``.
|
|
"""
|
|
items = _list_parents()
|
|
return {"total": len(items), "items": items[offset:offset + limit] if limit else items[offset:]}
|
|
|
|
|
|
def create_parent(payload: ParentCreate, response: Response):
|
|
"""Create a parent.
|
|
|
|
Parameters
|
|
----------
|
|
payload : ParentCreate
|
|
Parent data excluding the ``id`` field.
|
|
|
|
Returns
|
|
-------
|
|
Parent
|
|
The newly created parent.
|
|
"""
|
|
parent = _create_parent(payload)
|
|
response.status_code = 201
|
|
return parent
|
|
|
|
|
|
def get_parent(id: int):
|
|
"""Retrieve a single parent by ID.
|
|
|
|
Parameters
|
|
----------
|
|
id : int
|
|
Identifier of the parent.
|
|
|
|
Returns
|
|
-------
|
|
Parent
|
|
The requested parent.
|
|
|
|
Raises
|
|
------
|
|
HTTPException
|
|
404 if the parent does not exist.
|
|
"""
|
|
try:
|
|
return _get_parent(id)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Parent not found")
|
|
|
|
|
|
def update_parent(id: int, payload: ParentCreate):
|
|
"""Update an existing parent.
|
|
|
|
Parameters
|
|
----------
|
|
id : int
|
|
Identifier of the parent.
|
|
payload : ParentCreate
|
|
Updated parent data.
|
|
|
|
Returns
|
|
-------
|
|
Parent
|
|
The updated parent.
|
|
|
|
Raises
|
|
------
|
|
HTTPException
|
|
404 if the parent does not exist.
|
|
"""
|
|
try:
|
|
return _update_parent(id, payload)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Parent not found")
|
|
|
|
|
|
def delete_parent(id: int, response: Response):
|
|
"""Delete an existing parent.
|
|
|
|
Parameters
|
|
----------
|
|
id : int
|
|
Identifier of the parent.
|
|
|
|
Raises
|
|
------
|
|
HTTPException
|
|
404 if the parent does not exist.
|
|
"""
|
|
try:
|
|
_delete_parent(id)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Parent not found")
|
|
response.status_code = 204
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Vets
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def list_vets(limit: int = 20, offset: int = 0):
|
|
"""List vets (paginated)."""
|
|
items = _list_vets()
|
|
return {"total": len(items), "items": items[offset:offset + limit] if limit else items[offset:]}
|
|
|
|
|
|
def create_vet(payload: VetCreate, response: Response):
|
|
"""Create a vet."""
|
|
vet = _create_vet(payload)
|
|
response.status_code = 201
|
|
return vet
|
|
|
|
|
|
def get_vet(id: int):
|
|
"""Retrieve a single vet by ID."""
|
|
try:
|
|
return _get_vet(id)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Vet not found")
|
|
|
|
|
|
def update_vet(id: int, payload: VetCreate):
|
|
"""Update an existing vet."""
|
|
try:
|
|
return _update_vet(id, payload)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Vet not found")
|
|
|
|
|
|
def delete_vet(id: int, response: Response):
|
|
"""Delete an existing vet."""
|
|
try:
|
|
_delete_vet(id)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Vet not found")
|
|
response.status_code = 204
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Treatments
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def list_treatments():
|
|
"""List treatments (catalogue).
|
|
|
|
Returns
|
|
-------
|
|
list[Treatment]
|
|
A list of treatment domain objects.
|
|
"""
|
|
return _list_treatments()
|
|
|
|
|
|
def create_treatment(payload: TreatmentCreate, response: Response):
|
|
"""Add a treatment (admin only)."""
|
|
treatment = _create_treatment(payload)
|
|
response.status_code = 201
|
|
return treatment
|
|
|
|
|
|
def get_treatment(id: int):
|
|
"""Retrieve a single treatment by ID."""
|
|
try:
|
|
return _get_treatment(id)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Treatment not found")
|
|
|
|
|
|
def update_treatment(id: int, payload: TreatmentCreate):
|
|
"""Update an existing treatment."""
|
|
try:
|
|
return _update_treatment(id, payload)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Treatment not found")
|
|
|
|
|
|
def delete_treatment(id: int, response: Response):
|
|
"""Delete an existing treatment."""
|
|
try:
|
|
_delete_treatment(id)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Treatment not found")
|
|
response.status_code = 204
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pets
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def list_pets(limit: int = 20, offset: int = 0):
|
|
"""List pets (paginated)."""
|
|
items = _list_pets()
|
|
return {"total": len(items), "items": items[offset:offset + limit] if limit else items[offset:]}
|
|
|
|
|
|
def create_pet(payload: PetCreate, response: Response):
|
|
"""Create a pet."""
|
|
pet = _create_pet(payload)
|
|
response.status_code = 201
|
|
return pet
|
|
|
|
|
|
def get_pet(id: int):
|
|
"""Retrieve a single pet by ID."""
|
|
try:
|
|
return _get_pet(id)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Pet not found")
|
|
|
|
|
|
def update_pet(id: int, payload: PetCreate):
|
|
"""Update an existing pet."""
|
|
try:
|
|
return _update_pet(id, payload)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Pet not found")
|
|
|
|
|
|
def delete_pet(id: int, response: Response):
|
|
"""Delete an existing pet."""
|
|
try:
|
|
_delete_pet(id)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Pet not found")
|
|
response.status_code = 204
|
|
|
|
|
|
def upload_pet_photo(id: int, file: UploadFile):
|
|
"""Upload a pet photo.
|
|
|
|
Parameters
|
|
----------
|
|
id : int
|
|
Identifier of the pet.
|
|
file : UploadFile
|
|
Image file to upload.
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
A confirmation with the pet ID.
|
|
"""
|
|
_ = file # In a real app, save to disk / object store
|
|
return {"id": id, "status": "photo_uploaded"}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Appointments
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def list_appointments(limit: int = 20, offset: int = 0, date: str = None, vet: int = None, pet: int = None):
|
|
"""List appointments (paginated, filterable)."""
|
|
items = _list_appointments()
|
|
|
|
# Basic in-memory filtering
|
|
if date:
|
|
items = [a for a in items if a.date.startswith(date)]
|
|
if vet is not None:
|
|
items = [a for a in items if a.vet.id == vet]
|
|
if pet is not None:
|
|
items = [a for a in items if a.pet.id == pet]
|
|
|
|
return {"total": len(items), "items": items[offset:offset + limit] if limit else items[offset:]}
|
|
|
|
|
|
def create_appointment(payload: AppointmentCreate, response: Response):
|
|
"""Create an appointment."""
|
|
appointment = _create_appointment(payload)
|
|
response.status_code = 201
|
|
return appointment
|
|
|
|
|
|
def get_appointment(id: int):
|
|
"""Retrieve a single appointment by ID."""
|
|
try:
|
|
return _get_appointment(id)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Appointment not found")
|
|
|
|
|
|
def update_appointment(id: int, payload: AppointmentCreate):
|
|
"""Update an existing appointment."""
|
|
try:
|
|
return _update_appointment(id, payload)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Appointment not found")
|
|
|
|
|
|
def delete_appointment(id: int, response: Response):
|
|
"""Delete an existing appointment."""
|
|
try:
|
|
_delete_appointment(id)
|
|
except KeyError:
|
|
raise HTTPException(status_code=404, detail="Appointment not found")
|
|
response.status_code = 204
|
|
|
|
|
|
async def stream_calls():
|
|
"""Stream random animal sounds via SSE."""
|
|
q = await subscribe()
|
|
|
|
async def event_generator():
|
|
try:
|
|
while True:
|
|
data = await q.get()
|
|
yield f"data: {data}\n\n"
|
|
finally:
|
|
unsubscribe(q)
|
|
|
|
return StreamingResponse(event_generator(), media_type="text/event-stream")
|