Documentation for all official extensions
This commit is contained in:
84
docs/api.rst
84
docs/api.rst
@@ -4,93 +4,72 @@ API Reference
|
||||
``microdot`` module
|
||||
-------------------
|
||||
|
||||
The ``microdot`` module defines a few classes that help implement HTTP-based
|
||||
servers for MicroPython and standard Python, with multithreading support for
|
||||
Python interpreters that support it.
|
||||
|
||||
``Microdot`` class
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: microdot.Microdot
|
||||
:members:
|
||||
|
||||
``Request`` class
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: microdot.Request
|
||||
:members:
|
||||
|
||||
``Response`` class
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: microdot.Response
|
||||
:members:
|
||||
|
||||
``MultiDict`` class
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: microdot.MultiDict
|
||||
:members:
|
||||
|
||||
``microdot_asyncio`` module
|
||||
---------------------------
|
||||
|
||||
The ``microdot_asyncio`` module defines a few classes that help implement
|
||||
HTTP-based servers for MicroPython and standard Python that use ``asyncio``
|
||||
and coroutines.
|
||||
|
||||
``Microdot`` class
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: microdot_asyncio.Microdot
|
||||
:inherited-members:
|
||||
:members:
|
||||
|
||||
``Request`` class
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: microdot_asyncio.Request
|
||||
:inherited-members:
|
||||
:members:
|
||||
|
||||
``Response`` class
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: microdot_asyncio.Response
|
||||
:inherited-members:
|
||||
:members:
|
||||
|
||||
``microdot_utemplate`` module
|
||||
-----------------------------
|
||||
|
||||
.. automodule:: microdot_utemplate
|
||||
:members:
|
||||
|
||||
``microdot_jinja`` module
|
||||
-------------------------
|
||||
|
||||
.. automodule:: microdot_jinja
|
||||
:members:
|
||||
|
||||
``microdot_session`` module
|
||||
---------------------------
|
||||
|
||||
.. automodule:: microdot_session
|
||||
:members:
|
||||
|
||||
``microdot_test_client`` module
|
||||
-------------------------------
|
||||
|
||||
The ``microdot_test_client`` module defines a test client that can be used to
|
||||
create automated tests for the Microdot server.
|
||||
|
||||
``TestClient`` class
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: microdot_test_client.TestClient
|
||||
:members:
|
||||
|
||||
``TestResponse`` class
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: microdot_test_client.TestResponse
|
||||
:members:
|
||||
|
||||
``microdot_asyncio_test_client`` module
|
||||
---------------------------------------
|
||||
|
||||
.. autoclass:: microdot_asyncio_test_client.TestClient
|
||||
:members:
|
||||
|
||||
.. autoclass:: microdot_asyncio_test_client.TestResponse
|
||||
:members:
|
||||
|
||||
``microdot_wsgi`` module
|
||||
------------------------
|
||||
|
||||
The ``microdot_wsgi`` module provides an extended ``Microdot`` class that
|
||||
implements the WSGI protocol and can be used with a compliant WSGI web server
|
||||
such as `Gunicorn <https://gunicorn.org/>`_ or
|
||||
`uWSGI <https://uwsgi-docs.readthedocs.io/en/latest/>`_. Since there are
|
||||
no WSGI web servers available for MicroPython, this support is currently
|
||||
limited to standard Python.
|
||||
|
||||
``Microdot`` class
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: microdot_wsgi.Microdot
|
||||
:members:
|
||||
:exclude-members: shutdown, run
|
||||
@@ -98,15 +77,6 @@ limited to standard Python.
|
||||
``microdot_asgi`` module
|
||||
------------------------
|
||||
|
||||
The ``microdot_asgi`` module provides an extended ``Microdot`` class that
|
||||
implements the ASGI protocol and can be used with a compliant ASGI server such
|
||||
as `Uvicorn <https://www.uvicorn.org/>`_. Since there are no ASGI web servers
|
||||
available for MicroPython, this support is currently limited to standard
|
||||
Python.
|
||||
|
||||
``Microdot`` class
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: microdot_asgi.Microdot
|
||||
:members:
|
||||
:exclude-members: shutdown, run
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('../src'))
|
||||
|
||||
sys.path.insert(1, os.path.abspath('../libs/common'))
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
|
||||
@@ -22,10 +22,13 @@ Asynchronous Support with ``asyncio``
|
||||
- | CPython: None
|
||||
| MicroPython: `uasyncio <https://github.com/micropython/micropython/tree/master/extmod/uasyncio>`_
|
||||
|
||||
* - Examples
|
||||
- | `hello_async.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/hello_async.py>`_
|
||||
|
||||
Microdot can be extended to use an asynchronous programming model based on the
|
||||
``asyncio`` package. When the :class:`Microdot <microdot_asyncio.Microdot>`
|
||||
class is imported from the ``microdot_asyncio`` package, an asynchronous server
|
||||
is used.
|
||||
is used, and handlers can be defined as coroutines.
|
||||
|
||||
The example that follows uses ``asyncio`` coroutines for concurrency::
|
||||
|
||||
@@ -42,8 +45,14 @@ The example that follows uses ``asyncio`` coroutines for concurrency::
|
||||
Rendering HTML Templates
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Many web applications use HTML templates for rendering content to clients.
|
||||
Microdot includes extensions to render templates with the
|
||||
`utemplate <https://github.com/pfalcon/utemplate>`_ package on CPython and
|
||||
MicroPython, and with `Jinja <https://jinja.palletsprojects.com/>`_ only on
|
||||
CPython.
|
||||
|
||||
Using the uTemplate Engine
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. list-table::
|
||||
:align: left
|
||||
@@ -58,6 +67,32 @@ Using the uTemplate Engine
|
||||
* - Required external dependencies
|
||||
- | `utemplate <https://github.com/pfalcon/utemplate/tree/master/utemplate>`_
|
||||
|
||||
* - Examples
|
||||
- | `hello_utemplate.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/hello_utemplate.py>`_
|
||||
| `hello_utemplate_async.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/hello_utemplate_async.py>`_
|
||||
|
||||
The :func:`render_template <microdot_utemplate.render_template>` function is
|
||||
used to render HTML templates with the uTemplate engine. The first argument is
|
||||
the template filename, relative to the templates directory, which is
|
||||
*templates* by default. Any additional arguments are passed to the template
|
||||
engine to be used as arguments.
|
||||
|
||||
Example::
|
||||
|
||||
from microdot_utemplate import render_template
|
||||
|
||||
@app.get('/')
|
||||
def index(req):
|
||||
return render_template('index.html')
|
||||
|
||||
The default location from where templates are loaded is the *templates*
|
||||
subdirectory. This location can be changed with the
|
||||
:func:`init_templates <microdot_utemplate.init_templates>` function::
|
||||
|
||||
from microdot_utemplate import init_templates
|
||||
|
||||
init_templates('my_templates')
|
||||
|
||||
Using the Jinja Engine
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -74,6 +109,34 @@ Using the Jinja Engine
|
||||
* - Required external dependencies
|
||||
- | `Jinja2 <https://jinja.palletsprojects.com/>`_
|
||||
|
||||
* - Examples
|
||||
- | `hello_jinja.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/hello_jinja.py>`_
|
||||
|
||||
The :func:`render_template <microdot_jinja.render_template>` function is used
|
||||
to render HTML templates with the Jinja engine. The first argument is the
|
||||
template filename, relative to the templates directory, which is *templates* by
|
||||
default. Any additional arguments are passed to the template engine to be used
|
||||
as arguments.
|
||||
|
||||
Example::
|
||||
|
||||
from microdot_jinja import render_template
|
||||
|
||||
@app.get('/')
|
||||
def index(req):
|
||||
return render_template('index.html')
|
||||
|
||||
The default location from where templates are loaded is the *templates*
|
||||
subdirectory. This location can be changed with the
|
||||
:func:`init_templates <microdot_jinja.init_templates>` function::
|
||||
|
||||
from microdot_jinja import init_templates
|
||||
|
||||
init_templates('my_templates')
|
||||
|
||||
.. note::
|
||||
The Jinja extension is not compatible with MicroPython.
|
||||
|
||||
Maintaing Secure User Sessions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -89,11 +152,64 @@ Maintaing Secure User Sessions
|
||||
|
||||
* - Required external dependencies
|
||||
- | CPython: `PyJWT <https://pyjwt.readthedocs.io/>`_
|
||||
| MicroPython: `ujwt.py <https://github.com/miguelgrinberg/micropython-lib/blob/ujwt-module/python-ecosys/ujwt/ujwt.py>`_,
|
||||
| MicroPython: `jwt.py <https://github.com/miguelgrinberg/micropython-lib/blob/ujwt-module/python-ecosys/ujwt/ujwt.py>`_,
|
||||
`hmac <https://github.com/micropython/micropython-lib/blob/master/python-stdlib/hmac/hmac.py>`_,
|
||||
`hashlib <https://github.com/miguelgrinberg/micropython-lib/blob/ujwt-module/python-stdlib/hashlib>`_,
|
||||
`warnings <https://github.com/micropython/micropython-lib/blob/master/python-stdlib/warnings/warnings.py>`_
|
||||
|
||||
* - Examples
|
||||
- | `login.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/login.py>`_
|
||||
|
||||
The session extension provides a secure way for the application to maintain
|
||||
user sessions. The session is stored as a signed cookie in the client's
|
||||
browser, in `JSON Web Token (JWT) <https://en.wikipedia.org/wiki/JSON_Web_Token>`_
|
||||
format.
|
||||
|
||||
To work with user sessions, the application first must configure the secret key
|
||||
that will be used to sign the session cookies. It is very important that this
|
||||
key is kept secret. An attacker who is in possession of this key can generate
|
||||
valid user session cookies with any contents.
|
||||
|
||||
To set the secret key, use the :func:`set_session_secret_key <microdot_session.set_session_secret_key>` function::
|
||||
|
||||
from microdot_session import set_session_secret_key
|
||||
|
||||
set_session_secret_key('top-secret!')
|
||||
|
||||
To :func:`get_session <microdot_session.get_session>`,
|
||||
:func:`update_session <microdot_session.update_session>` and
|
||||
:func:`delete_session <microdot_session.delete_session>` functions are used
|
||||
inside route handlers to retrieve, store and delete session data respectively.
|
||||
The :func:`with_session <microdot_session.with_session>` decorator is provided
|
||||
as a convenient way to retrieve the session at the start of a route handler.
|
||||
|
||||
Example::
|
||||
|
||||
from microdot import Microdot
|
||||
from microdot_session import set_session_secret_key, with_session, \
|
||||
update_session, delete_session
|
||||
|
||||
app = Microdot()
|
||||
set_session_secret_key('top-secret')
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
@with_session
|
||||
def index(req, session):
|
||||
username = session.get('username')
|
||||
if req.method == 'POST':
|
||||
username = req.form.get('username')
|
||||
update_session(req, {'username': username})
|
||||
return redirect('/')
|
||||
if username is None:
|
||||
return 'Not logged in'
|
||||
else:
|
||||
return 'Logged in as ' + username
|
||||
|
||||
@app.post('/logout')
|
||||
def logout(req):
|
||||
delete_session(req)
|
||||
return redirect('/')
|
||||
|
||||
Test Client
|
||||
~~~~~~~~~~~
|
||||
|
||||
@@ -110,9 +226,69 @@ Test Client
|
||||
* - Required external dependencies
|
||||
- | None
|
||||
|
||||
The Microdot Test Client is a utility class that can be used during testing to
|
||||
send requests into the application.
|
||||
|
||||
Example::
|
||||
|
||||
from microdot import Microdot
|
||||
from microdot_test_client import TestClient
|
||||
|
||||
app = Microdot()
|
||||
|
||||
@app.route('/')
|
||||
def index(req):
|
||||
return 'Hello, World!'
|
||||
|
||||
def test_app():
|
||||
client = TestClient(app)
|
||||
response = client.get('/')
|
||||
assert response.text == 'Hello, World!'
|
||||
|
||||
See the documentation for the :class:`TestClient <microdot_test_client.TestClient>`
|
||||
class for more details.
|
||||
|
||||
Asynchronous Test Client
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. list-table::
|
||||
:align: left
|
||||
|
||||
* - Compatibility
|
||||
- | CPython & MicroPython
|
||||
|
||||
* - Required Microdot source files
|
||||
- | `microdot.py <https://github.com/miguelgrinberg/microdot/tree/main/src/microdot.py>`_
|
||||
| `microdot_asyncio.py <https://github.com/miguelgrinberg/microdot/tree/main/src/microdot_asyncio.py>`_
|
||||
| `microdot_test_client.py <https://github.com/miguelgrinberg/microdot/tree/main/src/microdot_test_client.py>`_
|
||||
| `microdot_asyncio_test_client.py <https://github.com/miguelgrinberg/microdot/tree/main/src/microdot_asyncio_test_client.py>`_
|
||||
|
||||
* - Required external dependencies
|
||||
- | None
|
||||
|
||||
Similar to the :class:`TestClient <microdot_test_client.TestClient>` class
|
||||
above, but for asynchronous applications.
|
||||
|
||||
Example usage::
|
||||
|
||||
from microdot_asyncio_test_client import TestClient
|
||||
|
||||
async def test_app():
|
||||
client = TestClient(app)
|
||||
response = await client.get('/')
|
||||
assert response.text == 'Hello, World!'
|
||||
|
||||
See the :class:`reference documentation <microdot_asyncio_test_client.TestClient>`
|
||||
for details.
|
||||
|
||||
Deploying on a Production Web Server
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``Microdot`` class creates its own simple web server. This is enough for an
|
||||
application deployed with MicroPython, but when using CPython it may be useful
|
||||
to use a separate, battle-tested web server. To address this need, Microdot
|
||||
provides extensions that implement the WSGI and ASGI protocols.
|
||||
|
||||
Using a WSGI Web Server
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -129,6 +305,34 @@ Using a WSGI Web Server
|
||||
* - Required external dependencies
|
||||
- | A WSGI web server, such as `Gunicorn <https://gunicorn.org/>`_.
|
||||
|
||||
* - Examples
|
||||
- | `hello_wsgi.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/hello_wsgi.py>`_
|
||||
|
||||
|
||||
The ``microdot_wsgi`` module provides an extended ``Microdot`` class that
|
||||
implements the WSGI protocol and can be used with a compliant WSGI web server
|
||||
such as `Gunicorn <https://gunicorn.org/>`_ or
|
||||
`uWSGI <https://uwsgi-docs.readthedocs.io/en/latest/>`_.
|
||||
|
||||
To use a WSGI web server, the application must import the
|
||||
:class:`Microdot <microdot_wsgi.Microdot>` class from the ``microdot_wsgi``
|
||||
module::
|
||||
|
||||
from microdot_wsgi import Microdot
|
||||
|
||||
app = Microdot()
|
||||
|
||||
@app.route('/')
|
||||
def index(req):
|
||||
return 'Hello, World!'
|
||||
|
||||
The ``app`` application instance created from this class is a WSGI application
|
||||
that can be used with any complaint WSGI web server. If the above application
|
||||
is stored in a file called *test.py*, then the following command runs the
|
||||
web application using the Gunicorn web server::
|
||||
|
||||
gunicorn test:app
|
||||
|
||||
Using an ASGI Web Server
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -146,3 +350,29 @@ Using an ASGI Web Server
|
||||
* - Required external dependencies
|
||||
- | An ASGI web server, such as `Uvicorn <https://uvicorn.org/>`_.
|
||||
|
||||
* - Examples
|
||||
- | `hello_asgi.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/hello_asgi.py>`_
|
||||
|
||||
The ``microdot_asgi`` module provides an extended ``Microdot`` class that
|
||||
implements the ASGI protocol and can be used with a compliant ASGI server such
|
||||
as `Uvicorn <https://www.uvicorn.org/>`_.
|
||||
|
||||
To use an ASGI web server, the application must import the
|
||||
:class:`Microdot <microdot_asgi.Microdot>` class from the ``microdot_asgi``
|
||||
module::
|
||||
|
||||
from microdot_asgi import Microdot
|
||||
|
||||
app = Microdot()
|
||||
|
||||
@app.route('/')
|
||||
async def index(req):
|
||||
return 'Hello, World!'
|
||||
|
||||
The ``app`` application instance created from this class is an ASGI application
|
||||
that can be used with any complaint ASGI web server. If the above application
|
||||
is stored in a file called *test.py*, then the following command runs the
|
||||
web application using the Uvicorn web server::
|
||||
|
||||
uvicorn test:app
|
||||
|
||||
|
||||
@@ -67,6 +67,9 @@ Running with CPython
|
||||
* - Required external dependencies
|
||||
- | None
|
||||
|
||||
* - Examples
|
||||
- | `hello.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/hello.py>`_
|
||||
|
||||
When using CPython, you can start the web server by running the script that
|
||||
defines and runs the application instance::
|
||||
|
||||
@@ -89,6 +92,10 @@ Running with MicroPython
|
||||
* - Required external dependencies
|
||||
- | None
|
||||
|
||||
* - Examples
|
||||
- | `hello.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/hello.py>`_
|
||||
| `gpio.py <https://github.com/miguelgrinberg/microdot/blob/main/examples/gpio.py>`_
|
||||
|
||||
When using MicroPython, you can upload a *main.py* file containing the web
|
||||
server code to your device along with *microdot.py*. MicroPython will
|
||||
automatically run *main.py* when the device is powered on, so the web server
|
||||
@@ -618,8 +625,8 @@ Example::
|
||||
return {'hello': 'world'}
|
||||
|
||||
.. note::
|
||||
JSON responses are sent with the ``Content-Type`` header set to
|
||||
``application/json``.
|
||||
A ``Content-Type`` header set to ``application/json`` is automatically added
|
||||
to the response.
|
||||
|
||||
Redirects
|
||||
^^^^^^^^^
|
||||
@@ -694,9 +701,9 @@ default content type::
|
||||
Setting Cookies
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Many web application rely on cookies to maintain client state between requests.
|
||||
Cookies can be set with the ``Set-Cookie`` header in the response, but since
|
||||
this is such a common practice, Microdot provides the
|
||||
Many web applications rely on cookies to maintain client state between
|
||||
requests. Cookies can be set with the ``Set-Cookie`` header in the response,
|
||||
but since this is such a common practice, Microdot provides the
|
||||
:func:`set_cookie() <microdot.Response.set_cookie>` method in the response
|
||||
object to add a properly formatted cookie header to the response.
|
||||
|
||||
@@ -719,7 +726,7 @@ Another option is to create a response object directly in the route function::
|
||||
|
||||
@app.get('/')
|
||||
def index(request):
|
||||
response = Response('Hello, World!'))
|
||||
response = Response('Hello, World!')
|
||||
response.set_cookie('name', 'value')
|
||||
return response
|
||||
|
||||
|
||||
@@ -18,6 +18,15 @@ def init_templates(template_dir='templates'):
|
||||
|
||||
|
||||
def render_template(template, *args, **kwargs):
|
||||
"""Render a template.
|
||||
|
||||
:param template: The filename of the template to render, relative to the
|
||||
configured template directory.
|
||||
:param args: Positional arguments to be passed to the render engine.
|
||||
:param kwargs: Keyword arguments to be passed to the render engine.
|
||||
|
||||
The return value is a string with the rendered template.
|
||||
"""
|
||||
if _jinja_env is None: # pragma: no cover
|
||||
init_templates()
|
||||
template = _jinja_env.get_template(template)
|
||||
|
||||
@@ -4,11 +4,22 @@ secret_key = None
|
||||
|
||||
|
||||
def set_session_secret_key(key):
|
||||
"""Set the secret key for signing user sessions.
|
||||
|
||||
:param key: The secret key, as a string or bytes object.
|
||||
"""
|
||||
global secret_key
|
||||
secret_key = key
|
||||
|
||||
|
||||
def get_session(request):
|
||||
"""Retrieve the user session.
|
||||
|
||||
:param request: The client request.
|
||||
|
||||
The return value is a dictionary with the data stored in the user's
|
||||
session, or ``{}`` if the session data is not available or invalid.
|
||||
"""
|
||||
global secret_key
|
||||
if not secret_key:
|
||||
raise ValueError('The session secret key is not configured')
|
||||
@@ -24,6 +35,14 @@ def get_session(request):
|
||||
|
||||
|
||||
def update_session(request, session):
|
||||
"""Update the user session.
|
||||
|
||||
:param request: The client request.
|
||||
:param session: A dictionary with the update session data for the user.
|
||||
|
||||
Calling this function adds a cookie with the updated session to the request
|
||||
currently being processed.
|
||||
"""
|
||||
if not secret_key:
|
||||
raise ValueError('The session secret key is not configured')
|
||||
|
||||
@@ -36,6 +55,13 @@ def update_session(request, session):
|
||||
|
||||
|
||||
def delete_session(request):
|
||||
"""Remove the user session.
|
||||
|
||||
:param request: The client request.
|
||||
|
||||
Calling this function adds a cookie removal header to the request currently
|
||||
being processed.
|
||||
"""
|
||||
@request.after_request
|
||||
def _delete_session(request, response):
|
||||
response.set_cookie('session', '', http_only=True,
|
||||
@@ -44,6 +70,19 @@ def delete_session(request):
|
||||
|
||||
|
||||
def with_session(f):
|
||||
"""Decorator that passes the user session to the route handler.
|
||||
|
||||
The session dictionary is passed to the decorated function as an argument
|
||||
after the request object. Example::
|
||||
|
||||
@app.route('/')
|
||||
@with_session
|
||||
def index(request, session):
|
||||
return 'Hello, World!'
|
||||
|
||||
Note that the decorator does not save the session. To update the session,
|
||||
call the :func:`update_session <microdot_session.update_session>` function.
|
||||
"""
|
||||
def wrapper(request, *args, **kwargs):
|
||||
return f(request, get_session(request), *args, **kwargs)
|
||||
|
||||
|
||||
@@ -19,6 +19,15 @@ def init_templates(template_dir='templates', loader_class=recompile.Loader):
|
||||
|
||||
|
||||
def render_template(template, *args, **kwargs):
|
||||
"""Render a template.
|
||||
|
||||
:param template: The filename of the template to render, relative to the
|
||||
configured template directory.
|
||||
:param args: Positional arguments to be passed to the render engine.
|
||||
:param kwargs: Keyword arguments to be passed to the render engine.
|
||||
|
||||
The return value is an iterator that returns sections of rendered template.
|
||||
"""
|
||||
if _loader is None: # pragma: no cover
|
||||
init_templates()
|
||||
render = _loader.load(template)
|
||||
|
||||
Reference in New Issue
Block a user