Flash source code reading notes

Keywords: Python Back-end Flask

This article is based on flash version 0.3.

Let's first look at an official example:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()

So what's going on here? Enter the source code to find out.

This is the module that Flask depends on. You can see that Flask relies on Jinja2 and Werkzeug. Most of the templates in Flask encapsulate some general methods of jinja template method, including the common render_template and Werkzeug are tool libraries for WSGI protocol in Python, which support most functions of the framework, such as Request and Response objects, LocalStack and LocalProxy are proxy objects using local threads, and Map and Rule are responsible for defining template rules and mapping methods for routing, SecureCookie is the parent class to be inherited by the Session object. It contains some mechanisms and methods for handling cookies. In addition, some middleware (shared data middleware), some auxiliary functions (create_environment, wrap_file, cached_property), exception classes (HTTPException,InternalServerError), etc. are imported.

//...
from jinja2 import Environment, PackageLoader, FileSystemLoader
from werkzeug import Request as RequestBase, Response as ResponseBase, \
     LocalStack, LocalProxy, create_environ, SharedDataMiddleware, \
     ImmutableDict, cached_property, wrap_file, Headers, \
     import_string
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, InternalServerError
from werkzeug.contrib.securecookie import SecureCookie

First, look at what the Flask class does when it is initialized:

  • A Config object is initialized, that is, the global configuration used by the application. It is a dict object, but various configuration loading methods (from_envvar,from_pyfile,from_object) are extended.
  • View initialized_ Functions (register all view functions, such as hello in the example), and initialize error_handlers (which handles jump view functions for errors like 404500).
  • Before initialized_ request_ FuncS and after_request_funcs, which correspond to the dictionary object of the hook function invoked before and after the request is stored.
  • URL initialized_ Map, the map class implemented in Werkzeug, is responsible for routing mapping.
  • The configuration of static resource path and some initialization of jinja (a template engine) are done.
class Flask(_PackageBoundObject):
    request_class = Request

    response_class = Response

    static_path = '/static'

    debug = ConfigAttribute('DEBUG')

    secret_key = ConfigAttribute('SECRET_KEY')

    session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME')

    permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME')

    use_x_sendfile = ConfigAttribute('USE_X_SENDFILE')

    debug_log_format = (
        '-' * 80 + '\n' +
        '%(levelname)s in %(module)s, %(pathname)s:%(lineno)d]:\n' +
        '%(message)s\n' +
        '-' * 80
    )

    jinja_options = ImmutableDict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
    )

    default_config = ImmutableDict({
        'DEBUG':                                False,
        'SECRET_KEY':                           None,
        'SESSION_COOKIE_NAME':                  'session',
        'PERMANENT_SESSION_LIFETIME':           timedelta(days=31),
        'USE_X_SENDFILE':                       False
    })

    def __init__(self, import_name):
        _PackageBoundObject.__init__(self, import_name)

        self.config = Config(self.root_path, self.default_config)
        self.view_functions = {}
        self.error_handlers = {}
        self.before_request_funcs = {}
        self.after_request_funcs = {}
        self.template_context_processors = {
            None: [_default_template_ctx_processor]
        }

        self.url_map = Map()

        if self.static_path is not None:
            self.add_url_rule(self.static_path + '/<filename>',
                              build_only=True, endpoint='static')
            if pkg_resources is not None:
                target = (self.import_name, 'static')
            else:
                target = os.path.join(self.root_path, 'static')
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
                self.static_path: target
            })

        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        self.jinja_env.globals.update(
            url_for=url_for,
            get_flashed_messages=get_flashed_messages
        )
        self.jinja_env.filters['tojson'] = _tojson_filter

What did you do when calling the @ app.route decorator? It's actually going to the URL_ A rule is added to the map, including information such as endpoint and method. Finally, the endpoint will be passed through the view_functions are bound to concrete functions (hello).

def route(self, rule, **options):
    def decorator(f):
        self.add_url_rule(rule, None, f, **options)
        return f
    return decorator

def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
    if endpoint is None:
        assert view_func is not None, 'expected view func if endpoint ' \
        'is not provided.'
    endpoint = view_func.__name__
    options['endpoint'] = endpoint
    options.setdefault('methods', ('GET',))
    self.url_map.add(Rule(rule, **options))
    if view_func is not None:
        self.view_functions[endpoint] = view_func

Next, let's look at the implementation of the run method, which is to pass the Flask class instance as a wsgi object to Werkzeug, and then use Werkzeug to run the web service.

def run(self, host='127.0.0.1', port=5000, **options):
    from werkzeug import run_simple
    if 'debug' in options:
        self.debug = options.pop('debug')
    options.setdefault('use_reloader', self.debug)
    options.setdefault('use_debugger', self.debug)
    return run_simple(host, port, self, **options)

The Flask class also reflects the place where the WSGI protocol is implemented. The wsgi_app method is the application object of WSGI, and the call of this method is returned in the _call_method. Therefore, when using some WSGI servers (such as wsgiref, gunicorn, uwsgi, etc.), we can pass the app instance to the web server.

def wsgi_app(self, environ, start_response):
    with self.request_context(environ):
        try:
            rv = self.preprocess_request()
            if rv is None:
                rv = self.dispatch_request()
            response = self.make_response(rv)
            response = self.process_response(response)
        except Exception, e:
            response = self.make_response(self.handle_exception(e))
        return response(environ, start_response)

def __call__(self, environ, start_response):
    """Shortcut for :attr:`wsgi_app`."""
    return self.wsgi_app(environ, start_response)

As you can see from the above, the Flask class inherits a Mixin class: _PackageBoundObject. As a Mixin that wraps a module or package, it will inherit this _packageboundobjectwhether it is an app or a module. Although BluePrint has not appeared in version 0.3, it already has the concept of multiple modules. For example, the module class to be mentioned below can be found in Admin and main modules are defined in an app, so admin and main here are objects wrapped by _packagebondobject, which contains the import name (import_name, used in register) and root path (root_path, which defines the path to find this module).

_ModuleSetupState is used to wrap the module when it is loaded Second hand QQ sale The object of module information is strongly associated with register_module in the Flask class.

class _PackageBoundObject(object):

    def __init__(self, import_name):
        #: the name of the package or module.  Do not change this once
        #: it was set by the constructor.
        self.import_name = import_name

        #: where is the app root located?
        self.root_path = _get_package_path(self.import_name)

    def open_resource(self, resource):
        if pkg_resources is None:
            return open(os.path.join(self.root_path, resource), 'rb')
        return pkg_resources.resource_stream(self.import_name, resource)


class _ModuleSetupState(object):

    def __init__(self, app, url_prefix=None):
        self.app = app
        self.url_prefix = url_prefix

The Module class in flask also inherits _packagebondobject. As a part of the modular design of flask, the Module can be registered by the flask app as a sub Module. The Module contains some basic information: name (Module name), url_prefix (routing address), _register_events (event function) , route, add_url_rule these two methods provide a way to add view function routes, which are familiar to anyone who has used flash, especially @ app.route('/') This decoration method is implemented here. Finally, it will call the add_url_rule method. Pay attention to the statement of state.app.add_url_rule in it. This state is the above _ModuleSetupState. In fact, when registering the Module, the flash app will call all events in _register_events in the Module. Take a closer look at the add_url_rule method in the Module. It will R_rule this event is placed in _register_events. If you want to know why, you can directly look at the register_module method in the flag class. In fact, when the app is in register_module, it will generate a state(_ModuleSetupState) , it contains the instance of the flash app itself, then traverses _register_events, takes state as a parameter, and calls all event functions! So let's look at the state.app.add_url_rule in the add_url_rule method in the Module. In fact, its function is to register the route in the url_map of the flash app where the current Module is located. You can see that it will add the Module itself in front of the route Similarly, the following hook s such as before_request, before_app_request, after_request, after_app_request, etc. return an event function that requires state as a parameter. That is, the work done on the distributed Module will eventually be implemented in the flash app.

class Module(_PackageBoundObject):

    def __init__(self, import_name, name=None, url_prefix=None):
        if name is None:
            assert '.' in import_name, 'name required if package name ' \
                'does not point to a submodule'
            name = import_name.rsplit('.', 1)[1]
        _PackageBoundObject.__init__(self, import_name)
        self.name = name
        self.url_prefix = url_prefix
        self._register_events = []

    def route(self, rule, **options):
        def decorator(f):
            self.add_url_rule(rule, f.__name__, f, **options)
            return f
        return decorator

    def add_url_rule(self, rule, endpoint, view_func=None, **options):
        def register_rule(state):
            the_rule = rule
            if state.url_prefix:
                the_rule = state.url_prefix + rule
            state.app.add_url_rule(the_rule, '%s.%s' % (self.name, endpoint),
                                   view_func, **options)
        self._record(register_rule)

    def before_request(self, f):
        self._record(lambda s: s.app.before_request_funcs
            .setdefault(self.name, []).append(f))
        return f

    def before_app_request(self, f):
        self._record(lambda s: s.app.before_request_funcs
            .setdefault(None, []).append(f))
        return f

    def after_request(self, f):
        self._record(lambda s: s.app.after_request_funcs
            .setdefault(self.name, []).append(f))
        return f

    def after_app_request(self, f):
        self._record(lambda s: s.app.after_request_funcs
            .setdefault(None, []).append(f))
        return f

    defcontext_processor(self,f):
        self._record(lambda s: s.app.template_context_processors
            .setdefault(self.name, []).append(f))
        return f

    defapp_context_processor(self,f):
        self._record(lambda s: s.app.template_context_processors
            .setdefault(None, []).append(f))
        return f

    def_record(self,func):
        self._register_events.append(func)

The modular mechanism of Flask should be clear now. Then, how does Flask handle each incoming request?

Flask defines Request and Response classes, corresponding to Request and Response objects respectively. They inherit Werkzeug's Request and Response objects. They have helped you encapsulate most things (such as http header parsing, environ, path_info, json, exception handling, etc.).

_RequestGlobals is a collection of global objects in a request, that is, g objects in flash.

class Request(RequestBase):

    endpoint = view_args = routing_exception = None

    @property
    def module(self):
        if self.endpoint and '.' in self.endpoint:
            return self.endpoint.rsplit('.', 1)[]

    @cached_property
    def json(self):
        if __debug__:
            _assert_have_json()
        if self.mimetype == 'application/json':
            return json.loads(self.data)


class Response(ResponseBase):
    default_mimetype = 'text/html'


class _RequestGlobals(object):
    pass

Students who have used flash know that when the view function wants to obtain the current request, it will generally use the from flash import request, and then use the request object anywhere, which is very convenient.

How is this mechanism for obtaining requests implemented? Looking at the flash source code, we can see that it defines several global objects:

# context locals
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)

They are ThreadLocal objects provided by Flask:

  • _request_ctx_stack: the stack that holds the request context object (the following stack represents this).
  • current_app: the proxy object of the application (APP) where the stack top request (i.e. the current request) is located.
  • Request: the proxy object for the request at the top of the stack.
  • Session: the proxy object of the session requested at the top of the stack.
  • g: The proxy object for the global object requested at the top of the stack.

You can see that the request object is a proxy for the request context object_ request_ctx_stack is actually a maintenance_ RequestContext stack, what is its function? In fact, whenever flash accepts a request, it will encapsulate it and some information into one_ An instance of RequestContext, and then insert the request context into_ request_ctx_stack stack. When the request ends, the stack will_ RequestContext pops up. Obviously, this_ request_ctx_stack is the stack that stores the request context. In addition, it is also a LocalStack, that is, a stack implemented by local threads (ThreadLocal). It ensures that the request object (global object) is thread isolated in a multi-threaded environment. Here, you can also observe the differences between flash and django, that is, writing a view function in django usually takes a request parameter, That's why flash doesn't.

So_ What is RequestContext_ RequestContext is a very important concept in flash. Its semantics is the context of the request. It encapsulates all relevant information about the request, such as:

  • app: the application where the request is located (flash is a multi application mechanism).
  • url_adapter: the adapter responsible for url mapping, associated with Werkzeug's Map.
  • Request: the current request object.
  • Session: the session object of the current request.
  • g: That is, the front_ RequestGlobal, which stores the global object about the current request. For example, we usually use it to store the user object when logging in.
  • flashes: Flash message about the current request.
class _RequestContext(object):

    def __init__(self, app, environ):
        self.app = app
        self.url_adapter = app.url_map.bind_to_environ(environ)
        self.request = app.request_class(environ)
        self.session = app.open_session(self.request)
        if self.session is None:
            self.session = _NullSession()
        self.g = _RequestGlobals()
        self.flashes = None

        try:
            self.request.endpoint, self.request.view_args = \
                self.url_adapter.match()
        except HTTPException, e:
            self.request.routing_exception = e

    def push(self):
        _request_ctx_stack.push(self)

    def pop(self):
        _request_ctx_stack.pop()

    def __enter__(self):
        self.push()
        return self

    def __exit__(self, exc_type, exc_value, tb):
        if tb is None or not self.app.debug:
            self.pop()

Others such as flash, url_for, Session and other interesting functions are not mentioned separately. The main implementation of flash is basically over here. Generally speaking, the flash code is very pleasant.

Posted by vinodkalpaka on Fri, 29 Oct 2021 01:19:21 -0700