Flask Multiple Databases
In more complex Flask applications, you might need to work with multiple databases simultaneously. This could be for various reasons, such as:
- Separating user data from application data
- Integrating with legacy systems that use different database engines
- Implementing a microservice architecture with specialized databases
- Creating read replicas for performance improvements
This tutorial will show you how to configure and use multiple databases in your Flask application using Flask-SQLAlchemy.
Prerequisites
Before diving in, make sure you're familiar with:
- Basic Flask application structure
- Flask-SQLAlchemy fundamentals
- Database connections and ORM concepts
Setting Up Multiple Databases in Flask
Step 1: Install Required Packages
First, ensure you have the necessary packages installed:
pip install flask flask-sqlalchemy psycopg2-binary pymysql
Step 2: Configure Multiple Database URIs
In your Flask application, you'll need to configure multiple database URIs:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# Configure the primary database (PostgreSQL)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@localhost/primary_db'
# Configure additional databases
app.config['SQLALCHEMY_BINDS'] = {
'users_db': 'postgresql://username:password@localhost/users_db',
'logs_db': 'mysql://username:password@localhost/logs_db'
}
# Disable track modifications warning
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Initialize SQLAlchemy
db = SQLAlchemy(app)
The SQLALCHEMY_DATABASE_URI
sets your primary database, while SQLALCHEMY_BINDS
is a dictionary that maps names to additional database URIs.
Step 3: Define Models for Different Databases
Next, define your models and associate them with specific databases:
# Model for the primary database (default)
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
price = db.Column(db.Float, nullable=False)
def __repr__(self):
return f'<Product {self.name}>'
# Model for the users_db
class User(db.Model):
__bind_key__ = 'users_db' # This associates the model with users_db
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return f'<User {self.username}>'
# Model for the logs_db
class Log(db.Model):
__bind_key__ = 'logs_db' # This associates the model with logs_db
id = db.Column(db.Integer, primary_key=True)
timestamp = db.Column(db.DateTime, nullable=False, default=db.func.current_timestamp())
level = db.Column(db.String(10), nullable=False)
message = db.Column(db.Text, nullable=False)
def __repr__(self):
return f'<Log {self.timestamp} {self.level}>'
The __bind_key__
class attribute is what connects your model to a specific database from the SQLALCHEMY_BINDS
configuration.
Step 4: Create the Tables
You'll need to create tables in each of your databases:
with app.app_context():
# Create tables in the primary database
db.create_all()
# Create tables in the bound databases
db.create_all(bind='users_db')
db.create_all(bind='logs_db')
Working with Multiple Databases
Inserting Data
Here's how you can insert data into your different databases:
with app.app_context():
# Add a product to the primary database
new_product = Product(name='Laptop', price=999.99)
db.session.add(new_product)
# Add a user to the users_db
new_user = User(username='john_doe', email='[email protected]')
db.session.add(new_user)
# Add a log to the logs_db
new_log = Log(level='INFO', message='Application started')
db.session.add(new_log)
# Commit all changes
db.session.commit()
Querying Data
Querying works the same way as with a single database:
with app.app_context():
# Query from the primary database
products = Product.query.all()
print("Products:", products)
# Query from the users_db
users = User.query.all()
print("Users:", users)
# Query from the logs_db
logs = Log.query.filter_by(level='INFO').all()
print("Info Logs:", logs)
Using Sessions with Specific Binds
If you need more control, you can create sessions bound to specific databases:
with app.app_context():
# Create a session for the users_db
users_session = db.create_scoped_session(options={"bind": db.get_engine(app, bind="users_db")})
# Use the specific session
new_user = User(username='jane_doe', email='[email protected]')
users_session.add(new_user)
users_session.commit()
users_session.close()
Real-World Example: E-commerce Application
Let's build a more complete example of an e-commerce application that uses multiple databases:
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
# Database configuration
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@localhost/ecommerce_products'
app.config['SQLALCHEMY_BINDS'] = {
'users': 'postgresql://username:password@localhost/ecommerce_users',
'orders': 'postgresql://username:password@localhost/ecommerce_orders',
'analytics': 'mysql://username:password@localhost/ecommerce_analytics'
}
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# Models for different databases
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
price = db.Column(db.Float, nullable=False)
stock = db.Column(db.Integer, nullable=False)
category = db.Column(db.String(50), nullable=False)
class User(db.Model):
__bind_key__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
class Order(db.Model):
__bind_key__ = 'orders'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, nullable=False)
total_amount = db.Column(db.Float, nullable=False)
status = db.Column(db.String(20), nullable=False, default='pending')
created_at = db.Column(db.DateTime, default=datetime.utcnow)
class OrderItem(db.Model):
__bind_key__ = 'orders'
id = db.Column(db.Integer, primary_key=True)
order_id = db.Column(db.Integer, nullable=False)
product_id = db.Column(db.Integer, nullable=False)
quantity = db.Column(db.Integer, nullable=False)
price = db.Column(db.Float, nullable=False)
class PageView(db.Model):
__bind_key__ = 'analytics'
id = db.Column(db.Integer, primary_key=True)
page = db.Column(db.String(200), nullable=False)
user_id = db.Column(db.Integer, nullable=True)
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
ip_address = db.Column(db.String(50), nullable=True)
# Create tables
with app.app_context():
db.create_all()
db.create_all(bind='users')
db.create_all(bind='orders')
db.create_all(bind='analytics')
# API Routes
@app.route('/products', methods=['GET'])
def get_products():
products = Product.query.all()
result = [
{'id': product.id, 'name': product.name,
'price': product.price, 'stock': product.stock}
for product in products
]
# Log this page view to analytics database
page_view = PageView(page='/products', ip_address=request.remote_addr)
db.session.add(page_view)
db.session.commit()
return jsonify(result)
@app.route('/users/<int:user_id>/orders', methods=['GET'])
def get_user_orders(user_id):
# Check if user exists in users database
user = User.query.get_or_404(user_id)
# Get orders from orders database
orders = Order.query.filter_by(user_id=user_id).all()
result = [
{'id': order.id, 'total': order.total_amount,
'status': order.status, 'date': order.created_at}
for order in orders
]
# Log this page view to analytics database
page_view = PageView(page=f'/users/{user_id}/orders',
user_id=user_id,
ip_address=request.remote_addr)
db.session.add(page_view)
db.session.commit()
return jsonify(result)
if __name__ == '__main__':
app.run(debug=True)
This example demonstrates:
- A product catalog database (primary)
- A separate users database for customer information
- An orders database for transactional data
- An analytics database for tracking user behavior
Best Practices for Multiple Databases
- Clearly define database responsibilities: Each database should have a specific purpose
- Use consistent naming conventions: For both database keys and model names
- Handle transactions carefully: Be aware that transactions are per database
- Consider connection pooling: Manage your connection pools efficiently for multiple databases
- Monitor database performance: Each database may have different performance characteristics
Common Challenges and Solutions
Challenge 1: Cross-Database Relationships
SQLAlchemy doesn't support relationships across different databases. To handle this, you'll need to manage these relationships in your application code:
def get_order_with_details(order_id):
# Get the order from the orders database
order = Order.query.get(order_id)
if not order:
return None
# Get the user from the users database
user = User.query.get(order.user_id)
# Get order items and associated products
order_items = OrderItem.query.filter_by(order_id=order_id).all()
# Build the complete order data
order_data = {
'id': order.id,
'user': {
'id': user.id,
'username': user.username
},
'total': order.total_amount,
'status': order.status,
'items': []
}
# Add each item with product details
for item in order_items:
product = Product.query.get(item.product_id)
order_data['items'].append({
'product_name': product.name,
'quantity': item.quantity,
'price': item.price
})
return order_data
Challenge 2: Managing Transactions Across Databases
When you need to make changes to multiple databases that must succeed or fail together:
def create_order(user_id, product_ids, quantities):
# Start with getting products and calculating total
products = []
total_amount = 0
for i, product_id in enumerate(product_ids):
product = Product.query.get(product_id)
if not product or product.stock < quantities[i]:
return False, "Product unavailable"
products.append(product)
total_amount += product.price * quantities[i]
try:
# Create order in orders database
order = Order(user_id=user_id, total_amount=total_amount)
db.session.add(order)
db.session.flush() # This assigns an ID to order without committing
# Add order items
for i, product in enumerate(products):
item = OrderItem(
order_id=order.id,
product_id=product.id,
quantity=quantities[i],
price=product.price
)
db.session.add(item)
# Update stock in products database
for i, product in enumerate(products):
product.stock -= quantities[i]
# Commit all changes
db.session.commit()
return True, order.id
except Exception as e:
db.session.rollback()
return False, str(e)
Note that this approach isn't a true distributed transaction but uses SQLAlchemy's session to manage the operations.
Summary
Working with multiple databases in Flask offers flexibility for complex applications, allowing you to separate concerns and optimize for different use cases. With Flask-SQLAlchemy's binds feature, you can connect to different database engines and manage your models appropriately.
Key takeaways:
- Use
SQLALCHEMY_BINDS
to configure multiple database connections - Assign models to specific databases with the
__bind_key__
attribute - Create tables in each database using
db.create_all(bind='database_name')
- Handle transactions carefully when working across databases
- Consider application design patterns that work well with data partitioning
Additional Resources and Exercises
Resources
Exercises
-
Basic Multiple Database Setup: Create a Flask application with two databases - one for products and another for reviews. Add functionality to display products with their associated reviews.
-
Read Replica Pattern: Implement a pattern where write operations go to a primary database, but read operations use a replica database. (You can simulate this with two database configurations.)
-
Database Migration Challenge: Create a Flask application that gradually migrates data from an old database schema to a new one, using both databases during the transition.
-
Analytics Database: Add an analytics database to an existing Flask application that tracks page views, user sessions, and conversion rates without affecting the performance of the main application.
-
Multi-tenant Application: Build a Flask application that serves multiple clients, each with their own separate database, while sharing common application code.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)