SQLalchemy: Could not determine join condition between parent/child tables on relationship - python

So I have these two simple classes inside the same python file which I'm trying to map with SQLAlchemy, where User column 'password_id' is a foreign key to the table Password 'password_id' column as well
from sqlalchemy.orm import relationship, declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.sql.schema import ForeignKey
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
user_id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String)
password_id = Column(Integer, ForeignKey('password.password_id'))
parent = relationship("Password", back_populates="users")
class Password(Base):
__tablename__ = 'passwords'
password_id = Column(Integer, primary_key=True)
password = Column(String)
last_change_date = Column(DateTime)
valid_until = Column(DateTime)
child = relationship("User", back_populates="passwords", uselist=False)
Here's the db schema for context:
I'm following this guide from sql alchemy but for some reason I keep getting the error from the title 'Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.', which is strange because accorind to what I see in the guide, my classes have been mapped correctly so I can't understand why this error is happening.
Thank you in advance

I think the issue is in the following line (the table name in ForeignKey)...
password_id = Column(Integer, ForeignKey('password.password_id'))
should be passwords instead of password.

Related

Alembic Build Python ORM files from database

I have a database with nearly 100 tables. I am trying to use alembic to manage it in python. Is there a way for alembic to build the ORM python files for me or do I have to write them myself.
For example in the database there is a user table with columns id, first_name, last_name, email is there a way alembic can pull this from the database and basically write the following file for me:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey
​
Base = declarative_base()
​
class User(Base):
__tablename__ = 'user'
​
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
email = Column(String)
Thanks

How do I update databases in sqlAlchemy and FastAPI

FastAPI/SQLAlchemy. I had two tables in sqlLite database: blog and user. I had created the database, but later decided to add relationship between the two and added new foreignKey Column. However, my changes did not appear in existing db (even though I deleted everything in it). Only after I deleted .db file entirely, did my --reload generate new db with new columns. I was wondering if there is analog of makemigrations + migrate from Django where you do not have to delete DB entirely for FastAPI and SQLAlchemy.
I made changes from this
class Blog(Base):
__tablename__ = "blogs"
id=Column(Integer, primary_key=True, index= True)
title= Column(String)
body=Column(String)
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key = True, index=True)
username = Column(String)
email = Column(String)
password= Column(String)
To this
class Blog(Base):
__tablename__ = "blogs"
id=Column(Integer, primary_key=True, index= True)
title= Column(String)
body=Column(String)
user_id= Column(Integer, ForeignKey("users.id")) ### NEW
creator = relationship("User", back_populates= "blogs") ### NEW
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key = True, index=True)
username = Column(String)
email = Column(String)
password= Column(String)
blogs = relationship("Blog", back_populates = "creator") ### NEW
You can used alembic to do that.
Please click this link for the official instructions! This is the link

AmbiguousForeignKeysError even after specifying foreign_keys

I'm setting up some table objects for SQLAlchemy.
I have a user and checkout tables. I want to associate a user object with the checkin and the checkout, which are both recorded in the same checkout object, so I have an in_user and out_user associated with each checkout object.
I've run into a sqlalchemy.exc.AmbiguousForeignKeysError
To quote the exact error message:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Checkout.out_auth_user - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
I've done as the error message requests (see below), but the error still occurs.
I originally only specified user email because I wanted to be able to remove users in the future without corrupting historical data. However, I tried to add user id, but still got the same error.
There are many similar questions on StackOverflow, but I couldn't find one that addressed my problem and most of them are working with much older versions of sqlalchemy that did not support the foreign_keys argument to relationship. It seems like this often occurs with backreferences, but I'm not using those as far as I'm aware. This is a simple one-way link from a checkout object to two user objects.
Flask foreign_keys still shows AmbiguousForeignKeysError
sqlalchemy , AmbiguousForeignKeysError
The full code is on github at https://github.com/ACMWM/hwcheckout
Below is an MRE
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Boolean, Integer, String, ForeignKey, DateTime
from sqlalchemy.orm import relationship
db = "sqlite:///mre.db"
engine = create_engine(db, convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String)
class HW(Base):
__tablename__ = "HW"
id = Column(Integer, primary_key=True)
class Checkout(Base):
__tablename__ = "Checkouts"
what = Column(Integer, ForeignKey(HW.id))
hardware = relationship(HW, foreign_keys=[what])
id = Column(Integer, primary_key=True)
out_auth_id = Column(Integer, ForeignKey(User.id))
out_auth_email = Column(String, ForeignKey(User.email))
out_auth_user = relationship(User, foreign_keys=[out_auth_id, out_auth_email])
in_auth_id = Column(Integer, ForeignKey(User.id))
in_auth_email = Column(String, ForeignKey(User.email))
in_auth_user = relationship(User, foreign_keys=[in_auth_id, in_auth_email])
Base.metadata.create_all(bind=engine, checkfirst=True)
u = User(email="test#example.com")
chk = Checkout(out_auth_user_id=u.id,out_auth_user_email=u.email)
I'm using SQLAlchemy 1.3.3
EDIT: Remove double import of models. Same error still occurs
EDIT again: Got the MRE to reproduce the error
Postgres EDIT: Don't know if this helps, but when I tried to move my code to a real database, I got this error:
sqlalchemy.exc.ProgrammingError: (psycopg2.errors.InvalidForeignKey) there is no unique constraint matching given keys for referenced table "users"
[SQL:
CREATE TABLE "Checkouts" (
id SERIAL NOT NULL,
outdate TIMESTAMP WITHOUT TIME ZONE,
returndate TIMESTAMP WITHOUT TIME ZONE,
who VARCHAR,
reason VARCHAR,
quantity INTEGER,
what INTEGER,
out_auth_id INTEGER,
out_auth_email VARCHAR,
in_auth_id INTEGER,
in_auth_email VARCHAR,
PRIMARY KEY (id),
UNIQUE (id),
FOREIGN KEY(what) REFERENCES "HW" (id),
FOREIGN KEY(out_auth_id) REFERENCES users (id),
FOREIGN KEY(out_auth_email) REFERENCES users (email),
FOREIGN KEY(in_auth_id) REFERENCES users (id),
FOREIGN KEY(in_auth_email) REFERENCES users (email)
)
]
Try to change your Checkout model definition:
class Checkout(Base):
__tablename__ = "Checkouts"
what = Column(Integer, ForeignKey(HW.id))
hardware = relationship(HW, foreign_keys=[what])
id = Column(Integer, primary_key=True)
out_auth_id = Column(Integer, ForeignKey(User.id))
out_auth_email = Column(String, ForeignKey(User.email))
in_auth_id = Column(Integer, ForeignKey(User.id))
in_auth_email = Column(String, ForeignKey(User.email))
out_auth_user = relationship('User', foreign_keys=[out_auth_id])
in_auth_user = relationship('User', foreign_keys=[in_auth_id])
out_auth_user_by_email = relationship('User', foreign_keys=[out_auth_email])
in_auth_user_by_email = relationship('User', foreign_keys=[in_auth_email])
Documentation: https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#handling-multiple-join-paths

Generate schema SQL with SQLAlchemy

How does one generate the SQL/Migrate Code/Whatever with SQLAlchemy when using the Declarative Base?
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
engine = create_engine('mysql://root:password#localhost/mydb_dev', echo=True)
metadata = MetaData(bind=engine)
class User(Base):
__table__ = Table("users", metadata, autoload=True)
id = Column(Integer, primary_key=True)
display_name = Column(String)
email = Column(String)
password = Column(String)
def __repr__(self):
return "<User(id='{}', display_name='{}', email='{}')>".format(self.id, self.display_name, self.email)
class Site(Base):
__table__ = Table("sites", metadata, autoload=True)
id = Column(Integer, primary_key=True)
name = Column(String)
urls = relationship("URL")
def __repr__(self):
return "<Site(id='{}', name='{}')>".format(self.id, self.name)
I have this so far, I'd like to see what SQLAlchemy would generate as a schema.
Or, does SQLAlchemy do this at all? Is this a case where I create and manage the database and it's schema separately, and I just update my entities to reflect it?
Do understand that I am used to Doctrine2, and im very new to SQLAlchemy
Thanks!
You can create the db models by calling:
metadata.create_all()
http://docs.sqlalchemy.org/en/latest/core/metadata.html#sqlalchemy.schema.MetaData.create_all
Note that this only works for the creation of previously non-existent models, and doesn't handle updates or downgrades. Check out alembic for even finer control:
http://alembic.zzzcomputing.com/en/latest/

alembic doesn't detect relationship table

I've defined some models with some association tables for m2m relationships:
from itsdangerous import TimedJSONWebSignatureSerializer
from passlib.hash import bcrypt
from sqlalchemy.ext.declarative import declarative_base
import app
from app import db
Base = declarative_base()
class UserGroupRelationship(Base):
__tablename__ = 'users_groups'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey('groups.id'), primary_key=True)
class FriendRelationship(Base):
__tablename__ = u'users_friends'
id = db.Column(db.Integer, primary_key=True)
user_left = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
user_right = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
class User(db.Model):
__tablename__ = u'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
email = db.Column(db.String(120), unique=True)
password = db.Column(db.String(120))
# ...
last_login = db.Column(db.DateTime, default=db.func.now())
friends = db.relationship(FriendRelationship,
primaryjoin=id==FriendRelationship.user_left,
backref='friends', lazy='dynamic')
friends_with = db.relationship(FriendRelationship,
primaryjoin=id==FriendRelationship.user_right,
backref='friends_with', lazy='dynamic')
class Group(db.Model):
__tablename__ = u'groups'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
users = db.relationship(UserGroupRelationship,
primaryjoin=id==UserGroupRelationship.group_id,
backref='groups', lazy='dynamic')
class Device(db.Model):
''' devices linked to users '''
__tablename__ = u'devices'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
uuid = db.Column(db.String(50))
date_added = db.Column(db.DateTime)
user_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
running alembic revision --autogenerate does generate table for classes inheriting from db.Model but not for the tables used for my m2m relationships.
INFO [alembic.migration] Context impl PostgresqlImpl.
INFO [alembic.migration] Will assume transactional DDL.
INFO [alembic.autogenerate] Detected added table u'groups'
INFO [alembic.autogenerate] Detected added table u'users'
INFO [alembic.autogenerate] Detected added table u'devices'
INFO [alembic.autogenerate] Detected added table u'question'
Generating /Users/rxdazn/w/xxx/xxx-
yyy/migrations/versions/4e47aa7f3050_.py...done
My alembic.ini and env.py files are the default ones. I simply import my models init my project's __init__.py
any idea of what could cause this behaviour?
(Miguel answered this in the comments. I'll delete this answer if he posts it and someone pokes me in a comment. I only posted it so it could be marked as an answer, as I had the same problem and almost left the page before reading the comments.)
Don't inherit the association tables from Base. All models should inherit from db.Model, like this:
class FriendRelationship(db.Model):
This is why:
There are two patterns for many-to-many relationships. The basic one
uses a Table object for the association table. The advanced one uses
models, and has the advantage that you can store additional columns in
the association table. You seem to be using the advanced one, but if
you are using Flask-SQLAlchemy then all your models should inherit
from db.Model. You should not go directly to SQLAlchemy.

Categories

Resources