Flask基础
配置
django中的配置是通过settings.py文件指定的,flask的配置是通过app.config加载的。app.config是一个继承于字典的对象,在字典之上还封装了一些其它的方法。
默认配置如下
default_config = ImmutableDict({
'DEBUG': get_debug_flag(default=False),
'TESTING': False,
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': None,
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
'USE_X_SENDFILE': False,
'LOGGER_NAME': None,
'LOGGER_HANDLER_POLICY': 'always',
'SERVER_NAME': None,
'APPLICATION_ROOT': None,
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_HTTP_EXCEPTIONS': False,
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http',
'JSON_AS_ASCII': True,
'JSON_SORT_KEYS': True,
'JSONIFY_PRETTYPRINT_REGULAR': True,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None,
})
想更改配置有如下几个方法:
方式一:
利用继承自字典的特性
app.config['DUBUG'] = True
或者
app.config.update(map)
方式二:
从文件中读,文件名不一定是以.py
结尾
settings.cfg:
DEBUG=True
app.config.from_pyfile("settings.cfg")
方式三:
从对象中读
app.config.from_object('flask_test.settings.TestingConfig')
class Config(object):
DEBUG = False
TESTING = False
DATABASE_URI = 'sqlite://:memory:'
class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@localhost/foo'
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
常用的就是方式二和方式三,当然还有其他不常用的方式:
app.config.from_json("json文件名称"): JSON文件名称,必须是json格式,因为内部会执行json.loads
app.config.from_mapping({'DEBUG':True}): 字典格式
在这里需要注意一点:我们给flask应用设置的配置,有些是给flask使用的,有些是给我们程序使用的。那在我们的python程序中怎么去读取设置给程序使用的配置呢?app.config.get('xxx')
。如果在当前的视图函数所在作用域无法拿到app变量,那么可以使用current_app,这里可以先理解成一个app实例变量的代理,后续会分析其源码流程
在这里,着重强调一下from_object方法,因为这也是经常使用的。看源码
其实就是通过字符串的方式去导入,类似于django配置文件导入中间件只需要写路径的字符串形式就可以。
settings.py
class Test(object):
pass
import importlib
setting = 'settings.Test'
module_str, class_str= setting.rsplit('.', maxsplit=1)
m = importlib.import_module(module_str)
print(getattr(m, class_str))
路由
查看所有路由
app.url_map
Map([<Rule '/index' (GET, HEAD, OPTIONS) -> index>,
<Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>])
Map对象,可以和django的urls.py一样,路由看作是一个列表,既然是列表就是有序的,也就是从前到后匹配,匹配成功就不会往下匹配。这也就是为什么给同一个路由绑定不同函数,最终执行的时候只有绑定的第一个函数才会被调用的原因。Map里放的是url对象,这个对象里封装了请求方法,路径等。
同一视图多个路由装饰
@app.route('/python')
@app.route('/index')
def index():
return 'xx'
Map([<Rule '/python' (GET, HEAD, OPTIONS) -> index>,
<Rule '/index' (GET, HEAD, OPTIONS) -> index>,
<Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>])
我们看到index最终对应两个路由了。要理解这个,看app.route干了什么事情?这里唯一需要注意的一点是app,route不是一个装饰器,app.route()执行得到的结果才是装饰器,知道这一点看源码就清楚了
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
我们发现decorator做了两件事,一件就是做路由注册,另一件就是把视图函数原封不动地返回,所以最上面的装饰器装饰的也是原视图函数,只不过又再注册了一个路由而已。这里其实还用到了闭包,decorator可以访问route函数的参数。
限制请求方式
@app.route('/index', methods=['poSt', "gEt"])
def index():
return 'xx'
methods 列表中的字符串大小写无所谓,最终都会转为大写
url_for进行反解析
url和视图函数进行绑定之后,我们通过url就能找到函数并执行,那么我们可以<font color='red'通过函数名找到对应的url吗?答案是肯定的,通过url_for函数就能实现
@app.route('/index', methods=['poSt', "gEt"], endpoint='xxx')
def index():
return 'xx'
@app.route('/login')
def login():
url = url_for('xxx')
print(url)
return url
当一个函数在路由注册的时候没有指定endpoint,默认的endpoint就是函数名。 url_for(endpoint, **values)
根据*values
可以创建动态url
app.route 参数
@app.route和app.add_url_rule参数:
rule, URL规则
view_func, 视图函数名称
defaults=None, 默认值,当URL中无参数,函数需要参数时,使用defaults={'k':'v'}为函数提供参数
endpoint=None, 名称,用于反向生成URL,即: url_for('名称')
methods=None, 允许的请求方式,如:["GET","POST"]
strict_slashes=None, 对URL最后的 / 符号是否严格要求,
如:
@app.route('/index',strict_slashes=False),
访问 http://www.xx.com/index/ 或 http://www.xx.com/index均可
@app.route('/index',strict_slashes=True)
仅访问 http://www.xx.com/index
redirect_to=None, 重定向到指定地址
如:
@app.route('/index/<int:nid>', redirect_to='/home/<nid>')
或
def func(adapter, nid):
return "/home/888"
@app.route('/index/<int:nid>', redirect_to=func)
subdomain=None, 子域名访问
from flask import Flask, views, url_for
app = Flask(import_name=__name__)
app.config['SERVER_NAME'] = 'jack.com:5000'
# http://admin.jack.com:5000/
@app.route("/", subdomain="admin")
def static_index():
"""Flask supports static subdomains
This is available at static.your-domain.tld"""
return "admin.xxx.com"
#动态生成 # http://common.jack.com:5000/dynamic
@app.route("/dynamic", subdomain="<username>")
def username_index(username):
"""Dynamic subdomains are also supported
Try going to user1.your-domain.tld/dynamic"""
return username + ".your-domain.tld"
if __name__ == '__main__':
动态路由
默认支持6种路由转换器,不仅可以按照规则匹配路由,还可以对匹配到的内容进行转换之后再传给视图函数
@app.route('/user/<username>') #常用的 不加参数的时候默认是字符串形式的
@app.route('/user/<str:username>') #常用的
@app.route('/post/<int:post_id>') #常用的 #指定int,说明是整型的
@app.route('/post/<float:post_id>')
@app.route('/post/<uuid:uid>')
@app.route('/post/<any("jack", "lily"):name>')
@app.route('/post/<path:path>')
from werkzeug.routing import BaseConverter, DEFAULT_CONVERTERS
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
自定义转换器类
from werkzeug.routing import BaseConverter
from werkzeug.urls import url_quote
app = Flask(__name__)
app.config.from_pyfile('settings.cfg')
class MyConverter(BaseConverter):
def __init__(self, url_map, regex):
super(MyConverter, self).__init__(url_map)
# regex 这个属性不是我们自定义的,是BaseConverter里的,我们只是重新赋值
self.regex = regex
def to_python(self, value):
# 匹配成功之后,例如匹配手机号13739191111成功,在把这个值作为变量传给视图函数之前会调用这个方法,并且把这个方法
# 的返回值才最终传给视图函数
print(value)
return value
def to_url(self, value):
# 进行url_for的时候进行调用, 会把值先传入to_url,我们可以对value做一些自定义,自定义之后的值再放到路由里
print(value)
return url_quote(value, charset=self.map.charset)
# 注册到app.url_map.converters中
app.url_map.converters['re'] = MyConverter
@app.route('/index/<re(\d+):phone>', endpoint='xxx')
def index(phone):
return 'xx'
@app.route('/login')
def login():
url = url_for('xxx', phone='1213123122')
return url
请求参数
request.body 里放的是请求体数据,如果请求体是urlencoded编码的格式,那么flask就会帮我们把数据解析到request.form里面,并且会清空request.data里的数据。
如果前端name字段传了两次值,那么get方式只能拿第一个值,要想拿所有值,用getlist方法,得到的就是一个列表
上传文件
from flask import request
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# xx 是form表单的name值,而不是文件名
f = request.files['xx']
# save方法是flask帮我们封装的,这样我们就不需要自己打开文件读取内容再自己写进去了
f.save('/var/www/uploads/' + secure_filename(f.filename))
abort函数与自定义异常处理
我们在视图函数想要提前终止函数的执行,可以使用return。但是函数如果执行逻辑会有出错的地方,我们通过raise抛出的错误会显示在页面上。abort的作用就是提前终止函数,并抛出错误,而这种错误和之间raise错误是有区别的。
abort一般有两种用法,要么传字符串参数,要么传http状态码(flask根据不同状态码都定制了相应的页面,我们可以重写这些页面)
from from flask import abort
@app.route('/index', endpoint='xxx', methods=['POST', 'GET'])
def index():
abort(404)
return 'xx'
@app.errorhandler(404)
def error(e):
return '您请求的页面不存在了,请确认后再次访问!%s'%e
返回响应数据
返回响应数据有两种形式,一种是返回元组,一种是返回Response对象
元组:(response, status, headers) 或者 (response, status) 其中status可以是’404'这样额数字字符串,也可以是带有code说明信息的字符串'666 not find',其中not find就是666的这个自定义状态码的说明信息
Response对象
from flask import make_response
resp = make_response()
resp.headers[“sample”] = “value”
resp.status = “404 not found”
使用jsonify返回json数据
如果想返回json数据,自己写代码是这样的
@app.route('/index', endpoint='xxx', methods=['POST', 'GET'])
def index():
re_str = json.dumps({'name':"jack"})
return re_str, 200, {"Content-type":'json'}
jsonify 实现的就是re_str, 200, {"Content-type":'json'}
的功能,jsonify可以穿一个字典,也可以传键值对的形式
请求响应相关
from flask import Flask
from flask import request
from flask import render_template
from flask import redirect
from flask import make_response
app = Flask(__name__)
@app.route('/login.html', methods=['GET', "POST"])
def login():
# 请求相关信息
# request.method
# request.args
# request.form
# request.values
# request.cookies
# request.headers
# request.path
# request.full_path
# request.script_root
# request.url
# request.base_url
# request.url_root
# request.host_url
# request.host
# request.files
# obj = request.files['the_file_name']
# obj.save('/var/www/uploads/' + secure_filename(f.filename))
# 响应相关信息
# return "字符串"
# return render_template('html模板路径',**{})
# return redirect('/index.html')
# response = make_response(render_template('index.html'))
# response是flask.wrappers.Response类型
# response.delete_cookie('key')
# response.set_cookie('key', 'value')
# response.headers['X-Something'] = 'A value'
# return response
return "内容"
if __name__ == '__main__':
app.run()
session
基本使用当中字典来使用即可,后续源码分析会解析session流程
from flask import Flask, jsonify, session
app = Flask(__name__)
app.config.from_pyfile('settings.cfg')
app.secret_key = 'asdasd'
app.config['SESSION_COOKIE_NAME'] = 'xxx'
@app.route('/index', endpoint='xxx', methods=['POST', 'GET'])
def index():
print(session.get("a"))
return jsonify({'a':1})
@app.route('/login')
def login():
session['a'] = 1
return 'login'
if __name__ == '__main__':
print(app.url_map)
app.run()
flash
flask的flash(闪现)是基于session来做的,我们知道session使用的时候可以看做是一个字典,字典里的值可以取多次,但是flash与其不同,flash存储的值取完就没有了。其实没有flash,我们完全可以自己实现,取完就没有无外乎就是一个pop操作。flask的闪现使用是用两个方法:flash和get_flashed_messages
from flask import Flask,flash, get_flashed_messages
app = Flask(__name__)
app.secret_key = 'adasd'
@app.route("/")
def index():
# flash({'a':1})
# get_flashed_messages() 取到是一个列表 [{'a': 1}]
# 借助catogary对数据进行分类
flash('xiyouji','book')
flash('monkey', 'animal')
return 'index'
@app.route('/index')
def i():
# 需要指明category_filter参数,否则get_flashed_messages('animal')取到的是所有的值
print(get_flashed_messages(category_filter='animal'))
return 'i'
if __name__ == '__main__':
app.__call__
app.run()
flash函数源码
def flash(message, category='message'):
flashes = session.get('_flashes', [])
flashes.append((category, message))
session['_flashes'] = flashes
message_flashed.send(current_app._get_current_object(),
message=message, category=category)
说白了,就是在session的那个大字典加一个key:value, 其中key是_flashes
.
全局中间件
请求进来执行self(), 其中self是一个对象,self.call, 也就是app.call
现在问题来了,我想要在执行self.wsgi_app(environ, start_response)之前和之后做点事情,比如说打印什么东西,应该怎么做?纳尼,改源码?好想法,但是你写的程序别人要使用难不成让别人机器上的flask源码都要修改一下吗?这肯定不行,要么是装饰器,要么是继承。装饰器也是需要改源码,那么只能用继承了。
class MyFlask(Flask):
def __call__(self, environ, start_response):
print('进来了')
ret = super(MyFlask, self).__call__(environ, start_response)
print('出去了')
return ret
这样我们的app只需要用MyFlask来实例化就行。但是如果我就想使用Flask来实例化,那么应该怎么做呢?因为self.wsgi_app是一个函数,加括号运行,那么这里我们能不能把self.wsgi_app封装成一个类的对象呢,然后调用对象的__call__ 方法。
class MyWsgiApp(object):
def __init__(self, wsgi_app):
self.wsgi_app = wsgi_app
def __call__(self, *args, **kwargs):
print('come in')
ret = self.wsgi_app(*args, **kwargs)
print('out')
return ret
if __name__ == '__main__':
app.wsgi_app = MyWsgiApp(app.wsgi_app)
app.run()
flask中的装饰器
想要每次请求判断是否登录,登录之后才能访问某些网页,没有登录就跳转到登录页面
from flask import Flask,session, redirect
app = Flask(__name__)
app.secret_key = 'adasd'
app.debug = True
def auth(func):
def inner(*args, **kwargs):
sid = session.get('sid')
if sid:
ret = func(*args, **kwargs)
return ret
else:
return redirect('/login')
return inner
@app.route("/")
@auth
def index():
return 'index'
@app.route('/login')
@auth
def login():
return 'login'
if __name__ == '__main__':
app.run()
发现报错了
问题就出在不同的路径对应相同的endpoint,那么通过url_for反推路径的时候应该取不同路径中的哪一个呢?所以就报错了,现在从源码角度看看
因为使用装饰器的时候,所有的被装饰函数的__name__都是inner
,但是得到的对象都是不一样的(内部的inner函数可以看做一个对象,每次执行一次auth就会生成一个新的inner对象)。
装饰器修改如下:
from functools import wraps
def auth(func):
@wraps(func)
def inner(*args, **kwargs):
sid = session.get('sid')
if sid:
ret = func(*args, **kwargs)
return ret
else:
return redirect('/login')
return inner
flask的CBV
from flask.views import MethodView
class MyView(MethodView):
# 对所有请求的装饰器
decorators = []
methods = []
def get(self, *args, **kwargs):
pass
def post(self, *args, **kwargs):
pass
# xxx 就是
app.add_url_rule('/', view_func=MyView.as_view(name='xxx'))