Flask 文档1.1.x走读笔记。
Foreword
Flask是micro框架,micro不意味着功能确实,而是意味着将核心功能保持在小范围,通过扩展的方式提供更多功能。比如,Flask默认不带数据库抽象层。
Flask有许多配置选项,但都提供了有意义的默认配置。比如模板和静态文件放在源代码树的template和static目录中。
Foreword for Experienced Programmers
Flask使用Thread Locals来存储请求和会话。
Flask会尽量保证网站的安全性。比如,HTML会被默认为不安全的,可能引起XSS。但是开发者本身也需要对网站安全性提供警觉。
Installation
Flask支持Python3.5,Python 2.7以及PyPy。并默认依赖于Werkzeug, Jinja, MarkupSafe(来自Jinja,对HTML进行转义), ItsDangerous(保护cookie)以及Click(用于方便书写命令行)。
以下依赖不是默认的,但是Flask会自动检测它们是否存在:
- Blinker提供Signals的支持
- SimpleJSON,Python的json模块的替代品
- python-dotenv,提供虚拟环境的支持
- Watchdog,快速重新加载服务端
章节的余下部分介绍了如果使用venv创建虚拟环境(对于python2的话,需要使用virtualenv)。
Quickstart
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
- Flask需要通过__name__来决定从哪里获取模板以及静态文件
- Flask通过
app.route
来修饰函数,以便将函数关联到route
Flask可以从环境变量FLASK_APP中查找app所在文件:
$ export FLASK_APP=hello.py
$ flask run
* Running on http://127.0.0.1:5000/
如果要接受本地以外的连接,则要:
flask run --host=0.0.0.0
开启Debug Mode,这样Flask可以在有代码改动的时候重新加载自己:
$ export FLASK_ENV=development
$ flask run
上面的操作还会开启Debug功能,如果只是想开启Debug功能而不需要自动加载,那么可以使用FLASK_DEBUG=1。
Werkzeug documentation说明如何使用自带的Debugger。如果想使用其他的Debugger,参考 Working with Debuggers.。
Routing
在绑定route的时候,可以用 <converter:variable_name>
的方式动态匹配URL,例子:
@app.route('/path/<path:subpath>')
def show_subpath(subpath):
# show the subpath after /path/
return 'Subpath %s' % escape(subpath)
Converter可以是:
- string,默认的,接受任何文本,但是不支持slash,也就是/
- int,接受正整型
- flaot,接受正浮点型
- path,类似string,但是接受slash
- uuid,接受UUID字符串
对于URL后面需不需要带slash这个问题,Flask是这么处理的:
- 如果route里面声明的是
/projects/
,那么访问/projects
的时候就会被重定向到/projects/
- 如果route里面声明的是
/projects
,那么访问/projects/
的时候就会出现404错误。
Flask提供一个叫做url_for()的函数来生成相对于根目录的url,例子:
from flask import Flask, url_for
from markupsafe import escape
app = Flask(__name__)
@app.route('/')
def index():
return 'index'
@app.route('/login')
def login():
return 'login'
@app.route('/user/<username>')
def profile(username):
return '{}\'s profile'.format(escape(username))
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=/
/user/John%20Doe
在route上可以指定HTTP方法,比如:
from flask import request
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return do_the_login()
else:
return show_the_login_form()
如果不指定methods,那么默认就只有GET(以及附带的HEAD和OPTIONS)。
对于静态文件,最好是通过HTTP server来提供服务。如果非得经过Flask,可以在源代码树中创建一个static目录,然后代码中通过下面的方法来获取:
url_for('static', filename='style.css') # 获取 static/style.css
Rendering Templates¶
render_template()可以用来渲染Jinja模板:
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目录下查找hello.html。
对于模块的话:
/application.py
/templates
/hello.html
如果打包的话:
/application
/__init__.py
/templates
/hello.html
参考Jinja2 Template Documentation获得更多Jinja相关的文档信息。
在模板代码里面,可以访问request,session以及g对象,还有get_flashed_messages()方法。
模板中的HTML会被自动转义,如果需要保持原样,可以使用|safe
过滤器。
Accessing Request Data¶
导入request:
from flask import request
Flask中的request和context包含的是具体请求相关的数据。reqeust的使用示例:
@app.route('/login', methods=['POST', 'GET'])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'],
request.form['password']):
return log_the_user_in(request.form['username'])
else:
error = 'Invalid username/password'
# the code below is executed if the request method
# was GET or the credentials were invalid
return render_template('login.html', error=error)
上述代码中,如果request.form访问出错,会抛出KeyError错误,造成一个400 Bad Request页面。
如果要访问URL中的参数,可以使用:
searchword = request.args.get('key', '')
别忘了将表单的enctype=“multipart/form-data"属性设置好
Flask支持处理文件上传。上传的文件可以保存在内存或者文件系统中。可以通过request.files来访问文件。request.files是一个字典,有着文件名到文件对象的映射。文件对象和Python的文件对象类似,不过具有一个save()方法,可以将文件保存到文件系统:
from flask import request
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/uploaded_file.txt')
...
文件对象的filename 属性保存着文件在客户端上传前的命名。但是客户端可以伪造这个名字,所以不一定可信。建议通过Werkzeug提供的secure_filename()来对filename进行处理。
f.save('/var/www/uploads/' + secure_filename(f.filename))
设置cookie的话可以在response上调用set_cookie()方法:
from flask import make_response
@app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('username', 'the username')
return resp
如果要设置cookie,但是response对象还不存在,那么可以参考Deferred Request Callbacks。
Redirects and Errors¶
使用redirect()可以将用户重新定向到一个新的url。如果要中止处理,那么可以使用abort():
from flask import abort, redirect, url_for
@app.route('/')
def index():
return redirect(url_for('login'))
@app.route('/login')
def login():
abort(401)
this_is_never_executed()
上面的abort(401)会产生一个401错误,如果要自定义这个错误页面,需要使用:
from flask import render_template
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
About Responses¶
route处理函数返回的对象会被自动转化为response对象:
- 如果返回的就是response,那么直接会被当作response使用
- 如果是字符串,那么会创建一个response,并且把返回的字符串当作内容
- 如果是字典,那么会创建一个response,并把返回的字典转为json
- 如果是元组,那么必须是这些格式:(response, status),(reponse, headers),(response, status, headers).
- 如果上面的都行不通,那么Flask会假设返回的是一个WSGI应用,并调用相应的方法将其转化为response
下面是返回response的一个例子:
@app.errorhandler(404)
def not_found(error):
resp = make_response(render_template('error.html'), 404)
resp.headers['X-Something'] = 'A value'
return resp
返回字典的例子:
@app.route("/me")
def me_api():
user = get_current_user()
return {
"username": user.username,
"theme": user.theme,
"image": url_for("user_image", filename=user.image),
}
或者可以调用jsonify显示将字典转化为json
@app.route("/users")
def users_api():
users = get_all_users()
return jsonify([user.to_json() for user in users])
Sessions
session可以帮助在同一个用户不同request之间的共享数据。实现上,session是基于加密的cookie的:
from flask import Flask, session, redirect, url_for, request
from markupsafe import escape
app = Flask(__name__)
# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@app.route('/')
def index():
if 'username' in session:
return 'Logged in as %s' % escape(session['username'])
return 'You are not logged in'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('index'))
return '''
<form method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
@app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))
因为没有使用模板,所以上面的显示使用escape来转义。
快速生成密钥:
$ python -c 'import os; print(os.urandom(16))'
b'_5#y2L"F4Q8z\n\xec]/'
注意,cookie的大小可能会受到浏览器的限制。Flask默认提供的是客户端的session,如果需要服务端的session,需要使用扩展来实现。
Message Flashing¶
Flask在用户反馈上,提供了简单的支持。通过flash()方法,Flask可以给用户展现一条消息。flash()应该是在reqeust后面附加了一条消息,然后紧邻的request中可以通过get_flashed_messages()获取。
更多参考Message Flashing。
Logging
使用app.logger可以记录debug信息:
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')
更多查看Application Errors。
Hooking in WSGI Middleware¶
如果要跟WSGI中间件挂钩,那么可以使用app示例的wsgi_app属性:
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)
ProxyFix是Werkzeug提供的中间件,可以让Flask运行在Nginx之上。使用app.wsgi_app而不是直接app,意味着你依然可以使用正常的方式来配置app。
Additional Notes > Design Decisions in Flask
The Explicit Application Object
一个Flask对象是一个支持wsgi应用规范的实例,需要显示创建,比如:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello World!'
文档列出了几个理由说明为什么不支持隐性实例化。
The Routing System
Flask所基于的Werkzeug允许以任意的顺序来定义routes。
Werkzeug还有努力保证URLs具有唯一性,如果URLs具有歧义的话,会自动跳转到一个规范过的URL。
One Template Engine
Flask默认绑定的是Jinja,并且依赖与很多Jinja的特性。你或许可以使用其他引擎,比如Genshi、Marko,但是无法把Jinja从Flask中剥离。
Micro with Dependencies
为啥作为Micro框架的Flask还基于Werkzeug和Jinja?这里举的例子是关于Ruby,Ruby有一个和WSGI类似的规范,叫做Rack,并且提供了一个实现了Rack规范的库,这个库本身也叫做Rack。基本上所有Ruby的Web框架都是基于Rack库的。对于Python来说,WSGI相关的库有WebOb(以前叫做Paste)和Werkzeug。
Thread Locals
Flask使用Thread Locals来保存请求和会话。在大型程序里面这个选择可能是一个有争议的选项,但是作为Micro框架,则是一个合理的选择。
What Flask is, What Flask is Not
Flask挺高兴自己是这个样子的。
其他
Tutorial举了一个很好的例子,讲解了如何以flask构建一个blog。
(本篇完)