Documentation for all official extensions

This commit is contained in:
Miguel Grinberg
2022-08-06 23:38:36 +01:00
parent 3bcdf4d496
commit 09dc3ef7aa
7 changed files with 331 additions and 67 deletions

View File

@@ -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

View File

@@ -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 -----------------------------------------------------

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)