A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby
—
Convenience functions for direct object creation, debugging tools, factory generation utilities, and functional-style declaration wrappers. These utilities provide alternative ways to use Factory Boy and simplify common patterns.
Standalone functions for creating objects without defining factory classes, useful for simple cases and one-off object generation.
def build(klass, **kwargs):
"""
Build single instance of a class without persistence.
Args:
klass: Class to instantiate
**kwargs: Attribute values for the instance
Returns:
Instance of klass
"""
def create(klass, **kwargs):
"""
Create single instance of a class with persistence.
Args:
klass: Class to instantiate
**kwargs: Attribute values for the instance
Returns:
Persisted instance of klass
"""
def stub(klass, **kwargs):
"""
Create stub object with attributes only (no persistence).
Args:
klass: Class to use as model reference
**kwargs: Attribute values for the stub
Returns:
StubObject with specified attributes
"""
def generate(klass, strategy, **kwargs):
"""
Generate instance using specified strategy.
Args:
klass: Class to instantiate
strategy (str): 'build', 'create', or 'stub'
**kwargs: Attribute values for the instance
Returns:
Instance created with specified strategy
"""
def simple_generate(klass, create, **kwargs):
"""
Generate instance with boolean create flag.
Args:
klass: Class to instantiate
create (bool): True for create strategy, False for build
**kwargs: Attribute values for the instance
Returns:
Instance created with specified strategy
"""from factory.helpers import build, create, stub, generate, simple_generate
# Direct object creation without factory classes
user = build(User, name='Test User', email='test@example.com')
user = create(User, name='Persistent User', email='persist@example.com')
user = stub(User, name='Stub User', email='stub@example.com')
# Using strategy parameter
user = generate(User, 'build', name='Generated User')
user = simple_generate(User, create=False, name='Simple User')Functions for creating multiple objects at once without factory classes.
def build_batch(klass, size, **kwargs):
"""
Build multiple instances without persistence.
Args:
klass: Class to instantiate
size (int): Number of instances to create
**kwargs: Attribute values for all instances
Returns:
List of instances
"""
def create_batch(klass, size, **kwargs):
"""
Create multiple instances with persistence.
Args:
klass: Class to instantiate
size (int): Number of instances to create
**kwargs: Attribute values for all instances
Returns:
List of persisted instances
"""
def stub_batch(klass, size, **kwargs):
"""
Create multiple stub objects.
Args:
klass: Class to use as model reference
size (int): Number of stubs to create
**kwargs: Attribute values for all stubs
Returns:
List of StubObjects
"""
def generate_batch(klass, strategy, size, **kwargs):
"""
Generate multiple instances using specified strategy.
Args:
klass: Class to instantiate
strategy (str): 'build', 'create', or 'stub'
size (int): Number of instances to create
**kwargs: Attribute values for all instances
Returns:
List of instances created with specified strategy
"""
def simple_generate_batch(klass, create, size, **kwargs):
"""
Generate multiple instances with boolean create flag.
Args:
klass: Class to instantiate
create (bool): True for create strategy, False for build
size (int): Number of instances to create
**kwargs: Attribute values for all instances
Returns:
List of instances
"""from factory.helpers import build_batch, create_batch, stub_batch
# Batch creation without factory classes
users = build_batch(User, 5, is_active=True)
users = create_batch(User, 10, department='Engineering')
stubs = stub_batch(User, 3, role='admin')
# Using strategy and simple_generate
users = generate_batch(User, 'build', 5, status='pending')
users = simple_generate_batch(User, create=True, size=3, verified=True)Functions for dynamically creating factory classes from existing classes.
def make_factory(klass, **kwargs):
"""
Create simple factory class for given class with default attributes.
Args:
klass: Class to create factory for
**kwargs: Default attribute values for the factory
Returns:
Factory class configured for klass
"""from factory.helpers import make_factory
# Dynamically create factory
UserFactory = make_factory(
User,
name='Test User',
email=factory.Sequence(lambda n: f'user{n}@example.com'),
is_active=True
)
# Use like any other factory
user = UserFactory()
user = UserFactory(name='Custom Name')
users = UserFactory.create_batch(5)
# With complex declarations
PostFactory = make_factory(
Post,
title=factory.Faker('sentence'),
content=factory.Faker('text'),
author=factory.SubFactory(UserFactory),
published_at=factory.LazyFunction(lambda: timezone.now())
)Tools for debugging factory behavior and understanding object creation.
def debug(logger='factory', stream=None):
"""
Context manager for factory debugging that logs detailed creation steps.
Args:
logger (str): Logger name to use (default: 'factory')
stream: Output stream for debug messages (default: None uses logger)
Returns:
Context manager for debugging factory operations
"""from factory.helpers import debug
import logging
# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
# Debug single factory call
with debug():
user = UserFactory()
# Logs: Building User instance
# Logs: Setting attribute 'name' to 'Test User'
# Logs: Setting attribute 'email' to 'user1@example.com'
# etc.
# Debug with custom stream
import sys
with debug(stream=sys.stdout):
users = UserFactory.create_batch(3)
# Debug with custom logger
with debug(logger='my_factory_debug'):
post = PostFactory(author__name='Custom Author')Decorator functions that convert regular functions into factory declarations, enabling functional programming patterns.
def lazy_attribute(func):
"""
Wrap function as LazyAttribute declaration.
Args:
func: Function that takes instance and returns attribute value
Returns:
LazyAttribute declaration
"""
def sequence(func):
"""
Wrap function as Sequence declaration.
Args:
func: Function that takes sequence number and returns attribute value
Returns:
Sequence declaration
"""
def lazy_attribute_sequence(func):
"""
Wrap function as LazyAttributeSequence declaration.
Args:
func: Function that takes (instance, sequence_number) and returns value
Returns:
LazyAttributeSequence declaration
"""
def container_attribute(func):
"""
Wrap function as ContainerAttribute declaration (non-strict mode).
Args:
func: Function that takes (instance, containers) and returns value
Returns:
ContainerAttribute declaration with strict=False
"""
def post_generation(func):
"""
Wrap function as PostGeneration declaration.
Args:
func: Function that takes (instance, create, extracted, **kwargs)
Returns:
PostGeneration declaration
"""
def iterator(func):
"""
Turn generator function into Iterator declaration.
Args:
func: Generator function that yields values
Returns:
Iterator declaration using function output
"""from factory.helpers import (
lazy_attribute, sequence, lazy_attribute_sequence,
container_attribute, post_generation, iterator
)
# Functional approach to factory definitions
@lazy_attribute
def full_name(obj):
return f'{obj.first_name} {obj.last_name}'
@sequence
def username(n):
return f'user_{n:04d}'
@lazy_attribute_sequence
def email(obj, n):
return f'{obj.username}_{n}@example.com'
@container_attribute
def parent_id(obj, containers):
return containers[0].id if containers else None
@post_generation
def setup_profile(obj, create, extracted, **kwargs):
if create:
obj.create_profile()
obj.send_welcome_email()
@iterator
def department_generator():
departments = ['Engineering', 'Sales', 'Marketing', 'HR']
while True:
for dept in departments:
yield dept
# Use in factory definition
class UserFactory(Factory):
class Meta:
model = User
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
full_name = full_name
username = username
email = email
department = department_generator
setup_profile = setup_profile
# Inline usage
class EmployeeFactory(Factory):
class Meta:
model = Employee
# Direct decorator usage
employee_id = sequence(lambda n: f'EMP{n:06d}')
display_name = lazy_attribute(
lambda obj: f'{obj.last_name}, {obj.first_name}'
)
notify_hr = post_generation(
lambda obj, create, extracted:
hr_system.notify_new_employee(obj) if create else None
)def make_factory_with_traits(klass, traits=None, **defaults):
"""Custom factory creation with trait support."""
class DynamicFactory(Factory):
class Meta:
model = klass
class Params:
pass
# Add default attributes
for key, value in defaults.items():
setattr(DynamicFactory, key, value)
# Add traits
if traits:
for trait_name, trait_attrs in traits.items():
setattr(DynamicFactory.Params, trait_name, Trait(**trait_attrs))
return DynamicFactory
# Usage
UserFactory = make_factory_with_traits(
User,
traits={
'admin': {'is_staff': True, 'is_superuser': True},
'inactive': {'is_active': False, 'last_login': None}
},
username=sequence(lambda n: f'user{n}'),
email=lazy_attribute(lambda obj: f'{obj.username}@example.com')
)
admin_user = UserFactory(admin=True)
inactive_user = UserFactory(inactive=True)from factory.helpers import create, build
def create_user_conditionally(persist=True, **kwargs):
"""Create user with conditional persistence."""
if persist:
return create(User, **kwargs)
else:
return build(User, **kwargs)
# Usage in tests
def test_user_creation():
# Development mode - don't persist
user = create_user_conditionally(
persist=settings.DEBUG,
name='Test User'
)def debug_factory(factory_class):
"""Decorator to add debugging to factory class."""
original_create = factory_class.create
original_build = factory_class.build
@classmethod
def debug_create(cls, **kwargs):
with debug():
return original_create(**kwargs)
@classmethod
def debug_build(cls, **kwargs):
with debug():
return original_build(**kwargs)
factory_class.create = debug_create
factory_class.build = debug_build
return factory_class
# Usage
@debug_factory
class UserFactory(Factory):
class Meta:
model = User
name = Faker('name')
# All creation operations are now automatically debugged
user = UserFactory.create() # Debug output includedInstall with Tessl CLI
npx tessl i tessl/pypi-factory-boy