1. 程式人生 > >Flask源碼解析(理解working outside of application context)

Flask源碼解析(理解working outside of application context)

dir IE ide info reference 檢查 acc 想要 inside

from flask import Flask, current_app

app = Flask(__name__)

a = current_app

d = current_app.config[DEBUG]

  首先從這段代碼看起,代碼運行的結果就是

RuntimeError: Working outside of application context.

技術分享圖片

  此時本地代理未綁定,不是我們想要的核心flask對象。代碼報錯。

技術分享圖片
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))
current_app源碼

  如何理解上下文

  flask分為應用上下文(對Flask核心對象封裝並提供方法)與請求上下文對象(對Request對象請求封裝)兩種。

技術分享圖片

  在flask源碼的ctx下可以找到flask的AppContext與RequestContext。

技術分享圖片
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 # flask核心對象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)
AppContext

  上下文對象包含了flask(封裝路由,配置信息等等)等對象的同時,還包含了一些其他的對象,並提供方法供外部使用。我們使用flask與request對象並不意味著我們就要直接import導入,而是使用上下文調用。而在flask中我們,那怎麽使用上下文呢?

技術分享圖片

  上圖就是flask運行的核心機制,從請求進入flask開始。

  第一步,接收到一個request請求。生成一個request context請求上下文(封裝本次請求信息)。準備將上下文推入棧中。

  第二步,在request入棧之前先去檢查app_ctx_stack的棧頂,如果棧頂為空或者不是當前對象,那麽將app上下文推入_app_ctx_stack棧。

  第三步,並將上下文推入(上下文的push方法)LocalStack棧中(實例化為_request_ctx_stack)。

# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()

  request和current_app永遠都是指向這兩個棧頂的元素,操作他就是操作棧頂元素。

  現在我們就可以解釋開始的問題了,我們使用current_app時,沒有請求進來,也沒有request上下文生成入棧的過程,那麽app上下文也就不會入棧,current_app拿到的棧頂上下文為空。所以結果是unbound。

current_app = LocalProxy(_find_app)

def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app # 返回給current_app就是一個app核心對象

  request返回的也不是上下文對象,而是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)

request = LocalProxy(partial(_lookup_req_object, request))傳入字符串就是request,partial就是執行_lookup_req_object

  第四步,當請求結束,兩個棧的元素會被彈出(pop)。

with app.app_context():
    a = current_app
    d = current_app.config[DEBUG]

  上下文裏做了什麽?

    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)

  with語句就是為了實現對資源管理。(連接數據庫,sql,釋放資源,當sql出了問題時釋放資源就尤其的重要。) 

技術分享圖片

  這裏的A()是上下文管理器,as後面一定不是上下文管理器。而是enter方法返回的值。我們對實現上下文協議對象使用with,實現上下文協議的就是上下文管理器。上下文管理器必須有enter和exit。上下文表達式必須返回一個上下文管理器。

  我們初始化是如何找到數據庫連接的信息的,setdefault進行了防禦性編程。

技術分享圖片
    def init_app(self, app):
        """This callback can be used to initialize an application for the
        use with this database setup.  Never use a database in the context
        of an application not initialized that way or connections will
        leak.
        """
        if (
            SQLALCHEMY_DATABASE_URI not in app.config and
            SQLALCHEMY_BINDS not in app.config
        ):
            warnings.warn(
                Neither SQLALCHEMY_DATABASE_URI nor SQLALCHEMY_BINDS is set. 
                Defaulting SQLALCHEMY_DATABASE_URI to "sqlite:///:memory:".
            )

        app.config.setdefault(SQLALCHEMY_DATABASE_URI, sqlite:///:memory:)
        app.config.setdefault(SQLALCHEMY_BINDS, None)
        app.config.setdefault(SQLALCHEMY_NATIVE_UNICODE, None)
        app.config.setdefault(SQLALCHEMY_ECHO, False)
        app.config.setdefault(SQLALCHEMY_RECORD_QUERIES, None)
        app.config.setdefault(SQLALCHEMY_POOL_SIZE, None)
        app.config.setdefault(SQLALCHEMY_POOL_TIMEOUT, None)
        app.config.setdefault(SQLALCHEMY_POOL_RECYCLE, None)
        app.config.setdefault(SQLALCHEMY_MAX_OVERFLOW, None)
        app.config.setdefault(SQLALCHEMY_COMMIT_ON_TEARDOWN, False)
        track_modifications = app.config.setdefault(
            SQLALCHEMY_TRACK_MODIFICATIONS, None
        )
init_app源碼
db.init_app(app)
db.create_all()

  SQLAlchemy在實例化的時候沒有將核心app綁定到db(SQLAlchemy的實例化對象),所以直接creat_all()並不能將db關聯到核心app上。那麽我們怎麽完成db與app的綁定呢?

技術分享圖片
    def create_all(self, bind=__all__, app=None):# self就是我們的db
        """Creates all tables.

        .. versionchanged:: 0.12
           Parameters were added
        """
        self._execute_for_all_tables(app, bind, create_all)# 這裏面將db與app綁定
create_all源碼 技術分享圖片
    def get_app(self, reference_app=None):
        """Helper method that implements the logic to look up an
        application."""

        if reference_app is not None:
            return reference_app

        if current_app:
            return current_app._get_current_object()

        if self.app is not None:
            return self.app

        raise RuntimeError(
            No application found. Either work inside a view function or push
             an application context. See
             http://flask-sqlalchemy.pocoo.org/contexts/.
        )
讀到這裏給了三種解決思路

  第一種,將核心app傳給create_all()作為參數,reference_app會得到hexinapp返回完成綁定。

db.init_app(app)
db.create_all(app=app)
技術分享圖片
    def get_engine(self, app=None, bind=None):
        """Returns a specific engine."""

        app = self.get_app(app)
        state = get_state(app)

        with self._engine_lock:
            connector = state.connectors.get(bind)

            if connector is None:
                connector = self.make_connector(app, bind)
                state.connectors[bind] = connector

            return connector.get_engine()
get_engine源碼

  第二種,將上下文推入棧,current_app得到值後綁定。

    with app.app_context():
        db.create_all()

  第三種,在實例化db的時候加入app。在這裏我們采取其他方式。

技術分享圖片
class SQLAlchemy(object):
    """This class is used to control the SQLAlchemy integration to one
    or more Flask applications.  Depending on how you initialize the
    object it is usable right away or will attach as needed to a
    Flask application.

    There are two usage modes which work very similarly.  One is binding
    the instance to a very specific Flask application::

        app = Flask(__name__)
        db = SQLAlchemy(app)

    The second possibility is to create the object once and configure the
    application later to support it::

        db = SQLAlchemy()

        def create_app():
            app = Flask(__name__)
            db.init_app(app)
            return app

    The difference between the two is that in the first case methods like
    :meth:`create_all` and :meth:`drop_all` will work all the time but in
    the second case a :meth:`flask.Flask.app_context` has to exist.

    By default Flask-SQLAlchemy will apply some backend-specific settings
    to improve your experience with them.  As of SQLAlchemy 0.6 SQLAlchemy
    will probe the library for native unicode support.  If it detects
    unicode it will let the library handle that, otherwise do that itself.
    Sometimes this detection can fail in which case you might want to set
    ``use_native_unicode`` (or the ``SQLALCHEMY_NATIVE_UNICODE`` configuration
    key) to ``False``.  Note that the configuration key overrides the
    value you pass to the constructor.

    This class also provides access to all the SQLAlchemy functions and classes
    from the :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` modules.  So you can
    declare models like this::

        class User(db.Model):
            username = db.Column(db.String(80), unique=True)
            pw_hash = db.Column(db.String(80))

    You can still use :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` directly, but
    note that Flask-SQLAlchemy customizations are available only through an
    instance of this :class:`SQLAlchemy` class.  Query classes default to
    :class:`BaseQuery` for `db.Query`, `db.Model.query_class`, and the default
    query_class for `db.relationship` and `db.backref`.  If you use these
    interfaces through :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` directly,
    the default query class will be that of :mod:`sqlalchemy`.

    .. admonition:: Check types carefully

       Don‘t perform type or `isinstance` checks against `db.Table`, which
       emulates `Table` behavior but is not a class. `db.Table` exposes the
       `Table` interface, but is a function which allows omission of metadata.

    The ``session_options`` parameter, if provided, is a dict of parameters
    to be passed to the session constructor.  See :class:`~sqlalchemy.orm.session.Session`
    for the standard options.

    .. versionadded:: 0.10
       The `session_options` parameter was added.

    .. versionadded:: 0.16
       `scopefunc` is now accepted on `session_options`. It allows specifying
        a custom function which will define the SQLAlchemy session‘s scoping.

    .. versionadded:: 2.1
       The `metadata` parameter was added. This allows for setting custom
       naming conventions among other, non-trivial things.

    .. versionadded:: 3.0
       The `query_class` parameter was added, to allow customisation
       of the query class, in place of the default of :class:`BaseQuery`.

       The `model_class` parameter was added, which allows a custom model
       class to be used in place of :class:`Model`.

    .. versionchanged:: 3.0
       Utilise the same query class across `session`, `Model.query` and `Query`.
    """

    #: Default query class used by :attr:`Model.query` and other queries.
    #: Customize this by passing ``query_class`` to :func:`SQLAlchemy`.
    #: Defaults to :class:`BaseQuery`.
    Query = None

    def __init__(self, app=None, use_native_unicode=True, session_options=None,
                 metadata=None, query_class=BaseQuery, model_class=Model):

        self.use_native_unicode = use_native_unicode
        self.Query = query_class
        self.session = self.create_scoped_session(session_options)
        self.Model = self.make_declarative_base(model_class, metadata)
        self._engine_lock = Lock()
        self.app = app
        _include_sqlalchemy(self, query_class)

        if app is not None:
            self.init_app(app)
SQLAlchemy源碼(部分)

  在實例化時是有app傳進來的,但是在這個模塊下,我們需要再導入app核心對象。在這樣的業務環境下不推薦使用,但是我們可以這樣操作:

    db.init_app(app)
    db.app = app
    db.create_all()

Flask源碼解析(理解working outside of application context)