Flask源碼解析(理解working outside of application context)
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, ‘current_app源碼request‘)) session = LocalProxy(partial(_lookup_req_object, ‘session‘)) g = LocalProxy(partial(_lookup_app_object, ‘g‘))
如何理解上下文
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.AppContext""" 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)
上下文對象包含了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)