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( 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¶
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 entrypoints¶
First, decide the strategy:
- If the entrypoint 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 roadmap, 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.