Flask 扩展生态
2026/3/20大约 9 分钟
Flask 扩展生态
第一章:扩展概述
Flask 扩展机制
Flask 扩展是为 Flask 应用添加特定功能的软件包。它们遵循一定的约定,使得集成变得简单统一。
扩展命名规范
- 包名:
flask-xxx或Flask-XXX - 导入名:
flask_xxx
扩展初始化模式
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
# 模式一:直接初始化
app = Flask(__name__)
db = SQLAlchemy(app)
# 模式二:延迟初始化(推荐用于工厂函数)
db = SQLAlchemy()
login_manager = LoginManager()
def create_app():
app = Flask(__name__)
db.init_app(app)
login_manager.init_app(app)
return app
第二章:核心扩展详解
Flask-SQLAlchemy(ORM)
pip install flask-sqlalchemy
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class User(db.Model):
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)
posts = db.relationship('Post', backref='author', lazy='dynamic')
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
# 创建表
with app.app_context():
db.create_all()
Flask-Migrate(数据库迁移)
pip install flask-migrate
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
app = Flask(__name__)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
# 命令行使用
# flask db init # 初始化迁移目录
# flask db migrate -m "Initial migration" # 生成迁移脚本
# flask db upgrade # 应用迁移
# flask db downgrade # 回滚迁移
# flask db history # 查看迁移历史
Flask-Login(用户会话)
pip install flask-login
from flask import Flask, render_template, redirect, url_for
from flask_login import LoginManager, UserMixin, login_user, logout_user
from flask_login import login_required, current_user
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80))
password_hash = db.Column(db.String(256))
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
user = User.query.filter_by(username=request.form['username']).first()
if user and user.check_password(request.form['password']):
login_user(user)
return redirect(url_for('dashboard'))
return render_template('login.html')
@app.route('/dashboard')
@login_required
def dashboard():
return f'Hello, {current_user.username}!'
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
Flask-WTF(表单处理)
pip install flask-wtf
from flask import Flask, render_template, redirect, url_for, flash
from flask_wtf import FlaskForm
from flask_wtf.csrf import CSRFProtect
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
csrf = CSRFProtect(app)
class LoginForm(FlaskForm):
username = StringField('Username', validators=[
DataRequired(),
Length(min=3, max=80)
])
password = PasswordField('Password', validators=[
DataRequired(),
Length(min=6)
])
submit = SubmitField('Login')
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# 处理登录
flash('Login successful!', 'success')
return redirect(url_for('index'))
return render_template('login.html', form=form)
Flask-Mail(邮件发送)
pip install flask-mail
from flask import Flask
from flask_mail import Mail, Message
app = Flask(__name__)
app.config.update(
MAIL_SERVER='smtp.example.com',
MAIL_PORT=587,
MAIL_USE_TLS=True,
MAIL_USERNAME='your-email@example.com',
MAIL_PASSWORD='your-password',
MAIL_DEFAULT_SENDER='noreply@example.com'
)
mail = Mail(app)
def send_email(to, subject, body, html=None):
msg = Message(subject, recipients=[to])
msg.body = body
if html:
msg.html = html
mail.send(msg)
# 异步发送
from threading import Thread
def send_async_email(app, msg):
with app.app_context():
mail.send(msg)
def send_email_async(to, subject, body):
msg = Message(subject, recipients=[to])
msg.body = body
Thread(target=send_async_email, args=(app, msg)).start()
# 使用模板
def send_welcome_email(user):
msg = Message('Welcome!', recipients=[user.email])
msg.html = render_template('email/welcome.html', user=user)
mail.send(msg)
第三章:缓存与性能
Flask-Caching
pip install flask-caching
from flask import Flask
from flask_caching import Cache
app = Flask(__name__)
# 简单缓存
app.config['CACHE_TYPE'] = 'simple'
# Redis 缓存
app.config['CACHE_TYPE'] = 'redis'
app.config['CACHE_REDIS_URL'] = 'redis://localhost:6379/0'
# Memcached 缓存
# app.config['CACHE_TYPE'] = 'memcached'
# app.config['CACHE_MEMCACHED_SERVERS'] = ['localhost:11211']
cache = Cache(app)
# 视图缓存
@app.route('/data')
@cache.cached(timeout=300) # 缓存 5 分钟
def get_data():
return expensive_operation()
# 带参数缓存
@app.route('/user/<int:user_id>')
@cache.cached(timeout=300, key_prefix='user')
def get_user(user_id):
return User.query.get(user_id).to_dict()
# 函数缓存
@cache.memoize(timeout=300)
def get_user_posts(user_id):
return Post.query.filter_by(user_id=user_id).all()
# 手动缓存
@app.route('/manual')
def manual_cache():
key = 'my_data'
data = cache.get(key)
if data is None:
data = expensive_operation()
cache.set(key, data, timeout=300)
return data
# 清除缓存
@app.route('/clear')
def clear_cache():
cache.delete('my_data')
cache.delete_memoized(get_user_posts)
return 'Cache cleared'
Flask-Limiter(速率限制)
pip install flask-limiter
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
app = Flask(__name__)
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"],
storage_uri="redis://localhost:6379"
)
# 默认限制
@app.route('/api/data')
def get_data():
return {'data': 'value'}
# 自定义限制
@app.route('/api/expensive')
@limiter.limit("10 per minute")
def expensive_operation():
return {'result': 'done'}
# 豁免限制
@app.route('/health')
@limiter.exempt
def health():
return 'OK'
# 动态限制
def get_limit_by_user():
if current_user.is_premium:
return "1000 per hour"
return "100 per hour"
@app.route('/api/resource')
@limiter.limit(get_limit_by_user)
def resource():
return {'data': 'value'}
# 共享限制
shared_limit = limiter.shared_limit("100 per hour", scope="api")
@app.route('/api/endpoint1')
@shared_limit
def endpoint1():
return {'endpoint': 1}
@app.route('/api/endpoint2')
@shared_limit
def endpoint2():
return {'endpoint': 2}
第四章:异步任务
Celery 集成
pip install celery redis
# celery_config.py
from celery import Celery
def make_celery(app):
celery = Celery(
app.import_name,
backend=app.config['CELERY_RESULT_BACKEND'],
broker=app.config['CELERY_BROKER_URL']
)
celery.conf.update(app.config)
class ContextTask(celery.Task):
def __call__(self, *args, **kwargs):
with app.app_context():
return self.run(*args, **kwargs)
celery.Task = ContextTask
return celery
# app.py
from flask import Flask
from celery_config import make_celery
app = Flask(__name__)
app.config.update(
CELERY_BROKER_URL='redis://localhost:6379/0',
CELERY_RESULT_BACKEND='redis://localhost:6379/0'
)
celery = make_celery(app)
# tasks.py
@celery.task
def send_email_task(to, subject, body):
"""异步发送邮件"""
msg = Message(subject, recipients=[to])
msg.body = body
mail.send(msg)
@celery.task(bind=True, max_retries=3)
def process_data(self, data):
"""带重试的任务"""
try:
result = expensive_operation(data)
return result
except Exception as exc:
self.retry(exc=exc, countdown=60)
# 使用
@app.route('/send-email', methods=['POST'])
def send_email():
send_email_task.delay(
request.json['to'],
request.json['subject'],
request.json['body']
)
return {'message': 'Email queued'}
# 启动 worker
# celery -A app.celery worker --loglevel=info
Flask-RQ(Redis Queue)
pip install flask-rq2
from flask import Flask
from flask_rq2 import RQ
app = Flask(__name__)
app.config['RQ_REDIS_URL'] = 'redis://localhost:6379/0'
rq = RQ(app)
@rq.job
def send_email_job(to, subject, body):
"""后台发送邮件"""
# 发送邮件逻辑
pass
@app.route('/send', methods=['POST'])
def send():
send_email_job.queue(
request.json['to'],
request.json['subject'],
request.json['body']
)
return {'status': 'queued'}
# 启动 worker
# rq worker
第五章:API 开发扩展
Flask-RESTX
pip install flask-restx
from flask import Flask
from flask_restx import Api, Resource, Namespace, fields
app = Flask(__name__)
api = Api(app, version='1.0', title='My API',
description='A sample API', doc='/docs')
# 命名空间
ns = api.namespace('users', description='User operations')
# 模型
user_model = ns.model('User', {
'id': fields.Integer(readonly=True),
'username': fields.String(required=True),
'email': fields.String(required=True)
})
@ns.route('/')
class UserList(Resource):
@ns.marshal_list_with(user_model)
def get(self):
"""获取所有用户"""
return User.query.all()
@ns.expect(user_model)
@ns.marshal_with(user_model, code=201)
def post(self):
"""创建用户"""
user = User(**api.payload)
db.session.add(user)
db.session.commit()
return user, 201
Flask-Marshmallow
pip install flask-marshmallow marshmallow-sqlalchemy
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
app = Flask(__name__)
db = SQLAlchemy(app)
ma = Marshmallow(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80))
email = db.Column(db.String(120))
class UserSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = User
load_instance = True
user_schema = UserSchema()
users_schema = UserSchema(many=True)
@app.route('/users')
def get_users():
users = User.query.all()
return users_schema.dump(users)
@app.route('/users/<int:id>')
def get_user(id):
user = User.query.get_or_404(id)
return user_schema.dump(user)
Flask-JWT-Extended
pip install flask-jwt-extended
from flask import Flask, jsonify, request
from flask_jwt_extended import JWTManager, create_access_token
from flask_jwt_extended import jwt_required, get_jwt_identity
from datetime import timedelta
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'your-secret-key'
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1)
jwt = JWTManager(app)
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username')
password = request.json.get('password')
user = User.query.filter_by(username=username).first()
if not user or not user.check_password(password):
return jsonify({'error': 'Invalid credentials'}), 401
access_token = create_access_token(identity=user.id)
return jsonify({'access_token': access_token})
@app.route('/protected')
@jwt_required()
def protected():
user_id = get_jwt_identity()
return jsonify({'user_id': user_id})
第六章:文件处理
Flask-Uploads
pip install flask-uploads
from flask import Flask
from flask_uploads import UploadSet, configure_uploads, IMAGES
app = Flask(__name__)
app.config['UPLOADED_PHOTOS_DEST'] = 'uploads/photos'
photos = UploadSet('photos', IMAGES)
configure_uploads(app, photos)
@app.route('/upload', methods=['POST'])
def upload():
if 'photo' in request.files:
filename = photos.save(request.files['photo'])
return {'filename': filename, 'url': photos.url(filename)}
return {'error': 'No file'}, 400
Flask-Dropzone
pip install flask-dropzone
from flask import Flask, render_template
from flask_dropzone import Dropzone
app = Flask(__name__)
app.config['DROPZONE_MAX_FILE_SIZE'] = 10 # MB
app.config['DROPZONE_ALLOWED_FILE_CUSTOM'] = True
app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image/*'
dropzone = Dropzone(app)
@app.route('/')
def index():
return render_template('upload.html')
@app.route('/upload', methods=['POST'])
def upload():
for key, f in request.files.items():
if key.startswith('file'):
f.save(os.path.join('uploads', f.filename))
return '', 204
第七章:管理后台
Flask-Admin
pip install flask-admin
from flask import Flask
from flask_admin import Admin, AdminIndexView
from flask_admin.contrib.sqla import ModelView
from flask_login import current_user
app = Flask(__name__)
# 自定义首页
class MyAdminIndexView(AdminIndexView):
def is_accessible(self):
return current_user.is_authenticated and current_user.is_admin
# 自定义模型视图
class UserAdmin(ModelView):
column_list = ['id', 'username', 'email', 'created_at']
column_searchable_list = ['username', 'email']
column_filters = ['is_active', 'created_at']
column_editable_list = ['is_active']
form_excluded_columns = ['password_hash', 'created_at']
def is_accessible(self):
return current_user.is_authenticated and current_user.is_admin
class PostAdmin(ModelView):
column_list = ['id', 'title', 'author', 'created_at', 'is_published']
column_searchable_list = ['title', 'content']
column_filters = ['is_published', 'author', 'created_at']
# 自定义表单
form_columns = ['title', 'content', 'category', 'tags', 'is_published']
# 行内编辑
column_editable_list = ['is_published']
# 批量操作
def on_model_change(self, form, model, is_created):
if is_created:
model.author = current_user
admin = Admin(
app,
name='My Admin',
template_mode='bootstrap4',
index_view=MyAdminIndexView()
)
admin.add_view(UserAdmin(User, db.session))
admin.add_view(PostAdmin(Post, db.session))
Flask-AppBuilder
pip install flask-appbuilder
from flask import Flask
from flask_appbuilder import AppBuilder, SQLA
from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_appbuilder import ModelView
app = Flask(__name__)
app.config.from_object('config')
db = SQLA(app)
appbuilder = AppBuilder(app, db.session)
class UserView(ModelView):
datamodel = SQLAInterface(User)
list_columns = ['username', 'email', 'created_at']
search_columns = ['username', 'email']
add_columns = ['username', 'email', 'password']
edit_columns = ['username', 'email']
show_columns = ['username', 'email', 'created_at', 'is_active']
appbuilder.add_view(
UserView,
"Users",
icon="fa-user",
category="Management"
)
第八章:其他实用扩展
Flask-DebugToolbar
pip install flask-debugtoolbar
from flask import Flask
from flask_debugtoolbar import DebugToolbarExtension
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'development-secret-key'
app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False
toolbar = DebugToolbarExtension(app)
Flask-CORS
pip install flask-cors
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
# 全局 CORS
CORS(app)
# 指定来源
CORS(app, resources={
r"/api/*": {
"origins": ["http://localhost:3000"],
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_headers": ["Content-Type", "Authorization"]
}
})
Flask-Babel(国际化)
pip install flask-babel
from flask import Flask, request
from flask_babel import Babel, gettext as _
app = Flask(__name__)
app.config['BABEL_DEFAULT_LOCALE'] = 'zh_CN'
app.config['BABEL_SUPPORTED_LOCALES'] = ['zh_CN', 'en_US']
babel = Babel(app)
@babel.localeselector
def get_locale():
return request.accept_languages.best_match(
app.config['BABEL_SUPPORTED_LOCALES']
)
@app.route('/')
def index():
return _('Hello, World!')
# 提取翻译字符串
# pybabel extract -F babel.cfg -o messages.pot .
# 初始化翻译文件
# pybabel init -i messages.pot -d translations -l zh_CN
# 编译翻译
# pybabel compile -d translations
Flask-SocketIO(WebSocket)
pip install flask-socketio
from flask import Flask, render_template
from flask_socketio import SocketIO, emit, join_room, leave_room
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, cors_allowed_origins="*")
@socketio.on('connect')
def handle_connect():
print('Client connected')
@socketio.on('disconnect')
def handle_disconnect():
print('Client disconnected')
@socketio.on('message')
def handle_message(data):
emit('response', {'data': f'Received: {data}'}, broadcast=True)
@socketio.on('join')
def on_join(data):
room = data['room']
join_room(room)
emit('status', {'msg': f'Joined room {room}'}, room=room)
@socketio.on('leave')
def on_leave(data):
room = data['room']
leave_room(room)
emit('status', {'msg': f'Left room {room}'}, room=room)
if __name__ == '__main__':
socketio.run(app, debug=True)
Flask-Session(服务端会话)
pip install flask-session
from flask import Flask, session
from flask_session import Session
app = Flask(__name__)
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url('redis://localhost:6379')
app.config['SESSION_PERMANENT'] = True
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
Session(app)
@app.route('/set')
def set_session():
session['user_data'] = {'id': 1, 'name': 'Alice'}
return 'Session set'
@app.route('/get')
def get_session():
return session.get('user_data', {})
第九章:自定义扩展开发
扩展结构
# flask_myext/__init__.py
from flask import current_app
class MyExtension:
def __init__(self, app=None):
self.app = app
if app is not None:
self.init_app(app)
def init_app(self, app):
# 设置默认配置
app.config.setdefault('MYEXT_SETTING', 'default_value')
# 保存扩展实例
if not hasattr(app, 'extensions'):
app.extensions = {}
app.extensions['myext'] = self
# 注册钩子
app.before_request(self.before_request)
app.teardown_appcontext(self.teardown)
def before_request(self):
pass
def teardown(self, exception):
pass
def do_something(self):
"""扩展功能"""
return current_app.config['MYEXT_SETTING']
使用扩展
from flask import Flask
from flask_myext import MyExtension
app = Flask(__name__)
myext = MyExtension(app)
# 或使用工厂模式
myext = MyExtension()
def create_app():
app = Flask(__name__)
myext.init_app(app)
return app
完整扩展示例
# flask_audit/__init__.py
from flask import g, request, current_app
from datetime import datetime
import json
class AuditLog:
def __init__(self, app=None):
self.app = app
self._storage = None
if app is not None:
self.init_app(app)
def init_app(self, app):
app.config.setdefault('AUDIT_ENABLED', True)
app.config.setdefault('AUDIT_LOG_FILE', 'audit.log')
if not hasattr(app, 'extensions'):
app.extensions = {}
app.extensions['audit'] = self
if app.config['AUDIT_ENABLED']:
app.before_request(self._before_request)
app.after_request(self._after_request)
def _before_request(self):
g.audit_start_time = datetime.utcnow()
def _after_request(self, response):
if hasattr(g, 'audit_start_time'):
log_entry = {
'timestamp': datetime.utcnow().isoformat(),
'method': request.method,
'path': request.path,
'status_code': response.status_code,
'duration': (datetime.utcnow() - g.audit_start_time).total_seconds(),
'ip': request.remote_addr,
'user_agent': str(request.user_agent),
'user_id': getattr(g, 'user_id', None)
}
self._write_log(log_entry)
return response
def _write_log(self, entry):
log_file = current_app.config['AUDIT_LOG_FILE']
with open(log_file, 'a') as f:
f.write(json.dumps(entry) + '\n')
def log_action(self, action, details=None):
"""手动记录操作"""
log_entry = {
'timestamp': datetime.utcnow().isoformat(),
'action': action,
'details': details,
'user_id': getattr(g, 'user_id', None),
'ip': request.remote_addr
}
self._write_log(log_entry)
第十章:扩展最佳实践
扩展选择指南
| 功能 | 推荐扩展 | 替代方案 |
|---|---|---|
| ORM | Flask-SQLAlchemy | SQLAlchemy |
| 迁移 | Flask-Migrate | Alembic |
| 认证 | Flask-Login | Flask-Security |
| 表单 | Flask-WTF | WTForms |
| REST API | Flask-RESTX | Flask-RESTful |
| 序列化 | Flask-Marshmallow | Marshmallow |
| JWT | Flask-JWT-Extended | PyJWT |
| 缓存 | Flask-Caching | Redis |
| 任务队列 | Celery | RQ |
| 邮件 | Flask-Mail | smtplib |
| 管理后台 | Flask-Admin | Flask-AppBuilder |
扩展加载顺序
def create_app(config_name='default'):
app = Flask(__name__)
app.config.from_object(config[config_name])
# 1. 首先初始化数据库
db.init_app(app)
# 2. 初始化迁移
migrate.init_app(app, db)
# 3. 初始化登录管理器
login_manager.init_app(app)
# 4. 初始化其他扩展
mail.init_app(app)
cache.init_app(app)
csrf.init_app(app)
# 5. 注册蓝图
register_blueprints(app)
# 6. 注册错误处理
register_error_handlers(app)
return app
扩展配置集中管理
# config.py
class Config:
# Flask
SECRET_KEY = os.environ.get('SECRET_KEY')
# SQLAlchemy
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
SQLALCHEMY_TRACK_MODIFICATIONS = False
# Flask-Mail
MAIL_SERVER = os.environ.get('MAIL_SERVER')
MAIL_PORT = int(os.environ.get('MAIL_PORT', 587))
MAIL_USE_TLS = True
# Flask-Caching
CACHE_TYPE = 'redis'
CACHE_REDIS_URL = os.environ.get('REDIS_URL')
# Celery
CELERY_BROKER_URL = os.environ.get('REDIS_URL')
CELERY_RESULT_BACKEND = os.environ.get('REDIS_URL')
总结
本章详细介绍了 Flask 扩展生态:
- 扩展机制:初始化模式、命名规范
- 核心扩展:SQLAlchemy、Migrate、Login、WTF、Mail
- 缓存与性能:Caching、Limiter
- 异步任务:Celery、RQ
- API 开发:RESTX、Marshmallow、JWT-Extended
- 文件处理:Uploads、Dropzone
- 管理后台:Admin、AppBuilder
- 其他扩展:CORS、Babel、SocketIO、Session
- 自定义扩展:开发模式与示例
- 最佳实践:选择指南、加载顺序
下一章我们将学习测试与调试。