Имеет ли 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