既に存在する場合はデータベースからオブジェクトを取得します(提供されたパラメーターに基づいて)。存在しない場合は作成します。
Djangoの get_or_create
(または source )がこれを行います。 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
それは基本的にそれを行う方法です、すぐに入手できるショートカットはありません。
もちろんそれを一般化できます:
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
@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):
__table= '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
_
私はすべての詳細について かなり広大なブログ投稿 を書きましたが、これを使用した理由のいくつかのかなりのアイデアがあります。
オブジェクトが存在するかどうかを知らせるTupleに展開します。これは多くの場合、ワークフローで役立ちます。
この関数は、_@classmethod
_で装飾された作成者関数(およびそれらに固有の属性)を操作する機能を提供します。
このソリューションは、データストアに複数のプロセスが接続されている場合に競合状態から保護します。
編集: このブログ投稿 で説明されているように、session.commit()
をsession.flush()
に変更しました。これらの決定は、使用するデータストア(この場合はPostgres)に固有であることに注意してください。
編集2:これは典型的なPythonの落とし穴であるため、関数のデフォルト値として{}を使用して更新しました。 コメント 、ナイジェルに感謝します!この落とし穴に興味がある場合は、 このStackOverflowの質問 と このブログ投稿 。
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
create_method
を移動します。作成されたオブジェクトにリレーションがあり、それらのリレーションを介してメンバーが割り当てられている場合、それは自動的にセッションに追加されます。例えば。対応する関係としてuser_id
とbook
を持つuser
を作成し、book.user=<user object>
内でcreate_method
を実行すると、book
がセッションに追加されます。これは、create_method
がwith
内になければならないことを意味します。 begin_nested
は自動的にフラッシュをトリガーすることに注意してください。MySQLを使用する場合、これを機能させるには、トランザクション分離レベルをREAD COMMITTED
ではなくREPEATABLE READ
に設定する必要があることに注意してください。 Djangoの get_or_create (および here )は同じ戦略を使用しています。Django documentation も参照してください。
このSQLALchemyのレシピ は、素敵でエレガントな仕事をします。
最初に行うことは、作業するセッションを指定し、現在のuniqueキーを追跡する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
この機能を利用する例は、ミックスインにあります。
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):
__table= '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
sqlalchemyでグローバルに定義されたSession
に依存するのがどれほど適切かはわかりませんが、Djangoバージョンは接続を取得しません...
返されるTupleには、インスタンスと、インスタンスが作成されたかどうかを示すブール値が含まれます(つまり、dbからインスタンスを読み取る場合はFalseです)。
Djangoのget_or_create
は、グローバルデータが利用可能であることを確認するためによく使用されるため、可能な限り早い段階でコミットしています。
採用した分離レベルに応じて、上記のソリューションはどれも機能しません。私が見つけた最良の解決策は、次の形式のRAW SQLです。
INSERT INTO table(f1, f2, unique_f3)
SELECT 'v1', 'v2', 'v3'
WHERE NOT EXISTS (SELECT 1 FROM table WHERE f3 = 'v3')
これは、分離レベルと並列度に関係なく、トランザクション的に安全です。
注意:効率的にするためには、一意の列にINDEXを設定するのが賢明です。
@Kevinを少し単純化しました。 if
/else
ステートメントで関数全体をラップしないようにするソリューション。この方法では、return
が1つしかありません。
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