I have follow model of my DB:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
adr = relationship('Address', backref='uuu')
class Address(Base):
__tablename__ = 'addresses'
id = Column(Integer, primary_key=True)
email = Column(String, nullable=False)
# user_id = Column(Integer)
user_id = Column(Integer, ForeignKey('users.id'))
Base.metadata.create_all(engine)
answer = sess.query(User).first()
print(answer.adr)
It's print:
[<__main__.Address object at 0x7fed81592e50>]
But by the docs it should print value instead address.
The above configuration establishes a collection of Address objects on User called User.addresses. It also establishes a .user attribute on Address which will refer to the parent User object.
I tried follow code:
answer = sess.query(User).first()
print(answer.adr.email)
Error:
AttributeError: 'InstrumentedList' object has no attribute 'email'
What it prints is absolutely correct.
If you want values of individual columns, you need to print for example answer.adr.email. And note that answer.adr is a list, not an object, so you need to iterate through it as well.
Related
I seem to have a hard time locating an issue I have with the following many-to-many relationship:
class UserTable(Base):
__tablename__ = "user"
id = Column(String, primary_key=True)
name = Column(UnicodeText)
courses = relationship("CourseTable", secondary="user_course")
class CourseTable(Base):
__tablename__ = "course"
id = Column(String, primary_key=True)
title = Column(UnicodeText)
users = relationship("UserTable", secondary="user_course")
class UserCourseTable(Base):
__tablename__ = "user_course"
user_id = Column(String, ForeignKey('user.id'), primary_key=True)
course_id = Column(String, ForeignKey('course.id'), primary_key=True)
user = relationship("UserTable", backref=backref("user_course"))
course = relationship("CourseTable", backref=backref("user_course"))
When querying for User
user = await self.session.execute(
select(UserTable)
.where(UserTable.id == user_id)
)
I get the following error:
When initializing mapper mapped class UserCourseTable->user_course, expression 'CourseTable' failed to locate a name ('CourseTable'). If this is a class name, consider adding this relationship() to the <class 'app.models.db.user_course.UserCourseTable'> class after both dependent classes have been defined.
I tried everything described in their docs, but to no avail: the error still persists
Any suggestions?
Thanks!
EDIT:
SQLAlchemy==1.4.36
Suppose we have a simple one-to-many relationship between Company and Employee, is there a way to query all companies and have a list of employees in the attribute of each company?
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
company_id = Column(Integer, ForeignKey(Company.id))
I'm looking for something like this:
>>> result = db.session.query(Company).join(Employee).all()
>>> result[0].Employee
[<Employee object at 0x...>, <Employee object at 0x...>]
The size of result should be same as the number of rows in company table.
I tried the following and it gives tuple of objects (which makes sense) instead of nice parent / child structure:
>>> db.session.query(Company, Employee).filter(Company.id = Employee.company_id).all()
It's not hard to convert this into my desired object structure but just wanted to see if there's any shortcut.
You have to configure the relationship in the parent class:
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
employees = relationship('Employee', lazy='joined') # <<< Add this line
Then you can query it without a join:
companies = session.query(Company).all()
print(companies[0].employees)
Documentation:
https://docs.sqlalchemy.org/en/13/orm/loading_relationships.html
You could do something like this:
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
employees = db.session.query(Company, Employee).filter(Company.id = self.id).all()
self.employee_list = ['{0} {1}'.format(c.first_name, c.last_name) for c in employees]
Then you could access a employee name with Company.employee_list[0]
I am trying to use an autoincrementing unique ID field as a foreign key in other tables. My model is below:
class User(Base):
__tablename__ = 'Users'
uid = Column(INT, primary_key=True, autoincrement=True)
name = Column(TEXT)
email = Column(TEXT)
dateRegistered = Column(TIMESTAMP)
phone = Column(TEXT)
class AddressMap(Base):
__tablename__ = 'AddressMaps'
uid = Column(INT, primary_key=True, autoincrement=True)
userId = Column(INT, ForeignKey('Users.uid'))
addressId = Column(INT, ForeignKey('Addresses.uid'))
dateCreated = Column(TIMESTAMP)
user = relationship("User", backref=backref('AddressMaps'))
address = relationship("Address", backref=backref('AddressMaps'))
class Address(Base):
__tablename__ = 'Addresses'
uid = Column(INT, primary_key=True, autoincrement=True)
street = Column(TEXT)
city = Column(TEXT)
state = Column(TEXT)
postal = Column(TEXT)
dateRegistered = Column(TIMESTAMP)
My problem is that when I create a user object, it is not created with a uid value. I understand this is because the object has not yet been committed to the database. The problem is that since the User object has None as the uid value, I have no way of linking the AddressMap object with it.
What is the idiomatic way of handling this problem in SQLAlchemy?
You shouldn't need a uid. Since you have created a relationship between the tables, you will be able to add an AddressMap object to the user:
samantha = User(name='Sam', email='sam#xyz.abc', phone='555-555-5555')
You now have access to the samantha.AddressMaps collection (see your backref under your user relationship in your AddressMap table). You can add AddressMap objects to this collection:
samantha.AddressMaps = [AddressMap(dateCreated=datetime.now()),
AddressMap(dateCreated=datetime.min)]
Now you can add these objects to your session and commit. See the docs for more info.
Incidentally, just as an FYI, you don't need to include autoincrement=True on the first integer column of a table.
I have the following tables configured:
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
class Gadget(Base):
__tablename__ = "gadget"
id = Column(Integer, primary_key=True)
brand = Column(String)
class UserGadget(Base):
__tablename__ = "user_gadget"
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
gadget_id = Column(Integer, ForeignKey('gadget.id'), primary_key=True)
user = relationship("User", backref=backref('userGadgets', order_by=user_id))
gadget = relationship("Gadget", backref=backref('userGadgets', order_by=gadget_id))
class GadgetComponent(Base):
__tablename__ = "gadget_component"
id = Column(String, primary_key=True)
gadget_id = Column(Integer,ForeignKey('gadget.id'))
component_maker = Column(String)
host = relationship("Gadget", backref=backref('components', order_by=id))
class ComponentUsingMetal(Base):
__tablename__ = "component_metal"
id = Column(Integer, primary_key=True)
component_id = Column(Integer, ForeignKey('GadgetComponent.id'))
metal = Column(String)
component = relationship("GadgetComponent", backref=backref('gadgetComponentMetals', order_by=id))
On doing the following query:
session.query(User).join("userGadgets", "gadget", "components","gadgetComponentMetals").filter(ComponentUsingMetal.metal == 'iron') , component_metal gets attached to the query twice giving an error 'table name component_metal specified more than once'.
Any idea what I am doing wrong?
I traced the problem down to the following line in selectable.py:
froms = [f for f in froms if f not in toremove] This line removes the tables which are already covered by joins so that FROM clause doesn't have the same table specified more than once. The line didn't remove component_metal even though toremove had it meaning that I had two different Table objects for the same db table. And then I noticed the import for component_metal's class, ComponentUsingMetal, looked different. The others imports looked like:
from myschema import GadgetComponent
from myschema import Gadget
from python.myschema ComponentUsingMetal
As soon as fixed the import the issue went away.
I'm following the flask-sqlalchemy tutorial on declaring models regarding one-to-many relationship. The example code is as follows:
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
addresses = db.relationship('Address', backref='person',
lazy='dynamic')
class Address(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50))
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
Now I'm wondering how to insert new records into DB using such model. I assume I need a constructor init, but I have difficulties to understand how it should be implemented and used. The main problem for me here is that Person depends on Address and Address has ForeignKey to Person, so it should know about the Person in advance.
Plase help me to understand how it should be performed.
Thank you in advance.
You dont need to write a constructor, you can either treat the addresses property on a Person instance as a list:
a = Address(email='foo#bar.com')
p = Person(name='foo')
p.addresses.append(a)
Or you can pass a list of addresses to the Person constructor
a = Address(email='foo#bar.com')
p = Person(name='foo', addresses=[a])
In either case you can then access the addresses on your Person instance like so:
db.session.add(p)
db.session.add(a)
db.session.commit()
print(p.addresses.count()) # 1
print(p.addresses[0]) # <Address object at 0x10c098ed0>
print(p.addresses.filter_by(email='foo#bar.com').count()) # 1
I've gathered information here and elsewhere and found 3 ways to do so. In this model example (same as question):
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
addresses = db.relationship('Address', backref='person',
lazy='dynamic')
class Address(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50))
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
1.
a = Address(email='foo#bar.com')
p = Person(name='foo', addresses=[a])
2.
p = Person(name='foo')
a = Address(email='foo#bar.com', person_id=p.id)
3.
a = Address(email='foo#bar.com')
p = Person(name='foo')
p.addresses.append(a)
The most important thing while looking into this model is to understand the fact that this model has a one to many relationship, i.e. one Person has more than one address and we will store those addresses in a list in our case.
So, the Person class with its init will look something like this.
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
addresses = db.relationship('Address', backref='person',
lazy='dynamic')
def __init__(self,id,name,addresses = tuple()):
self.id = id
self.name = name
self.addresses = addresses
So this Person class will be expecting an id, a name and a list that contains objects of type Address. I have kept that the default value to be an empty list.
Hope it helps. :)
Additionally to all previous answers, in one-to-one relationships with uselist=False, like:
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
address = db.relationship('Address', backref='person',
lazy=True, uselist=False)
class Address(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50))
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
Only next approach helped to insert records:
p = Person(name='foo')
a = Address(email='foo#bar.com', person=p) # Adding created person directly to Address's backref