pythonsqlalchemyfactory-boy

Avoiding duplicates with factory_boy factories


I'm using factory_boy to create test fixtures. I've got two simple factories, backed by SQLAlchemy models (simplified below).

I'd like to be able to call AddressFactory.create() multiple times, and have it create a Country if it doesn't already exist, otherwise I want it to re-use the existing record.

class CountryFactory(factory.Factory):
    FACTORY_FOR = Country

    cc = "US"
    name = "United States"


class AddressFactory(factory.Factory):
    FACTORY_FOR = Address

    name = "Joe User"
    city = "Seven Mile Beach"
    country = factory.SubFactory(CountryFactory, cc="KY", name="Cayman Islands")

My question is: how can I set up these factories so that factory_boy doesn't try to create a new Country every time it creates an Address?


Solution

  • Since version 3.0.0, SQLAlchemy factories support the sqlalchemy_get_or_create option. As the documentation says, "Fields whose name are passed in this list will be used to perform a Model.query.one_or_none() or the usual Session.add()".

    Using the example from the docs:

    class UserFactory(factory.alchemy.SQLAlchemyModelFactory):
        class Meta:
            model = User
            sqlalchemy_session = session
            sqlalchemy_get_or_create = ('username',)
    
        username = 'john'
    
    >>> User.query.all()
    []
    >>> UserFactory()                   # Creates a new user
    <User: john>
    >>> User.query.all()
    [<User: john>]
    
    >>> UserFactory()                   # Fetches the existing user
    <User: john>
    >>> User.query.all()                # No new user!
    [<User: john>]
    
    >>> UserFactory(username='jack')    # Creates another user
    <User: jack>
    >>> User.query.all()
    [<User: john>, <User: jack>]
    

    Take into consideration that when sqlalchemy_get_or_create is used, any new values passed to the factory are NOT used to update an existing model.