Does SQLAlchemy have an equivalent of Django's get_or_create?
That's basically the way to do it, there is no shortcut readily available AFAIK.
You could generalize it ofcourse:
def get_or_create(session, model, defaults=None, **kwargs):
instance = session.query(model).filter_by(**kwargs).one_or_none()
if instance:
return instance, False
else:
params = {k: v for k, v in kwargs.items() if not isinstance(v, ClauseElement)}
params.update(defaults or {})
instance = model(**params)
try:
session.add(instance)
session.commit()
except Exception: # The actual exception depends on the specific database so we catch all exceptions. This is similar to the official documentation: https://docs.sqlalchemy.org/en/latest/orm/session_transaction.html
session.rollback()
instance = session.query(model).filter_by(**kwargs).one()
return instance, False
else:
return instance, True
2020 update (Python 3.9+ ONLY)
Here is a cleaner version with Python 3.9's the new dict union operator (|=)
def get_or_create(session, model, defaults=None, **kwargs):
instance = session.query(model).filter_by(**kwargs).one_or_none()
if instance:
return instance, False
else:
kwargs |= defaults or {}
instance = model(**kwargs)
try:
session.add(instance)
session.commit()
except Exception: # The actual exception depends on the specific database so we catch all exceptions. This is similar to the official documentation: https://docs.sqlalchemy.org/en/latest/orm/session_transaction.html
session.rollback()
instance = session.query(model).filter_by(**kwargs).one()
return instance, False
else:
return instance, True
Note:
Similar to the Django version this will catch duplicate key constraints and similar errors. If your get or create is not guaranteed to return a single result it can still result in race conditions.
To alleviate some of that issue you would need to add another one_or_none()
style fetch right after the session.commit()
. This still is no 100% guarantee against race conditions unless you also use a with_for_update()
or serializable transaction mode.
SQLAlchemy equivalent to Django's x.objects.extra()?
I think you're looking for Query.from_statement
: http://www.sqlalchemy.org/docs/orm/query.html#sqlalchemy.orm.query.Query.from_statement
How to make a get_or_create using OR with SQLAlchemy?
Your problem isn't that or_
doesn't expect keyword arguments, It's that keyword arguments aren't a sensical thing to pass it in the first place. filter()
, and_()
and or_()
all expect one or more BinaryExpression
s that define the composite expression (in fact filter()
simply wraps its own *args
in a and_(*args)
to allow for the implicit default and'ing). Your method signature should be:
def get_or_create_or(cls, session, *args):
instance = session.query(cls).filter(or_(*args)).first()
# rest of stuff
And it should be called like:
>>> get_or_create_or(MyClass, session, MyClass.attribute == "foo", MyClass.attribute_other > 2)
Your confusion may come from having used filter_by
, which implicitly only allows for equality comparisons and thus using keyword arguments makes sense. Sadly, there is no OR'ing version of filter_by
, for whatever reason. You could fake it via the following:
def or_filter_by_conditions(cls, **kwargs):
return or_(getattr(cls, name) == value for name, value in kwargs.iteritems()) # python2
return or_(getattr(cls, name) == value for name, value in kwargs.items()) # python3
Which turns your original method into:
def get_or_create_or(cls, session, **kwargs):
instance = session.query(cls).filter(or_filter_by_conditions(**kwargs)).first()
# rest
Though obviously you loose much expressive power as now you can only do equality comparisons for your filtering conditions. Of course if that's what you were doing anyway (which for an upsert might be reasonable), then that's not a big deal.
Having said that, in general, I recommend you have a read through why upsert is so complicated, because there are many potential huge gotcha's in the way you're trying to do this beyond the simple need to get your query conditions correct. This will only do what you expect if there is only ever a single user, working in a single session, ever connected to your database at a time.
SQLAlquemy: INSERT only if specific item(s) dont exist
You can make the fact column unique across all rows by using a unique constraint, ie. unique=True
. Then just pass the datetime
columns separately on create only:
class Facts(Base):
__tablename__ = "facts"
id = sqlColumn(Integer, primary_key=True)
# Makes sure there are no duplicate facts on the database level
fact = sqlColumn(String(500), unique=True)
created_at = sqlColumn(DateTime)
updated_at = sqlColumn(DateTime)
def get_or_create(session, model, created_at, **unique_kwargs):
instance = session.query(model).filter_by(**unique_kwargs).one_or_none()
if instance:
return instance
else:
instance = model(created_at=created_at, updated_at=created_at, **unique_kwargs)
session.add(instance)
return instance
You could use this like:
result = get_or_create(session, Facts, datetime.now(), fact='A new fact')
The drawback of this approach is that one thread using a different session could create a fact at the same time another thread was creating the same thread and the code would fail on commit. The database integrity would be preserved with the constraint. You could remedy this by catching the intregity exception and trying get_or_create again. If you are using postgresql you should do an upsert, explained here: Upsert Documentation
Here is the SQLAlchemy documentation for creating unique constraints:
Unique Constraints
get_or_create object created or found?
You can take a look get_or_create()
. An example from official documentation;
obj, created = Person.objects.get_or_create(
first_name='John',
last_name='Lennon',
defaults={'birthday': date(1940, 10, 9)},
)
If an object is found, get_or_create() returns a tuple of that object and False.
Then check created
variable;
In [15]: created
Out[15]: False
# so, `get_or_create()` didn't create an object.
Is there something equivalent to django's managers in SQLAlchemy?
For common queries I add a class method to a mapped (ORM) class.
For example:
class User(object):
@classmethod
def get_by_username(cls, dbsession, username):
return dbsession.query(cls).filter(cls.username==username).one()
The mapped class is essentially the manager.
Related Topics
How to Dereference Variable Id'S
Pandas Add Column to Groupby Dataframe
How to Make Ball Bounce Off Wall with Pygame
How to Pass a List as a Command-Line Argument with Argparse
Python Requests. 403 Forbidden
How Is the 'Is' Keyword Implemented in Python
How to Postpone/Defer the Evaluation of F-Strings
When Does Python Allocate New Memory for Identical Strings
How to Define a Threshold Value to Detect Only Green Colour Objects in an Image with Python Opencv
"Getaddrinfo Failed", What Does That Mean
How to Install and Import Python Modules at Runtime
Detect New or Modified Files with Python
Keyboard Interrupts with Python's Multiprocessing Pool
Basic Python Hello World Program Syntax Error
Python MySQLdb Typeerror: Not All Arguments Converted During String Formatting