Flask进阶信号的使用和源码剖析

进阶知识点

信号

flask的内置信号

from flask import signals

知识点

Flask框架中的信号基于blinker,其主要就是让开发者可是在flask请求过程中定制一些用户行为。
必须注册 并且下载blinker模块
pip3 install blinker #

内置10个信号和

预留的局部钩子
request_started = _signals.signal('request-started')                # 请求到来前执行
request_finished = _signals.signal('request-finished')              # 请求结束后执行
 
before_render_template = _signals.signal('before-render-template')  # 模板渲染前执行
template_rendered = _signals.signal('template-rendered')            # 模板渲染后执行
 
got_request_exception = _signals.signal('got-request-exception')    # 请求执行出现异常时执行
 
request_tearing_down = _signals.signal('request-tearing-down')      # 请求执行完毕后自动执行(无论成功与否)
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')# 请求上下文执行完毕后自动执行(无论成功与否)
 
    
    
    
    
#需要手动触发 进行push pop的时候 用于上下文管理
appcontext_pushed = _signals.signal('appcontext-pushed')            # 请求上下文push时执行
appcontext_popped = _signals.signal('appcontext-popped')            # 请求上下文pop时执行
message_flashed = _signals.signal('message-flashed')                # 调用flask在其中添加数据时,自动触发flash存值之后只能取一次

信号和中间件的执行流程


a. before_first_request #请求开始
b. 触发 request_started 信号
c. before_request
d. 模板渲染
    渲染前的信号 before_render_template.send(app, template=template, context=context)
        rv = template.render(context) # 模板渲染
    渲染后的信号 template_rendered.send(app, template=template, context=context)
e. after_request #视图结束
f. session.save_session()#存储session
g. 触发 request_finished信号 #请求结束	
如果上述过程出错:
    触发错误处理信号 got_request_exception.send(self, exception=e)   		
h. 触发信号 request_tearing_down			

信号的源码和扩展点

1.在中间件中手动预留钩子

from flask import Flask,render_template

app = Flask(__name__)


@app.route('/index')
def index():
    return render_template('index.html')


@app.route('/order')
def order():
    return render_template('order.html')


class MyMiddleware(object):
    def __init__(self,old_app):
        self.wsgi_app = old_app.wsgi_app

    def __call__(self, *args, **kwargs):
        print('123')
        result = self.wsgi_app(*args, **kwargs)
        print('456')
        return result

app.wsgi_app = MyMiddleware(app)

if __name__ == '__main__':
    app.run()

2. before_first_request #第一次请求开始

有且执行一次

源码

注意执行
#寻找流程
1.项目启动执行app(flask实例化对象).run() 
2. run_simple(host, port, self, **options)
3.执行self() call方法
4.执行wsgi_app 
5.执行full_dispatch_reqeust
6.self.try_trigger_before_first_request_functions()
  for func in self.before_first_request_funcs:
                func()

使用示例

from flask import Flask,render_template

app = Flask(__name__)

@app.before_first_request
def f2():
    print('before_first_requestf2被触发')

@app.route('/index')
def index():
    return render_template('index.html')

@app.route('/order')
def order():
    return render_template('order.html')

if __name__ == '__main__':
    app.run()

3. 触发 request_started 信号

源码

#源码寻找流程
1.项目启动执行app(flask实例化对象).run() 
2. run_simple(host, port, self, **options)
3.执行self() call方法
4.执行wsgi_app 执行full_dispatch_request
5.    def full_dispatch_request(self):
            request_started.send(self)

使用示例

from flask import Flask,render_template,g
from flask import signals
app = Flask(__name__)


@app.url_value_preprocessor
def f5(endpoint,args):
    print('f5')

@app.route('/index/')
def index():
    print('index')
    return render_template('index.html')

@app.route('/order')
def order():
    print('order')
    return render_template('order.html')

if __name__ == '__main__':
    app.run()

4.url_value_processor

源码流程

#源码寻找流程
1.项目启动执行app(flask实例化对象).run() 
2. run_simple(host, port, self, **options)
3.执行self() call方法
4.执行wsgi_app 执行full_dispatch_request
5.    def full_dispatch_request(self):
            request_started.send(self)
           rv = self.preprocess_request()
6.         def preprocess_request(self):
        bp = _request_ctx_stack.top.request.blueprint
        funcs = self.url_value_preprocessors.get(None, ())
        if bp is not None and bp in self.url_value_preprocessors:
            funcs = chain(funcs, self.url_value_preprocessors[bp])
        for func in funcs:
            func(request.endpoint, request.view_args)        

使用示例

from flask import Flask,render_template,g
from flask import signals
app = Flask(__name__)


@app.url_value_preprocessor
def f5(endpoint,args):
    print('f5')

@app.route('/index/')
def index():
    print('index')
    return render_template('index.html')

@app.route('/order')
def order():
    print('order')
    return render_template('order.html')

if __name__ == '__main__':
    app.run()

5 before_reuqest

源码流程

#源码寻找流程
1.项目启动执行app(flask实例化对象).run() 
2. run_simple(host, port, self, **options)
3.执行self() call方法
4.执行wsgi_app 执行full_dispatch_request
5.    def full_dispatch_request(self):
            request_started.send(self)
           rv = self.preprocess_request()
6.         def preprocess_request(self):
        bp = _request_ctx_stack.top.request.blueprint
        funcs = self.url_value_preprocessors.get(None, ())
        if bp is not None and bp in self.url_value_preprocessors:
            funcs = chain(funcs, self.url_value_preprocessors[bp])
        for func in funcs:
            func(request.endpoint, request.view_args)        
	   funcs = self.before_request_funcs.get(None, ())
        if bp is not None and bp in self.before_request_funcs:
            funcs = chain(funcs, self.before_request_funcs[bp])
        for func in funcs:
            rv = func()
            if rv is not None:
                return rv

使用示例

from flask import Flask,render_template,g
from flask import signals
app = Flask(__name__)

@app.before_request
def f6():
    g.xx = 123
    print('f6')

@app.route('/index/')
def index():
    print('index')
    return render_template('index.html')

@app.route('/order')
def order():
    print('order')
    return render_template('order.html')

if __name__ == '__main__':
    app.run()

6.识图函数之后

​ 模板渲染

before_render_template / rendered_template

源码

@account.route('/login')
def login():
    return render_template('login.html')


def render_template(template_name_or_list, **context):
    ctx = _app_ctx_stack.top
    ctx.app.update_template_context(context)
    return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list),
                   context, ctx.app)

def _render(template, context, app):
    """Renders the template and fires the signal"""

    # ############### before_render_template 信号 ###############
    before_render_template.send(app, template=template, context=context)
    rv = template.render(context)
    
    # ############### template_rendered 信号 ###############
    template_rendered.send(app, template=template, context=context)
    return rv

使用

from flask import Flask,render_template,g
from flask import signals
app = Flask(__name__)

@signals.before_render_template.connect
def f7(app, template, context):
    print('f7')

@signals.template_rendered.connect
def f8(app, template, context):
    print('f8')

@app.route('/index/')
def index():
    return render_template('index.html')

@app.route('/order')
def order():
    print('order')
    return render_template('order.html')

if __name__ == '__main__':
    app.run()

7.after_request

源码

  
    
    def process_response(self, response):
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))

使用

from flask import Flask,render_template,g
from flask import signals
app = Flask(__name__)

@app.after_request
def f9(response):
    print('f9')
    return response

@app.route('/index/')
def index():
    return render_template('index.html')

@app.route('/order')
def order():
    print('order')
    return render_template('order.html')

if __name__ == '__main__':
    app.run()

8.got_request_exception 触发

源码

项目启动执行app(flask实例化对象).run() 
2. run_simple(host, port, self, **options)
3.执行self() call方法
4. def wsgi_app(self, environ, start_response):   
    response = self.full_dispatch_request()
    except Exception as e:
        error = e
        # 这里这里这里这里这里这里这里这里这里这里这里这里 #
        response = self.make_response(self.handle_exception(e))

使用

from flask import Flask,render_template,g
from flask import signals
app = Flask(__name__)

@app.before_first_request
def test():
    int('asdf')

@signals.got_request_exception.connect
def f11(app,exception):
    print('f11')

@app.route('/index/')
def index():
    return render_template('index.html')

@app.route('/order')
def order():
    print('order')
    return render_template('order.html')

if __name__ == '__main__':
    app.run()

10.request_tearing_down,

使用

from flask import Flask,render_template,g
from flask import signals
app = Flask(__name__)

@signals.request_tearing_down.connect
def f13(app,exc):
    print('f13')

@app.route('/index/')
def index():
    return render_template('index.html')

@app.route('/order')
def order():
    print('order')
    return render_template('order.html')

if __name__ == '__main__':
    app.run()

11.appcontext_tearing_down,

9. appcontext_pushed,

12.appcontext_popped

使用
from flask import Flask,render_template,g
from flask import signals
app = Flask(__name__)

@signals.appcontext_popped.connect
def f14(app):
    print('f14')

@app.route('/index/')
def index():
    return render_template('index.html')

@app.route('/order')
def order():
    print('order')
    return render_template('order.html')

if __name__ == '__main__':
    app.run()

总体源码

class AppContext(object):
    def push(self):
        """Binds the app context to the current context."""
        self._refcnt += 1
        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()
        _app_ctx_stack.push(self)
        # ############## 触发 appcontext_pushed 信号 ##############
        appcontext_pushed.send(self.app)

    def pop(self, exc=_sentinel):
        """Pops the app context."""
        try:
            self._refcnt -= 1
            if self._refcnt <= 0:
                if exc is _sentinel:
                    exc = sys.exc_info()[1]
                # ############## 触发 appcontext_tearing_down 信号 ##############
                self.app.do_teardown_appcontext(exc)
        finally:
            rv = _app_ctx_stack.pop()
        assert rv is self, 'Popped wrong app context.  (%r instead of %r)' \
            % (rv, self)

        # ############## 触发 appcontext_popped 信号 ##############
        appcontext_popped.send(self.app)

class RequestContext(object):
    def push(self):
        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)

        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            
            # ####################################################
            app_ctx = self.app.app_context()
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)

        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()

        _request_ctx_stack.push(self)

        # Open the session at the moment that the request context is
        # available. This allows a custom open_session method to use the
        # request context (e.g. code that access database information
        # stored on `g` instead of the appcontext).
        self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()

class Flask(_PackageBoundObject):


    def wsgi_app(self, environ, start_response):
        
        ctx = self.request_context(environ)
        ctx.push()
        error = None
        try:
            try:
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.make_response(self.handle_exception(e))
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)


    def pop(self, exc=_sentinel):
        app_ctx = self._implicit_app_ctx_stack.pop()

        try:
            clear_request = False
            if not self._implicit_app_ctx_stack:
                self.preserved = False
                self._preserved_exc = None
                if exc is _sentinel:
                    exc = sys.exc_info()[1]

                # ################## 触发 request_tearing_down 信号 ##################
                self.app.do_teardown_request(exc)

                # If this interpreter supports clearing the exception information
                # we do that now.  This will only go into effect on Python 2.x,
                # on 3.x it disappears automatically at the end of the exception
                # stack.
                if hasattr(sys, 'exc_clear'):
                    sys.exc_clear()

                request_close = getattr(self.request, 'close', None)
                if request_close is not None:
                    request_close()
                clear_request = True
        finally:
            rv = _request_ctx_stack.pop()

            # get rid of circular dependencies at the end of the request
            # so that we don't require the GC to be active.
            if clear_request:
                rv.request.environ['werkzeug.request'] = None

            # Get rid of the app as well if necessary.
            if app_ctx is not None:
                # ####################################################
                app_ctx.pop(exc)

            assert rv is self, 'Popped wrong request context.  ' \
                '(%r instead of %r)' % (rv, self)

    def auto_pop(self, exc):
        if self.request.environ.get('flask._preserve_context') or \
           (exc is not None and self.app.preserve_context_on_exception):
            self.preserved = True
            self._preserved_exc = exc
        else:
            self.pop(exc)




c. before_request
d. 模板渲染
    渲染前的信号 before_render_template.send(app, template=template, context=context)
        rv = template.render(context) # 模板渲染
    渲染后的信号 template_rendered.send(app, template=template, context=context)
e. after_request #视图结束
f. session.save_session()#存储session
g. 触发 request_finished信号 #请求结束	
如果上述过程出错:
    触发错误处理信号 got_request_exception.send(self, exception=e)   		
h. 触发信号 request_tearing_down			

13.message_flashed

源码流程

def flash(message, category='message'):
    flashes = session.get('_flashes', [])
    flashes.append((category, message))
    session['_flashes'] = flashes

    # ############### 触发 message_flashed 信号 ###############
    message_flashed.send(current_app._get_current_object(),
                         message=message, category=category)

message_flashed
posted @ 2019-11-26 22:58  strawberry*  阅读(189)  评论(0)    收藏  举报