[TOC]
基本概念
- 一个由python实现的web框架
- Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。如此,Flask 可以与您珠联璧合
- 不包含数据库抽象层,表单验证
- Flask 支持用扩展来给应用添加这些功能,众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能
安装
- Flask 依赖两个外部库:Werkzeug 和 Jinja2 。
- Werkzeug 是一个 WSGI(在 Web 应用和多种服务器之间的标准 Python 接口) 工具集。
- Jinja2 负责渲染模板
python -m venv venv
激活
win系统下
venv\Scripts\activate
virtualenv
- 拥有的项目越多,同时使用不同版本的 Python 工作的可能性也就越大,或者起码需要不同版本的 Python 库
- 悲惨现实是:常常会有库破坏向后兼容性,然而正经应用不采用外部库的可能微乎其微。当在你的项目中,出现两个或更多依赖性冲突时,你会怎么做?
- virtualenv 为每个不同项目提供一份 Python 安装。它并没有真正安装多个 Python 副本,但是它确实提供了一种巧妙的方式来让各项目环境保持独立。
“Hello,World” Flask应用
在Python中,包含__init__.py
文件的子目录被视为一个可导入的包。 当你导入一个包时,__init__.py
会执行并定义这个包暴露给外界的属性
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
代码解释
- 导入Flask类,该类的实例是WSCI应用(Web服务编排接口,将不同的Web服务编排在一起,应用到有意义的场景中去)
- 创建一个该类的实例,第一个参数是应用模块或者包的的名称。如果你使用单一的模块(如本例),你应该使用
__name__
,因为模块的名称将会因其作为单独应用启动还是作为模块导入而有不同( 也即是'__main__'
或实际的导入名)。这是必须的,这样 Flask 才知道到哪去找模板、静态文件等等。详情见Flask
的文档。 - 使用route()装饰器告诉Flask,什么样的URL能够触发我们的函数
- 该函数的名字在生成URL时被特定的函数采用,该函数返回我们想要显示在用户浏览器中的信息
- 最后我们用
run()
函数来让应用运行在本地服务器上。 其中if __name__ =='__main__':
确保服务器只会在该脚本被 Python 解释器直接执行的时候才会运行,而不是作为模块导入的时候
快速入门
外部可访问的服务器
-
如果你运行了这个服务器,你会发现它只能从你自己的计算机上访问,网络中其它任何的地方都不能访问。
-
在调试模式下,用户可以在你的计算机上执行任意 Python 代码。因此,这个行为是默认的。
-
禁用了 debug 或信任你所在网络的用户,你可以简单修改调用
run()
的方法使你的服务器公开可用,如下:app.run(host='0.0.0.0')
-
这会让操作系统监听所有公网 IP
调试模式
-
run()适用于启动本地的开发服务器
-
缺点:每次修改代码都需要手动重启它
-
Flask优点:如果启用了调试支持,服务器会在代码修改后自动重新载入,并且在发生错误时提供一个相当有用的调试器
-
有两种途径启用调试模式
- 直接在应用对象上设置
app.debug = True app.run()
- 作为run方法的一个参数传入
app.run(debug=True)
路由
- Web应用的URL十分优雅,易于辨识
- route()装饰器把一个函数绑定到对应的URL上
@app.route('/')
def index():
return 'Index Page'
@app.route('/hello')
def hello():
return 'Hello World'
变量规则
- 要给 URL 添加变量部分,你可以把这些特殊的字段标记为
<variable_name>
, 这个部分将会作为命名参数传递到你的函数。规则可以用<converter:variable_name>
指定一个可选的转换器。这里有一些不错的例子:
@app.route('/user/<username>')
def show_user_profile(username):
# show the user profile for that user
return 'User %s' % username
@app.route('/post/<int:post_id>')
def show_post(post_id):
# show the post with the given id, the id is an integer
return 'Post %d' % post_id
转换器有下面几种:
int | 接受整数 |
---|---|
float | 同 int ,但是接受浮点数 |
path | 和默认的相似,但也接受斜线 |
唯一URL/重定向行为
- Flask 的URL规则时基于Werkzeug的路由模块
- 以下面两个规则为例
@app.route('/projects/')
def projects():
return 'The project page'
@app.route('/about')
def about():
return 'The about page'
- 虽然它们看起来着实相似,但它们结尾斜线的使用在 URL 定义 中不同。 第一种情况中,指向 projects 的规范 URL 尾端有一个斜线。这种感觉很像在文件系统中的文件夹。访问一个结尾不带斜线的 URL 会被 Flask 重定向到带斜线的规范 URL 去。
- 然而,第二种情况的 URL 结尾不带斜线,类似 UNIX-like 系统下的文件的路径名。访问结尾带斜线的 URL 会产生一个 404 “Not Found” 错误。
- 这个行为使得在遗忘尾斜线时,允许关联的 URL 接任工作,与 Apache 和其它的服务器的行为并无二异。此外,也保证了 URL 的唯一,有助于避免搜索引擎索引同一个页面两次。
构造URL
- 用url_for()给指定的函数构造URL
- 接受函数名作为第一个参数
>>> from flask import Flask, url_for
>>> app = Flask(__name__)
>>> @app.route('/')
... def index(): pass
...
>>> @app.route('/login')
... def login(): pass
...
>>> @app.route('/user/<username>')
... def profile(username): pass
...
>>> with app.test_request_context():
... print url_for('index')
... print url_for('login')
... print url_for('login', next='/')
... print url_for('profile', username='John Doe')
...
/
/login
/login?next=/
为什么你要构建 URL 而非在模板中硬编码?这里有三个绝妙的理由:
- 反向构建通常比硬编码的描述性更好。更重要的是,它允许你一次性修改 URL, 而不是到处边找边改。
- URL 构建会转义特殊字符和 Unicode 数据,免去你很多麻烦。
- 如果你的应用不位于 URL 的根路径(比如,在
/myapplication
下,而不是/
),url_for()
会妥善处理这个问题。
Http方法
HTTP(与 Web 应用会话的协议)有许多不同的访问 URL 方法。默认情况下,路由只回应 GET 请求,但是通过 route()
装饰器传递 methods 参数可以改变这个行为。这里有一些例子:
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
do_the_login()
else:
show_the_login_form()
# 上述写法出错,采用如下写法
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return "POST"
else:
return "GET"
如果存在 GET ,那么也会替你自动地添加 HEAD,无需干预。它会确保遵照 HTTP RFC(描述 HTTP 协议的文档)处理 HEAD 请求,所以你可以完全忽略这部分的 HTTP 规范。同样,自从 Flask 0.6 起, 也实现了 OPTIONS 的自动处理。
HTTP 方法(也经常被叫做“谓词”)告知服务器,客户端想对请求的页面 做 些什么。下面的都是非常常见的方法:
-
GET
浏览器告知服务器:只 获取 页面上的信息并发给我。这是最常用的方法。
-
HEAD
浏览器告诉服务器:欲获取信息,但是只关心 消息头 。应用应像处理 GET 请求一样来处理它,但是不分发实际内容。在 Flask 中你完全无需 人工 干预,底层的 Werkzeug 库已经替你打点好了。
-
POST
浏览器告诉服务器:想在 URL 上 发布 新信息。并且,服务器必须确保 数据已存储且仅存储一次。这是 HTML 表单通常发送数据到服务器的方法。
-
PUT
类似 POST 但是服务器可能触发了存储过程多次,多次覆盖掉旧值。你可 能会问这有什么用,当然这是有原因的。考虑到传输中连接可能会丢失,在 这种 情况下浏览器和服务器之间的系统可能安全地第二次接收请求,而 不破坏其它东西。因为 POST它只触发一次,所以用 POST 是不可能的。
-
DELETE
删除给定位置的信息。
-
OPTIONS
给客户端提供一个敏捷的途径来弄清这个 URL 支持哪些 HTTP 方法。 从 Flask 0.6 开始,实现了自动处理。
静态文件
- 一般是CSS或者JavaScript文件
- 给静态文件生成URL,使用特殊的
static
端点名:
url_for('static', filename='style.css')
模板渲染
用 Python 生成 HTML 十分无趣,而且相当繁琐,因为你必须手动对 HTML 做转义来保证应用的安全。为此,Flask 配备了 Jinja2 模板引擎。
你可以使用 render_template()
方法来渲染模板。你需要做的一切就是将模板名和你想作为关键字的参数传入模板的变量。这里有一个展示如何渲染模板的简例:
from flask import render_template
@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
Flask 会在 templates 文件夹里寻找模板。所以,如果你的应用是个模块,这个文件夹应该与模块同级;如果它是一个包,那么这个文件夹作为包的子目录:
情况 1: 模块:
/application.py
/templates
/hello.html
情况 2: 包:
/application
/__init__.py
/templates
/hello.html
关于模板,你可以发挥 Jinja2 模板的全部实例。更多信息请见 Jinja2 模板文档 。
这里有一个模板实例:
<!doctype html>
<title>Hello from Flask</title>
<h1>Hello World!</h1>
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
在模板里,你也可以访问 request
、 session
和 g
[1] 对象, 以及 get_flashed_messages()
函数。
模板继承让模板用起来相当顺手。如欲了解继承的工作机理,请跳转到 模板继承 模式的文档。最起码,模板继承能使特定元素 (比如页眉、导航栏和页脚)可以出现在所有的页面。
自动转义功能默认是开启的,所以如果 name 包含 HTML ,它将会被自动转义。如果你能信任一个变量,并且你知道它是安全的(例如一个模块把 Wiki 标记转换为 HTML),你可以用 Markup
类或 |safe
过滤器在模板中把它标记为安全的。在 Jinja 2 文档中,你会看到更多的例子。
这里是一个 Markup
类如何使用的简单介绍:
>>> from flask import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello <blink>hacker</blink>!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup(u'<blink>hacker</blink>')
>>> Markup('<em>Marked up</em> » HTML').striptags()
u'Marked up \xbb HTML'
在 0.5 版更改: 自动转义不再在所有模板中启用。下列扩展名的模板会触发自动转义:.html
、 .htm
、.xml
、 .xhtml
。从字符串加载的模板会禁用自动转义。
#
blueprints蓝图
介绍
- 一个应用中或跨应用制作应用组件和支持通用的模式。
- 简化了大型应用工作的方式
- 提供给Flask扩展在应用上注册操作的核心方法
- 与flask应用对象很相似,但是它不是一个应用,而是一个描述如何构建或扩展应用的蓝图
- 在 Flask 中构建大型 Web 项目,可以通过蓝图为路由分组,并在蓝图中添加通用的规则(url 前缀、静态文件路径、模板路径等)
为什么要使用
- 一个应用分解为一个蓝图的集合
- URL前缀或者子域名,在应用上注册一个蓝图
- 可以在一个应用中用不同的URL规则多次注册一个蓝图
- 通过蓝图提供模板过滤器、静态文件、模板和其它功能。
- 举个例子,想象一下我们有一个用于管理面板的蓝图。这个蓝图将定义像/admin/login和/admin/dashboard这样的路由的视图。它可能还包括所需的模板和静态文件。你可以把这个蓝图当做你的应用的管理面板,管它是宇航员的交友网站,还是火箭推销员的CRM系统。
第一个蓝图
from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound
simple_page = Blueprint('simple_page', __name__,
template_folder='templates')
@simple_page.route('/', defaults={'page': 'index'})
@simple_page.route('/<page>')
def show(page):
try:
return render_template('pages/%s.html' % page)
except TemplateNotFound:
abort(404)
蓝图的创建
from flask import Blueprint
main = Blueprint('main', __name__)
from . import views, errors
from ..models import Permission
蓝图资源文件夹
像常规的应用一样,蓝图被设想为包含在一个文件夹中。当多个蓝图源于同一个文件夹时,可以不必考虑上述情况,但也这通常不是推荐的做法。
这个文件夹会从 Blueprint
的第二个参数中推断出来,通常是 name 。 这个参数决定对应蓝图的是哪个逻辑的 Python 模块或包。如果它指向一个存在的 Python 包,这个包(通常是文件系统中的文件夹)就是资源文件夹。如果是一个模块, 模块所在的包就是资源文件夹。你可以访问 Blueprint.root_path
属性来查看资源文件夹是什么:
print("simple_page.root_path", simple_page.root_path)
蓝图理解
# -*- coding: utf-8 -*-
# run.py
from flask import Flask, render_template, request
# flask_flatpages: 对于flask应用提供页面的集合
from flask_flatpages import FlatPages
FLATPAGES_AUTO_RELOAD = True
FLATPAGES_ROOT = 'pages'
FLATPAGES_EXTENSION = '.md'
app = Flask(__name__)
app.config.from_object(__name__)
flatpages = FlatPages(app)
# 首页
@app.route('/')
def index():
return "hello"
# pass
# 文章列表
@app.route('/list/<string:tag>/')
def page_list(tag):
pass
# 文章详情
@app.route('/page/<string:uri>/')
def page(uri):
pass
if __name__ == '__main__':
app.run()
运行结果
Flask-RESTPlus
- 现在web开发大多采用前后端分离的方式,路由的控制权限都交给前端,而后端只提供api服务。前端只管调用后端api,而不用管后端api是怎么实现的。
- 文档的编写和整理工作将花费巨大精力甚至不亚于代码的编写,因此在时间紧任务重的情况下,文档是首先被忽略的工作。不过,就算项目在初期存在文档,但在后面的迭代中,文档落后严重,其产生的误导比没有文档更加可怕。
- 文档随代码走,代码改动时文档也应该跟进变动,但本着 人是不可靠的 原则,文档理想上是应该由代码生成,而不是靠人工维护。如果代码有任何改动,文档也能自动更新,这将是一件非常优雅的事情。虽然对很多文档来说这并不现实,但对于 Api 文档来说,实现成本并不高。
Flask-RESTPlus
是一个优秀的 Api 文档生成工具,这个包将会替换 Flask 路由层的编写方式,通过自己的语法来规定 Api 细节,并生成 Api 文档
例子
from flask import Flask
from flask_restplus import Resource, Api
app = Flask(__name__)
api = Api(app, prefix="/v1", title="Users", description="Users CURD api.")
@api.route('/users')
class UserApi(Resource):
def get(self):
return {'user': '1'}
if __name__ == '__main__':
app.run()
- 上面是一个可以运行的微型博客的全部后端程序
例子2
from flask import Flask
from flask_restplus import Resource, Api
app = Flask(__name__) # Create a Flask WSGI application
api = Api(app) # Create a Flask-RESTPlus API
@api.route('/hello') # Create a URL route to this resource
class HelloWorld(Resource): # Create a RESTful resource
def get(self): # Create GET endpoint
return {'hello': 'world'}
if __name__ == '__main__':
app.run(debug=True) # Start a development server
-
debug=True,这是为了更详细地打印错误信息,以及确保我们每次修改代码时,都会自动发现变更并重新启动运行最新的代码。不过,生产环境绝对不要开启调试模式,因为它会使你的后台服务处于被攻击的风险之中!
-
可以在浏览器中直接访问我们API的根路径,即http://127.0.0.1:5000,此时会显示Swagger的界面,里面包含了我们的Restful API的相应信息,这就是Flask-RESTPlus的强大之处(当然,其实是Swagger的强大之处)
存在问题
- 哪儿可以添加新的文章和管理文章分类?的确,这段代码离一个传统的博客程序该有的全貌相差甚远,因为还欠缺一个管理后台。
- 以WordPress为例子,在一般情况下,如果访问如http://xxx.com,可以看到博客的前台,也就是各种博客文章。而访问如http://xxx.com/wp-admin 则可以进入到后台,用来管理文章和发布新文章。在路由的写法上,对于上面这个文件你可能要这样扩充一下功能(为了节省篇幅方便演示,下面代码详细部分省略,以 pass 代替详细部分)。
问题解决
# 新增的后台部分代码
@app.route('/admin/login/')
def admin_login():
pass
@app.route('/admin/page/')
def admin_pages():
pass
@app.route('/admin/page/new/')
def new_page():
pass
@app.route('/admin/page/edit/')
def edit_page():
pass
- 上述代码新增加了管理后台部分,具体是增加了一个后台登录界面、文章列表以及新增和编辑文章的功能共四个部分。
新的问题
1)一般情况下,前台和后台用两套模板。或者通俗的讲,前台费力弄得好看点,后台反正自己用,能用就成,丑点无所谓。那么怎么让前台和后台用两套模板? 2)后台部分逻辑比前台复杂,还需要导入新的包,如果和前台写在一个文件里,后面修改会不会容易出错,例如本来改后台部分结果牵连前台出问题? 3)既然Python力求简洁,那代码能否再简洁些?比如新增的路由参数 /admin 重复写了4遍,能不能对后台定义一个前缀,后台部分的自动加这个/admin ? 4)如果这个博客程序需要多人来维护,多人编辑同一个文件去提交时冲突如何解决?
继续解决
- 把程序做大的同时,还能保证简洁、良好、易于维护的代码组织结构,一个容易想到的方向就是程序的「模块化」
- 在你学习Python的过程中,早已见识到了Python模块化,比如Python的包概念,也即你总是能看到的 import …… 引入的那些东西和本身的这个引入方法。那么,对于Flask而言,你也许会举一反三的把博客的后台程序部分抽离出来,单独编写一个文件,然后再 import …… ,这样不就模块化了?!
-
提出Blueprint方法实现模块化组织程序结构
- 新增adminPages.py
# -*- coding: utf-8 -*-
# adminPages.py
from flask import Blueprint
admin = Blueprint('admin', __name__)
@admin.route('/login/')
def admin_login():
return 'login'
@admin.route('/page/')
def admin_pages():
pass
@admin.route('/page/new/')
def new_page():
pass
@admin.route('/page/edit/')
def edit_page():
pass
- 修改run.py
# -*- coding: utf-8 -*-
# run.py
from flask import Flask, render_template, request
from flask_flatpages import FlatPages
FLATPAGES_AUTO_RELOAD = True
FLATPAGES_ROOT = 'pages'
FLATPAGES_EXTENSION = '.md'
app = Flask(__name__)
app.config.from_object(__name__)
flatpages = FlatPages(app)
@app.route('/')
def index():
return "hello"
# pass # 为了方便演示这里省略详细代码
@app.route('/list/<string:tag>/')
def page_list(tag):
pass
@app.route('/page/<string:uri>/')
def page(uri):
pass
# 新增的后台部分代码
from adminPages import admin
app.register_blueprint(admin, url_prefix='/admin')
if __name__ == '__main__':
app.run()
- 结果
蓝图与flask-RESTPlus的结合
from flask import Blueprint
from flask_restplus import Api
api_blueprint = Blueprint("open_api", __name__, url_prefix="/api")
api = Api(api_blueprint, version="1.0",
prefix="/v1", title="OpenApi", description="The Open Api Service")
然后,就可以创建出不同的 namespace,来编写自己的 api 代码了。而只需要在 app 工厂中注册该 blueprint,便可将自己的编写的 api 挂载到 flask app 中。
def create_app():
app = Flask("Flask-Web-Demo")
# register api namespace
register_api()
# register blueprint
from apis import api_blueprint
app.register_blueprint(api_blueprint)
return app
HttpException
在api的设计中, 无论异常还是正常数据均需要服务器以json的格式返回, 为了对异常的统一管理, 同时为了后续更加方便的返回和验证数据, 我们自定义异常返回类.
设计异常数据的返回格式为:
{
"error_code": 999,
"msg": "sorry, we make a mistake",
"request": "POST /v1/client/register"
}
异常值代表
> 0 接口请求成功
> 999 默认api请求错误
> 1000 传入参数异常
> 1001 资源没找到
> 1002 签名不匹配
> 1003 签名时间超过设定最大值
> 1004 禁止访问 (通常是用户权限作用域的问题)
> 1005 授权失败 (通常客户端输入错误的用户名密码)
> 1006 客户端类型错误
> 1007 请求url错误
> 1008 无法处理请求的方法
REST
REST描述的是在网络中client和server的一种交互形式;REST本身不实用,实用的是如何设计 RESTful API(REST风格的网络接口);【其实是要设计一种合适的API,RESTful的】
总结一下什么是RESTful架构:
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现”表现层状态转化”
- REpresentational State Transfer 直接翻译:表现层状态转移
- API 应用程序接口,比如腾讯,阿里巴巴可以提供一个API, 然后小公司可以编写一个软件去跟接口API进行交互或者连接
- REST是一种架构风格,腾讯或者其他公司建立API时要遵守的一种的规则或者风格
- REST是以Web作为平台的
- Web是什么:分布式信息系统为超文本文件和其它对象(资源)提供访问入口
- 资源是Web框架的关键点; 3个操作 识别(identify) 表示(represent) 交互(interact with),通过这三个操作,引出3个概念
- uri,url,urm
- representation(html, xml, 图片,视频)表示资源
- 协议(http, ftp)与资源进行交互
- REST就是选择通过使用http协议和uri,利用client/server model 对资源进行CURD(Create、Read、Update、Delete)增删改查
使用rest风格原因
六个限制
logging模块使用
python内置的标准模块, 主要用于输出日志
- 设置日志的等级
- 日志保存
- 日志文件回滚
log = logging.getLogger(name)
import logging
logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
控制台输出结果
2016-10-09 19:11:19,434 - __main__ - INFO - Start print log
2016-10-09 19:11:19,434 - __main__ - WARNING - Something maybe fail.
2016-10-09 19:11:19,434 - __main__ - INFO - Finish