Избегайте дубликатов с завода 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 не пытался создавать новую страну каждый раз, когда создает адрес?

Ответ 1

В последнем factory -boy == 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 является уникальным идентификатором.

Ответ 2

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

http://factoryboy.readthedocs.org/en/latest/recipes.html#choosing-from-a-populated-table

Таким образом, вы могли бы взломать решение в своем factory с помощью ленивого атрибута, который сначала проверяет, существует ли объект в db, и если он использует этот метод для итерации через них, но если объект doesn ' t существует, он вызывает SubFactory для создания объекта в первую очередь.

Ответ 3

Другим хакерским решением является перезаписать метод create factory таким образом, чтобы объект искавался путем запроса и кеширования результатов.

Этот простой пример не фильтрует на **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                                             

Ответ 4

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

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',)