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.

Tables, Not Tales: Building a Tiny Civilization in SQLite

Day 77 of 100 Days Coding Challenge: Python

Today I put down the party streamers and picked up a blueprint. The job: lay the foundation for my Civilization Timeline app. On the menu were four tables—Civilization, Event, Tag, and the many-to-many glue EventTag—plus Alembic migrations and a tiny pytest to make sure I didn’t accidentally build a pyramid upside down.

This project has more moving parts than my last one, so I treated each step like crossing a rope bridge: slow, steady, and testing every plank. I reminded myself that I don’t need to understand every microscopic detail today; I just need to know why each step matters. With AI as my trail guide (and occasional comic relief), I focused on purpose over perfection—and got to a green “tests passed” without falling into the canyon.

Today’s Motivation / Challenge

Databases are the memory of an app. If I want timelines, comparisons, and “who overlapped whom,” I first need a clean vocabulary of things and relationships. Today’s work turns ideas into durable structures—like labeling boxes before you stack them in the attic, so Future Me doesn’t cry later.

Purpose of the Code (Object)

The code defines four tables—civilizations, events, tags, and the event-tag join—using SQLModel. Alembic migrations snapshot this structure so I can version changes safely. A simple pytest spins up a temporary database, runs the migration, and confirms the tables exist. If the test passes, my schema is real—not just a wish.

AI Prompt

(And we also recorded the Day 2 acceptance line in the project script: “Day 2 — Data model v1: Tables: civilizations, events, tags, joins. Use SQLModel + Alembic. Accept: migration creates tables; pytest -k ‘migrations’ passes.”)

Functions & Features

  • Define core schema with SQLModel: Civilization, Event, Tag.
  • Model many-to-many relationships via EventTag.
  • Set up Alembic and generate an initial migration.
  • Run a pytest that upgrades a fresh DB and asserts the four tables exist.

Requirements / Setup

  • Python 3.11+ recommended

Install basics:

pip install sqlmodel sqlalchemy alembic pytest

Environment:

# SQLite file location

setx DATABASE_URL “sqlite:///db/civ.db”   # (PowerShell: $env:DATABASE_URL = …)

Minimal Code Sample

# app/models/core.py

from typing import Optional, List

from sqlmodel import SQLModel, Field, Relationship

class EventTag(SQLModel, table=True):        # link table first so it’s importable

    event_id: int = Field(primary_key=True, foreign_key=”event.id”)

    tag_id:   int = Field(primary_key=True, foreign_key=”tag.id”)

class Civilization(SQLModel, table=True):

    id: Optional[int] = Field(default=None, primary_key=True)

    name: str; region: str

    start_year: int; end_year: Optional[int] = None

    events: List[“Event”] = Relationship(back_populates=”civilization”)

class Event(SQLModel, table=True):

    id: Optional[int] = Field(default=None, primary_key=True)

    civilization_id: int = Field(foreign_key=”civilization.id”)

    title: str; year: int

    civilization: Optional[Civilization] = Relationship(back_populates=”events”)

    tags: List[“Tag”] = Relationship(back_populates=”events”, link_model=EventTag)

class Tag(SQLModel, table=True):

    id: Optional[int] = Field(default=None, primary_key=True)

    name: str

    events: List[Event] = Relationship(back_populates=”tags”, link_model=EventTag)

Define the link table first; then reference it with link_model=EventTag for a clean many-to-many.

The Civilization Timeline Builder

Notes / Lessons Learned

  • I first tried adding __tablename__ like in raw SQLAlchemy. SQLModel didn’t appreciate that. Dropping __tablename__ and using table=True made everything click. Foreign keys then target the default singular table names (“civilization.id”, “event.id”, “tag.id”), which is tidy and predictable.
  • AI is a fantastic copilot—as long as I stay the pilot. When in doubt, I asked for small, verifiable steps and sanity checks. Explaining my error messages clearly got me faster, better fixes.
  • Alembic initialization matters. The one-liner alembic init alembic builds the right scaffolding. File structure isn’t ceremony—it’s how tools find each other.
  • A tiny gotcha: Alembic autogenerate used SQLModel’s AutoString in the revision, but the file only imported SQLAlchemy. Adding import sqlmodel at the top (or swapping AutoString for sa.String) fixed it. Another tiny snag: from __future__ import annotations must be first or not at all in migration files—easiest solution was to remove it.
  • Pytest wasn’t installed at first; the error reminded me. After installing, pytest -k “migrations” passed. The green check felt like a gold star sticker from elementary school—strangely motivating.

Optional Ideas for Expansion

  • Add seed scripts and CSVs (e.g., 10–12 major civilizations with a few events each) so the UI can show real bands tomorrow.
  • Introduce an enum (or controlled vocabulary) for event kinds and a couple of helpful indexes for faster filtering.
  • Write a second test that inserts a sample event with two tags and asserts the join table behaves as expected.

A New Project: Starting a Civilization Timeline Builder

Day 76 of 100 Days Coding Challenge: Python

Civilization Time Builder (Day 76 – Day 96)

When AI first suggested this project, I hesitated. The generated project plan was full of terms I barely understood. Take “Poetry,” for example. At first, it sounded like something Shakespeare might dabble in—but in reality, it’s a tool for managing Python dependencies. Once I looked it up and understood what it did, I decided to give it a try.

Environment management had been a thorn in my side during the Stock Tracker App Project. A single update to Python or one of its libraries could break everything I had built. I had already watched my programs collapse because of an accidental update. It was clear: if I wanted to build something more complex, I needed to learn a tool like Poetry. 

The theme of this project also pulled me in. It was all about history—a subject I’ve loved since childhood. I can still remember sitting in my parents’ library, leafing through their history books for hours. In that sense, this project felt tailor-made for me.

What excites me most is how expandable this program can be. I could easily adapt it into something larger, like mapping the timelines of Asimov’s Foundation series, Sanderson’s Stormlight Archive, or other Cosmere-related books.

That said, this was one of the toughest challenges I’ve faced so far. Some days I spent five or six hours just trying to fix problems. At one point, I almost quit altogether. But I kept going. Looking back, I’m glad I didn’t give up. Yes, I made plenty of mistakes—but each one taught me something valuable. If nothing else, I learned the importance of patience—with my code and with myself.

Experience

Today marked Day 1 of my shiny new project: the Civilization Timeline Builder. Finally, a project that ties together two of my favorite things—coding and history. The idea is to build an app that makes exploring history feel a little less like flipping through a dusty textbook and a little more like discovering buried treasure.

This project feels bigger than anything I’ve tackled before. Bigger data, bigger scope, bigger headaches. To avoid drowning in dependency drama, I brought in Poetry, my new virtual assistant. Poetry takes charge of libraries, versions, and packaging so I don’t end up in dependency jail. Think of it as a backstage manager who makes sure the actors (libraries) show up on time and don’t fight with each other.

The process, however, wasn’t like my earlier, simpler projects where I just spun up a virtual environment and carried on. Right away, I could sense something different. I usually code at an unholy hour of the morning, fueled by post-exercise clarity, but even then I had to stop and double-check every step. I even started a day early just to reread the instructions, as if I were reviewing the rules of an unfamiliar board game before play.

Today’s Motivation / Challenge

Why bother with all this fuss? Because Poetry is the difference between a messy desk covered in loose papers and a neat binder where everything is tabbed, labeled, and ready to go. Dependency management sounds boring—until it breaks. Then it’s all you think about. This project matters because it forces me to upgrade my tools and habits, making me a more organized (and hopefully less panicked) developer.

Purpose of the Code (Object)

The code for today doesn’t build empires or conquer continents just yet. Instead, it sets up a tiny FastAPI server with a health check and a stub of civilization data. It’s the scaffolding—the skeleton—upon which everything else will hang. Think of it as the tent poles before the circus begins.

Functions & Features

  • Start a FastAPI server with a health check endpoint.
  • Return a couple of sample civilizations from in-memory data.
  • Filter civilizations by region and year range.

Requirements / Setup

  • Python 3.13
  • Poetry (dependency management)
  • FastAPI, Streamlit, Uvicorn

Install Poetry’s deps via:

poetry install

Run the API:

poetry run uvicorn app.main:api –reload

Minimal Code Sample

from fastapi import FastAPI, Query

from typing import List, Optional

api = FastAPI(title=”CivTimeline API”)

@api.get(“/health”)

def health():

    return {“ok”: True}

CIVS = [

    {“id”: 1, “name”: “Roman Republic/Empire”, “region”: “Europe”, “start_year”: -509, “end_year”: 476},

    {“id”: 2, “name”: “Han Dynasty”, “region”: “East Asia”, “start_year”: -206, “end_year”: 220},

]

@api.get(“/civs”)

def list_civs(region: Optional[List[str]] = Query(None), start: int = -3000, end: int = 2000):

    # Filter civs by year and region

    return {“items”: [c for c in CIVS if c[“start_year”] <= end and c[“end_year”] >= start 

                      and (not region or c[“region”] in region)]}

A small API stub with civilizations you can query.

The Civilization Timeline Builder

Notes / Lessons Learned

This project began with a repo scaffold: Poetry + FastAPI + Streamlit. It quickly proved to be a different animal than the smaller projects I’d done before. Installing Poetry felt like watching paint dry—it took so long I thought I had failed. But once I checked, it was there, quietly waiting for me to notice.

Next came the .env and .env.example files, along with .gitignore. These guardrails saved me from committing private config files to git. I learned this lesson the hard way in a previous project, where I nearly exposed secrets before catching myself. A README was also added, though I’ll admit I skipped it at first and paid the price in confusion later.

Booting up the FastAPI and Streamlit stubs worked, but the process felt chaotic. Nothing was “normal” compared to my earlier projects. To cope, I broke the steps into tiny pieces and forced myself to ask “why” at each step. The trick is not to stay in “why mode” too long—you’ll skip something important. Next time, I’m making a checklist before indulging my curiosity.

Optional Ideas for Expansion

  • Add a proper database instead of temporary in-memory data.
  • Build a simple UI filter in Streamlit for regions and years.
  • Visualize civilizations on a timeline chart.