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
Python parses the declaration and calls (thanks to the metaclass declaration):
factory.base.BaseFactory.__new__( 'UserFactory', (factory.Factory,), attributes, )
That metaclass removes
Meta
andParams
from the class attributes, then generate the actual factory class (according to standard Python rules)It initializes a
FactoryOptions
object, and links it to the class
Parsing, Step 2: adapting the class definition
The
FactoryOptions
reads the options from theclass Meta
declarationIt 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 declarationsDeclarations 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:
If the entrypoint is specific to a strategy (
build()
,create_batch()
, …), use itIf it is generic (
generate()
,Factory.__call__()
), use the strategy defined at theclass Meta
level
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
The
StepBuilder
merges overrides with the class-level declarationsThe sequence counter for this instance is initialized
A
Resolver
is set up with all those declarations, and parses them in order; it will call each value’sevaluate()
method, including extra parameters.If needed, the
Resolver
might recurse (through theStepBuilder
, e.g when encountering aSubFactory
.
Instantiating, Step 3: Building the object
The
StepBuilder
fetches the attributes computed by theResolver
.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)
Note
This document discusses implementation details; there is no guarantee that the described methods names and signatures will be kept as is.