Flask主要依赖于Werkzeug
和Jinja
这两个库,是很简洁的Python Web框架。
Werkzeug
是一个WSGI的工具包,是Flask
的核心库。Jinja
则是一个模板渲染的库,主要负责渲染返回给客户端的html文件。
各个文件的功能简介
文件名 | 功能简介 |
---|---|
__init__.py | 主要是添加了一些常用的模块或者对象,方便导入 |
_compat.py | 主要是做 Python3 与 Python2 之间的兼容处理,还有pypy 的兼容处理 |
app.py | 实现与定义WSGI对象的主要模块(WSGI规范可以参考PEP333和PEP3333) |
blueprints.py | 是Blueprints的实现与定义模块。Blueprints的作用是项目的模块划分,类似于Django的APP |
cli.py | 主要是Flask的命令行功能实现和命令行解析 |
config.py | 实现与配置相关等功能 |
ctx.py | 实现需要上下文管理相关的类与方法 |
debughelpers.py | 定义一些错误提示或者检查的方法或类,增强开发者的体验 |
globals.py | 定义所有全局对象或方法的文件 |
helpers.py | 各种帮助类工具类和方法的文件 |
logging.py | 日志模块的实现文件 |
sessions.py | 定义和实现cookie 和session 的文件 |
signals.py | 定义和处理信号的文件,主要以blinker 库实现 |
templating.py | 实现了与Jinja 交互的接口文件 |
testing.py | 实现测试需要的帮助类或者函数。一般不用于生产环境 |
views.py | 定义和实现了视图的类 |
wrappers.py | 实现WSGI规范中的Wrapper,Request和Response |
主要类的简单解析
主要类的关系
-
主要类的关系
-
Session类的关系
-
Wrapper类的关系
Context 机制
Werkzeug.local
模块
参考之http://python.jobbole.com/87738/
Pyhton标准库中thread.local
,实现了线程局部变量,使得每个线程可以有私有变量,具有线程隔离性,可以通过线程安全的方式获取或者改变线程中的变量。
而Werkzeug.local
的功能与thread.local
类似,不过Werkzeug.local
在支持线程的基础上,加上了greenlet
的协程支持。
在Flask的上下文管理中,主要依赖了Werkzeug.local
来实现的。
Werkzeug.local
主要有4个类:
Local
LocalStack
LocalProxy
LocalManager
Local
# werkzeug.local
...
try:
from greenlet import getcurrent as get_ident
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident
...
class Local(object):
__slots__ = ('__storage__', '__ident_func__')
def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
def __iter__(self):
return iter(self.__storage__.items())
def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)
def __release_local__(self):
self.__storage__.pop(self.__ident_func__(), None)
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
__storage__
: 字典,用来保存不同线程/协程中的线程变量。字典中的Key为线程/协程的ID。__ident_func__
: 这个是保存获取当前线程/协程的ID函数。如果是协程,则是greenlet.getcurrent
。如果是线程,则是thread. get_ident
。
在Local中调用local.xxx
时,会调用__getattr__
函数,在__storage__
中用当前线程/协程ID作为Key然后再查找变量,代码: self.__storage__[self.__ident_func__()][name]
。
LocalStack
LocalStack
则是使用Local
实现一个栈来存储线程/协程的变量。
LocalStack
还实现了push、pop、top等方法或属性。调用这些属性或者方法时,该类会根据当前线程/协程的ID,在Local实例中对相应的数值进行操作。
AppContext
应用上下文
AppContext
在请求、Flask的CLI命令及其他活动期间,保存整个应用程序级别的数据。类似于RequestContext
是保存请求期间的数据一样。
# ctx.py
class AppContext(object):
"""The application context binds an application object implicitly
to the current thread or greenlet, similar to how the
:class:`RequestContext` binds request information. The application
context is also implicitly created if a request context is created
but the application is not on top of the individual application
context.
"""
def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()
# Like request context, app contexts can be pushed multiple times
# but there a basic "refcount" is enough to track them.
self._refcnt = 0
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.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]
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.send(self.app)
def __enter__(self):
self.push()
return self
def __exit__(self, exc_type, exc_value, tb):
self.pop(exc_value)
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
reraise(exc_type, exc_value, tb)
# globals.py
from functools import partial
from werkzeug.local import LocalStack, LocalProxy
...
def _lookup_app_object(name):
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return getattr(top, name)
...
def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return top.app
...
# context locals
_app_ctx_stack = LocalStack()
...
current_app = LocalProxy(_find_app)
g = LocalProxy(partial(_lookup_app_object, 'g'))
从代码中可以看到,_app_ctx_stack
是一个werkzeug.local.LocalStack
的栈,current_app
和g
是通过代理,每次获取栈顶元素AppContext
的app
和g
属性。
AppContext
的实现目的,主要目的是:
- 使得一个进程中,可以多个Flask App共存,比如
Werkzeug
内置的Middleware
可以将两个Flask App组合成一个WSGI应用,这种情况下,每个App都有自己独立的AppContext
。 - 可以使得一些App的全局属性,在离线的CLI命令或者单元测试等非Web环境中中进行访问,而不需要构造出请求,只需要将
AppContext
push
到栈顶。
AppContext
中有属性_refcnt
,这个值是记录该上下文是否已经没被使用,默认值为0
,每次push()
则增1,pop()
则减1,直至_refcnt
减至0
,才会触发teardown_appcontext
的函数。
RequestContext
请求上下文
RequestContext
在整个请求处理期间,保存整个请求级别的数据。
# ctx.py
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
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None
# Request contexts can be pushed multiple times and interleaved with
# other request contexts. Now only if the last level is popped we
# get rid of them. Additionally if an application context is missing
# one is created implicitly so for each level we add this information
self._implicit_app_ctx_stack = []
# indicator if the context was preserved. Next time another context
# is pushed the preserved context is popped.
self.preserved = False
# remembers the exception for pop if there is one in case the context
# preservation kicks in.
self._preserved_exc = None
# Functions that should be executed after the request on the response
# object. These will be called before the regular "after_request"
# functions.
self._after_request_functions = []
self.match_request()
# globals.py
from functools import partial
from werkzeug.local import LocalStack, LocalProxy
...
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
...
# context locals
_request_ctx_stack = LocalStack()
...
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
...
从代码中可以看到,_request_ctx_stack
是一个werkzeug.local.LocalStack
的栈,request
和session
是通过代理,每次获取栈顶元素RequestContext
的request
和session
属性。
RequestContext
的实现目的,主要目的是:
- 保证了每个请求之间的信息完全隔离,避免冲突。
- 同时,可以简单的使用
from flask import request
获取当前的请求。
一个线程只执行一个请求,所以RequestContext
对于每个线程是唯一的。
当Flask应用开始处理请求时,会将当前RequestContext``push
到栈顶,也会同时将AppContext``push
到它的栈顶。当请求结束时,会将RequestContext``pop
出,然后将AppContext
也从其栈顶pop
出。
流程
- 在经过步骤A后,
globals.py
的_request_ctx_stack
和_app_ctx_stack
的top分别为当前的req_ctx
和app_ctx
,经过LocalProxy
的代理后,在Flask中所有的from flask import session, request, g
,都是来自栈顶元素的req_ctx
或者app_ctx
- 所以在步骤B中,对请求进行处理的都是当前线程的request。
- 在步骤C中,会将
_request_ctx_stack
和_app_ctx_stack
的栈顶元素弹出。
在RequestContext
中的_implicit_app_ctx_stack
是为了防止在处理请求中有如下操作,使得app_ctx
和req_ctx
出栈错误:
# 在请求处理中
from
with req_ctx:
...
# 如果没有`_implicit_app_ctx_stack`做判断,会出错。AppContext中的`_refcnt`作用也类似
请求接收到返回响应流程
在流程的图里,当req_ctx.push()
后,就是开始调用self.full_dispatch_request()
进行请求的处理了。