1. 程式人生 > 其它 >Flask原始碼閱讀

Flask原始碼閱讀

上下文篇

整個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)
此時此刻,非我莫屬