1. 框架介绍
1.1 认识flask
-
Flask诞生于2010年,是Armin ronacher(人名)用 Python 语言编写的 轻量级Web开发框架。
-
Flask 本身相当于一个 内核, 主要实现了路由分发和模板渲染功能, 分别集成自 Werkzeug 和 Jinja2模块包, 这两个也是Flask框架的核心。
-
虽然核心精简, 但flask提供了 非常好的扩展机制, 开发中的各类需求基本都有对应的官方/第三方扩展可以实现, 甚至连自己动手实现也很简单。
常用扩展包 -
Flask-SQLalchemy:ORM操作数据库;
-
Flask-RESTful:开发REST API的工具;
*Flask-Session:Session存储; -
Flask-Migrate:管理迁移数据库;
-
Flask-Caching:缓存;
-
Flask-WTF:表单;
-
Flask-Mail:邮件;
-
Flask-Login:认证用户状态;
-
Flask-OpenID:认证;
-
Flask-Admin:简单而可扩展的管理接口的框架
-
Flask-Bable:提供国际化和本地化支持,翻译;
-
Flask-Bootstrap:集成前端Twitter Bootstrap框架;
-
Flask-Moment:本地化日期和时间;
可用扩展仓库: https://pypi.org/search/?c=Framework+%3A%3A+Flask
# 视图
处理业务逻辑代码的函数
# 路由
发送网络请求的url地址
# web框架
协助开发者快速开发的web应用程序
作用:稳定性&可拓展性
降低开发难度&提升开发效率
# Flask框架
1.轻量级web后台框架
2.Werkzeug(路由模块)
3.模板引擎则使用 Jinja2
4.其他功能需要通过第三方拓展实现
1.2 web框架对比
框架轻重
- 重量级的框架:包含 全家桶式 丰富的工具,方便业务程序的 快速开发,如Django
- 轻量级的框架:只提供Web框架的 核心功能,自由灵活、方便高度定制,如Flask、Tornado
1.3 环境安装
- 目前官方最新版1.2.1,最好使用前一个版本(比较稳定)1.1.1
# 1.具备虚拟环境能力
sudo pip install virtualenv
sudo pip install virtualenvwrapper
# 2.创建虚拟环境(python3)
mkvirtualenv -p python3 虚拟环境名称
# 3.查看安装过哪些虚拟环境
workon 两下tab键
# 4.进入虚拟环境
workon 虚拟环境名称
# 5.在这个虚拟环境中安装flask框架
pip install flask
# 作用 :
虚拟环境可以搭建独立的python运行环境, 使得单个项目的运行环境与其它项目互不影响.
# 查看虚拟环境中安装的第三方包
pip list
# pycharm读取虚拟环境安装的路径
/home/.virtualenvs/虚拟环境名称/bin/python3
环境搭建参考:https://blog.csdn.net/weixin_46072106/article/details/109049093
1.4 文档
2. 基本使用
2.1 最小应用
hello flask
新建flask项目
* 创建pure python
类型的项目,使用flask_env
作为虚拟环境
- 打开pycharm,进入
settings
,取消默认的Flask项目关联(避免生成默认配置)
实践
- 新建文件helloflask.py
- 导入Flask类
from flask import Flask
创建Flask对象,接收一个参数__name__,他会指程序所在的包
# 1.创建Flask应用
app = Flask(__name__)
- Flask应用程序示例的run方法启动WEB服务器
if __name__ == '__main__':
# 2.运行应用 (启动一个测试服务器, 接收请求并调用对应的视图函数)
app.run()
- 装饰我的作用:将路由映射到视图函数index
# 3.定义路由
@app.route('/')
def index():
return 'hello flask'
app.run的参数
- 可以孩子定运行的主机IP地址,端口,是否开启调试模式
# host: 绑定的ip(域名) 0.0.0.0
# port: 监听的端口号
# debug: 是否开启调试模式 1> 可以在网页上显示python错误 2> 更新代码后测试服务器自动重启
app.run(host='0.0.0.0', port=8000, debug=True)
# 1. 导包
from flask import Flask
# 2. 创建flask应用对象app
app = Flask(__name__)
# 3. 自定义视图函数绑定路由信息
@app.route('/')
def index():
return "hello world"
# 4. 运行flask应用程序
app.run()
# 查看路由属性:app.url_map
# 1.导包
from flask import Flask
# 2.创建flask应用对象app
app = Flask(__name__)
# 3.自定义视图函数绑定路由信息
@app.route('/')
def index():
return 'Hello World'
# 4.运行flask应用程序
app.run()
# 查看路由信息属性:app.url_map
Flask程序初始化参数
初始化app对象的时候,可以指明对应初始化参数实现特定作用
1.__name__(必须传): (重点)
认为当前文件所有在目录就是项目目录,会在这个目录下寻找静态文件路径(图片,js,css),模板文件路径(html)
2.static_url_path(可以不传,知道作用就好):
静态文件访问路径前缀,,默认为:/static
3.static_folder(可以不传,知道作用就好):
静态文件存储的文件夹,,默认为 static
4.template_folder
模板文件存储的文件夹,可以不传,默认为 templates
app.run()的参数&配置信息读取
# Flask旧版本的运行方式
通过修改app.run()函数的host,port参数,指定运行的ip地址和端口号。
# 读取配置信息
app.config.get(key)
app.config["key"]
1.x版本的启动方式
- flask1.0开始, 类似Django, 通过终端执行封装的脚本命令flask run来运行应用, 不再需要调用app.run()
- 新的形式更加有利于 灵活修改环境配置
flask1.0开始, 类似Django, 通过终端执行封装的脚本命令flask run来运行应用, 不再需要调用app.run()
新的形式更加有利于 灵活修改环境配置
- 环境变量相当于 系统的全局变量,所有程序可用
# 获取环境变量
export # 查看所有环境变量
echo $环境变量 # 查看指定的环境变量
# 设置环境变量
export 环境变量名=值 # 给本次会话设置环境变量, 一旦终端关闭, 环境变量会清空
# 将环境变量写入配置文件(.bashrc/.bash_profile), 重新打开终端时再次加载环境变量
Pycharm运行flask程序
- pycharm针对
flask1.x
的启动方式,也提供了对应的运行配置 - pycharm运行flask程序
为了方便测试代码, 代码中仍会主要采用app.run的形式, 但实际开发中应该尽量使用 环境变量&终端命令的形式 - 旧版本运行pycharm方式
Flask新版本(1.x)运行方案–终端运行
总结:
#0.代码中可以省略:app.run() 这行代码
#1.使用环境变量的形式指明那个文件需要被运行
export FLASK_APP=flask文件名称
#3.设置运行模式:(非必须,可以不指明,默认是production生产模块)
export FLASK_ENV=production 运行在生产模式,未指明则默认为此方式
export FLASK_ENV=development 运行在开发模式 默认开启debug模式
#2.使用命令启动:默认运行在127.0.0.1:5000端口
flask run
#2.1指明ip和端口
flask run -h 0.0.0.0 -p 8000
# 注意:FLASK_APP FLASK_ENV production development 固定写法 不能出错
2.2 路由
定义路由
- 定义路由的三个细节
- 路由对应的URL必须以
/
开头 app.url_map
获取所有路由规则- 路由规则中主要包含 URL资源段、支持的请求方式、视图函数标记 三部分内容
app.route()
的methods
参数 指定路由支持的请求方式
- 路由对应的URL必须以
from flask import Flask
app = Flask(__name__)
# 1.路由对应的URL必须以/开头
# 2.通过app的url_map属性获取所有的路由规则 (URL资源段 支持的请求方式 视图函数标记)
# 3.可以通过route方法的methods参数指定路由支持的请求方式
@app.route('/hello', methods=['post', 'get'])
def index():
return "index"
if __name__ == '__main__':
print(app.url_map)
# 获取路由信息
# for rule in app.url_map.iter_rules():
# print(rule.rule, rule.methods, rule.endpoint)
app.run(debug=True)
路由变量
- 路由变量的作用是 传递URL路径参数, 实现动态URL
- 格式: /xx/<路由变量>
from flask import Flask
app = Flask(__name__)
# 路由变量: 传递URL路径参数
# 格式: /user/<路由变量名>
@app.route('/user/<userid>')
def index(userid): # 必须定义同名形参接收路由变量的值
print(userid)
return "index"
if __name__ == '__main__':
app.run(debug=True)
路由转换器
- 路由转换器的作用是 对URL传递的参数进行格式校验, 类似Django设置URL时的正则表达式参数
格式: /xx/<转换器名:路由变量>
所有的转换器类都继承自 BaseConverter 类
路由基本定义
from flask import Flask
app = Flask(__name__)
# 路由转换器: 对路由变量进行格式校验 条件不满足返回404
# 格式: /user/<路由转换器名:路由变量>
@app.route('/user/<int:userid>') # int: 内置转换器, 要求1-n个整数
def index(userid):
print(userid)
return "index"
if __name__ == '__main__':
app.run(debug=True)
#
1.修改@app.route('路由地址', methods=['post', 'get'])的参数达到修改访问url:
目的:不同的url引导到对应的视图函数
1.1 查询路由和视图函数的绑定关系
app.url_map
2.转换器:
作用:提取路由url`路径`上面的参数
语法:<参数类型:形参>
注意:参数类型`暂时`理解成对应类型
flask路由基本使用
自定义转换器
- 除了使用内置的变量转换器, 开发者还可以自定义转换器, 更加灵活的校验路由变量
- 使用自定义转换器的步骤
- 定义转换器类, 继承BaseConverter
- 设置regex属性 (正则匹配规则)
- 应用添加自定义转换器
# 自定义转换器:
# 1.定义转换器类, 继承BaseConverter
# 2.设置regex属性 (正则匹配规则)
# 3.添加自定义转换器
from flask import Flask
from werkzeug.routing import BaseConverter
app = Flask(__name__)
# 1.定义转换器类
class MobileConverter(BaseConverter):
# 2.设置regex属性(匹配规则)
regex = '1[3-9]\d{9}$' # 不要设置开头的^
# 3.添加自定义转换器
app.url_map.converters['mob'] = MobileConverter
@app.route('/user/<mob:mobile>')
def index(mobile):
print(mobile)
return "index"
if __name__ == '__main__':
# 获取所有的转换器 {转换器名: 转换器类}
# print(app.url_map.converters)
app.run(debug=True)
指定请求方式&使用 PostMan 对请求进行测试
2.3 请求
- flask的请求数据通过
request
对象来获取 - 常用属性
from flask import Flask, request
from werkzeug.datastructures import FileStorage
app = Flask(__name__)
@app.route('/', methods=['get', 'post'])
def index():
# 获取请求的基础数据
# print(request.url) # 请求的URL
# print(request.method) # 本次请求的请求方式
# print(request.headers) # 获取请求头信息 类字典对象
# print(request.headers['Host'])
# print(request.headers.get('Host')) # 建议使用get方法, 键不存在不报错
# 请求传递数据 1> URL路径 -> 路由变量 2> 查询字符串 get 3> 请求体 post 4> 请求头 -> request.headers
# 获取查询字符串 -> request.args xx?name=zs&age=20 类字典对象
# print(request.args.get('name'))
# print(request.args.get('age'))
# 请求体: 键值对(表单) 文本(json/xml) 文件(图片/音频)
# 获取post键值对 -> request.form 类字典对象
# print(request.form.get('username'))
# 获取post文本数据 -> request.data / request.json
# print(request.data) # 返回bytes类型
# print(request.json.get('age')) # request.json直接将json字符串转为字典
# 获取post文件 -> request.files 类字典对象
file = request.files.get("avatar") # type: FileStorage
# print(type(file)) # 返回 FileStorage文件对象
# 将文件保存到本地
file.save('123.jpg')
# 获取文件的二进制数据
# img_bytes = file.read()
# print(img_bytes)
return "index"
if __name__ == '__main__':
app.run(debug=True)
# 总结:
修改请求方式:
我们可以根据需求修改对应的请求方式,完成不同的业务需求
语法:app.route(路径,methods=["POST","GET"])
注意:methods是一个列表。请求方法大小写均可。
2.4 响应
1. 访问静态资源
- 将静态资源放入到 项目的
static
文件夹中 - 通过内置的静态资源的访问路由, URL路径格式为
/static/<filename>
- 如
static
目录放入文件123.jpg
, 则访问URL为http://127.0.0.1:5000/static/123.jpg
- 如
- Flask对象的初始化参数 也可以 修改静态资源的存储和访问路径
from flask import Flask
app = Flask(__name__, # 导入名称, flask会根据该参数查询静态文件的存储路径
# 官方建议直接使用__name__, 表示从当前目录中查询静态文件存储路径
static_folder="static1", # 设置静态文件的存储目录
static_url_path='/res/img', # 设置静态文件的URL访问路径 如 127.0.0.1:5000/res/img/123.jpg
)
if __name__ == '__main__':
app.run(debug=True)
2. 设置响应数据
- Flask中设置响应数据主要有两种方式:
- 设置多个返回值
- 自定义响应对象
2.1 三个返回值
- Flask中
视图函数的返回值
可以设置三个, 分别对应响应体, 响应状态码, 响应头
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route('/demo1')
def demo1():
# 返回值: 响应体, 响应状态码, 响应头
return 'demo1', 400, {'A': 40}
if __name__ == '__main__':
app.run(debug=True)
2.2 自定义响应对象
- 视图函数返回的 str / bytes 类型数据会被包装为 Response 响应对象, 也可以 创建响应对象来 自定义响应头 等信息
from flask import Flask, make_response, Response
app = Flask(__name__)
# 自定义响应对象
@app.route('/demo2')
def demo2():
# 视图函数的返回值可以为str/bytes类型, 并且flask内部会将其包装为Response响应对象
# return 'hello flask'
# 创建响应对象 设置响应头时,需要手动创建响应对象
response = make_response('hello flask') # type: Response
# 设置响应头
response.headers['B'] = 10
return response
if __name__ == '__main__':
app.run(debug=True)
3. 返回json数据
- 如果接口需要返回
JSON
数据,在 Flask 中可以直接使用jsonify()
生成一个 JSON 的响应 - 不推荐使用
json.dumps()
直接返回,因为返回的数据要符合 HTTP 协议规范,如果是JSON
需要指定content-type:application/json
from flask import Flask, make_response, Response, jsonify
app = Flask(__name__)
@app.route('/demo3')
def demo3():
dict1 = {'name': 'zs', 'age': 20}
# 字典转json字符串
# return json.dumps(dict1)
# 可以将字典转json字符串, 并且设置响应头的content-type为application/json
# return jsonify(dict1)
return jsonify(name='zs', age=20) # 也支持关键字实参的形式
if __name__ == '__main__':
app.run(debug=True)
总结:
当需要给客户端返回json类型的数据的时候,可以借助jsonify函数将python字典转换成json字符串
语法格式:
jsonify(字典)
作用:
1.将字典转换成json字符串
2.将返回值包装成resonse对象
3.将·数据类型·设置成application/json格式
4. 重定向
- flask中通过 redirect() 实现重定向功能
from flask import Flask, redirect
app = Flask(__name__)
@app.route('/demo3')
def demo3():
return jsonify(name='zs', age=20)
@app.route('/demo4')
def demo4():
# 重定向到指定网站
# return redirect('http://www.baidu.com')
# 重定向到自己的路由 只需要URL资源段
return redirect('/demo3')
if __name__ == '__main__':
app.run(debug=True)
# 总结:
概念:当你访问某一url路由的时候,不是给你引导到当前url对应的网页而是跳转到了另一个url对应的网页。
# 重定向函数:
redirect(url地址)
响应状态码
# 方法1: 元祖方式
返回响应格式: (response, status, headers[字典数据])
# 方法2:响应对象
resp = make_response(响应体)
resp.headers[key] = value
resp.set_cookie()
resp.status_code = 200
2.5 状态保持
1. cookie
- 特点:
- 将数据保存在 客户端 (用户的电脑上), 可以减轻服务器压力
- 访问网站时, 浏览器会 自动 将该网站的cookie数据发送给服务器
- 使用场景:
- 保存一些 不太重要的数据
from flask import Flask, make_response, Response, request
app = Flask(__name__)
@app.route('/')
def index():
# 后端设置cookie: 通过响应体的set_cookie字段
# 创建响应对象
response = make_response('index') # type: Response
# 设置响应头的set_cookie字段 value必须是str/bytes类型
response.set_cookie('per_page', '10', max_age=86400)
# 删除cookie 本质: 设置max-age=0
# response.delete_cookie('per_page')
# 返回响应对象
return response
@app.route('/demo1')
def demo1():
# 获取cookie: 浏览器会自动通过请求头的cookie字段来传递cookie数据
# request.cookies 直接获取到字典形式的cookie数据
print(request.cookies.get('per_page'))
return 'demo1'
if __name__ == '__main__':
app.run(debug=True)
# 设置响应头的set_cookie字段 value必须是str/bytes类型
response.set_cookie('per_page', '10', max_age=86400)
# 本质是将max_age=0
response.delete_cookie('per_page')
# 直接获取到字典形式的cookie数据
request.cookies.get('per_page')
2. session
- 特点:
- 将数据保存在 服务端 (服务器的数据库中), 安全性更高
- 使用场景:
- 保存一些 重要/敏感的数据
from datetime import timedelta
from flask import Flask, session
app = Flask(__name__)
# 设置应用秘钥 会被用于session签名
app.secret_key = 'test'
# 设置session过期时间 默认31天
app.permanent_session_lifetime = timedelta(days=7)
@app.route('/')
def index():
# session是一个类字典对象, 对其取值/赋值 就可以实现session数据的读写
# 记录session数据
session['username'] = 'zs'
# 设置session支持过期时间
session.permanent = True
# 删除session数据
# session.pop('username')
return "index"
@app.route('/demo1')
def demo1():
# 获取session数据
name = session.get('username')
print(name)
return 'demo1'
if __name__ == '__main__':
app.run(debug=True)
- flask的默认session机制没有将session数据保存到服务器的数据库中, 而是将session数据编码后保存到了cookie中 (签名cookie机制)
- 可以使用
flask-session组件
来实现传统的session存储
from flask import session
session 本质不是全局变量,线程隔离,线程id区分
# 注意:flask中使用session需要设置加密混淆字符串
app.secret_key = "saldhjhaldk(*i"
# 给flask中的session设置有效时长 [1天]
# flask默认是:31天过期
app.permanent_session_lifetime = timedelta(days=1)
session["user_name"] = "laowang"
# 运行设置有效时长
session.permanent = True
# 删除session
# session.pop(key) 删除一条
# session.clear() 删除全部
JWT
2.6 异常处理
- flask对 HTTP错误 进行了封装, 可以捕获http错误, 也可以主动抛出http错误
from flask import Flask, abort
# flask对http错误进行了封装, 可以捕获http错误也可以主动抛出http错误
app = Flask(__name__)
# 捕获http错误
@app.errorhandler(404)
def error_404(error): # 一旦进行捕获, 要求必须定义形参接收具体错误信息
return "<h3>您访问的页面去浪迹天涯了</h3> %s" % error
# 还可以捕获系统内置错误
@app.errorhandler(ZeroDivisionError)
def error_zero(error):
return '除数不能为0'
@app.route('/')
def index():
# a = 1 / 0
abort(500) # 主动抛出异常 (只能抛出http错误)
return "index"
if __name__ == '__main__':
app.run(debug=True)
abort(http错误状态码)
# 捕获http错误
@app.errorhandler(404)
# 还可以捕获系统内置错误
@app.errorhandler(ZeroDivisionError)
3. 高级机制
3.1 请求钩子
- 请求钩子可以对请求的各阶段进行监听, 方便开发者 针对请求完成一些统一的处理, 以便减少重复代码, 作用类比Django中的中间件
- 开发中中主要会用到以下四种请求钩子:
- before_request
- 每次执行视图函数之前调用
- 对请求进行一些准备处理
- 如果在该函数中返回了一个响应,视图函数将不再被调用
- before_request
- after_request
- 如果没有抛出错误,每次执行视图函数之后(已经包装为响应对象)调用
- 在此函数中可以对响应值在返回之前做最后一步修改处理
- 接受一个参数:包装好的响应对象
- 需要将修改后的响应对象返回
- before_first_request
- web应用被第一次请求前调用
- 可以进行web应用初始化处理
- teardown_request:
- 每次执行视图函数之后调用
- 无论是否出现异常都会执行, 一般用于请求收尾
- 接受一个参数:错误信息,如果有相关错误抛出
from flask import Flask, Response
# 请求钩子: 类比django中间件 ,请求钩子可以对请求的各阶段进行监听, 方便开发者针对请求完成一些统一的处理
app = Flask(__name__)
# 每次执行视图函数之前调用, 对请求进行一些准备处理, 如参数解析, 黑名单过滤, 数据统计等
@app.before_request
def prepare():
print('before_request')
# 每次执行视图函数之后(已经包装为响应对象)调用, 对响应进行一些加工处理, 如设置统一响应头, 设置数据的外层包装
@app.after_request
def process(response:Response): # 必须定义形参接收响应对象
print('after_request:')
# print(response.headers)
# print(response.data)
# print(response.status_code)
return response
# web应用被第一次请求前调用, 可以进行web应用初始化处理, 如数据库连接
@app.before_first_request
def initial():
print('before_first_request')
# 每次执行视图函数之后调用, 无论是否出现异常都会执行, 一般用于请求收尾, 如资源回收, 异常统计
@app.teardown_request # 测试时不要开启调试模式
def request_handle(error): # 必须定义形参来接收具体错误信息, 如果没有错误, error=None
print('teardown_request : %s' % error)
@app.route('/')
def index():
print('执行视图')
a = 1 / 0
return "index"
if __name__ == '__main__':
app.run()
- 请求钩子可以通过两种方式进行添加, 装饰器形式 或者 直接方法调用
from flask import Flask, Response
app = Flask(__name__)
# 每次执行视图函数之前调用
# @app.before_request
# def prepare():
# print('before_request')
# 另一种语法
def prepare():
print('before_request')
app.before_request(prepare)
@app.route('/')
def index():
print('执行视图')
a = 1 / 0
return "index"
if __name__ == '__main__':
app.run()
总结:
1.当访问一路由:127.0.0.1:5000/ 浏览器会发送一个请求给flask后端: request
2.当请求发送过来的时候有四个时机:
1.第一次请求之前 before_first_request
2.每次请求之前 before_request
3.每次请求之后 after_request
4.每次请求之后是否有错误 teardown_request
@app.before_request
执行时机:每一次请求之前触发,视图函数执行之前触发
作用:拦截请求,封ip,token用户权限认证
@app.before_first_request
执行时机:项目第一次启动就会触发,而且仅会执行一次
作用:项目初始化工作
@app.after_request
执行时机:每次在视图函数执行完毕的时候触发[没有错误],要接受一个响应对象
作用:拦截响应,设置cookie,设置响应状态码 响应头信息
@app.teardown_request
执行时机:每次在视图函数执行完毕后触发[无论是否有错误], 接受一个异常对象
作用:异常处理,收尾工作
3.2 蓝图
1. 基本使用
- 蓝图的作用: 实现Flask项目 模块化
- 项目模块化主要是 将业务以功能模块进行划分, 每个功能模块对应一个包, 用于存放和其有关的视图/工具/模型文件等, 如home, user
- 对于大型项目, 一般 每个功能模块对应创建一个蓝图, 由多个蓝图代替应用来分别管理各模块的视图
--------- project # 工程目录
|------ main.py # 启动文件
|------ user # 用户模块
| |--- __init__.py # 包的初始化文件, 此处创建管理用户模块的蓝图对象
| |--- views.py # 视图文件
| |--- ...
|
|------ home # 首页模块
| |--- __init__.py # 包的初始化文件, 此处创建管理首页模块的蓝图对象
| |--- views.py # 视图文件
| |--- ...
|...
- 在 home包 的初始化文件 init.py 中, 创建蓝图对象
# home/__init__.py
from flask import Blueprint
# 1. 创建蓝图对象
home_blu = Blueprint("home_b", __name__)
- 在 home包中创建 views文件, 存放视图函数
定义视图函数时, 使用蓝图对象来定义路由
# home/views.py
from home import home_blu
# 2. 使用蓝图对象来定义路由
@home_blu.route('/')
def index():
return "index"
- 想要让蓝图对象能够完成路由定义, 还需要 Flask应用注册蓝图对象
# main.py
from flask import Flask
from home import home_blu
app = Flask(__name__)
# 3.应用注册蓝图对象
app.register_blueprint(home_blu)
if __name__ == '__main__':
print(app.url_map)
app.run(debug=True)
- 视图文件目前是独立的, 还需要 包的初始化文件导入视图文件, 让视图文件和主程序建立关联
# home/__init__.py
from flask import Blueprint
# 1. 创建蓝图对象
home_blu = Blueprint("home_b", __name__)
# 4. 让视图文件和主程序建立关联
# 遇到ImportError, 需要查看和调整代码的执行顺序
from . import views
2. 使用细节
- 蓝图的三个使用细节
- 创建蓝图时, 可以通过 url_prefix参数 给蓝图定义的路由添加 统一的URL资源段前缀
- 蓝图定义的路由, 其函数标记为 蓝图名.函数名
蓝图也可以 设置请求钩子- 只有访问该蓝图定义的路由时才会触发
- 实现局部监听
# home/__init__.py
from flask import Blueprint
# 细节1: 可以通过url_prefix参数给蓝图定义的路由添加统一的URL资源段前缀
home_blu = Blueprint("home_b", __name__, url_prefix='/home')
# 细节3: 蓝图也可以设置请求钩子 只有访问该蓝图定义的路由时才会触发 局部监听
@home_blu.before_request
def home_prepare():
print('home_prepare')
from . import views
# home/views.py
from flask import url_for
from home import home_blu
@home_blu.route('/')
def index():
return "index"
@home_blu.route('/demo1')
def demo1():
# 细节2: 蓝图定义的路由, 其函数标记为 蓝图名.函数名
url1 = url_for('home_b.demo1')
# print(url1)
return 'demo1'
3.3 上下文
*上下文:是一个 数据容器,保存了 Flask 程序运行过程中的一些信息。
- Flask中有两种上下文,请求上下文 和 应用上下文
- 两种上下文的使用范围相同, 从请求开始到请求结束, 在范围外使用会报错
- 请求上下文
- 记录一些和请求有关的数据, 包括request和session两个变量
- request
- 封装了HTTP请求的内容,针对的是http请求。
- session
- 用来记录请求会话中的信息,针对的是用户信息。
- 应用上下文
- 记录一些和应用有关的数据, 包括current_app和g两个变量
- current_app
- 会自动引用创建的Flask对象, 需要在项目的其他文件中使用app时, 应该通过current_app来获取, 可以减少循环导入问题
- g
- flask给开发者预留的一个容器, 用于记录自定义数据
- g变量每次请求会重置数据
- g使用场景:
- 1> 在钩子函数和视图函数之间传递数据
- 2> 函数嵌套调用时传递数据
# main.py
from flask import Flask, request, current_app, g
# 上下文变量: 有使用范围 [请求开始, 请求结束]
# 请求上下文: 记录一些和请求有关的数据 request session
# 应用上下文: 记录一些和应用有关的数据 current_app g
import tool
app = Flask(__name__)
@app.route('/')
def index():
# print(request.url)
g.name = 'zs'
tool.func1()
return "index"
@app.route('/demo1')
def demo1():
print(g.name) # 会报错
return 'demo1'
if __name__ == '__main__':
# print(request.url) # 使用范围外, 会报错
app.run(debug=True)
# tool.py
from flask import g, current_app
def func1():
print(g.name)
print(current_app.url_map)
# tool2.func2()
- 上下文机制 (理解)
- 问题1: 上下文变量是否为全局变量?
- 不是全局变量, web服务器会通过多线程并发调用web应用, 而全局变量会被所有线程共享, 无法记录并发的多个请求数据
- 上下文机制实现了线程隔离(LocalStack类型, 本质是字典, key是线程id, 值是上下文变量), 每个线程存取自己的数据, 相互不影响
- 请求1 -> 线程1 -> request = 请求1
- 请求2 -> 线程2 -> request = 请求2
- 问题2: 上下文为什么设置使用范围?
- 主要目的为节省内存
- 请求开始时, 创建上下文(记录上下文变量);
- 请求结束时, 销毁上下文(将上下文变量删除, 数据占用的空间被释放)
拓展阅读:https://fanchunke.me/Flask/Flask%E4%B8%AD%E7%9A%84%E8%AF%B7%E6%B1%82%E4%B8%8A%E4%B8%8B%E6%96%87%E5%92%8C%E5%BA%94%E7%94%A8%E4%B8%8A%E4%B8%8B%E6%96%87/
3.4 综合认证
1. 统一处理
- 统一处理
- 需求: 获取用户身份
- 分析: 除了静态资源, 基本所有视图都需要获取用户身份, 每个视图单独获取出现大量的代码冗余
- 解决办法: 设置 请求钩子, 并通过 g变量 将数据传递给视图函数
from flask import Flask, session, g
app = Flask(__name__)
app.secret_key = 'test'
# 需求1: 所有视图都需要获取用户身份
# 解决办法: 用钩子函数进行封装 减少代码冗余
@app.before_request
def prepare():
# 必须使用g变量来传递数据, 使用全局变量不能记录并发的多个请求数据
g.name = session.get('username')
@app.route('/')
def index():
if g.name:
return "欢迎回来, %s" % g.name
else:
return '首页'
@app.route('/demo1/')
def demo1():
print(g.name)
return 'demo1'
@app.route('/login')
def login():
"""登录"""
session['username'] = 'zs'
return '登录成功'
if __name__ == '__main__':
app.run(debug=True)
2. 访问权限
- 需求: 对指定的路由进行访问限制
- 分析: 部分视图需要身份校验, 这部分视图每个单独校验仍会出现大量的代码冗余
- 解决办法: 封装 装饰器 完成身份校验逻辑, 对指定视图函数设置装饰器
from flask import Flask, session, g, abort
from functools import wraps
app = Flask(__name__)
app.secret_key = 'test'
@app.before_request
def prepare():
g.name = session.get('username')
@app.route('/')
def index():
if g.name:
return "欢迎回来, %s" % g.name
else:
return '首页'
@app.route('/login')
def login():
"""登录"""
session['username'] = 'zs'
return '登录成功'
# 需求2: 对部分视图进行访问限制 如个人中心必须登录才能访问
# 解决方案: 使用装饰器封装访问限制 减少代码冗余
def login_required(f): # f = user
def wrapper(*args, **kwargs):
# 获取函数名
print(wrapper.__name__)
if g.name: # 用户已登录
return f(*args, **kwargs) # 正常访问视图函数
else: # 用户未登录
abort(401) # 400 语法/参数错误 401 未认证 403 已认证, 权限不足 404 资源不存在 405 请求方式不支持 500 服务器错误
return wrapper
@app.route('/user')
@login_required # user = login_required(user)
def user():
"""个人中心"""
return '访问 %s 的个人中心' % g.name
if __name__ == '__main__':
print(app.url_map)
app.run(debug=True)
functools.wraps
- 系统内置的装饰器, 主要用于装饰器中的闭包函数
- 作用是 将被装饰的函数(wrapper)的函数信息 替换为 指定函数(f)的函数信息 (包括name 函数名, doc 函数注释等)
- 未设置wraps装饰器的情况:
def decorater1(f): # f = demo
def wrapper(*args, **kwargs):
# 获取函数名
print(wrapper.__name__)
return f(*args, **kwargs)
return wrapper
@decorater1
def demo():
print("哈哈")
# 调用函数
demo()
- 设置wraps装饰器的情况:
from functools import wraps
def decorater1(f): # f = demo
@wraps(f) # 设置装饰器
def wrapper(*args, **kwargs):
# 获取函数名
print(wrapper.__name__)
return f(*args, **kwargs)
return wrapper
@decorater1
def demo():
print("哈哈")
# 调用函数
demo()
- flask中的函数标记是根据函数名生成的
( 视图函数添加装饰器login_required 后, 函数标记都会使用闭包函数名wrapper, 这样会出现函数标记冲突, 程序报错 - 解决办法: 给闭包函数添加装饰器functools.wraps(f), 让函数标记使用原视图函数名生成
from flask import Flask, session, g, abort
from functools import wraps
app = Flask(__name__)
app.secret_key = 'test'
@app.before_request
def prepare():
g.name = session.get('username')
@app.route('/')
def index():
if g.name:
return "欢迎回来, %s" % g.name
else:
return '首页'
@app.route('/login')
def login():
"""登录"""
session['username'] = 'zs'
return '登录成功'
# 使用装饰器封装访问限制
def login_required(f): # f = user
@wraps(f) # 会将被装饰的函数(wrapper)的函数信息替换为指定函数(f)的函数信息(__name__ 函数名, __doc__ 函数注释)
# 设置该装饰器后, 可以让闭包函数使用原函数名, 避免函数标记出现冲突(函数标记是根据函数名来生成的)
def wrapper(*args, **kwargs):
if g.name:
return f(*args, **kwargs)
else:
abort(401)
return wrapper
@app.route('/user')
@login_required
def user():
"""个人中心"""
return '访问 %s 的个人中心' % g.name
@app.route('/demo1')
@login_required
def demo1():
return 'demo1'
if __name__ == '__main__':
print(app.url_map)
app.run(debug=True)
3.5 应用配置
1. 加载配置
app.config
用于设置配置, 该属性继承自 dict, 可以以字典形式赋值取值
from datetime import timedelta
from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'test'
# config属性用于设置配置
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
@app.route('/')
def index():
# 设置session 用于测试配置是否生效
session['name'] = 'zs'
# 读取配置
print(app.config.get('PERMANENT_SESSION_LIFETIME'))
return "index"
if __name__ == '__main__':
app.run(debug=True)
- 实际开发中, 应该将配置封装起来, 方便进行统一的管理, 类似Django的settings文件
- Flask提供了多种封装方式, 这里先介绍一种常用的方案 – 从对象中加载配置
从对象中加载配置 - 实际开发中, 项目往往存在多套配置
- 开发环境配置
- 生产环境配置
- 测试环境配置
- …
- 各种环境的配置既有相同也有不同, 大量相同的配置会出现代码冗余
- 从对象中加载配置
- 配置封装方案之一, 以面向对象的形式 封装配置, 有利于 减少重复代码 以及 代码解耦合
定义配置文件 config.py, 在文件中将应用配置 以类的形式 封装起来
- 配置封装方案之一, 以面向对象的形式 封装配置, 有利于 减少重复代码 以及 代码解耦合
# config.py
from datetime import timedelta
class BaseConfig:
"""配置基类 可以将相同的配置抽取到基类中, 减少重复代码"""
# 定义和配置同名的类属性
PERMANENT_SESSION_LIFETIME = timedelta(days=7)
class DevelopmentConfig(BaseConfig):
"""开发环境"""
SQL_URL = '127.0.0.1:3306/test1' # 数据库地址
class ProductionConfig(BaseConfig):
"""生产环境"""
SQL_URL = '222.10.15:3306/users' # 数据库地址
- 主文件 main.py 从对象中加载封装的配置 app.config.from_object()
# main.py
from datetime import timedelta
from flask import Flask
app = Flask(__name__)
# 从对象中加载配置
# 优点: 面向对象的设计有利于 减少重复代码 以及 代码解耦合
from config import DevelopmentConfig
app.config.from_object(DevelopmentConfig)
@app.route('/')
def index():
print(app.config.get('PERMANENT_SESSION_LIFETIME'))
return "index"
if __name__ == '__main__':
app.run(debug=True)
2. 切换配置
- 虽然封装了多套配置, 但需要 修改代码才能切换配置, 这种方式并不利于开发和测试
- Flask提供了切换配置的更好方案, 需要进行以下两步:
- 定义工厂函数, 封装应用的创建过程
- 利用环境变量, 调用工厂函数, 指定配置并动态创建应用
2.1 定义工厂函数
- 定义工厂函数, 封装应用的创建过程
- 在 config.py 的基础上, 定义字典记录配置类型和配置子类之间的映射关系
# config.py
from datetime import timedelta
class BaseConfig:
"""配置基类 可以将相同的配置抽取到基类中, 减少重复代码"""
# 定义和配置同名的类属性
PERMANENT_SESSION_LIFETIME = timedelta(days=7)
class DevelopmentConfig(BaseConfig):
"""开发环境"""
SQL_URL = '127.0.0.1:3306/test1' # 数据库地址
class ProductionConfig(BaseConfig):
"""生产环境"""
SQL_URL = '222.10.15:3306/users' # 数据库地址
# 定义字典来记录 配置类型 和 配置子类 之间的映射关系
config_dict = {
'dev': DevelopmentConfig,
'pro': ProductionConfig
}
- 在主文件 main.py 中, 定义工厂函数封装应用的创建过程, 并通过参数指定应用对应的配置类型
from flask import Flask, current_app, Config
from config import config_dict
# 工厂函数: 根据参数需求, 内部封装对象的创建过程
def create_app(config_type):
"""封装应用的创建过程"""
# 创建应用
flask_app = Flask(__name__)
# 根据配置类型取出对应的配置子类
config_class = config_dict[config_type]
# 加载普通配置
flask_app.config.from_object(config_class)
return flask_app
# 创建应用对象
app = create_app('dev')
@app.route("/")
def index():
print(app.config.get('SQL_URL'))
return "index"
if __name__ == '__main__':
app.run()
2.2 动态创建应用
- 通过代码调用函数 create_app(‘dev’) 创建应用, 仍然在代码中写死了 配置类型dev
- FLASK提供的环境变量 FLASK_APP 允许 以调用函数的形式动态创建应用
- 格式: FLASK_APP="应用所在模块:工厂函数(参数)"
$ export FLASK_APP="main:create_app('dev')" # 设置环境变量, 通过调用函数 设置配置并动态创建Flask应用
$ flask run # 运行web程序
from flask import Flask, current_app
from config import config_dict
def create_app(config_type):
"""封装应用的创建过程"""
flask_app = Flask(__name__)
config_class = config_dict[config_type]
flask_app.config.from_object(config_class)
# 由于动态创建应用(app失效), 不能通过app.route定义路由
# 添加路由规则 (实际开发中使用较少, 以蓝图定义路由为主)
flask_app.add_url_rule('/', index.__name__, index)
# 注册蓝图对象 蓝图可以正常使用
# from home import home_blu
# flask_app.register_blueprint(home_blu)
return flask_app
# 定义视图函数, 不添加app.route()装饰器, 通过 添加路由规则 的方式手动定义路由
def index():
print(current_app.config.get('SQL_URL'))
return "index"
# app = create_app('dev') # app无效
# @app.route("/") # app无效
# def index():
# print(app.config.get('SQL_URL'))
# return "index"
# if __name__ == '__main__':
# app.run() # app失效
3. 加载隐私配置
- 对于线上项目,隐私配置卸载项目的配置文件中,会 *** 增加数据泄露的风险***
- FLASK提供了 *** 从环境变量中加载配置*** 的方案,可用于加载隐私配置
- 该方案允许 通过环境变量指定配置文件的路径, 且 路径允许在项目目录以外
- 环境变量格式: export 环境变量名=“隐私配置的文件路径”
- 在项目目录外 创建隐私配置文件secret_config.py, 并以全局变量形式设置隐私配置
# secret_config.py
SECRET_KEY = 'heima123' # 隐私配置
- 主文件main.py 通过环境变量方式来加载隐私配置
# main.py
from datetime import timedelta
from flask import Flask
app = Flask(__name__)
# 从环境变量中加载配置
# 优点: 可以保护隐私配置 export ENV_CONFIG="隐私配置的文件路径"
app.config.from_envvar('ENV_CONFIG')
@app.route('/')
def index():
print(app.config.get('SECRET_KEY'))
return "index"
# if __name__ == '__main__':
# app.run(debug=True)
- 环境变量&终端命令 启动程序
$ export FLASK_APP="main" # 设置内置环境变量
$ export ENV_CONFIG="/xx/secret_config.py" # 设置隐私配置对应的环境变量
$ flask run # 启动web程序
- 实际开发中的方案
- 开发阶段, 只加载普通配置
- 生产阶段,*** 先加载普通配置, 再通过环境变量的方式加载项目以外的隐私配置并覆盖原有配置***
def create_app(config_type):
"""封装应用的创建过程"""
# 创建应用
flask_app = Flask(__name__)
# 根据配置类型取出对应的配置子类
config_class = config_dict[config_type]
# 先加载普通配置
flask_app.config.from_object(config_class)
# 再加载隐私配置 silent=True, 配置加载失败也不报错
flask_app.config.from_envvar('ENV_CONFIG', silent=True)
return flask_app
- 加载配置时, 设置参数 silent=True, 则配置加载失败也不会报错