I have a problem creating relationships between my tables in flask-sqlalchemy. I have a table with project overview, and from there on out I want to dynamically create new experiment tables with a relationship to my project overview. However, when I try to define the relationship, sqlalchemy throws the following error:
sqlalchemy.exc.InvalidRequestError: When initializing mapper mapped class Projects->projects, expression 'Experiment_Overview' failed to locate a name ('Experiment_Overview'). If this is a c
lass name, consider adding this relationship() to the <class 'app.Projects'> class after both dependent classes have been defined.
This seems to be the case because the class Experiment_Overview(db.Model) does not exist yet, which is correct since it will be dynamically generated later on through user input. How can I mitigate this error?
import os
from flask import Flask, render_template, redirect, request, url_for
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
Bootstrap(app)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///DATA/DB.db"
db = SQLAlchemy(app)
def TableCreator(tablename):
class Experiment_Overview(db.Model):
__tablename__ = tablename
creation_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
experiment_name = db.Column(db.String(30), unique=False, nullable=False, primary_key=True)
projectname = db.Column(db.String(150), db.ForeignKey('projects.projectname'), nullable=False, unique=True)
return MyTable
class Projects(db.Model):
projectname = db.Column(db.String(150), unique=True, nullable=False, primary_key=True)
creation_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
experiments = db.relationship('Experiment_Overview', backref="experiments", lazy=True, uselist=False)
def __init__(self, owner, projectname, status, created_at):
self.owner=owner
self.projectname=projectname
self.status=status
self.created_at=created_at
db.create_all()
Generally speaking, you don't dynamically create tables; you usually shouldn't create or drop tables while your program is running.
Additionally, I believe it's impossible to create a true relationship that links back to an entire table. Relationships/Foreign Keys are for linking between rows in tables.
Don't worry, there are easier ways to achieve the behavior that you are looking for here.
From your question it sounds like you can have multiple Projects, and each project can have multiple Experiments within the project.
This would make the relationship between a Project and its Experiments a One-To-Many relationship.
If this is the case, you would need one Projects table (which you have already in your code), and you would also have one Experiments table.
Each row in the Projects table represents one Project.
Each row in the Experiments table represents one Experiment. The Experiments table will have a column containing a foreign key linking back to the Project the Experiment is linked to.
I've modified your code according to the One-To-Many example code given in the SQLAlchemy documentation that I linked above.
Note the addition of the back_populates option to the relationship(), this allows bi-directional knowledge of the relationship: the Experiment know what project it belongs to, and the Project know what Experiments it has.
import os
from flask import Flask, render_template, redirect, request, url_for
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
Bootstrap(app)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///DATA/DB.db"
db = SQLAlchemy(app)
class Projects(db.Model):
__tablename__ = "projects"
projectname = db.Column(db.String(150), unique=True, nullable=False, primary_key=True)
creation_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
experiments = db.relationship("Experiment", back_populates="project")
def __init__(self, owner, projectname, status, created_at):
self.owner=owner
self.projectname=projectname
self.status=status
self.created_at=created_at
class Experiment(db.Model):
__tablename__ = "experiments"
creation_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
experiment_name = db.Column(db.String(30), unique=False, nullable=False, primary_key=True)
projectname = db.Column(db.String(150), db.ForeignKey('projects.projectname'), nullable=False)
project = relationship("Project", back_populates="experiments")
db.create_all()
Related
I have a flask app using sql-alchemy and flask migrate to handle database changes. Every time I run a flask migrate to create a script for Alembic to update the database, the script contains commands to create foreign keys that already exist in my database.
The table definition in my models.py is
class Airline(db.Model):
__tablename__ = 'Airlines'
AirlineID = db.Column(db.Integer, primary_key=True)
AirlineShortCode = db.Column(db.String(3), index=True, unique=True, nullable=False)
FullName = db.Column(db.String(256), unique=False, nullable=True)
ShortName = db.Column(db.String(64), unique=False, nullable=True)
class CabinClass(db.Model):
__tablename__ = 'CabinClasses'
CabinClassID = db.Column(db.Integer, primary_key=True)
AirlineShortCode = db.Column(db.ForeignKey("Airlines.AirlineShortCode"), nullable=True)
CabinClassShortCode = db.Column(db.String(32), unique=False, nullable=False)
CabinClassName = db.Column(db.String(64), unique=False, nullable=True)
The line in the migration database update script that is generated to create the foreign key is
op.create_foreign_key(None, 'CabinClasses', 'Airlines', ['AirlineShortCode'], ['AirlineShortCode'])
This line is generated every time I create the migration script, resulting in multiple foreign key entries in the CabinClasses table:
I see that the name of each foreign key created is different and that the create_foreign_key command in the database migration script states the name as None. I believe this is correct if you are using an automated naming scheme, which I believe is what happens by default
For setups that use an automated naming scheme such as that described
at Configuring Constraint Naming Conventions, name here can be None,
as the event listener will apply the name to the constraint object
when it is associated with the table
https://alembic.sqlalchemy.org/en/latest/naming.html
Can anyone identify what would cause these foreign keys to be created every time I update the database?
The names of the constraints that you are getting look like they come from your database, not SQLAlchemy. You need to add the constraint naming templates for all types of constraints to the SQLAlchemy metadata, and then I think you will get consistent names. See how to do this in the Flask-SQLAlchemy documentation. I'm copying the code example from the docs below for your convenience:
from sqlalchemy import MetaData
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
convention = {
"ix": 'ix_%(column_0_label)s',
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s"
}
metadata = MetaData(naming_convention=convention)
db = SQLAlchemy(app, metadata=metadata)
I have a Flask app with Flask-SQLAlchemy. I'm trying to make a one to many relationship from a user to a trip. I saw that, if I use the declarative_base of SQLAlchemy it works but when I use the db.Model from Flask-SQLAlchemy it dosen't work.
I'm getting the following error:
sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper|User|User, expression 'Trip' failed to locate a name ("name 'Trip' is not defined"). If this is a class name, consider adding this relationship() to the <class 'models.user.User'> class after both dependent classes have been defined.
Here are the models:
database.py
from flask import abort
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import exc
db = SQLAlchemy()
class Mixin(db.Model):
__abstract__ = True
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime, default=db.func.now())
date_changed = db.Column(
db.DateTime, nullable=True, onupdate=db.func.now())
date_to = db.Column(db.DateTime, nullable=True)
user.py
from database import Mixin, db
class User(Mixin, db.Model):
__tablename__ = 'User'
trips = db.relationship(
'Trip',
backref=db.backref('user', lazy='joined'),
lazy='dynamic'
)
trip.py
from database import Mixin, db
class Trip(Mixin, db.Model):
__tablename__ = 'Trip'
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(250), nullable=False)
user_id = db.Column(
db.Integer,
db.ForeignKey('User.id'),
nullable=False
)
Storing all models in one file is a good idea, but only for a small number of models. For the large I would prefer to store models in semantically separate files.
models
├── __init__.py
├── model1.py
├── model2.py
└── model3.py
In the __init__.py need to import all models files
import models.model1.py
import models.model2.py
import models.model3.py
I fixed it by using one file for my models. I didn't have enough models to need multiple model files anyway.
I am using Flask-SQLAlchemy to define my models, and then using Flask-Migrate to auto-generate migration scripts for deployment onto a PostgreSQL database. I have defined a number of SQL Views on the database that I use in my application like below.
However, Flask-Migrate now generates a migration file for the view as it thinks it's a table. How do I correctly get Flask-Migrate / Alembic to ignore the view during autogenerate?
SQL View name: vw_SampleView with two columns: id and rowcount.
class ViewSampleView(db.Model):
__tablename__ = 'vw_report_high_level_count'
info = dict(is_view=True)
id = db.Column(db.String(), primary_key=True)
rowcount = db.Column(db.Integer(), nullable=False)
Which means I can now do queries like so:
ViewSampleView.query.all()
I tried following instructions on http://alembic.zzzcomputing.com/en/latest/cookbook.html and added the info = dict(is_view=True) portion to my model and the following bits to my env.py file, but don't know where to go from here.
def include_object(object, name, type_, reflected, compare_to):
"""
Exclude views from Alembic's consideration.
"""
return not object.info.get('is_view', False)
...
context.configure(url=url,include_object = include_object)
I think (though haven't tested) that you can mark your Table as a view with the __table_args__ attribute:
class ViewSampleView(db.Model):
__tablename__ = 'vw_report_high_level_count'
__table_args__ = {'info': dict(is_view=True)}
id = db.Column(db.String(), primary_key=True)
rowcount = db.Column(db.Integer(), nullable=False)
I have a model being used by my flask server as well as a command-line tool. I want both the script and flask app to be able to create tables programmatically using this model.
In the flask app, I do this:
from models import db
db.app = app
db.init_app(app)
db.create_all()
However I can't use this code in the command-line tool as there is no 'app', and without the app I get
RuntimeError: application not registered on db instance and no application bound to current context
Which makes sense. So how do I make it work with the command-line tool without creating another version of my model file?
My model file (roughly):
class User(db.Model):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
account_id = Column(BigInteger, nullable=False)
team_name = Column(String, nullable=False)
external_id = Column(String(36), nullable=False)
# should not be filled with web app, but for misc notes we take later
notes = Column(String)
def __str__(self):
return "User (account_id={}, team_name='{}', external_id='{}')".format(
self.account_id, self.team_name, self.external_id)
__table_args__ = (
UniqueConstraint("account_id", name="unique_account_id"),
UniqueConstraint("external_id", name="unique_external_id"),
UniqueConstraint("team_name", name="unique_team_name")
)
related to this question: SQLAlchemy logging of changes with date and user
I'm using a modified version of the "recipe" for versioning changes automatically. I think it's able to handle some forms of relationships already (not sure, though), but I'm not able to handle the case where there's a many-to-many relationship in a separate table.
Here's a simple example that's an issue:
from history_meta import (Versioned, versioned_session)
Base = declarative_base()
user_to_group = Table('user_to_group', Base.metadata,
Column('user_login', String(60), ForeignKey('user.login')),
Column('group_name', String(100), ForeignKey('group.name'))
)
class User(Versioned, Base):
__tablename__ = 'user'
login = Column(String(60), primary_key=True, nullable=False)
password = Column(BINARY(20), nullable=False)
class Group(Versioned, Base):
__tablename__ = 'group'
name = Column(String(100), primary_key=True, nullable=False)
description = Column(String(100), nullable=True)
users = relationship(User, secondary=user_to_group, backref='groups')
When generating the tables in the database with Base.metadata.create_all(engine) I can see that there are only 5 tables: user, group, user_to_group, user_history, and group_history There is no user_to_group_history.
The "versioning" gets added to the declarative objects through inheritance of Versioned, but there's no way (that I can see) to do something similar with the user_to_group table which isn't using the declarative format. There's also notes in the documentation saying that it's not a good idea using a table that's mapped to a class so I'm trying to avoid using a declarative object for the relationship.