I'm trying to make a pair of tables with a many-many relationship. Here's my code to set up the tables:
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.exc import IntegrityError
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
recipe_ingredient = db.Table('recipe_ingredient',
db.Column('recipe_name', db.String(64), db.ForeignKey('recipe.name')),
db.Column('ingredient_name', db.String(64), db.ForeignKey('ingredient.name'))
)
class Recipe(db.Model):
name = db.Column(db.String(64), primary_key = True)
ingredients_for = db.relationship('Ingredient', secondary='recipe_ingredient', backref='recipes_for')
def __repr__(self):
return f'<Recipe: {self.name}>'
class Ingredient(db.Model):
name = db.Column(db.String(64), primary_key = True)
def __repr__(self):
return f'<Ingredient: {self.name}>'
Now, here's some test code to try and add data to the tables:
db.create_all()
r0 = Recipe(name='eggsandwich')
db.session.merge(r0)
r1 = Recipe(name='tomatoegg')
db.session.merge(r1)
i0 = Ingredient(name='egg')
db.session.merge(i0)
i1 = Ingredient(name='bread')
db.session.merge(i1)
i2 = Ingredient(name='tomato')
db.session.merge(i2)
db.session.commit()
r0.ingredients_for.append(i0)
r0.ingredients_for.append(i1)
r1.ingredients_for.append(i0)
r1.ingredients_for.append(i2)
db.session.commit()
print([i.name for i in i.ingredients_for])
When I run this, it prints "['eggsandwich', 'tomatoegg']", as expected. However, in DB Browser, the recipe_ingredient table is empty:
Why isn't this table populating? How do I get the relationships to show up there?
Let say I have a model
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Students(db.Model):
__tablename__ = "students"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
description = db.Column(db.String, nullable=False)
class Hobbies(db.Model):
__tablename__ = "hobbies"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, nullable=False)
description = db.Column(db.String)
student_id = db.Column(db.Integer, db.ForeignKey("students.id"), nullable=False)
Now in flask I have following code
import os
from flask import Flask, render_template, request
from models import *
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql://junaid:junaid#localhost:5432/UOB"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db.init_app(app)
Now I get all the record in the table Hobbies
from sqlalchemy.sql import select
with app.app_context():
a = Hobbies.query.all()
I want to get the result like
Hobby.ID, Hobby.title, Hobby.description, Student.name (The filter is Hobby.student_id = Students.id)
I want to print all the record in the hobbies table with student name in short.
Here is the solution. Sharing if someone else can benefit from this.
hobbies_with_stud = db.session.query(Students, Hobbies).join(Hobbies, Hobbies.student_id == Students.id).all()
for record in hobbies_with_stud:
print(record.Hobbies.title + " is the hobby of "+ record.Students.name)
When trying to get a many to many relationship working I keep getting the following error:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'Mapper|User|Users'. Original exception was: When initializing mapper Mapper|User|Users, expression 'Device' failed to locate a name ("name 'Device' is not defined"). If this is a class name, consider adding this relationship() to the class after both dependent classes have been defined.
I have looked over all the sqlalchemy documents and reviewed multiple links on many to many but no luck. I am sure its a naming or importing issue, but have not found a solution yet
I removed some of the code that I don't feel is related
Users.py
from random import SystemRandom
from backports.pbkdf2 import pbkdf2_hmac, compare_digest
from flask_login import UserMixin
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship
from devices.models import Device
user_device = db.Table('UserDevice', db.Model.metadata,
db.Column('userID', db.Integer, db.ForeignKey('Users.userID')),
db.Column('deviceID', db.Integer, db.ForeignKey('Device.deviceID')))
class User(UserMixin, db.Model):
__tablename__ = 'Users'
__table_args__ = {'mysql_engine': 'InnoDB',
'extend_existing': True}
id = db.Column('userID', db.Integer, primary_key=True)
# Relationship to UserDevice association table
user_device = relationship('Device',
secondary=user_device,
backref=db.backref('users', lazy='dynamic'))
Device.py
class Device(db.Model):
__tablename__ = 'Device'
__table_args__ = {'mysql_engine': 'InnoDB',
'extend_existing': True}
id = db.Column('deviceID', db.Integer, primary_key=True)
date_created = db.Column('deviceDateCreated', db.DateTime, default=db.func.current_timestamp())
date_modified = db.Column('deviceDateModified', db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp())
device_created_user = db.Column('deviceCreatedUser', db.String, default='App Server')
device_last_updated_user = db.Column('deviceLastUpdatedUser', db.String, default='App Server', onupdate=current_user)
#Serial Number
serial_number = db.Column('deviceSerialNumber', db.Integer, nullable=False, unique=True)
#Sampling Interval
sampling_interval = db.Column('deviceSamplingInterval', db.Integer, default=60, nullable=False)
# Relationship to Device Status Table
device_status_id = db.Column('deviceStatusID', db.Integer, db.ForeignKey('DeviceStatus.deviceStatusID'))
# New instance instantiation procedure
def __init__(self, serial_number):
self.serial_number = serial_number
self.device_status_id = 1
def __repr__(self):
return '<Device %r>' % self.serial_number
Image of Database Model:
Turns out I didn't provide enough information to solve this problem. The problem turned out to be using the db variable created by calling SQLAlchemy. I created a python file just for the database called database.py. The mistake I made was in User\models.py I called the following import from database import db and in Device\models.py I called from app import db. This caused the db.Model to not function properly and also wouldn't create the user tables when calling create_all(). Hope this helps someone in the future.
Database.py
from flask_influxdb import InfluxDB
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
influx_db = InfluxDB()
influx_db_client = None
def init_db():
# import all modules here that might define models so that
# they will be registered properly on the metadata. Otherwise
# you will have to import them first before calling init_db()
from users.models import User, UserStatus, UserDevice
from devices.models import Device, DeviceStatus
db.Model.metadata.drop_all(bind=db.engine)
db.Model.metadata.create_all(bind=db.engine)
Devices\models.py
from app import db
from flask_login import current_user
from sqlalchemy.orm import relationship
import enum
class DeviceStatusType(enum.Enum):
INACTIVE = "Inactive"
ACTIVE = "Active"
# Define a Device model
class Device(db.Model):
__tablename__ = 'Device'
__table_args__ = {'extend_existing': True}
id = db.Column('deviceID', db.Integer, primary_key=True)
date_created = db.Column('deviceDateCreated', db.DateTime, default=db.func.current_timestamp())
date_modified = db.Column('deviceDateModified', db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp())
device_created_user = db.Column('deviceCreatedUser', db.String(128), default='App Server')
device_last_updated_user = db.Column('deviceLastUpdatedUser', db.String(128), default='App Server', onupdate=current_user)
#Serial Number
serial_number = db.Column('deviceSerialNumber', db.Integer, nullable=False, unique=True)
#Sampling Interval
sampling_interval = db.Column('deviceSamplingInterval', db.Integer, default=60, nullable=False)
# Relationship to Device Status Table
device_status_id = db.Column('deviceStatusID', db.Integer, db.ForeignKey('DeviceStatus.deviceStatusID'))
users = relationship("User", secondary="userDevice")
# New instance instantiation procedure
def __init__(self, serial_number):
self.serial_number = serial_number
self.device_status_id = 1
def __repr__(self):
return '<Device %r>' % self.serial_number
users\models.py
from random import SystemRandom
from backports.pbkdf2 import pbkdf2_hmac, compare_digest
from flask_login import UserMixin, current_user
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship, backref
from devices.models import Device
import enum
# Import the database object (db) from the main application module
# We will define this inside /app/__init__.py in the next sections.
from app import db
class UserStatusType(enum.Enum):
INACTIVE = "Inactive"
ACTIVE = "Active"
# Define a User model
class User(UserMixin, db.Model):
__tablename__ = 'User'
__table_args__ = {'extend_existing': True}
id = db.Column('userID', db.Integer, primary_key=True)
date_created = db.Column('userDateCreated', db.DateTime, default=db.func.current_timestamp())
date_modified = db.Column('userDateModified', db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp())
user_created_user = db.Column('userCreatedUser', db.String(128), default=current_user)
user_last_updated_user = db.Column('userLastUpdatedUser', db.String(128), default=current_user, onupdate=current_user)
# First Name
first_name = db.Column('userFirstName', db.String(128), nullable=False)
# Last Name
last_name = db.Column('userLastName', db.String(128), nullable=False)
# User Name
user_name = db.Column('userUserName', db.String(128), nullable=False, unique=True)
# Email
email = db.Column('userEmailAddress', db.String(128), nullable=False, unique=True)
# Password
_password = db.Column('userPassword', db.LargeBinary(128))
_salt = db.Column('userSalt', db.LargeBinary(128))
# Relationship to User Status table
user_status_id = db.Column('userStatusID', db.Integer, db.ForeignKey('UserStatus.userStatusID'))
# Relationship to UserDevice association table
devices = relationship("Device", secondary="userDevice")
#hybrid_property
def password(self):
return self._password
# In order to ensure that passwords are always stored
# hashed and salted in our database we use a descriptor
# here which will automatically hash our password
# when we provide it (i. e. user.password = "12345")
#password.setter
def password(self, value):
# When a user is first created, give them a salt
if self._salt is None:
self._salt = bytes(SystemRandom().getrandbits(8))
self._password = self._hash_password(value)
def is_valid_password(self, password):
"""Ensure that the provided password is valid.
We are using this instead of a ``sqlalchemy.types.TypeDecorator``
(which would let us write ``User.password == password`` and have the incoming
``password`` be automatically hashed in a SQLAlchemy query)
because ``compare_digest`` properly compares **all***
the characters of the hash even when they do not match in order to
avoid timing oracle side-channel attacks."""
new_hash = self._hash_password(password)
return compare_digest(new_hash, self._password)
def _hash_password(self, password):
pwd = password.encode("utf-8")
salt = bytes(self._salt)
buff = pbkdf2_hmac("sha512", pwd, salt, iterations=100000)
return bytes(buff)
# New instance instantiation procedure
def __init__(self, first_name, last_name, user_name, email, password):
self.first_name = first_name
self.last_name = last_name
self.user_name = user_name
self.email = email
self.password = password
self.user_status_id = 2
def __repr__(self):
return "<User #{:d}>".format(self.id)
I'm building a Flask-Restful API using python and sqlalchemy, and I'm trying to join two tables from different databases. It appears that I'm only able to search for tables in one database at a time. Am I missing something?
from flask_sqlalchemy import SQLAlchemy
from flask import Flask, jsonify, request
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://username:password#host:8000/database1'
app.config['SQLALCHEMY_BINDS'] = {
'database2': 'mysql://username:password#host:8000/database2'
}
db = SQLAlchemy(app)
db.create_all(bind='database2')
class Table1(db.Model):
__tablename__ = "table1"
__table_args__ = {'schema':'database1'}
location_id = db.Column(db.Integer, primary_key=True)
def __init__(self, location_id):
self.location_id = location_id
def __repr__(self):
return '{}'.format(self.location_id)
class Table2(db.Model):
__bind_key__ = "database2"
__tablename__ = "table2"
__table_args__ = {'schema':'database2'}
other_id = db.Column(db.Integer, primary_key=True)
location_id = db.Column(db.Integer, db.ForeignKey('database1.table1.location_id'))
def __init__(self, other_id, location_id):
self.other_id = other_id
self.location_id = location_id
def __repr__(self):
return '{}'.format(self.other_id)
#app.route('/', methods=['GET'])
def returnRes():
session = db.session
q = session.query(table1).join(table2, table1.location_id==table2.location_id).all()
return str(q)
In my browser, I am getting the error:
'sqlalchemy.exc.ProgrammingError: (_mysql_exceptions.ProgrammingError) (1146, "Table 'database1.table2' doesn't exist").
Both tables indeed exist, as when I change my query to
q = session.query(table2).join(table1, table2.location_id==table1.location_id).all()
I get an error that database2.table1 doesn't exist.
I'm using python==3.6.1, Flask==0.11.1 and Flask-SQLAlchemy==2.1
Adding a data base schema argument to my table classes and adding a foreign key fixed this problem. I found the answer at this link: https://github.com/mitsuhiko/flask-sqlalchemy/issues/172
I've updated the question to reflect the answer in case it helps anyone else.
I'm not sure if the binds are redundant, but I've left them in because they don't seem to interfere with anything.
You need to add Schema declarations. It is strange that Flask-SQLAlchemy doesn't mention this at all in their documentation.
The Foreign Keys are unnecessary for this to work.
class Table1(db.Model):
__tablename__ = "table1"
__table_args__ = {'schema':'database1'}
#...
def __init__(self, location_id):
self.location_id = location_id
def __repr__(self):
return '{}'.format(self.location_id)
class Table2(db.Model):
__bind_key__ = "database2"
__tablename__ = "table2"
__table_args__ = {'schema':'database2'}
#...
so long as your config file looks like this
SQLALCHEMY_DATABASE_URI = 'postgres:////path/to/database1'
SQLALCHEMY_BINDS = {
'database2': 'mysqldb:////path/to/database2'
}
What i have: multiple identical databses and a SQL-Alchemy setup.
What I need to achieve: a single "Flask" function able to query all the databses. All other functions need to access only one of the databases.
The DB's already exist and I am loading data from them.
Initially I was only using the SQL-Alchemy abstraction layer, without any Flask model. This is how the code looked like:
app = Flask(__name__)
app.config.from_object('myflaskapp.config.settings')
metadata = None
def connect_db():
engine = create_engine(app.config['DATABASE_URI'])
global metadata
metadata = MetaData(bind=engine)
return engine.connect()
#app.before_request
def before_request():
g.db = connect_db()
#app.after_request
def after_request(response):
close_connection()
return response
being DATABASE_URI declared in settings.py. I guess doesn't matter, however the underlying DB is a Mysql server and DATABASE_URI looks like:
DATABASE_URI = 'mysql://' + dbuser + ':' + dbpass + '#' + dbhost + '/' +dbname
The above code allows me to write something like this:
myTable = Table('branch', metadata, autoload=True)
myBranch = select([myTable])
which is quite handy. This approach works just fine unless I have to deal with more than one DB. To be more precise I'd like to show (from within the same function) data belonging to multiple DB's that have exactly the same structure. Meaning that the same query can be succesfully run againsy any DB. In pseudo code:
#app.route('/summary')
def summary():
....
allData = []
for db in all_of_my_dbs:
allData.append(db.query())
....
sendToTemplate(allData)
Is it something doable? Is it feasable?
Thank you.
It's definitely possible.
Try adding something like this to your settings file
SQLALCHEMY_BINDS = {
'users': 'mysqldb://localhost/users',
'appmeta': 'sqlite:////path/to/appmeta.db'
}
Edit
So here is a working copy/paste example
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.debug = True
databases = {
'sqlite1': 'sqlite:///sqlite1.db',
'sqlite2': 'sqlite:///sqlite2.db'
}
app.config['SQLALCHEMY_BINDS'] = databases
db = SQLAlchemy(app)
class Thing(db.Model):
__tablename__ = "sqlite1"
thing_id = db.Column(db.Integer, primary_key=True)
thing_val = db.Column(db.String(40))
def __init__(self, thing_val):
self.thing_val = thing_val
class Thing2(db.Model):
__tablename__ = "sqlite2"
thing_id = db.Column(db.Integer, primary_key=True)
thing_val = db.Column(db.String(40))
def __init__(self, thing_val):
self.thing_val = thing_val
if __name__ == "__main__":
db.create_all()
t1 = Thing("test1")
t2 = Thing2("test2")
db.session.add(t1)
db.session.add(t2)
db.session.commit()
t1 = Thing.query.filter_by(thing_id=1).first()
t2 = Thing2.query.filter_by(thing_id=1).first()
print t1.thing_val
print t2.thing_val
First I would like to remark a couple of things regarding my very own situation. The two tables have the same name, same DDL but belong to different schema's/database's.
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
databases = {
'sqlite1': 'sqlite:///sqlite1.db',
'sqlite2': 'sqlite:///sqlite2.db'
}
app.config['SQLALCHEMY_BINDS'] = databases
db = SQLAlchemy(app)
class Thing(db.Model):
__tablename__ = "mytable"
__bind_key__ = 'sqlite1'
thing_id = db.Column(db.Integer, primary_key=True)
thing_val = db.Column(db.String(40))
def __init__(self, thing_val):
self.thing_val = thing_val
if __name__ == "__main__":
t1 = Thing.query.filter_by(thing_id=1).first()
print t1.name
The moment I add a second Class named Thing2 just after the first class definition everything breakes. I.e.:
class Thing(db.Model):
__tablename__ = "mytable"
__bind_key__ = 'sqlite1'
thing_id = db.Column(db.Integer, primary_key=True)
thing_val = db.Column(db.String(40))
def __init__(self, thing_val):
self.thing_val = thing_val
class Thing2(db.Model):
__tablename__ = "mytable"
__bind_key__ = 'sqlite2'
thing_id = db.Column(db.Integer, primary_key=True)
thing_val = db.Column(db.String(40))
def __init__(self, thing_val):
self.thing_val = thing_val
The above code breaks with this error:
InvalidRequestError: Table 'mytable' is already defined for this MetaData instance. Specify 'extend_existing=True' to redefine options and columns on an existing Table object.
So the framework doesn't seem to be able to distinguish between the two of them.
apparently there is a bug on that issue -https://github.com/mitsuhiko/flask-sqlalchemy/pull/222 so the problem is that the bind key must be unique and can be used only for one class (at least for now),so you could still have multiple databases but each database can have only one table or a class but it does appear to have a huge overhead if you many tables still this is just a workaround at least for now...
here is the reference for "Binds" -http://flask-sqlalchemy.pocoo.org/2.0/binds/#binds
Below is the configuration:
SQLALCHEMY_DATABASE_URI = 'sqlite://///main.db'
SQLALCHEMY_BINDS= {
'table1':'sqlite://///DB1.db',
'table2':'sqlite://///DB2.db',
}
and below is the model:
class table1(db.Model):
__tablename__ = "table1"
__bind_key__='table1'
class table2(db.Model):
__tablename__ = "table2"
__bind_key__='table2'