Flask上下文

一、应用上下文

1、app context的作用

Flask 的设计理念是允许在同一个进程中创建多个应用,因此一个应用对象对应着一个app 上下文,代码通过应用上下文会找到正确的应用。
不同的app上下文之间不能共享数据,不会在线程之间进行移动,因此可存储数据库配置及连接等信息。这时候就要理解Flask的LocalStack的概念。

2、Local介绍

Localstack源于Threading Local。Threading Local是一种特殊的对象,他可保持线程之间保持隔离,即线程内部的修改不会改变其他线程内部的对象。在多个线程存在的情况下,每一个线程都用一个唯一的ID来标注自己。
在Flask中,没有直接使用Threading Local,而是使用werkzeug的LocalStack 和 LocalProxy。
LocalStack 是用 Local 实现的栈结构,可以将对象推入、弹出,也可以快速拿到栈顶对象。当然,所有的修改都只在本线程可见。和 Local 一样,LocalStack 也同样实现了支持 release_pool 的接口。
LocalProxy 则是一个典型的代理模式实现,它在构造时接受一个 callable 的参数(比如一个函数),这个参数被调用后的返回值本身应该是一个 Thread Local 对象。对一个 LocalProxy 对象的所有操作,包括属性访问、方法调用(当然方法调用就是属性访问)甚至是二元操作 都会转发到那个 callable 参数返回的 Thread Local 对象上。


_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)

request = LocalProxy(partial(_lookup_req_object, 'request'))

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)

LocalProxy 的一个使用场景是 LocalStack 的 call 方法。比如 my_local_stack 是一个 LocalStack 实例,那么 my_local_stack() 能返回一个 LocalProxy 对象,这个对象始终指向 my_local_stack 的栈顶元素。如果栈顶元素不存在,访问这个 LocalProxy 的时候会抛出 RuntimeError。
对于LocalProxy作用的介绍:


@implements_bool
class LocalProxy(object):
    """Acts as a proxy for a werkzeug local.  Forwards all operations to
    a proxied object.  The only operations not supported for forwarding
    are right handed operands and any kind of assignment.

__slots__ = ('__local', '__dict__', '__name__')

def __init__(self, local, name=None):
    # 这里有一个点需要注意一下,通过了__setattr__方法,self的
    # "_LocalProxy__local" 属性被设置成了local,你可能会好奇
    # 这个属性名称为什么这么奇怪,其实这是因为Python不支持真正的
    # Private member,具体可以参见官方文档:
    # http://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references
    # 在这里你只要把它当做 self.__local = local 就可以了 :)
    object.__setattr__(self, '_LocalProxy__local', local)
    object.__setattr__(self, '__name__', name)

def _get_current_object(self):
    """
    获取当前被代理的真正对象,一般情况下不会主动调用这个方法,除非你因为
    某些性能原因需要获取做这个被代理的真正对象,或者你需要把它用来另外的
    地方。
    """
    # 这里主要是判断代理的对象是不是一个werkzeug的Local对象,在我们分析request
    # 的过程中,不会用到这块逻辑。
    if not hasattr(self.__local, '__release_local__'):
        # 从LocalProxy(partial(_lookup_req_object, 'request'))看来
        # 通过调用self.__local()方法,我们得到了 partial(_lookup_req_object, 'request')()
        # 也就是 ``_request_ctx_stack.top.request``
        return self.__local()
    try:
        return getattr(self.__local, self.__name__)
    except AttributeError:
        raise RuntimeError('no object bound to %s' % self.__name__)

# 接下来就是一大段一段的Python的魔法方法了,Local Proxy重载了(几乎)?所有Python
# 内建魔法方法,让所有的关于他自己的operations都指向到了_get_current_object()
# 所返回的对象,也就是真正的被代理对象。

... ...
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
__str__ = lambda x: str(x._get_current_object())
__lt__ = lambda x, o: x._get_current_object() < o
__le__ = lambda x, o: x._get_current_object() <= o
__eq__ = lambda x, o: x._get_current_object() == o
__ne__ = lambda x, o: x._get_current_object() != o
__gt__ = lambda x, o: x._get_current_object() > o
__ge__ = lambda x, o: x._get_current_object() >= o

通过代码可以看到,代理替我们做了很多的事,不需要自己做了,比如我们不必自己get当前的request,而是直接指向了当前的request。

3、app上下文的创建。

应用上下文的创建有两种方法。一是当请求上下文被压入栈的时候,就会创建一个应用上下文,当前其实这里都是指的是同一个应用;二是用显示的方法:

from flask import Flask, current_app

current_app = LocalProxy(_find_app)

app = Flask(name) with app.app_context():
# within this block, current_app points to app.
print current_app.name

二、请求上下文

每次响应一个Http请求时,即每次访问一个URL时,都会创建一个Request对象上下文,该上下文会绑定到当前上下文上。
首先看一下响应请求的源代码:


def wsgi_app(self, environ, start_response):
        """The actual WSGI application.  This is not implemented in
        __call__ so that middlewares can be applied without losing a
        reference to the class.  So instead of doing this::

    :param environ: a WSGI environment
    :param start_response: a callable accepting a status code,
                           a list of headers and an optional
                           exception context to start the 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)

首先会根据environ创建一个RequestContext,request_context,会返回一个RequestContext对象。源代码:

class Flask(object):

def request_context(self, environ):
    return _RequestContext(self, environ)

_RequestContext传入的实例是应用本身,即同一个多个请求上下文共享一个应用。 再往下:


class RequestContext(object):
    """The request context contains all request relevant information.  It is
    created at the beginning of the request and pushed to the
    _request_ctx_stack and removed at the end of it.  It will create the
    URL adapter and request object for the WSGI environment provided.

Do not attempt to use this class directly, instead use
:meth:`~flask.Flask.test_request_context` and
:meth:`~flask.Flask.request_context` to create this object.

When the request context is popped, it will evaluate all the
functions registered on the application for teardown execution
(:meth:`~flask.Flask.teardown_request`).

The request context is automatically popped at the end of the request
for you.  In debug mode the request context is kept around if
exceptions happen so that interactive debuggers have a chance to
introspect the data.  With 0.4 this can also be forced for requests
that did not fail and outside of ``DEBUG`` mode.  By setting
``'flask._preserve_context'`` to ``True`` on the WSGI environment the
context will not pop itself at the end of the request.  This is used by
the :meth:`~flask.Flask.test_client` for example to implement the
deferred cleanup functionality.

You might find this helpful for unittests where you need the
information from the context local around for a little longer.  Make
sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
that situation, otherwise your unittests will leak memory.
"""

def __init__(self, app, environ, request=None):
    self.app = app
    if request is None:
        request = app.request_class(environ)
    self.request = request

request = app.request_class(environ)此时创建一个request对象,flask就是通过RequestContext将app和Request连接了起来。
总结:一个请求上下文的生命周期只限于一个URL请求上,而一个应用上下文的生命周期则存在于一个应用上。多个请求上下文可对应同一个应用上下文。

--------EOF---------
本文微信分享/扫码阅读