文章

Flask使用记录

Flask使用及踩坑

Flask使用记录

Flask是一个轻量级的Web应用框架,基于Werkzeug和Jinja2。 以下参考自官方Quick Start与菜鸟教程

参考链接

推荐使用官方文档入门: 官方文档

中文文档

菜鸟教程

第三方教程文档-HelloFlask

安装

1
pip install Flask

基础项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/home/user/Projects/flask-tutorial
├── flaskr/ # 用于存放应用代码和文件
│   ├── __init__.py
│   ├── db.py
│   ├── schema.sql
│   ├── auth.py
│   ├── blog.py
│   ├── templates/ # 用于存放模板文件,支持Jinja2模板引擎
│   │   ├── base.html
│   │   ├── auth/
│   │   │   ├── login.html
│   │   │   └── register.html
│   │   └── blog/
│   │       ├── create.html
│   │       ├── index.html
│   │       └── update.html
│   └── static/ # 用于存放静态文件
│       └── style.css
├── tests/ # 一个包含测试模块的目录
│   ├── conftest.py
│   ├── data.sql
│   ├── test_factory.py
│   ├── test_db.py
│   ├── test_auth.py
│   └── test_blog.py
├── .venv/ # python自带的虚拟环境, 详情见[venv](https://docs.python.org/zh-cn/3/library/venv.html)
├── pyproject.toml
└── MANIFEST.in

基础类/函数概览

详细API见官方文档

  • Flask: 核心对象,实现WSGI应用,作为中心对象,用于注册视图函数、URL规则、模板配置等。

  • request: 对象,用于处理请求数据,包含请求头、查询字符串、表单数据、cookies、文件数据等。

  • session: 对象,用于存储用户会话信息,基于签名cookie实现,支持字典操作。

  • render_template: 函数,用于渲染模板,返回模板文件的内容,支持Jinja2模板引擎。

  • url_for: 函数,用于构建URL,接受函数名作为第一个参数,返回URL。

  • redirect: 函数,用于重定向,接受URL作为参数,返回重定向响应。

  • make_response: 函数,用于显式地构建响应,接受响应内容、状态码、响应头作为参数,返回响应对象Response。

  • redirect: 函数,用于重定向,接受URL作为参数,返回重定向响应,常配合url_for使用。

  • escape: 函数,用于转义HTML字符,返回转义后的字符串。

Flask的最小实例

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask # 导入Flask类,用于创建app

# The flask object implements a WSGI application and acts as the central object.
# It is passed the name of the module or package of the application.
# Once it is created it will act as a central registry for the view functions, the URL rules, template configuration and much more.
app = Flask(__name__) # 创建一个应用,核心对象

@app.route('/') # 路由装饰器,用于注册视图函数和URL规则
def hello_world():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run() # 运行应用,可选,或者通过flask run命令运行,详见后文"运行应用"

运行应用

1
2
3
flask --app hello_world run # 运行应用hello_world.py, 默认端口5000
flask run --host=0.0.0.0 --port=5001 # 指定主机和端口
flask run --debug # 调试模式下运行

路由

使用route()装饰器将函数绑定到URL,当访问URL时,执行绑定的函数;支持动态URL,支持多个路由绑定到同一个函数。

1. 基本路由

1
2
3
4
5
6
7
8
@app.route('/') # 将index()函数绑定到根URL
def index():
    return 'Index Page'

@app.route('/hello') # 将hello()函数绑定到/hello URL
def hello():
    return 'Hello, World!'

2. 动态路由

1
2
3
4
5
6
7
@app.route('/user/<username>') # 将user()函数绑定到/user/<username> URL
def user(username): # <username>是动态部分,作为参数传递给user()函数
    return f'User {username}'

@app.route('/post/<int:post_id>') # 将post()函数绑定到/post/<post_id> URL,限制post_id为整数
def post(post_id): # <post_id>是动态部分,作为参数传递给post()函数
    return f'Post {post_id}'

支持的转换器类型:

  • string: 接受任何不包含斜杠的文本(默认)
  • int: 接受正整数
  • float: 接受正浮点数
  • path: 类似string,但可以包含斜杠
  • uuid: 接受UUID字符串

3. 多URL绑定

1
2
3
4
@app.route('/name/') # 将name()函数绑定到/name/ URL
@app.route('/name/<name>') # 将name()函数绑定到/name/<name> URL
def name(name=None): # name是可选参数,如果不传递参数,默认为空字符串
    return f'Name {name}'

4. HTTP方法

route()装饰器接受methods参数,限制HTTP方法,支持GET、POST、PUT、DELETE等。

1
2
3
4
5
6
@app.route('/login', methods=['GET', 'POST']) # 将login()函数绑定到/login URL,限制GET和POST方法
def login():
    if request.method == 'POST':
        return 'Login POST'
    else:
        return 'Login GET'
1
2
3
4
5
6
7
8
@app.route('/login', methods=['POST']) # 将login()函数绑定到/login URL,限制POST方法
def login():
    return 'Login POST'

@app.post('/login') # 限制POST方法,等价于@app.route('/login', methods=['POST'])
def login():
    return 'Login POST'

URL构建

使用url_for()函数构建URL,接受函数名作为第一个参数,返回URL。 从视图函数URL反向映射,可以避免硬编码URL,提高代码的可维护性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import url_for,Flask
app = Flask(__name__) # 创建一个应用,核心对象,之后的示例中会省略

@app.route('/login') # 将login()函数绑定到/login URL
def login():
    return 'Login'

@app.route('/user/<username>') # 将user()函数绑定到/user/<username> URL
def user(username):
    return f'User {username}'

if __name__ == '__main__':
    with app.test_request_context(): # 创建一个请求上下文,用于测试
        print(url_for('login')) # 输出: /login
        print(url_for('login', next='/')) # 输出: /login?next=/ ,next是查询参数
        print(url_for('user', username='John')) # 输出: /user/John

静态文件

使用url_for()函数构建静态文件URL,接受特殊的static参数,返回静态文件URL。

1
2
3
from flask import url_for

url_for('static', filename='style.css') # 输出: /static/style.css

注意,Flask默认将static文件夹作为静态文件目录,可以通过static_folder参数指定静态文件目录。

1
app = Flask(__name__, static_folder='static') # 指定static文件夹为静态文件目录

模板渲染

Flask支持Jinja2模板引擎,使用render_template()函数渲染模板,返回模板文件的内容。

详细请参考官方文档

1
2
3
4
5
from flask import render_template

@app.route('/hello/<name>') # 将hello()函数绑定到/hello/<name> URL
def hello(name=None): # name是可选参数,如果不传递参数,默认为空字符串
    return render_template('hello.html', name=name) # 渲染hello.html模板,传递name参数
1
2
3
4
5
6
# hello.html模板:
<!doctype html>
<title>Hello from Flask</title>

  <h1>Hello, World!</h1>

注意,Flask默认将templates文件夹作为模板文件目录,可以通过template_folder参数指定模板文件目录。

1
app = Flask(__name__, template_folder='templates') # 指定templates文件夹为模板文件目录

使用请求数据

使用Request

使用request对象处理请求数据,包含请求头、查询字符串、表单数据、cookies、文件数据等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import request # 导入request对象

@app.route('/login', methods=['GET', 'POST']) # 将login()函数绑定到/login URL,限制GET和POST方法
def login():
    request_path = request.path # 请求路径,/login
    request_method = request.method # 请求方法,GET或POST
    request_form_dict = request.form # 表单数据,字典形式
    request_args_dict = request.args # 查询字符串,字典形式, 用于处理URL中的查询参数(?key=value)

    request_files_dict = request.files # 文件数据,字典形式,用于处理文件上传
    file = request.files['file'] # 获取文件对象
    file.save('file.txt') # 保存文件

    request_cookies = request.cookies # cookies数据

    return f'Path: {request_path}, Method: {request_method}, Form: {request_form_dict}, Args: {request_args_dict}, Files: {request_files_dict}, Cookies: {request_cookies_dict}'

关于主要request成员的说明:

files: 文件数据,字典形式,用于处理文件上传。

form: 表单数据,字典形式,用于处理表单提交。

args: 查询字符串,字典形式,用于处理URL中的查询参数(?key=value)。

cookies: cookies数据,用于处理cookies。

使用Session

使用session对象处理用户会话信息,允许存储从一个请求到下一个请求的用户特定信息。这是在 cookie 之上实现的,并对 cookie 进行加密签名。这意味着用户可以查看 cookie 的内容,但不能修改它,除非他们知道用于签名的密钥。基于签名cookie实现,支持字典操作

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from flask import session
import secrets

secret_key = secrets.token_urlsafe(16) # 生成一个16字节的随机URL安全令牌
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' # 设置密钥,用于加密签名cookie

# 以下实现了一个简单的登录和注销功能
@app.route('/')
def index():
    if 'username' in session: # 判断是否存在username键
        return f'Logged in as {session["username"]}' # 获取username键的值
    return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    session.pop('username', None) # 删除username键
    return redirect(url_for('index'))

重定向与错误处理

使用redirect函数重定向,使用abort函数或errorhandler装饰器处理错误。

重定向示例:

1
2
3
4
5
6
7
8
9
10
from flask import abort, redirect, url_for

@app.route('/')
def index():
    return redirect(url_for('login')) # 重定向到login()函数

@app.route('/login')
def login():
    abort(401) # 重定向到401错误页面
    this_is_never_executed()

默认情况下,每个错误代码都会显示一个黑白错误页面。如果想自定义错误页面,可以使用 errorhandler() 装饰器:

1
2
3
4
5
from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404 # 在 render_template() 调用后的 404 。这告诉 Flask,该页面的状态码应该是 404,意味着未找到。默认情况下假设为 200(一切顺利)

关于错误处理,详见官方文档

关于响应

在Flask中,响应即是函数的返回值,可以是字符串、元组、Response对象、字典等。如果返回不是Response对象,Flask会将其隐式地转换为Response对象。

Flask默认的MIME类型是text/html,状态码是200。如果

需要显式地构建Response对象时,可以使用make_response函数。

具体而言,遵循以下规则:

  • 如果返回值是一个Response对象,Flask会将其直接返回给客户端。

  • 如果返回值是一个字符串,Flask会将其作为一个包含该字符串的响应对象返回给客户端。其中包含相应体(即该字符串)、默认的状态码200、默认的MIME类型text/html。

  • 如果返回值是一个元组,且元组的形式为(response, status, headers),Flask会将其作为一个响应对象返回给客户端。其中包含相应体(response)、状态码(status)、响应头(headers)。也可采用(response, status)或(response, headers)的形式。其中headers是一个字典或列表,用于设置响应头。

  • 如果返回值是一个字典或列表,Flask会将会使用jsonify函数将其进行JSON序列化,并将其作为一个包含JSON数据的响应对象返回给客户端。其中包含相应体(JSON数据)、默认的状态码200、默认的MIME类型application/json。请注意,提供的返回值必须是可被JSON序列化的。

  • 如果返回的是一个可迭代对象,则Flask视其为流式响应

  • 对于其余复杂类型,如数据库模型,则需要序列化为字典或列表,然后返回,请参考这里

  • 如果以上方法都不奏效,Flask 将假定返回值是有效的 WSGI 应用程序,并将其转换为响应对象。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import make_response

# 显式构建Response对象
@app.errorhandler(404)
def not_found(error):
    resp = make_response(render_template('error.html'), 404) # 显式构建Response对象
    resp.headers['X-Something'] = 'A value'
    return resp

# Json
@app.route('/json')
def json():
    return {'hello': 'world'} # 返回字典,Flask会将其转换为JSON格式

注意,Flask默认的MIME类型是text/html,因此相依返回的值需要进行转义(使用escape()函数),以避免注入攻击:

1
2
3
4
5
from flask import escape

@app.route('/<name>')
def hello(name):
    return f'Hello, {escape(name)}!' # 使用escape()函数转义

对于Jinja2模板,Flask会自动转义,因此不需要手动转义。

应用Flask

优秀的应用程序和用户界面都关乎反馈。如果用户得不到足够的反馈,他们可能会对应用程序产生厌恶。Flask 提供了一个非常简单的方式来使用闪烁系统向用户提供反馈。闪烁系统基本上使得在请求结束时记录一条消息并在下一个(以及仅下一个)请求中访问它成为可能。这通常与布局模板结合使用,以显示消息。

为了闪烁消息,可以使用flash函数。要获取闪烁的消息,可以使用get_flashed_messages函数。

详见官方文档

Logging/日志记录

Flask 使用 Python 的标准 logging 模块来记录信息。默认情况下,Flask 不会记录任何信息。要启用日志记录,可以使用app.logger属性。

1
2
3
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

详见官方文档

蓝图(Blueprint)

Flask 的蓝图(Blueprints)是一种组织代码的机制,允许你将 Flask 应用分解成多个模块。这样可以更好地组织应用逻辑,使得应用更具可维护性和可扩展性。类似于fastapi的APIRouter。

每个蓝图可以有自己的路由、视图函数、模板和静态文件,这样可以将相关的功能分组。 通过使用蓝图,你可以将 Flask 应用拆分成多个模块,每个模块处理相关的功能,使得代码更加清晰和易于管理。

详见官方文档

添加中间件(WSGI Middleware)

使用Hooks

Flask提供了几种钩子用于在不同的请求生命周期的不同阶段:

  • before_request:在每个请求处理之前执行。
  • after_request:在每个请求处理之后执行。
  • teardown_request:请求处理结束后,无论是否发生异常都会执行。
  • before_first_request:仅在应用第一次处理请求之前执行。
1
2
3
4
5
from flask import request

@app.before_request
def before_request():
    app.logger.debug('Before request: %s', request.path)

自定义中间件

将 WSGI 中间件添加到您的 Flask 应用程序中,包装应用程序的 wsgi_app 属性。例如,要应用 Werkzeug 的 ProxyFix 中间件以在 Nginx 后面运行:

1
2
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app) # 使用ProxyFix中间件修饰app.wsgi_app,以在Nginx后面运行

关于werkzeug,其是一个WSGI工具库,Flask是基于werkzeug开发的。

添加扩展

Flask 本身是一个轻量级的框架,但是可以通过扩展来增加功能。Flask 的扩展是 Python 包,可以添加应用程序的功能。Flask 本身没有数据库抽象层、表单验证或其它任何已有功能,但是 Flask 的扩展可以添加这些功能。扩展可以为第三方库,也可以自定义。

例如,Flask-SQLAlchemy是一个用于 Flask 的 SQLAlchemy 扩展,可以添加数据库支持。

详见官方文档

部署

详见官方文档

关于 SQLAlchemy 和 FastAPI

TODO: 将该部分分离到单独的文章中。

SQLAlchemy是一个基于Python的ORM框架,支持多种数据库,包括SQLite、MySQL、PostgreSQL、Oracle、Microsoft SQL Server等。Flask-SQLAlchemy是Flask的SQLAlchemy扩展,用于支持Flask应用程序。

中文文档SQLAlchemy:机器翻译,仅供参考。

FastAPI是一个现代的Python web框架,基于Starlette和Pydantic,支持异步请求处理,自动文档生成,数据验证等功能。

中文文档FastAPI:官方中文文档。

本文由作者按照 CC BY 4.0 进行授权