Factory API Reference
The Factory class is the core component of factorio. It provides the mechanism for generating model instances with realistic data.
Factory[T]
A generic base class that all factories must inherit from. The type parameter T specifies the model type that this factory creates.
Type Parameter
T: The model type (dataclass, Pydantic model, SQLAlchemy model, etc.)
Class Methods
get_model() → type[T]
Extracts the model type from the factory's generic type annotation.
Returns: The concrete model type that this factory creates.
Raises:
- TypeError - If no concrete model is found
- TypeError - If multiple concrete models are found (ambiguous inheritance)
Example:
from factorio.factories import Factory
from dataclasses import dataclass
@dataclass
class User:
name: str
class UserFactory(Factory[User]):
name = fields.TextField("name")
model_type = UserFactory.get_model()
assert model_type is User
How it works:
The method inspects the Method Resolution Order (MRO) of the factory class and extracts type arguments from __orig_bases__. It looks for non-TypeVar types in the generic parameters.
Note: This is called automatically by .build(), so you rarely need to call it directly.
build(**kwargs) → T
Creates an instance of the model with generated field values.
Parameters:
- **kwargs: Optional field overrides. Can be:
- Direct values (e.g., name="John")
- Field instances (e.g., age=fields.IntegerField(min_value=20, max_value=30))
Returns: An instance of type T (the model)
Example:
from factorio import fields
from factorio.factories import Factory
class UserFactory(Factory[User]):
name = fields.TextField("name")
age = fields.IntegerField(min_value=18, max_value=65)
email = fields.TextField("email")
# Basic usage
user = UserFactory.build()
assert isinstance(user, User)
assert user.name
assert 18 <= user.age <= 65
# Override with values
user = UserFactory.build(name="Jane", age=30)
assert user.name == "Jane"
assert user.age == 30
# Override with field instances
user = UserFactory.build(
age=fields.IntegerField(min_value=50, max_value=60)
)
assert 50 <= user.age <= 60
How it works:
1. Calls get_model() to determine the model type
2. Iterates through class attributes to find AbstractField instances
3. Calls each field to generate a value
4. Applies any kwargs overrides (values or field instances)
5. Instantiates the model with all field values
6. Returns the model instance
Important:
- Fields are evaluated when .build() is called, not when the factory is defined
- Override values take precedence over factory-defined fields
- If you pass a field instance as an override, it will be evaluated
Creating Factories
Basic Factory
from dataclasses import dataclass
from factorio import fields
from factorio.factories import Factory
@dataclass
class Product:
name: str
price: float
in_stock: bool
class ProductFactory(Factory[Product]):
name = fields.TextField("word").capitalize()
price = fields.FloatField(min_value=0.01, max_value=999.99)
in_stock = fields.BooleanField(truth_probability=80)
product = ProductFactory.build()
Factory with Nested Objects
@dataclass
class Address:
street: str
city: str
@dataclass
class User:
name: str
address: Address
class AddressFactory(Factory[Address]):
street = fields.TextField("street_address")
city = fields.TextField("city")
class UserFactory(Factory[User]):
name = fields.TextField("name")
address = fields.FactoryField(AddressFactory)
user = UserFactory.build()
assert isinstance(user.address, Address)
Factory Inheritance
class BaseUserFactory(Factory[User]):
name = fields.TextField("name")
email = fields.TextField("email")
class AdminUserFactory(BaseUserFactory):
role = fields.ConstantField("admin")
is_admin = fields.ConstantField(True)
class RegularUserFactory(BaseUserFactory):
role = fields.ChoiceField(["user", "member"])
is_admin = fields.ConstantField(False)
admin = AdminUserFactory.build()
assert admin.role == "admin"
assert admin.is_admin is True
Common Patterns
Minimal Factory
Only define fields you want to customize:
@dataclass
class Config:
timeout: int = 30
retries: int = 3
debug: bool = False
class ConfigFactory(Factory[Config]):
# Only override timeout, use defaults for others
timeout = fields.IntegerField(min_value=1, max_value=120)
config = ConfigFactory.build()
assert config.retries == 3 # Default
assert config.debug is False # Default
Factory with All Fields
Define all fields explicitly:
class CompleteUserFactory(Factory[User]):
name = fields.TextField("name")
email = fields.TextField("email")
age = fields.IntegerField(min_value=18, max_value=65)
bio = fields.TextField("paragraph", nb_sentences=2)
is_active = fields.BooleanField(truth_probability=90)
created_at = fields.DateTimeField()
Reusable Factory Base
Create mixins for common functionality:
class TimestampedFactory(Factory[T]):
created_at = fields.DateTimeField()
updated_at = fields.DateTimeField()
class AuditableFactory(Factory[T]):
created_by = fields.TextField("user_name")
updated_by = fields.TextField("user_name")
class ArticleFactory(TimestampedFactory, AuditableFactory):
title = fields.TextField("sentence")
content = fields.TextField("paragraphs", nb=3)
article = ArticleFactory.build()
assert hasattr(article, 'created_at')
assert hasattr(article, 'created_by')
Error Handling
No Model Found
class BadFactory(Factory): # Missing type parameter
name = fields.TextField("name")
try:
BadFactory.get_model()
except TypeError as e:
print(e) # "No concrete model found for BadFactory"
Fix: Always specify the type parameter:
class GoodFactory(Factory[User]):
name = fields.TextField("name")
Multiple Models Found
class UserFactory1(Factory[User]):
pass
class UserFactory2(Factory[User]):
pass
# Ambiguous inheritance
class ConfusedFactory(UserFactory1, UserFactory2):
pass
try:
ConfusedFactory.get_model()
except TypeError as e:
print(e) # "Multiple concrete models found for ConfusedFactory"
Fix: Avoid multiple inheritance from different typed factories, or ensure they share the same model type.
Best Practices
DO:
✅ Always specify the type parameter: Factory[MyModel]
✅ Use descriptive factory names: UserFactory, not UF
✅ Keep factories focused on one model
✅ Override fields in tests when needed
✅ Use factory inheritance for common patterns
DON'T:
❌ Call Factory() without .build() (creates factory instance, not model)
❌ Define fields that aren't in the model
❌ Create circular factory dependencies
❌ Hardcode values that should be randomized
❌ Forget that fields are evaluated at build time
Advanced Usage
Dynamic Factory Creation
def create_factory_for_model(model_class: type[T]) -> type[Factory[T]]:
"""Dynamically create a factory for any model."""
class DynamicFactory(Factory[model_class]):
pass
return DynamicFactory
UserFactory = create_factory_for_model(User)
user = UserFactory.build()
Factory Registration Pattern
FACTORY_REGISTRY: dict[type, type[Factory]] = {}
def register_factory(model_class: type[T], factory_class: type[Factory[T]]):
FACTORY_REGISTRY[model_class] = factory_class
def build_instance(model_class: type[T], **kwargs) -> T:
factory = FACTORY_REGISTRY.get(model_class)
if not factory:
raise ValueError(f"No factory registered for {model_class}")
return factory.build(**kwargs)
# Register factories
register_factory(User, UserFactory)
register_factory(Product, ProductFactory)
# Build instances
user = build_instance(User, name="John")
product = build_instance(Product, price=29.99)
Custom Build Logic
Override .build() for custom behavior:
class ValidatedUserFactory(Factory[User]):
name = fields.TextField("name")
email = fields.TextField("email")
age = fields.IntegerField(min_value=0, max_value=150)
@classmethod
def build(cls, **kwargs) -> User:
instance = super().build(**kwargs)
# Post-build validation
if instance.age < 18:
instance.is_minor = True
else:
instance.is_minor = False
return instance
user = ValidatedUserFactory.build(age=15)
assert user.is_minor is True
Performance Tips
- Reuse factory classes - Don't recreate factories in loops
- Minimize nested factories - Each
FactoryFieldcreates a full object graph - Use ConstantField for static values - Faster than generating random data
- Batch operations - Generate lists with list comprehensions
# ✅ Fast
users = [UserFactory.build() for _ in range(100)]
# ❌ Slower
users = []
for _ in range(100):
users.append(UserFactory.build())
See Also
- Field Reference - All available field types
- Usage Guide - Comprehensive usage examples
- Cookbook - Common patterns and recipes