Flask-SQLAlchemy: One-To-Many between separate moduls (NoReferencedTableError) - python

I have two model classes in separate files, created a One-To-Many relationship between them.
user.py:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), index=True, unique=True, nullable=False)
password = db.Column(db.String(128), nullable=False)
projects = db.relationship('Project', backref='owner', lazy='dynamic')
project.py:
class Project(db.Model):
__tablename__ = 'projects'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), index=True, unique=True, nullable=False)
owner_id = db.Column(db.Integer, db.ForeignKey('User.id'))
This is how I create my app:
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
db = SQLAlchemy(app)
#app.before_first_request
def create_tables():
db.create_all()
app.run(debug=True)
However when I create a request I get the following error:
sqlalchemy.exc.NoReferencedTableError:
Foreign key associated with column 'projects.owner_id' could not find
table 'User' with which to generate a foreign key to target column 'id'
I understand this is a duplicate of this question asked before, however I tried that and did not work:
#app.before_first_request
def create_tables():
from projectx.models.auth.user import User
from projectx.models.private_data.project import Project
db.create_all()
I also tried giving the same __table_args__ = {"schema": "testdb"} args to both models (I manually created the db with this name) and refer to db.ForeignKey('testdb.User.id) to no avail, the error is the same.
What am I missing here?

It seems I misunderstood some concepts about how this relationship abstraction works.
The foreign key reference is to the tablename and not the class name naturally.
I should have written db.ForeignKey('users.id').

Related

One-To-Many database relationship Foreign Key error - SQLAlchemy

I am having trouble making a One-To-Many relationship between 'User' and 'GymObj' using a Foreign Key. In my code, a user can have only one gym objective however a gym objective can have many users. The code which I attempted seems to be correct as I followed a tutorial for it however a 'NoForeignKeysError' appears.
python code
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(200), nullable=False, unique=True)
birth_year = db.Column(db.Integer, nullable=False)
weight = db.Column(db.Integer, nullable = False)
date_added = db.Column(db.DateTime, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('gymobj.id'))
#Create a String
def __repr__(self):
return '<Username %r>' % self.username
class GymObj(db.Model):
id = db.Column(db.Integer, primary_key=True)
gym_obj = db.Column(db.String)
users = db.relationship('User', backref='user')
console
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship GymObj.users - 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 attempted to make a migration and push the migration in the Shell as well however the same error appeared. Any help would be much appreciated.

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

Cannot test simple sqlalchemy model dataclass: "Mapper mapped class could not assemble any primary key columns for mapped table"

I have a simple Python script (models.py) to create a Flask-SQLAlchemy app and a simple sqlalchemy dataclass (reference here) that looks like this:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///test.db"
db = SQLAlchemy(app)
class User(db.Model):
__tablename__ = 'user'
user_id: db.Column(db.Integer, primary_key=True)
user_url: db.Column(db.String(2083), unique=True, nullable=False)
username: db.Column(db.String(80), unique=True, nullable=False)
avatar_url: db.Column(db.String(2083), unique=True, nullable=False)
I then wrote an extremely simple pytest script that looks like this, following the example on creating a Flask-SQLAlchemy app from here:
import pytest
from models import User
from models import db
def test_basic():
db.create_all()
u1 = User(1, "http://testuserurl", "charliebrown", "http://avatarurl")
assert u1.user_id == 1
assert u1.user_url == "http://testuserurl"
assert u1.username == "charliebrown"
assert u1.avatar_url == "http://avatarurl"
This test fails immediately on the line "from models import User" with the message:
sqlalchemy.exc.ArgumentError: Mapper mapped class User->user could not
assemble any primary key columns for mapped table 'user'
I don't understand this message (specially given that I do indeed specify the primary_key in User). Any help please?
Try to use = instead of :
Like this:
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
user_url = db.Column(db.String(2083), unique=True, nullable=False)
username = db.Column(db.String(80), unique=True, nullable=False)
avatar_url = db.Column(db.String(2083), unique=True, nullable=False)

Flask sqlalchemy model shared across multiple Oracle schemas

I have to create an Flask application which need to read data from multiple almost identical schemas placed on this same Oracle instance. I am able to connect to this database only by 'read_only' user.
By do this I am using blueprints.
project
|-warehouse01
| -__init__.py
| - view.py
|-warehouse02
| -__init__.py
| - view.py
|-warehouse999
| -__init__.py
| - view.py
-model.py
run.py
config.py
Of course model for all schemas is identical
class Product(db.Model):
#__table_args__ = {'schema': 'warehouse01'}
__tablename__ = 'PRODUCTS'
prod_no = db.Column(db.Integer, primary_key=True, nullable=False)
prod_name = db.Column(db.String(35), nullable=False)
quantity = db.Column(db.Integer, nullable=False)
...
steps = db.relationship('Shipment', backref='shipments', lazy='dynamic')
class Shipment(db.Model):
__tablename__ = 'SHIPMENTS'
#__table_args__ = {'schema': 'warehouse01'}
shipm_no = db.Column(db.Integer, , primary_key=True, nullable=False)
prod_no = db.Column(db.Integer, db.ForeignKey('PRODUCTS.prod_no'), nullable=False)
customer_id= db.Column(db.Integer, db.ForeignKey('CUSTOMERS.customer_id') nullable=False)
...
Problem is that connecting by different schema (READ_ONLY) I have to declare a schema in table_args
I would like to use this same model to all schemas. Obviously I do not want to create separate models for all blueprints.
Is it possible to use this same model for all schemas or redefine in blueprint a schema name?
I think that you can use a factory function to create your models on the fly:
def create_product_model(schema):
class Product(db.Model):
__table_args__ = {'schema': schema}
__tablename__ = 'PRODUCTS'
prod_no = db.Column(db.Integer, primary_key=True, nullable=False)
prod_name = db.Column(db.String(35), nullable=False)
quantity = db.Column(db.Integer, nullable=False)
...
steps = db.relationship('Shipment', backref='shipments', lazy='dynamic')
return Product
Finally I have found a way to do this. Unfortunately a approach proposed by #Laurent LAPORTE was not proper. It works only when I had just one class in model, and only after adding 'extend_existing': True. Please correct me if I am wrong but in Flask a database model is declared at application start. As schema name is a part of a class declared in __table_args__ = {'schema': schema}, while you would like to change the schema, SqlAlchemy treats it as try to defining new class with existing name in model registry.
My solution is to remove schema from table_args and setup a schema name by sql command like below:
def estimate_schema(schema):
db.engine.execute(f"alter session set current_schema={schema}")
This I use before each route and it works
def index():
estimate_schema('warehouse01')
...

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