zoukankan      html  css  js  c++  java
  • 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'))
    
  • 相关阅读:
    大型项目使用Automake/Autoconf完成编译配置
    用C语言编写Windows服务程序的五个步骤
    RPC的发展历史(本质就是双方定义好协议,传递参数后远程调用)
    libuv和libev 异步I/O库的比较
    zlog 程序日志的库 交叉编译(Linux生成ARM库,观察执行步骤)
    应用服务
    EvnetBus
    this指向
    CPU使用率
    数据量小,创建索引有必要吗
  • 原文地址:https://www.cnblogs.com/longyunfeigu/p/9435759.html
Copyright © 2011-2022 走看看