Inheritance issue in SQLAlchemy - python

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.

Related

SQLAlchemy: Subclassed model db entry gets properties of other subclassed model

I'm having an issue with SQLAlchemy in a Flask app where I have two models Instructor and Student that subclass the same model User. When I'm creating a Student object, it's listed in the database under User with properties that should be unique for Instructor. Here are my (simplified) classes:
class User(db.Model):
__tablename__ = "user"
discriminator = db.Column("type", db.String(50)) # Student or Instructor
__mapper_args__ = {"polymorphic_on": discriminator}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
class Instructor(User):
__mapper_args__ = {"polymorphic_identity": "instructor"}
reputation = db.Column(db.Integer, default=1)
approved_for_teaching = db.Column(db.Boolean, default=False)
class Student(User):
__mapper_args__ = {"polymorphic_identity": "student"}
lessons_taken = db.Column(db.Integer, default=0)
I'm creating a Student like this:
new_student = Student(name=user_name)
db.session.add(new_student)
db.session.commit()
But when inspecting the object in the database, the student gets attributes from the Instructor model, i.e. reputation (value is 1) and approved_for_teaching (value is False). Am I doing something wrong here, or is this expected behaviour? I could understand if the columns would have to be present, since they share the same table (User) in the db, but then I'd expect the values to be null or something. Thanks!
It is expected behaviour, since you're using single table inheritance:
Single table inheritance represents all attributes of all subclasses within a single table. A particular subclass that has attributes unique to that class will persist them within columns in the table that are otherwise NULL if the row refers to a different kind of object.
Though the documentation mentions leaving the columns belonging to other classes NULL, the client side defaults you've given the columns kick in during inserts to user table, which underlies all the classes inheriting from User, even when they're not a part of the particular subclass' columns. A context sensitive default function could perhaps be used to avoid that, for example:
def polymorphic_default(discriminator, identity, value):
def default(context):
# This should be replaced with context.get_current_parameters() in 1.2 and above
if context.current_parameters[discriminator.name] == identity:
return value
return default
...
class Instructor(User):
__mapper_args__ = {"polymorphic_identity": "instructor"}
reputation = db.Column(
db.Integer, default=polymorphic_default(User.discriminator, 'instructor', 1))
approved_for_teaching = db.Column(
db.Boolean, default=polymorphic_default(User.discriminator, 'instructor', False))
but that seems like a lot of work to avoid a rather small issue.

Sqlalchemy - how to get an object, and filter which rows in the child class

I’m new to Sqlalchemy, and in need of some help.
i have a model, with a one to many relation:
class Metnadev(DeclarativeBase):
__tablename__ = 'metnadev'
#personal info
id = Column(Integer, autoincrement=True, primary_key=True)
first_name = Column(Unicode(255))
last_name = Column(Unicode(255))
birth_day = Column(Date)
activitys = relationship("Activity", backref="metnadev")
class Activity(DeclarativeBase):
__tablename__ = 'activity'
id = Column(Integer, autoincrement=True, primary_key=True)
metnadev_id = Column(Integer, ForeignKey('metnadev.id'))
location = Column(Unicode(255))
visible = Column(Boolean,default=True)
When I do
metnadev = DBSession.query(Metnadev).filter_by(id=kw['id']).one()
I get the object, with the child, great.
I want to get the object, but only get the rows from the child class, where visible == True
I searched but I’m not sure how to do it,
Thanks for the help
This section of the documentation has your answer: http://docs.sqlalchemy.org/en/rel_0_6/orm/relationships.html#building-query-enabled-properties
class Metnadev(DeclarativeBase):
#...
#property
def activities(self):
return object_session(self).query(Activity).filter_by(visible=True).with_parent(self).all()
There's a couple ways you can do this.
For a one-off, you can just run a second query:
from sqlalchemy import and_
activities = Activity.query.filter(and_(Activity.metnadev_id == kw['id'], Activity.visible==True)).all()
You can change the relationship so only visible items are returned:
activities = relationship("Activity",
primaryjoin="and_(Activity.metnadev_id==Metnadev.id, "
"Activity.visible==True)")
If you need more control you can join the tables, but it sounds like the relationship configuration would work for you. Let me know if that's not the case.
Hope that helps!

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

SqlAlchemy Select Relation Type

I have a simple One-to-Many relation mapped with SqlAlchemy:
Base = declarative_base()
class Type(Base):
__tablename__ = "entity_types"
type = Column(String(100), primary_key=True)
description = Column(String(300))
class Entity(Base):
__tablename__ = "entities"
id = Column(Integer, primary_key=True)
type_id = Column('type', String(100), ForeignKey(Types.type),
nullable=False)
type = relation(Type, backref='entities')
value = Column(Text, nullable=False)
I want to query all types ever used in an entity. In pure SQL I would accomplish this by:
SELECT entity_types.*
FROM entities
JOIN entity_types ON entities.type == entity_types.type
GROUP BY entity_types.type
How do I solve this using SqlAlchemy's ORM-Engine?
I've tried these queries, but they all don't return what I want:
session.query(Action.type).group_by(Action.type).all()
session.query(Type).select_from(Action).group_by(Type).all()
I've also tried using options(joinedload('type')), but I found out, this is only used to force eager loading and to bypass lazy-loading.
ADDITION: I've just added the backref in the relation of Entity. I think the problem is solvable by querying count(Type.entities) > 0, but I cannot figure out how to exactly form a valid ORM query.
I've just figured it out:
session.query(ActionType).filter(ActionType.actions.any()).all()
The any() does the trick.

SQLAlchemy many-to-many relationship on declarative tables

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.

Categories

Resources