Async-native seeder and factory library for SQLAlchemy
Project description
sqlalchemy-seedling
Async-native seeder and factory library for SQLAlchemy. Dependency-aware runners, declarative factories, and a full CLI — designed for the async Python ecosystem.
Why seedling?
- Async-native — built for
async/awaitand SQLAlchemy 2.0's async session from day one. No sync wrappers, no thread-pool shims. - Dependency-aware — declare
depends_onbetween seeders; the runner topologically sorts them and runs independent seeders in parallel viaasyncio.gather. - Rich factories —
Factory[T]withFaker,LazyAttribute,Sequence,SubFactory, declarativeTraitclasses,@post_generationhooks, andAutoFactory[T]for mapper-introspected defaults. - State tracking — every run is recorded in a
seedling_statetable: append-only audit log, drift detection via content hash, and a--new-onlyskip flag. - Full CLI —
seed 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 (email → faker.email(), phone → faker.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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sqlalchemy_seedling-1.0.0rc1.tar.gz.
File metadata
- Download URL: sqlalchemy_seedling-1.0.0rc1.tar.gz
- Upload date:
- Size: 56.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67e3782e27167815b85c1c49181f7ae54d7b2dfdb586a0ed3f2cec1b0a8bc4c5
|
|
| MD5 |
84078df6e7614c0b04fe8481fe5fe45d
|
|
| BLAKE2b-256 |
a38e03aa402b44ea6547e2ffd5c0c040b91e912a7b50f44c25d3045e2b483121
|
File details
Details for the file sqlalchemy_seedling-1.0.0rc1-py3-none-any.whl.
File metadata
- Download URL: sqlalchemy_seedling-1.0.0rc1-py3-none-any.whl
- Upload date:
- Size: 33.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9fe8414fe99ae30df25aa6b529c61abe81bd0f140b6deee89aa6eb2972fba02b
|
|
| MD5 |
cdbca0d016c0c0e37a0d7adbc3fccde6
|
|
| BLAKE2b-256 |
796253990ff2c5eb3544235c123c5be3c1bf716c6c11c690ada40a1c8fda0f64
|