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.

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.

New Call-to-action
blog comments powered by Disqus
Times
Check

Success!

Times

You're already subscribed

Times