Flask蓝graph and module化Development

overview

随着Flaskapplication continuously增 long , code会变得越来越 complex , 难以maintenance. 蓝graph (Blueprint) is Flaskproviding 一种module化Development方式, 它允许我们将application分解 for many 个可复用 component, 每个component可以 has 自己 routing, 模板, 静态file and errorprocessing. 本章节将介绍such as何usingFlask蓝graphformodule化Development.

1. what is 蓝graph?

蓝graph is Flaskapplication 一种component化mechanism, 它允许我们将application组织成不同 module, 每个module可以独立Development and test, 然 after 再集成 to 主applicationin. 蓝graph 主要特点including:

  • 可以定义自己 routing and 视graphfunction
  • 可以 has 自己 模板 and 静态file
  • 可以定义自己 errorprocessingfunction
  • 可以register to application 不同URL before 缀 or 子域名
  • supportmodule化Development and code复用

2. creation and using蓝graph

using蓝graph basic步骤including: creation蓝graphobject, 定义routing and 视graphfunction, register蓝graph to application.

2.1 creation蓝graph

首先, 我们需要creation一个蓝graphobject:

from flask import Blueprint

# creation蓝graphobject
# 第一个parameter is 蓝graph 名称, 第二个parameter is 蓝graphwhere module or package
main = Blueprint('main', __name__)

2.2 定义routing and 视graphfunction

然 after , 我们可以using蓝graphobject来定义routing and 视graphfunction:

@main.route('/')
def index():
    return 'Hello, Blueprint!'

2.3 register蓝graph

最 after , 我们需要将蓝graphregister to Flaskapplicationin:

from flask import Flask
from main import main as main_blueprint

app = Flask(__name__)

# register蓝graph
# 第一个parameter is 蓝graphobject, url_prefixparameter指定蓝graph URL before 缀
app.register_blueprint(main_blueprint, url_prefix='/main')

in on 面 例子in, 蓝graph routing/会被register to application /main/path under .

3. 蓝graph Table of Contentsstructure

for 于较 big application, 我们通常会将蓝graph组织成独立 package. 以 under is a 典型 蓝graphTable of Contentsstructure:

myapp/
├── app.py                  # 主applicationfile
└── blueprints/             # 蓝graphTable of Contents
    ├── __init__.py         # package初始化file
    ├── main/               # main蓝graph
    │   ├── __init__.py     # 蓝graph初始化file
    │   ├── routes.py       # routing and 视graphfunction
    │   ├── templates/      # 模板Table of Contents
    │   │   └── main/       # 模板子Table of Contents, 避免名称conflict
    │   │       └── index.html
    │   └── static/         # 静态fileTable of Contents
    │       └── main/       # 静态file子Table of Contents, 避免名称conflict
    │           └── style.css
    └── auth/               # auth蓝graph
        ├── __init__.py
        ├── routes.py
        ├── templates/
        │   └── auth/
        │       └── login.html
        └── static/
            └── auth/
                └── style.css

3.1 蓝graphpackage 初始化

in 蓝graphpackage __init__.pyfilein, 我们需要creation蓝graphobject:

# blueprints/main/__init__.py
from flask import Blueprint

main = Blueprint('main', __name__, 
                 template_folder='templates',
                 static_folder='static')

# importroutingmodule, 确保routing被register
from . import routes

3.2 定义routing

in routingmodulein, 我们可以using蓝graphobject来定义routing and 视graphfunction:

# blueprints/main/routes.py
from . import main

@main.route('/')
def index():
    return render_template('main/index.html')

@main.route('/about')
def about():
    return render_template('main/about.html')

3.3 register蓝graph to application

in 主applicationfilein, 我们需要import并register蓝graph:

# app.py
from flask import Flask
from blueprints.main import main as main_blueprint
from blueprints.auth import auth as auth_blueprint

app = Flask(__name__)

# register蓝graph
app.register_blueprint(main_blueprint, url_prefix='/main')
app.register_blueprint(auth_blueprint, url_prefix='/auth')

4. 蓝graph advancedfunctions

4.1 URL before 缀 and 子域名

我们可以 for 蓝graph指定URL before 缀 or 子域名, 以便更 good 地组织application routing:

URL before 缀

# register蓝graph时指定URL before 缀
app.register_blueprint(main_blueprint, url_prefix='/main')
app.register_blueprint(auth_blueprint, url_prefix='/auth')

这样, main蓝graph routing/index会被register to /main/index, auth蓝graph routing/login会被register to /auth/login.

子域名

我们还可以 for 蓝graph指定子域名:

# configurationserver名称
app.config['SERVER_NAME'] = 'example.com:5000'

# register蓝graph时指定子域名
app.register_blueprint(main_blueprint, subdomain='www')
app.register_blueprint(blog_blueprint, subdomain='blog')

这样, main蓝graph routing/会被register to www.example.com:5000/, blog蓝graph routing/会被register to blog.example.com:5000/.

4.2 模板 and 静态file

蓝graph可以 has 自己 模板 and 静态fileTable of Contents, 这样可以更 good 地组织application resource:

模板Table of Contents

in creation蓝graph时, 我们可以指定模板Table of Contents:

main = Blueprint('main', __name__, template_folder='templates')

in 蓝graph 视graphfunctionin, 我们可以using相 for path来引用模板:

@main.route('/')
def index():
    return render_template('main/index.html')

Flask会自动 in 蓝graph 模板Table of Contentsinfind模板file.

静态fileTable of Contents

in creation蓝graph时, 我们可以指定静态fileTable of Contents:

main = Blueprint('main', __name__, static_folder='static')

in 模板in, 我们可以usingurl_forfunction来引用蓝graph 静态file:

<link rel="stylesheet" href="{{ url_for('main.static', filename='style.css') }}">
<img src="{{ url_for('main.static', filename='image.jpg') }}" alt="Image">

Flask会自动 in 蓝graph 静态fileTable of Contentsinfind静态file.

4.3 errorprocessing

蓝graph可以定义自己 errorprocessingfunction, 这些function只会processing蓝graph in 部 error:

@main.errorhandler(404)
def main_not_found_error(error):
    return render_template('main/404.html'), 404

@auth.errorhandler(401)
def auth_unauthorized_error(error):
    return render_template('auth/401.html'), 401

4.4 request before processing and request after processing

蓝graph可以定义自己 request before processing and request after processingfunction, 这些function只会processing蓝graph in 部 request:

request before processing

@main.before_request
def main_before_request():
    #  in requestprocessing之 before 执行 code
    pass

request after processing

@main.after_request
def main_after_request(response):
    #  in requestprocessing之 after 执行 code
    return response

request结束processing

@main.teardown_request
def main_teardown_request(error=None):
    #  in request结束时执行 code, 无论request is 否成功
    pass

4.5 on under 文processing器

蓝graph可以定义自己 on under 文processing器, 这些processing器只会影响蓝graph in 部 模板:

@main.context_processor
def main_context_processor():
    return {
        'app_name': 'My App',
        'current_year': 2024
    }

5. 蓝graph practicalapplication

让我们through一个完整 example来演示such as何using蓝graphformodule化Development.

5.1 projectstructure

blog_app/
├── app.py                      # 主applicationfile
├── config.py                   # configurationfile
└── blueprints/                 # 蓝graphTable of Contents
    ├── __init__.py             # package初始化file
    ├── auth/                   # authentication蓝graph
    │   ├── __init__.py         # 蓝graph初始化file
    │   ├── forms.py            # 表单定义
    │   ├── models.py           # datamodel
    │   ├── routes.py           # routing and 视graphfunction
    │   └── templates/          # 模板Table of Contents
    │       └── auth/           # 模板子Table of Contents
    │           ├── login.html
    │           └── register.html
    └── blog/                   # 博客蓝graph
        ├── __init__.py         # 蓝graph初始化file
        ├── forms.py            # 表单定义
        ├── models.py           # datamodel
        ├── routes.py           # routing and 视graphfunction
        ├── templates/          # 模板Table of Contents
        │   └── blog/           # 模板子Table of Contents
        │       ├── index.html
        │       ├── post.html
        │       └── create_post.html
        └── static/             # 静态fileTable of Contents
            └── blog/           # 静态file子Table of Contents
                └── style.css

5.2 configurationfile

# config.py
class Config:
    SECRET_KEY = 'your-secret-key'
    SQLALCHEMY_DATABASE_URI = 'sqlite:///site.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

5.3 主applicationfile

# app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import Config

# creationdatalibraryinstance
db = SQLAlchemy()

def create_app(config_class=Config):
    # creationFlaskapplicationinstance
    app = Flask(__name__)
    # 加载configuration
    app.config.from_object(config_class)
    # 初始化datalibrary
    db.init_app(app)
    
    # import并register蓝graph
    from blueprints.auth import auth as auth_blueprint
    app.register_blueprint(auth_blueprint, url_prefix='/auth')
    
    from blueprints.blog import blog as blog_blueprint
    app.register_blueprint(blog_blueprint, url_prefix='/blog')
    
    # 主页routing
    @app.route('/')
    def home():
        return "欢迎来 to 博客application!"
    
    return app

# creationapplicationinstance
app = create_app()

if __name__ == '__main__':
    app.run(debug=True)

5.4 authentication蓝graph

蓝graph初始化file

# blueprints/auth/__init__.py
from flask import Blueprint

auth = Blueprint('auth', __name__, 
                 template_folder='templates',
                 static_folder='static')

from . import routes, models

datamodel

# blueprints/auth/models.py
from app import db
from datetime import datetime

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(60), nullable=False)
    posts = db.relationship('Post', backref='author', lazy=True)
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

    def __repr__(self):
        return f"<User {self.username}>"

表单定义

# blueprints/auth/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo

class RegistrationForm(FlaskForm):
    username = StringField('user名', validators=[DataRequired(), Length(min=2, max=20)])
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    password = PasswordField('password', validators=[DataRequired()])
    confirm_password = PasswordField('确认password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('register')

class LoginForm(FlaskForm):
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    password = PasswordField('password', validators=[DataRequired()])
    remember = BooleanField('记住我')
    submit = SubmitField('login')

routing and 视graphfunction

# blueprints/auth/routes.py
from flask import render_template, url_for, flash, redirect, request
from flask_login import login_user, current_user, logout_user, login_required
from app import db, bcrypt
from blueprints.auth import auth
from blueprints.auth.forms import RegistrationForm, LoginForm
from blueprints.auth.models import User

@auth.route('/register', methods=['GET', 'POST'])
def register():
    if current_user.is_authenticated:
        return redirect(url_for('blog.index'))
    form = RegistrationForm()
    if form.validate_on_submit():
        hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
        user = User(username=form.username.data, email=form.email.data, password=hashed_password)
        db.session.add(user)
        db.session.submitting()
        flash('register成功!', 'success')
        return redirect(url_for('auth.login'))
    return render_template('auth/register.html', title='register', form=form)

@auth.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('blog.index'))
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user and bcrypt.check_password_hash(user.password, form.password.data):
            login_user(user, remember=form.remember.data)
            next_page = request.args.get('next')
            return redirect(next_page) if next_page else redirect(url_for('blog.index'))
        else:
            flash('login失败, 请check邮箱 and password', 'danger')
    return render_template('auth/login.html', title='login', form=form)

@auth.route('/logout')
def logout():
    logout_user()
    return redirect(url_for('blog.index'))

5.5 博客蓝graph

蓝graph初始化file

# blueprints/blog/__init__.py
from flask import Blueprint

blog = Blueprint('blog', __name__, 
                 template_folder='templates',
                 static_folder='static')

from . import routes, models

datamodel

# blueprints/blog/models.py
from app import db
from datetime import datetime

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

    def __repr__(self):
        return f"<Post {self.title}>"

表单定义

# blueprints/blog/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired

class PostForm(FlaskForm):
    title = StringField('标题', validators=[DataRequired()])
    content = TextAreaField(' in 容', validators=[DataRequired()])
    submit = SubmitField('release')

routing and 视graphfunction

# blueprints/blog/routes.py
from flask import render_template, url_for, flash, redirect, request, abort
from flask_login import current_user, login_required
from app import db
from blueprints.blog import blog
from blueprints.blog.forms import PostForm
from blueprints.blog.models import Post

@blog.route('/')
def index():
    posts = Post.query.order_by(Post.created_at.desc()).all()
    return render_template('blog/index.html', posts=posts)

@blog.route('/post/')
def post(post_id):
    post = Post.query.get_or_404(post_id)
    return render_template('blog/post.html', title=post.title, post=post)

@blog.route('/post/new', methods=['GET', 'POST'])
@login_required
def new_post():
    form = PostForm()
    if form.validate_on_submit():
        post = Post(title=form.title.data, content=form.content.data, author=current_user)
        db.session.add(post)
        db.session.submitting()
        flash('文章release成功!', 'success')
        return redirect(url_for('blog.index'))
    return render_template('blog/create_post.html', title=' new 建文章', form=form, legend=' new 建文章')

@blog.route('/post//update', methods=['GET', 'POST'])
@login_required
def update_post(post_id):
    post = Post.query.get_or_404(post_id)
    if post.author != current_user:
        abort(403)
    form = PostForm()
    if form.validate_on_submit():
        post.title = form.title.data
        post.content = form.content.data
        db.session.submitting()
        flash('文章update成功!', 'success')
        return redirect(url_for('blog.post', post_id=post.id))
    elif request.method == 'GET':
        form.title.data = post.title
        form.content.data = post.content
    return render_template('blog/create_post.html', title='update文章', form=form, legend='update文章')

@blog.route('/post//delete', methods=['POST'])
@login_required
def delete_post(post_id):
    post = Post.query.get_or_404(post_id)
    if post.author != current_user:
        abort(403)
    db.session.delete(post)
    db.session.submitting()
    flash('文章delete成功!', 'success')
    return redirect(url_for('blog.index'))

6. 蓝graph best practices

  • 将相关functions组织 to 同一个蓝graphin
  • for 每个蓝graphcreation独立 模板 and 静态fileTable of Contents
  • usingURL before 缀 or 子域名来区分不同 蓝graph
  • in 蓝graph in 部定义自己 errorprocessing and requesthook
  • usingfactory patterncreationapplication并register蓝graph
  • for 蓝graphwriting独立 test用例
  • 遵循一致 命名规范, 便于maintenance and scale

7. 蓝graph and application工厂

application工厂 is acreationFlaskapplication design模式, 它允许我们 in run时根据不同 configurationcreation不同 applicationinstance. 蓝graph and application工厂结合using, 可以implementation更加flexible and 可scale applicationstructure.

7.1 application工厂 basicstructure

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import Loginmanagementr

# creationscaleinstance
db = SQLAlchemy()
bcrypt = Bcrypt()
login_manager = Loginmanagementr()
login_manager.login_view = 'auth.login'
login_manager.login_message_category = 'info'

def create_app(config_class=None):
    """application工厂function"""
    app = Flask(__name__)
    
    # 加载configuration
    if config_class is None:
        from config import Config
        app.config.from_object(Config)
    else:
        app.config.from_object(config_class)
    
    # 初始化scale
    db.init_app(app)
    bcrypt.init_app(app)
    login_manager.init_app(app)
    
    # register蓝graph
    from blueprints.auth import auth as auth_blueprint
    app.register_blueprint(auth_blueprint, url_prefix='/auth')
    
    from blueprints.blog import blog as blog_blueprint
    app.register_blueprint(blog_blueprint, url_prefix='/blog')
    
    # 主页routing
    @app.route('/')
    def home():
        return "欢迎来 to 博客application!"
    
    return app

7.2 usingapplication工厂

# app.py
from app_factory import create_app

# creationapplicationinstance
app = create_app()

if __name__ == '__main__':
    app.run(debug=True)

# testfileinusing
import pytest
from app_factory import create_app, db

@pytest.fixture
def app():
    app = create_app(TestingConfig)
    with app.app_context():
        db.create_all()
        yield app
        db.drop_all()

@pytest.fixture
def client(app):
    return app.test_client()

summarized

本章节介绍了Flask蓝graph and module化Development, including:

  • 蓝graph basicconcepts and 特点
  • creation and using蓝graph 步骤
  • 蓝graph Table of Contentsstructuredesign
  • 蓝graph advancedfunctions, such asURL before 缀, 子域名, 模板 and 静态filemanagement
  • 蓝graph errorprocessing, requesthook and on under 文processing器
  • 蓝graph in practicalprojectin application
  • 蓝graph and application工厂 结合using

using蓝graphformodule化Development可以improvingcode 可maintenance性 and 可scale性, 便于团队协作Development. through合理design蓝graphstructure, 可以将 big 型application分解 for many 个 small 型, 独立 component, 每个component可以独立Development, test and deployment.

codeexample

以 under is a simple 蓝graphexample, 演示了such as何creation and using蓝graph:

# app.py
from flask import Flask

app = Flask(__name__)

# import并register蓝graph
from blueprints.main import main as main_blueprint
from blueprints.auth import auth as auth_blueprint

app.register_blueprint(main_blueprint, url_prefix='/main')
app.register_blueprint(auth_blueprint, url_prefix='/auth')

@app.route('/')
def home():
    return "欢迎来 to 主application!"

if __name__ == '__main__':
    app.run(debug=True)

# blueprints/main/__init__.py
from flask import Blueprint

main = Blueprint('main', __name__)

from . import routes

# blueprints/main/routes.py
from . import main

@main.route('/')
def index():
    return "这 is main蓝graph 首页!"

@main.route('/about')
def about():
    return "这 is main蓝graph 关于页面!"

# blueprints/auth/__init__.py
from flask import Blueprint

auth = Blueprint('auth', __name__)

from . import routes

# blueprints/auth/routes.py
from . import auth

@auth.route('/login')
def login():
    return "这 is auth蓝graph login页面!"

@auth.route('/register')
def register():
    return "这 is auth蓝graph register页面!"

练习题

  1. creation一个Flaskapplication, using蓝graph将application分 for usermodule and 文章module
  2. for 每个蓝graph指定不同 URL before 缀
  3. for 每个蓝graphcreation独立 模板 and 静态fileTable of Contents
  4. in 蓝graphinimplementationrequest before processing and request after processingfunction
  5. usingapplicationfactory patterncreationapplication并register蓝graph
  6. implementation蓝graph errorprocessingfunction