Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equivalent of models.Manager for SqlAlchemy

I'm using SQLAlchemy and I what I liked with Django ORM was the Manager I could implement to override the initial query of an object.

Is something like this exist in SQLAlchemy? I'd like to always exclude items that have "visible = False", when I do something like :

session.query(BlogPost).all()

Is it possible?

Thanks!

like image 993
Cyril N. Avatar asked Jul 07 '11 06:07

Cyril N.


People also ask

Should I use SQLAlchemy core or ORM?

If you want to view your data in a more schema-centric view (as used in SQL), use Core. If you have data for which business objects are not needed, use Core. If you view your data as business objects, use ORM. If you are building a quick prototype, use ORM.

Is SQLAlchemy an ORM?

SQLAlchemy is a library that facilitates the communication between Python programs and databases. Most of the times, this library is used as an Object Relational Mapper (ORM) tool that translates Python classes to tables on relational databases and automatically converts function calls to SQL statements.

Is SQLAlchemy better than Django ORM?

4) Applications In addition to this, since it interacts directly with the database, you can simply run the queries against the database without actually using the ORM. Plus, SQLAlchemy is much more powerful than Django, albeit with a little higher learning curve.

Does Django ORM use SQLAlchemy?

Both Django and SQLAlchemy can be used with MySQL, PostgreSQL, Oracle and SQLite. If you're using MSSQL, you should go for SQLAlchemy, as it's fully supported by it and you'll find more information and documentation about it.


1 Answers

EDIT: the original version almost worked. The following version actually works.

It sounds like what you're trying to do is arrange for the query entity to be something other than SELECT table.* FROM table. In sqlalchemy, you can map any "selectable" to a class; There are some caveats, though; if the selectable is not a table, inserting data can be tricky. Something like this approaches a workable solution. You probably do want to have a regular table mapped to permit inserts, so the first part is a totally normal table, class and mapper.

blog_post_table = Table("blog_posts", metadata,
    Column('id', Integer, primary_key=True),
    Column('visible', Boolean, default=True),
    ...
)

class BlogPost(object):
    pass

blog_post_mapper = mapper(BlogPost, blog_post_table)

Or, if you were using the declarative extension, it'll all be one

class BlogPost(Base):
    __tablename__ = 'blog_posts'
    id = Column(Integer, primary_key=True)
    visible = Column(Boolean, default=True)

Now, we need a select expression to represent the visible posts.

visible_blog_posts_expr = sqlalchemy.sql.select(
        [BlogPost.id,
         BlogPost.visible]) \
    .where(BlogPost.visible == True) \
    .alias()

Or, since naming all of the columns of the desired query is tedious (not to mention in violation of DRY), you can use the same construct as session.query(BlogPost) and extract the 'statement'. You don't actually want it bound to a session, though, so call the class directly.

visible_blog_posts_expr = \
    sqlalchemy.orm.Query(BlogPost) \
    .filter(BlogPost.visible == True) \
    .statement \
    .alias()

And we map that too.

visible_blog_posts = mapper(BlogPost, visible_blog_posts_expr, non_primary=True)

You can then use the visible_blog_posts mapper instead of BlogPosts with Session.query, and you will still get BlogPost, which can be updated and saved as normal.

posts = session.query(visible_blog_posts).all()
assert all(post.visible for post in posts)

For this particular example, there's not much difference between explicit mapper use and declarative extension, you still must call mapper for the non-primary mappings. At best, it allows you to type SomeClass.colname instead of some_table.c.colname (or SomeClass.__table__.colname, or BlogPost.metadata.tables[BlogPost.__tablename__] or ... and so on).

The mistakes I made in the original example, which are now corrected. I was missing some missing []'s in the call to sqlalchemy.sql.select, which expects the columns to be in a sequence. when using a select statement to mapper, sqlalchemy insists that the statement be aliased, so that it can be named (SELECT .... ) AS some_subselect_alias_5

like image 56
SingleNegationElimination Avatar answered Nov 02 '22 23:11

SingleNegationElimination