When do relationships / backrefs become usable - python

I'm having a difficult time understanding how relationships / backrefs work.
I seem to be missing the point regarding how to make them 'live' so I keep getting errors like:
'NoneType' object has no attribute 'decid'.
These tables are 1 to 1 forming a heirachy.
I have an SQLite db and the following classes defined.
class Person(DECLARATIVE_BASE):
__tablename__ = 'person'
__table_args__ = ({'sqlite_autoincrement': True})
idperson = Column(INTEGER, autoincrement=True,
primary_key=True, nullable=False)
lastname = Column(VARCHAR(45), index=True, nullable=False)
firstname = Column(VARCHAR(45), index=True, nullable=False)
def __repr__(self):
return self.__str__()
def __str__(self):
return "<Person(%(idperson)s)>" % self.__dict__
class Schoolmember(DECLARATIVE_BASE):
__tablename__ = 'schoolmember'
person_id = Column(INTEGER, ForeignKey("person.idperson"),
index=True, primary_key=True, nullable=False)
decid = Column(VARCHAR(45), unique=True, nullable=False)
type = Column(VARCHAR(20), nullable=False)
person = relationship("Person", foreign_keys=[person_id],
backref=backref("schoolmember", uselist=False))
def __repr__(self):
return self.__str__()
def __str__(self):
return "<Schoolmember(%(person_id)s)>" % self.__dict__
class Student(DECLARATIVE_BASE):
__tablename__ = 'student'
person_id = Column(INTEGER, ForeignKey("schoolmember.person_id"),
autoincrement=False, primary_key=True, nullable=False)
studentnum = Column(VARCHAR(30), unique=True, nullable=False)
year = Column(INTEGER, nullable=False)
graduated = Column(BOOLEAN, default=0, nullable=False)
schoolmember = relationship("Schoolmember", foreign_keys=[person_id],
backref=backref("student", uselist=False))
def __repr__(self):
return self.__str__()
def __str__(self):
return "<Student(%(person_id)s)>" % self.__dict__
I don't understand why here I can't access schoolmember from Student.
I was expecting declaritive to cascade up the relationships.
newstu = Student()
newstu.studentnum = '3456'
newstu.schoolmember.decid = 'fred.frog' # Error, 'NoneType' object
The following works, but only by stomping over the relationships defined in the class?
Do I need to do it this way?
s = Schoolmember(decid = 'fred.frog')
newstu = Student(schoolmember=s, studentnum='3456')
I don't 'get' what's is going on. I'm trying to understand the principals involved so I don't get bamboozled by the next problem

The reason your first example doesn't work is because when you initialize student there is no schoolmember associated with it. SQLAlchemy doesn't automatically generate this for you. If you wanted to, every time you create a Student object for it to automatically create a new schoolmember, you could do that inside of an __init__. In addition, if you wanted it to work you could do something like:
student = Student()
schoolmember = Schoolmember()
student.studentnum = 3456
student.schoolmember = schoolmember
student.schoolmember.decid = 'fred.frog'
An __init__ method could help also, if this is behavior you want every time.
def __init__(self, studentnum=None, year=None, graduated=None, schoolmember=None):
# If no schooolmember was provided, automatically generate one.
self.schoolmember = schoolmember or Schoolmember()
Hope this helps.

Related

Update a value based on Join FlaskSqlALchemy

I have 2 tables in db.
class Vehicle(db.Model):
__tablename__ = "vehicles"
id = db.Column(db.Integer, primary_key=True, nullable=False)
num_plate = db.Column(db.String(7), nullable=False, unique=True)
type = db.Column(db.String, nullable=False)
suspicious = db.Column(db.Boolean, nullable=False, default=False)
def __repr__(self) -> str:
return f"Vehicle(number playe={self.num_plate}, type={self.type}, suspicious={self.suspicious})"
class Registered(db.Model):
"""
Registered User
"""
__tablename__ = "registered"
regid = db.Column(db.Integer, primary_key=True, nullable=False)
name = db.Column(db.String, nullable=False)
cnic = db.Column(db.String, nullable=False)
contactno = db.Column(db.String, nullable=False)
gender = db.Column(db.String, nullable=False)
dor = db.Column(db.DateTime, nullable=False)
doe = db.Column(db.DateTime, nullable=False)
vehicle_id = db.Column(
db.Integer, db.ForeignKey("vehicles.id"), unique=True, nullable=False
)
Now I want to update a specific person's car num_plate. Let's say person with regid 3.
I can get his data.
reg_visitor = (
Registered.query.filter_by(regid=3)
.join(Vehicle)
.add_columns(
Registered.name,
Registered.cnic,
Registered.contactno,
Registered.gender,
Registered.dor,
Registered.doe,
Vehicle.num_plate,
Vehicle.suspicious,
)
).first()
It is of type
<class 'sqlalchemy.engine.row.Row'>
If I print it's attribute, it gives correct answer.
print(reg_visitor.num_plate)
output is
ANB 127
Now If I want to update this attribute, I can not.
reg_visitor.num_plate = "ABC1234"
ERROR:
reg_visitor.num_plate = "ABC1234"
File "/home/ahmad/Desktop/FYP/venv/lib/python3.7/site-packages/sqlalchemy/engine/row.py", line 219, in __setattr__
raise AttributeError("can't set attribute")
AttributeError: can't set attribute
My Flask-SQL Alchemy version is '2.5.1', and SQLAlchemy version is 1.4.29.
I do not want to mess up with my versions, is there any other way to update the attribute based on join?
It works well on single tables though.
vehicle = Vehicle.query.filter_by(num_plate="ANB 127").first()
vehicle.num_plate = "ABC123"
db.session.commit()
vehicle = Vehicle.query.filter_by(num_plate="ABC123").first() # works well
print(vehicle)
As you pointed out, the result of your query reg_visitor is a sqlalchemy.engine.row.Row, meaning it does not map to a single class.
When you try to update an attribute in it, SQLAlchemy doesn't know how it could update the tables based on the query.
You need to query the Vehicle directly (like again you've shown) to be able to update.
add this column to your Vehicle Class
registers = db.relationship('Registered', backref='vehicles',lazy='dynamic')
read about that in SqlAlchemy Relationship Docs
after that you can execute
reg_visitor=db.session.query(Vehicle).join(Registered,Registered.regid==3).one_or_none()
reg_visitor.num_plate=2331
db.session.commit()

How to correctly define relationships in a Python SQLAlchemy class with multiple base classes?

I'm trying to recreate my database using SQLAlchemy and Flask.
I've created all the models, but now have some problems with the relationships between the models. When inserting or updating an object Flask returns the following error message:
File "C:\Users\Lenna\SchoolMi\api-server-v4\venv\lib\site-packages\sqlalchemy\ext\declarative\clsregistry.py", line 326, in __call__
x = eval(self.arg, globals(), self._dict)
File "<string>", line 1, in <module>
# ext/declarative/clsregistry.py
AttributeError: 'Table' object has no attribute 'id'
The error message references to the active_channel relationship in the profile class and indicates that the channel class has no id attribute. However I've already defined this attribute in the ObjectWithDefaultProps class. After inspection of the SQL file in a SQL browser, the id attribute is indeed present on the Channel entity.
My first assumption was a misconfiguration of the foreignkey, so I tried to change the foreignkey:
active_channel = db.relationship("Channel", foreign_keys="Channel.id")
instead of
active_channel = db.relationship("Channel", foreign_keys="channel.id")
Unfortunately, this did not work. The error changes to the following:
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on
relationship Profile.active_channel - there are no foreign keys linking these tables. Ensure that referencing
columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression. //
Werkzeug Debugger
I've looked further into the polymorphism aspects of SQLAlchemy and thought it might had something to do with the mapperargs but couldn't figure out the correct way of implementing this.
In my code I have the following classes:
My entities derive from multiple objects, that represent shared attributes or relationships.
class Profile(db.Model, ObjectWithDefaultProps, ObjectWithAvatar, ObjectWithNotificationProfile):
__tablename__ = "profile"
firebase_uid = db.Column(db.String, unique=True, nullable=False, primary_key=True)
username = db.Column(db.String, unique=True, nullable=False)
firstname = db.Column(db.String, nullable=False)
lastname = db.Column(db.String, nullable=False)
about = db.Column(db.String)
score = db.Column(db.Integer)
email = db.Column(db.String, nullable=False)
active_channel_id = db.Column(db.Integer, db.ForeignKey("channel.id"))
active_channel = db.relationship("Channel", foreign_keys="channel.id")
Channel.py
class Channel(db.Model, ObjectBase, ObjectWithAvatar, ObjectWithName, ProfileLinkedObject):
__tablename__ = "channel"
description = db.Column(db.String)
can_add_tags = db.Column(db.Boolean, default=False, nullable=False)
can_public_join = db.Column(db.Boolean, default=False, nullable=False)
from database.provider import db
from datetime import datetime
class ObjectWithDefaultProps:
deleted = db.Column(db.Boolean, nullable=False, default=False)
date_modified = db.Column(db.DateTime, default=datetime.utcnow)
date_added = db.Column(db.DateTime, onupdate=datetime.utcnow)
from database.provider import db
from database.extensions.object_with_color import ObjectWithColor
class ObjectWithAvatar(ObjectWithColor):
image_url = db.Column(db.String)
from database.provider import db
class ObjectWithColor:
color_index = db.Column(db.Integer, default=0)
from database.provider import db
from sqlalchemy.ext.declarative import declared_attr
class ObjectWithNotificationProfile:
auto_follow_questions = db.Column(db.Integer)
auto_follow_answers = db.Column(db.Integer)
auto_follow_comments = db.Column(db.Integer)
auto_follow_questions_on_comment = db.Column(db.Integer)
auto_follow_questions_on_answer = db.Column(db.Integer)
auto_follow_answers_on_comment = db.Column(db.Integer)
send_new_data_notification = db.Column(db.Boolean)
send_new_members_notification = db.Column(db.Boolean)
#declared_attr
def question_event_preferences_id(cls):
return db.Column(db.Integer, db.ForeignKey("custom_event_preferences.id"))
#declared_attr
def question_event_preferences(cls):
return db.relationship("CustomEventPreferences", foreign_keys="custom_event_preferences.id")
#declared_attr
def answer_event_preferences_id(cls):
return db.Column(db.Integer, db.ForeignKey("custom_event_preferences.id"))
#declared_attr
def answer_event_preferences(cls):
return db.relationship("CustomEventPreferences", foreign_keys="custom_event_preferences.id")
#declared_attr
def comment_event_preferences_id(cls):
return db.Column(db.Integer, db.ForeignKey("custom_event_preferences.id"))
#declared_attr
def comment_event_preferences(cls):
return db.relationship("CustomEventPreferences", foreign_keys="custom_event_preferences.id")
#declared_attr
def question_tagging_preferences_id(cls):
return db.Column(db.Integer, db.ForeignKey("custom_tagging_preferences.id"))
#declared_attr
def question_tagging_preferences(cls):
return db.relationship("CustomTaggingPreferences", foreign_keys="custom_tagging_preferences.id")
#declared_attr
def answer_tagging_preferences_id(cls):
return db.Column(db.Integer, db.ForeignKey("custom_tagging_preferences.id"))
#declared_attr
def answer_tagging_preferences(cls):
return db.relationship("CustomTaggingPreferences", foreign_keys="custom_tagging_preferences.id")
#declared_attr
def comment_tagging_preferences_id(cls):
return db.Column(db.Integer, db.ForeignKey("custom_tagging_preferences.id"))
#declared_attr
def comment_tagging_preferences(cls):
return db.relationship("CustomTaggingPreferences", foreign_keys="custom_tagging_preferences.id")
For those facing the same or similar issues as described in my question:
I've managed to get my code working by replacing the string variables in the foreign_keys attribute with the column variables itself.
After testing, I found that using a string as foreign_keys attribute always trigged an error for me, but the alternative methods using primaryjoin and foreign_keys=cls.foreign_key worked.
#declared_attr
def comment_event_preferences(cls):
return db.relationship("CustomEventPreferences", foreign_keys="custom_event_preferences.id")
becomes:
#declared_attr
def comment_event_preferences(cls):
return db.relationship("CustomEventPreferences", foreign_keys=cls.comment_event_preferences)

How to get parrent of object in session SQLAlchemy

I have two tables:
class Project(DataBase):
__tablename__ = 'projects'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
name = Column(String, nullable=False, unique=True)
domain = Column(String, nullable=False)
phrases = relationship("Phrase", backref='proj')
def __init__(self, name, domain):
self.name = name
self.domain = domain
class Phrase(DataBase):
__tablename__ = 'phrases'
query_id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
query_text = Column(String, nullable=False)
project = Column(Integer, ForeignKey('projects.id'), nullable=False)
enable = Column(Boolean, nullable=False, default=True)
def __init__(self, query_text, project, city):
self.query_text = query_text
self.project = project
And I have a function:
def get_first_query():
session = Session(bind=engine)
q = session.query(Phrase).filter(Phrase.enable == True).first()
session.close()
return q
I want to get an object from table 2 and than get its parrent from first table:
session = Session(bind=engine)
q = get_first_query()
print(q.proj)
It doesn't work and print this error:
sqlalchemy.orm.exc.DetachedInstanceError: Parent instance is not bound to a Session; lazy load operation of
attribute 'proj' cannot proceed
I can do this:
session = Session(bind=engine)
q = get_first_query()
q_project = session.query(Project).filter(Project.id == q.project)
But it's a bad way.
You can assess related object via proj attribute.
session.query(Phrase).filter(
Phrase.enable == True
).first().proj
This way you'll hit database one additional time to get it so you'll need to open session again. To avoid additional queries you can use joined load:
session.query(Phrase).filter(
Phrase.enable == True
).options(
joinedload('proj')
).first().proj

Query One to Many Relationship SQLAlchemy

I am trying to query the users based upon their skills from these tables.
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
username = db.Column(db.String(64), unique=True, index=True)
skills = db.relationship('Skill', backref='author', lazy='dynamic')
class Skill(db.Model):
__tablename__ = 'skills'
id = db.Column(db.Integer, primary_key=True)
skill = db.Column(db.String(64), index=True)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
i tried this in User table and got this error.
#classmethod
def users_by_skill(cls, skill):
return User.query.join(Skill).filter(skill.skill).all()
AttributeError: 'str' object has no attribute 'skill'
Where i am missing badly?
You define the following class method:
#classmethod
def users_by_skill(cls, skill):
return User.query.join(Skill).filter(skill.skill).all()
You are probably expecting to use this function like so:
users = Users.users_by_skill('nunchuk')
That means the skill argument in users_by_skill is a string. Then, you try to use skill.skill, which essentially is like doing 'nunchuk'.skill. Python does not have a skill attribute on the string class, hence the error.
The filter function actually takes a Criteria object. In other words, you don't pass it a value like "filter", you instead pass it a criterion that represents the notion of "the skill column on the Skill table must equal 'nunchuk'". You can do this using syntax like the following:
#classmethod
def users_by_skill(cls, skill_name):
return User.query.join(Skill).filter(Skill.skill == skill_name).all()

SQLAlchemy Using relationship()

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

Categories