木心

毕竟几人真得鹿,不知终日梦为鱼

导航

flask框架

1、flask安装配置
    1.1、认识flask
    1.2、使用虚拟环境
    1.3、第一个flask程序
2、app对象的初始化和配置
3、视图函数的路由
    3.1、视图函数的路由规则设置说明
    3.2、路由提取参数与自定义路由转换器
4、request对象
    4.1、request.form,request.args,request.data获取请求数据
    4.2、上传文件
5、abort函数、自定义错误、 视图函数的返回值、返回json数据
6、cookie和session
7、flask上下文、请求钩子、flask_script脚本扩展
8、模板Jinja2
9、数据库ORM框架flask-sqlalchemy
10、数据库迁移扩展包Flask-Migrate
11、使用蓝图划分模块
12、单元测试
13、flask部署

1、flask安装配置    <--返回目录

1.1、认识flask    <--返回目录

  web框架:能够被服务器调用,根据客户端的不同请求执行不同的逻辑处理形成要返回的数据的程序。核心是实现路由和视图(业务逻辑处理)

  重量级的框架:为方便业务程序的开发,提供了丰富的工具、组件,如Django;轻量级的框架:只提供web框架的核心功能,自由、灵活、高度定制,如Flask、Tornado。

  Flask诞生于2010年,是Armin ronacher用python语言基于Werkzeug工具箱编写的轻量级web开发框架,主要面向需求简单的小应用。Flask本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login),都需要用第三方的扩展来实现。比如可以用Flask-extension加入ORM、窗体验证工具,文件上传、身份验证等。Flask没有默认使用的数据库,你可以选择MySQL,也可以用NoSQL。其WSGI工具箱采用Werkzeug(路由模块),模板引擎则使用Jinja2。可以说Flask框架的核心就是Werkzeug和Jinja2。

  与Django对比,Django提供了:djaingo-admin快速创建项目工程目录,manage.py管理项目工程,orm模型(数据库抽象层),admin后台管理站点,缓存机制,文件存储系统,用户认证系统。而这些,flask都没有,需要扩展包来提供。

  Flask扩展包:Flask-SQLalchemy 操作数据库,Flask-migrate 管理迁移数据库,Flask-Mail 邮件,Flask-WTF 表单,Flask-script 插入脚本,Flask-Login 认证用户状态,Flask-RESTful 开发REST API的工具,Flask-Bootstrap 集成前端Twitter Bootstrap框架,Flask-Moment 本地化日期和时间。

  flask官网文档:https://flask.palletsprojects.com/en/2.0.x/

  中文文档:https://dormousehole.readthedocs.io/en/latest/

 

1.2、使用虚拟环境    <--返回目录

  参考:python虚拟环境virtualenv,在vscode中启用virtualenv

 

1.3、第一个flask程序    <--返回目录

# coding:utf-8
from flask import Flask

# 创建flask的应用对象
# 第一个参数 __name__ 表示当前的模块名字, flask以这个模块所在目录为根目录,默认这个目录中的static为静态目录,templates为模板目录
app = Flask(__name__)

# 定义视图函数
@app.route("/index")
def index():
    return "hello flask"

if __name__ == "__main__":
    # 启动flask程序
    app.run(port=8080)

 

  app.run()默认启动5000端口。最好指定端口,否则容易出现端口冲突报错:OSError: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。

  查看5000端口是否被占用

 

2、app对象的初始化和配置    <--返回目录

  flask应用对象(app对象)初始化参数说明

# 创建flask的应用对象
# 第一个参数 __name__ 表示当前的模块名字, flask以这个模块所在目录为根目录,默认这个目录中的static为静态目录,templates为模板目录
app = Flask(__name__,
            static_url_path="/static", # 访问静态资源的url前缀,默认值为static
            static_folder="static", # 静态文件的目录,默认为static
            template_folder="templates" # 模板文件的目录,默认为templates
            )

  在项目根目录下创建static目录,并新建Index.html,使用 http://127.0.0.1:8080/static/index.html 访问。

 

  flask的配置参数

# coding:utf-8
from flask import Flask # 创建flask的应用对象 app = Flask(__name__) # 配置参数的使用方式 # 方式1、使用配置文件 #app.config.from_pyfile("config.cfg") # 方式2、使用对象配置参数 # class MyConfig(object): # DEBUG = True # app.config.from_object(MyConfig) # 方式3、直接操作config的字典对象 app.config["DEBUG"] = True # 定义视图函数 @app.route("/index") def index(): num = 1 / 0 return "hello flask" if __name__ == "__main__": # 启动flask程序 app.run(port=8080)

 

  读取配置参数的方式

# coding:utf-8
from flask import Flask, current_app

# 创建flask的应用对象
app = Flask(__name__)

# 配置参数
class MyConfig(object):
    DEBUG = True
    MY_NAME = "test"
app.config.from_object(MyConfig)

# 定义视图函数
@app.route("/index")
def index():
    # 读取配置参数
    # 方式1、直接从全局对象app的config字典中取值
    # my_name = app.config.get("MY_NAME")
    # return my_name
    # 方式2、使用app的代理对象 current_app
    my_name = current_app.config.get("MY_NAME")
    return my_name

if __name__ == "__main__":
    # 启动flask程序
    app.run(port=8080)

  

  app的run使用说明

  配置参数 host="0.0.0.0":除了 127.0.0.1/localhost 可以访问,也可以使用本地地址 http://172.20.xxx.xxx:8080/

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

 

3、视图函数的路由    <--返回目录

3.1、视图函数的路由规则设置说明    <--返回目录

  通过url_map可以查看整个flask程序中的路由信息

if __name__ == "__main__":
    # 通过url_map可以查看整个flask程序中的路由信息
    print("\n url_map = {0} \n".format(app.url_map))
    app.run(host="0.0.0.0", port=8080, debug=True)

  @app.route("/post_only", methods = ["post"]) 可以指定使用post方式访问,不指定默认为get

# coding:utf-8
from flask import Flask

app = Flask(__name__)

# 通过methods限定访问方式
@app.route("/post_only", methods = ["post"])
def post_only():
    return "post only"

@app.route("/index")
def index():
    return "hello python get"

# 路由相同, 但是methods不同
@app.route("/index", methods = ["post"])
def index_post():
    return "hello python post"

if __name__ == "__main__":
    # 通过url_map可以查看整个flask程序中的路由信息
    print("\n url_map = {0} \n".format(app.url_map))
    app.run(host="0.0.0.0", port=8080, debug=True)

 

  视图的重定向 url_for

# coding:utf-8
from flask import Flask, redirect, url_for

app = Flask(__name__)

@app.route("/")
def index():
    return "index page"

@app.route("/login")
def login():
    # 重定向到主页
    # 方式1、redirect直接重定向到指定路由地址
    # url = "/"
    # return redirect(url)
    # 方式2、url_for 通过视图函数名来 反查 路由地址
    url = url_for("index") # 这里写视图函数的名称
    return redirect(url)

if __name__ == "__main__":
    # 通过url_map可以查看整个flask程序中的路由信息
    print("\n url_map = {0} \n".format(app.url_map))
    app.run(host="0.0.0.0", port=8080, debug=True)

 

3.2、路由提取参数与自定义路由转换器    <--返回目录

  从请求参数中提取参数,flask已经实现了三种转换器 int, float, string,不写转换器名,默认使用string

# coding:utf-8
from flask import Flask

app = Flask(__name__)

@app.route("/goods/<int:id>")
def goods_detail1(id):
    return "goods id: {0}".format(id)

@app.route("/goods/<float:money>")
def goods_detail2(money):
    return "goods money: {0}".format(money)

@app.route("/goods/<string:name>")
def goods_detail3(name):
    return "goods name: {0}".format(name)

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

  请求 http://172.20.158.97:8080/goods/123,http://172.20.158.97:8080/goods/1.23,http://172.20.158.97:8080/goods/abc测试。

 

   自定义路由转换器

# coding:utf-8
from flask import Flask
from werkzeug.routing import BaseConverter

app = Flask(__name__)

# 1、自定义路由转换器
class MyRegexConverter(BaseConverter):
    # flask调用自定义的路由转换器使,会将 app.url_map 传进来
    def __init__(self, url_map, regex):
        # 调用父类的初始化方法
        # super(MyRegexConverter, self).__init__(url_map) # python2
        super().__init__(url_map)
        # 将正则表达式的参数保存到对象的属性中,flask会使用这个属性来进行路由的正则匹配
        self.regex = regex

# 2、将自定义的转换器添加到flask应用中
app.url_map.converters["re"] = MyRegexConverter

# 3、使用自定义的转换器
@app.route("/send/<re(r'1[34578]\d{9}'):mobile_number>")
def send_sms(mobile_number):
    return "send sms to: {0}".format(mobile_number)

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

  测试 http://172.20.158.97:8080/send/13512341234,http://172.20.158.97:8080/send/135abc

 

4、request对象    <--返回目录

  request常用属性

 

4.1、request.form,request.args,request.data获取请求数据    <--返回目录

  request.form 获取表单格式数据,request.args 获取 url 后面带的 "?key=xxx" 数据(查询字符串QueryString)

# coding:utf-8
from flask import Flask, request

app = Flask(__name__)

@app.route("/index", methods = ["get", "post"])
def index():
    name = request.form.get("name") # 请求body中表单格式的数据 name=xxx&age=xxx
    age = request.args.get("age")   # 请求url中"?age=xxx"的数据
    return "hello python, name: {0}, age: {1}".format(name, age)

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

 

  前端发送 json 格式数据,flask 中使用 request.data 接收

# coding:utf-8
from flask import Flask, request

app = Flask(__name__)

@app.route("/index", methods = ["get", "post"])
def index():
    name = request.form.get("name") # 请求body中表单格式的数据 name=xxx&age=xxx
    age = request.args.get("age") # 请求url中"?age=xxx"的数据

    # 打印结果 request.data: b'{"name": "aaa","age": 20}'
    print("\n request.data: {0} \n".format(request.data))
    
    return "hello python, name: {0}, age: {1}".format(name, age)

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

 

  request.form.getlist 获取多个同名key的值

@app.route("/index", methods = ["get", "post"])
def index():
    name = request.form.getlist("name") # 请求body中表单格式的数据 name=aaa&name=bbb
    print(type(name)) # <class 'list'>
    return "hello python, name: {0}".format(name)

 

4.2、上传文件    <--返回目录

# coding:utf-8
from flask import Flask, request

app = Flask(__name__)

@app.route("/upload", methods = ["post"])
def upload():
    pic_file = request.files.get("pic")
    if pic_file is None:
        return "未上传文件"

    # f = open("./test.jpg", "wb") # 创建一个文件
    # data = pic_file.read()
    # f.write(data)
    # f.close()

    pic_file.save("./demo.jpg")
    return "上传成功"

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

 

5、abort函数、自定义错误、 视图函数的返回值、返回json数据    <--返回目录

  abort函数的使用:使用abort函数可以立即终止视图函数的执行,并可以返回给前端特定的信息

  测试 127.0.0.1:8080/login?name=admin&pwd=admin1

# coding:utf-8
from flask import Flask, request, abort, Response

app = Flask(__name__)

@app.route("/login", methods = ["get", "post"])
def login():
    name = request.args.get("name")
    pwd = request.args.get("pwd")
    if check_name_pwd(name, pwd):
        return "login success"
    # 用户名和密码校验失败
    # 使用abort函数可以立即终止视图函数的执行,并可以返回给前端特定的信息
    # 1、传递状态码,必须是标准的http状态码
    # abort(403)
    # 2、传递响应体信息
    resp = Response("login failed")
    abort(resp)

def check_name_pwd(name, pwd):
    if (name != "admin" or pwd != "admin"):
        return False
    return True

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

  自定义错误处理方法:@app.errorhandler(403),测试 127.0.0.1:8080/login?name=admin&pwd=admin1

# coding:utf-8
from flask import Flask, request, abort, Response

app = Flask(__name__)

@app.route("/login", methods = ["get", "post"])
def index():
    name = request.args.get("name")
    pwd = request.args.get("pwd")
    if check_name_pwd(name, pwd):
        return "login success"
    # 用户名和密码校验失败
    # 使用abort函数可以立即终止视图函数的执行,并可以返回给前端特定的信息
    abort(403)

def check_name_pwd(name, pwd):
    if (name != "admin" or pwd != "admin"):
        return False
    return True

# 定义错误处理方法
@app.errorhandler(403)
def handle_403_error(err):
    """自定义的处理403状态码错误的方法"""
    # 这个函数的返回值是前端用户最终看到的结果
    return "出现了403错误, 错误信息: {0}".format(err)

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

  设置响应信息的方法

# coding:utf-8
from flask import Flask, request, make_response

app = Flask(__name__)

@app.route("/index", methods = ["get", "post"])
def index():
    # 1、使用元组,返回自定义的响应信息
    #       响应体       状态码  响应头
    # return "index page", 403, [("myhead1", "aaa"), ("myhead2", "bbb")]
    # return "index page", 666, {"myhead1": "aaa", "myhead2": "bbb"} # 状态码可以不是标准的http状态码,响应头可以是dict
    # return "index page", "666 are you ok", {"myhead1": "aaa", "myhead2": "bbb"} # 状态码可以为字符串
    # return "index page", "666 are you ok" # 可以不传响应头

    # 2、使用 make_response 来构造响应信息
    resp = make_response(" index page2")
    resp.status = "666 are you ok"
    resp.headers["myhead3"] = "ccc"
    return resp

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

  返回json数据的方法

# coding:utf-8
from flask import Flask, jsonify
import json

app = Flask(__name__)

@app.route("/index", methods = ["get", "post"])
def index():
    resp_data = {
        "name": "xxx",
        "age": 10
    }
    # json.dumps(字典)     将python的字典转换为json字符串
    # json.loads(字符串)   将字符串转换为python中的字典
    # return json.dumps(resp_data), 200, {"Content-Type": "application/json"}

    # jsonify 帮助转为json数据,并设置响应头 Content-Type 为 application/json
    return jsonify(city = "xxx", country = "yyy")
    # return jsonify(resp_data)

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

 

6、cookie和session    <--返回目录

  cookie的使用

# coding:utf-8
from flask import Flask, make_response, request

app = Flask(__name__)

@app.route("/set_cookie", methods = ["get", "post"])
def set_cookie():
    resp = make_response("success")
    # 设置cookie, 默认有效期是临时cookie,浏览器关闭就失效
    resp.set_cookie("cookie1", "xxx")
    resp.set_cookie("cookie2", "yyy", max_age=3600) # max_age设置有效期,单位秒
    return resp

@app.route("/get_cookie")
def get_cookie():
    return request.cookies.get("cookie1")
    
@app.route("/del_cookie")
def del_cookie():
    resp = make_response("del success")
    resp.delete_cookie("cookie2")
    return resp

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

  session的使用:flask默认将session数据保存到cookie中。所以需要一个SECRET_KEY用来加密数据

# coding:utf-8
from flask import Flask, session

app = Flask(__name__)
app.config["SECRET_KEY"] = "TEST_SECRET_KEY"

@app.route("/login", methods = ["get", "post"])
def login():
    # 设置session数据
    session["mobile"] = "13512341234"
    return "login success"

@app.route("/index")
def index():
    # 获取session数据
    return session.get("mobile")

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

  请求/login后,后台往session中设值,默认保存到cooke中。下图中名为session的cookie除了保存session_id还保存了"mobile"的值。

 

7、flask上下文、请求钩子、flask_script脚本扩展    <--返回目录

  请求钩子:是通过装饰器的形式实现

# coding:utf-8
from flask import Flask, request

app = Flask(__name__)

@app.route("/index", methods = ["get", "post"])
def index():
    print("执行index")
    return "index page"

@app.before_first_request
def handle_before_first_request():
    """在第一次请求处理之前被执行"""
    print("handle_before_first_request 被执行")

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

@app.after_request
def handle_after_request(response):
    """在每次请求(视图函数处理)之后被执行,前提是视图函数没有出现异常"""
    print("handle_after_request 被执行")
    return response

@app.teardown_request
def handle_teardown_request(response):
    """在每次请求(视图函数处理)之后被执行,无论视图函数是否出现异常,都被执行"""
    path = request.path
    if (path == "/favicon.ico"):
        print("")
    print("handle_teardown_request 被执行")
    return response

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

 

  请求上下文 request context:request 和 session 都属于请求上下文对象

  应用上下文 application context:current_app 和 g 都属于应用上下文对象

  current_app: 表示当前运行程序的实例。g: 处理请求时,用于临时存储的对象,每次请求都会重设这个变量。

  测试 g 对象的使用:1)首先请求 http://127.0.0.1:8080/index,再次请求 http://127.0.0.1:8080/test

# coding:utf-8
from flask import Flask, g

app = Flask(__name__)

@app.route("/index", methods = ["get", "post"])
def index():
    g.name = "xxx"
    fun()
    return "index page"

@app.route("/test")
def test():
    fun()
    return "index page"

# 请求 "/index" 时,往 g 对象中设置了name属性的值,调用fun()可以从中获取属性 name 的值;
# 请求 "/test" 时,由于每次请求都重置 g 对象,所以此时 g 变量没有属性name
def fun():
    name = g.get("name")
    print("fun g: {0}".format(name))

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

 

  flask_script脚本扩展的使用

  安装

pip install Flask-Script

  manager.run()

# coding:utf-8
from flask import Flask
from flask_script import Manager

app = Flask(__name__)
# 创建Manager管理类的对象
manager = Manager(app)

@app.route("/index", methods = ["get", "post"])
def index():
    return "index page"

if __name__ == "__main__":
    # app.run(host="0.0.0.0", port=8080, debug=True)
    manager.run()

  runserver: Runs the Flask development server i.e. app.run()

python first.py runserver --help

Runs the Flask development server i.e. app.run()

optional arguments:
  -?, --help            show this help message and exit
  -h HOST, --host HOST
  -p PORT, --port PORT
  --threaded
  --processes PROCESSES
  --passthrough-errors
  -d, --debug           enable the Werkzeug debugger (DO NOT use in production code)
  -D, --no-debug        disable the Werkzeug debugger
  -r, --reload          monitor Python files for changes (not 100% safe for production use)
  -R, --no-reload       do not monitor Python files for changes
  --ssl-crt SSL_CRT     Path to ssl certificate
  --ssl-key SSL_KEY     Path to ssl key

  shell: Runs a Python shell inside Flask application context.

(myenv1) PS D:\hello> python .\first.py shell
>>> app.url_map
Map([<Rule '/index' (POST, HEAD, OPTIONS, GET) -> index>,
 <Rule '/static/<filename>' (HEAD, GET, OPTIONS) -> static>])
>>>exit()

 

8、模板Jinja2    <--返回目录

flask框架--模板引擎Jinja2

 

9、数据库ORM框架flask-sqlalchemy    <--返回目录

flask框架--数据库ORM框架flask-sqlalchemy

 

10、数据库迁移扩展包Flask-Migrate    <--返回目录

  在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。在Flask中可以使用flask-migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。为了导出数据库迁移命令,Flask-Migrate提供了一个migrateCommand类,可以附加到flask-script的manager对象上。

  首先在虚拟环境中安装Flask-Migrate:

pip install flask-migrate==2.7.0

  代码中 导入flask-migrate的使用:

from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand

# 创建flask脚本管理工具对象
manager = Manager(app)
# 创建数据库迁移工具对象
Migrate(app, db)
# 向manager对象中添加数据库的操作命令
manager.add_command("db", MigrateCommand)

if __name__ == "__main__":
    # pre_insert_data()
    manager.run()

  因此,前面的图书案例代码可以改造为(只要关注红色标注的代码,由于图书案例代码过多,这里不好全部贴出,删减了部分代码):

# coding:utf-8
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand

app = Flask(__name__)
app.config["SECRET_KEY"] = "TEST_SECRET_KEY"

# sqlalchemy 的配置
app.config["SECRET_KEY"] = "TEST_SECRET_KEY"
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:123456@127.0.0.1:3306/db1"
# 如果设置成 True (默认情况),Flask-SQLAlchemy 将会追踪对象的修改并且发送信号。这需要额外的内存, 如果不必要的可以禁用它。
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
# 查询时显示原始SQL语句
app.config["SQLALCHEMY_ECHO"] = True
db = SQLAlchemy(app)

# 创建flask脚本管理工具对象
manager = Manager(app)
# 创建数据库迁移工具对象
Migrate(app, db)
# 向manager对象中添加数据库的操作命令
manager.add_command("db", MigrateCommand)

# 创建模型类
class Author(db.Model):
    __tablename__ = "tb_author"
    id = db.Column(db.Integer, primary_key = True) # 整形的主键,默认设置为自增主键
    name = db.Column(db.String(32), unique =True)
    
    books = db.relationship("Book", backref = "author")

class Book(db.Model):
    __tablename__ = "tb_book"
    id = db.Column(db.Integer, primary_key = True) # 整形的主键,默认设置为自增主键
    name = db.Column(db.String(64), unique = True)
    author_id = db.Column(db.Integer, db.ForeignKey("tb_author.id")) # 外键,存储tb_author表的idif __name__ == "__main__":
    manager.run()

 

  使用脚本命令

  初始化 python db_demo.py db init,生成migrate目录

(myenv1) PS E:\pycode> python db_demo.py db init       
Creating directory E:\pycode\migrations ...  done
Creating directory E:\pycode\migrations\versions ...  done
Generating E:\pycode\migrations\alembic.ini ...  done
Generating E:\pycode\migrations\env.py ...  done
Generating E:\pycode\migrations\README ...  done
Generating E:\pycode\migrations\script.py.mako ...  done
Please edit configuration/connection/logging settings in 'E:\\pycode\\migrations\\alembic.ini' before proceeding.
(myenv1) PS E:\pycode> 

 

   生成迁移脚本 python db_demo.py db migrate,如果没有改变模型类的字段

No changes in schema detected.

  下面在Auhor模型类中添加字段 email = db.Column(db.String(64), unique =True),会在 migrates/version中生成脚本

INFO  [alembic.autogenerate.compare] Detected added unique constraint 'None' on '['email']'
Generating E:\pycode\migrations\versions\6cfbd11eaad4_.py ...  done

  查看下该升级脚本:

from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = '6cfbd11eaad4'
down_revision = None
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('tb_author', sa.Column('email', sa.String(length=64), nullable=True))
    op.create_unique_constraint(None, 'tb_author', ['email'])
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_constraint(None, 'tb_author', type_='unique')
    op.drop_column('tb_author', 'email')
    # ### end Alembic commands ###

  执行升级命令 python db_demo.py db upgrade 【-m "备注信息"】

  alembic_version表由flask_migrate自动创建,并且版本号为刚才升级后所处的版本号。

 

  再次在Author模型类中添加字段 msg = db.Column(db.String(64))。执行 migrate和upgrade指令。然后执行 python db_demo.py db history 可以查看升级历史

(myenv1) PS E:\pycode> python db_demo.py db history
6cfbd11eaad4 -> 51ad629fac1b (head), empty message
<base> -> 6cfbd11eaad4, empty message

  回退 python db_demo.py db downgrade 6cfbd11eaad4

 

11、使用蓝图划分模块    <--返回目录

flask项目之模块划分

 

12、单元测试    <--返回目录

   视图函数的单元测试

# main.py
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/login", methods=["get", "post"])
def login():
    user_name = request.form.get("user_name")
    password = request.form.get("password")

    if not all([user_name, password]):
        resp = {"code": 1, "msg": "invalid params"}
        return jsonify(resp)

    if user_name == "admin" and password == "admin":
        resp = {"code": 0, "msg": "login succ"}
        return jsonify(resp)
    else:
        resp = {"code": 2, "msg": "wrong username or password"}
        return jsonify(resp)

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


# test.py
import unittest
from main import app
import json

class LoginTest(unittest.TestCase):
    """构造单元测试用例"""

    def setUp(self):
        # 创建进行web请求的客户端,使用flask提供的
        self.client = app.test_client()
        # 设置flask工作在测试模式下
        # app.config["TESTING"] = True
        app.testing = True

    def test_empty_username_password(self):
        """测试用户名和密码不完整的情况"""
        ret = self.client.post("/login", data={})
        # ret 是视图返回的响应对象,data属性是响应体的数据
        resp = json.loads(ret.data) # loads将json字符串转成字典

        self.assertIn("code", resp) # 断言有code这个键
        self.assertEqual(resp["code"], 1)

    def test_right_username_password(self):
        ret = self.client.post("/login", data={"user_name":"admin", "password":"admin"})
        # ret 是视图返回的响应对象,data属性是响应体的数据
        resp = json.loads(ret.data) # loads将json字符串转成字典

        self.assertIn("code", resp) # 断言有code这个键
        self.assertEqual(resp["code"], 0)
        
if __name__ == "__main__":
    unittest.main()
View Code

  数据库测试

import unittest
from author_book import Author, db, app

class LoginTest(unittest.TestCase):
    def setUp(self):
        # 创建进行web请求的客户端,使用flask提供的
        self.client = app.test_client()
        # 设置flask工作在测试模式下
        app.testing = True
        app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:passwrod@127.0.0.1:3306/db1"
        db.create_all()

    def test_add_author(self):
        author = Author(name="aaa", mobile="xxxx")
        db.session.add(author)
        db.session.commit()

        result = Author.query.filter_by("aaa").first()
        self.assertIsNotNone(result)

    def test_delete_author(self):
        pass
        
    def tearDown(self):
        db.session.remove()
        db.drop_all()

if __name__ == "__main__":
    unittest.main()
View Code

 

13、flask部署    <--返回目录

   当我们开发时使用flask自带的服务器,完成了web服务的启动。在生产环境中,flask自带的服务器无法满足性能要求,我们这里采用Gunicorn做wsgi容器,来部署flask程序。Gunicorn(绿色独角兽)是一个Python WSGI 的HTTP服务器。从Ruby的独角兽(Unicorn)项目移植。该Gunicorn服务器与各种web框架兼容,实现非常简单,轻量级的资源消耗。Gunicorn直接用命令启动,不需要编写配置文件,相对uWSGI要容易很多。

  WSGI: Web Server Gatewary Interface(web服务器网关接口),它是一种规范,是web服务器和web应用程序之间的接口。它的作用就像桥梁,连接在web服务器和web应用框架之间。

  uwsgi: 是一种传输协议,用于定义传输信息的类型。

  uWSGI: 是实现了uwsgi协议WSGI的web服务器。

  我们的部署方式:nginx + gunicorn + flask程序。

posted on 2022-02-11 09:25  wenbin_ouyang  阅读(198)  评论(0编辑  收藏  举报