SQLAlchemy many-to-many relationship on declarative tables - python

I have the following tables defined declaratively (very simplified version):
class Profile(Base):
__tablename__ = 'profile'
id = Column(Integer, primary_key = True)
name = Column(String(65), nullable = False)
def __init__(self, name):
self.name = name
class Question(Base):
__tablename__ = 'question'
id = Column(Integer, primary_key = True)
description = Column(String(255), nullable = False)
number = Column(Integer, nullable = False, unique = True)
def __init__(self, description, number):
self.description = description
self.number = number
class Answer(Base):
__tablename__ = 'answer'
profile_id = Column(Integer, ForeignKey('profile.id'), primary_key = True)
question_id = Column(Integer, ForeignKey('question.id'), primary_key = True)
value = Column(Integer, nullable = False)
def __init__(self, profile_id, question_id, value):
self.profile_id = profile_id
self.question_id = question_id
self.value = value
Profile is linked to Question via a many-to-many relationship. In the linking table (Answer) I need to store a value for the answer.
The documentation says I need to use an association object to do this but it's confusing me and I can't get it to work.
How do I define the many-to-many relationship for the Profile and Question tables using Answer as the intermediary table?

The documentation says I need to use
an association object to do this but
it's confusing me and I can't get it
to work.
That's right. And the Answer class is your association object as it maps to the association table 'answer'.
How do I define the many-to-many
relationship for the Profile and
Question tables using Answer as the
intermediary table?
The code you've presented in your question is correct. It only needs additional information about relationships on the ORM level:
from sqlalchemy.orm import relationship
...
class Profile(Base):
__tablename__ = 'profile'
...
answers = relationship("Answer", backref="profile")
...
class Question(Base):
__tablename__ = 'question'
...
answers = relationship("Answer", backref="question")
...
Also, you shouldn't setup values for profile_id and question_id in your Answer's init function, because it's the ORM that's responsible for setting them accordingly based on you assignments to relationship attributes of your objects.
You may be interested in reading documentation for declarative, especially the part about configuring relationships. Reading about working with related objects may be helpful as well.

Related

Flask-SQLAlchemy returning object in query when working with many-to-many relationship

I'm new with SQLAlch and I'm trying to do a simple query in my database but i'm getting objects in response instead of strings. My data model is the following:
wallet_tags_association = db.Table(
'wallet_tags', db.Model.metadata,
db.Column('wallet_id', db.Integer, db.ForeignKey('wallet.id')),
db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'))
)
class WalletData(db.Model):
__tablename__ = 'wallet'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20))
total_value = db.Column(db.DECIMAL)
tags = db.relationship('Tags', secondary='wallet_tags')
def __init__(self, name):
self.name = name
class Tags(db.Model):
__tablename__ = 'tags'
id = db.Column(db.Integer, primary_key=True)
tag_name = db.Column(db.String(20))
def __init__(self, tag_name):
self.tag_name = tag_name
and the query I'm trying to do is the following:
wallet_tags = WalletData.query.join(Tags, WalletData.tags).all()
for u in wallet_tags:
print(u.tags)
And this is what I got after iterating...
[<Tags: dividends>, <Tags: value>, <Tags: high yield>]
I have tried to follow the SQLAlch docs and there the approach is to use labels. Couldn't find a way to use labels when querying with Models.
Any help will be highly appreciated
Thanks in advance
The corrected query for this needs to be:
wallet_tags = WalletData.query.join(Tags, WalletData.tags).all()
for u in wallet_tags:
print(', '.join([tag.tag_name for tag in u.tags]))
Explanation: There is a many-to-many join between the two main tables (an intermediate table was constructed). The relationship expression on WalletData will pick up a list of connected Tags objects. Iterating through this collection for each WalletData object will yield the desired set of record field names.

SQLAlchemy Handling Multiple Paths In One Relationship

Please note: this question is related but separate from my other currently open question SQLAlchemy secondary join relationship on multiple foreign keys.
The SQLAlchemy documentation describes handling multiple join paths in a single class for multiple relationships:
from sqlalchemy import Integer, ForeignKey, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Customer(Base):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
name = Column(String)
billing_address_id = Column(Integer, ForeignKey("address.id"))
shipping_address_id = Column(Integer, ForeignKey("address.id"))
billing_address = relationship("Address")
shipping_address = relationship("Address")
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
street = Column(String)
city = Column(String)
state = Column(String)
zip = Column(String)
Within the same section the documentation shows three separate ways to define the relationship:
billing_address = relationship("Address", foreign_keys=[billing_address_id])
billing_address = relationship("Address", foreign_keys="[Customer.billing_address_id]")
billing_address = relationship("Address", foreign_keys="Customer.billing_address_id")
As you can see in (1) and (2) SQLAlchemy allows you to define a list of foreign_keys. In fact, the documentation explicitly states:
In this specific example, the list is not necessary in any case as there’s only one Column we need: billing_address = relationship("Address", foreign_keys="Customer.billing_address_id")
But I cannot determine how to use the list notation to specify multiple foreign keys in a single relationship.
For the classes
class PostVersion(db.Model):
id = db.Column(db.Integer, primary_key=True)
...
tag_1_id = db.Column(db.Integer, db.ForeignKey("tag.id"))
tag_2_id = db.Column(db.Integer, db.ForeignKey("tag.id"))
tag_3_id = db.Column(db.Integer, db.ForeignKey("tag.id"))
tag_4_id = db.Column(db.Integer, db.ForeignKey("tag.id"))
tag_5_id = db.Column(db.Integer, db.ForeignKey("tag.id"))
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
tag = db.Column(db.String(127))
I have tried all of the following:
tags = db.relationship("Tag", foreign_keys=[tag_1_id, tag_2_id, tag_3_id, tag_4_id, tag_5_id]) resulting in
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship AnnotationVersion.tags - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
tags = db.relationship("Tag", foreign_keys="[tag_1_id, tag_2_id, tag_3_id, tag_4_id, tag_5_id]") resulting in
sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper|AnnotationVersion|annotation_version, expression '[tag_1_id, tag_2_id, tag_3_id, tag_4_id, tag_5_id]' failed to locate a name ("name 'tag_1_id' is not defined"). If this is a class name, consider adding this relationship() to the class after both dependent classes have been defined.
And many others variations on the list style, using quotes inside and outside, using Table names and Class names.
I've actually solved the problem in the course of this question. Since there seems to be no direct documentation, I'll answer it myself instead of deleting this question.
The key is to define the relationship on a primary join and specify the uselist parameter.
tags = db.relationship("Tag", primaryjoin="or_(PostVersion.tag_1_id==Tag.id,"
"PostVersion.tag_2_id==Tag.id, PostVersion.tag_3_id==Tag.id,"
"PostVersion.tag_4_id==Tag.id, PostVersion.tag_5_id==Tag.id)",
uselist=True)

Ordering SQLAlchemy relationship by another field, not by primary key

I have the following model using sqlalchemy in a pyramid application:
class Issue(base_iop):
__tablename__ = 'issues'
issueid = Column(BIGINT, primary_key=True, name="ISSUEID")
issuenum = Column(VARCHAR(20), name="ISSUENUM")
status = Column(VARCHAR(32), name="STATUS")
datetime = Column(TIMESTAMP, name="ISSUETIME")
class Related(base_iop):
__tablename__ = 'related'
relationid = Column(BIGINT, primary_key=True, name="ROWSTAMP")
parent_num = Column(VARCHAR(20), ForeignKey('issue.ISSUENUM'),name="RECORDKEY")
children_num = Column(VARCHAR(20), ForeignKey('issue.ISSUENUM'), name="RELATEDKEY")
issues = relationship(iop, foreign_keys=[child_num])
I can get the related issues of an issue just fine using: the issues attribute of the related table:
for related in db.query(Issue).all()[0].issues:
print related.status
However, I didn't find the solution to order the issues by the datetime attribute. If the Related had the datetime attribute like this, it should be pretty straightforward using the order_by in the relationship:
class Issue(base_iop):
__tablename__ = 'issues'
issueid = Column(BIGINT, primary_key=True, name="ISSUEID")
issuenum = Column(VARCHAR(20), name="ISSUENUM")
status = Column(VARCHAR(32), name="STATUS")
datetime = Column(TIMESTAMP, name="ISSUETIME")
children = relationship("Related", foreign_keys="[Related.parent_num]", backref="parent", order_by="[Related.datetime]")
class Related(base_iop):
__tablename__ = 'related'
relationid = Column(BIGINT, primary_key=True, name="ROWSTAMP")
parent_num = Column(VARCHAR(20), ForeignKey('issue.ISSUENUM'),name="RECORDKEY")
children_num = Column(VARCHAR(20), ForeignKey('issue.ISSUENUM'), name="RELATEDKEY")
datetime = Column(TIMESTAMP, name="ISSUETIME")
issues = relationship(iop, foreign_keys=[child_num])
How do I order the related issues by another field, not by primary key like is it now?
relationship accept the keyword argument order_by to handle your default ordering of the relation.
it should be somthing like:
issues = relationship(iop, foreign_keys=[child_num], order_by="Issue.datetime")
Note that backref function has the same keyword, if you need to handle that.
Here you can read more: http://docs.sqlalchemy.org/en/rel_1_0/orm/tutorial.html#building-a-relationship on this topic

Inheritance issue in SQLAlchemy

I need some help with understanding how inheritance works in SQLAlchemy. I've created a base class for a user with some basic functionality. And then some specific users (admin, cooluser, uncooluser). Each one has unique functionality so I decided to use inheritance in SQLAlchemy. The problem is that I need to be able to upgrade a user to cooluser or uncooluser, and downgrade a cooluser to user, at any time,.
class User(Base):
__tablename__ = 'tbl_users'
__table_args__ = {'mysql_engine': 'InnoDB'}
user_id = Column(Integer, primary_key = True, unique = True, nullable = False)
user_name = Column(String(100), nullable = False, unique = True)
password = Column(String(100), nullable = False)
user_type = Column('user_type', String(50))
first_name = Column(String(50), nullable = False)
last_name = Column(String(50), nullable = False)
address = Column(String(50), nullable = False)
city = Column(String(20), nullable = False)
postal_code = Column(String(10), nullable = False)
country_id = Column(Integer, ForeignKey(Contries.country_id))
country = relationship('Country', backref = 'users')
query = Session.query_property()
__mapper_args__ = {'polymorphic_on': user_type, 'polymorphic_identity': 'User'}
class CoolUser(User):
__tablename__ = 'tbl_cool_user'
__table_args__ = {'mysql_engine': 'InnoDB'}
__mapper_args__ = {'polymorphic_identity': 'CoolUser'}
cool_user_id = Column(Integer, ForeignKey(User.user_id, ondelete = 'CASCADE'), primary_key = True)
cool_user_balance = Column(Numeric(15, 3))
Can I create a CoolUser without creating a new row in 'tbl_users', but use an existing one? Can change some setting so that when a CoolUser gets removed it just removes the entry in 'tbl_cool_user', not 'tbl_user'?
Am I missing the point of inheritance in SQLAlchemy?
Am I missing the point of inheritance in SQLAlchemy?
I think that you misusing the inheritance concept in general, and not even in SA implementation specifics.
In a classical Animal (Cat, Dog (Terier)) type of hierarchy would you ever imagine that a Cat would be up/down-graded to a Dog, or even a specific type of a Dog? I don't think so. Also, I can well imagine that some CoolUser could also be an Administrator at the same time. How will you resolve this type of relationship?
Therefore, for your requirements, inheritance is just not the concept to employ to solve this state-changing model. I would suggest to google for User-Role type of relationships and implementations. You would still have to resolve the issue of storing Role-specific data thought.
As #aquavitae mentioned: in SA, you can hack-around and change the user_type. But be aware that in this case the following will happen:
when you load the object from the database, its class will reflect the new type (GOOD)
when you downgrade the object (from CoolUser to User), the row which corresponds to the CoolUser will not be deleted (i think it is BAD, but it might be OK)
when you upgrade the object (from User to CoolUser), no new row for the CoolUser table will be created, and all your values will be NULL. As such, setting/adding any property that is stored in the non-created row will throw a nice error. And when queries for the specific subclass, you will not recieve the object back as the INNER JOIN is used to retrieve the object. (BAD)
in summary, do not change a Cat to a Dog
I don't think you should be using 'polymorphic_on' and 'polymorphic_identity' in the same table. What you need to do is create a base table User containing all users (i.e. with mapper_args 'polymorphic_on'), then CoolUser subclassed from that (with 'polymorphic_identity'):
class User(Base):
...
__mapper_args__ = {'polymorphic_on': user_type}
class CoolUser(User):
...
__mapper_args__ = {'polymorphic_identity': 'CoolUser'}
Then you can change user_type as you like.
You have to use Concrete Table Inheritance. All all common attribute in base table and create 2 different CoolUser and UnCoolUser.

How to set an SQLAlchemy relationship up to access a field value instead of the related instance

The following totally incomplete snippet defines a basic SQLAlchemy relationship using declarative syntax...
Base = declarative_base()
class Movie(Base):
__tablename__ = 'movies'
id = Column(Integer, primary_key=True)
name = Column(String)
director = relationship("People", uselist = False)
class People(Base):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
name = Column(String, nullable = false)
To access the director name it would be something like:
assert isinstance(movie, Movie) # <-- retrieved with query or whatever
director_name = movie.director.name
If, for convenience, I always want the director relationship to just give me the director's name, rather than a People instance, how do you do this? eg: it should work just like this:
assert isinstance(movie, Movie)
director_name = movie.director # <-- should get the string directly
I'm 99% sure I've done this before but can't find any reference code or documentation on it anymore. I'm going a bit crazy trying to locate it. Stack Overflow will be a good/permanent reference location for the answer.
The association proxy is used for all kinds of "object reference-> attribute reference" styles of transformation on the Python side. Docs have been newly updated and rewritten:
http://www.sqlalchemy.org/docs/orm/extensions/associationproxy.html
What if you use property?
class Movie(Base):
__tablename__ = 'movies'
id = Column(Integer, primary_key=True)
name = Column(String)
_director = relationship("People", uselist = False)
#property
def director_name(self):
return self._director.name

Categories

Resources