Как избежать дубликатов с фабриками 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