ORM Factories & Seeders
Factories and seeders are two complementary tools for populating your database with data.
Factories define how to generate a single model instance with realistic fake data. They are designed for tests — each call produces a fresh record with randomised values, and they compose cleanly to handle relationships.
Seeders are async classes that insert a known, deterministic dataset. They are typically used to bootstrap a development or staging environment with a curated set of records (admin users, categories, demo content, etc.).
Factories
Defining a Factory
Create a factory class in databases/factories/. Extend Factory, declare the model it produces, and implement definition() to return a dictionary of field values.
The fake attribute is a pre-wired Faker instance available on every factory:
# databases/factories/UserFactory.py
from fastapi_startkit.masoniteorm.factory import Factory
from app.models.User import User
class UserFactory(Factory):
model = User
def definition(self):
return {
"first_name": self.fake.first_name(),
"last_name": self.fake.last_name(),
"email": self.fake.unique.email(),
"password": "secret",
"owner": False,
}factory.create() — Persist to the Database
Call Factory.new().create() to insert a record and return the saved model instance:
user = await UserFactory.new().create()
print(user.id) # set by the database
print(user.email) # a unique fake emailPass keyword arguments to override specific fields:
admin = await UserFactory.new().create(owner=True, email="admin@example.com")factory.make() — Build Without Saving
make() builds a model instance in memory without writing to the database. Useful for unit tests that do not require a database connection:
user = await UserFactory.new().make()
assert user.first_name # populated from definition()
assert user.id is None # not persistedOverride fields the same way as create():
user = await UserFactory.new().make(email="test@example.com")Creating Multiple Records
Chain .count(n) before create() or make() to produce a list of n instances:
users = await UserFactory.new().count(10).create()
# returns a list of 10 User instances, each persisted
drafts = await UserFactory.new().count(5).make()
# returns a list of 5 User instances, not persistedWhen count() is used the return value is always a list. Without count() a single instance is returned.
States / Variations
A state applies a partial override on top of definition(). Define states as methods on the factory that call self.state() with a callback returning the fields to merge:
class UserFactory(Factory):
model = User
def definition(self):
return {
"first_name": self.fake.first_name(),
"last_name": self.fake.last_name(),
"email": self.fake.unique.email(),
"password": "secret",
"account_status": "active",
}
def suspended(self):
return self.state(lambda attributes: {
"account_status": "suspended",
})Chain a state method before calling create() or make():
user = await UserFactory.new().suspended().create()
assert user.account_status == "suspended"Multiple states can be stacked — they are applied in order, each receiving the accumulated attributes:
user = await UserFactory.new().suspended().count(3).create()Lifecycle Hooks
Use configure() to register callbacks that run after make() or create():
class UserFactory(Factory):
model = User
def definition(self):
return {
"first_name": self.fake.first_name(),
"email": self.fake.unique.email(),
"password": "secret",
}
def configure(self):
async def after_making(user):
print(f"Built: {user.first_name}")
async def after_creating(user):
print(f"Persisted: {user.first_name} (id={user.id})")
return self.after_making(after_making).after_creating(after_creating)configure() is called automatically by Factory.new(). It must return self (or the result of chaining .after_making() / .after_creating()).
Relationships
has() creates a related factory record after the parent is saved. The foreign key is inferred automatically as {parent_table_singular}_id:
# Create an organization and automatically create 3 contacts for it
org = await OrganizationFactory.new().has(ContactFactory.new().count(3)).create()for_() creates the parent first and injects its id as a foreign key into the child:
contact = await ContactFactory.new().for_(OrganizationFactory.new()).create()Complete Factory Example
# databases/factories/ContactFactory.py
from faker import Faker
from fastapi_startkit.masoniteorm.factory import Factory
from app.models.Contact import Contact
fake = Faker()
class ContactFactory(Factory):
model = Contact
def definition(self):
return {
"first_name": self.fake.first_name(),
"last_name": self.fake.last_name(),
"email": self.fake.unique.email(),
"phone": self.fake.phone_number(),
"city": self.fake.city(),
"country": "US",
}Seeders
Defining a Seeder
Place seeder files in databases/seeds/. Extend Seeder and implement an async run() method:
# databases/seeds/category_seeder.py
from fastapi_startkit.masoniteorm.seeds import Seeder
from app.models.category import Category
class CategorySeeder(Seeder):
async def run(self):
categories = ["Programming", "Web Development", "Data Science", "Design"]
for name in categories:
await Category.first_or_create({"name": name})Using first_or_create makes seeders idempotent — running them multiple times will not create duplicate records.
Combining Factories and Seeders
Factories and seeders work well together. Use factories inside a seeder to generate large volumes of varied data:
# databases/seeds/database_seeder.py
from fastapi_startkit.masoniteorm.seeds import Seeder
from app.models.Account import Account
from app.models.User import User
from databases.factories.UserFactory import UserFactory
from databases.factories.OrganizationFactory import OrganizationFactory
from databases.factories.ContactFactory import ContactFactory
import random
class DatabaseSeeder(Seeder):
async def run(self):
# Create a root account
account = await Account.create({"name": "Acme Corporation"})
# Create a known admin user
await User.first_or_create(
{"email": "admin@example.com"},
{
"account_id": account.id,
"first_name": "Admin",
"last_name": "User",
"password": "secret",
"owner": True,
},
)
# Generate 5 random users with the factory
await UserFactory.new().count(5).create(account_id=account.id)
# Generate 100 organizations
organizations = await OrganizationFactory.new().count(100).create(account_id=account.id)
# Generate 100 contacts, each tied to a random organization
for _ in range(100):
await ContactFactory.new().create(
account_id=account.id,
organization_id=random.choice(organizations).id,
)Orchestrating Seeders with self.call()
The DatabaseSeeder is the conventional entry point. Use self.call() to run other seeders in a defined order:
# databases/seeds/database_seeder.py
from fastapi_startkit.masoniteorm.seeds import Seeder
from .category_seeder import CategorySeeder
from .user_seeder import UserSeeder
from .course_seeder import CourseSeeder
class DatabaseSeeder(Seeder):
async def run(self):
await self.call(CategorySeeder)
await self.call(UserSeeder)
await self.call(CourseSeeder)self.call() runs each seeder and waits for it to complete before starting the next. Order matters when seeders have dependencies — categories must exist before courses that reference them.
Running Seeders via Artisan
Run the DatabaseSeeder entry point:
python artisan db:seedRun a specific seeder class by name:
python artisan db:seed --class UserSeederUse a fully qualified dotted path to target a seeder in a sub-directory:
python artisan db:seed --class databases.seeds.user_seeder.UserSeederSpecify a custom seed directory with --directory:
python artisan db:seed --directory databases/seedsGenerating a Seeder File
The seed artisan command scaffolds a new seeder file:
python artisan seed PostThis creates databases/seeds/post_table_seeder.py with a PostTableSeeder stub:
"""PostTableSeeder Seeder."""
from fastapi_startkit.masoniteorm.seeds import Seeder
class PostTableSeeder(Seeder):
async def run(self):
"""Run the database seeds."""
pass