Python + SqlAlchemy 'init' with single table inheritance - python

got problems with a Sqlalchemy thing.
I've defined my database, relevant parts as per below...
class Person(Base):
__tablename__ = 'PERSON'
#
id = Column(Integer, primary_key=True)
person_type = Column(String(32), nullable=False)
name = Column(String(50))
address = Column(String(120))
phonenum = Column(String(20))
__mapper_args__ = {'polymorphic_on':person_type}
#
class Student(Person):
__mapper_args__ = {'polymorphic_identity': 'Student'}
dob = Column(Date)
I've other subclasses of 'Person' too. I'm having problems with the way the 'init' should look. My last attempt was..
for 'Person'..
def __init__(self, a_name, a_address, a_phonenum, a_person_type = None):
self.name = a_name
self.address = a_address
self.phonenum = a_phonenum
and for 'Student'..
def __init__ (self, a_name, a_address,a_phonenum, a_dob=None, a_stud_caregiver_id=None, a_person_type = 'Student'):
self.name = a_name
self.address = a_address
self.phonenum = a_phonenum
self.dob = a_dob
self.stud_caregiver_id = a_stud_caregiver_id
self.person_type = a_person_type
but though this creates the database ok, when I come to 'init' my students, I'm getting messages along the lines of...
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Original exception was: Class object expected, got 'Table('PERSON', MetaData(bind=None)....
I've tried with/without the person_type parameter .. but really, I'm just shooting in the dark.
I've obviously done something dumb, but what? Thanks!

Related

Accessing relationships within the after_insert and before_insert event hooks when using SQLAlchemy

I have two very related questions about manipulating relationships in SQLAlchemy event hooks. Note I'm using flask-sqlalchemy and essentially the same model as below.
1) edit I'm trying to add an event listener for the Record object so that I do something to the people in a Record instance after it has been created by the Record default initializer. The only problem is target in the do_action body doesn't have the people list appended, whereas the people list is present after the instance is created. What's the reason for this and is there a workaround for do_action?
My model:
record_structure = db.Table(
'record_structure',
db.Column('person_id', db.Integer, db.ForeignKey('person.id')),
db.Column('record_id', db.Integer, db.ForeignKey('record.id')))
class Person(db.Model):
__tablename__ = 'person'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, nullable=False)
class Record(db.Model):
__tablename__ = 'record'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, nullable=False)
people = db.relationship('Person', secondary=record_structure)
def __init__(self, name, people):
self.name = name
self.people = list()
self.people.extend(people)
#db.event.listens_for(Record, 'after_insert')
def do_action(mapper, connection, target):
print(target.name) # output 'my record'
print(len(target.people)) # output: 0 ??
# do something with target.people
My load_db.py
p1 = Person(name='tom')
p2 = Person(name='fred')
db.session.add(p1)
db.session.add(p2)
db.session.commit()
r = Record('my record', [p1, p2])
print(len(r.people)) # output: 2
db.session.add(r)
db.session.commit()
2) If there is a workaround for the 'after_insert' event hook. Is there a workaround if I replace the hook with a 'before_insert' event hook?
So it turns out the code I provided in 1) is completely fine so len(target.people) == 2 and 2) holds as well. My original code was slightly different and that's what caused the len(target.people) == 0. The bookkeeper.records.append(self) seems to have caused a premature insert and caused self.people to initialize as empty list. Thanks #SuperShoot for trying to reproduce it.
class Record(db.Model):
__tablename__ = 'record'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, nullable=False)
people = db.relationship('Person', secondary=record_structure)
def __init__(self, name, bookkeeper, people):
self.name = name
bookkeeper.records.append(self)
self.people = list()
self.people.extend(people)
I guess a good practice is to put lines that pass self like bookkeeper.records.append(self) at the end of __init__ so relevant data is passed to event hooks.

When do relationships / backrefs become usable

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.

Flask SQLAlchemy filter records from foreign key relationship

I have 2 models:
class Scenario(db.Model):
__tablename__ = 'scenarios'
scenario_id = db.Column(db.Integer, primary_key=True)
scenario_name = db.Column(db.String(120))
scenario_text = db.Column(db.Text)
hints = db.relationship('Hint', backref='scenario', lazy='dynamic')
def __init__(self, scenario_name, scenario_text):
self.scenario_name = scenario_name
self.scenario_text = scenario_text
def __repr__(self):
return "<Scenario(scenario_name='%s', scenario_text='%s', hints='%s')>" % self.scenario_name, self.scenario_text, self.hints
class Hint(db.Model):
__tablename__ = 'hints'
hint_id = db.Column(db.Integer, primary_key=True)
scenario_id = db.Column(db.Integer, db.ForeignKey('scenarios.scenario_id'))
hint = db.Column(db.Text)
release_time = db.Column(db.Integer)
def __init__(self, scenario_id, hint, release_time):
self.scenario_id = scenario_id
self.hint = hint
self.release_time = release_time
def __repr__(self):
return "<Hint(scenario_id='%s', hint='%s', release_time='%s')>" % self.scenario_id, self.hint, self.release_time
I want to be able to get all the scenarios with their corresponding hints but only the hints that have a release_time less than the current time.
I figured this would work:
scenarios = Scenario.query.filter(Scenario.hints.release_time < time.time())
But I get this error:
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Scenario.hints has an attribute 'release_time'
I just started playing around with Flask and SQLAlchemy. Any advice would be appreciated.
Scenario.hints is a query, so you'll need to use a join to perform this kind of filtering.
>>> scenarios = Scenario.query.join(Hint).filter(Hint.release_time < time.time())
>>> scenarios.first()
>>> <Scenario(scenario_name='hi', scenario_text='world', hints='SELECT hints.hint_id AS hints_hint_id, hints.scenario_id AS hints_scenario_id, hints.hint AS hints_hint, hints.release_time AS hints_release_time FROM hints WHERE :param_1 = hints.scenario_id')>
See the query docs and the ORM tutorial for more details.

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

SQLAlchemy ForeignKey can't find table

Getting this error when I try to instantiate the ConsumerAdvice class.
Foreign key associated with column 'tbConsumerAdvice.ConsumerAdviceCategory_ID'
could not find table 'tbConsumerAdviceCategories' with which to generate a
foreign key to target column 'ID_ConsumerAdviceCategories'
class ConsumerAdviceCategory(Base):
__tablename__ = 'tbConsumerAdviceCategories'
__table_args__ = {'schema':'dbo'}
ID_ConsumerAdviceCategories = Column(INTEGER, Sequence('idcac'),\
primary_key=True)
Name = Column(VARCHAR(50), nullable=False)
def __init__(self,Name):
self.Name = Name
def __repr__(self):
return "< ConsumerAdviceCategory ('%s') >" % self.Name
class ConsumerAdvice(Base):
__tablename__ = 'tbConsumerAdvice'
__table_args__ = {'schema':'dbo'}
ID_ConsumerAdvice = Column(INTEGER, Sequence('idconsumeradvice'),\
primary_key=True)
ConsumerAdviceCategory_ID = Column(INTEGER,\
ForeignKey('tbConsumerAdviceCategories.ID_ConsumerAdviceCategories'))
Name = Column(VARCHAR(50), nullable=False)
Category_SubID = Column(INTEGER)
ConsumerAdviceCategory = relationship("ConsumerAdviceCategory",\
backref=backref('ConsumerAdvices'))
def __init__(self,Name):
self.Name = Name
def __repr__(self):
return "< ConsumerAdvice ('%s') >" % self.Name
Define the FK including schema: dbo.tbConsumerAdviceCategories.ID_ConsumerAdviceCategories
I also hit this error. In my case the root cause was that I attempted to define different sqlalchemy base classes:
Base1 = declarative_base(cls=MyBase1)
Base1.query = db_session.query_property()
Base2 = declarative_base(cls=MyBase2)
Base2.query = db_session.query_property()
I had a ForeignKey relationship from one class that derives from Base1 to another class that derives from Base2. This didn't work -- I got a similar NoReferencedTableError. Apparently classes must derive from the same Base class in order to know about each other.
Hope this helps someone.
That didn't solve my problem, I had to use.
ConsumerAdviceCategory_ID = Column(INTEGER,
ForeignKey('tbConsumerAdviceCategories.ID_ConsumerAdviceCategories',
schema='dbo'))

Categories