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

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)

Related

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')))

SQLAlchemy: #property mapping?

I have following models:
class Details(db.Model):
details_id = db.Column(db.Integer, primary_key=True)
details_main = db.Column(db.String(50))
details_desc = db.Column(db.String(50))
class Data(db.Model):
data_id = db.Column(db.Integer, primary_key=True)
data_date = db.Column(db.Date)
details_main = db.Column(db.String(50))
#property
def details_desc(self):
result = object_session(self).\
scalar(
select([Details.details_desc]).
where(Details.details_main == self.details_main)
)
return result
Now, I would like to run query using filter which depends on defined property. I get empty results (of course proper data is in DB). It doesn't work because, probably, I have to map this property. The question is how to do this? (One limitation: FK are not allowed).
Data.query\
.filter(Data.details_desc == unicode('test'))\
.all()
You can implement this with a regular relationship and an association proxy:
class Data(db.Model):
data_id = db.Column(db.Integer, primary_key=True)
data_date = db.Column(db.Date)
details_main = db.Column(db.String(50))
details = relationship(
Details,
primaryjoin=remote(Details.details_main) == foreign(details_main))
details_desc = association_proxy('details', 'details_desc')
Since there are no foreign keys in the schema, you need to tell SQLAlchemy yourself what the join condition for the relationship should be. This is what the remote() and foreign() annotations do.
With that in place, you can use an association_proxy "across" the relationship to create a property on Data which will work the way you want.

SQLAlchemy: Combine contains_eager with defer

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.

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 delete association objects

I'm trying to batch delete objects from an association table by filtering on a column in one of the relationships. I use the following call in SQLAlchemy to make the delete
db.session.query(UserPaper).join(Paper, (UserPaper.paper_id ==
Paper.id)).filter(UserPaper.user_id == user.id).filter(Paper.journal_id
== journal.id).delete()
and it results in the following error
OperationalError: (OperationalError) (1054, "Unknown column 'papers.journal_id'
in 'where clause'") 'DELETE FROM userpapers WHERE userpapers.user_id = %s AND
papers.journal_id = %s' (1L, 1L)
Without the delete at the end, the SQLAlchemy query is
SELECT userpapers.user_id AS userpapers_user_id, userpapers.paper_id AS
userpapers_paper_id, userpapers.created AS userpapers_created,
userpapers.read_at AS userpapers_read_at, userpapers.score AS userpapers_score
FROM userpapers JOIN papers ON userpapers.paper_id = papers.id
WHERE userpapers.user_id = :user_id_1 AND papers.journal_id = :journal_id_1
which is correct. From the error I can see that when I append delete() to the query the join part of SQL statement gets lost and the database doesn't know how to find the papers.journal_id column obviously. What I don't understand is why does that happen?
This is the setup of my ORM objects
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
papers = db.relationship("UserPaper", backref=db.backref('users'), lazy='dynamic')
class Paper(db.Model):
__tablename__ = 'papers'
id = db.Column(db.Integer, primary_key = True)
title = db.Column(db.String(1024))
journal_id = db.Column(db.Integer, db.ForeignKey('journals.id'))
class UserPaper(db.Model):
__tablename__ = 'userpapers'
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
paper_id = db.Column(db.Integer, db.ForeignKey('papers.id'), primary_key=True)
paper = db.relationship("Paper", backref=db.backref('user_paper'))
read_at = db.Column(db.DateTime)
score = db.Column(db.Integer)
class Journal(db.Model):
__tablename__ = 'journals'
id = db.Column(db.Integer, primary_key = True)
title = db.Column(db.String(100), index = True, unique = True)
papers = db.relationship('Paper', backref = 'journal', lazy = 'dynamic')
I had the same problem with SQLALchemy 0.9 using MySQL 5.6. It looks like a bug/limitation. However, one better way to get arround (in comparison to creating the query, looping through the results and deleting them one by one) is to perform this task in two subsequent queries:
paperQuery = db.session.query(Paper.id)\
filter(Paper.journal_id == journal.id)
baseQuery = db.session.query(UserPaper)\
.filter(UserPaper.paper_id.in_(paperQuery.subquery()))
.filter(UserPaper.user_id == user.id).delete(synchronize_session='fetch')
It worked well for me, it should solve you issue too.

Categories