Имеет ли SQLAlchemy эквивалент Django get_or_create?
Я хочу получить объект из базы данных, если он уже существует (на основе предоставленных параметров) или создать его, если это не так.
Django get_or_create
(или источник) это. Есть ли эквивалентный ярлык в SQLAlchemy?
В настоящее время я пишу это явно:
def get_or_create_instrument(session, serial_number):
instrument = session.query(Instrument).filter_by(serial_number=serial_number).first()
if instrument:
return instrument
else:
instrument = Instrument(serial_number)
session.add(instrument)
return instrument
Ответы
Ответ 1
Что в основном способ сделать это, нет ярлыка, легко доступного AFAIK.
Вы можете его обобщить:
def get_or_create(session, model, defaults=None, **kwargs):
instance = session.query(model).filter_by(**kwargs).first()
if instance:
return instance, False
else:
params = dict((k, v) for k, v in kwargs.iteritems() if not isinstance(v, ClauseElement))
params.update(defaults or {})
instance = model(**params)
session.add(instance)
return instance, True
Ответ 2
Следуя решению @WoLpH, это код, который работал у меня (простая версия):
def get_or_create(session, model, **kwargs):
instance = session.query(model).filter_by(**kwargs).first()
if instance:
return instance
else:
instance = model(**kwargs)
session.add(instance)
session.commit()
return instance
С этим я могу get_or_create любой объект моей модели.
Предположим, что мой объект модели:
class Country(Base):
__tablename__ = 'countries'
id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
Чтобы создать или создать свой объект, пишу:
myCountry = get_or_create(session, Country, name=countryName)
Ответ 3
Я играл с этой проблемой и получил довольно надежное решение:
def get_one_or_create(session,
model,
create_method='',
create_method_kwargs=None,
**kwargs):
try:
return session.query(model).filter_by(**kwargs).one(), False
except NoResultFound:
kwargs.update(create_method_kwargs or {})
created = getattr(model, create_method, model)(**kwargs)
try:
session.add(created)
session.flush()
return created, True
except IntegrityError:
session.rollback()
return session.query(model).filter_by(**kwargs).one(), True
Я только что написал довольно экспансивный пост в блоге по всем деталям, но несколько довольно соображений, почему я использовал это.
-
Он распаковывается в кортеж, который сообщает вам, существует ли объект или нет. Это часто может быть полезно в вашем рабочем процессе.
-
Функция дает возможность работать с @classmethod
украшенными функциями создателя (и атрибутами, специфичными для них).
-
Решение защищает от условий гонки, когда у вас есть несколько процессов, связанных с хранилищем данных.
EDIT: я изменил session.commit()
на session.flush()
, как описано в в этом сообщении в блоге. Обратите внимание, что эти решения специфичны для используемого хранилища данных (Postgres в этом случае).
EDIT 2: Ive обновлен, используя {} в качестве значения по умолчанию в функции, поскольку это типичный Python. Спасибо за комментарий, Найджел! Если вам интересно об этом, прочтите fooobar.com/questions/15466/... и этот пост в блоге.
Ответ 4
Думаю, я просто искал то же самое. Этот рецепт SQLALchemy делает работу приятной и элегантной.
Ответ 5
Измененная версия erik отлично answer
def get_one_or_create(session,
model,
create_method='',
create_method_kwargs=None,
**kwargs):
try:
return session.query(model).filter_by(**kwargs).one(), True
except NoResultFound:
kwargs.update(create_method_kwargs or {})
try:
with session.begin_nested():
created = getattr(model, create_method, model)(**kwargs)
session.add(created)
return created, False
except IntegrityError:
return session.query(model).filter_by(**kwargs).one(), True
- Используйте вложенную транзакцию, чтобы только откатить добавление нового элемента вместо того, чтобы откатывать все (см. это answer для использования вложенных транзакций с SQLite)
- Переместить
create_method
. Если созданный объект имеет отношения и ему присваиваются члены через эти отношения, он автоматически добавляется к сеансу. Например. создайте book
, который имеет user_id
и user
в качестве соответствующего отношения, а затем book.user=<user object>
внутри create_method
добавит book
к сеансу. Это означает, что create_method
должен находиться внутри with
, чтобы извлечь выгоду из возможного откат. Обратите внимание, что begin_nested
автоматически запускает флеш.
Обратите внимание, что при использовании MySQL уровень изоляции транзакции должен быть установлен READ COMMITTED
, а не REPEATABLE READ
, чтобы это работало. Django get_or_create (и здесь) использует ту же стратагему, см. также документацию Django .
Ответ 6
Скорее всего семантически возможно:
def get_or_create(model, **kwargs):
"""SqlAlchemy implementation of Django get_or_create.
"""
session = Session()
instance = session.query(model).filter_by(**kwargs).first()
if instance:
return instance, False
else:
instance = model(**kwargs)
session.add(instance)
session.commit()
return instance, True
не уверен, как кошерно полагаться на глобально определенный Session
в sqlalchemy, но версия Django не принимает соединение, поэтому...
Возвращенный кортеж содержит экземпляр и логическое значение, указывающее, был ли экземпляр создан (т.е. он False, если мы читаем экземпляр из db).
Django get_or_create
часто используется для обеспечения доступности глобальных данных, поэтому я беру на себя как можно скорее.
Ответ 7
В зависимости от уровня изоляции, который вы приняли, ни одно из вышеперечисленных решений не будет работать.
Лучшим решением, которое я нашел, является RAW SQL в следующей форме:
INSERT INTO table(f1, f2, unique_f3)
SELECT 'v1', 'v2', 'v3'
WHERE NOT EXISTS (SELECT 1 FROM table WHERE f3 = 'v3')
Это безопасно для транзакций независимо от уровня изоляции и степени parallelism.
Остерегайтесь: чтобы сделать его эффективным, было бы разумно иметь ИНДЕКС для уникального столбца.
Ответ 8
Я слегка упростил @Kevin. чтобы избежать обертывания всей функции в инструкции if
/else
. Таким образом, существует только один return
, который я нахожу более чистым:
def get_or_create(session, model, **kwargs):
instance = session.query(model).filter_by(**kwargs).first()
if not instance:
instance = model(**kwargs)
session.add(instance)
return instance