Columns seedgraph cannot fill on its own¶
seedgraph generates strings, numbers, dates, booleans, enums, UUIDs, binary and arrays of those, fitted to each column's type. Two kinds of column have no defensible default: JSON, whose expected shape only your application knows, and custom types built on TypeDecorator. A nullable one is left empty; a required one stops the seed with UnsupportedPlaceholderError until you say how to fill it.
These tests use the seedgraph_graph fixture, which the plugin registers when seedgraph is installed.
The models¶
from decimal import Decimal
import pytest
from sqlalchemy import JSON, Integer, TypeDecorator
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from seedgraph import UnsupportedPlaceholderError
class Cents(TypeDecorator):
"""Store a Decimal amount as an integer number of cents."""
impl = Integer
cache_ok = True
def process_bind_param(self, value, dialect):
return None if value is None else int(value * 100)
def process_result_value(self, value, dialect):
return None if value is None else Decimal(value) / 100
class Base(DeclarativeBase):
pass
class Order(Base):
__tablename__ = "orders"
id: Mapped[int] = mapped_column(primary_key=True)
reference: Mapped[str]
total = mapped_column(Cents(), nullable=False)
details: Mapped[dict] = mapped_column(JSON)
The error names the column¶
def test_a_required_custom_column_stops_the_seed(seedgraph_graph):
with pytest.raises(UnsupportedPlaceholderError, match="orders.total"):
seedgraph_graph(Order)
Declaring a generator¶
A generator receives a context: ctx.fake is the session's seeded Faker, so values stay reproducible, and ctx.column is the column name.
def amount(ctx):
return Decimal(ctx.fake.pyint(min_value=100, max_value=99_999)) / 100
def test_generators_fill_the_custom_and_json_columns(seedgraph_graph):
graph = seedgraph_graph(
Order,
order=5,
generators={
Order: {
"total": amount,
"details": lambda ctx: {"channel": ctx.fake.random_element(["web", "store"])},
}
},
)
assert all(Decimal("1.00") <= order.total <= Decimal("999.99") for order in graph.orders)
assert {order.details["channel"] for order in graph.orders} <= {"web", "store"}
Pinning one value instead¶
When every object needs the same value, an override is shorter than a generator. A plain value pins it; a callable taking the same context computes it:
def test_an_override_pins_the_value(seedgraph_graph):
graph = seedgraph_graph(
Order,
order=2,
generators={Order: {"total": amount}},
overrides={Order: {"details": {"channel": "web"}, "reference": lambda ctx: ctx.fake.bothify("ORD-####")}},
)
assert [order.details for order in graph.orders] == [{"channel": "web"}, {"channel": "web"}]
assert all(order.reference.startswith("ORD-") for order in graph.orders)