MOOC Flask advanced programming practice-4.flask core mechanism

MOOC Flask advanced programming practice-4.flask core mechanism

4.1 classic error in flask working outside application context

In Section 3.8, we db.create_all(app=app)solved the error of working outside application context through the method. Let's take a closer look at the specific cause of this error.

First write a piece of test code

from flask import Flask, current_app

__author__ = "gaowenfeng"

app = Flask(__name__)

#  current_app=[LocalProxy]<LocalProxy unbound>
a = current_app

# RuntimeError: Working outside of application context.
b = current_app.config["DEBUG"]
 

We get the configuration through current_app, and the code that seems to have no problem throws the same exception.

Through breakpoint debugging, it is found that current_app is not a Flask object, but an unbound LocalProxy.

Recall that our previous request object is actually a LocalProxy.

# 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'))
 

So why is this exception being thrown here? To answer this question, you need to understand this LocalProxy in depth. We will introduce in the next section

4.2 The relationship between AppContext, RequestContext, Flask and Request

1. Locate AppContext, RequestContext

Flask has two contexts, application context-AppContext and request context-RequestContext. They are all objects in nature, a kind of encapsulation. The application context is an encapsulation of Flask, and the request context is an encapsulation of Request

Let's use the source code to understand these two contexts. The full picture of the Flask source code is under External Libraries/site-pacages/flask

Flask is a very good micro-framework. There are not many source codes in it. Most of them are comments, which gives us a convenient way to read the source code.

The two contexts we want to look at are in ctx.py (short for context), where AppContext is the application context, and RequestContext is the request context

Read the constructors of AppContext and RequestContext and found that they both use the core object app as one of their attributes

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
 

And they all have the same four methods

def push(self):
...
def pop(self, exc=_sentinel):
...
def __enter__(self):
...
def __exit__(self, exc_type, exc_value, tb):
...
 

2. Why do you need context

Why do we need context? Can't we operate Flask's core object app?

This is a design idea. Sometimes, we not only need the core object app, but also some external things. At this time, we can combine them together and encapsulate them together to form a new context object, and on top of this object, we can provide some New methods, such as push, pop, etc. mentioned above

3. Make an explanation of the meaning of AppContext, RequestContext, Flask and Request

  • Flask: The core object, which carries a variety of functions, such as saving configuration information, and registering routing attempt functions, etc.
  • AppContext: encapsulation of Flask, and added some additional parameters
  • Request: Save the request information, such as url parameters, url's full path and other information
  • RequestContext: Encapsulation of Request

In the actual coding process, we may need Flask or Request information, but this does not mean that we should directly import these two objects to obtain relevant information. The correct approach is to indirectly obtain the information we need from AppContext and RequestContext.

Even so, we do not need to import Context to use the context, which returns to the LocalProxy of current_app and request, which provide the ability to indirectly manipulate the context object and use the proxy mode

4.3 Explain flask context and stacking in detail

How Flask works

1. When a request enters the Flask framework, it will first instantiate a Request Context. This context encapsulates the requested information in the Request, and pushes this context into a stack (_request_ctx_stack/_app_ctx_strack) structure, that is, the previous push method

2. Before RequestContext enters _request_ctx_stack, it will first check whether _app_ctx_strack is empty, if it is empty, an AppContext object will be pushed onto the stack, and then the request will be pushed onto _request_ctx_stack

3. Our current_app and request objects always point to the top element of _app_ctx_strack/_request_ctx_stack, that is, point to two contexts respectively. If these two values are empty, then LocalProxy will appear in an unbound state

4. When the request is over, the request will be popped-pop

Going back to our previous test code, if we want our test code to run normally, we need to manually push an AppContext onto the stack.

from flask import Flask, current_app

__author__ = "gaowenfeng"

app = Flask(__name__)

#  AppContext return AppContext(self)
ctx = app.app_context()
#  AppContext 
ctx.push()
#  current_app=[LocalProxy]<LocalProxy unbound>
a = current_app

# RuntimeError: Working outside of application context.
b = current_app.config["DEBUG"]
print(b)
 

note

Although current_app and request point to two contexts, they return Flask core exclusive and Request objects. Let's take a look at the source code of this part

globals.py

# globals.py LocalProxy current_app _find_app 
current_app = LocalProxy(_find_app)

def _find_app():
#  
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
#  ctx app 
return top.app
 

As you can see from the source code, what he gets is the core object of the app.

4.4 Flask context and with statement

In the previous section, we solved the problem of working outside application context by manually pushing the app onto the stack and popping the stack. In fact, the more classic approach is to use the with statement to complete.

First use the with statement to replace the previous code

app = Flask(__name__)

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

When can you use the with statement:

1. For objects that implement the context protocol, you can use the with statement. 2. For objects that implement the context protocol, we are usually called context managers. 3. Implement the context protocol by implementing __enter__ and __exit__ 4. Context expressions Must return a context manager

For the above piece of code, AppContext is the context manager; it app.app_context()is the context expression. A push operation was done in __enter__, and a pop operation was done in __exit__. So as long as you enter the with statement, current_app has a value. Once you leave the with statement, current_app will pop up, and then it has no value (it becomes unbound again).

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)
 

Understand the specific meaning of the with statement through the link and release of the database

Steps to connect to the database: 1. Connect to the database 2. SQL or other business logic 3. Release resources If the second part of the above error occurs, then the third part of the released resources will not be executed, and the resources will always be occupied. The usual way to solve this problem is to use, try-except-finally but in the finally a more elegant way is to use the with statement. We can write the operation of connecting to the database in the __enter__ method of the context manager, the business code in the code block of the with statement, and the statement to release the resource in __exit__.

Specific examples of reading and writing files

General writing

try:
f = open(r'/Users/test.txt')
print(f.read())
finally:
f.close()
 

Use the with statement to write:

with open(r'/Users/test.txt') as f:
print(f.read())
 

Note that the as after the with statement above does not return a context manager, it is actually a value returned by the __enter__ method .

In the above piece of code, we returned an a in __enter__, so the obj_A after as below is 1.

Detailed exit method

Note that the test code we wrote will report an error when it runs. The reason for the error is that the number of parameters accepted by the exit method is insufficient. The role of the exit method is not only to release resources, but also to handle exceptions, so the exit method also accepts three parameters, exc_type, exc_value, and tb. These three parameters return control when no exception occurs. If there is an exception, the three parameters are the exception type, exception message, and detailed exception stack information.

The exit method also needs to return a boolean value. If it returns True, then no exception will be thrown outside. If it returns False, it will also throw an exception outside. If nothing is returned, it will be handled as False. Flask provides a very flexible way that allows us to choose whether to handle exceptions inside or outside the with statement

4.6 Read the source code to solve the problem of db.create_all

For Flask, the documentation is more suitable for intermediate and advanced developers, but not particularly friendly to novices. So keep changing. When we encounter problems, we can solve them by reading the source code.

Let's take a look at the third chapter, why our flask_sqlalchemy has already registered the app object, but the create_all method still needs to pass in the app parameter, if it is not passed, an error will be reported

First look at the source code of the init_app method

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.
"""
#  app 
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:".'
)

#  dict 
# setdefault dict 
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
)

if track_modifications is None:
warnings.warn(FSADeprecationWarning(
'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
'will be disabled by default in the future. Set it to True '
'or False to suppress this warning.'
))

app.extensions['sqlalchemy'] = _SQLAlchemyState(self)

@app.teardown_appcontext
def shutdown_session(response_or_exc):
if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:
if response_or_exc is None:
self.session.commit()

self.session.remove()
return response_or_exc
 

Source code of create_app method

def _execute_for_all_tables(self, app, bind, operation, skip_tables=False):
app = self.get_app(app)

if bind == '__all__':
binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ())
elif isinstance(bind, string_types) or bind is None:
binds = [bind]
else:
binds = bind

for bind in binds:
extra = {}
if not skip_tables:
tables = self.get_tables_for_bind(bind)
extra['tables'] = tables
op = getattr(self.Model.metadata, operation)
op(bind=self.get_engine(app, bind), **extra)

def create_all(self, bind='__all__', app=None):
"""Creates all tables.

.. versionchanged:: 0.12
Parameters were added
"""
self._execute_for_all_tables(app, bind, 'create_all')
 

You can see that the create_all method calls the _execute_for_all_tables private method, and the get_app method in the first line of _execute_for_all_tables is used to obtain an app core object

def get_app(self, reference_app=None):
"""Helper method that implements the logic to look up an
application."""

#  app app
if reference_app is not None:
return reference_app
#  current_app current_app
if current_app:
return current_app._get_current_object()
#  app app 
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/.'
)
 

So through three judgments, we can sum up three different methods to solve the problems we encounter. 1. Pass in the keyword parameter app in create_all. That is what we have used before. 2. Push an app_context into the stack so that current_app is not empty.

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

3. When initializing the flask_sqlalchemy object, pass in the app parameters.

The specific method to choose depends on the situation. For example, in our current situation, it is not appropriate to use the third method, because our flask_sqlalchemy object is in book.py in models. If you use the third method , You also need to import the app object here.