I´m developing a python app which uses Flask-SQLAlchemy and it´s ORM Mapper.
I´ve got two tables with a Many To One relation. The main problem is that I want to load the content of both Objects with one join query and not later, when I access the object of the child table.
I already tried to get these behavior by using the joinedload option like this:
Event.query.filter(Event.timestamp == day)
.join(Event.user)
.options(joinedload(Event.user))
.all()
Also tried to set the lazy="joined" attribute in the entity-class for the relationship, which caused no difference.
The SQL Query looks right and the join is correctly generated, but if I access the user attribute later, another join query is sent. With other strategies it´s also not working, like the contains_eager option.
So the expected behavior would be, to save all information on the first load and don´t execute a query later on.
All SQLAlchemy options are default, except the DATABASE_URI. Is there any global option to disable this behavior or to override the default?
The Entities are the following:
class Event(db.Model):
__tablename__ = "event"
__table_args__ = (
db.ForeignKeyConstraint(
["username", "userfirstname"], ["users.name", "users.firstname"]
),
)
timestamp = db.Column(db.Date, primary_key=True, index=True)
username= db.Column(db.String, primary_key=True)
userfirstname= db.Column(db.String, primary_key=True)
...
user = db.relationship("UserEntity")
class UserEntity(db.Model):
__tablename__ = "users"
name= db.Column(db.String, primary_key=True)
firstname= db.Column(db.String, primary_key=True)
...
try this
class Event(db.Model):
__tablename__ = "event"
__table_args__ = (
db.ForeignKeyConstraint(
["username", "userfirstname"], ["users.name", "users.firstname"]
),
)
timestamp = db.Column(db.Date, primary_key=True, index=True)
username= db.Column(db.String, primary_key=True)
userfirstname= db.Column(db.String, primary_key=True)
user = db.relationship("UserEntity", back_populates="events")
user_id = Column(Integer, ForeignKey('user.id'))
class UserEntity(db.Model):
__tablename__ = "users"
name= db.Column(db.String, primary_key=True)
firstname= db.Column(db.String, primary_key=True)
events = relationship(MyOtherClass, lazy='joined')
from sqlalchemy docs:
joined applies a JOIN to the given SELECT statement so that related rows are loaded in the same result set. Joined eager loading is detailed at Joined Eager Loading.
I´ve found the mistake. The basic idea of disabling lazyloading was right and worked fine. I just messed up the object refs on the validation step and destroyed the SQLAlchemy background logic.
Related
I am new to this and I have a little test database which looks like this:
class Company(Base):
__tablename__ = 'company'
building_id = Column(Integer, primary_key=True, nullable=False, index=True)
name = Column(String, nullable=False)
buildings = relationship("Building", back_populates="company")
class Building(Base):
__tablename__ = 'building'
building_id = Column(Integer, primary_key=True, nullable=False, index=True)
name = Column(String, nullable=False)
ip_address = Column(String, nullable=True)
company_id = Column(Integer, ForeignKey('company.company_id'),nullable=False)
company = relationship("Company", back_populates="buildings")
As you may have noticed I have messed up the name for the company id, naming it "building_id".
I have changed this in the model, but it won't update the table with the error message
"(sqlite3.OperationalError) no such column: company.company_id".
How do I update it?
When you've actually launched your product you use a tool to handle database migrations for you, such as Alembic. Alembic can generate a migration script for you that runs the necessary ALTER statements to bring your database up to date with your model.
However while developing, it might be easier to just delete the .sqlite file and call create_all() again to get a fresh db created according to the schema.
I am working on a Flask app, using Flask-SQLAlchemy extension for database interactions. Since I have multiple apps writing on the same DB, I was getting concurrency issues with SQLite and I wanted to switch to PostgreSQL instead. I am able to create the tables on new database without a problem and pgAdmin displays the tables and columns.
# working
def createTables():
with app.app_context():
from models import User, Invoice
db.create_all()
But when it comes to adding a user, I am now getting an error: sqlalchemy.exc.NoForeignKeysError Although, I think, I declared one-to-many relationship in my models, based on the documentation, I get an error states that "there are no foreign keys linking these tables."
# not working
def create_test_user():
with app.app_context():
user = User(
username="Bob",
email="bob#email.com",
password="test"
)
db.session.add(user)
db.session.commit()
The full error message:
""" NoForeignKeysError: Could not determine join condition between parent/child tables on relationship User.invoices
- there are no foreign keys linking these tables.
Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression. """
I can't figure out what causes the error. What is missing with my models?
# models.py
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
invoices = db.relationship('Invoice', backref='user', lazy=True)
class Invoice(db.Model):
__tablename__ = "invoice"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
amount = db.Column(db.Integer, nullable=False)
Solved
Your code works for me. Maybe you need to re-create your tables or something similar. To be sure that we have the identical code: I have tested the following code:
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
invoices = db.relationship('Invoice', backref='user', lazy=True)
class Invoice(db.Model):
__tablename__ = "invoice"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
amount = db.Column(db.Integer, nullable=False)
In the route:
user = User(
username="Bob",
email="bob#email.com",
password="test"
)
db.session.add(user)
db.session.commit()
print(user)
I finally solved the problem and it was not where I was looking for. I was getting NoForeignKeysError due to importing a wrong model file during initializing the app. One of my imported modules was calling a wrong/old version of the model. It was causing the table relationship in the actual model to break I guess.
When I went through step by step create_test_user() I noticed that the error occurs actually during the class creation, before even it hits to db.session.add and I replicated the error even without a DB. I went through all my modules that are calling the models and caught wrong model import.
I have the following four model classes in my Flask/SQLAlchemy application (a fifth class MyClassA is not shown here but is reference):
class MyClassF(db.Model):
valid_fs = ['F1', 'F2']
f_id = db.Column(db.Integer, nullable=False, primary_key=True)
name = db.Column(db.Enum(*valid_fs, name='f_enum'), default='F1', nullable=False)
class MyClassD(db.Model):
d_id = db.Column(db.Integer, nullable=False, primary_key=True)
name = db.Column(db.String(128))
v_collection = db.relationship('MyClassV', lazy='dynamic', backref=db.backref('d'), cascade='all, delete-orphan')
class MyClassV(db.Model):
v_id = db.Column(db.Integer, nullable=False, primary_key=True)
d_id = db.Column(db.Integer, db.ForeignKey(MyClassD.d_id), nullable=False)
c_collection = db.relationship('MyClassC', lazy='dynamic', backref=db.backref('v'), cascade='all, delete-orphan')
a_collection = db.relationship('MyClassA', lazy='dynamic', backref=db.backref('v'), cascade='all, delete-orphan')
class MyClassC(db.Model):
c_id = db.Column(db.Integer, nullable=False, primary_key=True)
v_id = db.Column(db.Integer, db.ForeignKey(MyClassV.v_id), nullable=False)
f_id = db.Column(
db.Integer,
db.ForeignKey(MyClassF.f_id),
nullable=False,
#default=MyClassF.query.filter(MyClassF.name == "F1").one().f_id
)
Creating this schema using the Flask-Migrate command db init, db migrate and db upgrade works just fine.
However, when I un-comment the line the definition of MyClassC.f_id and try it again (after removing the migrations directory), I get the following circular dependency error:
sqlalchemy.exc.InvalidRequestError: When initializing mapper
Mapper|MyClassV|my_classV, expression 'MyClassC' failed to locate a
name ("name 'MyClassC' is not defined"). If this is a class name,
consider adding this relationship() to the
class after both dependent classes have been defined.
All I'm trying to do is ensure that the default value of MyClassC.f_id is set by querying the MyClassF table. This check should occur at insertion time -- not when the database is being created. So I don't understand why I am getting this error now.
How can I use db.relationship() (or any other technique) to get around this circular dependency error while enforcing the database integrity rule I'm trying to implement?
I believe the default parameter is translated to the SQL DEFAULT constraint. AFAICT this constraint is not evaluated at insertion time, but when creating the table and the value is then used as a default (which means the default value is set once and forall at creation of the table and used for all rows that lack that column, it cannot dynamically change according to the contents of an other table).
However the documentation of SQLAlchemy mentions the fact that you can pass in a python function as default value and it will be called for each insert to obtain the default value to use, so you can do:
class MyClassC(db.Model):
c_id = db.Column(db.Integer, nullable=False, primary_key=True)
v_id = db.Column(db.Integer, db.ForeignKey(MyClassV.v_id), nullable=False)
f_id = db.Column(
db.Integer,
db.ForeignKey(MyClassF.f_id),
nullable=False,
default=lambda: MyClassF.query.filter(MyClassF.name == "F1").one().f_id
)
Note however that this will not use any database facility to provide the default value, it's SQLAlchemy first obtaining the default value and then manually inserting it in your queries.
I have this code, thats been mostly taken from the sqlalchemy site
class Order(Base):
__tablename__ = 'order'
id = Column(Integer, Sequence('tri_id_seq'), primary_key=True)
text = Column(String(1024), nullable=False)
items = relationship("Item", cascade="save-update, delete-orphan, merge, delete", backref="parent")
class Item(Base):
__tablename__ = 'item'
id = Column(Integer, Sequence('tri_id_seq'), primary_key=True)
text = Column(String(1024), nullable=False)
parent_id = Column(Integer, ForeignKey('order.id'))
I want deletes to Order to cascade down and delete its items as well. In code:
# test insert/delete - save data to mssql server
i1 = Item(text="item one")
i2 = Item(text="item two")
o = Order(text="one", items=[i1, i2])
session.add(o)
session.commit()
session.delete(o) # delete it
# tests to make sure items/order gone ....
session.commit()
This works ok.
BUT if I try and delete an Order in MS SQL management studio. i.e.
DELETE FROM [dbo].[order] WHERE id = 1
I get the error
"the DELETE statement conflicted with the REFERENCE constraint FK__item__parent_id_1D00044F" The conflict error blah blah....
I guess theres something missing on the relationship definitions but I can't see it.
Any help/thoughts?
ta.
class Comment(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete='CASCADE'), nullable=False)
user = relationship("User", backref=backref('comments', cascade="all,delete", order_by=id))
This kind of setup works for me, my User class doesn't have any special fields except for primary key.
So basically, this works as intended, when I delete the user - his comments are gone as well.
It doesn't have to be deletion through SQLAlchemy either, this code creates an appropriate table structure, that even if you delete user manually (sql query) - cascade deletion will still work.
I am learning python and using the framework pyramid with sqlalchemy as the orm. I can not figure out how relationships work. I have 2 tables, offices and users. the foreign key is on the users table 'offices_id'. I am trying to do a query that will return to me what office a user is a part of.
This is how I have my models set up.
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255), unique=True, nullable=False)
trec_number = Column(Unicode(255), unique=True, nullable=False)
office_id = Column(Integer, ForeignKey('offices.id'))
class Office(Base):
__tablename__ = 'offices'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255), unique=True, nullable=False)
address = Column(Unicode(255), unique=True, nullable=False)
members = relationship("User", backref='offices')
In my view how would I write a query that would return the the office information for a given user?
I am trying this:
for user in DBSession.query(User).join(Office).all():
print user.address
but I think I am misunderstanding how the queries work because I keep getting errors
AttributeError: 'User' object has no attribute 'address'
when I do this:
for user in DBSession.query(User).join(Office).all():
print user.name
it prints out the users name fine since name is an attribute of the User class.
I also can not get the inverse to work
for offices in DBSession.query(Office).join(User).all():
print offices.users.name
You need to use the name you used in the backref argument to access the Office model. Try user.offices.address