一. 安装
推荐使用 python 3
, 但 Flask
支持 Python 3.4+
、 Python2.7
、 PyPy
依赖
下列的包会在安装 Flask
时自动安装:
- Werkzeug:
WSGI
的实现 - Jinja: 模板渲染语言.
- MarkupSafe: Jinja提供的, 目的是为了渲染的安全, 防止XSS攻击等
- ItsDangerous: 安全地签署数据以确保其完整性。 这用于保护Flask的会话cookie。
- Click: 编写命令行工具的框架. 它为
Flask
提供命令,并且允许添加自定义的管理命令.
还有一些可选的依赖, Flask
会检测它们是否安装,如果安装了则使用它们:
- Blinker: 提供了对 Signals 的支持
- SimpleJSON:
- python-dotenv: 运行
flask
命令时,可以从.env
或.flaskenv
中读取环境变量. - Watchdog: 为开发时使用的server 提供了更快速的reloader
虚拟环境
mkdir flaskyProject
cd flaskyProject
# 创建虚拟环境
python3 -m venv venv
# 激活虚拟环境
source ven/bin/activate 激活
# 或者在windows 下直接使用 ven/Scripts/activate 激活
# 以上也可以直接使用 pycharm 创建项目, pycharm 会自动创建虚拟环境, 然后在 pycharm 的 Terminal 中执行命令
# 安装 flask
pip3 install Flask
# 安装其他常用的包
二. 快速开始
最简单的例子
创建一个文件, 名称为 hello.py
, 内容如下:
(注意不能叫 flask.py
)
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
运行它
export FLASK_APP=hello.py
# 或者在Windows下 set FLASK_APP=hello.py
flask run
# 设置调试模式
export FLASK_ENV=development
# 也可以配置单独的环境变量
export FLASK_DEBUG=1
flask run --host=0.0.0.0
这只是启动了一个非常简单的 内置
server ,用来测试是足够了,
但是你可能不会想在 生产环境 使用它.
现在打开浏览器访问 http://127.0.0.1:5000/
from flask import Flask,request,make_response,redirect,render_template,abort
app=Flask(__name__)
@app.route('/', methods=['GET','POST'])
def index():
return 'Hello World!', 200
# 动态路由
@app.route('/user/<name>', methods=['GET','POST'])
def user(name):
return 'Hello {}!'.format(name), 200
# 动态路由支持变量类型
# 包括 string(默认选项,不支持/),int,float,path(和string类似,但允许/),uuid
@app.route('/path/<path:subpath>')
def show_subpath(subpath):
# show the subpath after /path/
return 'Subpath %s' % subpath
# 路由声明为 /projects/ (以/结尾)时, 当访问 /project 时会重定向到 /projects/
# 但反之不可以
# 即路由声明为 /projects 时, 不可以匹配 /project/
@app.route('/projects/')
def projects():
return 'The project page'
if (__name__=='__main__'):
app.run(debug=1)
url_for
from flask import Flask, url_for
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(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
"""
request 对象
from flask import Flask, request, make_response
app = Flask(__name__)
@app.route('/', methods=['POST', 'GET'])
def hello_world():
username = None
if request.method == 'POST':
try:
# 当form中的key不存在时会抛出 KeyError异常
username = request.form['username']
except KeyError as e:
pass
else:
username = request.args.get('username', '')
# 获取所有参数
all_params_dict = request.values.to_dict()
if username and username != '':
return 'Hello, {}!'.format(username)
else:
return 'Hello, World!'
# 上传文件
@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时要注意使用secure_filename
f.save('/var/www/uploads/' + secure_filename(f.filename))
# 读取cookie
@app.route('/cookie')
def get_cookies():
username = request.cookies.get('username')
# use cookies.get(key) instead of cookies[key] to not get a
# KeyError if the cookie is missing.
# 设置cookie
@app.route('/setcookie')
def set_cookies():
resp = make_response('this page is for set cookie')
resp.set_cookie('username', 'the username')
return resp
# redirect
@app.route('/redirect')
def redirect_url():
return redirect(url_for('hello_world'))
# error
@app.route('/login')
def login():
abort(401)
this_is_never_executed()
# errorHandler
@app.errorhandler(404)
def page_not_found(error):
return 'page_not_found', 404
response 对象
from flask import Flask, request, make_response
app = Flask(__name__)
@app.errorhandler(404)
def not_found(error):
resp = make_response('page_not_found', 404)
resp.headers['X-Something'] = 'A value'
return resp
session 对象
通过cookie中的 session 这个key实现, 其内容时加密的.
然后 session 中的内容完全保存在 cookie 中, 所以当session中的内容过多时要注意浏览器对cookie的长度限制.
session['username'] = 'llllllll'
session.pop('username', None)
但是这个 session 机制还是有点问题的, 客户端如果保存了这个 session 后, 服务端何时认定这个session过期?
logging
from flask import Flask, request, make_response
app = Flask(__name__)
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')
三. 测试 Flask 应用
http://flask.pocoo.org/docs/1.0/testing/
测试工具 pytest
pip3 install pytest
测试骨架
假设我们的程序是 hello.py
, 其内容如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'hello world!'
测试骨架:
# pytest 会自动检测 test_*.py 文件 中的 test_* 方法
cat > test/test_hello.py << EOF
import pytest
import hello
@pytest.fixture
def client():
yield hello.app.test_client()
# pytest 会自动检测 test_*.py 文件 中的 test_* 方法
def test_index(client):
rv = client.get('/', follow_redirects=True)
print(rv.data)
assert b'hello world!' in rv.data
assert b'hello world!' == rv.data
EOF
# 执行测试
pytest -v
四. 处理错误
http://flask.pocoo.org/docs/1.0/errorhandling/
1. 错误日志工具
推荐使用 Sentry , 在应用发生错误的时候给你发邮件.
pip install raven[flask]
五. Logging
http://flask.pocoo.org/docs/1.0/logging/
基础配置
from logging.config import dictConfig
dictConfig({
'version': 1,
'formatters': {'default': {
'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
}},
'handlers': {'wsgi': {
'class': 'logging.StreamHandler',
'stream': 'ext://flask.logging.wsgi_errors_stream',
'formatter': 'default'
}},
'root': {
'level': 'INFO',
'handlers': ['wsgi']
}
})
app = Flask(__name__)
发生错误给管理员
import logging
from logging.handlers import SMTPHandler
mail_handler = SMTPHandler(
mailhost='127.0.0.1',
fromaddr='[email protected]',
toaddrs=['[email protected]'],
subject='Application Error'
)
mail_handler.setLevel(logging.ERROR)
mail_handler.setFormatter(logging.Formatter(
'[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
))
if not app.debug:
app.logger.addHandler(mail_handler)
六. 配置管理
http://flask.pocoo.org/docs/1.0/config/
Flask
类的实例对象有一个 config
属性可以用来保存配置.
它其实就是 dict 的一个子类
from flask import current_app
# 将 app 的config属性转成dict
print(dict(current_app.config))
app = Flask(__name__)
app.config['TESTING'] = True
# 一些特定的配置会反应到 flask对象的属性上, 例如
app.testing==app.config['TESTING']
# 同时更新多个配置
app.config.update(
TESTING=True,
SECRET_KEY=b'_5#y2L"F4Q8z\n\xec]/'
)
# 设置config内容
app.config.from_object()
# 覆盖默认配置内容
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
app.config.from_pyfile('application.cfg', silent=True)
# 或直接配置系统的环境变量(优先级最高)
七. 信号
http://flask.pocoo.org/docs/1.0/signals/
就是解耦, 发生信号,接收信号
八. The Application Context
http://flask.pocoo.org/docs/1.0/appcontext/
current_app
from flask import current_app
print(dict(current_app.config))
my_app=current_app._get_current_object()
# _get_current_object 的作用是获取本线程的应用实例以作为参数传递给其他线程使用,应用实例就是 _find_app中返回的top.app
g
from flask import g
# 传统的 attr 方法
hasattr(g,'username')
getattr(g,'username',None)
setattr(g,'username',some_value)
# g 对象提供的方法
# 判断 key 是否在 g 中
print('key' in g)
# 遍历 g
for i in g:
print('attr_name:', i, 'attr_value:', g.get(i, default=None))
# 获取key的值
g.some_key = 'hhha'
print(g.get('some_key', default=None)) # 输出 hhha
print(g.get('some_key2')) # 输出 None
# 获取key的值并移除
g.some_key = 'hhha'
print(g.pop('some_key', default=None)) # 输出 hhha
print(g.pop('some_key', default=None)) # 输出 None
g.some_key = 'hhha'
print(g.pop('some_key')) # 输出 hhha
print(g.pop('some_key')) # 抛出异常
# 设置key的值
g.some_key = 'hhha'
# setdefault的作用是当key存在时获取key的值, 否则为该key设置值并返回设置的值
g.some_key = 'hhha'
print(g.setdefault('some_key', 'hhha2')) # 输出 hhha
print(g.setdefault('some_key2', 'hhha2')) # 输出 hhha2
九. The Request Context
http://flask.pocoo.org/docs/1.0/reqcontext/
十. 应用模块化 with Blueprints
http://flask.pocoo.org/docs/1.0/blueprints/
大型应用的项目结构一般为:
|-flask_project
|-requirements.txt
|-config.py
|-flasky.py
|-app/
|-__init__.py
|-main/
|-__init__.py
|-views.py
|-tests/
|-__init__.py
|-test*.py
|-venv/
其中 config.py
用于应用的配置,其内容一般如下:
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
SOME_ENV_NAME = '123'
@staticmethod
def init_app():
pass
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
class ProductionConfig(Config):
pass
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
app/__init__.py
用于创建应用的工厂函数:
from flask import Flask
# 其他 flask扩展
from config import config
# 初始化扩展
def create_app(config_name):
app=Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app()
# 初始化app
# 注册 blueprint
from .main import main as main_blueprint
app1.register_blueprint(main_blueprint)
return app
app/main/__init__.py
用于创建blueprint:
from flask import Blueprint
main=Blueprint('main',__name__)
from . import views
app/main/views.py
用于定义应用路由:
from . import main
@main.route('/', methods=['GET','POST'], defaults={'page': 'index'})
@main.route('/<page>', methods=['GET','POST'])
def index():
return 'hello world!'
最后, 主脚本 flasky.py
的内容:
import os
from app import create_app
app=create_app(os.getenv('FLASK_CONFIG') or 'default')
# other statement...
应用中最好有一个 requirements.txt
, 用于记录所有依赖包以及其精确的版本号.
pip freeze > requirements.txt
pip install -r requirements.txt
单元测试可以这样:
...
self.app=create_app('testing')