Categories
Uncategorized

Python Web Flask source interpretation (three) – template rendering process

about me
    A thoughtful program ape, lifelong learning practitioners, is currently in a start-up team any team lead, technology stack involves Android, Python, Java, and Go, this is the main technology stack our team.
    Github: https: //github.com/hylinux1024
    Micro-channel public number: Lifetime developer (angrycode)

Flask front of the startup process and routing principles have been the source commuting. Today we take a look at the process template rendering.

0x00 using a template

First look at an example using a template to render official document from

from flask import render_template

@app.route('/hello/')
@app.route('/hello/')
def hello(name=None):
    return render_template('hello.html', name=name)

Under the project directory needs to have a templates directory and creates a file hello.html

/templates
    /hello.html

Hello.html content is


Hello from Flask
{% if name %}
  

Hello {{ name }}!

{% else %}

Hello, World!

{% endif %}

This template name is a parameter, you can achieve rendering html template file according to the parameters by calling render_template method.

0x01 Flask.render_template

def render_template(template_name, **context):
    """Renders a template from the template folder with the given
    context.

    :param template_name: the name of the template to be rendered
    :param context: the variables that should be available in the
                    context of the template.
    """
    current_app.update_template_context(context)
    return current_app.jinja_env.get_template(template_name).render(context)

Notes approach is clear, find the folder name from the templates to render template_name file. Which is initialized by the following statement current_app

_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)

LocalStack is an implementation class stack. And it is in the _request_ctx_stack Flask.request_context () method of the current context instance to push the stack inside

def request_context(self, environ):
    """Creates a request context from the given environment and binds
    it to the current context.  This must be used in combination with
    the `with` statement because the request is only bound to the
    current context for the duration of the `with` block.

    Example usage::

        with app.request_context(environ):
            do_something_with(request)

    :params environ: a WSGI environment
    """
    return _RequestContext(self, environ)

_RequestContext class implements the context management protocol, it can be used with statement

class _RequestContext(object):
    """The request context contains all request relevant information.  It is
    created at the beginning of the request and pushed to the
    `_request_ctx_stack` and removed at the end of it.  It will create the
    URL adapter and request object for the WSGI environment provided.
    """

    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)
        self.g = _RequestGlobals()
        self.flashes = None

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

    def __exit__(self, exc_type, exc_value, tb):
        # do not pop the request stack if we are in debug mode and an
        # exception happened.  This will allow the debugger to still
        # access the request object in the interactive shell.
        if tb is None or not self.app.debug:
            _request_ctx_stack.pop()

__Enter __ perform operations push (time), on the implementation of pop operation with exit statement.
    Back request_context () method, which is called wsgi_app () in

def wsgi_app(self, environ, start_response):
    """The actual WSGI application.  This is not implemented in
    `__call__` so that middlewares can be applied:

        app.wsgi_app = MyMiddleware(app.wsgi_app)

    :param environ: a WSGI environment
    :param start_response: a callable accepting a status code,
                           a list of headers and an optional
                           exception context to start the response
    """
    with self.request_context(environ):
        rv = self.preprocess_request()
        if rv is None:
            rv = self.dispatch_request()
        response = self.make_response(rv)
        response = self.process_response(response)
        return response(environ, start_response)

Know from the analysis of the routing principle of the article, wsgi_app () will be executed when the server receives a client request.
    Therefore, when a request comes, the request will Flask current context instance is saved to stack instance _request_ctx_stack instance in; the request processing context instance to pop from the stack inside the current request.

LocalProxy is a proxy class, its constructor is passed a lambda expression: lambda: _request_ctx_stack.top.app.
    This operation took current context instance is encapsulated by LocalProxy, i.e., the context agent current_app Flask current instance.
    So when current_app.jinja_env this statement is actually an instance attribute access Flask jinja_env, this property is initialized in the constructor’s Flask.

class Flask(object):
    ...
    #: 源码太长了省略
    #: options that are passed directly to the Jinja2 environment
    jinja_options = dict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
    )

    def __init__(self, package_name):
        ...
        #: 源码太长省略部分源码
        #: the Jinja2 environment.  It is created from the
        #: :attr:`jinja_options` and the loader that is returned
        #: by the :meth:`create_jinja_loader` function.
        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
        )

Environment is an example of jinja_env. This is jinja template engine provides a class template Flask frame rendering is implemented by the jinja.
    Environment needs a loader, is obtained by the following method

def create_jinja_loader(self):
    """Creates the Jinja loader.  By default just a package loader for
    the configured package is returned that looks up templates in the
    `templates` folder.  To add other loaders it's possible to
    override this method.
    """
    if pkg_resources is None:
        return FileSystemLoader(os.path.join(self.root_path, 'templates'))
    return PackageLoader(self.package_name)

The default is to construct an example of a FileSystemLoader templates from the catalog, the role of this class is to load the template file from the file system.

0x02 Environment.get_template

@internalcode
def get_template(self, name, parent=None, globals=None):
    """Load a template from the loader.  If a loader is configured this
    method ask the loader for the template and returns a :class:`Template`.
    If the `parent` parameter is not `None`, :meth:`join_path` is called
    to get the real template name before loading.

    The `globals` parameter can be used to provide template wide globals.
    These variables are available in the context at render time.

    If the template does not exist a :exc:`TemplateNotFound` exception is
    raised.

    .. versionchanged:: 2.4
       If `name` is a :class:`Template` object it is returned from the
       function unchanged.
    """
    if isinstance(name, Template):
        return name
    if parent is not None:
        name = self.join_path(name, parent)
    return self._load_template(name, self.make_globals(globals))

Internal () method is called get_template _load_template () method

@internalcode
def _load_template(self, name, globals):
    if self.loader is None:
        raise TypeError('no loader for this environment specified')
    if self.cache is not None:
        template = self.cache.get(name)
        if template is not None and (not self.auto_reload or \
                                     template.is_up_to_date):
            return template
    template = self.loader.load(self, name, globals)
    if self.cache is not None:
        self.cache[name] = template
    return template

_load_template () method first checks to see if there is a cache, if the cache is available on the use of cache; the cache is not available on the use of loader to load a template, this loader is an example of FileSystemLoader previously mentioned (by default).

0x03 BaseLoader.load

@internalcode
def load(self, environment, name, globals=None):
    ...
    # 省略部分源码
    return environment.template_class.from_code(environment, code, globals, uptodate)

BaseLoader is FileSystemLoader base class. The load method to achieve a compilation of templates, such as load logic. Finally, use environment.template_class.from_code () method. Which is template_class Template class that represents the template objects compiled.
    from_code Template class is a static method can be used to create a Template instance. When the load method returns, there was obtained a Template object.
    And finally back to render_template method

def render_template(template_name, **context):
    ...
    return current_app.jinja_env.get_template(template_name).render(context)

Implementation of the Template object render () method.

0x04 Template.render

def render(self, *args, **kwargs):
    """This function accepts either a dict or some keyword arguments which
    will then be the context the template is evaluated in.  The return
    value will be the rendered template.

    :param context: the function accepts the same arguments as the
                    :class:`dict` constructor.
    :return: the rendered template as string
    """
    ns = self.default_context.copy()
    if len(args) == 1 and isinstance(args[0], utils.MultiDict):
        ns.update(args[0].to_dict(flat=True))
    else:
        ns.update(dict(*args))
    if kwargs:
        ns.update(kwargs)
    context = Context(ns, self.charset, self.errors)
    exec self.code in context.runtime, context
    return context.get_value(self.unicode_mode)

This method receives a dict type parameter, used to pass parameters to the template. The core of this method is an exec function. Python is built exec function, it can perform dynamic Python code.

0x05 summarize

Flask Jinja use as a template engine. Execution path

Flask.render_template => Environment.get_template => Template.render => exec

0x06 learning materials

  • https://palletsprojects.com/p/flask/
  • http://jinja.pocoo.org/docs/2.10/

Leave a Reply