db seed
Populate your database with initial or test data using seeders — Python classes that define exactly what gets inserted. Run all seeders at once or target a specific one by name.
Overview
Seeders are useful in several situations:
Populate a fresh database with realistic test data so developers can work without manually inserting rows.
Insert required lookup data — roles, categories, status codes — that must exist before the app runs.
Seed a demo environment with sample users, products, and orders for presentations or staging.
Reset and re-seed a test database to a known state before each test run.
Usage
Run all seeders
fastkit db seed
Run a specific seeder
fastkit db seed UserSeeder
fastkit db seed ProductSeeder
The seeder name must match the class name exactly, including capitalization — UserSeeder not userseeder.
Writing Seeders
Each seeder is a Python class with a run() method. FastKit CLI discovers them automatically from the seeders/ directory in your project.
Basic seeder
# seeders/user_seeder.py
from fastkit_core.database import get_db
from modules.users.models import User
from modules.users.schemas import UserCreate
from modules.users.service import UserService
class UserSeeder:
"""Seed initial users."""
def run(self) -> None:
session = next(get_db())
service = UserService(session)
users = [
UserCreate(
name="Alice Admin",
email="alice@example.com",
password="Secret1!",
role="admin"
),
UserCreate(
name="Bob User",
email="bob@example.com",
password="Secret1!",
role="user"
),
]
for user_data in users:
if not service.exists(email=user_data.email):
service.create(user_data)
session.close()
Always check for existing records before inserting — service.exists(email=...) — so seeders are idempotent and safe to run multiple times without creating duplicates.
Seeder with reference data
Reference data — roles, categories, statuses — that must exist before the application runs:
# seeders/role_seeder.py
from fastkit_core.database import get_db
from modules.roles.models import Role
class RoleSeeder:
"""Seed application roles — required before UserSeeder."""
ROLES = [
{"name": "admin", "description": "Full access"},
{"name": "manager", "description": "Manage resources"},
{"name": "user", "description": "Standard access"},
{"name": "guest", "description": "Read-only access"},
]
def run(self) -> None:
session = next(get_db())
for role_data in self.ROLES:
exists = session.query(Role).filter_by(name=role_data["name"]).first()
if not exists:
session.add(Role(**role_data))
session.commit()
session.close()
Async seeder
If your project uses async sessions, implement run() as a coroutine:
# seeders/product_seeder.py
import asyncio
from fastkit_core.database import get_async_db
from modules.products.service import ProductService
from modules.products.schemas import ProductCreate
class ProductSeeder:
"""Seed sample products — async version."""
async def run(self) -> None:
async for session in get_async_db():
service = ProductService(session)
products = [
ProductCreate(name="Widget Pro", price=29.99, stock=100),
ProductCreate(name="Gadget Basic", price=9.99, stock=500),
ProductCreate(name="Super Bundle", price=49.99, stock=50),
]
for product_data in products:
if not await service.exists(name=product_data.name):
await service.create(product_data)
break
Project Structure
Place all seeders in a seeders/ directory at the project root. FastKit CLI discovers all classes ending in Seeder automatically:
seeders/
Seeder discovery directory
__init__.py
Optional — for shared seeder utilities
role_seeder.py
class RoleSeeder
user_seeder.py
class UserSeeder
client_seeder.py
class ClientSeeder
product_seeder.py
class ProductSeeder
Naming conventions — one seeder class per file, file named in snake_case, class named in PascalCase with a Seeder suffix:
| File | Class name |
|---|---|
role_seeder.py | RoleSeeder |
user_seeder.py | UserSeeder |
invoice_seeder.py | InvoiceSeeder |
Ordering & Dependencies
When running all seeders with fastkit db seed, the execution order follows the file discovery order (alphabetical by filename). Since seeders often depend on each other — users need roles, invoices need clients — design your filenames to run in the right order:
01_role_seeder.py
→
No dependencies — runs first
02_user_seeder.py
→
Needs roles
03_client_seeder.py
→
Needs users
04_product_seeder.py
→
Independent
05_invoice_seeder.py
→
Needs clients & products
With numeric prefixes, the order is explicit and immediately readable. Running a specific seeder by name still works regardless of the prefix:
# Still works — the name is the class name, not the filename
fastkit db seed UserSeeder
fastkit db seed InvoiceSeeder
Best Practices
Make seeders idempotent
Seeders should be safe to run multiple times. Always check before inserting:
def run(self):
# Inserts duplicates every run
service.create(UserCreate(
email="admin@example.com",
...
))
def run(self):
email = "admin@example.com"
if not service.exists(email=email):
service.create(UserCreate(
email=email, ...
))
Keep seeders focused
One seeder per model — don't insert users and roles in the same file. This makes it easy to run or debug individual seeders and keeps the order of execution explicit.
Use realistic data
Test data should resemble real data. Libraries like Faker make it easy to generate names, emails, addresses, and more:
from faker import Faker
fake = Faker()
class ClientSeeder:
def run(self) -> None:
session = next(get_db())
service = ClientService(session)
for _ in range(20):
service.create(ClientCreate(
name=fake.company(),
email=fake.company_email(),
phone=fake.phone_number(),
address=fake.address(),
))
session.close()
Never run in production with destructive data
Seeders that truncate tables before inserting are fine in development but dangerous in production. Use environment checks to guard destructive operations:
import os
class DemoDataSeeder:
def run(self) -> None:
if os.getenv("APP_ENV") == "production":
print("Skipping DemoDataSeeder in production.")
return
# ... insert demo data safely