added uvicorn web api
This commit is contained in:
+102
@@ -0,0 +1,102 @@
|
||||
"""REST API over the scraped Fundbureau events.
|
||||
|
||||
Datasource is the JSON file produced by ``scraper/scraper.py``
|
||||
(``fundi-scraped-output.json`` by default; override with the DATA_FILE env var).
|
||||
|
||||
Run with:
|
||||
uvicorn main:app --reload # from inside the api/ folder
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Query
|
||||
|
||||
from datasource import EventStore, iter_artist_names, parse_event_date
|
||||
from models import Event, EventList
|
||||
|
||||
app = FastAPI(
|
||||
title="Fundi Scraper API",
|
||||
description="REST API over the scraped upcoming/past events at the Fundbureau Hamburg.",
|
||||
version="1.0.0",
|
||||
)
|
||||
|
||||
store = EventStore()
|
||||
|
||||
|
||||
def _matches(
|
||||
event: Event,
|
||||
free: Optional[bool],
|
||||
name: Optional[str],
|
||||
artist: Optional[str],
|
||||
event_date: Optional[str],
|
||||
upcoming: Optional[bool],
|
||||
today: datetime.date,
|
||||
) -> bool:
|
||||
if free is not None and event.event_free is not free:
|
||||
return False
|
||||
if name is not None and name.lower() not in event.event_name.lower():
|
||||
return False
|
||||
if artist is not None:
|
||||
needle = artist.lower()
|
||||
if not any(needle in n.lower() for n in iter_artist_names(event)):
|
||||
return False
|
||||
if event_date is not None and event.event_date != event_date:
|
||||
return False
|
||||
if upcoming is not None:
|
||||
parsed = parse_event_date(event.event_date)
|
||||
if parsed is None:
|
||||
return False
|
||||
is_upcoming = parsed >= today
|
||||
if is_upcoming is not upcoming:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok", "events_loaded": len(store.all()), "data_file": str(store.path)}
|
||||
|
||||
|
||||
@app.post("/reload")
|
||||
def reload():
|
||||
"""Re-read the JSON file from disk (e.g. after a fresh scrape)."""
|
||||
try:
|
||||
count = store.reload()
|
||||
except FileNotFoundError as exc:
|
||||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
return {"reloaded": True, "events_loaded": count}
|
||||
|
||||
|
||||
@app.get("/events", response_model=EventList)
|
||||
def list_events(
|
||||
free: Optional[bool] = Query(None, description="Filter by free admission"),
|
||||
name: Optional[str] = Query(None, description="Case-insensitive substring match on event name"),
|
||||
artist: Optional[str] = Query(None, description="Case-insensitive substring match on any artist name"),
|
||||
date: Optional[str] = Query(None, description="Exact match on the raw event date (dd.mm.yy)"),
|
||||
upcoming: Optional[bool] = Query(None, description="true = today or later, false = past events"),
|
||||
):
|
||||
today = datetime.date.today()
|
||||
events = [
|
||||
e
|
||||
for e in store.all()
|
||||
if _matches(e, free, name, artist, date, upcoming, today)
|
||||
]
|
||||
return EventList(count=len(events), events=events)
|
||||
|
||||
|
||||
@app.get("/artists", response_model=List[str])
|
||||
def list_artists():
|
||||
"""Deduplicated, sorted list of all artist names across all events."""
|
||||
names = set()
|
||||
for event in store.all():
|
||||
names.update(iter_artist_names(event))
|
||||
return sorted(names, key=str.lower)
|
||||
|
||||
|
||||
@app.get("/events/{index}", response_model=Event)
|
||||
def get_event(index: int):
|
||||
event = store.get(index)
|
||||
if event is None:
|
||||
raise HTTPException(status_code=404, detail=f"No event at index {index}")
|
||||
return event
|
||||
Reference in New Issue
Block a user