[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH v3 06/25] python/aqmp: add runstate state machine to AsyncPro
From: |
Eric Blake |
Subject: |
Re: [PATCH v3 06/25] python/aqmp: add runstate state machine to AsyncProtocol |
Date: |
Thu, 5 Aug 2021 16:02:41 -0500 |
User-agent: |
NeoMutt/20210205-687-0ed190 |
On Tue, Aug 03, 2021 at 02:29:22PM -0400, John Snow wrote:
> This serves a few purposes:
>
> 1. Protect interfaces when it's not safe to call them (via @require)
>
> 2. Add an interface by which an async client can determine if the state
> has changed, for the purposes of connection management.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> python/qemu/aqmp/__init__.py | 6 +-
> python/qemu/aqmp/protocol.py | 159 ++++++++++++++++++++++++++++++++++-
> 2 files changed, 160 insertions(+), 5 deletions(-)
>
> +++ b/python/qemu/aqmp/protocol.py
> +
> +F = TypeVar('F', bound=Callable[..., Any]) # pylint: disable=invalid-name
> +
> +
> +# Don't Panic.
> +def require(required_state: Runstate) -> Callable[[F], F]:
Technically, the definition of F is integral to the definition of
require, so I might have put the 'Don't Panic' note a bit sooner. And
it took me a while before I noticed...
> + """
> + Decorator: protect a method so it can only be run in a certain
> `Runstate`.
> +
> + :param required_state: The `Runstate` required to invoke this method.
> + :raise StateError: When the required `Runstate` is not met.
> + """
> + def _decorator(func: F) -> F:
> + # _decorator is the decorator that is built by calling the
> + # require() decorator factory; e.g.:
> + #
> + # @require(Runstate.IDLE) def # foo(): ...
Is that second # intentional?
> + # will replace 'foo' with the result of '_decorator(foo)'.
> +
> + @wraps(func)
> + def _wrapper(proto: 'AsyncProtocol[Any]',
> + *args: Any, **kwargs: Any) -> Any:
> + # _wrapper is the function that gets executed prior to the
> + # decorated method.
> +
> + name = type(proto).__name__
> +
> + if proto.runstate != required_state:
> + if proto.runstate == Runstate.CONNECTING:
> + emsg = f"{name} is currently connecting."
> + elif proto.runstate == Runstate.DISCONNECTING:
> + emsg = (f"{name} is disconnecting."
> + " Call disconnect() to return to IDLE state.")
> + elif proto.runstate == Runstate.RUNNING:
> + emsg = f"{name} is already connected and running."
> + elif proto.runstate == Runstate.IDLE:
> + emsg = f"{name} is disconnected and idle."
> + else:
> + assert False
> + raise StateError(emsg, proto.runstate, required_state)
> + # No StateError, so call the wrapped method.
> + return func(proto, *args, **kwargs)
> +
> + # Return the decorated method;
> + # Transforming Func to Decorated[Func].
> + return cast(F, _wrapper)
> +
> + # Return the decorator instance from the decorator factory. Phew!
> + return _decorator
...that it paired with this comment, explaining what looks like a
monstrosity, but in reality is a fairly typical and boilerplate
implementation of a function decorator (not that you write one every
day - the whole point of the decorator is to have a one-word
abstraction already implemented so you DON'T have to reimplement the
decoration yourself ;).
> +
> +
> class AsyncProtocol(Generic[T]):
> """
> AsyncProtocol implements a generic async message-based protocol.
> @@ -118,7 +202,24 @@ def __init__(self) -> None:
> #: exit.
> self._dc_task: Optional[asyncio.Future[None]] = None
>
> + self._runstate = Runstate.IDLE
> + self._runstate_changed: Optional[asyncio.Event] = None
> +
> + @property # @upper_half
Is it intentional to leave the @upper_half decorator commented out?
But that's minor enough that I trust you to handle it as you see fit.
Reviewed-by: Eric Blake <eblake@redhat.com>
--
Eric Blake, Principal Software Engineer
Red Hat, Inc. +1-919-301-3266
Virtualization: qemu.org | libvirt.org
- [PATCH v3 00/25] python: introduce Asynchronous QMP package, John Snow, 2021/08/03
- [PATCH v3 01/25] python/aqmp: add asynchronous QMP (AQMP) subpackage, John Snow, 2021/08/03
- [PATCH v3 02/25] python/aqmp: add error classes, John Snow, 2021/08/03
- [PATCH v3 03/25] python/pylint: Add exception for TypeVar names ('T'), John Snow, 2021/08/03
- [PATCH v3 07/25] python/aqmp: Add logging utility helpers, John Snow, 2021/08/03
- [PATCH v3 05/25] python/aqmp: add generic async message-based protocol support, John Snow, 2021/08/03
- [PATCH v3 06/25] python/aqmp: add runstate state machine to AsyncProtocol, John Snow, 2021/08/03
- Re: [PATCH v3 06/25] python/aqmp: add runstate state machine to AsyncProtocol,
Eric Blake <=
- [PATCH v3 08/25] python/aqmp: add logging to AsyncProtocol, John Snow, 2021/08/03
- [PATCH v3 11/25] python/aqmp: add _cb_inbound and _cb_outbound logging hooks, John Snow, 2021/08/03
- [PATCH v3 04/25] python/aqmp: add asyncio compatibility wrappers, John Snow, 2021/08/03
- [PATCH v3 09/25] python/aqmp: add AsyncProtocol.accept() method, John Snow, 2021/08/03