Skip to content

sqlalchemy-seedlingAsync-native seeder & factory library

Built for SQLAlchemy 2.0+ async workflows — seed databases, generate fixtures, and run factories without the boilerplate.

sqlalchemy-seedling

How it compares

For an async SQLAlchemy 2.0 app that needs both fixture factories and a seeder pipeline, the alternatives are usually factory_boy plus a hand-rolled script. Here's the same job, three ways.

Seed 1 000 rows + run a dependent seeder

python
class UserFactory(AutoFactory[User]):
    model = User

class UserSeeder(Seeder):
    environments = DEV_AND_TEST
    async def run(self, session):
        await UserFactory.create_batch(session, 1000, bulk=True)

class ProfileSeeder(Seeder):
    depends_on = [UserSeeder]
    environments = DEV_AND_TEST
    async def run(self, session): ...

runner = SeederRunner(session_factory, env="development")
runner.register(UserSeeder, ProfileSeeder)
await runner.run()
python
class UserFactory(SQLAlchemyModelFactory):
    class Meta:
        model = User
        sqlalchemy_session_persistence = "flush"
    name = factory.Faker("name")
    email = factory.Faker("email")
    # ...every column declared manually

# factory_boy is sync — bridge to async yourself
def seed_users(sync_session):
    UserFactory._meta.sqlalchemy_session = sync_session
    UserFactory.create_batch(1000)

def seed_profiles(sync_session): ...

# Ordering, environments, idempotency, parallel — DIY
seed_users(session)
seed_profiles(session)

Feature matrix

Seedlingfactory_boyraw SQLAlchemy
Async-native API❌ (sync only)
Zero-config field defaults from mapperAutoFactory❌ declare each
Bulk INSERT … RETURNING pathbulk=True✅ manual
Seeder orchestration (depends_on)
Environment gating (dev / test / prod)
State tracking + drift detection
Parallel level execution
pytest fixtures + @seed() decorator
CLI (seed run, fresh, graph, …)
Per-row throughput (1 000 rows, SQLite)2 300 rows/s16 500 rows/s8 200 rows/s
Bulk throughput (1 000 rows, SQLite)17 400 rows/s507 000 rows/s

Honest trade-off: factory_boy is faster per-row because its sync code path skips the refresh round-trip Seedling does after each insert. If you don't need @post_generation hooks on the live instance, use bulk=True and you match factory_boy. See the benchmarks page for full numbers.