python自动化(七)自动化测试平台开发:3.flask技术讲解上

一.后端开发框架flask

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第1张图片

本次主要讲解flask框架

1.flask框架简介

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第2张图片

flask框架是一款非常受欢迎的python web框架,它非常的灵活小巧。

2 flask框架的基本使用

from flask import Flask

# 传入__name__初始化一个Flask实例
app = Flask(__name__)


# 装饰器,将当前路由映射到指定函数
@app.route('/')
def hello_world():
    return 'hello world'


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

运行结果如下:

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第3张图片
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第4张图片

3.指定参数来启动flask

from flask import Flask

# 传入__name__初始化一个Flask实例
app = Flask(__name__)


# 装饰器,将当前路由映射到指定函数
@app.route('/')
def hello_world():
    return 'hello world'


if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)
    # 说明
    # debug=True,表示使用调试模式启动flask,这种模式下代码更新后不需要重新启动flask,系统会自动刷新。且当代码有问题时,会在终端输出异常信息
    # host="0.0.0.0",这种方式可以让其他电脑访问flask服务
    # port=5055,指定端口为5055

4.flask的配置与配置文件

flask项目的配置都是以app.config对象来进行配置的。

在Flask项目中,有四种方式进行项目的配置。

4.1 直接硬编码

app = Flask(__name__)
app.config['DEBUG'] = True # 设置项目以debug模式运行

这种方式不灵活,复用性太低

4.2 通过update()方式

因为app.config本质上是一个dict,使用可以使用update()来进行配置

app = Flask(__name__)

app.config.update(
    DEBUG=True,
    SECRET_KEY='....'
)

4.3.通过from_object()方法

如果项目的配置项特别多,那么我们可以把所有的配置项都放在一个模块中,然后通过加载模块的方式进行配置,假设有一个settings.py模块,专门用来存储配置项的,此你可以通过app.config.from_object()方法进行加载,并且该方法既可以接收模块的的字符串名称,也可以模块对象本身。
有两种形式:

# 1. 通过模块字符串
app.config.from_object('settings')
# 2. 通过模块对象
import settings
app.config.from_object(settings)

使用这种方式,添加配置文件后,将配置项都放入该文件中,其他文件直接引用该配置文件中的配置项,提高了代码的复用性、降低了耦合度,同时,在配置文件中修改了配置项时,其他代码中均不需要修改,从而提高了代码的灵活性。

例如:新建config.py文件,添加一些配置项如下:

# 设置Debug模式为True
DEBUG = True

# 指定HOST
HOST = '127.0.0.1'

在flask文件中导入:

from flask import Flask
import config

app = Flask(__name__)


# 装饰器,将当前路由映射到指定函数
@app.route('/')
def hello_world():
    return 'hello world'

if __name__ == '__main__':
    app.config.from_object(config)
    app.run()

再运行,也能开启Debug模式。
也可以通过字符串形式导入:

if __name__ == '__main__':
    app.config.from_object('config')
    app.run()

此时不需要再导入config模块。

4.4.通过from_pyfile()方法

app.config.from_pyfile()方法传入一个文件名,通常是以.py结尾的文件,但也不限于只使用.py后缀的文件。
通过导入Python文件的形式导入配置文件:

if __name__ == '__main__':
    app.config.from_pyfile('config.py')
    app.run()

from_pyfile()方法有一个silent参数,设置为True时,如果配置文件不存在也不会报错;
不仅支持Python格式的配置文件,也支持.ini等格式。

5.flask实现url与函数的映射

5.1实现基本映射

flask中使用@app.route('/')来实现函数与url之间的映射,app.route()中传入指定的url。

from flask import Flask

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

# 装饰器,将当前路由映射到指定函数。当我们访问http://127.0.0.1:5055/app时,就会自动映射到hello_world()函数中。我们一般将这种函数称为视图函数
@app.route('/app')
def hello_world():
    return 'hello app'

if __name__ == '__main__':
    app.run(host="0.0.0.0",port=5055)

5.2 实现函数中使用url的参数

视图函数中可以获取到url中的参数。

from flask import Flask

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)
# 装饰器,将当前路由映射到指定函数,id为url中的可变参数
@app.route('/app/')
def hello_world(id):
    return f'hello app{id}'

if __name__ == '__main__':
    app.run(host="0.0.0.0",port=5055)

效果如下:

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第5张图片

说明:

  1. 如果要在url中设置可变参数,必须以<变量名称>的方式在app.route中设置路由
  2. 视图函数中需要传入对应变量作为参数。
  3. 可以指定变量的数据类型,格式为<类型:变量名>,常见的限制类型有:string(接受任何没有斜杠/的字符串,为flask中的默认格式),int(整数类型),float(浮点型),path(与string类似,但可以接受斜杠/),uuid(uuid类型的字符串),any(可以同时指定多种路径)
from flask import Flask

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

# 装饰器,将当前路由映射到指定函数,id为url中的可变参数
@app.route('/app/')
def hello_world(id):
    return f'hello app{id}'

if __name__ == '__main__':
    app.run(host="0.0.0.0",port=5055)

运行效果如下:

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第6张图片

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第7张图片

6.指定url的HTTP请求方式

app.route()中可以使用参数methods来指定使用的HTTP连接方式

from flask import Flask
from flask import request

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

# 使用methods来指定HTTP方式,默认为GET
@app.route('/app',methods=['POST']) 
def hello_world():
    return 'hello app'

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

使用postman来访问效果如下:

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第8张图片

7.使用url_for获取视图函数的映射路径

可以使用url_for(视图函数名称,函数参数1,函数参数2,…)来获取对应视图函数的url地址

from flask import Flask, url_for

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

@app.route('/app/')
def hello_world(id):
    return f'hello app{id}'

@app.route('/url_for')
def test_url_for():
    s = url_for('hello_world',id=3) # 根据视图函数获取对应的url
    return s

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行后效果如下:

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第9张图片

8.页面重定向

页面重定向就是当我们访问一个页面时,自动跳转到另一个页面。一般分为两种重定向:

第一种:永久重定向。比如某个网站地址变为了另一个地址,则当我们访问这个网站时,会自动跳转到新的地址。例如输入www.jingdong.com的时候,会被重定向到www.jd.com,因为jingdong.com这个网址已经被废弃了,被改成jd.com,所以这种情况下应该用永久重定向。

第二种:临时重定向。比如某个页面需要登录权限,当你未登陆时访问,则会自动跳转到登录页面

from flask import Flask, url_for, redirect

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

@app.route('/app')
def hello_world():
    return 'hello app'

@app.route('/redict')
def test_redict():
    return redirect('/app') # 重定向到‘/app’页面

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

说明:redirect(url地址,code=xxx),第一个参数为需要重定向的目标url,第二个参数code用来指定返回码,默认为302

9.函数的返回值 - 响应(Response)

视图函数中可以返回以下类型的值:

  • Response对象。
  • 字符串
    Flask是根据返回的字符串类型重新创建一个werkzeug.wrappers.Response对象,Response将该字符串作为主体,状态码为200,MIME类型为text/html,然后返回该Response对象。
  • 元组
    传入元组的格式是(response,status,headers),response为一个字符串,status值是状态码,headers是响应头。

如果不是以上三种类型,Flask会通过Response.force_type(rv,request.environ)转换为一个请求对象。

之前在函数中返回的都是字符串,现进行返回列表的测试:

from flask import Flask,url_for,request,redirect

app = Flask(__name__)

@app.route('/about')
def about():
    return ['123']

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

访问http://127.0.0.1:5000/about,会报错:
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第10张图片
提示返回的类型只能是string、dict、tuple、Response instance或WSGI callable,其他类型会报错,现换成字典测试:

from flask import Flask,url_for,request,redirect

app = Flask(__name__)

@app.route('/about')
def about():
    return {'name':'Corley'}

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

显示:
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第11张图片
也可以返回元组,是元组时,只返回元组的第一个元素;
在一般情况下,返回元组的用法是return '关于我们',200,即return '字符串',状态码
返回Response测试:

from flask import Flask,url_for,request,redirect,Response

app = Flask(__name__)

@app.route('/about')
def about():
    return Response('关于我们')

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

显示:
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第12张图片
可以看到,给Response传入字符串参数后,返回内容和字符串是类似的;
Response('关于我们')相当于Response('关于我们',status=200,mimetype='text/html')
Response的用法是Response('字符串',状态码,mimetype='')
也可以用make_response()方法创建Response对象并返回,这个方法可以设置额外的数据,比如设置cookie、header等信息,测试如下:

from flask import Flask,url_for,request,redirect,Response,make_response

app = Flask(__name__)


@app.route('/about')
def about():
    return make_response('关于我们')

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

效果与前者是一样的。

10.获取url的请求数据

视图函数中可以使用flask.request获取url中的请求数据

from flask import Flask, request

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

@app.route('/app')
def hello_world():
    request_data = dict(request.args) # request.args来获取get请求中的数据
    return request_data

@app.route('/redict' ,methods=['POST'])
def test_redict():
    request_data = dict(request.json) # 使用request.json来获取post请求中的json数据;使用request.form来获取post请求中的form数据
    return request_data

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

11.Flask-Restful的概念和使用

11.1 Restful API规范

Restful API是用于在前端与后台进行通信的一套规范,使用这个规范可以让前后端开发变得更加简单。

(1)协议

需要采用http或者是采用https协议。

(2)数据传输格式

数据之间传输的格式应该使用json形式

(4)HTTP请求方法
方法 含义 举例
GET 从服务器上获取资源 /users/获取所有用户
/user/id/根据id获取一个用户
POST 在服务器上新创建一个资源 /user/新建一个用户
PUT 在服务器上更新资源(客户端提供所有改变后的数据) /user/id/更新某个id的用户的信息(需要提供用户的所有信息)
PATCH 在服务器上更新资源(客户端只提供需要改变的属性) /user/id/更新某个id的用户信息(只需要提供需要改变的信息)
DELETE 从服务器上删除资源 /user/id/删除一个用户
状态码 描述 含义
200 OK 服务器成功响应客户端的请求
400 INVALID REQUEST 用户发出的请求有错误,服务器没有进行新建或修改数据的操作
401 Unauthorized 用户没有权限访问这个请求
403 Forbidden 因为某些原因禁止访问这个请求
404 NOT FOUND 用户发送的请求的url不存在
406 NOT Acceptable 用户请求不被服务器接收(比如服务器期望客户端发送某个字段,但是没有发送)
500 Internal server error 服务器内部错误,比如出现了bug

12 Flask-Restful插件的基本使用

12.1 基本概念

Flask-Restful是Flask中专门用来实现Restful API的插件,使用它可以快速集成Restful API功能。
在普通的网站中,这个插件显得有些鸡肋,因为在普通的网页开发中,是需要去渲染HTML代码的,而Flask-Restful在每个请求中都返回json格式的数据。但是在前后端分离的项目中,Flask-Restful是flask非常好用的一个插件。

2.安装

使用pip install flask-restful命令安装。

3.基本使用

from flask import Flask, request
from flask_restful import Api , Resource

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)
api = Api(app)

# 视图类需要集成Resource
class IndexView(Resource):

    # get表示get请求
    def get(self):
        return {'username': 'Corley'}

    # post表示post请求
    def post(self):
        return {'info': 'Login Successfully!!'}

# 使用api.add_resource()添加路由
api.add_resource(IndexView, '/', endpoint='index')


if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行效果如下:
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第13张图片
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第14张图片

说明:

  1. 使用Flask-Restful自定义视图类需要继承Resource基类,使用post,get方法来指定HTTP请求类型
  2. api.add_resource()来实现视图类与url的映射。第一个参数为视图类对象;第二个参数为url;第三个参数为可选对象,相当于给视图类自定义一个别名,用于url_for()反向获取url,如果不指定则默认为视图类名称的小写字符串

13. Restful获取请求中的参数

在flask_restful中与之前一样,也是使用request.args来获取get请求中的参数,使用request.json来获取post请求中的json数据参数,使用request.form来获取post请求中的form数据

from flask import Flask, request
from flask_restful import Api , Resource

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)
api = Api(app)

# 视图类需要集成Resource
class IndexView(Resource):

    # get表示get请求
    def get(self):
        return {'args': request.args}

    # post表示post请求
    def post(self):
        return {'args': request.json}

# 使用api.add_resource()添加路由
api.add_resource(IndexView, '/', endpoint='index')


if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行后结果如下:

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第15张图片

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第16张图片

14.Flask-Restful对请求中的数据进行校验

Flask-Restful插件提供了类似WTForms来验证提交的数据是否合法的包,即reqparse
RequestParser对象的add_argument()方法可以指定字段的名字、数据类型等,常见的参数有:

  • default
    默认值,如果参数没有值,那么将使用这个参数指定的值。
  • required
    是否必须。
    默认为False;如果设置为True,则必须提交这个参数。
  • type
    参数的数据类型,如果指定,那么将使用指定的数据类型来强制转换提交得到的值。
  • choices
    选项,提交上来的值只有满足选项中的值才符合验证通过,否则验证不通过。
  • help
    错误信息,如果验证失败后,将会使用help参数指定的值作为错误信息。
  • trim
    是否要去掉前后的空格。
  • location
    请求的对象,要从(例如: location : args, form, json, headers, cookies等)中获取参数,可以是an迭代器,默认为(‘json’, ‘values’,)

练习如下:

from flask import Flask, request
from flask_restful import Api , Resource, reqparse, inputs

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)
api = Api(app)

# 视图类需要集成Resource
class IndexView(Resource):

    # get表示get请求
    def get(self):
        return {'args': request.args}

    # post表示post请求
    def post(self):
        parse = reqparse.RequestParser()
        parse.add_argument('username', type=str, help='用户名验证错误', required=True)
        parse.add_argument('password', type=str, help='密码验证错误', trim=True)
        parse.add_argument('age', type=int, help='年龄错误')
        parse.add_argument('gender', type=str, help='性别错误', choices=['male', 'female', 'secret'], default='secret')
        parse.add_argument('blog', type=inputs.url, help='博客地址错误')
        parse.add_argument('phone', type=inputs.regex(r'1[35789]\d{9}'), help='电话号码错误')

        args = parse.parse_args()
        return {'info': 'Register Successfully!!', 'args': args}

# 使用api.add_resource()添加路由
api.add_resource(IndexView, '/', endpoint='index')


if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

显示:
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第17张图片

15. Flask-Restful高级使用

1.自定义输出格式模板

flask_restful可以设置一个返回数据的json模板,只需要将数据传入到这个模板中就可以返回特定模式的数据
这需要导入flask_restful.marshal_with装饰器,在视图类中定义一个字典来指定需要返回的字段,以及该字段的数据类型。

测试如下:

from flask import Flask, request
from flask_restful import Api , Resource, reqparse, inputs,fields,marshal_with

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)
api = Api(app)

class IndexView(Resource):

    # 定义一个数据返回的模板,使用flask_restful.fields来指定数据的类型
    template_data = {
        'name': fields.String,  # 指定为一个字符串
        'age': fields.Integer,  # 指定为一个整数
        'vip': fields.Boolean  # 指定为一个布尔值
    }

    @marshal_with(template_data) # 将数据模板传入到装饰器中
    def get(self):
        return {'name': "zhangsan", "age":18}

# 使用api.add_resource()添加路由
api.add_resource(IndexView, '/', endpoint='index')

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行效果如下:

可以看到即便没传vip字段的,也会在网页中渲染该字段

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第18张图片

有时候想要在返回的数据格式中,返回列表或者字典
要在一个字段中放置一个列表,可以使用fields.List
在一个字段下面又是一个字典,可以使用fields.Nested

练习如下:

from flask import Flask, request
from flask_restful import Api , Resource, reqparse, inputs,fields,marshal_with

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)
api = Api(app)

class IndexView(Resource):

    # 定义一个数据返回的模板
    template_data = {
        'name': fields.String,  # 指定为一个字符串
        'age': fields.Integer,  # 指定为一个整数
        'hehe': fields.Nested({'dict1':fields.String,'dict2':fields.String}) # 指定一个字典
        # 'haha': fields.List,  # 指定一个列表
        # 'hehe': fields.Nested  # 指定一个字典
    }

    @marshal_with(template_data) # 将数据模板传入到装饰器中
    def get(self):
        return {'name': "zhangsan", "age":18, 'hehe':{'dict1':'ss11','dict2':'ddfff'}}

# 使用api.add_resource()添加路由
api.add_resource(IndexView, '/', endpoint='index')


if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行结果如下:

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第19张图片

16.flask中cookie和session的使用

16.1 设置cookie

在Flask中操作cookie,是通过Response对象来设置cookie,具体实现为在response返回之前,通过response.set_cookie()方法来设置,这个方法有以下几个参数:

  • key
    cookie的键
  • value
    cookie的键对应的值。
  • max_age
    cookie的过期时间,如果不设置,则浏览器关闭后就会自动过期。
  • expires
    过期时间,时间戳的形式(1970到现在的时间)。
  • domain
    该cookie在哪个域名中有效,一般设置子域名,比如cms.example.com。
  • path
    该cookie在哪个路径下有效,即当前主域名。

例如:

from flask import Flask, Response

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

@app.route('/set_cookie')
def set_cookie():
    res = Response('Start set cookie')
    res.set_cookie(key='username',value='zhangsan')
    return res

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行效果如下:

python自动化(七)自动化测试平台开发:3.flask技术讲解上_第20张图片

16.2 设置session

设置session

flask中的session机制是将内容加密后储存在cookie中。

from datetime import timedelta
from flask import Flask, session

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
    SECRET_KEY='asfdsdgfdhyfgjguygjk', # 设置一个密匙用于session的加密
    PERMANENT_SESSION_LIFETIME = timedelta(hours=2) # 设置session的过期时间
)

@app.route('/set_session')
def set_session():
    session.permanent = True # 设置session持久化,默认过期时间为30天
    session['username'] = 'zhangsan'
    return 'start set session'

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行结果如下:
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第21张图片

删除session

flask中可以使用session.clear()删除整个session,使用session.pop(key)删除指定的session值

17.flask的上下文

Flask项目中上下文包括两类:

  • 应用上下文
    用于记录和应用相关的数据,包括current_app和g。
  • 请求上下文
    用于记录和请求相关的数据,包括request和session。

请求上下文request和应用上下文current_app都是全局变量,所有请求都是共享的。
Flask中使用特殊的机制来保证每次请求的数据都是隔离的,即用户A请求所产生的数据不会影响到用户B的请求,所以直接导入request对象也不会被一些脏数据影响,并且不需要像Django一样在每个函数中使用request的时候传入request参数。
4类上下文对象如下:

  • request
    请求上下文对象,一般用来保存一些请求的变量,如method、args、form等。
  • session
    请求上下文对象,一般用来保存一些会话信息。
  • current_app
    应用上下文对象,返回当前的app。
  • g
    应用上下文对象,处理请求时用于临时存储的对象。

current_app使用示意如下:

from flask import Flask, current_app

app = Flask(__name__)


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


@app.route('/login/')
def login():
    ca = current_app.name
    cfg = current_app.config['DEBUG']
    return '登录页面:' + ca + '-' + str(cfg)


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

1234567891011121314151617181920

显示:
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第22张图片
显然,得到了当前所处的app,即初始化Flask对象时传入的参数__name__,也就是当前Python文件名;
除了获取当前app名称,还可以获取绑定到app中的配置参数。

注意:current_app只能在视图函数中使用,在视图函数之外使用会报错。

一般情况测试:
新建utils.py如下:

'''
工具文件
'''


def log_a(username):
    print("Log A %s" % username)


def log_b(username):
    print("Log B %s" % username)

主程序flask_context.py如下:

from flask import Flask, session
from utils import log_a, log_b
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)


@app.route('/')
def index():
    username = session.get('username')
    log_a(username)
    log_b(username)
    return '这是首页'


@app.route('/login/')
def login():
    session['username'] = 'Corley'
    session['user_id'] = 1
    return '登录页面'


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

1234567891011121314151617181920212223242526

运行后,先访问http://127.0.0.1:5000/,再访问http://127.0.0.1:5000/login/,再访问http://127.0.0.1:5000/,控制台打印如下:

127.0.0.1 - - [13/May/2020 11:57:59] "GET / HTTP/1.1" 200 -
Log A None
Log B None
127.0.0.1 - - [13/May/2020 11:58:14] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 11:58:23] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley

12345678

显然,在访问登录页面之后,控制台打印出了username信息。

此时使用g对象进行改进:
主程序文件修改如下:

from flask import Flask, current_app, g, session
from utils import log_a, log_b
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)


@app.route('/')
def index():
    g.username = session.get('username')
    log_a()
    log_b()
    return '这是首页'


@app.route('/login/')
def login():
    session['username'] = 'Corley'
    session['user_id'] = 1
    return '登录页面'


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

utils.py修改如下:

'''
工具文件
'''
from flask import g

def log_a():
    print("Log A %s" % g.username)


def log_b():
    print("Log B %s" % g.username)

123456789101112

运行之后,按之前的顺序访问,控制台打印:

127.0.0.1 - - [13/May/2020 12:04:34] "GET / HTTP/1.1" 200 -
Log A None
Log B None
127.0.0.1 - - [13/May/2020 12:04:42] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:04:52] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley

12345678

显然,达到了同样的效果,并且此时不需要再在log_a()log_b()函数中传递参数;
每次刷新页面后,g对象都会被重置,是临时存储的对象;
g对象一般用于解决频繁传参的问题。

可以在一个视图函数中实现调用另一个函数,如下:

from flask import Flask, current_app, g, session
from utils import log_a, log_b
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)


@app.route('/')
def index():
    g.username = session.get('username')
    log_a()
    log_b()
    hello()
    return '这是首页'


def hello():
    print('Hello %s' % g.username)


@app.route('/login/')
def login():
    session['username'] = 'Corley'
    session['user_id'] = 1
    return '登录页面'


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

按之前的顺序访问,控制台打印如下:

127.0.0.1 - - [13/May/2020 12:13:41] "GET / HTTP/1.1" 200 -
Log A None
Log B None
Hello None
127.0.0.1 - - [13/May/2020 12:13:48] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:13:52] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley
Hello Corley

18.常用的钩子函数

钩子函数是指在执行函数和目标函数之间挂载的函数,框架开发者给调用方提供一个point即挂载点,至于挂载什么函数由调用方决定, 从而大大提高了灵活性

1.before_first_request

第一次请求之前执行。
练习如下:

from flask import Flask

app = Flask(__name__)


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


@app.before_first_request
def handle_first_request():
    print('在第一次请求之前执行')


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

运行后,访问http://127.0.0.1:5000/,控制台打印:

127.0.0.1 - - [13/May/2020 12:39:01] "GET / HTTP/1.1" 200 -
在第一次请求之前执行
这是首页
123

2.before_request

在每次请求之前执行,通常可以用这个装饰器来给视图函数增加一些变量。

练习如下:

from flask import Flask

app = Flask(__name__)


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


@app.before_first_request
def handle_first_request():
    print('在第一次请求之前执行')


@app.before_request
def handle_before():
    print("在每一次请求之前执行")


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

运行后,多次访问http://127.0.0.1:5000/,控制台打印:

在第一次请求之前执行
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:36] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:38] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:39] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:40] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:41:41] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页

1234567891011121314151617

3.after_request

在每次请求之后执行。

练习如下:

from flask import Flask

app = Flask(__name__)


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


@app.before_first_request
def handle_first_request():
    print('在第一次请求之前执行')


@app.before_request
def handle_before():
    print("在每一次请求之前执行")


@app.after_request
def handle_after(response):
    print("在每一次请求之后执行")
    return response


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

运行后,多次访问http://127.0.0.1:5000/,控制台打印:

127.0.0.1 - - [13/May/2020 12:51:05] "GET / HTTP/1.1" 200 -
在第一次请求之前执行
在每一次请求之前执行
这是首页
在每一次请求之后执行
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:06] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:07] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:08] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:09] "GET / HTTP/1.1" 200 -

4.teardown_appcontext

不管是否有异常,注册的函数都会在每次请求之后执行。

练习如下:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    1/0
    print('这是首页')
    return '这是首页'


@app.before_first_request
def handle_first_request():
    print('在第一次请求之前执行')


@app.before_request
def handle_before():
    print("在每一次请求之前执行")


@app.after_request
def handle_after(response):
    print("在每一次请求之后执行")
    return response


@app.teardown_appcontext
def handle_teardown(response):
    print('teardown被执行')
    return response


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

运行后,访问http://127.0.0.1:5000/,显示:
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第23张图片
控制台打印:

在第一次请求之前执行
在每一次请求之前执行
在每一次请求之后执行
teardown被执行
[2020-05-13 12:58:48,206] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\_compat.py", line 39, in reraise
    raise value
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "XXXX\flask_hook.py", line 8, in index
    1/0
ZeroDivisionError: division by zero
127.0.0.1 - - [13/May/2020 12:58:48] "GET / HTTP/1.1" 500 -

1234567891011121314151617181920212223

显然, 在出现异常的情况下也执行了handle_teardown()函数;
虚造关闭Debug模式才能有明显的现象,在Debug模式下是不能执行handle_teardown()函数的。

5.context_processor

上下文处理器,返回的字典中的键可以在模板上下文中使用。

假如在多个视图函数中渲染模板时都需要传入同样的参数,flask_hook.py如下:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html', username='Corley')


@app.route('/list/')
def list():
    return render_template('list.html', username='Corley')


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

123456789101112131415161718

在模板目录templates中新建index.html和list.html,index.html如下:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页title>
head>
<body>
    <p>这是首页p>
    <p>{{ username }}p>
body>
html>
1234567891011

list.html如下:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表title>
head>
<body>
    <p>这是列表p>
    <p>{{ username }}p>
body>
html>
1234567891011

显示:
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第24张图片
显然,达到了效果,但是在每个视图函数中都要传入参数,很麻烦冗余,可以用context_processor装饰器定义钩子函数传递公共参数:

from flask import Flask, render_template

app = Flask(__name__)


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


@app.route('/list/')
def list():
    return render_template('list.html')


@app.context_processor
def context_process():
    return {'username': 'Corley'}


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

效果与之前一样;
此时在每个视图函数中不需要再传递参数,而是在context_processor装饰器定义的钩子函数中返回参数。

6.errorhandler

errorhandler接收状态码作为参数,可以自定义处理返回这个状态码的响应的方法。

测试如下:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    1/0
    return render_template('index.html')


@app.route('/list/')
def list():
    return render_template('list.html')


@app.context_processor
def context_process():
    return {'username': 'Corley'}


@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404


@app.errorhandler(500)
def server_error(error):
    return '

服务器内部错误...

'
, 500 if __name__ == '__main__': app.run() 12345678910111213141516171819202122232425262728293031323334

新建404.html如下:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面404title>
head>
<body>
    <p>页面跑到火星去了...p>
body>
html>
12345678910

显示:
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第25张图片
显然,捕获到了404和500错误。

还可以在视图函数中用abort()方法主动抛出异常,如下:

from flask import Flask, render_template, abort

app = Flask(__name__)


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


@app.route('/list/')
def list():
    abort(404)
    return render_template('list.html')


@app.context_processor
def context_process():
    return {'username': 'Corley'}


@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404


@app.errorhandler(500)
def server_error(error):
    return '

服务器内部错误...

'
, 500 if __name__ == '__main__': app.run()

显示:
python自动化(七)自动化测试平台开发:3.flask技术讲解上_第26张图片
显然,此时再访问http://127.0.0.1:5000/list/,就会报错;
因为在list()视图函数中使用sort()方法抛出404错误,被钩子函数page_not_found()捕获到,因此访问会出现404错误。

19.WTForms表单验证

  • 我们在视图函数中,往往要对请求中的参数进行校验,判断其是否合法。如果使用常规的if等判断语句会使得我们的代码十分冗余,尤其是当请求中的参数较多时。
  • 我们可以使用WTForms表单来校验请求参数的合法性。
  • flask-wtf是flask-wtf是一个简化了WTFForms操作的第三方库。
  • 安装方式为:pip install flask-wtf

19.1 基本使用

新建forms.py如下:

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo

class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])

新建wtf_demo.py如下:

from flask import Flask, request, render_template
from forms import RegisterForm

app = Flask(__name__)


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


@app.route('/register/', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    else:
        form = RegisterForm(request.form) # 生成一个form对象
        if form.validate():
            return '验证成功'
        else:
            return '验证失败:\n' + str(form.errors)


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

总结:

  • RegisterForm初始化时传入request.form(即需要校验的请求参数),并且根据form.validate()的值来判断用户提交的数据是否满足表单的验证。
  • RegisterForm初始化时传入request.form(即需要校验的请求参数),并且根据form.validate()的值来判断用户提交的数据是否满足表单的验证。
  • form.errors会返回错误提示,即表单校验时指定的message中的内容。
  • StringField表示校验的参数值的数据类型

19.2 Flask-WTF常用的验证器

(1)Email

可以直接使用Email类进行右键地址的验证。
在forms.py中新增LoginForm如下:

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo, Email

class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    email = StringField(validators=[Email(message='邮箱格式不正确')])

(2)Number

可以通过NumberRange类对数的范围进行验证。

class LoginForm(Form):
    age = IntegerField(validators=[NumberRange(1, 120, message='年龄范围有误!!!')])
  • IntegerField指定待校验的参数值类型为int

(3)是否必填

可以通过InputRequired类限制某些字段必填。

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo, InputRequired


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    username = StringField(validators=[InputRequired(message='用户名必填')])

(5)正则表达式

可以通过Regexp类自定义正则表达式进行验证。

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo, Regexp


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    phone_number = StringField(validators=[Regexp(r'1[35789]\d{9}', message='手机号码不正确')])

(5)URL

可以使用URL类验证某个字符串是否是标准的URL格式。

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo, URL


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    info_page = StringField(validators=[URL(message='地址格式不正确')])

(6)扩展-验证码的验证

要验证验证码长度和有效性,长度用Length验证,有效性可以在表单类中自定义方法即可。

forms.py修改如下:

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo, ValidationError


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    captcha = StringField(validators=[Length(min=4, max=4, message='验证码不正确')])

    def validate_captcha(self, field):
        '''自定义验证,方法名为固定格式,即validate_加要验证的变量名'''
        if field.data != '1234':
            raise ValidationError("验证码错误")
  • 可以得到,自定义验证需要在表单类中定义方法,方法名为固定格式,即validate_加要验证的变量名。验证失败的提示信息使用ValidationError(‘信息’)

(7)其他验证器

AnyOf 确保输入值在可选值列表中
NoneOf 确保输入值不在可选值列表中
FileRequired 确保文件是上传了的
FileAllowed(['jpg', 'png', 'gif']) 确保文件上传的类型

19.3 Flask-WTF中的常见数据类型

StringField 文本字段
TextAreaField 多行文本字段
PasswordField 密码文本字段
HiddenField 隐藏文本字段
DateField 文本字段,值为 datetime.date 格式
DateTimeField 文本字段,值为 datetime.datetime 格式
IntegerField 文本字段,值为整数
DecimalField 文本字段,值为 decimal.Decimal
FloatField 文本字段,值为浮点数
BooleanField 复选框,值为 True 和 False
RadioField 一组单选框
SelectField 下拉列表
SelectMultipleField 下拉列表,可选择多个值
FileField 文件上传字段
SubmitField 表单提交按钮
FormField 把表单作为字段嵌入另一个表单
FieldList 一组指定类型的字段

你可能感兴趣的:(python自动化,python,flask,软件测试)