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=datetime.timezone.utc), datetime.datetime(2015, 12, 31, 20, tzinfo=datetime.timezone.utc) ) # Conditional flags is_active = factory.SelfAttribute('enabled') deactivation_date = factory.Maybe( 'enabled', None, factory.fuzzy.FuzzyDateTime( datetime.datetime.now().replace(tzinfo=datetime.timezone.utc) - datetime.timedelta(days=10), datetime.datetime.now().replace(tzinfo=datetime.timezone.utc) - datetime.timedelta(days=1), ), ) # Related logs creation_log = factory.RelatedFactory( UserLogFactory, factory_related_name='user', action='create', timestamp=factory.SelfAttribute('user.creation_date'), )
Parsing, Step 1: Metaclass and type declaration¶
Python parses the declaration and calls (thanks to the metaclass declaration):
factory.base.BaseFactory.__new__( 'UserFactory', (factory.Factory,), attributes, )
That metaclass removes
Paramsfrom the class attributes, then generate the actual factory class (according to standard Python rules)
It initializes a
FactoryOptionsobject, and links it to the class
Parsing, Step 2: adapting the class definition¶
FactoryOptionsreads the options from the
It finds a few specific pointer (loading the model class, finding the reference factory for the sequence counter, etc.)
It copies declarations and parameters from parent classes
It scans current class attributes (from
vars()) to detect pre/post declarations
Declarations are split among pre-declarations and post-declarations (a raw value shadowing a post-declaration is seen as a post-declaration)
A declaration for
foo__bar will be converted into parameter
Instantiating, Step 1: Converging entry points¶
First, decide the strategy:
If the entry point is specific to a strategy (
create_batch(), …), use it
If it is generic (
Factory.__call__()), use the strategy defined at the
Then, we’ll pass the strategy and passed-in overrides to the
According to the project road map, a future version will use a
_generate_batch`() at its core instead.
_generate() function actually delegates to a
This object will carry the overall “build an object” context (strategy, depth, and possibly other).
Instantiating, Step 2: Preparing values¶
StepBuildermerges overrides with the class-level declarations
The sequence counter for this instance is initialized
Resolveris set up with all those declarations, and parses them in order; it will call each value’s
evaluate()method, including extra parameters.
If needed, the
Resolvermight recurse (through the
StepBuilder, e.g when encountering a
Instantiating, Step 3: Building the object¶
StepBuilderfetches the attributes computed by the
It applies renaming/adjustment rules
It passes them to the
FactoryOptions.instantiate()method, which forwards to the proper methods.
Post-declaration are applied (in declaration order)
This document discusses implementation details; there is no guarantee that the described methods names and signatures will be kept as is.