Refactor OpenAPI fetcher for improved clarity and robustness

Reorganized and enhanced the OpenAPI fetch logic for better maintainability and error handling. Key updates include improved environment variable validation, more detailed error messages, streamlined configuration loading, and additional safety checks for file paths and directories. Added proper logging and ensured the process flow is easy to trace.
This commit is contained in:
GotthardG 2024-12-18 14:22:37 +01:00
parent ea15dbb555
commit ef981d1e45
4 changed files with 96 additions and 36 deletions

View File

@ -1,7 +1,13 @@
from fastapi import APIRouter, HTTPException, Depends from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List from typing import List
from app.schemas import Puck as PuckSchema, Sample as SampleSchema from datetime import datetime
from app.schemas import (
Puck as PuckSchema,
Sample as SampleSchema,
SampleEventResponse,
SampleEventCreate,
)
from app.models import ( from app.models import (
Puck as PuckModel, Puck as PuckModel,
Sample as SampleModel, Sample as SampleModel,
@ -45,3 +51,50 @@ async def get_all_pucks_with_samples_and_events(db: Session = Depends(get_db)):
status_code=404, detail="No pucks found in the database" status_code=404, detail="No pucks found in the database"
) # More descriptive ) # More descriptive
return pucks return pucks
# Route to post a new sample event
@router.post("/samples/{sample_id}/events", response_model=SampleEventResponse)
async def create_sample_event(
sample_id: int, event: SampleEventCreate, db: Session = Depends(get_db)
):
# Ensure the sample exists
sample = db.query(SampleModel).filter(SampleModel.id == sample_id).first()
if not sample:
raise HTTPException(status_code=404, detail="Sample not found")
# Create the event
sample_event = SampleEventModel(
sample_id=sample_id,
event_type=event.event_type,
timestamp=datetime.now(), # Use the current timestamp
)
db.add(sample_event)
db.commit()
db.refresh(sample_event)
return (
sample_event # Response will automatically use the SampleEventResponse schema
)
# Route to fetch the last (most recent) sample event
@router.get("/samples/{sample_id}/events/last", response_model=SampleEventResponse)
async def get_last_sample_event(sample_id: int, db: Session = Depends(get_db)):
# Ensure the sample exists
sample = db.query(SampleModel).filter(SampleModel.id == sample_id).first()
if not sample:
raise HTTPException(status_code=404, detail="Sample not found")
# Get the most recent event for the sample
last_event = (
db.query(SampleEventModel)
.filter(SampleEventModel.sample_id == sample_id)
.order_by(SampleEventModel.timestamp.desc())
.first()
)
if not last_event:
raise HTTPException(status_code=404, detail="No events found for the sample")
return last_event # Response will automatically use the SampleEventResponse schema

View File

@ -83,6 +83,16 @@ class SampleEventCreate(BaseModel):
event_type: str event_type: str
class SampleEventResponse(BaseModel):
id: int
sample_id: int
event_type: str
timestamp: datetime
class Config:
from_attributes = True
class Results(BaseModel): class Results(BaseModel):
# Define attributes for Results here # Define attributes for Results here
pass pass

View File

@ -36,6 +36,28 @@ def get_project_metadata():
) )
def run_server():
import uvicorn
print(f"[INFO] Starting server in {environment} environment...")
print(f"[INFO] SSL Certificate Path: {cert_path}")
print(f"[INFO] SSL Key Path: {key_path}")
port = config.get("PORT", os.getenv("PORT"))
if not port:
print("[ERROR] No port defined in config or environment variables. Aborting!")
sys.exit(1) # Exit if no port is defined
port = int(port)
print(f"[INFO] Running on port {port}")
uvicorn.run(
app,
host="127.0.0.1" if environment in ["dev", "test"] else "0.0.0.0",
port=port,
log_level="debug",
ssl_keyfile=key_path,
ssl_certfile=cert_path,
)
# Get project metadata from pyproject.toml # Get project metadata from pyproject.toml
project_name, project_version = get_project_metadata() project_name, project_version = get_project_metadata()
app = FastAPI( app = FastAPI(
@ -145,7 +167,6 @@ app.include_router(sample.router, prefix="/samples", tags=["samples"])
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys
import uvicorn
from dotenv import load_dotenv from dotenv import load_dotenv
from multiprocessing import Process from multiprocessing import Process
from time import sleep from time import sleep
@ -169,52 +190,28 @@ if __name__ == "__main__":
print("openapi.json generated successfully.") print("openapi.json generated successfully.")
sys.exit(0) # Exit after generating the file sys.exit(0) # Exit after generating the file
# Default behavior: Run the server # Default behavior: Run the server based on the environment
environment = os.getenv("ENVIRONMENT", "dev") environment = os.getenv("ENVIRONMENT", "dev")
port = int(os.getenv("PORT", 8000)) port = int(os.getenv("PORT", 8000))
is_ci = os.getenv("CI", "false").lower() == "true" is_ci = os.getenv("CI", "false").lower() == "true"
def run_server(): if is_ci or environment == "test":
print(f"[INFO] Starting server in {environment} environment...") # Test or CI Mode: Run server process temporarily for test validation
print(f"[INFO] SSL Certificate Path: {cert_path}") ssl_dir = Path(cert_path).parent
print(f"[INFO] SSL Key Path: {key_path}") ssl_dir.mkdir(parents=True, exist_ok=True)
port = config.get("PORT", os.getenv("PORT"))
if not port:
print(
"[ERROR] No port defined in config or environment variables. Aborting!"
)
sys.exit(1) # Exit if no port is defined
port = int(port)
print(f"[INFO] Running on port {port}")
uvicorn.run(
app,
host="127.0.0.1" if environment in ["dev", "test"] else "0.0.0.0",
port=port,
log_level="debug",
ssl_keyfile=key_path,
ssl_certfile=cert_path,
)
# Run in CI mode
# Generate or use SSL Key and Certificate
if environment in ["test", "dev", "ci"]:
ssl_dir = Path(
cert_path
).parent # Ensure we work with the parent directory of the cert path
ssl_dir.mkdir(
parents=True, exist_ok=True
) # Create the directory structure if it doesn't exist
# Generate self-signed certs if missing # Generate self-signed certs if missing
if not Path(cert_path).exists() or not Path(key_path).exists(): if not Path(cert_path).exists() or not Path(key_path).exists():
print(f"[INFO] Generating self-signed SSL certificates at {ssl_dir}") print(f"[INFO] Generating self-signed SSL certificates at {ssl_dir}")
ssl_heidi.generate_self_signed_cert(cert_path, key_path) ssl_heidi.generate_self_signed_cert(cert_path, key_path)
# Start the server as a subprocess, wait, then terminate
server_process = Process(target=run_server) server_process = Process(target=run_server)
server_process.start() server_process.start()
sleep(5) # Wait 5 seconds to ensure the server starts without errors sleep(5) # Wait for 5 seconds to verify the server is running
server_process.terminate() # Terminate the server (test purposes) server_process.terminate() # Terminate the server process (for CI)
server_process.join() # Ensure proper cleanup server_process.join() # Ensure proper cleanup
print("CI: Server started and terminated successfully for test validation.") print("CI: Server started and terminated successfully for test validation.")
else: else:
# Dev or Prod: Start the server as usual
run_server() run_server()

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "aareDB" name = "aareDB"
version = "0.1.0a5" version = "0.1.0a6"
description = "Backend for next gen sample management system" description = "Backend for next gen sample management system"
authors = [{name = "Guillaume Gotthard", email = "guillaume.gotthard@psi.ch"}] authors = [{name = "Guillaume Gotthard", email = "guillaume.gotthard@psi.ch"}]
license = {text = "MIT"} license = {text = "MIT"}