Create a REST API with Flask (0 - 1)

学习 flask 笔记,包含官方教程内容及重构时遇到的问题

Create a REST API with Flask (Mac)

安装与环境配置

Create a project folder and a .venv folder within:

1
2
3
$ mkdir flask-demo
$ cd flask-demo
$ python3 -m venv .venv

Activate the corresponding environment

1
$ . .venv/bin/activate

Install Package

1
2
3
4
5
# 安装flask
$ pip install --trusted-host=pypi.org --trusted-host=files.pythonhosted.org flask
# 验证flask安装
$ pip freeze
Flask==3.1.0

示例 Demo

Create a new file app.py, which is same level with .venv

1
2
3
4
5
6
7
8
# app.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
return "<p>Hello world!</p>"

Run the application, and open http://127.0.0.1:5000/

1
2
# the second app is the name of your application
$ flask --app app run

Output requirement file

1
$ pip freeze > requirement.txt

If you have a exist requirement.txt, and you want to install all the package

1
$ pip install -r requirement.txt

使用内置 SQLite+Jinja+Flask+Python 写一个博客页面

项目结构:这里的项目比较小,只有内置数据库+html 实现,没有专门的前端

image-20250505190552707

步骤:详见官方 Flask 教程

其他需要学习的点:

了解不同项目规模,不同项目结构

了解 instance,config.py 使用

了解不同数据库连接与使用,了解 ORM 在 flask 中的使用

深刻理解 Blueprint

了解前后端分离在 flask 中的使用,比如不用 Jinja,而使用 React

思考优化代码,用户认证代码重构

部署 docker 及其他 devops,了解 flask 应用常规部署方式

了解 pytest

其他补充,详见:explore flask 中译

配置 Config

为了加载配置变量,我通常使用app.config.from_object()。如果是单一模块应用中,是在app.py;或者在yourapp/init.py,如果是基于包的应用。无论在哪种情况下,代码看上去像这样:

1
2
3
4
5
from flask import Flask

app = Flask(__name__)
app.config.from_object('config')
# 现在通过 app.config["VAR_NAME"],我们可以访问到对应的变量

instance 文件夹

有时你需要定义一些不能为人所知的配置变量。为此,你会想要把它们从config.py中的其他变量分离出来,并保持在版本控制之外。你可能要隐藏类似数据库密码和 API 密钥的秘密,或定义特定于当前机器的参数。为了让这更加轻松,Flask 提供了一个叫instance 文件夹的特性。instance 文件夹是根目录的一个子文件夹,包括了一个特定于当前应用实例的配置文件。我们不要把它提交到版本控制中。

这是一个使用了 instance 文件夹的简单 Flask 应用的结构:

1
2
3
4
5
6
7
8
9
10
11
config.py
requirements.txt
run.py
instance/
config.py
yourapp/
__init__.py
models.py
views.py
templates/
static/
使用 instance 文件夹

要想加载定义在 instance 文件夹中的配置变量,你可以使用app.config.from_pyfile()。如果在调用Flask()创建应用时设置了instance_relative_config=Trueapp.config.from_pyfile()将查看在instance文件夹的特殊文件。

1
2
3
app = Flask(__name__, instance_relative_config=True)
app.config.from_object('config')
app.config.from_pyfile('config.py')

现在,你可以在instance/config.py中定义变量,一如在config.py。你也应该将 instance 文件夹加入到版本控制系统的忽略名单中。比如假设你用的是 git,你需要在gitignore中新开一行,写下instance/

密钥

instance 文件夹的隐秘属性使得它成为藏匿密钥的好地方。你可以在放入应用的密钥或第三方的 API 密钥。假如你的应用是开源的,或者将会是开源的,这会很重要。我们希望其他人去使用他们自己申请的密钥。

1
2
3
4
5
6
# instance/config.py

SECRET_KEY = 'Sm9obiBTY2hyb20ga2lja3MgYXNz'
STRIPE_API_KEY = 'SmFjb2IgS2FwbGFuLU1vc3MgaXMgYSBoZXJv'
SQLALCHEMY_DATABASE_URI= \
"postgresql://user:TWljaGHFgiBCYXJ0b3N6a2lld2ljeiEh@localhost/databasename"

最小化依赖于环境的配置

如果你的生产环境和开发环境之间的差别非常小,你可以使用你的 instance 文件夹抹平配置上的差别。在instance/config.py中定义的变量可以覆盖在config.py中设定的值。你只需要在app.config.from_object()之后才调用app.config.from_pyfile()。这样做的其中一个优点是你可以在不同的机器中修改你的应用的配置。你的开发版本库可能看上去像这样:

config.py

1
2
DEBUG = False
SQLALCHEMY_ECHO = False

instance/config.py

1
2
DEBUG = True
SQLALCHEMY_ECHO = True

然后在生产环境中,你将这些代码从instance/config.py中移除,它就会改用回config.py中设定的变量。

依照环境变量来配置

instance 文件夹不应该在版本控制中。这意味这你将不能追踪你的 instance 配置。在只有一两个变量的情况下这不是什么问题,但如果你有关于多个环境(生产,稳定,开发,等等)的一大堆配置,你不会愿意冒失去它们的风险。

Flask 给我们提供了根据环境变量选择一个配置文件的能力。这意味着我们可以在我们的版本库中有多个配置文件,并总是能根据具体环境,加载到对的那个。

当我们到了有多个配置文件共存的境况,是时候把文件都移动到config包之下。下面是在这样的一个版本库中大致的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
requirements.txt
run.py
config/
__init__.py # 空的,只是用来告诉Python它是一个包。
default.py
production.py
development.py
staging.py
instance/
config.py
yourapp/
__init__.py
models.py
views.py
static/
templates/

在我们有一些不同的配置文件的情况下,可以这样设置:

文件名 内容
config/default.py 默认值,适用于所有的环境或交由具体环境进行覆盖。举个例子,在config/default.py中设置DEBUG = False,在config/development.py中设置DEBUG = True
config/development.py 在开发环境中用到的值。这里你可以设定在 localhost 中用到的数据库 URI 链接。
config/production.py 在生产环境中用到的值。这里你可以设定数据库服务器的 URI 链接,而不是开发环境下的本地数据库 URI 链接。
config/staging.py 在你的开发过程中,你可能需要在一个模拟生产环境的服务器上测试你的应用。你也许会使用不一样的数据库,想要为稳定版本的应用替换掉一些配置。

要在不同的环境中指定所需的变量,你可以调用app.config.from_envvar():

1
2
3
4
5
6
# yourapp/__init__.py

app = Flask(__name__, instance_relative_config=True)
app.config.from_object('config.default')
app.config.from_pyfile('config.py') # 从 instance 文件夹中加载配置
app.config.from_envvar('APP_CONFIG_FILE')

app.config.from_envvar(‘APP_CONFIG_FILE’)将加载由环境变量APP_CONFIG_FILE指定的文件。这个环境变量的值应该是一个配置文件的绝对路径。

这个环境变量的设定方式取决于你运行你的应用的平台。如果你是在一台标准的 Linux 服务器上运行,你可以使用一个 shell 脚本来设置环境变量并运行run.py

start.sh

1
2
APP_CONFIG_FILE=/var/www/yourapp/config/production.py
python run.py

start.sh特定于某个环境,所以它也不能放入版本控制当中。如果你把应用托管到 Heroku,你可以用 Heroku 提供的工具设置环境变量参数。对于其他 PAAS 平台也是同样的处理。

总结

  • 一个简单的应用也许仅需一个配置文件:config.py
  • instance 文件夹可以帮助我们隐藏不愿为人所知的配置变量。
  • instance 文件夹可以用来改变特定环境下的程序配置。
  • 应对复杂的,基于环境的配置,我们可以结合环境变量和app.config.from_envvar()来使用。

使用 React+Flask+PostgreSQL+SQLAlchemy+Docker 写博客页面

待补充 …

Fundemental

Routing

在链接中使用变量,可以在 URL 使用<variable_name>,变量前可以指定变量类型

1
2
3
4
@app.route('/post/<int:post_id>') #这里的 int 可写可不写
def show_post(post_id):
# show the post with the given id, the id is an integer
return f'Post {post_id}'

Unique URLs / Redirection Behavior

两种,

项目 URL 可以是/project/,如果输入/project会重定向到/project/

端点 URL 是/about,如果输入/about/会报 404 错误

1
2
3
4
5
6
7
@app.route('/projects/')
def projects():
return 'The project page'

@app.route('/about')
def about():
return 'The about page'

URL Building

示例:

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
30
from flask import Flask, url_for, redirect, render_template_string

app = Flask(__name__)

@app.route('/')
def index():
return '首页'

@app.route('/login')
def login():
return '登录页'

@app.route('/go-login')
def go_login():
# 用 url_for 替代 "/login"
return redirect(url_for('login'))

@app.route('/user/<int:uid>')
def profile(uid):
# 在视图里生成链接
return f'<a href="{ url_for("index") }">回首页</a>'

# 在模板里更常见:
@app.route('/tpl')
def tpl():
tpl_str = '''
<a href="{{ url_for('login') }}">去登录</a>
<a href="{{ url_for('profile', uid=99) }}">访问用户 99</a>
'''
return render_template_string(tpl_str)
image-20250504150223091

避免硬编码 URL

1
2
3
4
5
<!-- 硬编码,路径一改全都要改 -->
<a href="/user/42/profile">用户资料</a>

<!-- 动态生成,根据路由函数名和参数,路径变动时不需改模板 -->
<a href="{{ url_for('show_profile', user_id=42) }}">用户资料</a>

集中管理路由

  • 当你在 @app.route('/u/<int:user_id>') 改为 @app.route('/member/<int:user_id>'),只需改这处定义,所有使用 url_for('show_profile', user_id=…) 的链接自动跟着更新。

自动拼接查询参数 & 处理斜杠

1
2
url_for('search', q='Flask', page=2)
# 自动生成 "/search?q=Flask&page=2"
  • 省去手动拼接 ?q=…&page=… 的繁琐,且能正确 URL-encode。

支持蓝图 & 多模块

1
2
3
4
5
# blueprint user 注册时 name='user'
@user.route('/<int:id>')
def profile(id): ...
# 模板中
url_for('user.profile', id=42)
  • 蓝图名 + 视图名自动拼接,不用自己去记 URL 层级。

HTTP Method

两种

一种使用 route() + method

1
2
3
4
5
6
7
8
from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return do_the_login()
else:
return show_the_login_form()

一种使用单独的 get(), post() 方法

1
2
3
4
5
6
7
@app.get('/login')
def login_get():
return show_the_login_form()

@app.post('/login')
def login_post():
return do_the_login()

默认情况下,一个路由只响应 GET 请求。

其他详见:QuickStart

报错

问题一:terminal 输入pip3 install Flask 报错

1
2
3
4
5
6
7
8
9
WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)'))': /simple/flask/
WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)'))': /simple/flask/
WARNING: Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)'))': /simple/flask/
WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)'))': /simple/flask/
WARNING: Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)'))': /simple/flask/
Could not fetch URL https://pypi.org/simple/flask/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/flask/ (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)'))) - skipping
ERROR: Could not find a version that satisfies the requirement Flask (from versions: none)
Could not fetch URL https://pypi.org/simple/pip/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/pip/ (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)'))) - skipping
ERROR: No matching distribution found for Flask

image-20250504105351217

解决:

1
2
3
4
# 更新pip
$ python -m pip install --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org --upgrade pip
# 安装flask
$ pip install --trusted-host=pypi.org --trusted-host=files.pythonhosted.org flask

验证:

1
2
$ pip freeze
Flask==3.1.0

问题二:Access to 127.0.0.1 was denied

解决:open chrome://net-internals/#sockets,click [Flush socket pools], refresh your website.

问题三:运行 flask –app flaskr run –debug 时报错

Usage: flask run [OPTIONS] Try ‘flask run –help’ for help. Error: Failed to find Flask application or factory in module ‘flaskr’. Use ‘flaskr:name’ to specify one.

解决:重开一个新项目,写好代码后重新跑,不再报错

问题四:运行 pytest,报错 ModuleNotFoundError: No module named

1
2
3
4
5
ImportError while loading conftest '/Users/ella/Documents/Code/Demo/flask-api/tests/conftest.py'.
tests/conftest.py:5: in <module>
from flaskr import create_app
E ModuleNotFoundError: No module named 'flaskr'
.venvella@ellas-MacBook-Pro flask-api %

解决:在 conftest.py 中添加下面代码:

1
2
3
4
5
import sys
import logging
import os
# 把当前文件所在文件夹的父文件夹路径加入到 PYTHONPATH
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

原因:在 cmd 或者 terminal 控制台中直接使用 python 相关命令来执行程序,不会自动将当前项目加入到 PYTHONPATH 环境变量下,如果涉及到 import 其他文件夹下的变量就会报类似 ImportError: No module named xxx 这样的错误。

解决方法是使用 sys.append() 命令把报警包的所在文件夹路径加入到 PYTHONPATH。详见:ModuleNotFoundError: No module named ‘xxx’可能的解决方案大全