FastAPI Database Seeding: Lessons From a Very Long Morning

Day 78 of 100 Days Coding Challenge: Python

Today was the first time this project nearly made me cry on a weekend. Silver lining: I had the weekend off. I went full detective, fixed each clue, and 2.5 hours later, the case was closed.

It started with my app pretending it had never heard of my “event” and “civilization” seeds. We tried to reason with it; the app replied with more errors. So I made the dramatic choice to roll back to Python 3.11. Did my blood run cold? Absolutely. I had nightmares about dependency dominoes falling over. I’ve been burned before—change Python and some random file screams in a different room.

Did I quit? Not today. I switched to 3.11, watched a few files wobble, and then fixed them, one by one. In the end, every model, seed, and endpoint reported for duty. No tears, just a very caffeinated victory lap.

Today’s Motivation / Challenge

Seeding a timeline of civilizations isn’t just “data entry with a cape.” It’s giving your app a memory. The database goes from goldfish to historian, and your API finally has something interesting to say. It’s like stocking a library before opening day.

Purpose of the Code (Object)

This code loads a starter set of civilizations and events into a database, then exposes simple read-only API endpoints to browse them. You can filter events by year range and civilization (by slug or name) and see each event alongside the civilization it belongs to. In short: seed, serve, search.

AI Prompt: Make it cleaner.

Functions & Features

  • Seed CSV files into the database (idempotent: re-run without duplicates).
  • /events endpoint returns events with the civilization name and slug.
  • Filters: start, end, civ (slug or name, case-insensitive), kind, limit.
  • /civs lists civilizations, with an optional region filter (and an easy name contains search).
  • Friendly API docs at /docs (FastAPI’s Swagger UI).

Requirements / Setup

  • Python 3.11
  • Poetry (recommended) or pip
  • Key packages: sqlmodel, sqlalchemy, pydantic, fastapi, uvicorn

Example with pip:

pip install “sqlmodel>=0.0.21” “sqlalchemy>=2.0,<2.1” “pydantic>=2,<3” fastapi “uvicorn[standard]”

Minimal Code Sample

# Return events with attached civilization info and filters

from sqlalchemy import and_, or_

from sqlalchemy.orm import selectinload

stmt = select(Event).options(selectinload(Event.civilization))

conds = []

if start is not None: conds.append(Event.year >= start)

if end is not None:   conds.append(Event.year <= end)

    stmt = stmt.join(Civilization)

    conds.append(or_(Civilization.slug == civ, Civilization.name.ilike(f”%{civ}%”)))

if conds: stmt = stmt.where(and_(*conds))

events = session.exec(stmt.order_by(Event.year).limit(limit)).all()

One query, optional joins, and eager loading to return civilized results.

The Civilization Timeline Builder

Notes / Lessons Learned

I learned the hard way that your editor and Poetry can quietly disagree about which interpreter you’re using. Always aim them both at the same Poetry venv before chasing ghosts. Rolling back Python isn’t a moral failure; it’s maintenance. Things did break, but fixing them systematically beat panicking artistically. The relationship typing error was just Python version meets SQLAlchemy expectations—once aligned, the models behaved. And yes, I triple-checked what I pushed to GitHub. The .gitignore did the heavy lifting, and a tiny .gitkeep kept the db/ folder tidy. Complicated project, simple rule: automate the boring parts and commit the safe parts.

Optional Ideas for Expansion

  • Pagination and sorting for /events (e.g., page and order params).
  • Simple front-end table at / that fetches from /events with filters.
  • Tag filtering (tags=trade,tech) and a tiny autocomplete for civilization names.

Leave a Reply

Your email address will not be published. Required fields are marked *