Skip to main content

Async-native seeder and factory library for SQLAlchemy

Project description

sqlalchemy-seedling logo

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.

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sqlalchemy_seedling-1.0.0rc1.tar.gz (56.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sqlalchemy_seedling-1.0.0rc1-py3-none-any.whl (33.1 kB view details)

Uploaded Python 3

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

Hashes for sqlalchemy_seedling-1.0.0rc1.tar.gz
Algorithm Hash digest
SHA256 67e3782e27167815b85c1c49181f7ae54d7b2dfdb586a0ed3f2cec1b0a8bc4c5
MD5 84078df6e7614c0b04fe8481fe5fe45d
BLAKE2b-256 a38e03aa402b44ea6547e2ffd5c0c040b91e912a7b50f44c25d3045e2b483121

See more details on using hashes here.

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

Hashes for sqlalchemy_seedling-1.0.0rc1-py3-none-any.whl
Algorithm Hash digest
SHA256 9fe8414fe99ae30df25aa6b529c61abe81bd0f140b6deee89aa6eb2972fba02b
MD5 cdbca0d016c0c0e37a0d7adbc3fccde6
BLAKE2b-256 796253990ff2c5eb3544235c123c5be3c1bf716c6c11c690ada40a1c8fda0f64

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page