How to fix factory_boy post-generation deprecation warnings
We use factory_boy for
bootstrapping test data on many Python and Django projects at Caktus.
Recently, we encountered a deprecation warning on an older project that
had been using factory_boy
for some time:
warnings.warn( /usr/local/lib/python3.12/site-packages/factory/django.py:182: DeprecationWarning: MyFactory._after_postgeneration will stop saving the instance after postgeneration hooks in the next major release. If the save call is extraneous, set skip_postgeneration_save=True in the MyFactory.Meta. To keep saving the instance, move the save call to your postgeneration hooks or override [after_postgeneration]{#after_postgeneration}.
We saw the warning because we run tests on CI with
export PYTHONWARNINGS=always
enabled, so we're warned early if we
miss fixing a deprecation issue in Django or another dependency of our
projects.
The fix for this deprecation warning is nicely described in the warning itself. Specifically, one needs to identify post-generation hooks (usually decorated with @factory.post_generation), and update them to explicitly save the instance on their own if a change was made. This is also described in the release notes, but as I found when reviewing my colleague Simon's pull request with this change, it is sometimes easier to understand the fix with sample code.
In our case, we decided to move the save()
call into the
post-generation hook itself, while respecting the create
flag that is
passed into the hook. For example, when creating a book, we can
optionally also create an author and assign it to the author
attribute on the book. This example has been simplified somewhat
from the original, but the essential pattern is like this:
class BookFactory(factory.django.DjangoModelFactory):
class Meta:
model = Book
skip_postgeneration_save = True
@factory.post_generation
def post(instance: Book, create: bool, extracted, **kwargs):
save = False
if not instance.author:
author = kwargs.get("author") or AuthorFactory()
instance.author = author
save = True
if create and save:
instance.save()
In particular, instead of assuming that instance.save()
will be called
for us after the post-generation hook, we need to set
skip_postgeneration_save = True
and call save()
ourselves, but only
when the "create" strategy was used and we made a change to the
factory instance that merits saving to the database. create
will be
False
when using the "build" strategy (BookFactory.build()
), so we
want to ensure that the book is not saved, but that it still has a
valid author. Django will prevent you from assigning unsaved
instances to foreign key fields, so you may need to find another option
if you use the "build" strategy and don't want it to save anything to
the database.
We hope this post will help you find and fix factory_boy post-generation deprecation warnings in your projects, and good luck! Please comment below if you have any questions.