I have 2 SqlAlchemy entities: study and series with 1 to many relation (study has many series). I crearted a relationship that way:
class SeriesDBModel(Base):
__tablename__ = "series"
series_uid = Column(String, primary_key=True)
study_uid = Column(String, ForeignKey(StudyDBModel.study_uid))
study = relationship("StudyDBModel", lazy="subquery", cascade="all")
class StudyDBModel(Base):
__tablename__ = "study"
study_uid = Column(String, primary_key=True)
My question is:
Do I have to create the entities like this:
series = SeriesDBModel()
study = StudyDBModel()
series.study = study
Or
Can I just create both entities independently but with the same study_uid?
I'm asking because the second way i how I save my entities but using series.study returns None.
BTW, it's not a "sessio.commit()" issue. I'm verifing both entities exist in the DB before trying series.study..
I tried to fetch series.study as above.
Related
I have two tables, Products and Orders, inside my Flask-SqlAlchemy setup, and they are linked so an order can have several products:
class Products(db.Model):
id = db.Column(db.Integer, primary_key=True)
....
class Orders(db.Model):
guid = db.Column(db.String(36), default=generate_uuid, primary_key=True)
products = db.relationship(
"Products", secondary=order_products_table, backref="orders")
....
linked via:
order_products_table = db.Table("order_products_table",
db.Column('orders_guid', db.String(36), db.ForeignKey('orders.guid')),
db.Column('products_id', db.Integer, db.ForeignKey('products.id'))
# db.Column('license', dbString(36))
)
For my purposes, each product in an order will receive a unique license string, which logically should be added to the order_products_table rows of each product in an order.
How do I declare this third license column on the join table order_products_table so it gets populated it as I insert an Order?
I've since found the documentation for the Association Object from the SQLAlchemy docs, which allows for exactly this expansion to the join table.
Updated setup:
# Instead of a table, provide a model for the JOIN table with additional fields
# and explicit keys and back_populates:
class OrderProducts(db.Model):
__tablename__ = 'order_products_table'
orders_guid = db.Column(db.String(36), db.ForeignKey(
'orders.guid'), primary_key=True)
products_id = db.Column(db.Integer, db.ForeignKey(
'products.id'), primary_key=True)
order = db.relationship("Orders", back_populates="products")
products = db.relationship("Products", back_populates="order")
licenses = db.Column(db.String(36), nullable=False)
class Products(db.Model):
id = db.Column(db.Integer, primary_key=True)
order = db.relationship(OrderProducts, back_populates="order")
....
class Orders(db.Model):
guid = db.Column(db.String(36), default=generate_uuid, primary_key=True)
products = db.relationship(OrderProducts, back_populates="products")
....
What is really tricky (but also shown on the documentation page), is how you insert the data. In my case it goes something like this:
o = Orders(...) # insert other data
for id in products:
# Create OrderProducts join rows with the extra data, e.g. licenses
join = OrderProducts(licenses="Foo")
# To the JOIN add the products
join.products = Products.query.get(id)
# Add the populated JOIN as the Order products
o.products.append(join)
# Finally commit to database
db.session.add(o)
db.session.commit()
I was at first trying to populate the Order.products (or o.products in the example code) directly, which will give you an error about using a Products class when it expects a OrderProducts class.
I also struggled with the whole field naming and referencing of the back_populates. Again, the example above and on the docs show this. Note the pluralization is entirely to do with how you want your fields named.
Background
I have an existing database with 3 levels of dependent tables. Here I give an example of a 3-level, joined table, inheritance mapping problem I cannot solve. Unfortunately I cannot change the database design.
Problem
If I execute:
entities = session.query(Entity)
for ent in entities:
print ent.isin
I get the error:
AttributeError: 'Asset' object has no attribute 'isin'
However the following code executes fine:
entities = session.query(Entity)
for ent in entities:
print ent.composition
Question
So the attribute composition is accessible but not isin !?!?
It seems obvious that the classes Entity and Asset are being mapped, but not Listed. Why would this be so?
I would have believed that sqlalchemy would be able to map to an arbitrary depth of inheritance.
I have read the docs, tried everything and I am stumped. Please help.
Inheritance mapping
class Entity(Base):
__tablename__ = 'entity'
id = Column(Integer, primary_key=True)
name = Column(String)
entity_type = Column('type',String)
source_table_id = Column(Integer)
__mapper_args__ = {
'polymorphic_identity':'entity',
'polymorphic_on':entity_type,
}
class Asset(Entity):
__tablename__ = 'asset'
id = Column('entity_id',Integer, ForeignKey('entity.id'), primary_key=True)
asset_type = Column('type',String)
asset_class = Column('class',String)
composition = Column(String)
__mapper_args__ = {
'polymorphic_identity':'asset',
'polymorphic_on':asset_type,
}
class Listed(Asset):
__tablename__ = 'listed'
id = Column('asset_entity_id',Integer, ForeignKey('asset.entity_id'), primary_key=True)
ticker = Column(String)
isin = Column(String)
__mapper_args__ = {
'polymorphic_identity':'listed',
}
I think maybe this is just a coincidence with your data.
You should be either iterating over the particular type of object you want to look at e.g.
entities = session.query(Asset)
for ent in entities:
print ent.composition
and
entities = session.query(Listed)
for ent in entities:
print ent.isin
or checking the type of object before you access an attribute on that type of object
entities = session.query(Entity)
for ent in entities:
if isinstance(ent, Asset):
print ent.composition
else isinstance(ent, Listing):
print ent.isin
Otherwise what's going to happen is you're going to get an Entity object which will be either a Listing or a Asset object. If you hardcode the attribute isin then it's going to break when dealing with an Asset object because the Asset object does not have an isin property.
I'd like to add a custom attribute "searchable" to the columns in an sqlalchemy model. The purpose is to extract data for just these columns (using ModelFoo.__ table__.columns) and put the data into solr. Is there a way I can mark certain columns using a custom attribute?
class ModelFoo(AppBase):
__tablename__ = 'foo'
id = Column("id", Integer, primary_key=True, autoincrement=True)
os = Column(String, nullable=False, searchable=True)
platform = Column(String, searchable=True)
by default, I get the following error when I try the above:
sqlalchemy.exc.ArgumentError: Unknown arguments passed to Column: ['searchable']
I am looking for a generic way to add only "searchable" columns to solr, something along these lines:
for table in Base.metadata.tables.values():
keys = [str(key) for key in table.columns if key.searchable]
solr.add(session.query(*keys).all())
in the above code, I am looking for some short solution or alternative to get "key.searchable" to work. Hope this clarifies the question.
I solved this using a separate attribute in model:
class ModelFoo(Base):
__tablename__ = 'foo'
id = Column("id", Integer, primary_key=True, autoincrement=True)
os = Column(String, nullable=False)
platform = Column(String)
search_cols = ["os", "value"]
for k, v in list(Base._decl_class_registry.items()):
if (hasattr(v, "search_cols")):
cols = [getattr(v, val) for val in v.search_cols]
query = sess.query(*cols)
solr.add(query.all())
I'm a little unclear on your question but it looks like your are trying to do something like this from the link you provided:
si.add(Book.objects.all())
Where Book.objects.all() is a list of records from an ORM mapped table. Note that the docs say a list of objects is also acceptable. I think to solution here is to use the SQLAlchemy query method to build records with only the fields you want. Using your example with would look like this:
si.add(session.query(ModelFoo.os, ModelFoo.platform).all())
So I'm quite new to SQLAlchemy.
I have a model Showing which has about 10,000 rows in the table. Here is the class:
class Showing(Base):
__tablename__ = "showings"
id = Column(Integer, primary_key=True)
time = Column(DateTime)
link = Column(String)
film_id = Column(Integer, ForeignKey('films.id'))
cinema_id = Column(Integer, ForeignKey('cinemas.id'))
def __eq__(self, other):
if self.time == other.time and self.cinema == other.cinema and self.film == other.film:
return True
else:
return False
Could anyone give me some guidance on the fastest way to insert a new showing if it doesn't exist already. I think it is slightly more complicated because a showing is only unique if the time, cinmea, and film are unique on a showing.
I currently have this code:
def AddShowings(self, showing_times, cinema, film):
all_showings = self.session.query(Showing).options(joinedload(Showing.cinema), joinedload(Showing.film)).all()
for showing_time in showing_times:
tmp_showing = Showing(time=showing_time[0], film=film, cinema=cinema, link=showing_time[1])
if tmp_showing not in all_showings:
self.session.add(tmp_showing)
self.session.commit()
all_showings.append(tmp_showing)
which works, but seems to be very slow. Any help is much appreciated.
If any such object is unique based on a combination of columns, you need to mark these as a composite primary key. Add the primary_key=True keyword parameter to each of these columns, dropping your id column altogether:
class Showing(Base):
__tablename__ = "showings"
time = Column(DateTime, primary_key=True)
link = Column(String)
film_id = Column(Integer, ForeignKey('films.id'), primary_key=True)
cinema_id = Column(Integer, ForeignKey('cinemas.id'), primary_key=True)
That way your database can handle these rows more efficiently (no need for an incrementing column), and SQLAlchemy now automatically knows if two instances of Showing are the same thing.
I believe you can then just merge your new Showing back into the session:
def AddShowings(self, showing_times, cinema, film):
for showing_time in showing_times:
self.session.merge(
Showing(time=showing_time[0], link=showing_time[1],
film=film, cinema=cinema)
)
First, the database overview:
competitors - people who compete
competitions - things that people compete at
competition_registrations - Competitors registered for a particular competition
event - An "event" at a competition.
events_couples - A couple (2 competitors) competing in an event.
First, EventCouple, a Python class corresponding to events_couples, is:
class EventCouple(Base):
__tablename__ = 'events_couples'
competition_id = Column(Integer, ForeignKey('competitions.id'), primary_key=True)
event_id = Column(Integer, ForeignKey('events.id'), primary_key=True)
leader_id = Column(Integer)
follower_id = Column(Integer)
__table_args__ = (
ForeignKeyConstraint(['competition_id', 'leader_id'], ['competition_registrations.competition_id', 'competition_registrations.competitor_id']),
ForeignKeyConstraint(['competition_id', 'follower_id'], ['competition_registrations.competition_id', 'competition_registrations.competitor_id']),
{}
)
I have a Python class, CompetitorRegistration, that corresponds to a record/row in competition_registrations. A competitor, who is registered, can compete in multiple events, but either as a "leader", or a "follower". I'd like to add to CompetitorRegistration an attribute leading, that is a list of EventCouple where the competition_id and leader_id match. This is my CompetitorRegistration class, complete with attempt:
class CompetitorRegistration(Base):
__tablename__ = 'competition_registrations'
competition_id = Column(Integer, ForeignKey('competitions.id'), primary_key=True)
competitor_id = Column(Integer, ForeignKey('competitors.id'), primary_key=True)
email = Column(String(255))
affiliation_id = Column(Integer, ForeignKey('affiliation.id'))
is_student = Column(Boolean)
registered_time = Column(DateTime)
leader_number = Column(Integer)
leading = relationship('EventCouple', primaryjoin=and_('CompetitorRegistration.competition_id == EventCouple.competition_id', 'CompetitorRegistration.competitor_id == EventCouple.leader_id'))
following = relationship('EventCouple', primaryjoin='CompetitorRegistration.competition_id == EventCouple.competition_id and CompetitorRegistration.competitor_id == EventCouple.follower_id')
However, I get:
ArgumentError: Could not determine relationship direction for primaryjoin
condition 'CompetitorRegistration.competition_id == EventCouple.competition_id
AND CompetitorRegistration.competitor_id == EventCouple.leader_id', on
relationship CompetitorRegistration.leading. Ensure that the referencing Column
objects have a ForeignKey present, or are otherwise part of a
ForeignKeyConstraint on their parent Table, or specify the foreign_keys parameter
to this relationship.
Thanks for any help, & let me know if more info is needed on the schema.
Also, another attempt of mine is visible in following — this did not error, but didn't give correct results either. (It only joined on the competition_id, and completely ignored the follower_id)
Your leading's condition mixes expression and string to be eval()ed. And following's condition mixes Python and SQL operators: and in Python is not what you expected here. Below are corrected examples using both variants:
leading = relationship('EventCouple', primaryjoin=(
(competition_id==EventCouple.competition_id) & \
(competitor_id==EventCouple.leader_id)))
leading = relationship('EventCouple', primaryjoin=and_(
competition_id==EventCouple.competition_id,
competitor_id==EventCouple.leader_id))
following = relationship('EventCouple', primaryjoin=\
'(CompetitorRegistration.competition_id==EventCouple.competition_id) '\
'& (CompetitorRegistration.competitor_id==EventCouple.follower_id)')