[python] SQLAlchemy에는 Django의 get_or_create와 동등한 기능이 있습니까?

이미 존재하는 경우 (제공된 매개 변수를 기반으로) 데이터베이스에서 객체를 가져 오거나 그렇지 않은 경우 생성하고 싶습니다.

장고 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



답변

이것이 기본적으로 수행하는 방법이며 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

2020 업데이트

다음은 Python 3.9의 새로운 dict 공용체 연산자 (| =) 가 포함 된 더 깨끗한 버전입니다.

def get_or_create(session, Model, defaults=None, **kwargs):
    instance = session.query(Model).filter_by(**kwargs).first()
    if instance:
        return instance
    else:
        kwargs |= defaults or {}
        instance = Model(**kwargs)
        session.add(instance)
        return instance


답변

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


답변

나는이 문제를 가지고 놀았고 상당히 강력한 솔루션으로 끝났습니다.

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(), False

나는 모든 세부 사항에 대해 상당히 광범위한 블로그 게시물 을 썼지 만 왜 이것을 사용했는지에 대한 몇 가지 아이디어를 썼습니다 .

  1. 객체가 존재하는지 여부를 알려주는 튜플에 압축을 풉니 다. 이는 종종 워크 플로에서 유용 할 수 있습니다.

  2. 이 기능은 @classmethod데코 레이팅 된 크리에이터 기능 (및 특정 속성) 으로 작업 할 수있는 기능을 제공합니다 .

  3. 이 솔루션은 데이터 스토어에 둘 이상의 프로세스가 연결된 경우 경합 조건으로부터 보호합니다.

편집 : 변경 한 session.commit()session.flush()에 설명 된대로 이 블로그 게시물 . 이러한 결정은 사용 된 데이터 저장소 (이 경우 Postgres)에 따라 다릅니다.

편집 2 : 나는 이것이 전형적인 Python gotcha이므로 함수의 기본값으로 {}를 사용하여 업데이트했습니다. 댓글 주셔서 감사합니다 , 나이젤! 이 잡았다 대한 호기심 경우, 체크 아웃 이 StackOverflow의 질문이 블로그 게시물을 .


답변

에릭의 탁월한 답변 수정 버전

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
  • 중첩 된 트랜잭션 을 사용하여 모든 항목을 롤백하는 대신 새 항목 추가 만 롤백합니다 (이 답변 참조 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 문서 도 참조하세요 .


답변

이 SQLALchemy 레시피 는 훌륭하고 우아한 작업을 수행합니다.

가장 먼저해야 할 일은 작업 할 세션이 제공되는 함수를 정의하고 현재 고유 키 를 추적하는 Session ()과 사전을 연결하는 것 입니다.

def _unique(session, cls, hashfunc, queryfunc, constructor, arg, kw):
    cache = getattr(session, '_unique_cache', None)
    if cache is None:
        session._unique_cache = cache = {}

    key = (cls, hashfunc(*arg, **kw))
    if key in cache:
        return cache[key]
    else:
        with session.no_autoflush:
            q = session.query(cls)
            q = queryfunc(q, *arg, **kw)
            obj = q.first()
            if not obj:
                obj = constructor(*arg, **kw)
                session.add(obj)
        cache[key] = obj
        return obj

이 기능을 활용하는 예는 mixin에 있습니다.

class UniqueMixin(object):
    @classmethod
    def unique_hash(cls, *arg, **kw):
        raise NotImplementedError()

    @classmethod
    def unique_filter(cls, query, *arg, **kw):
        raise NotImplementedError()

    @classmethod
    def as_unique(cls, session, *arg, **kw):
        return _unique(
                    session,
                    cls,
                    cls.unique_hash,
                    cls.unique_filter,
                    cls,
                    arg, kw
            )

마지막으로 고유 한 get_or_create 모델을 만듭니다.

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

engine = create_engine('sqlite://', echo=True)

Session = sessionmaker(bind=engine)

class Widget(UniqueMixin, Base):
    __tablename__ = 'widget'

    id = Column(Integer, primary_key=True)
    name = Column(String, unique=True, nullable=False)

    @classmethod
    def unique_hash(cls, name):
        return name

    @classmethod
    def unique_filter(cls, query, name):
        return query.filter(Widget.name == name)

Base.metadata.create_all(engine)

session = Session()

w1, w2, w3 = Widget.as_unique(session, name='w1'), \
                Widget.as_unique(session, name='w2'), \
                Widget.as_unique(session, name='w3')
w1b = Widget.as_unique(session, name='w1')

assert w1 is w1b
assert w2 is not w3
assert w2 is not w1

session.commit()

레시피는 아이디어에 더 깊이 들어가고 다양한 접근 방식을 제공하지만이 방법을 성공적으로 사용했습니다.


답변

가장 가까운 의미는 다음과 같습니다.

def get_or_create(model, **kwargs):
    """SqlAlchemy implementation of Django's 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

전 세계적으로 정의 된 Sessionsqlalchemy에서 하지 않지만 Django 버전은 연결을 취하지 않습니다.

반환 된 튜플은 인스턴스와 인스턴스가 생성되었는지 여부를 나타내는 부울을 포함합니다 (즉, db에서 인스턴스를 읽으면 False입니다).

Django get_or_create는 전역 데이터를 사용할 수 있는지 확인하는 데 자주 사용되므로 가능한 한 빠른 시점에 커밋하고 있습니다.


답변

@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