flask框架

1. 框架介绍

1.1 认识flask

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框架对比

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参数 指定路由支持的请求方式
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
      • 每次执行视图函数之前调用
      • 对请求进行一些准备处理
      • 如果在该函数中返回了一个响应,视图函数将不再被调用
  • 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.15000/  浏览器会发送一个请求给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中有两种上下文,请求上下文 和 应用上下文
  • 两种上下文的使用范围相同, 从请求开始到请求结束, 在范围外使用会报错
  1. 请求上下文
  • 记录一些和请求有关的数据, 包括request和session两个变量
  • request
    • 封装了HTTP请求的内容,针对的是http请求。
  • session
    • 用来记录请求会话中的信息,针对的是用户信息。
  1. 应用上下文
  • 记录一些和应用有关的数据, 包括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: 上下文为什么设置使用范围?

3.4 综合认证

1. 统一处理

  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, 则配置加载失败也不会报错
  • 0
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值