Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQLAlchemy equivalent to Django's annotate() method

I'm doing a join like this in SQLAlchemy:

 items = Item.query\
    .outerjoin((ItemInfo, ItemInfo.item_id==Item.id))
 items.add_columns(ItemInfo.count)

This causes SQLAlchemy to return tuples:

 >>> items.first()
 (<Item ...>, 2)

I'd much prefer it if the "count" value would instead be returned as an attribute of the item, i.e. I want to do:

 >>> items.first().count
 2

Is this supported?

like image 506
miracle2k Avatar asked Oct 29 '10 14:10

miracle2k


People also ask

Is SQLAlchemy better than Django ORM?

Community Size. Without any doubt, SQLAlchemy has the largest community among Python ORM frameworks. If community is important to you (and I think it should be), SQL Alchemy should be your choice. It doesn't mean though that you won't find any help for other frameworks such as Django.

What does SQLAlchemy all () return?

As the documentation says, all() returns the result of the query as a list.

What is first () in SQLAlchemy?

filter() - filter on SQL expressions. method sqlalchemy.orm.Query. first() Return the first result of this Query or None if the result doesn't contain any row.


Video Answer


1 Answers

Actually, "items.first().count" would work, since the tuple you get back is a named tuple...but guessing you don't want to see items.first().item.foo.

The second way you could do this would be just to run the result of your query() through a function that constructs the kind of result you want:

def process(q):
    for item, count in q:
        item.count = count
        yield count

edit: here is a generalized version:

from sqlalchemy.orm.query import Query

class AnnotateQuery(Query):
    _annotations = ()

    def annotate(self, key, expr):
        q = self.add_column(expr)
        q._annotations = self._annotations + (key, )
        return q

    def __iter__(self):
        if not self._annotations:
            return super(AnnotateQuery, self).__iter__()
        else:
            for row in super(AnnotateQuery, self):
                item, remaining = row[0], row[1:]
                for i, key in enumerate(self._annotations):
                    setattr(item, key, remaining[i])
                yield item


# session usage:

Session = sessionmaker(query_cls=AnnotateQuery)

# query usage:
q = Session.query(Item).outerjoin(...).annotate('count', Item.count)

The third, is that you alter the Item class to support this function. You'd use column_property() to apply a select subquery to your class: http://www.sqlalchemy.org/docs/orm/mapper_config.html#sql-expressions-as-mapped-attributes . If you wanted the loading of the attribute to be conditional, you'd use deferred: http://www.sqlalchemy.org/docs/orm/mapper_config.html#deferred-column-loading .

like image 195
zzzeek Avatar answered Sep 30 '22 11:09

zzzeek