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'))
posted @ 2018-08-07 11:53  龙云飞谷  阅读(130)  评论(0编辑  收藏  举报