add generate_template and generate_template_string functions

This commit is contained in:
pgjones 2022-06-09 09:21:48 +01:00 committed by David Lord
parent 762382e436
commit 46433e9807
No known key found for this signature in database
GPG key ID: 7A1C87E3F5BC42A8
7 changed files with 139 additions and 37 deletions

View file

@ -287,6 +287,10 @@ Template Rendering
.. autofunction:: render_template_string
.. autofunction:: stream_template
.. autofunction:: stream_template_string
.. autofunction:: get_template_attribute
Configuration

View file

@ -20,7 +20,7 @@ data and to then invoke that function and pass it to a response object::
def generate():
for row in iter_all_rows():
yield f"{','.join(row)}\n"
return app.response_class(generate(), mimetype='text/csv')
return generate(), {"Content-Type": "text/csv")
Each ``yield`` expression is directly sent to the browser. Note though
that some WSGI middlewares might break streaming, so be careful there in
@ -29,52 +29,57 @@ debug environments with profilers and other things you might have enabled.
Streaming from Templates
------------------------
The Jinja2 template engine also supports rendering templates piece by
piece. This functionality is not directly exposed by Flask because it is
quite uncommon, but you can easily do it yourself::
The Jinja2 template engine supports rendering a template piece by
piece, returning an iterator of strings. Flask provides the
:func:`~flask.stream_template` and :func:`~flask.stream_template_string`
functions to make this easier to use.
def stream_template(template_name, **context):
app.update_template_context(context)
t = app.jinja_env.get_template(template_name)
rv = t.stream(context)
rv.enable_buffering(5)
return rv
.. code-block:: python
@app.route('/my-large-page.html')
def render_large_template():
rows = iter_all_rows()
return app.response_class(stream_template('the_template.html', rows=rows))
from flask import stream_template
@app.get("/timeline")
def timeline():
return stream_template("timeline.html")
The parts yielded by the render stream tend to match statement blocks in
the template.
The trick here is to get the template object from the Jinja2 environment
on the application and to call :meth:`~jinja2.Template.stream` instead of
:meth:`~jinja2.Template.render` which returns a stream object instead of a
string. Since we're bypassing the Flask template render functions and
using the template object itself we have to make sure to update the render
context ourselves by calling :meth:`~flask.Flask.update_template_context`.
The template is then evaluated as the stream is iterated over. Since each
time you do a yield the server will flush the content to the client you
might want to buffer up a few items in the template which you can do with
``rv.enable_buffering(size)``. ``5`` is a sane default.
Streaming with Context
----------------------
.. versionadded:: 0.9
The :data:`~flask.request` will not be active while the generator is
running, because the view has already returned at that point. If you try
to access ``request``, you'll get a ``RuntimeError``.
Note that when you stream data, the request context is already gone the
moment the function executes. Flask 0.9 provides you with a helper that
can keep the request context around during the execution of the
generator::
If your generator function relies on data in ``request``, use the
:func:`~flask.stream_with_context` wrapper. This will keep the request
context active during the generator.
.. code-block:: python
from flask import stream_with_context, request
from markupsafe import escape
@app.route('/stream')
def streamed_response():
def generate():
yield 'Hello '
yield request.args['name']
yield '!'
return app.response_class(stream_with_context(generate()))
yield '<p>Hello '
yield escape(request.args['name'])
yield '!</p>'
return stream_with_context(generate())
Without the :func:`~flask.stream_with_context` function you would get a
:class:`RuntimeError` at that point.
It can also be used as a decorator.
.. code-block:: python
@stream_with_context
def generate():
...
return generate()
The :func:`~flask.stream_template` and
:func:`~flask.stream_template_string` functions automatically
use :func:`~flask.stream_with_context` if a request is active.

View file

@ -201,3 +201,29 @@ templates::
You could also build `format_price` as a template filter (see
:ref:`registering-filters`), but this demonstrates how to pass functions in a
context processor.
Streaming
---------
It can be useful to not render the whole template as one complete
string, instead render it as a stream, yielding smaller incremental
strings. This can be used for streaming HTML in chunks to speed up
initial page load, or to save memory when rendering a very large
template.
The Jinja2 template engine supports rendering a template piece
by piece, returning an iterator of strings. Flask provides the
:func:`~flask.stream_template` and :func:`~flask.stream_template_string`
functions to make this easier to use.
.. code-block:: python
from flask import stream_template
@app.get("/timeline")
def timeline():
return stream_template("timeline.html")
These functions automatically apply the
:func:`~flask.stream_with_context` wrapper if a request is active, so
that it remains available in the template.