Serving a page with multiple database's - python

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'

Related

Flask relationships with sql alschemy - no such table error

I'm working through a flask relationships tutorial. I'm trying to make a database that stores puppies, information about their toys, and information about their owners, showing in
basic.py:
# basic.py
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
Migrate(app, db)
class Puppy(db.Model):
__tablename__ = 'puppies'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.Text)
toys = db.relationship('Toy', backref = 'puppy', lazy = 'dynamic')
owner = db.relationship('Owner', backref = 'puppy', uselist = False)
def __init__(self, name):
self.name = name
def __repr__(self):
if self.owner:
return "Puppy name is {self.name} and owner is {self.owner}"
else:
return "Puppy is {self.name} and has no owner yet"
def report_toys(self):
print("Here are my toys:")
for toy in self.toys:
print(toy.item_name)
class Toy(db.Model):
__tablename__ = 'toys'
id = db.Column(db.Integer, primary_key = True)
item_name = db.Column(db.Text)
puppy_id = db.Column(db.Integer, db.ForeignKey('puppies.id'))
def __init__(self, item_name, puppy_id):
self.item_name = item_name
self.puppy_id = puppy_id
class Owner(db.Model):
__tablename__ = 'owners'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.Text)
puppy_id = db.Column(db.Integer, db.ForeignKey('puppies.id'))
def __init__(self, name, puppy_id):
self.name = name
self.puppy_id = puppy_id
I then set up a manual database in models.py:
# models.py
from basic import db, Puppy, Owner, Toy
rufus = Puppy('Rufus')
fido = Puppy('Fido')
db.session.add_all([rufus, fido])
db.session.commit
rufus = Puppy.query.filter_by(name = 'Rufus').first()
kate = Owner('Kate', rufus.id)
toy1 = Toy('Chew Toy', rufus.id)
toy2 = Toy('Ball', rufus.id)
db.session.add_all([kate, toy1, toy2])
db.session.commit()
But I'm getting an error:
cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (raised as a result of Query-invoked autoflush; consider using a session.no_autoflush block if this flush is occurring prematurely)
(sqlite3.OperationalError) no such table: puppies
[SQL: INSERT INTO puppies (name) VALUES (?)]
[parameters: ('Rufus',)]
(Background on this error at: http://sqlalche.me/e/13/e3q8)
I'm new to this, so any help would be appreciated.
did you create the database ?
you need to import your db object and models and then create the database like following:
(venv) $ flask shell
>>> from basic import db
>>> from basic import Puppy, Toy, Owner
>>> db.create_all()
let me know if this solves your problem
The error message shows no table of puppies in your database. To apply migrations to your database, you need some steps while using flask-migrate.
The link would be helpful https://flask-migrate.readthedocs.io/en/latest/
Because you are using flask_migrate you need to open a terminal, be sure to be in the same working directory of your project and run these commands:
This you need to run it before you run the actual Flask App
CMD with in the envirorments folder:
>>> set FLASK_APP= app.py (optional)
>>> flask db init
>>> flask db migrate -m 'Any name you want' ---> what's inside the '' is the name of migrati
>>> flask db upgrade (perform the actual migration)
Normally if you don't use flask_migrate but only flask_sqlalchemy or SQLAlchemy vanilla, this is what you should run.
CMD with in the envirorments folder:
>>> from app import db
>>> db.create_all()
>>> exit() #or ctrl + z then enter
EDIT
Everytime you are adding something (or removing) from the DB Tables of instance:
class Puppy(db.Model):
__tablename__ = 'puppies'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.Text)
toys = db.relationship('Toy', backref = 'puppy', lazy = 'dynamic')
owner = db.relationship('Owner', backref = 'puppy', uselist = False)
Becomes --->
class Puppy(db.Model):
__tablename__ = 'puppies'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.Text)
age = db.Column(db.Integer, nullable=False)#Added new item
toys = db.relationship('Toy', backref = 'puppy', lazy = 'dynamic')
owner = db.relationship('Owner', backref = 'puppy', uselist = False)
Then you should delete the database file, in your case 'data.sqlite' (within your working directory) and run the above mentioned code again,
In fact by running the above code, you create database table but also the actual sqlite file

How do I change the schema for both a table and a foreign key?

I have the following simplified database access layer and two tables:
class DataAccessLayer():
def __init__(self):
conn_string = "mysql+mysqlconnector://root:root#localhost/"
self.engine = create_engine(conn_string)
Base.metadata.create_all(self.engine)
Session = sessionmaker()
Session.configure(bind=self.engine)
self.session = Session()
class MatchesATP(Base):
__tablename__ = "matches_atp"
__table_args__ = {"schema": "belgarath", "extend_existing": True}
ID_M = Column(Integer, primary_key=True)
ID_T_M = Column(Integer, ForeignKey("oncourt.tours_atp.ID_T"))
class TournamentsATP(Base):
__tablename__ = "tours_atp"
__table_args__ = {"schema": "oncourt", "extend_existing": True}
ID_T = Column(Integer, primary_key=True)
NAME_T = Column(String(255))
I want to be able to switch the schema names for the two tables to test databases as follows:
belgarath to belgarath_test
oncourt to oncourt_test
I've tried adding:
self.session.connection(execution_options={"schema_translate_map": {"belgarath": belgarath, "oncourt": oncourt}})
To the bottom of DataAccessLayer and then initialising the class with two variables as follows:
def __init__(self, belgarath, oncourt):
However, when I build the following query:
dal = DataAccessLayer("belgarath_test", "oncourt_test")
query = dal.session.query(MatchesATP)
print(query)
I get the following SQL:
SELECT belgarath.matches_atp.`ID_M` AS `belgarath_matches_atp_ID_M`, belgarath.matches_atp.`ID_T_M` AS `belgarath_matches_atp_ID_T_M`
FROM belgarath.matches_atp
This is still referencing the belgarath table.
I also can't figure out a way of changing the schema of the foreign key of oncourt.tours_atp.ID_T at the same time as the tables.
Are there individual solutions or a combined solution to my issues?
You might wanna decorate your subclassed Base declarative model with the #declared_attr decorator.
Try this--
In a base class for your models, say __init__.py...
from sqlalchemy.ext.declarative import declarative_base, declared_attr
SCHEMA_MAIN = 'belgarath' # figure out how you want to retrieve this
SCHEMA_TEST = 'belgarath_test'
class _Base(object):
#declared_attr
def __table_args__(cls):
return {'schema': SCHEMA_MAIN}
...
Base = declarative_base(cls=_Base)
Base.metadata.schema = SCHEMA_MAIN
Now that you have a Base that subclasses _Base with the main schema already defined, all your other models will subclass Base and do the following:
from . import Base, declared_attr, SCHEMA_TEST
class TestModel(Base):
#declared_attr
def __table_args__(cls):
return {'schema': SCHEMA_TEST}
Changing a schema for a foreign key could look like this:
class TournamentsATP(Base):
__tablename__ = "tours_atp"
__table_args__ = {"schema": "oncourt", "extend_existing": True}
ID_T = Column(Integer, primary_key=True)
NAME_T = Column(String(255))
match_id = Column('match_id', Integer, ForeignKey(f'{__table_args__.get("schema")}.matches_atp.id'))
Where match_id is a foreign key to matches_atp.id by using the __table_args[schema] element defined at the class level via #declared_attr.
It only took me 18 months to figure this out. Turns out I needed to add the schema_translate_map to an engine and then create the session with this engine:
from sqlalchemy import create_engine
engine = create_engine(conn_str, echo=False)
schema_engine = engine.execution_options(schema_translate_map={<old_schema_name>: <new_schema_name>})
NewSession = sessionmaker(bind=schema_engine)
session = NewSession()
All ready to roll...
Assuming your goal is to:
have dev/test/prod schemas on a single mysql host
allow your ORM classes to be flexible enough to be used in three different environments without modification
Then John has you most of the way to one type of solution. You could use #declared_attr to dynamically generate __table_args__ as he has suggested.
You could also consider using something like flask-sqlalchemy that comes with a built-in solution for this:
import os
DB_ENV = os.getenv(DB_ENV)
SQLALCHEMY_BINDS = {
'belgarath': 'mysql+mysqlconnector://root:root#localhost/belgarath{}'.format(DB_ENV),
'oncourt': 'mysql+mysqlconnector://root:root#localhost/oncourt{}'.format(DB_ENV)
}
class MatchesATP(Base):
__bind_key__ = "belgarath"
ID_M = Column(Integer, primary_key=True)
ID_T_M = Column(Integer, ForeignKey("oncourt.tours_atp.ID_T"))
class TournamentsATP(Base):
__bind_key__ = "oncourt"
ID_T = Column(Integer, primary_key=True)
NAME_T = Column(String(255))
Basically this method allows you to create a link to a schema (a bind key), and that schema is defined at run-time via the connection string. More information at the flask-sqlalchemy link.

flask-sqlalchemy: Trouble joining tables from two databases (different bind keys). Getting error 1146 (see below)

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'
}

Error deseralizing JSON string with nested objects with SqlAlchemy and Marshmallow

I'm trying to build a REST API application in Python 3.4, using Flask, SQLAlchemy and Marshmallow.
In my model I have a User class with a one to many relationship with a MailAddress class.
If I run a GET request, I manage to read data from the DB, and the data is correctly returned as a JSON string.
Instead, if I run a POST request with the JSON serialization of my User object with some MailAddresses, I get this error:
File "X:\test\...\site-packages\sqlalchemy\orm\collections.py", line 785, in bulk_replace
constants = existing_idset.intersection(values or ())
File "X:\test\...\site-packages\sqlalchemy\util\_collections.py", line 612, in intersection
result._members.update(self._working_set(members).intersection(other))
TypeError: unhashable type: 'dict'
I've tried adding a __hash__ function to my model classes (as suggested at sqlalchemy: TypeError: unhashable type creating instance, sqlalchemy) but that didn't help.
Here is a complete code example that shows this problem:
from flask import Flask, request
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy
from marshmallow import fields
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
class Config(object):
SQLALCHEMY_DATABASE_URI = '<CONNECTION STRING HERE>'
SQLALCHEMY_TRACK_MODIFICATIONS = False
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
ma = Marshmallow(app)
# Model
class MailAddress(db.Model):
__tablename__ = 'mail_addresses'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
mail_type = Column(String(200), nullable=False)
mail = Column(String(200), nullable=False)
def __init__(self, mail, mail_type):
self.mail = mail
self.mail_type = mail_type
class MailAddressSchema(ma.ModelSchema):
class Meta:
model = MailAddress
class User(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(200), nullable=False)
mail_addresses = relationship('MailAddress', backref='user')
def __init__(self, name, mail_addresses):
self.name = name
self.mail_addresses = mail_addresses
def __hash__(self):
return hash(self.name)
class UserSchema(ma.ModelSchema):
mail_addresses = fields.Nested(MailAddressSchema, many = True, only=('mail', 'mail_type'))
class Meta:
model = User
# Routes
user_schema = UserSchema()
#app.route('/api/v0/user', methods=['GET'])
def user_get():
users = db.session.query(User).all()
return user_schema.jsonify(users, many = True), 200
#app.route('/api/v0/user', methods=['POST'])
def user_create():
new_instance = user_schema.make_instance(request.json)
db.session.add(new_instance)
db.session.commit()
return user_schema.jsonify(new_instance), 201
# Main
if __name__ == '__main__':
app.run('localhost', 5555)
Is there something I'm missing?
Use load instead make_instance
#app.route('/api/v0/user', methods=['POST'])
def user_create():
new_instance, errors = user_schema.load(request.json)
db.session.add(new_instance)
db.session.commit()
return user_schema.jsonify(new_instance), 201

How to query with many tables

from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
#import sqlite3 as sql
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://ahmad:ahmad#192.168.3.103/utama'
db = SQLAlchemy(app)
class ak(db.Model):
__tablename__ = 'ak'
id = db.Column(db.Integer, primary_key=True)
nama = db.Column(db.String)
alamat = db.Column(db.String)
akreditasi = db.Column(db.String)
def __init__(self, id, nama, alamat, akreditasi):
self.id = id
self.city = nama
self.alamat = alamat
self.akreditasi = akreditasi
class av(db.Model):
__tablename__ = 'av'
id = db.Column(db.Integer, primary_key=True)
nama = db.Column(db.String)
alamat = db.Column(db.String)
akreditasi = db.Column(db.String)
def __init__(self, id, nama, alamat, akreditasi):
self.id = id
self.city = nama
self.alamat = alamat
self.akreditasi = akreditasi
id_jurusan = db.Table('id_jurusan',
db.Column('id', db.Integer, db.ForeignKey('ak.id')),
db.Column('id', db.Integer, db.ForeignKey('av.id'))
)
#app.route('/ak')
def jurusan(jurusan):
return render_template('index.html', rows=ak.query.all() )
#app.route('/av')
def Akuntansi():
return render_template('index.html', rows=av.query.all() )
if __name__ == '__main__':
app.run(debug=True, host='1.1.1.1', port=80)
I am a new to learn python, in this case I studied the framework flask and I had trouble on the declaration SQLAlchemy, precisely displays the contents of the table but with the same structure,when executed will be like this.....
[
which one success
You are using the decorator
#app.route('/av')
The method which succeeds Akuntansi() does not require a parameter. So this works. The method which fails expects a parameter jurusan(jurusan) but your decorator #app.route('/ak') does not consider this.
To pass a parameter you need to use the decorator like this:
#app.route("/ak/<jurusan>") and then also pass the parameter in the request.

Categories