Querying association table object directly - python

I have to query the relationships directly, since I have to display them seperatly.
task_user = db.Table(
'pimpy_task_user',
db.Column('task_id', db.Integer, db.ForeignKey('pimpy_task.id')),
db.Column('user_id', db.Integer, db.ForeignKey('user.id'))
)
How do I do this in SQLAlchemy?
When I try this:
tasks_rel = task_user.join(Task).join(User).filter(Task.group_id == group_id)
It causes this error:
AttributeError: 'Join' object has no attribute 'filter'

the usage you have above is creating a join() construct, which is a Core (non-ORM) construct representing a join() of two tables (but not a full blown select()).
when using the ORM you usually start off a SELECT using the Query object. Querying from the class itself is a pattern offered by extensions like flask-sqlalchemy, but these extensions are usually confusing in this regard. Given any class or table you can query against it using the Query object:
session.query(task_user).join(Task).join(User).filter(Task.group_id == group_id)

Related

Python delete attribute method is deleting the value not the attribute

In my flask admin app, I have a sqlalchemy orm model, old_model. I want to delete an attribute, action_id from the object. I am doing
del old_model.action_id
I expect the attribute action_id to be removed from the object old_model. Instead of getting deleted, its value is updated to None and the attribute still exists. How to delete the attribute completely along with the value?
So this was expected, as SQLAlchemy ORM mapped objects don't support this particular state for an attribute, that is, attribute doesn't exist and would raise AttributeError. for an ORM mapped class, a mapped attribute always defaults to None and/or empty collection. there's a little bit of an introduction to this here: https://docs.sqlalchemy.org/en/14/tutorial/orm_data_manipulation.html#instances-of-classes-represent-rows
For this particular problem, you can define your column as
id = Column('id', String, FetchedValue())
FetchedValue is used when the database is configured to provide some automatic default for a column. So, in this case you only want to ignore the column this would work like a charm. Updated model would look something like:
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
pid = Column('id', Integer, primary_key=True)
id = Column('id', String, FetchedValue())

SQLAlchemy: Disable lazy loading and load object only on join()

I'm working on disabling lazy loading on SQLAlchemy, so it will not load by default all the objects when fetching the records from the database. I'm trying to load for example the user object only from the event object when you specifically join it on the query or if you access it for example event.user. Is this possible somehow with a parameter or is it a bad practice to disabling lazy loading?
I already tried the noload("*") but it disables any join at the end.
For example I have the below model and also the queries which I'm doing my tests.
# Event model
class Event(Base):
__tablename__ = 'events'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(50), nullable=False)
amount = Column(Integer)
_user_id = Column("user_id", Integer, ForeignKey("users.id"), nullable=False)
user = relationship(User)
# Query - This fetches also the whole user object <-- I don't want such behavior
some_session.query(Event).all()
# Query - I would like to load the user object when I will use the join() if possible
some_session.query(Event).join(Event.user).all()
The default relationship loading strategy is "lazy loading", which works the way you want; the related User is loaded only if the user attribute of an Event object is touched. In your case it is touched by your IDE, when it inspects the object in order to display the attributes as a handy tree, and that triggers the fetch. The same happens easily with custom __repr__() implementations, if not careful.
When you wish to eager load the related users using a join, either use joined loading:
some_session.query(Event).options(joinedload(Event.user)).all()
or if you wish to filter based on User in the same query, explicit join(s) and contains_eager():
some_session.query(Event).join(Event.user).options(contains_eager(Event.user)).all()

Can I use an ORM class as association table for a many-to-many realtionship in SQLAlchemy?

I want to collect statistical information for songs (rating, play-count, last time played) from a couple of players over different devices and from different users. I use Python and SQL Alchemy.
I came up with the following table layout:
I can access all related Stats objects from my Commit ORM class as a list. I also want to have access to the related Song objects from the Commit class. Following the examples in the SQLALchemy Documentation I came up with an association table for the mtm relationship.
In code it looks like this (full source):
songcommits_table = Table(
'songcommits', Base.metadata,
Column('commit_id', Integer, ForeignKey('commits.commit_id')),
Column('song_id', Integer, ForeignKey('songs.song_id'))
)
class Commit(Base):
__tablename__ = 'commits'
commit_id = Column(Integer, primary_key=True)
# ...
songs = relationship(
"Song", secondary=songcommits_table, backref="commits"
)
stats = relationship('Stat', backref='commit')
def __repr__(self):
return "<Commit {0.commit_id}>".format(self)
It works. But I have the feeling it might work without the table, using the info already stored in the stats table, but I have no idea how to formulate this in the ORM.
So how can I use the information (commit_id and song_id) already present on the Stats ORM class instead of the helper table?

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!
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
You can do e.g.
session.query(BlogPost).filter_by(visible=True)
which should give you just the posts you need.

Many-to-many relationship on same table with association object

Related (for the no-association-object use case): SQLAlchemy Many-to-Many Relationship on a Single Table
Building a many-to-many relationship is easy. Building a many-to-many relationship on the same table is almost as easy, as documented in the above question.
Building a many-to-many relationship with an association object is also easy.
What I can't seem to find is the right way to combine association objects and many-to-many relationships with the left and right sides being the same table.
So, starting from the simple, naïve, and clearly wrong version that I've spent forever trying to massage into the right version:
t_groups = Table('groups', metadata,
Column('id', Integer, primary_key=True),
)
t_group_groups = Table('group_groups', metadata,
Column('parent_group_id', Integer, ForeignKey('groups.id'), primary_key=True, nullable=False),
Column('child_group_id', Integer, ForeignKey('groups.id'), primary_key=True, nullable=False),
Column('expires', DateTime),
)
mapper(Group_To_Group, t_group_groups, properties={
'parent_group':relationship(Group),
'child_group':relationship(Group),
})
What's the right way to map this relationship?
I guess you are getting an error like Could not determine join condition between parent/child tables...
In this case, Change the mapper for Group_To_Group to the following:
mapper(Group_To_Group, t_group_groups, properties={
'parent_group':relationship(Group,
primaryjoin=(t_group_groups.c.parent_group_id==t_groups.c.id),),
'child_group':relationship(Group,
primaryjoin=(t_group_groups.c.child_group_id==t_groups.c.id),),
})
also you might want to add the backref so that you can navigate the relations from the Group objects as well.

Categories

Resources