问题:SQLAlchemy是否具有与Django的get_or_create等效的功能?
我想从数据库中获取一个对象(如果已存在)(基于提供的参数),或者如果不存在则创建它。
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
回答 0
基本上就是这样做的方法,没有快捷方式可供使用的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
回答 1
在@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)
回答 2
我一直在解决这个问题,并最终得到了一个相当强大的解决方案:
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陷阱。谢谢你的评论,奈杰尔!如果您对此问题感到好奇,请查看此StackOverflow问题和此博客文章。
回答 3
埃里克出色答案的修改版
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 文档。
回答 4
这个SQLALchemy食谱能很好地完成工作。
首先要做的是定义一个函数,该函数被赋予要使用的Session,并将字典与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()
配方更深入地介绍了这个想法,并提供了不同的方法,但是我已经成功地使用了这一方法。
回答 5
语义上最接近的可能是:
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版本没有连接,所以…
返回的元组包含实例和一个布尔值,指示是否创建了实例(即,如果我们从数据库读取实例,则为False)。
get_or_create
经常使用Django 来确保全局数据可用,因此我会尽早提交。
回答 6
我稍微简化了@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
回答 7
根据您采用的隔离级别,以上解决方案均无效。我发现的最佳解决方案是以下形式的RAW SQL:
INSERT INTO table(f1, f2, unique_f3)
SELECT 'v1', 'v2', 'v3'
WHERE NOT EXISTS (SELECT 1 FROM table WHERE f3 = 'v3')
无论隔离级别和并行度如何,这在事务上都是安全的。
当心:为了使其高效,为唯一列使用INDEX是明智的。