SQLAlchemy: Combine contains_eager with defer - python

There are these SQLAlchemy mapped classes:
class Person(Base):
__tablename__ = 'person'
id = sqla.Column(sqla.Integer, primary_key=True)
name = sqla.Column(sqla.String)
assoc_owned_things = relationship('Owns')
class Thing(Base):
__tablename__ = 'thing'
id = sqla.Column(sqla.Integer, primary_key=True)
name = sqla.Column(sqla.String)
assoc_owned_by = relationship('Owns')
class Owns(Base):
__tablename__ = 'owns'
person_id = sqla.Column(sqla.Integer, sqla.ForeignKey('person.id'), primary_key = True, autoincrement = False)
person = relationship("Person")
thing_id = sqla.Column(sqla.Integer, sqla.ForeignKey('thing.id'), primary_key = True, autoincrement = False)
thing = relationship("Thing")
ownership_payload = sqla.Column(sqla.Text)
Then this code:
person = (session.query(Person).join(Owns).join(Thing).filter(Person.id == id_p2)
.options(Load(Owns).defer('ownership_payload'), contains_eager(Person.assoc_owned_things, Owns.thing)).one())
print(p2.assoc_owned_things[0].ownership_payload)
loads all fields in one query. How make the ownership_payload field to be deferred in this query so that accessing it will query the database again? Used defer option does not seem to have any impact on ORM behaviour.

Related

Order by association proxy: invalid sql

I want to attach a column of a related model to another model and use it like any other sqlalchemy column attribute. As mentioned in docs, association_proxy makes it easy to use.
from sqlalchemy.ext.associationproxy import association_proxy
from app import db
class User(db.Model):
id = db.Column(...)
name = db.Column(...)
class Book(db.Model):
author_id = db.Column(...)
author = db.relationship('User', foreign_keys=[author_id])
author_name = association_proxy('author', 'name')
But when I call Book.query.order_by(Book.author_name.asc()).all() I got this:
psycopg2.errors.SyntaxError: syntax error at or near "ASC"
LINE 4: ...user.id = book.author_id AND user.name ASC)
Sqlalchemy generates EXISTS statement:
...
EXISTS (SELECT 1
FROM user
WHERE user.id = book.author_id AND user.name ASC)
So how to use ordering properly with an associated attribute?
In this case it is probably best to use a column_property:
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer(), primary_key=True)
name = Column(Unicode())
class Book(Base):
__tablename__ = 'book'
id = Column(Integer(), primary_key=True)
author_id = Column(Integer(), ForeignKey('user.id'), nullable=False)
author = relationship('User')
Book.author_name = column_property(select([User.name]).where(User.id == Book.author_id))
# sample ORM query
books = session.query(Book).order_by(Book.author_name)

How to automatically populate date in the table that defines relationship between two other table in SQL Alcehmy?

So, I am writing a webapp in python Flask with sqlite3 database. I am using SQLAlchemy. As part of my database I have these three tables. Here, table DisasterEvent has many to many relationship with Location Table(a disaster event occur in many locations and many disaster could have happened in that particular location. Is there a way to automatically populate association table when I insert data in DisasterEvent and Location table or should I do it populate it myself? Even if you guys haven't have sql alchemy experience, I would love to hear how it is done usually?
class DisasterEvent(Base):
'''Disaster Event Table'''
__tablename__ = 'DisasterEvent'
id = Column(Integer, primary_key = True, autoincrement = True)
title = Column(String(80), nullable = False)
glide = Column(String, nullable = False)
status = Column(Enum('current', 'alert', 'past'))
description = Column(String)
date_occured = Column(String(11), nullable = False)
date_ended = Column(String(11))
disaster_type = Column(String(120), nullable = False)
location = relationship("Location", secondary = association_table,
back_populates = 'disaster_event')
class Location(Base):
'''Disaster Location Table'''
__tablename__ = 'Location'
id = Column(Integer, primary_key = True)
country = Column(String)
state = Column(String)
city = Column(String)
lat = Column(Float)
lon = Column(Float)
disaster_event = relationship("DisasterEvent", secondary = association_table,
back_populates = 'location')
#this is the association table that joins the two above tables
association_table = Table('association', Base.metadata,
Column('event_id', Integer, ForeignKey('DisasterEvent.id')),
Column('location_id', Integer, ForeignKey('Location.id')))

Trouble dropping rows involving inherited models and relationships with Flask-SQLAlchemy

I have (poorly) designed myself a DB that involves many relationships and inherited models as follows:
class Instrument(db.Model):
__tablename__ = 'instrument'
id = db.Column(db.Integer, primary_key = True)
sn = db.Column(db.String(24), unique = True, index = True)
...
data = db.relationship('Data', backref = 'Instrument', lazy = 'dynamic', cascade = 'all, delete')
sensors = db.relationship('Sensor', backref = 'Instrument', lazy = 'dynamic', cascade = 'all, delete')
class Sensor(db.Model):
__tablename__ = 'sensors'
id = db.Column(db.Integer, primary_key = True)
sn = db.Column(db.String(24), unique = True, index = True)
...
data = db.relationship('SensorData', backref = 'Sensor', lazy = 'dynamic', cascade = 'all, delete')
instr_sn = db.Column(db.String(24), db.ForeignKey('instrument.sn'), index = True)
class SensorTypeB(Sensor):
__tablename__ = 'sensor_type_b'
id = db.Column(db.Integer, db.ForeignKey('sensors.id'), primary_key = True)
extracolumn = db.Column(db.Float)
__mapper_args__ = {'polymorphic_identity': 'sensor_type_b'}
def __init__(self, extracolumn = None, **kwargs):
super(SensorTypeB, self).__init__(**kwargs)
self.extracolumn = extracolumn
class Data(db.Model):
__tablename__ = 'data'
id = db.Column(db.Integer, primary_key = True)
timestamp = db.Column(db.DateTime)
value = db.Column(db.Float)
parameter = db.Column(db.String(24), index = True)
unit = db.Column(db.String(24))
flag = db.Column(db.Boolean)
instr_sn = db.Column(db.String(24), db.ForeignKey('instrument.sn'), index = True)
class SensorData(Data):
__tablename__ = 'sensor_data'
id = db.Column(db.Integer, db.ForeignKey('data.id'), primary_key = True)
sensor_sn = db.Column(db.String(24), db.ForeignKey('sensors.sn'), index = True)
__mapper_args__ = {'polymorphic_identity': 'sensor_data'}
def __init__(self, sensor_sn, **kwargs):
super(SensorData, self).__init__(**kwargs)
self.sensor_sn = sensor_sn
class MetSensorData(SensorData):
__tablename__ = 'met_sensor_data'
id = db.Column(db.Integer, db.ForeignKey('sensor_data.id'), primary_key = True)
raw = db.Column(db.Float)
__mapper_args__ = {'polymorphic_identity': 'met_sensor_data'}
def __init__(self, raw = None, **kwargs):
super(MetSensorData, self).__init__(**kwargs)
self.raw = raw
I have left out a good chunk for brevitiy..but can add in any details one might thing are relevant. The purpose of setting it up this way (in my mind) was to do the following:
Every Sensor must belong to an Instrument, but not every Instrument must have a sensor
All Instruments have data (Instrument.data)
All Sensors have data (Sensor.data)
The Child classes of the Sensor model have data (SensorTypeB.data)
Everything works as expected until I try to drop data points from the DB. It works perfectly fine in unittesting using SQLite3, but as soon as I move it to MySQL, everything breaks with the error of type:
IntegrityError: (_mysql_exceptions.IntegrityError) (1451, 'Cannot delete or update a parent row: a foreign key constraint fails (`db_name`.`met
_sensor_data`, CONSTRAINT `met_sensor_data_ibfk_1` FOREIGN KEY (`id`) REFERENCES `sensor_data` (`id`))') [SQL: u'DELETE FROM sensor_data WHE
RE sensor_data.id = %s'] [parameters: (3L,)]
Why does this happen only on MySQL and not SQLite? How can I fix and/or improve it? Is this not the worst DB design ever?
The error that you get is telling you that you are trying to delete a SensorData row, but in doing so, an entry in the MetSensorData that references it would be orphaned, and because of the way SQLAlchemy configures the relationship between SensorData and MetSensorData, orphans in MetSensorData are not allowed.
If you can find a way to not need that class, or to make it be a stand-alone model instead of an extension of the SensorData model, then your problem will go away. If you want to keep the model as it is, then you can add a cascade clause to the SensorData foreign key that instructs the database to remove the orphaned row instead of complain about it. I haven't tested it to make sure this is the right syntax, but I think you would do it as follows:
id = db.Column(db.Integer, db.ForeignKey('sensor_data.id', ondelete='CASCADE'), primary_key = True)

Database structure for voting of articles and comments

I'm quite new to relational databases and web programming in general and I'm facing a problem on how to structure my database for use of a voting system, similar to reddit.
I'm using sqlalchemy, the important bits are shown below:
class Vote(Base):
__tablename__ = 'votes'
id = Column(Integer, primary_key = True)
vote_type = Column(Integer, default = 0)
user_id = Column(Integer, ForeignKey('users.id'))
#article_id = Column(Integer, ForeignKey('article_items.id'))
#comment_id = Column(Integer, ForeignKey('comments.id'))
class ArticleItem(Base):
__tablename__ = 'article_items'
id = Column(Integer, primary_key = True)
vote_ups = Column(Integer, default = 0)
vote_downs = Column(Integer, default = 0)
article = relationship("Article",
uselist = False,
backref = 'article_item')
comments = relationship("Comment")
votes = relationship("Vote", cascade = "all")
class Comment(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key = True)
data = Column(Text, nullable = False)
replies = relationship("Comment")
vote_ups = Column(Integer, default = 0)
vote_downs = Column(Integer, default = 0)
votes = relationship("Vote", cascade = "all")
parent_id = Column(Integer, ForeignKey('comments.id'))
article_id = Column(Integer, ForeignKey('article_items.id'))
As it stands now, I have to include two foreign keys in class Vote, but only one of the two keys is used at any time.
Is there any easier/recommended way to do this? Should I keep two different vote types instead; one for comments and one for articles? Or should I maybe merge ArticleItem and Comment into one class Voteable?

SQLAlchemy: retrieve all episodes from favorite_series of specific user

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.

Categories