In a Pyramid application I'm working on, I have the following scenario:
class Widget(Base):
__tablename__ = 'widgets'
id = Column(Integer, primary_key=True)
name = Column(String(50))
sidebar = Column(mysql.TINYINT(2))
def __init__(self, name, sidebar):
self.name = name
self.sidebar = sidebar
class Dashboard(Base):
__tablename__ = 'dashboard'
user_id = Column(Integer, ForeignKey('users.id'), primary_key=True)
widget_id = Column(Integer, ForeignKey('widgets.id'), primary_key=True)
delta = Column(mysql.TINYINT)
widget = relationship('Widget')
def __init__(self, user_id, widget_id, delta):
self.user_id = user_id
self.widget_id = widget_id
self.delta = delta
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
login = Column(Unicode(255), unique=True)
password = Column(Unicode(60))
fullname = Column(Unicode(100))
dashboard = relationship('Dashboard', order_by='Dashboard.widget.sidebar, Dashboard.delta')
def __init__(self, login, password, fullname):
self.login = login
self.password = crypt.encode(password)
self.fullname = fullname
So, I want the User 'dashboard' relationship to have the dashboard records for the user but ordered by 'sidebar' (which is a relationship property of Dashboard). Currently I am getting this error:
sqlalchemy.exc.InvalidRequestError: Property 'widget' is not an instance of ColumnProperty (i.e. does not correspond directly to a Column).
Is this ordering possible in a relationship declaration?
Thanks!
With this, try to think what SQL SQLAlchemy should emit when it tries to load User.dashboard. Like SELECT * FROM dashboard JOIN widget ... ORDER BY widget.sidebar ? Or SELECT * FROM dashboard ORDER BY (SELECT sidebar FROM widget... ? ordering the results by a different table is too open-ended of a job for relationship() to decide on it's own. The way this can be done is by providing a column expression in terms of Dashboard that can provide this ordering, when the ORM emits a simple SELECT against dashboard's table, as well as when it refers to it in a not-so-simple SELECT where it might be joining across User, Dashboard tables at once (e.g. eager loading).
We provide custom SQL expressions, particularly those that involve other tables, using column_property(), or alternatively with deferred() when we don't want that expression to be loaded by default (as is likely the case here). Example:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Widget(Base):
__tablename__ = 'widgets'
id = Column(Integer, primary_key=True)
name = Column(String(50))
sidebar = Column(Integer)
class Dashboard(Base):
__tablename__ = 'dashboard'
user_id = Column(Integer, ForeignKey('users.id'), primary_key=True)
widget_id = Column(Integer, ForeignKey('widgets.id'), primary_key=True)
delta = Column(Integer)
widget = relationship('Widget')
widget_sidebar = deferred(select([Widget.sidebar]).where(Widget.id == widget_id))
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
login = Column(Unicode(255), unique=True)
dashboard = relationship('Dashboard', order_by='Dashboard.widget_sidebar, Dashboard.delta')
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
w1, w2 = Widget(name='w1', sidebar=1), Widget(name='w2', sidebar=2)
s.add_all([
User(login='u1', dashboard=[
Dashboard(
delta=1, widget=w1
),
Dashboard(
delta=2, widget=w2
)
]),
])
s.commit()
print s.query(User).first().dashboard
the final SQL emitted by the load of ".dashboard" is:
SELECT dashboard.user_id AS dashboard_user_id, dashboard.widget_id AS dashboard_widget_id, dashboard.delta AS dashboard_delta
FROM dashboard
WHERE ? = dashboard.user_id ORDER BY (SELECT widgets.sidebar
FROM widgets
WHERE widgets.id = dashboard.widget_id), dashboard.delta
Keep in mind that MySQL does a terrible job optimizing for subqueries like the one above. If you need high performance here, you might consider copying the value of "sidebar" into "dashboard", even though that makes consistency more difficult to maintain.
Related
I am using Eve-SQLAlchemy==0.5.0
I would like to perform a nested query using Postman on my users such that I find all users that are within a specified organization.
Using SQL I would write my query such that:
select * from app_user
left join user_organization on user_organization.user_id = app_user.id
left join organization on organization.id = user_organization.organization_id
where organization.id = 2
I have a user model, an organization model, and a relational model linking the two user_organization.
from sqlalchemy import Column, DateTime, func, String, Integer
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class BaseModel(Base):
id = Column(Integer, primary_key=True, autoincrement=True)
__abstract__ = True
_created = Column(DateTime, default=func.now())
_updated = Column(DateTime, default=func.now(), onupdate=func.now())
_etag = Column(String(40))
class User(BaseModel):
__tablename__ = 'app_user'
organizations = relationship("Organization", secondary=UserOrganization.__tablename__)
class Organization(BaseModel):
__tablename__ = 'organization'
name = Column(String)
class UserOrganization(BaseModel):
__tablename__ = 'user_organization'
user_id = Column(Integer,
ForeignKey('app_user.id', ondelete='CASCADE'))
organization_id = Column(Integer,
ForeignKey('organization.id', ondelete='CASCADE'))
In my settings.py I have the resources registered:
# Resource Registration
DOMAIN = DomainConfig({
'organization': ResourceConfig(Organization),
'user': ResourceConfig(User)
}).render()
I have a series of postman collections setup, and using a GET request I can easily query any attribute... GET localhost:5000/user?where={"id":1}
I have tried (amongst many other things):
GET user?where={"organizations": {"organization_id" :2 }}
GET user?where={"organizations": 2}
It seems it's not possible at the moment due to a bug. I will try to fix it within the next week.
The code in https://github.com/pyeve/eve-sqlalchemy/blob/master/eve_sqlalchemy/parser.py#L73 is causing a GET ?where={"organizations": 2} to result in a SQL expression like user_id = 42 AND organization_id = 42 is generated. Which rarely makes any sense.
Here is my model:
user_map = Table(
"user_map",
Column('user_id', Integer, ForeignKey('user.id'), primary_key=True),
Column('map_id', Integer, ForeignKey('map.id'), primary_key=True),
PrimaryKeyConstraint('user_id', 'map_id', name='pk_user_map')
)
class Map(Base):
id = Column(Integer, primary_key=True)
name = Column(String)
owner_id = Column(Integer, ForeignKey('user.id'))
shared_maps = relationship(
'User',
secondary=user_map,
backref=backref('maps', lazy='dynamic')
)
class User(Base):
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String, unique=True)
shared_maps = Map.query.filter(Map.shared_maps.any()).all()
I want to query the user_map table, using the join condition "Map.id == user_map.map_id", but SQLAlchemy is trying to join using "Map.id == user_map.map_id and Map.owner_id == user_map.user_id". How can I specify my join condition?
I tried to use primaryjoin attribute in the relationship and to specify the condition inside the .join() but without success. Thanks in advance!
Based on your code, I've rebuilt the setup; I guess your relationships were mixed up. Furthermore, I've hardly ever seen primary keys (or PrimaryKeyConstraints) in sqlalchemy's many-to-many association tables. It may make sense from a non-orm perspective, but as far as I know, it is unusual or even not required at all.
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
Base = declarative_base()
UsersXMaps = sa.Table(
'users_x_maps',
Base.metadata,
sa.Column('user', sa.Integer, sa.ForeignKey('users.id')),
sa.Column('map', sa.Integer, sa.ForeignKey('maps.id'))
)
class User(Base):
__tablename__ = 'users'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
mail = sa.Column(sa.String, unique=True)
own_maps = relationship('Map', back_populates='owner')
maps = relationship(
'Map',
secondary=UsersXMaps,
back_populates='users'
)
def __str__(self):
return '{} ({}) with {} maps'.format(
self.name, self.mail, len(self.own_maps))
class Map(Base):
__tablename__ = 'maps'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
owner_id = sa.Column(sa.Integer, sa.ForeignKey('users.id'))
owner = relationship('User', back_populates='own_maps')
users = relationship(
'User',
secondary=UsersXMaps,
back_populates='maps'
)
def __str__(self):
return '{} (by {})'.format(self.name, self.owner.name)
So far for the setup; I've extended it a bit for proper output when printing strings. Additionally, your Map.shared_maps relationship actually refers to Users, not Maps, so I also renamed that one.
When binding your association table to the two classes, you can refer to it from both sides (even though back_populates seems to overwrite/replace the original definition) - this simplifies joins from either side.
Executing the following works as expected:
if __name__ == '__main__':
engine = sa.create_engine('sqlite:///usermaps.db')
sfactory = sessionmaker(engine)
session = sfactory()
Base.metadata.create_all(bind=engine)
bilbo = User(id=1, name='Bilbo', mail='bilbo#shire.nz')
frodo = User(id=2, name='Frodo', mail='frodo#shire.nz')
mordor = Map(id=1, name='Mordor', owner=frodo, users=[bilbo, frodo])
gondor = Map(id=2, name='Gondor', owner=bilbo, users=[bilbo, frodo])
rohan = Map(id=3, name='Rohan', owner=bilbo, users=[bilbo, frodo])
session.add_all([frodo, bilbo, mordor, gondor, rohan])
session.commit()
print('Maps by owner:')
for owner in [bilbo, frodo]:
print(owner)
for item in session.query(Map).filter(Map.owner == owner).all():
print(' - ' + str(item))
print('Maps by users:')
for item in session.query(Map).filter(Map.users.any()).all():
print(' - ' + str(item))
The output is:
Maps by owner:
Bilbo (bilbo#shire.nz) with 2 maps
- Gondor (by Bilbo)
- Rohan (by Bilbo)
Frodo (frodo#shire.nz) with 1 maps
- Mordor (by Frodo)
Maps by users:
- Mordor (by Frodo)
- Gondor (by Bilbo)
- Rohan (by Bilbo)
There are two tables that one column of table A is pointing another table B's primary key.
But they are placed in different database, so I cannot configure them with foreign key.
Configuring via relationship() is unavailable, so I implemented property attribute manually.
class User(Base):
__tablename__ = 'users'
id = Column(BigInteger, id_seq, primary=True)
name = Column(Unicode(256))
class Article(Base):
__tablename__ = 'articles'
__bind_key__ = 'another_engine'
# I am using custom session configures bind
# each mappers to multiple database engines via this attribute.
id = Column(BigInteger, id_seq, primary=True)
author_id = Column(BigInteger, nullable=False, index=True)
body = Column(UnicodeText, nullable=False)
#property
def author(self):
_session = object_session(self)
return _session.query(User).get(self.author_id)
#author.setter
def author(self, user):
if not isinstance(user, User):
raise TypeError('user must be a instance of User')
self.author_id = user.id
This code works well for simple operations. But it causes dirty queries making SQLAlchemy's features meaningless.
Code would be simple if it was configured via relationship() (e.g. query.filter(author=me)) got messed up(e.g. query.filter(author_id=me.id)).
Relationship(e.g. join) related features are never able to be used in query building.
Can I use property attribute, at least, in building query criterion(filter()/filter_by())?
you can still use relationship here. If you stick to "lazy loading", it will query for the related item in database B after loading the lead item in database A. You can place a ForeignKey() directive in the Column, even if there isn't a real one in the database. Or you can use primaryjoin directly:
class User(Base):
__tablename__ = 'users'
id = Column(BigInteger, id_seq, primary=True)
name = Column(Unicode(256))
class Article(Base):
__tablename__ = 'articles'
__bind_key__ = 'another_engine'
id = Column(BigInteger, id_seq, primary=True)
author_id = Column(BigInteger, nullable=False, index=True)
body = Column(UnicodeText, nullable=False)
author = relationship("User",
primaryjoin="foreign(Article.author_id) == User.id")
I have user who can have his favorite series and there are episodes which have series as foreign key and I am trying to retrieve all episodes from favorite series of user.
I am using Flask-SQLAlchemy.
Database:
db = SQLAlchemy(app)
# cross table for user-series
favorite_series = db.Table('favorite_series',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('series_id', db.Integer, db.ForeignKey('series.id'))
)
# user
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
favorite_series = db.relationship('Series', secondary=favorite_series,
backref=db.backref('users', lazy='dynamic'))
# series
class Series(db.Model):
__tablename__ = 'series'
id = db.Column(db.Integer, primary_key=True)
# episode
class Episode(db.Model):
__tablename__ = 'episode'
id = db.Column(db.Integer, primary_key=True)
series_id = db.Column(db.Integer, db.ForeignKey('series.id'))
series = db.relationship('Series',
backref=db.backref('episodes', lazy='dynamic'))
Friend helped me with SQL
select user_id,series.name,episode.name from (favorite_series left join series on favorite_series.series_id = series.id) left join episode on episode.series_id = series.id where user_id=1;
Altough, I want it in SQLAlchemy API, but can't manage to get it working.
EDIT:
My final working result:
episodes = Episode.query.filter(Episode.series_id.in_(x.id for x in g.user.favorite_series)).filter(Episode.air_time!=None).order_by(Episode.air_time)
First of all you don't seem to be declaring your table names?
Also, the whole point of bothering with orm is so you never have to write sql queries:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import orm
import sqlalchemy as db
Base = declarative_base()
favorite_series = db.Table('favorite_series', Base.metadata,
db.Column('user_id', db.Integer, db.ForeignKey('User.id')),
db.Column('series_id', db.Integer, db.ForeignKey('Series.id'))
)
class Episode(Base):
__tablename__ = 'Episode'
id = db.Column(db.Integer, primary_key=True)
season = db.Column(db.Integer)
episode_num = db.Column(db.Integer)
series_id = db.Column(db.Integer, db.ForeignKey('Series.id'))
def __init__(self, season, episode_num, series_id):
self.season = season
self.episode_num = episode_num
self.series_id = series_id
def __repr__(self):
return self.series.title + \
' S' + str(self.season) + \
'E' + str(self.episode_num)
class Series(Base):
__tablename__ = 'Series'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
episodes = orm.relationship('Episode', backref='series')
def __init__(self, title):
self.title = title
def __repr__(self):
return self.title
class User(Base):
__tablename__ = 'User'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
favorite_series = orm.relationship('Series',
secondary=favorite_series, backref='users')
def __init__(self, name):
self.name = name
def __repr__(self):
return self.name
Now you can just access the attributes of your objects and let sql alchemy deal with keeping you DB in sync and issuing queries.
engine = db.create_engine('sqlite:///:memory:')
session = orm.sessionmaker(bind=engine)()
Base.metadata.create_all(engine)
lt = User('Ludovic Tiako')
the_wire = Series('The Wire')
friends = Series('Friends')
session.add_all([lt, the_wire, friends])
session.commit() # need to commit here to generate the id fields
tw_s01e01 = Episode(1,1,the_wire.id)
tw_s01e02 = Episode(1,2,the_wire.id)
f_s01e01 = Episode(1,1,friends.id)
f_s01e02 = Episode(1,2,friends.id)
f_s01e03 = Episode(1,3,friends.id)
session.add_all([tw_s01e01, tw_s01e02,
f_s01e01, f_s01e02, f_s01e03])
session.commit()
the_wire.episodes # > [The Wire S1E1, The Wire S1E2]
friends.episodes # > [Friends S1E1, Friends S1E2, Friends S1E3]
Finally, to answer your question:
lt.favorite_series.append(the_wire)
session.commit()
lt.favorite_series # > [The Wire]
[s.episodes for s in lt.favorite_series] # >> [[The Wire S1E1, The Wire S1E2]]
I don't know about Flask, but from the docs of Flask-SQLAlchemy, it seems it uses declarative, so the ORM. And so, you should have a session. I think it is accessible to you from db.session.
Anyway, if those assumptions are true, this is how you should do it:
query = db.session.query(User.id, Series.name, Episode.name).filter((Episode.series_id == Series.id) & \
(User.id == favorite_series.c.user_id) & (Series.id == favorite_series.c.id) & \
(User.id == 1))
results = query.all();
It might not be the exact query you provided, but should do the same.
UPDATE: I just checked Flask-SQLALchemy code on github, it seems that db is an instance of SQLAlchemy, which has a session attribute, created by self.session = self.create_scoped_session(session_options) which returns a session object. So this should work.
Also, not that by doing that, you won't be using their BaseQuery, although I don't know what that would mean...
Check the documentation to know what to do exactly.
I have read the SQLAlchemy documentation and tutorial about building many-to-many relation but I could not figure out how to do it properly when the association table contains more than the 2 foreign keys.
I have a table of items and every item has many details. Details can be the same on many items, so there is a many-to-many relation between items and details
I have the following:
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
My association table is (It's defined before the other 2 in the code):
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
In the documentation, it's said that I need to use the "association object". I could not figure out how to use it properly, since it's mixed declarative with mapper forms and the examples seem not to be complete. I added the line:
details = relation(ItemDetail)
as a member of Item class and the line:
itemDetail = relation('Detail')
as a member of the association table, as described in the documentation.
when I do item = session.query(Item).first(), the item.details is not a list of Detail objects, but a list of ItemDetail objects.
How can I get details properly in Item objects, i.e., item.details should be a list of Detail objects?
From the comments I see you've found the answer. But the SQLAlchemy documentation is quite overwhelming for a 'new user' and I was struggling with the same question. So for future reference:
ItemDetail = Table('ItemDetail',
Column('id', Integer, primary_key=True),
Column('itemId', Integer, ForeignKey('Item.id')),
Column('detailId', Integer, ForeignKey('Detail.id')),
Column('endDate', Date))
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail, backref='Detail')
Like Miguel, I'm also using a Declarative approach for my junction table. However, I kept running into errors like
sqlalchemy.exc.ArgumentError: secondary argument <class 'main.ProjectUser'> passed to to relationship() User.projects must be a Table object or other FROM clause; can't send a mapped class directly as rows in 'secondary' are persisted independently of a class that is mapped to that same table.
With some fiddling, I was able to come up with the following. (Note my classes are different than OP's but the concept is the same.)
Example
Here's a full working example
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=False)
# Make the DeclarativeMeta
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
projects = relationship('Project', secondary='project_users', back_populates='users')
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship('User', secondary='project_users', back_populates='projects')
class ProjectUser(Base):
__tablename__ = "project_users"
id = Column(Integer, primary_key=True)
notes = Column(String, nullable=True)
user_id = Column(Integer, ForeignKey('users.id'))
project_id = Column(Integer, ForeignKey('projects.id'))
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add users
usr1 = User(name="bob")
session.add(usr1)
usr2 = User(name="alice")
session.add(usr2)
session.commit()
# add projects
prj1 = Project(name="Project 1")
session.add(prj1)
prj2 = Project(name="Project 2")
session.add(prj2)
session.commit()
# map users to projects
prj1.users = [usr1, usr2]
prj2.users = [usr2]
session.commit()
with Session(bind=engine) as session:
print(session.query(User).where(User.id == 1).one().projects)
print(session.query(Project).where(Project.id == 1).one().users)
Notes
reference the table name in the secondary argument like secondary='project_users' as opposed to secondary=ProjectUser
use back_populates instead of backref
I made a detailed writeup about this here.
Previous Answer worked for me, but I used a Class base approach for the table ItemDetail. This is the Sample code:
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True, index=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail.__table__, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail.__table__, backref='Detail')