On Feb 15, 2013, at 5:23 AM, Moritz Schlarb <[email protected]> wrote:
> Hi there,
>
> I'd like to be able to run sorted() on a list of model objects without
> class-specific code which sorts based on what's specified in
> __mapper_args__['order_by'].
>
> To do that, I would add __lt__(), __le__(), __gt__(), and/or __ge__() to the
> declarative base class, which try to get the order_by specification from the
> mapper and behave accordingly.
>
> My problem is now to find the correct programmatical way to get the class
> attribute name and whether to sort reversed or not.
>
> With instance.__mapper__.order_by, I can get the list of expressions, from
> which I can extract the attribute name, if it's just a plain attribute. But
> when its order_by=[desc(column)], I only get an
> sqlalchemy.sql.expression._UnaryExpression from which I can't figure out the
> correct way to get what I need (....elements would contain the column, but I
> can't find the place where I see that it's descending order).
>
> Can someone help me on this?
Note that order_by can have any kind of SQL expression in it, so here we are
composing a limited solution that only accommodates straight columns with at
best an asc() or desc() modifier.
A fairly neutral way to locate a Unary and a Column is to use
visitors.traverse(), though you could just use isinstance() checks here.
from sqlalchemy.sql import visitors, operators
from sqlalchemy.orm import object_mapper
from sqlalchemy import util
class Sortable(object):
@util.memoized_property
def _order_by_comparator(self):
def _gen_getter(expr):
getter = [None]
direction = [False]
def set_direction(unary_expr):
if unary_expr.operator is operators.desc_op:
direction[0] = True
def set_column(col):
# technically this can be more indirect here if you've
# named the property differently, but here we are sticking
# with methods that don't use semi-private APIs
getter[0] = lambda obj: getattr(obj, col.key)
visitors.traverse(expr, {},
{
"unary": set_direction,
"column": set_column
}
)
if direction[0]:
return lambda a, b: cmp(getter[0](b), getter[0](a))
else:
return lambda a, b: cmp(getter[0](a), getter[0](b))
mapper = object_mapper(self)
getters = [
_gen_getter(expr)
for expr in mapper.order_by
]
def cmp_(a, b):
for getter in getters:
ret = getter(a, b)
if ret != 0:
return ret
else:
return ret
return cmp_
def __lt__(self, other):
return self._order_by_comparator(self, other) == -1
def __le__(self, other):
return self._order_by_comparator(self, other) <= 0
def __gt__(self, other):
return self._order_by_comparator(self, other) == 1
def __ge__(self, other):
return self._order_by_comparator(self, other) >= 0
from sqlalchemy import Column, Integer
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class A(Sortable, Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
x = Column(Integer)
y = Column(Integer)
z = Column(Integer)
__mapper_args__ = {"order_by": [x, y.desc(), z]}
a1 = A(x=5, y=7, z=10)
a2 = A(x=8, y=2, z=9)
a3 = A(x=5, y=2, z=15)
assert a1 < a2
assert a3 < a1
--
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/sqlalchemy?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.