Flask框架学习笔记_3
发送邮件
-
整个流程与Django类似,需要用到Flask—Mail
-
安装:
pip install Flask-Mail
-
示例
from flask import Flask from flask_mail import Mail, Message app = Flask(__name__) #配置邮件:服务器/端口/传输层安全协议/邮箱名/密码 app.config.update( DEBUG = True, MAIL_SERVER='smtp.qq.com', MAIL_PROT=465, MAIL_USE_TLS = True, MAIL_USERNAME = 'xxxxx@qq.com', MAIL_PASSWORD = 'xxxx', ) mail = Mail(app) @app.route('/') def index(): # sender 发送方,recipients 接收方列表 msg = Message("This is a test ",sender='xxxxx@qq.com', recipients=['xxx@163.com','xxxxx@qq.com']) #邮件内容 msg.body = "Flask test mail" #发送邮件 mail.send(msg) print "Mail sent" return "Sent Succeed" if __name__ == "__main__": app.run()
蓝图
- 我们在单个文件中可以定义路由、视图函数、定义模型等等,但随着功能的增加,这不仅会让代码阅读变得困难,而且会给后期维护带来麻烦
- 在Flask中,
app = Flask(__name__)
的app与Django中的app并不是同个概念,它其实相当于的Django中的project,而蓝图简单来说就是用于实现单个应用的视图、模块文件、静态文件的集合
-
我们可以像Django一样,把main.py中关于各模块的视图函数等文件都分别放进对应的应用模块的文件夹中
-
然后在main.py中,导入对应的视图函数
from orders import get_order from users import register
-
但运行时,你会看到一个不能导入的异常错误
-
其原因其实类似于进程中的死锁,在main.py中,执行导入时,它会去对应的模块寻找视图函数,找到后,对应的模块又会执行,而模块里又需要从mian.py导入app对象,这样两个文件的执行都相当于卡在了开头,谁都执行不下去,僵持住了,这就是循环导入的问题
-
其一种解决方法就是错开,推迟一方的导入,让另一方先完成
-
用装饰器来解决循环导入的问题
-
装饰器除了在函数定义前用,也可以在函数定义后用
@app.route('/get_goods)') def get_goods(): pass ###################### def get_goods(): pass app.route('/get_goods)')(get_goods)
app.route('/get_goods)')
相当于一个函数,要被定义的函数可以当作参数传给它这时,最后一条语句就可以扔进主程序了,对应的模块不需要导入app,循环导入的问题自然就解决了
-
蓝图的使用
-
用上面的方法虽然解决了循环导入的问题,但是模块与主程序的耦合度还是相当的高
-
这时就可以使用蓝图来解决问题
-
使用
-
创建蓝图对象及视图函数
from flask import Blueprint # 1. 创建蓝图对象 app_goods = Blueprint("goods", __name__) # goods为自定义的名字 # 2. 创建蓝图的视图函数 @app_goods.route("/get_goods") def get_goods(): return "get goods page"
-
在主程序中注册蓝图
# 注册蓝图 from goods import app_goods app.register_blueprint(app_goods)
-
-
以目录的形式定义蓝图
-
在Django中,每个页面可能会有一个前缀,如:
/goods/get_goods
,在Flask中也可以实现,只要在注册时加多一个参数url_prefix
app.register_blueprint(app_goods, url_prefix="/goods")
-
-
这时,方便代码的管理与维护,我们可以在项目中为每个应用模块单独创建一个应用包,其中可以存放该应用模块的模板文件、静态文件、视图函数等
-
在包的
__init__.py
文件中from flask import Blueprint # 创建蓝图 app_goods = Blueprint("goods", __name__) # 在__init__.py被执行时,把视图函数也加载进来,让蓝图与应用程序知道有视图函数的存在,让其产生关联 from .views import get_goods
-
这时,在主程序注册蓝图导入模块时,就应该是从应用包中导入
from goods import app_goods
-
-
蓝图模板目录的处理
-
这时如果把模板文件放在应用模块文件下的模板文件夹中,是无法让视图函数找到的,需要在
__init__.py
中注册时指定template_folderapp_goods = Blueprint("goods", __name__, template_folder="templates")
-
这时,视图函数寻找模板文件时的顺序为:项目目录中的模板文件夹>蓝图中的模板文件夹
-
单元测试
-
单元测试的相当于与一些**断言(assert)**代码
-
assert后面是一个表达式,如果返回真,则断言成功,程序继续执行,如果返回假,则断言失败,抛出AssertionError异常,程序中止
-
测试其实有点像爬虫,让测试程序向程序的接口发送请求,然后看其返回的数据是否与预期的一致
-
常用的断言方法
断言 作用 assertEqual 如果两个值相等,则pass assertNotEqual 如果两个值不相等,则pass assertTrue 判断bool值为True,则pass assertFalse 判断bool值为False,则pass assertIsNone 不存在,则pass assertIsNotNone 存在,则pass -
单元测试的基本写法
-
新建一个文件来写测试程序,在该文件中定义一个测试类,继承于
unittest.TestCase
import unittest class TestClass(unitest.TestCase): '''测试xxx''' def test_xxx(self): # 写测试代码的方法一定要以test开头,否则不会被识别 pass if __name__ == '__main__': unittest.main() # 用unittest来启动
-
-
setUp()
与tearDown()
- 在测试类中,有两个特殊的方法
def setUp(self)
:该函数会在进行测试前先被执行,可以把初始化的东西扔进去,如:client = app.test_client()
def tearDown(self)
:该函数会在测试代码执行完成后被执行
- 在测试类中,有两个特殊的方法
几个测试案例
-
登录测试案例
import unittest from login import app import json class TestLogin(unittest.TestCase): """定义测试案例""" def setUp(self): """在执行具体的测试方法前,先被调用""" # 可以使用python的http标准客户端进行测试 # urllib urllib2 requests # 使用flask提供的测试客户端进行测试 self.client = app.test_client() def test_empty_name_password(self): """测试模拟场景,用户名或密码不完整""" # 使用客户端向后端发送post请求, data指明发送的数据,会返回一个响应对象 response = self.client.post("/login", data={}) # respoonse.data是响应体数据 resp_json = response.data # 按照json解析 resp_dict = json.loads(resp_json) # 使用断言进行验证 self.assertIn("code", resp_dict) code = resp_dict.get("code") self.assertEqual(code, 1) # 测试只传name response = self.client.post("/login", data={"name": "admin"}) # respoonse.data是响应体数据 resp_json = response.data # 按照json解析 resp_dict = json.loads(resp_json) # 使用断言进行验证 self.assertIn("code", resp_dict) code = resp_dict.get("code") self.assertEqual(code, 1) def test_wrong_name_password(self): """测试用户名或密码错误""" # 使用客户端向后端发送post请求, data指明发送的数据,会返回一个响应对象 response = self.client.post("/login", data={"name": "admin", "password": "python"}) # respoonse.data是响应体数据 resp_json = response.data # 按照json解析 resp_dict = json.loads(resp_json) # 使用断言进行验证 self.assertIn("code", resp_dict) code = resp_dict.get("code") self.assertEqual(code, 2) if __name__ == '__main__': unittest.main()
-
发送邮件测试
import unittest from apps import app class TestCase(unittest.TestCase): # 创建测试环境,在测试代码执行前执行 def setUp(self): self.app = app # 激活测试标志 app.config['TESTING'] = True self.client = self.app.test_client() # 在测试代码执行完成后执行 def tearDown(self): pass # 测试代码 def test_email(self): resp = self.client.get('/') print resp.data self.assertEqual(resp.data,'Sent Succeed')
-
数据库测试
import unittest from author_book import Author, db, app class TestDatabase(unittest.TestCase): """测试数据库的案例""" def setUp(self): app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:mysql@127.0.0.1:3306/flask_test" db.drop_all() db.create_all() def test_author(self): """测试添加作者的案例""" author = Author(name="itcast", email="itcast@itcast.cn") db.session.add(author) db.session.commit() ret_author = Author.query.filter_by(name="itcast").first() self.assertIsNotNone(ret_author) self.assertEqual(ret_author.name, "itcast") def tearDown(self): """在所有测试方法执行后,被调用""" # 清除记录的测试任务 db.session.remove() # 清除数据库数据 db.drop_all() if __name__ == '__main__': unittest.main()
-
测试模式
-
在此模式下,要测试的程序出现异常,在测试端也能知道错误异常出现在哪里
app.testing = True
-
部署
-
开发时,我们都是使用的Flask自带的web服务器,但在实际生产环境中,其性能是无法满足要求的
-
可以采用Gunicorn做wsgi容器,来部署程序
-
区分几个概念:
- WSGI:全称是Web Server Gateway Interface(web服务器网关接口),它是一种规范,它是web服务器和web应用程序之间的接口。它的作用就像是桥梁,连接在web服务器和web应用框架之间
- uwsgi:是一种传输协议,用于定义传输信息的类型
- uWSGI:是实现了uwsgi协议WSGI的web服务器
-
部署方式:nginx + gunicorn + flask
-
web开发中,部署方式大致类似。简单来说,前端代理使用Nginx主要是为了实现分流、转发、负载均衡,以及分担服务器的压力。Nginx部署简单,内存消耗少,成本低。Nginx既可以做正向代理,也可以做反向代理
- 正向代理:请求经过代理服务器从局域网发出,然后到达互联网上的服务器,服务端并不知道真正的客户端是谁,对象是客户端
- 反向代理:请求从互联网发出,先进入代理服务器,再转发给局域网内的服务器,客户端并不知道真正的服务端是谁,对象是服务器
使用Gunicorn
-
安装:
pip install gunicorn
-
使用
gunicorn -w 4 -b 127.0.0.1:5001 --access-logfile ./logs/log main:app
-w
:work 指定开启的进程数-b
:指定绑定到哪个IP和端口--access-logfile
:把用户的请求信息放进后面指定的日志文件,该日志文件需自己去创建main:app
:主程序所在的文件:和Flask实例名 -
守护进程:之前程序运行都是在终端,终端退出程序就挂了,所以这是需要让程序以后台的方式去运行,即以守护进程的方式去运行,在上面命令启动是加多一个参数
-D
gunicorn -w 4 -b 127.0.0.1:5001 -D --access-logfile ./logs/log main:app
Nginx
-
配置nginx的配置文件(
/usr/local/nginx/
)upstream flask{ server IP:port server xxxxx:xxxx } server { # 监听80端口 listen 80; # 本机 server_name localhost; # 默认请求的url location / { # 请求转发到gunicorn服务器 # proxy_pass http://127.0.0.1:5001; proxy_pass http://flask; # 设置请求头,并将头信息传递给服务器端 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
upstream
:相当于指明nginx可以识别的ip和端口flask
:为下面ip组定义的名字,下面请求转发指明ip时,就可以把ip组放后面,然后nginx会把请求均衡的分配给里面的ip服务器
END~