Flask Best Practices
This guide covers best practices for developing Flask applications, helping you write more maintainable, scalable, and professional code.
Project Structure
Use Application Factory Pattern
python
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app(config_name='default'):
app = Flask(__name__)
app.config.from_object(f'config.{config_name}')
# Initialize extensions
db.init_app(app)
# Register blueprints
from app.main import main as main_blueprint
app.register_blueprint(main_blueprint)
return appOrganize Code with Blueprints
python
# app/main/__init__.py
from flask import Blueprint
main = Blueprint('main', __name__)
from . import views, errorsRecommended Directory Structure
myapp/
├── app/
│ ├── __init__.py
│ ├── models.py
│ ├── main/
│ │ ├── __init__.py
│ │ ├── views.py
│ │ └── errors.py
│ ├── auth/
│ │ ├── __init__.py
│ │ └── views.py
│ ├── static/
│ └── templates/
├── migrations/
├── tests/
├── venv/
├── config.py
├── requirements.txt
└── run.pyConfiguration Management
Use Environment-Specific Configs
python
# config.py
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard-to-guess-string'
SQLALCHEMY_TRACK_MODIFICATIONS = False
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}Use Environment Variables
python
# .env
SECRET_KEY=your-secret-key
DATABASE_URL=postgresql://user:pass@localhost/dbname
FLASK_ENV=developmentDatabase Best Practices
Use Migrations
Always use Flask-Migrate for database schema changes:
bash
flask db init
flask db migrate -m "Initial migration"
flask db upgradeImplement Proper Models
python
from datetime import datetime
from app import db
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(120), unique=True, index=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def __repr__(self):
return f'<User {self.username}>'Use Database Sessions Properly
python
from app import db
# Good practice
try:
user = User(username='john')
db.session.add(user)
db.session.commit()
except Exception as e:
db.session.rollback()
raise e
finally:
db.session.close()Error Handling
Implement Custom Error Pages
python
@app.errorhandler(404)
def not_found(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('500.html'), 500Use Logging
python
import logging
from logging.handlers import RotatingFileHandler
if not app.debug:
file_handler = RotatingFileHandler('logs/app.log', maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Application startup')Security Best Practices
Protect Against CSRF
python
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect()
csrf.init_app(app)Use Strong Secret Keys
python
import secrets
# Generate a secure secret key
secret_key = secrets.token_hex(32)Validate User Input
python
from flask_wtf import FlaskForm
from wtforms import StringField, validators
class RegistrationForm(FlaskForm):
username = StringField('Username', [
validators.Length(min=4, max=25),
validators.Regexp('^[A-Za-z0-9_]+$')
])
email = StringField('Email', [validators.Email()])Performance Optimization
Use Caching
python
from flask_caching import Cache
cache = Cache(config={'CACHE_TYPE': 'simple'})
cache.init_app(app)
@app.route('/expensive')
@cache.cached(timeout=300)
def expensive_operation():
# Expensive computation
return resultImplement Pagination
python
@app.route('/users')
def users():
page = request.args.get('page', 1, type=int)
users = User.query.paginate(page=page, per_page=20)
return render_template('users.html', users=users)Use Database Connection Pooling
python
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
'pool_size': 10,
'pool_recycle': 3600,
'pool_pre_ping': True
}Testing
Write Unit Tests
python
import unittest
from app import create_app, db
class BasicTestCase(unittest.TestCase):
def setUp(self):
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
self.client = self.app.test_client()
def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
def test_home_page(self):
response = self.client.get('/')
self.assertEqual(response.status_code, 200)Deployment
Use Production WSGI Server
Never use Flask's built-in server in production:
bash
# Use Gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 "app:create_app()"Set Up Proper Logging
python
# Production logging configuration
if not app.debug and not app.testing:
if app.config['LOG_TO_STDOUT']:
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.INFO)
app.logger.addHandler(stream_handler)
else:
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/app.log',
maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s '
'[in %(pathname)s:%(lineno)d]'))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)Code Quality
Follow PEP 8
Use tools like flake8 or black for code formatting:
bash
pip install black flake8
black app/
flake8 app/Use Type Hints
python
from typing import Optional, List
def get_user(user_id: int) -> Optional[User]:
return User.query.get(user_id)
def get_all_users() -> List[User]:
return User.query.all()Document Your Code
python
def process_payment(amount: float, currency: str = 'USD') -> dict:
"""
Process a payment transaction.
Args:
amount: The payment amount
currency: The currency code (default: USD)
Returns:
A dictionary containing transaction details
Raises:
ValueError: If amount is negative
"""
if amount < 0:
raise ValueError("Amount cannot be negative")
# Process payment logic
return {'status': 'success', 'amount': amount}Summary
Following these best practices will help you:
- Write more maintainable and scalable code
- Improve application security
- Enhance performance
- Make debugging easier
- Facilitate team collaboration
Remember that best practices evolve over time, so stay updated with the Flask community and documentation.