Internals

Behind the scenes: steps performed when parsing a factory declaration, and when calling it.

This section will be based on the following factory declaration:

class UserFactory(factory.Factory):
    class Meta:
        model = User

    class Params:
        # Allow us to quickly enable staff/superuser flags
        superuser = factory.Trait(
            is_superuser=True,
            is_staff=True,
        )
        # Meta parameter handling all 'enabled'-related fields
        enabled = True

    # Classic fields
    username = factory.Faker('user_name')
    full_name = factory.Faker('name')
    creation_date = factory.fuzzy.FuzzyDateTime(
        datetime.datetime(2000, 1, 1, tzinfo=UTC),
        datetime.datetime(2015, 12, 31, 20, tzinfo=UTC)
    )

    # Conditional flags
    is_active = factory.SelfAttribute('enabled')
    deactivation_date = factory.Maybe(
        'enabled',
        None,
        factory.fuzzy.FuzzyDateTime(
#            factory.SelfAttribute('creation_date'),
            datetime.datetime.now().replace(tzinfo=UTC) - datetime.timedelta(days=10),
            datetime.datetime.now().replace(tzinfo=UTC) - datetime.timedelta(days=1),
        ),
    )

    # Related logs
    creation_log = factory.RelatedFactory(
        UserLogFactory, 'user',
        action='create', timestamp=factory.SelfAttribute('user.creation_date'),
    )

Parsing, Step 1: Metaclass and type declaration

  1. Python parses the declaration and calls (thanks to the metaclass declaration):

    factory.base.BaseFactory.__new__(
        'UserFactory',
        (factory.Factory,),
        attributes,
    )
    
  2. That metaclass removes Meta and Params from the class attributes, then generate the actual factory class (according to standard Python rules)

  3. It initializes a FactoryOptions object, and links it to the class

Parsing, Step 2: adapting the class definition

  1. The FactoryOptions reads the options from the class Meta declaration

  2. It finds a few specific pointer (loading the model class, finding the reference factory for the sequence counter, etc.)

  3. It copies declarations and parameters from parent classes

  4. It scans current class attributes (from vars()) to detect pre/post declarations

  5. Declarations are split among pre-declarations and post-declarations (a raw value shadowing a post-declaration is seen as a post-declaration)

Note

A declaration for foo__bar will be converted into parameter bar for declaration foo.

Instantiating, Step 1: Converging entrypoints

First, decide the strategy:

Then, we’ll pass the strategy and passed-in overrides to the _generate() method.

Note

According to the project roadmap, a future version will use a _generate_batch`() at its core instead.

A factory’s _generate() function actually delegates to a StepBuilder() object. This object will carry the overall “build an object” context (strategy, depth, and possibly other).

Instantiating, Step 2: Preparing values

  1. The StepBuilder merges overrides with the class-level declarations

  2. The sequence counter for this instance is initialized

  3. A Resolver is set up with all those declarations, and parses them in order; it will call each value’s evaluate() method, including extra parameters.

  4. If needed, the Resolver might recurse (through the StepBuilder, e.g when encountering a SubFactory.

Instantiating, Step 3: Building the object

  1. The StepBuilder fetches the attributes computed by the Resolver.

  2. It applies renaming/adjustment rules

  3. It passes them to the FactoryOptions.instantiate() method, which forwards to the proper methods.

  4. Post-declaration are applied (in declaration order)

Note

This document discusses implementation details; there is no guarantee that the described methods names and signatures will be kept as is.