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.