diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3c72e84..2e39bf2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,6 +16,7 @@ jobs: - run: python -m pip install --upgrade pip wheel - run: pip install tox tox-gh-actions - run: tox -eflake8 + - run: tox -edocs tests: name: tests strategy: diff --git a/docs/api.rst b/docs/api.rst index b9487a9..f63db86 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,8 +1,8 @@ API Reference ============= -``microdot`` module -------------------- +Core API +-------- .. autoclass:: microdot.Microdot :members: @@ -14,51 +14,57 @@ API Reference :members: -``websocket`` extension ------------------------ +WebSocket +--------- .. automodule:: microdot.websocket :members: -``utemplate`` templating extension ----------------------------------- +Server-Sent Events (SSE) +------------------------ + +.. automodule:: microdot.sse + :members: + +Templates (uTemplate) +--------------------- .. automodule:: microdot.utemplate :members: -``jinja`` templating extension ------------------------------- +Templates (Jinja) +----------------- .. automodule:: microdot.jinja :members: -``session`` extension ---------------------- +User Sessions +------------- .. automodule:: microdot.session :members: -``cors`` extension ------------------- +Cross-Origin Resource Sharing (CORS) +------------------------------------ .. automodule:: microdot.cors :members: -``test_client`` extension -------------------------- +Test Client +----------- .. automodule:: microdot.test_client :members: -``asgi`` extension ------------------- +ASGI +---- .. autoclass:: microdot.asgi.Microdot :members: :exclude-members: shutdown, run -``wsgi`` extension -------------------- +WSGI +---- .. autoclass:: microdot.wsgi.Microdot :members: diff --git a/docs/intro.rst b/docs/intro.rst index 71b7d3a..ef6414b 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -25,14 +25,15 @@ and incorporated into a custom MicroPython firmware. Use the following guidelines to know what files to copy: -- For a minimal setup with only the base web server functionality, copy +* For a minimal setup with only the base web server functionality, copy `microdot.py `_ into your project. -- For a configuration that includes one or more optional extensions, create a +* For a configuration that includes one or more optional extensions, create a *microdot* directory in your device and copy the following files: - - `__init__.py `_ - - `microdot.py `_ - - any needed `extensions `_. + + * `__init__.py `_ + * `microdot.py `_ + * any needed `extensions `_. Getting Started diff --git a/src/microdot/asgi.py b/src/microdot/asgi.py index 37b3f2e..1894904 100644 --- a/src/microdot/asgi.py +++ b/src/microdot/asgi.py @@ -45,6 +45,12 @@ async def readuntil(self, separator=b'\n'): class Microdot(BaseMicrodot): + """A subclass of the core :class:`Microdot ` class that + implements the ASGI protocol. + + This class must be used as the application instance when running under an + ASGI web server. + """ def __init__(self): super().__init__() self.embedded_server = False diff --git a/src/microdot/session.py b/src/microdot/session.py index 0701122..663d2a2 100644 --- a/src/microdot/session.py +++ b/src/microdot/session.py @@ -134,7 +134,7 @@ 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 ` function. + call the :func:`session.save() ` method. """ async def wrapper(request, *args, **kwargs): return await invoke_handler( diff --git a/src/microdot/sse.py b/src/microdot/sse.py index 4e17a68..9ceaf1a 100644 --- a/src/microdot/sse.py +++ b/src/microdot/sse.py @@ -3,6 +3,11 @@ class SSE: + """Server-Sent Events object. + + An object of this class is sent to handler functions to manage the SSE + connection. + """ def __init__(self): self.event = asyncio.Event() self.queue = [] @@ -40,19 +45,9 @@ def sse_response(request, event_function, *args, **kwargs): :param args: additional positional arguments to be passed to the response. :param kwargs: additional keyword arguments to be passed to the response. - Example:: - - @app.route('/events') - async def events_route(request): - async def events(request, sse): - # send an unnamed event with string data - await sse.send('hello') - # send an unnamed event with JSON data - await sse.send({'foo': 'bar'}) - # send a named event - await sse.send('hello', event='greeting') - - return sse_response(request, events) + This is a low-level function that can be used to implement a custom SSE + endpoint. In general the :func:`microdot.sse.with_sse` decorator should be + used instead. """ sse = SSE() @@ -95,9 +90,14 @@ def with_sse(f): @app.route('/events') @with_sse async def events(request, sse): - for i in range(10): - await asyncio.sleep(1) - await sse.send(f'{i}') + # send an unnamed event with string data + await sse.send('hello') + + # send an unnamed event with JSON data + await sse.send({'foo': 'bar'}) + + # send a named event + await sse.send('hello', event='greeting') """ async def sse_handler(request, *args, **kwargs): return sse_response(request, f, *args, **kwargs) diff --git a/src/microdot/websocket.py b/src/microdot/websocket.py index 40d6877..c7b6034 100644 --- a/src/microdot/websocket.py +++ b/src/microdot/websocket.py @@ -5,6 +5,11 @@ class WebSocket: + """A WebSocket connection object. + + An instance of this class is sent to handler functions to manage the + WebSocket connection. + """ CONT = 0 TEXT = 1 BINARY = 2 @@ -26,6 +31,7 @@ async def handshake(self): b'Sec-WebSocket-Accept: ' + response + b'\r\n\r\n') async def receive(self): + """Receive a message from the client.""" while True: opcode, payload = await self._read_frame() send_opcode, data = self._process_websocket_frame(opcode, payload) @@ -35,12 +41,20 @@ async def receive(self): return data async def send(self, data, opcode=None): + """Send a message to the client. + + :param data: the data to send, given as a string or bytes. + :param opcode: a custom frame opcode to use. If not given, the opcode + is ``TEXT`` or ``BINARY`` depending on the type of the + data. + """ frame = self._encode_websocket_frame( opcode or (self.TEXT if isinstance(data, str) else self.BINARY), data) await self.request.sock[1].awrite(frame) async def close(self): + """Close the websocket connection.""" if not self.closed: # pragma: no cover self.closed = True await self.send(b'', self.CLOSE) diff --git a/src/microdot/wsgi.py b/src/microdot/wsgi.py index 7f1e4e4..0087892 100644 --- a/src/microdot/wsgi.py +++ b/src/microdot/wsgi.py @@ -9,6 +9,12 @@ class Microdot(BaseMicrodot): + """A subclass of the core :class:`Microdot ` class that + implements the WSGI protocol. + + This class must be used as the application instance when running under a + WSGI web server. + """ def __init__(self): super().__init__() self.loop = asyncio.new_event_loop() diff --git a/tox.ini b/tox.ini index 8d5a46f..8989ab0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=flake8,py38,py39,py310,py311,py312,upy,cpy,benchmark +envlist=flake8,py38,py39,py310,py311,py312,upy,cpy,benchmark,docs skipsdist=True skip_missing_interpreters=True @@ -36,7 +36,6 @@ commands=sh -c "bin/circuitpython run_tests.py" [testenv:upy-mac] allowlist_externals=micropython commands=micropython run_tests.py -deps= [testenv:benchmark] deps= @@ -59,3 +58,13 @@ deps= flake8 commands= flake8 --ignore=W503 --exclude examples/templates/utemplate/templates src tests examples + +[testenv:docs] +changedir=docs +deps= + sphinx + pyjwt +allowlist_externals= + make +commands= + make html