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.


Reply via email to