Skip to content

Commit

Permalink
context: Implement asynchronous context manager
Browse files Browse the repository at this point in the history
With this, it is now recommended to run servers as::

    async with Context.create_server_context(site):
        ...

and

    async with Context.create_client_context() as ctx:
        ...
  • Loading branch information
chrysn committed Nov 29, 2021
1 parent cd2aa0f commit a456d7b
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 0 deletions.
2 changes: 2 additions & 0 deletions aiocoap/cli/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from pathlib import Path

from ..util import hostportsplit
from ..util.asyncio.coro_or_contextmanager import AwaitOrAenter
from ..protocol import Context
from ..credentials import CredentialsMap

Expand Down Expand Up @@ -99,6 +100,7 @@ def extract_server_arguments(namespace):

return server_arguments

@AwaitOrAenter.decorate
async def server_context_from_arguments(site, namespace, **kwargs):
"""Create a bound context like
:meth:`.aiocoap.Context.create_server_context`, but take the bind and TLS
Expand Down
28 changes: 28 additions & 0 deletions aiocoap/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
SERVICE_UNAVAILABLE, CONTINUE, REQUEST_ENTITY_INCOMPLETE,
OBSERVATION_RESET_TIME, MAX_TRANSMIT_WAIT)
from .numbers.optionnumbers import OptionNumber
from .util.asyncio.coro_or_contextmanager import AwaitOrAenter

import warnings
import logging
Expand Down Expand Up @@ -92,6 +93,12 @@ class Context(interfaces.RequestProvider):
:meth:`.shutdown()`, but typical applications will not need to because
they use the context for the full process lifetime.
The context creation functions also work as `asynchronous context
managers`__, shutting down the (aiocoap) context when the (Python) context
ends.
.. __: https://docs.python.org/3/reference/datamodel.html#async-context-managers
.. automethod:: create_client_context
.. automethod:: create_server_context
Expand Down Expand Up @@ -141,6 +148,20 @@ def __init__(self, loop=None, serversite=None, loggername="coap", client_credent
For both, block-key is as extracted by _extract_block_key."""

#
# Asynchronous context manager
#

async def __aenter__(self):
# Note that this is usually not called that way; the more common idiom
# is `async with Context.create_client_context()` which returns a
# future that also has an __aenter__ method.

return self

async def __aexit__(self, exc_type, exc, tb):
await self.shutdown()

#
# convenience methods for class instanciation
#
Expand All @@ -164,11 +185,17 @@ async def _append_tokenmanaged_transport(self, token_interface_constructor):
self.request_interfaces.append(tman)

@classmethod
@AwaitOrAenter.decorate
async def create_client_context(cls, *, loggername="coap", loop=None):
"""Create a context bound to all addresses on a random listening port.
This is the easiest way to get a context suitable for sending client
requests.
Note that while this looks in the documentation like a function rather
than an asynchronous function, it does return an awaitable; the way it
is set up allows using the result as an asynchronous context manager as
well.
"""

if loop is None:
Expand Down Expand Up @@ -213,6 +240,7 @@ async def create_client_context(cls, *, loggername="coap", loop=None):
return self

@classmethod
@AwaitOrAenter.decorate
async def create_server_context(cls, site, bind=None, *, loggername="coap-server", loop=None, _ssl_context=None, multicast=[], server_credentials=None):
"""Create a context, bound to all addresses on the CoAP port (unless
otherwise specified in the ``bind`` argument).
Expand Down
35 changes: 35 additions & 0 deletions aiocoap/util/asyncio/coro_or_contextmanager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# This file is part of the Python aiocoap library project.
#
# Copyright (c) 2012-2014 Maciej Wasilak <http://sixpinetrees.blogspot.com/>,
# 2013-2014 Christian Amsüss <c.amsuess@energyharvesting.at>
#
# aiocoap is free software, this file is published under the MIT license as
# described in the accompanying LICENSE file.

import functools

class AwaitOrAenter:
"""Helper to wrap around coroutines to make them usable either with
``await c`` (possibly later with an asynchronous context manager)
or with ``async with c as ...:`` without the extra await."""

def __init__(self, coro):
self.__coro = coro

def __await__(self):
return self.__coro.__await__()

async def __aenter__(self):
self.__managed = await self.__coro
return await self.__managed.__aenter__()

async def __aexit__(self, exc_type, exc_value, traceback):
return await self.__managed.__aexit__(exc_type, exc_value, traceback)

@classmethod
def decorate(cls, coroutine):
@functools.wraps(coroutine)
def decorated(*args, **kwargs):
coro = coroutine(*args, **kwargs)
return cls(coro)
return decorated

0 comments on commit a456d7b

Please sign in to comment.