I'm having an issue where SQLAlchemy throws a NoForeignKeysError when it attempts to determine the primary join for two tables. The odd thing is this issue only occurs on the second instantiation of the models (i.e. if I drop the tables and recreate them it works the first time I run the script but not the subsequent times). Even odder is if I use inspect to view the ForeignKeys of the child tables I can clearly see that they exist (psql and just looking at the declarative base's metadata confirms this as well). My models are generated by a script and all other tables with similar or more complex relations work as expected however it is only this particular set of tables that has this issue.
The specific errors it throws are:
sqlalchemy.exc.NoForeignKeysError: Can't find any foreign key relationships between 'task_statuses' and 'task_data'.
and
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship task_statuses.task_data - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
Code snippet:
class TaskData(db_base):
__tablename__ = 'task_data'
__table_args__ = ({'extend_existing' : True})
id = Column(Integer, primary_key=True)
tags = relationship('TaskTags', backref='task_data', cascade='all, delete-orphan', passive_deletes=True)
statuses = relationship('TaskStatuses', backref='task_data', cascade='all, delete-orphan', passive_deletes=True)
class TaskTags(db_base):
__tablename__ = 'task_tags'
__table_args__ = ({'extend_existing' : True})
id = Column(Integer, primary_key=True)
taskdata_id = Column(Integer, ForeignKey('task_data.id', ondelete='CASCADE'))
class TagStatuses(db_base):
__tablename__ = 'task_statuses'
__table_args__ = ({'extend_existing' : True})
id = Column(Integer, primary_key=True)
taskdata_id = Column(Integer, ForeignKey('task_data.id', ondelete='CASCADE'))
Update:
After rereading the error message, I've realized that SQLAlchemy seems to think that task_statuses is the parent table when in fact it's the child. That still explains nothing and is actually more confusing but may be noteworthy. Even if a primaryjoin is specified the error persists.
The problem ended up being with Graphene-SQLAlchemy
For whatever reason, defining the SQLAlchemy model and the Graphene-SQLAlchemy equivalent in the same file was causing SQLAlchemy to have some unexpected behavior.
I resolved the issue by placing the models in a different file then generating the required Graphene-SQLAlchemy objects in the original file
Related
In FastAPI application I am using sqlalchemy library. I have tried to establish a many to many relationship as described below:
PostCity = Table('PostCity',
Base.metadata,
Column('id', Integer, primary_key=True),
Column('post_id', Integer, ForeignKey('post.id')),
Column('city_id', Integer, ForeignKey('city.id')))
class DbPost(Base):
__tablename__ = 'post'
id = Column(Integer, primary_key=True, index=True)
image_url = Column(String)
cities = relationship('DbCity', secondary=PostCity, backref='post')
class DbCity(Base):
__tablename__ = 'city'
id = Column(Integer, primary_key=True, index=True)
name = Column(String)
posts = relationship('DbPost', secondary=PostCity, backref='city')
During add and commit commands it throws an error:
SAWarning: relationship 'DbCity.posts' will copy column city.id to column PostCity.city_id, which conflicts with relationship(s): 'Dntention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards. To silence this warning, add the parameter 'overlaps="cities,post"' to the 'DbCity.posts' relationship. (Background on this error at: https://sqlalche.me/e/14/qzyx) city = DbCity(name=city_name)
This answer suggests to use back_populates= , however, when I am replacing backref with back_populates in both DbPost and DbCity classes it gives another error:
sqlalchemy.exc.InvalidRequestError: Mapper 'mapped class DbCity->city' has no property 'post'
What is the reason of this and how could I fix it ?
The warning
SAWarning: relationship 'DbCity.posts' will copy column post.id to column PostCity.post_id, which conflicts with relationship(s): 'DbCity.post' (copies post.id to PostCity.post_id), 'DbPost.cities' (copies post.id to PostCity.post_id).
is emitted because the two relationships are duplicating each other. The backref on each creates a relationship on the other. You only need to configure the relationship on one of the models, the backref will automatically configure the other model.
I am trying to set up a self-referential one-to-many relationship in a Flask application using SQLAlchemy.
from app import db
[...]
class Task(db.Model):
__tablename__ = 'tasks'
id = db.Column(db.Integer, primary_key=True)
[...]
parent_id = db.Column(db.Integer, db.ForeignKey('tasks.id'))
children = db.relationship("Task", backref='parent',
remote_side=[id])
I am attempting to implement something like the example from this page on Adjacency List Relationships on the SQLAlchemy tutorial. Each task node may have many children and only one parent.
The relationship is working right now but the wrong way around from what I had intended ie. a task with parent_id of 1, will list the task with id = 1 as its child. Equally, Task.parent returns a list.
Alembic, the migration tool, encoded the above as the following:
op.add_column('tasks', sa.Column('parent_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'tasks', 'tasks', ['parent_id'], ['id'])
My assumption is that I have provided the wrong syntax for the remote_side declaration which is absent from the above. Unfortunately, self-referential relationships throw me every time and I cannot get my head around how this is to work. Any help or pointers would be appreciated.
According to the documentation you have linked, declaring your relationships may either be done by:
class Task(db.Model):
__tablename__ = 'tasks'
id = db.Column(db.Integer, primary_key=True)
[...]
parent_id = db.Column(db.Integer, db.ForeignKey('tasks.id'))
parent = db.relationship("Task", backref='children',
remote_side=[id])
or
class Task(db.Model):
__tablename__ = 'tasks'
id = db.Column(db.Integer, primary_key=True)
[...]
parent_id = db.Column(db.Integer, db.ForeignKey('tasks.id'))
children = db.relationship("Task", backref=backref('parent',
remote_side=[id]))
Note that in the second case, the remote_side parameter is provided to the backref function, not to the relationship. The code you show is an hybrid of the two (you declare the children relation, but the remote_side declaration is outside the backref call).
This is because remote_side=['id'] declares the field that is the key that is being referenced by the foreign_key, and thus must be added to the relationship that points to the '1' side of the relation (the parent).
I am trying modify the pyramid_blogr example. The only closest QA thread is this one and the link mentioned in that thread. I went through both of them. I am using concrete inheritance, by which I understand that separate tables will be created for each model. Still, when I query records from the second model, I get errors saying that the model do have columns like title or created in TaggedBlogRecord.
Errors
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: ====> entries.created <======
[SQL: SELECT entrieslanguage.id AS entrieslanguage_id
FROM entrieslanguage ORDER BY entries.created DESC
LIMIT ? OFFSET ?]
The sample code as follows
class BlogRecord(Base):
__tablename__ = 'entries'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255), unique=True, nullable=False)
body = Column(UnicodeText, default=u'')
created = Column(DateTime, default=datetime.datetime.utcnow)
edited = Column(DateTime, default=datetime.datetime.utcnow)
class TaggedBlogRecord(BlogRecord):
__tablename__ = 'taggedentries'
__mapper_args__ = {'concrete':True}
id = Column(Integer, primary_key=True)
tags = Column(Unicode(255))
Read https://docs.sqlalchemy.org/en/latest/orm/inheritance.html#concrete-table-inheritance more closely (emphasis added):
Two critical points should be noted:
We must define all columns explicitly on each subclass, even those of
the same name. A column such as Employee.name here is not copied out
to the tables mapped by Manager or Engineer for us.
while the Engineer
and Manager classes are mapped in an inheritance relationship with
Employee, they still do not include polymorphic loading. Meaning, if
we query for Employee objects, the manager and engineer tables are not
queried at all.
I have these models:
class User(UserMixin, db.Model):
__tablename__ = 'users_user'
...
country = db.Column(db.Integer, db.ForeignKey('countries.id'))
class Country(db.Model):
__tablename__ = 'countries'
id = db.Column(db.Integer, primary_key=True)
...
user_country = db.relationship('User', backref='user_country', lazy='joined')
I am trying this query:
User.query.options(joinedload(Country.user_country)).filter_by(id=current_user.get_id()).first()
That will throw this error:
ArgumentError: Can't find property 'user_country' on any entity specified in this Query.
Note the full path from root (Mapper|User|users_user) to target entity must be specified.
What is wrong here?
The joinedload here is unnecessary.
By default relationships are lazily-loaded. This causes additional SELECT queries to be issued to retrieve the data. joinedload is one of the ways to force the relationship to be eagerly loaded by using a JOIN instead.
In this case, however, you've defaulted the relationship between User and Country to use eager loading by specifying lazy='joined'. This would reduce your query to
User.query.filter(id=current_user.get_id()).first()
While this will help you with the ArgumentError, we can go a little further. The query itself is unnecessary as well. current_user already has the data for its related Country because of the eager join. Accessing current_user.user_country will not send any additional queries to the database.
I'm new to python(sqlalchemy), and I'm learning to build web site with pylons and sqlalchemy.
I have a problem when I declare the relationship between models. I've tried it several hours, but failed. But I think it should be a basic question.
I have two classes: User and Article, user can create articles, and modified the other people's article(like wiki).
So a user has created-articles and edited-articles.
class Article(Base):
__tablename__ = 'articles'
id = Column(Integer, primary_key=True)
title = ...
user_id = Column(Integer, ForeignKey('users.id'))
editor_id = Column(Integer, ForeignKey('users.id'))
# relations
user = relationship('User', backref='articles') # -> has error
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String(20))
def __init__(self):
pass
But there is an error displayed:
InvalidRequestError: One or more mappers failed to compile. Exception was probably suppressed within a hasattr() call. Message was: Could not determine join condition between parent/child tables on relationship Article.user. Specify a 'primaryjoin' expression. If this is a many-to-many relationship, 'secondaryjoin' is needed as well.
I tried to add primaryjoin to the line('has error'), but don't know what it should be. I tried some codes, but none works.
Thank you in advance!
Ah, thats obvious one.
Article class has two references to User, user_id and editor_id, so SQLA does not know which one of them to use for your relation. Just use explicit primaryjoin:
user = relation('User', backref='articles', primaryjoin="Article.user_id==User.id")