A small FastAPI service for managing flight records: create, read, update, delete, and list with filtering, sorting and pagination. Every response, including errors, uses the same JSON envelope. Built as a hiring take-home; kept deliberately small.
Stack: Python 3.9+, FastAPI, SQLAlchemy 2, Pydantic 2, SQLite (any SQLAlchemy URL works), pytest.
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reloadInteractive docs: http://localhost:8000/docs
The database defaults to ./flights.db and is created on first start. Point DATABASE_URL at anything SQLAlchemy understands to use another database:
DATABASE_URL=mysql+pymysql://user:pass@localhost/flights uvicorn app.main:app| Method | Path | Purpose |
|---|---|---|
| POST | /flights/ |
Create a flight |
| GET | /flights/ |
List flights (filter, sort, paginate) |
| GET | /flights/{id} |
Get one flight |
| PUT | /flights/{id} |
Partial update; only the fields sent change |
| DELETE | /flights/{id} |
Delete a flight |
List parameters: origin, destination, status, process_id (filters), sort_by (one of flight_number, departure_time, arrival_time, seats_available, created_at, updated_at), sort_order (asc/desc), page, page_size (max 100).
{ "status": "success", "message": "Flight created", "data": { "flight_id": 1, "...": "..." } }Errors use the same shape with "status": "error". Validation errors carry the field list in data:
{ "status": "error", "message": "Validation error", "data": [ { "loc": ["body", "arrival_time"], "msg": "..." } ] }| Code | When |
|---|---|
| 404 | Flight id does not exist |
| 409 | flight_number already exists |
| 422 | Invalid input, unknown sort field, or an update that would leave the record invalid |
arrival_timemust be afterdeparture_time;duration_minutesis derived from the two.seats_availablecannot exceedseats_total; it defaults toseats_totalon create.- Updates are validated against the merged record, so a partial update cannot bypass a rule that creation enforces.
- Timezone-aware datetimes are converted to UTC and stored naive.
curl -X POST http://localhost:8000/flights/ -H "Content-Type: application/json" -d '{
"flight_number": "IR700", "origin": "IKA", "destination": "MHD",
"departure_time": "2025-11-10T10:00:00", "arrival_time": "2025-11-10T11:30:00",
"aircraft_type": "A320", "seats_total": 150
}'
curl "http://localhost:8000/flights/?origin=IKA&sort_by=departure_time&sort_order=desc&page=1&page_size=5"
curl -X PUT http://localhost:8000/flights/1 -H "Content-Type: application/json" -d '{"status": "delayed"}'app/
main.py app factory, error handlers (envelope for all errors)
db.py engine, session, DATABASE_URL
models.py SQLAlchemy model and FlightStatus enum
schemas.py Pydantic request/response models and validation rules
routers/ HTTP layer: paths, query parameters, response envelope
services/ business rules: 404/409 mapping, merged-record validation on update
repositories/ database access only
tests/
test_flights.py one test per endpoint against an in-memory SQLite database
pytestEach test gets a fresh in-memory database, so the suite is repeatable and never touches flights.db. Warnings are treated as errors, so a deprecated API call fails the run.
MIT