Как избежать дубликатов с фабриками factory_boy

Я использую factory_boy для создания тестовых приборов. У меня есть две простые фабрики, поддерживаемые моделями SQLAlchemy (упрощенно ниже).

Я хотел бы иметь возможность позвонить AddressFactory.create() несколько раз, и пусть это создаст Country если он еще не существует, в противном случае я хочу повторно использовать существующую запись.

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")

У меня вопрос: как я могу настроить эти фабрики, чтобы factory_boy не пытался создавать новую страну каждый раз, когда создает адрес?

3 ответа

В последней версии фабрики ==2.3.1 вы можете добавить FACTORY_DJANGO_GET_OR_CREATE

class CountryFactory(factory.django.DjangoModelFactory):
    FACTORY_FOR = 'appname.Country'
    FACTORY_DJANGO_GET_OR_CREATE = ('cc',)

    cc = "US"
    name = "United States"

Предполагая, что поле cc является уникальным идентификатором.

Пока ты прав, что нет get_or_create функция для фабрик, основанных на SQLAlchemy, если объекты, которые вы хотите использовать в качестве внешнего ключа, уже существуют, вы можете выполнить итерации по ним:

http://factoryboy.readthedocs.org/en/latest/recipes.html

Поэтому вполне возможно, что вы могли бы взломать решение на своей фабрике, используя ленивый атрибут, который сначала проверяет, существует ли объект в БД, и если это так, он использует этот метод для итерации по ним, но если объект не существует, он вызывает SubFactory сначала создать объект.

Для SqlAlchemy вы можете попробовать это. Это тоже фабрика cahce:

class StaticFactory(factory.alchemy.SQLAlchemyModelFactory):):
    __static_exclude = ('__static_exclude', '__static_cache',)
    __static_cache = {}

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        """Helper for avoid duplicate factory"""

        # Exclude static cache
        cls._meta.exclude += cls.__static_exclude

        _unique_key = None

        # Get first unique keys from table. I'll be cache key.
        for col in model_class.__table__.columns:
            if any([col.primary_key, col.unique]):
                _unique_key = kwargs.get(col.name)
                if _unique_key:
                    break

        _instance = cls.__static_cache.get(_unique_key)
        if _instance:
            return _instance

        _session = cls._meta.sqlalchemy_session
        with _session.no_autoflush:
            obj = model_class(*args, **kwargs)
            _session.add(obj)
            cls.__static_cache.update({_unique_key: obj})
            return obj

class LanguageFactory(StaticFactory):
    class Meta:
        model = Language
        exclude = ('lang',)

Мы можем создать новый экземпляр адреса с уже существующим экземпляром страны, используя метод factory.Iterator

import factory, factory.django
from . import models


class CountryFactory(factory.Factory.DjangoModelFactory):
    model = models.Country

    cc = "US"
    name = "United States"

class AddressFactory(factory.Factory.DjangoModelFactory):
    model = models.Address

    name = "Joe User"
    city = "Seven Mile Beach"
    country = factory.Iterator(models.Country.objects.all())

Здесь мы получили доступ к экземплярам Country из базы данных и передали их в атрибут страны AddressFactory, который создает экземпляр адреса с уже созданным экземпляром страны в базе данных.

Еще одно хакерское решение - переписать create метод фабрики таким образом, что объект ищется путем запроса и кэширования результатов.

Этот простой пример не фильтрует **kwargs хоть:

class StaticFactory(SQLAlchemyModelFactory):                        

    counter = 0                                                     
    cache = []                                                      
    model = None                                                    

    @classmethod                                                    
    def create(cls, **kwargs):                                      
        if not cls.cache:                                           
            cls.cache = your_session.query(cls.model).all()     
        instance = cls.cache[cls.counter]                           
        cls.counter = (cls.counter + 1) % len(cls.cache)            
        return instance                                             
Другие вопросы по тегам