Skip to content

arthurvasconcelos/seedling

Repository files navigation

sqlalchemy-seedling logo

sqlalchemy-seedling

1.0 RC — feedback wanted. Track feedback in #1. Final 1.0 ships ~2026-06-16.

Async-native seeder and factory library for SQLAlchemy. Dependency-aware runners, declarative factories, and a full CLI — designed for the async Python ecosystem.

PyPI version Python versions License: MIT CI Coverage Downloads


Why seedling?

  • Async-native — built for async/await and SQLAlchemy 2.0's async session from day one. No sync wrappers, no thread-pool shims.
  • Dependency-aware — declare depends_on between seeders; the runner topologically sorts them and runs independent seeders in parallel via asyncio.gather.
  • Rich factoriesFactory[T] with Faker, LazyAttribute, Sequence, SubFactory, declarative Trait classes, @post_generation hooks, and AutoFactory[T] for mapper-introspected defaults.
  • State tracking — every run is recorded in a seedling_state table: append-only audit log, drift detection via content hash, and a --new-only skip flag.
  • Full CLIseed run, seed fresh, seed status, seed validate, seed graph, seed export, seed restore, and scaffolding commands.
  • Framework-agnostic — works with FastAPI, Litestar, or a plain script; no framework coupling.

Installation

pip install sqlalchemy-seedling
# or
uv add sqlalchemy-seedling

Optional YAML fixture support:

pip install sqlalchemy-seedling[yaml]

Requires Python 3.11+ and SQLAlchemy 2.0+.


Quick start

Scaffold the project layout

seed init

Creates seeders/ and factories/ packages and appends [tool.seedling] to pyproject.toml.

Define seeders

# seeders/users.py
from seedling import Seeder, DEV_AND_TEST
from sqlalchemy.ext.asyncio import AsyncSession
from myapp.models import User

class UserSeeder(Seeder):
    environments = DEV_AND_TEST
    models = [User]
    tags = {"demo"}

    async def run(self, session: AsyncSession) -> None:
        session.add(User(email="admin@example.com", name="Admin"))
        await session.commit()


# seeders/posts.py
from seedling import Seeder, DEV_AND_TEST
from seeders.users import UserSeeder

class PostSeeder(Seeder):
    depends_on = [UserSeeder]       # runs after UserSeeder automatically
    environments = DEV_AND_TEST

    async def run(self, session: AsyncSession) -> None:
        ...

Create a runner factory

# seeders/__init__.py
from seedling import SeederRunner
from myapp.db import async_session_maker
from .users import UserSeeder
from .posts import PostSeeder

def create_runner(env: str = "development") -> SeederRunner:
    runner = SeederRunner(session_factory=async_session_maker, env=env)
    runner.register(UserSeeder, PostSeeder)
    return runner

Configure the CLI

# pyproject.toml
[tool.seedling]
runner = "seeders:create_runner"

Run

seed run                          # run all seeders for development
seed run --tag demo               # run only seeders tagged "demo"
seed fresh                        # truncate then re-seed
seed status                       # latest run per seeder + drift detection
seed list                         # print execution order without running

Factories

from seedling import Factory, Faker, LazyAttribute, Sequence, SubFactory, Trait

class UserFactory(Factory[User]):
    model = User
    email = Faker("email")
    name  = Sequence(lambda n: f"User {n}")

    class admin(Trait):
        is_superuser = True

class PostFactory(Factory[Post]):
    model = Post
    author = SubFactory(UserFactory)
    title  = Faker("sentence")
# In-memory — no DB required
user = UserFactory.build(admin=True)

# Persisted
user  = await UserFactory.create(session)
posts = await PostFactory.create_batch(session, 5)

# Fast bulk insert — no hooks fired, no SubFactory resolution
rows  = await UserFactory.create_batch(session, 10_000, bulk=True)

AutoFactory[T] generates sensible defaults from mapper introspection, with name-based smart defaults (emailfaker.email(), phonefaker.phone_number(), etc.):

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

Helpers

from seedling import upsert, truncate_tables, reset_sequences, deferred_constraints

await upsert(session, User, {"id": 1, "email": "a@b.com"})   # idempotent insert
await truncate_tables(session, User, Post, cascade=True)      # dialect-aware TRUNCATE
await reset_sequences(session, User)                          # PostgreSQL: reset SERIAL

async with deferred_constraints(session):                     # PostgreSQL: defer FKs
    ...

State tracking

seed status                       # show last run per seeder + drift flag
seed run --new-only               # skip seeders whose source hasn't changed
seed run --force                  # force re-run even if --new-only would skip

Disable per-project:

[tool.seedling]
state_tracking = false

pytest integration

# conftest.py
@pytest.fixture(scope="session")
def seedling_session_factory():
    engine = create_async_engine("sqlite+aiosqlite:///:memory:")
    return async_sessionmaker(engine, expire_on_commit=False)
from seedling.pytest_plugin import seed

@seed(UserSeeder)
async def test_with_user(seedling_transactional_session):
    # UserSeeder ran before the test body;
    # session is rolled back automatically after the test
    ...

CLI reference

Command Description
seed run Run seeders in dependency order
seed fresh Truncate then re-seed
seed list Print resolved execution order
seed status Latest run per seeder with drift detection
seed validate Static checks: cycles, missing deps, empty envs
seed graph Dependency graph (Graphviz DOT or Mermaid)
seed export Dump seeded rows to JSON or YAML
seed restore Load a fixture file back into the database
seed init Scaffold seeders/ and factories/
seed make:seeder <Name> Generate a seeder stub
seed make:factory <module:ClassName> Generate a factory stub

See the CLI reference for full options and flags.


Stability

From 1.0.0 onward, seedling follows Semantic Versioning: breaking changes only on major version bumps.


Documentation

Full docs: https://arthurvasconcelos.github.io/seedling


License

MIT — see LICENSE.

About

Async-native seeder and factory library for SQLAlchemy — dependency-aware runners, declarative factories, and a CLI. Laravel-style ergonomics for the Python async ecosystem.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Contributors