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作用的介绍:
通过代码可以看到,代理替我们做了很多的事,不需要自己做了,比如我们不必自己get当前的request,而是直接指向了当前的request。@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
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对象上下文,该上下文会绑定到当前上下文上。
首先看一下响应请求的源代码:
首先会根据environ创建一个RequestContext,request_context,会返回一个RequestContext对象。源代码: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)
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请求上,而一个应用上下文的生命周期则存在于一个应用上。多个请求上下文可对应同一个应用上下文。
微信分享/微信扫码阅读