ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Flask源码阅读

2022-03-05 13:35:34  阅读:304  来源: 互联网

标签:__ Flask ctx self request 源码 阅读 app stack


上下文篇

整个Flask生命周期中都依赖LocalStack()。而LocalStack()分为请求上下文_request_ctx_stack和应用上下文_app_ctx_stack.

  • _request_ctx_stack:包含requestsession等请求信息

  • _app_ctx_stack:包含应用信息

...

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


def _lookup_app_object(name):
    print("_lookup_app_object===", name)
    top = _app_ctx_stack.top
    print("top===", top)
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    print("getattr(top, name)", getattr(top, name))
    return getattr(top, name)


def _find_app():
    top = _app_ctx_stack.top
    print("find_app", top)
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    print("top.app", top.app)
    return top.app


# context locals
# 请求上下文
_request_ctx_stack = LocalStack()
# 应用上下文
_app_ctx_stack = LocalStack()

current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

接下来我们看看LocalStack()的内容,有一个Local()类 、push()方法、pop()方法、top()方法,还有一个通过列表维护成栈的stack

  • Local()LocalStack()的核心
  • push(): 往stack中推送数据
  • pop():弹出stack中数据
  • top():返回stack顶元素
  • stack:一个列表 []
class LocalStack:

    def __init__(self) -> None:
        self._local = Local()

    def __release_local__(self) -> None:
        self._local.__release_local__()

    @property
    def __ident_func__(self) -> t.Callable[[], int]:
        return self._local.__ident_func__

    @__ident_func__.setter
    def __ident_func__(self, value: t.Callable[[], int]) -> None:
        object.__setattr__(self._local, "__ident_func__", value)

    def __call__(self) -> "LocalProxy":
        def _lookup() -> t.Any:
            rv = self.top
            if rv is None:
                raise RuntimeError("object unbound")
            return rv

        return LocalProxy(_lookup)

    def push(self, obj: t.Any) -> t.List[t.Any]:
        """Pushes a new item to the stack"""
        rv = getattr(self._local, "stack", []).copy()
        rv.append(obj)
        print("stack0000000000000", rv)
        self._local.stack = rv
        print("self.local00000000", self._local._storage)
        print("self.__ident_func__00000000", self._local.__ident_func__)
        return rv  # type: ignore

    def pop(self) -> t.Any:
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, "stack", None)
        print("stack111111111", stack)
        print("self.local111111111", self._local._storage)
        print("self.__ident_func__11111111", self._local.__ident_func__)
        if stack is None:
            return None
        elif len(stack) == 1:
            
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self) -> t.Any:
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None

来到Local()我们看到有一个_storage_storage而核心是 ContextVar("local_storage")

class Local:
    __slots__ = ("_storage",)

    def __init__(self) -> None:
        object.__setattr__(self, "_storage", ContextVar("local_storage"))

    @property
    def __storage__(self) -> t.Dict[str, t.Any]:
        warnings.warn(
            "'__storage__' is deprecated and will be removed in Werkzeug 2.1.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self._storage.get({})  # type: ignore

    @property
    def __ident_func__(self) -> t.Callable[[], int]:
        warnings.warn(
            "'__ident_func__' is deprecated and will be removed in"
            " Werkzeug 2.1. It should not be used in Python 3.7+.",
            DeprecationWarning,
            stacklevel=2,
        )
        return _get_ident  # type: ignore

    @__ident_func__.setter
    def __ident_func__(self, func: t.Callable[[], int]) -> None:
        warnings.warn(
            "'__ident_func__' is deprecated and will be removed in"
            " Werkzeug 2.1. Setting it no longer has any effect.",
            DeprecationWarning,
            stacklevel=2,
        )

    def __iter__(self) -> t.Iterator[t.Tuple[int, t.Any]]:
        return iter(self._storage.get({}).items())

    def __call__(self, proxy: str) -> "LocalProxy":
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self) -> None:
        __release_local__(self._storage)

    def __getattr__(self, name: str) -> t.Any:
        values = self._storage.get({})
        print(values, name, "xxxxxxxxxxxxx")
        try:
            return values[name]
        except KeyError:
            raise AttributeError(name) from None

    def __setattr__(self, name: str, value: t.Any) -> None:
        values = self._storage.get({}).copy()
        values[name] = value
        print(name, values, "xxxxxxxxxx222xxxxxx")
        self._storage.set(values)

    def __delattr__(self, name: str) -> None:
        values = self._storage.get({}).copy()
        try:
            del values[name]
            self._storage.set(values)
        except KeyError:
            raise AttributeError(name) from None

进入ContextVar我们会发现ContextVar有两个,一个是系统的ContextVar另一个是本地维护ContextVar类。第一选择使用的是系统的ContextVargreenlet协程,我们主动报错,使其使用本地维护的ContextVar类。这个ContextVar类就是维护一个全局字典,这个字典是线程安全的关键,每个请求对应一个线程ID,通过这个全局字典来维护。

{9064: {'stack': [<flask.ctx.AppContext object at 0x0000016B7E27B748>]}} # 应用上下文

{9064: {'stack': [<RequestContext 'http://127.0.0.1:5000/22' [GET] of test_testing>]}} # 请求上下文

try:
    from contextvars import ContextVar
    # 主动报错,自己维护ContextVar
    raise ImportError("xxxx")
    if "gevent" in sys.modules or "eventlet" in sys.modules:
        # Both use greenlet, so first check it has patched
        # ContextVars, Greenlet <0.4.17 does not.
        import greenlet

        greenlet_patched = getattr(greenlet, "GREENLET_USE_CONTEXT_VARS", False)

        if not greenlet_patched:
            # If Gevent is used, check it has patched ContextVars,
            # <20.5 does not.
            try:
                from gevent.monkey import is_object_patched
            except ImportError:
                # Gevent isn't used, but Greenlet is and hasn't patched
                raise _CannotUseContextVar() from None
            else:
                if is_object_patched("threading", "local") and not is_object_patched(
                    "contextvars", "ContextVar"
                ):
                    raise _CannotUseContextVar()

    def __release_local__(storage: t.Any) -> None:
        # Can remove when support for non-stdlib ContextVars is
        # removed, see "Fake" version below.
        storage.set({})


except (ImportError, _CannotUseContextVar):

    class ContextVar:  # type: ignore
        """A fake ContextVar based on the previous greenlet/threading
        ident function. Used on Python 3.6, eventlet, and old versions
        of gevent.
        """

        def __init__(self, _name: str) -> None:
            self.storage: t.Dict[int, t.Dict[str, t.Any]] = {}

        def get(self, default: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
            print(self.storage, _get_ident(), default, "1111111")
            return self.storage.get(_get_ident(), default)

        def set(self, value: t.Dict[str, t.Any]) -> None:
            self.storage[_get_ident()] = value
            print(self.storage, "000000")

    def __release_local__(storage: t.Any) -> None:
        # Special version to ensure that the storage is cleaned up on
        # release.
        # 释放栈
        print("storage.storage", _get_ident(), storage.storage)
        storage.storage.pop(_get_ident(), None)
        
...
    
try:
    from greenlet import getcurrent as _get_ident
    raise ImportError("xxxx")
except ImportError:
    from threading import get_ident as _get_ident
    
def get_ident() -> int:
    warnings.warn(
        "'get_ident' is deprecated and will be removed in Werkzeug"
        " 2.1. Use 'greenlet.getcurrent' or 'threading.get_ident' for"
        " previous behavior.",
        DeprecationWarning,
        stacklevel=2,
    )
    return _get_ident()  # type: ignore

流程篇

此处有图,后面再补

  1. 启动时调用 __call__ 方法
class Flask(_PackageBoundObject):
    ...
    # step 1
    def __call__(self, environ, start_response):
        """Shortcut for :attr:`wsgi_app`."""
        return self.wsgi_app(environ, start_response)
  1. 初始化请求上下文
class Flask(_PackageBoundObject):
    ...
    
    def wsgi_app(self, environ, start_response):
        # 初始化请求上下文
        # step 2
        ctx = self.request_context(environ)
        # 将请求上下文 推进_request_ctx_stack栈中
        # step 3
        ctx.push()
        error = None
        try:
            try:
                # 分发请求 获取结果
                # step 4
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
         	except:
             	error = sys.exc_info()[1]
             	raise
            # 返回结果
            # step 5
          	return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
             # 弹出_request_ctx_stack、_app_ctx_stack栈数据
             # step 6
             ctx.auto_pop(error)
  1. 将请求推入_request_ctx_stack栈中和应用推入_app_ctx_stack栈中
class RequestContext(object):
    ...
    
    def push(self):
        """Binds the request context to the current context."""
        # If an exception occurs in debug mode or if context preservation is
        # activated under exception situations exactly one context stays
        # on the stack.  The rationale is that you want to access that
        # information under debug situations.  However if someone forgets to
        # pop that context again we want to make sure that on the next push
        # it's invalidated, otherwise we run at risk that something leaks
        # memory.  This is usually only a problem in test suite since this
        # functionality is not active in production environments.
        top = _request_ctx_stack.top
        print("_request_ctx_stack.top", top)
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)

        # Before we push the request context we have to ensure that there
        # is an application context.
        # 初始化应用上下文
        # step 3.1
        print("_app_ctx_stack", _app_ctx_stack)
        app_ctx = _app_ctx_stack.top
        print("_app_ctx_stack.top", app_ctx)
        if app_ctx is None or app_ctx.app != self.app:
            # 初始化应用上下文
            app_ctx = self.app.app_context()
            print("app_ctx", app_ctx)
            # 将应用上下文推入应用上下文栈中
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)
        print("self._implicit_app_ctx_stack", self._implicit_app_ctx_stack)
        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()
		# step 3.2
        _request_ctx_stack.push(self)
        print("_request_ctx_stack", _request_ctx_stack, 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).
        # 处理session
        # step 3.3
        self.session = self.app.open_session(self.request)
        print("self.session", self.session)
        if self.session is None:
            self.session = self.app.make_null_session()
            print("self.session", self.session)

            
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.send(self.app)
  1. 派发、处理请求
class Flask(_PackageBoundObject):
    ...
    # step 4.1
    def full_dispatch_request(self):
        """Dispatches the request and on top of that performs request
        pre and postprocessing as well as HTTP exception catching and
        error handling.

        .. versionadded:: 0.7
        """
        self.try_trigger_before_first_request_functions()
        try:
            request_started.send(self)
            # 
            rv = self.preprocess_request()
            if rv is None:
                # 派发请求
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)
    
    # step 4.2
    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.

        .. versionchanged:: 0.7
           This no longer does the exception handling, this code was
           moved to the new :meth:`full_dispatch_request`.
        """
        req = _request_ctx_stack.top.request
        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        rule = req.url_rule
        # if we provide automatic options for this URL and the
        # request came with the OPTIONS method, reply automatically
        if getattr(rule, 'provide_automatic_options', False) \
           and req.method == 'OPTIONS':
            return self.make_default_options_response()
        # otherwise dispatch to the handler for that endpoint
        print("rule.endpoint", rule.endpoint)
        print("self.view_functions", self.view_functions, "req.view_args", req.view_args)
        # 执行views
        return self.view_functions[rule.endpoint](**req.view_args)
    
    # step 4.3
	def finalize_request(self, rv, from_error_handler=False):
        """Given the return value from a view function this finalizes
        the request by converting it into a response and invoking the
        postprocessing functions.  This is invoked for both normal
        request dispatching as well as error handlers.

        Because this means that it might be called as a result of a
        failure a special safe mode is available which can be enabled
        with the `from_error_handler` flag.  If enabled, failures in
        response processing will be logged and otherwise ignored.

        :internal:
        """
        response = self.make_response(rv)
        try:
            # 处理响应
            response = self.process_response(response)
            request_finished.send(self, response=response)
        except Exception:
            if not from_error_handler:
                raise
            self.logger.exception('Request finalizing failed with an '
                                  'error while handling an error')
        return response
    
    # step 4.4
	def process_response(self, response):
        """Can be overridden in order to modify the response object
        before it's sent to the WSGI server.  By default this will
        call all the :meth:`after_request` decorated functions.

        .. versionchanged:: 0.5
           As of Flask 0.5 the functions registered for after request
           execution are called in reverse order of registration.

        :param response: a :attr:`response_class` object.
        :return: a new response object or the same, has to be an
                 instance of :attr:`response_class`.
        """
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprint
        funcs = ctx._after_request_functions
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
        for handler in funcs:
            response = handler(response)
        # step 4.4.1
        if not self.session_interface.is_null_session(ctx.session):
            # 增加session
            self.save_session(ctx.session, response)
        return response
  1. _request_ctx_stack_app_ctx_stack栈中弹出当前请求上下文、应用上下文
class RequestContext(object):
    ...
    # step 6.2
    def pop(self, exc=_sentinel):
        """Pops the request context and unbinds it by doing that.  This will
        also trigger the execution of functions registered by the
        :meth:`~flask.Flask.teardown_request` decorator.

        .. versionchanged:: 0.9
           Added the `exc` argument.
        """
        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]
                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)
                
	# step 6.1
    def auto_pop(self, exc):
        print("auto_pop", 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)

标签:__,Flask,ctx,self,request,源码,阅读,app,stack
来源: https://www.cnblogs.com/taozhengquan/p/15967701.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有