이미 존재하는 경우 (제공된 매개 변수를 기반으로) 데이터베이스에서 객체를 가져 오거나 그렇지 않은 경우 생성하고 싶습니다.
장고 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
나는 모든 세부 사항에 대해 상당히 광범위한 블로그 게시물 을 썼지 만 왜 이것을 사용했는지에 대한 몇 가지 아이디어를 썼습니다 .
-
객체가 존재하는지 여부를 알려주는 튜플에 압축을 풉니 다. 이는 종종 워크 플로에서 유용 할 수 있습니다.
-
이 기능은
@classmethod
데코 레이팅 된 크리에이터 기능 (및 특정 속성) 으로 작업 할 수있는 기능을 제공합니다 . -
이 솔루션은 데이터 스토어에 둘 이상의 프로세스가 연결된 경우 경합 조건으로부터 보호합니다.
편집 : 변경 한 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
전 세계적으로 정의 된 Session
sqlalchemy에서 하지 않지만 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