CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-factory-boy

A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby

Pending
Overview
Eval results
Files

declarations.mddocs/

Attribute Declarations

Comprehensive declaration types for generating dynamic attribute values in factories. These declarations enable sophisticated test data generation with sequences, lazy evaluation, faker integration, conditional logic, and complex object relationships.

Capabilities

Basic Attribute Declarations

Core declarations for computing attribute values using functions and external data.

class LazyFunction:
    """
    Computed by calling function without arguments each time.
    
    Args:
        function: Callable that returns the attribute value
    """
    def __init__(self, function): ...

class LazyAttribute:
    """
    Computed using function that receives the current instance being built.
    
    Args:
        function: Callable that takes the instance and returns attribute value
    """
    def __init__(self, function): ...

class SelfAttribute:
    """
    Copy values from other fields, supports dot notation and parent access.
    
    Args:
        attribute_name (str): Name of attribute to copy (supports 'field', 'sub.field', '..parent.field')
        default: Default value if attribute not found
    """
    def __init__(self, attribute_name, default=UNSPECIFIED): ...

Usage Examples

class UserFactory(Factory):
    class Meta:
        model = User
    
    # LazyFunction - called fresh each time
    created_at = LazyFunction(lambda: timezone.now())
    uuid = LazyFunction(lambda: str(uuid.uuid4()))
    
    # LazyAttribute - receives the current instance
    email = LazyAttribute(lambda obj: f'{obj.username}@example.com')
    display_name = LazyAttribute(lambda obj: f'{obj.first_name} {obj.last_name}')
    
    # SelfAttribute - copy from other fields
    username = 'john_doe'
    login_name = SelfAttribute('username')
    backup_email = SelfAttribute('email', default='fallback@example.com')

Sequence and Iterator Declarations

Declarations for generating unique, sequential, or cyclic values.

class Sequence:
    """
    Generate increasing unique values using sequence counter.
    
    Args:
        function: Callable that takes sequence number (int) and returns value
    """
    def __init__(self, function): ...

class LazyAttributeSequence:
    """
    Combination of LazyAttribute and Sequence - receives instance and sequence number.
    
    Args:
        function: Callable that takes (instance, sequence_number) and returns value
    """
    def __init__(self, function): ...

class Iterator:
    """
    Fill value using iterator values, with optional cycling and getter function.
    
    Args:
        iterator: Iterable to get values from
        cycle (bool): Whether to cycle through values when exhausted
        getter: Optional function to extract values from iterator items
    """
    def __init__(self, iterator, cycle=True, getter=None): ...
    
    def reset(self):
        """Reset internal iterator to beginning."""

Usage Examples

class UserFactory(Factory):
    class Meta:
        model = User
    
    # Simple sequence
    email = Sequence(lambda n: f'user{n}@example.com')
    
    # LazyAttributeSequence - instance + sequence
    username = LazyAttributeSequence(lambda obj, n: f'{obj.first_name.lower()}_{n}')
    
    # Iterator with cycling
    department = Iterator(['Engineering', 'Sales', 'Marketing'])
    
    # Iterator with getter function
    priority = Iterator([
        {'name': 'High', 'value': 1},
        {'name': 'Medium', 'value': 2}, 
        {'name': 'Low', 'value': 3}
    ], getter=lambda item: item['value'])
    
    # Non-cycling iterator
    one_time_code = Iterator(['ABC123', 'DEF456', 'GHI789'], cycle=False)

Faker Integration

Integration with the Faker library for generating realistic fake data.

class Faker:
    """
    Wrapper for faker library values with locale support and custom providers.
    
    Args:
        provider (str): Faker provider name (e.g., 'name', 'email', 'address')
        locale (str, optional): Locale for this faker instance
        **kwargs: Additional arguments passed to the faker provider
    """
    def __init__(self, provider, locale=None, **kwargs): ...
    
    def generate(self, extra_kwargs=None):
        """Generate fake value with optional extra parameters."""
    
    @classmethod
    def override_default_locale(cls, locale):
        """Context manager for temporarily overriding default locale."""
    
    @classmethod  
    def add_provider(cls, provider, locale=None):
        """Add custom faker provider."""

Usage Examples

class UserFactory(Factory):
    class Meta:
        model = User
    
    # Basic faker usage
    first_name = Faker('first_name')
    last_name = Faker('last_name')
    email = Faker('email')
    
    # Faker with parameters
    birthdate = Faker('date_of_birth', minimum_age=18, maximum_age=65)
    bio = Faker('text', max_nb_chars=200)
    
    # Locale-specific faker
    phone = Faker('phone_number', locale='en_US')
    
    # Using context manager for locale
    with Faker.override_default_locale('fr_FR'):
        address = Faker('address')
        
# Custom provider example
from faker.providers import BaseProvider

class CustomProvider(BaseProvider):
    def department_code(self):
        return self.random_element(['ENG', 'SAL', 'MKT', 'HR'])

Faker.add_provider(CustomProvider)

class EmployeeFactory(Factory):
    dept_code = Faker('department_code')

Container and Complex Declarations

Declarations for working with containers and accessing factory context.

class ContainerAttribute:
    """
    Receives current instance and container chain for complex attribute computation.
    
    Args:
        function: Callable that takes (instance, containers) and returns value
        strict (bool): Whether to enforce strict container access
    """
    def __init__(self, function, strict=True): ...

Usage Examples

class ProfileFactory(Factory):
    class Meta:
        model = Profile
    
    # Access container chain (useful in SubFactory contexts)
    user_id = ContainerAttribute(lambda obj, containers: containers[0].id if containers else None)
    
    # Complex container logic
    role = ContainerAttribute(
        lambda obj, containers: 'admin' if containers and containers[0].is_staff else 'user'
    )

Factory Composition

Declarations for creating related objects and complex data structures.

class SubFactory:
    """
    Create related objects using another factory.
    
    Args:
        factory: Factory class or string path to factory
        **defaults: Default values to pass to the sub-factory
    """
    def __init__(self, factory, **defaults): ...
    
    def get_factory(self):
        """Retrieve the wrapped factory class."""

class Dict:
    """
    Fill dictionary with declarations.
    
    Args:
        params (dict): Dictionary of key-value pairs, values can be declarations
        dict_factory (str): Factory to use for creating dictionary
    """
    def __init__(self, params, dict_factory='factory.DictFactory'): ...

class List:
    """
    Fill list with declarations.
    
    Args:
        params (list): List of values, can include declarations
        list_factory (str): Factory to use for creating list
    """
    def __init__(self, params, list_factory='factory.ListFactory'): ...

Usage Examples

class UserFactory(Factory):
    class Meta:
        model = User
    
    name = Faker('name')
    email = Faker('email')

class PostFactory(Factory):
    class Meta:
        model = Post
    
    title = Faker('sentence')
    
    # SubFactory creates related User
    author = SubFactory(UserFactory, name='Post Author')
    
    # Dict with mixed static and dynamic values
    metadata = Dict({
        'views': 0,
        'created_at': LazyFunction(lambda: timezone.now().isoformat()),
        'tags': List([Faker('word'), Faker('word'), 'default-tag'])
    })
    
    # List of related objects
    comments = List([
        SubFactory('CommentFactory', content='First comment'),
        SubFactory('CommentFactory', content='Second comment')
    ])

Conditional Logic

Declarations for conditional attribute generation based on other values.

class Maybe:
    """
    Conditional declaration based on decider value.
    
    Args:
        decider (str or callable): Field name or function to evaluate condition
        yes_declaration: Declaration to use when condition is truthy
        no_declaration: Declaration to use when condition is falsy (defaults to SKIP)
    """
    def __init__(self, decider, yes_declaration=SKIP, no_declaration=SKIP): ...

class Trait:
    """
    Enable declarations based on boolean flag, used in Params section.
    
    Args:
        **overrides: Attribute overrides to apply when trait is enabled
    """
    def __init__(self, **overrides): ...

Usage Examples

class UserFactory(Factory):
    class Meta:
        model = User
    
    name = Faker('name')
    is_premium = Iterator([True, False])
    
    # Maybe based on field value
    premium_features = Maybe(
        'is_premium',
        yes_declaration=Dict({'advanced_analytics': True, 'priority_support': True}),
        no_declaration=Dict({'basic_features': True})
    )
    
    # Maybe with function decider
    account_type = Maybe(
        lambda obj: obj.is_premium,
        yes_declaration='Premium',
        no_declaration='Basic'
    )

class PostFactory(Factory):
    class Meta:
        model = Post
    
    title = Faker('sentence')
    status = 'draft'
    
    class Params:
        # Trait for published posts
        published = Trait(
            status='published',
            published_at=LazyFunction(lambda: timezone.now()),
            slug=LazyAttribute(lambda obj: slugify(obj.title))
        )

# Usage: PostFactory(published=True) enables the published trait
published_post = PostFactory(published=True)

Post-Generation Declarations

Declarations that execute after object creation for relationships and method calls.

class PostGeneration:
    """
    Call function after object generation.
    
    Args:
        function: Callable that takes (instance, create, extracted, **kwargs)
    """
    def __init__(self, function): ...

class PostGenerationMethodCall:
    """
    Call method on generated object after creation.
    
    Args:
        method_name (str): Name of method to call on the instance
        *args: Positional arguments to pass to method
        **kwargs: Keyword arguments to pass to method
    """
    def __init__(self, method_name, *args, **kwargs): ...

class RelatedFactory:
    """
    Create related object after main object generation.
    
    Args:
        factory: Factory class to use for creating related object
        factory_related_name (str): Attribute name on related object to set main object
        **defaults: Default values for related object factory
    """
    def __init__(self, factory, factory_related_name='', **defaults): ...

class RelatedFactoryList:
    """
    Create multiple related objects after main object generation.
    
    Args:
        factory: Factory class to use for creating related objects
        factory_related_name (str): Attribute name on related objects to set main object
        size (int): Number of related objects to create
        **defaults: Default values for related object factory
    """
    def __init__(self, factory, factory_related_name='', size=2, **defaults): ...

Usage Examples

class UserFactory(Factory):
    class Meta:
        model = User
    
    username = Faker('user_name')
    email = Faker('email')
    
    # PostGeneration for custom setup
    setup_profile = PostGeneration(
        lambda obj, create, extracted, **kwargs: obj.create_default_profile() if create else None
    )
    
    # PostGenerationMethodCall for method execution
    set_password = PostGenerationMethodCall('set_password', 'default_password')
    
    # RelatedFactory creates profile after user
    profile = RelatedFactory('ProfileFactory', 'user')

class PostFactory(Factory):
    class Meta:
        model = Post
    
    title = Faker('sentence')
    author = SubFactory(UserFactory)
    
    # RelatedFactoryList creates multiple comments
    comments = RelatedFactoryList(
        'CommentFactory', 
        'post', 
        size=3,
        author=SubFactory(UserFactory)
    )

# Usage with extracted values
user = UserFactory(set_password__password='custom_password')

Special Values and Constants

# Special marker for skipping fields
SKIP = object()

# Unspecified default marker
UNSPECIFIED = object()

Declaration Wrapper Functions

Functional-style wrappers for creating declarations:

def lazy_attribute(func):
    """Wrap function as LazyAttribute declaration."""

def sequence(func): 
    """Wrap function as Sequence declaration."""

def lazy_attribute_sequence(func):
    """Wrap function as LazyAttributeSequence declaration."""

def container_attribute(func):
    """Wrap function as ContainerAttribute declaration (non-strict)."""

def post_generation(func):
    """Wrap function as PostGeneration declaration."""

Usage Examples

# Using wrapper functions
@lazy_attribute
def full_name(obj):
    return f'{obj.first_name} {obj.last_name}'

@sequence  
def email(n):
    return f'user{n}@example.com'

@post_generation
def send_welcome_email(obj, create, extracted, **kwargs):
    if create:
        obj.send_welcome_email()

class UserFactory(Factory):
    class Meta:
        model = User
    
    first_name = Faker('first_name')
    last_name = Faker('last_name')
    full_name = full_name
    email = email
    send_welcome_email = send_welcome_email

Install with Tessl CLI

npx tessl i tessl/pypi-factory-boy

docs

core-factories.md

declarations.md

helpers-and-utilities.md

index.md

orm-integration.md

tile.json