I need to write two classes like this:
class Item(Base, DBBase):
__tablename__ = 'items'
id = Column(Integer, primary_key = True)
name = Column(String)
description = Column(String)
price = Column(Float, default = 0)
on_sell = Column(Boolean, default = False)
img = Column(String)
attributes = relationship('ItemAttribute')
def __init__(self, name, description):
self.name = name
self.description = description
class ItemAttribute(Base, DBBase):
__tablename__ = 'itemattributes'
id = Column(Integer, primary_key = True)
name = Column(String, nullable = False)
value = Column(String, nullable = False)
item_id = Column(Integer, ForeignKey('items.id'))
item = relationship('Item')
def __init__(self, name, value):
self.name = name
self.value = value
One item can own several attributes, and I need to:
1. insert some methods on class Item to easily do CURD(insertion, deletion, update and query) attributes for it. I need to search a attribute of a item and return it's corresponding value.
2. have the ability to search items by attributes. For example, some items have the attributes of 'Feature' = 'True'. I need to get all items which have this attribute.
Thanks for help. :-)
If you add backref onto your ItemAttribute relationship:
item_id = Column(Integer, ForeignKey('items.id', onupdate='CASCADE', ondelete='CASCADE'))
item = relationship(Items, backref='attributes')
This will create and Item.attributes[] array which contains the ItemAttribute's. You might also add the onupdate and ondelete if you're using mysql.
Then when you query, you can do this:
rs = mySession.query(Items)
firstItem = rs.first()
for attribute in firstItem.attributes:
print attribute
When querying you can filter by joining the backref:
rs = mySession.query(Items).join(Items.attributes).filter(ItemAttribute.name=='somethingSpecial')
Further, if it's a one to one relationship (but it's not in this case), you could skip the list by specifing uselist=False:
item = relationship(ITEM, backref='attribute', uselist=False)
Related
I'm working on a project with Flask-SQLAlchemy.
The model looks like this:
cars have components,
components can have issues
car has a column_property 'needs_repair' which is true when a car's component has issues
needs_repair = column_property(exists().where(and_(
carcomponent.columns['car_id'] == id,
carcomponent.columns['component_id'] == componentissue.columns['component_id']
)))
I added a table for tags with a 'skip'-column, tags are assigned via a table issue_car_tag(ignoring components, only referencing specific car-issue-relations).
Now, i want needs_repair to be true if all assigned tags have skip = False or no tags are assigned
How do I extend the column_property to achieve this?
edit:
Model/table definitions:
class Component(Base):
id = db.Column(db.Integer, primary_key=True)
[...]
issues = db.relationship('ISsue', secondary=componentissue, lazy='dynamic',
back_populates='components')
cars = db.relationship('Car', lazy = 'dynamic', secondary=carcomponent,
back_populates="component"
broken = column_property(exists().where(componentissue.columns['component_id'] == id))
class Car(Base):
id = db.Column(db.Integer, primary_key=True)
[...]
components = db.relationship('Component', secondary=carcomponent,
back_populates="cars", lazy='dynamic')
needs_repair = column_property(exists().where(and_(
carcomponent.columns['car_id'] == id,
carcomponent.columns['component_id'] == componentissue.columns['component_id']
)))
class Issue(Base):
__tablename__ = "issues"
[...]
components = db.relationship('Component' lazy = 'dynamic', secondary=componentissue,
back_populates='issues')
class Tag(Base):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, unique=True)
description = db.Column(db.Text, nullable=False, default="")
skip = db.Column(db.Boolean, default = False)
class Issue_Car_Tag(Base):
id = db.Column(db.Integer, primary_key=True)
tag_id = db.Column(db.Integer, db.ForeignKey('tag.id'))
car_id = db.Column(db.Integer, db.ForeignKey('car.id'))
issue_id = db.Column(db.Integer, db.ForeignKey('issue.id'))
tag = db.relationship('Tag', backref=db.backref('issue_car_tags'))
car = db.relationship('Car', backref=db.backref('issue_car_tags'))
issue = db.relationship('Issue', backref=db.backref('issue_car_tags'))
If you'd move the definition of Car after the definitions of Tag and Issue_Car_Tag or reference those tables in some other manner, you could produce the following query construction
func.coalesce(func.bool_and(not_(Tag.skip)), False).\
select().\
where(Tag.id == Issue_Car_Tag.tag_id).\
where(Issue_Car_Tag.car_id == id).\
as_scalar()
and use that in an OR with your existing check:
needs_repair = column_property(
or_(func.coalesce(func.bool_and(not_(Tag.skip)), False).
select().
where(Tag.id == Issue_Car_Tag.tag_id).
where(Issue_Car_Tag.car_id == id).
as_scalar(),
exists().where(and_(
carcomponent.c.car_id == id,
carcomponent.c.component_id == componentissue.c.component_id))))
The query selects tags related to a car using the association table issue_car_tag and aggregates the skip values, coalescing an empty result or all null values.
Note that this results in false if no tags are assigned, so you have to handle that separately. If I've understood your existing query correctly, this is handled by your EXISTS expression already. Put another way, the new query results in true if tags exist and all have skip set to false.
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)
I have an existing database an want to build an SQLAlchemy wrapper to use the DB in Python. Lookup tables like the following are commonly used in the DB:
class Industry(Base):
__tablename__ = 'industry'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=True)
class IndustrySector(Base):
__tablename__ = 'industry_sector'
id = Column(Integer, primary_key=True)
industry_id = Column(Integer, ForeignKey('industry.id'), nullable=False)
name = Column(String, nullable=True)
What I would like to do is to create a new instance of IndustrySector using the name of the industry rather than the (technical) key of the industry, i.e.,
new_industry_sector = IndustrySector(industry_id = 'Manufacturing', name = 'Textile')
instead of
manu_industry_id = session.query(Industry.id).filter(Industry.name=='Manufacturing').first().id
new_industry_sector = IndustrySector(name = 'Textile', industry_id = new_industry_id)
Obviously, above example can't work because I am filtering on the ID rather than the name. But I don't know how to get the name of the foreign-keyed table into this. Of course I could simply add a #classmethod that handles the lookup, but if there exists any built-in functionality I'd much rather use that.
Any help / pointers are appreciated
You could create a constructor which will replace the string with the id value whenever an object is created.
class IndustrySector(Base):
__tablename__ = 'industry_sector'
id = Column(Integer, primary_key=True)
industry_id = Column(Integer, ForeignKey('industry.id'), nullable=False)
name = Column(String, nullable=True)
def __init__(self, industry_id, name):
self.name = name
self.industry_id = fetch_id(industry_id)
def fetch_id(industry_id):
# fetch and return the id
I am using SQLAlchemy here, trying to make a couple tables and link them and am having problems implementing this.
class Team(Base):
__tablename__ = "teams"
id = Column(Integer, primary_key=True)
espn_team_id = Column(Integer, unique=True, nullable=False)
games = relationship("Game", order_by="Game.date")
def __init__(self, name):
self.name = name
self.espn_team_id = espn_team_id
self.games = games
class Game(Base):
__tablename__ = "games"
id = Column(Integer, primary_key=True)
espn_game_id=Column(Integer, unique=True, nullable=False)
date = Column(Date)
h_espn_id = Column(Integer, ForeignKey('teams.espn_team_id'))
a_espn_id = Column(Integer, ForeignKey('teams.espn_team_id'))
I have this in one file which I use to create the tables. Then in another file I use the insert() function to put values into both tables. I think if I have a team with espn_team_id 360, and then I put in multiple games into the game table which have either h_espn_id=360, or a_espn_id=360, i should be able to do:
a = Table("teams", metadata, autoload=True)
a = session.query(a).filter(a.c.espn_team_id==360).first().games
and it should give me a list of all games team with ID 360 has played. But instead I get this error
AttributeError: 'NamedTuple' object has no attribute 'games'
What am I misunderstanding about SQLAlchemy or relational databases here?
Firstly, you don't have to create another Table object, as it is available as Team.__table__. Anyway, you can just query the mapped class, e.g.
query = Session.query(Team).filter(Team.espn_team_id == 360)
team360 = query.one()
games = team360.games
Refer to the documentation for methods .one(), .first(), and .all(): http://docs.sqlalchemy.org/en/latest/orm/query.html
Here is the solution I found, took way too long to understand this...
class Team(Base):
__tablename__ = "teams"
id = Column(Integer, primary_key=True)
name = Column(String)
espn_team_id = Column(Integer, unique=True, nullable=False)
h_games = relationship(
"Game",
primaryjoin="Game.h_espn_id==Team.espn_team_id",
order_by="Game.date")
a_games = relationship(
"Game",
primaryjoin="Game.a_espn_id==Team.espn_team_id",
order_by="Game.date")
#hybrid_property
def games(self):
return self.h_games+self.a_games
def __init__(self, name):
self.name = name
self.espn_team_id = espn_team_id
self.h_games = h_games
self.a_games = a_games
self.games = games
I have a SyncEntities class (shown below).
I have several other classes (such as CommodityTypes also shown below) related to the SyncEntities class.
All of my Base subclasses have this column uuidKey = Column(String, primary_key=True)
Assume se is an instance of SyncEntities.
se.entityKind is the name of a Base subclass.
How do I query for an object that is in the se.entityKind class filtering for se.uuidKey?
class SyncEntities(Base):
__tablename__ = 'SyncEntities'
uuidKey = Column(String, primary_key=True)
dateCreated = Column(DateTime, index=True)
dateModified = Column(DateTime, index=True)
dateSynced = Column(DateTime, index=True)
username = Column(String)
entityKind = Column(String)
deleted = Column(Boolean)
def __init__(self, entity, security):
self.uuidKey = newUUID()
self.dateCreated = security.now
self.dateModified = security.now
self.dateSynced = security.then
self.username = security.username
self.entityKind = entity.__tablename__
self.deleted = False
def modified(self, security):
self.dateModified = security.now
self.username = security.username
class CommodityTypes(Base):
__tablename__ = 'CommodityTypes'
uuidKey = Column(String, ForeignKey('SyncEntities.uuidKey'), primary_key=True)
myName = Column(String, unique = True)
sortKey = Column(Integer, unique = True)
mySyncEntity = relationship("SyncEntities")
def __init__(self, security, myName, sortKey):
self.syncEntity = SyncEntities(self, security)
self.uuidKey = self.syncEntity.uuidKey
self.myName = myName
self.sortKey = sortKey
The structure here is similar, though not quite the same, as a "polymorphic association", and you can read about this pattern over at this blog post: http://techspot.zzzeek.org/2007/05/29/polymorphic-associations-with-sqlalchemy/ . It's an old post but the example at http://techspot.zzzeek.org/files/2007/discriminator_on_association.py was added later as an updated example.
This case is a little different in that an object like CommodityTypes only refers to a single SyncEntities, not multiple as in the usual polymorphic association. The SyncEntities also can only refer to a single type of related object since you have entityKind on it locally.
I would note that a potential problem with this design is that you could have rows in other tables that have a uuidKey pointing to a particular SyncEntities instance, but are not of a type that matches "entityKind". If the relationship between CommodityTypes and SyncEntities is actually one-to-one, that changes everything - this pattern is really simple joined table inheritance and you'd use the patterns described at http://docs.sqlalchemy.org/en/rel_0_7/orm/inheritance.html.
You also don't have backrefs between the target and SyncEntities, which is often a way to automate these styles of lookup. But you can still approximate things using a lookup of entityKind types to classes:
def lookup_related(se):
types = {
'commodity':CommodityTypes,
'foobar':FooBarTypes
}
cls = types[se.entityKind]
session = object_session(se)
return session.query(cls).filter(cls.mySyncEntity==se).all()
here's a mixin that could do it also, using a backref:
class HasSyncEntity(object):
entity_kind = None
"subclasses need to populate this"
#declared_attr
def uuidKey(cls):
return Column(String, ForeignKey("SyncEntities.uuidKey"), primary_key=True)
#declared_attr
def mySyncEntity(cls):
return relationship("SyncEntities", backref="_%s_collection" % cls.entity_kind)
CommodityTypes becomes:
class CommodityTypes(HasSyncEntity, Base):
entity_kind = "commodity"
# ...
You then add a method like this to SyncEntities, which looks up the appropriate backref, and you're done:
def get_related(self):
return getattr(self, "_%s_collection" % self.entityKind)