From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.5 required=3.0 tests=BAYES_00,DKIM_ADSP_CUSTOM_MED, DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,HTML_MESSAGE,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id B88B0C07E95 for ; Wed, 7 Jul 2021 14:53:23 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 31B4D61130 for ; Wed, 7 Jul 2021 14:53:23 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 31B4D61130 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Received: from localhost ([::1]:35368 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1m18vS-0007IF-92 for qemu-devel@archiver.kernel.org; Wed, 07 Jul 2021 10:53:22 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33746) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1m18uq-0006d5-QE for qemu-devel@nongnu.org; Wed, 07 Jul 2021 10:52:44 -0400 Received: from mail-il1-x135.google.com ([2607:f8b0:4864:20::135]:37608) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1m18uo-0005MR-2Z for qemu-devel@nongnu.org; Wed, 07 Jul 2021 10:52:44 -0400 Received: by mail-il1-x135.google.com with SMTP id i13so3032463ilu.4 for ; Wed, 07 Jul 2021 07:52:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc; bh=6Em5jNvFF596T5AQWDXJkVv2+A0Fj0kSVmlM5vGe07c=; b=dFJH0BtowIT5o74Qn47MEXWSEKpICVC9zPPGMjek7aziZFOmZRi5hNGiMrWgyQ9uHE ofdn72RrmkONvFhFu6uaeqRc3P6xXMueS7y0qRTduuDZ6FT+7dSihkWljILaNRpMsTlL lAxDb5HH4rNeWxuT4IrUTdWXALGukrypau4O5yQJV3HWaLyURRIm9AqF+2H1vmuwpecR vtPa7DIJZdCLATLbbeHdAQgGdHw4TITK3ko90lyQ0XkOJPZ7vzmQtjD5UWcV+PmLdwmx 0QLAqJc3OEDXiqfuVmRBuTFRNcn0Ozdf+MU+tueq0Fxtj3xRK4FgwUXz7yJWiazJsr5X MOsQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=6Em5jNvFF596T5AQWDXJkVv2+A0Fj0kSVmlM5vGe07c=; b=r1GY1F/uW11LLFQYlqcvG6ysUFSYty9vPRZUzm9ZrcTW+4KsLqJtHhYgpOb70dwWdh a/BJiKPeZ2m8FrmS2B7OnGXEkUlAFnOegNYJSmTVuwffJthtezH4xULfDI+oMmets2UT LgcIYrQMtK/v+g/N2rMcZlUlXlHU+YlY0W3R0Pq0Lfp0gDiMsZw/jT9suxVXm/OI+xcK fksxA0BThruhjh/oRn8cq3RMOGw/f8LF9CGMaDj3sd91xn4ZUS9gC5SJXQebM8RuiOYn VtznD/GCLviybiC70hXl/v/DQeo65lVNHIVvsGtjkewLw0GEe5e3LoyAku0ZjTvIosX8 EgLQ== X-Gm-Message-State: AOAM530G1WBwJorTkOzN/bjBZE5Km1u2NiokioUjTMAhlxdLifs3yw69 plcqgA+oomVKl4ecKG82dzoKXEQsSchepSm317E= X-Google-Smtp-Source: ABdhPJwQkFqtc+8ApBWy0/JHWR/SKGW3KgxjgC+J0K9CjhQZ3IGSlXauWUIwtH14CHZpb6x7tjSqqdTpQXtRq5bA0io= X-Received: by 2002:a92:c651:: with SMTP id 17mr18586734ill.44.1625669560020; Wed, 07 Jul 2021 07:52:40 -0700 (PDT) MIME-Version: 1.0 References: <20210701041313.1696009-1-jsnow@redhat.com> <20210701041313.1696009-13-jsnow@redhat.com> In-Reply-To: <20210701041313.1696009-13-jsnow@redhat.com> From: "Niteesh G. S." Date: Wed, 7 Jul 2021 20:22:13 +0530 Message-ID: Subject: Re: [PATCH 12/20] python/aqmp: add QMP Message format To: John Snow Content-Type: multipart/alternative; boundary="000000000000a5b28605c689b0be" Received-SPF: pass client-ip=2607:f8b0:4864:20::135; envelope-from=niteesh.gs@gmail.com; helo=mail-il1-x135.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Eduardo Habkost , Stefan Hajnoczi , qemu-devel@nongnu.org, Wainer dos Santos Moschetta , Markus Armbruster , Willian Rampazzo , Cleber Rosa , Eric Blake Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" --000000000000a5b28605c689b0be Content-Type: text/plain; charset="UTF-8" On Thu, Jul 1, 2021 at 9:43 AM John Snow wrote: > The Message class is here primarily to serve as a solid type to use for > mypy static typing for unambiguous annotation and documentation. > > We can also stuff JSON serialization and deserialization into this class > itself so it can be re-used even outside this infrastructure. > > Signed-off-by: John Snow > --- > python/qemu/aqmp/__init__.py | 4 +- > python/qemu/aqmp/message.py | 207 +++++++++++++++++++++++++++++++++++ > 2 files changed, 210 insertions(+), 1 deletion(-) > create mode 100644 python/qemu/aqmp/message.py > > diff --git a/python/qemu/aqmp/__init__.py b/python/qemu/aqmp/__init__.py > index 5c44fabeea..c1ec68a023 100644 > --- a/python/qemu/aqmp/__init__.py > +++ b/python/qemu/aqmp/__init__.py > @@ -22,12 +22,14 @@ > # the COPYING file in the top-level directory. > > from .error import AQMPError, MultiException > +from .message import Message > from .protocol import ConnectError, Runstate > > > # The order of these fields impact the Sphinx documentation order. > __all__ = ( > - # Classes > + # Classes, most to least important > + 'Message', > 'Runstate', > > # Exceptions, most generic to most explicit > diff --git a/python/qemu/aqmp/message.py b/python/qemu/aqmp/message.py > new file mode 100644 > index 0000000000..3a4b283032 > --- /dev/null > +++ b/python/qemu/aqmp/message.py > @@ -0,0 +1,207 @@ > +""" > +QMP Message Format > + > +This module provides the `Message` class, which represents a single QMP > +message sent to or from the server. > +""" > + > +import json > +from json import JSONDecodeError > +from typing import ( > + Dict, > + Iterator, > + Mapping, > + MutableMapping, > + Optional, > + Union, > +) > + > +from .error import ProtocolError > + > + > +class Message(MutableMapping[str, object]): > + """ > + Represents a single QMP protocol message. > + > + QMP uses JSON objects as its basic communicative unit; so this > + Python object is a :py:obj:`~collections.abc.MutableMapping`. It may > + be instantiated from either another mapping (like a `dict`), or from > + raw `bytes` that still need to be deserialized. > + > + Once instantiated, it may be treated like any other MutableMapping:: > + > + >>> msg = Message(b'{"hello": "world"}') > + >>> assert msg['hello'] == 'world' > + >>> msg['id'] = 'foobar' > + >>> print(msg) > + { > + "hello": "world", > + "id": "foobar" > + } > + > + It can be converted to `bytes`:: > + > + >>> msg = Message({"hello": "world"}) > + >>> print(bytes(msg)) > + b'{"hello":"world","id":"foobar"}' > + > + Or back into a garden-variety `dict`:: > + > + >>> dict(msg) > + {'hello': 'world'} > + > + > + :param value: Initial value, if any. > + :param eager: > + When `True`, attempt to serialize or deserialize the initial value > + immediately, so that conversion exceptions are raised during > + the call to ``__init__()``. > + """ > + # pylint: disable=too-many-ancestors > + > + def __init__(self, > + value: Union[bytes, Mapping[str, object]] = b'', *, > + eager: bool = True): > + self._data: Optional[bytes] = None > + self._obj: Optional[Dict[str, object]] = None > + > + if isinstance(value, bytes): > + self._data = value > + if eager: > + self._obj = self._deserialize(self._data) > + else: > + self._obj = dict(value) > + if eager: > + self._data = self._serialize(self._obj) > + > + # Methods necessary to implement the MutableMapping interface, see: > + # > https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping > + > + # We get pop, popitem, clear, update, setdefault, __contains__, > + # keys, items, values, get, __eq__ and __ne__ for free. > + > + def __getitem__(self, key: str) -> object: > + return self._object[key] > + > + def __setitem__(self, key: str, value: object) -> None: > + self._object[key] = value > + self._data = None > + > + def __delitem__(self, key: str) -> None: > + del self._object[key] > + self._data = None > + > + def __iter__(self) -> Iterator[str]: > + return iter(self._object) > + > + def __len__(self) -> int: > + return len(self._object) > + > + # Dunder methods not related to MutableMapping: > + > + def __repr__(self) -> str: > + return f"Message({self._object!r})" > + > + def __str__(self) -> str: > + """Pretty-printed representation of this QMP message.""" > + return json.dumps(self._object, indent=2) > + > + def __bytes__(self) -> bytes: > + """bytes representing this QMP message.""" > + if self._data is None: > + self._data = self._serialize(self._obj or {}) > + return self._data > + > + # > Is this something intentional? > + > + @property > + def _object(self) -> Dict[str, object]: > + """ > + A `dict` representing this QMP message. > + > + Generated on-demand, if required. This property is private > + because it returns an object that could be used to invalidate > + the internal state of the `Message` object. > + """ > + if self._obj is None: > + self._obj = self._deserialize(self._data or b'') > + return self._obj > + > + @classmethod > + def _serialize(cls, value: object) -> bytes: > + """ > + Serialize a JSON object as `bytes`. > + > + :raise ValueError: When the object cannot be serialized. > + :raise TypeError: When the object cannot be serialized. > + > + :return: `bytes` ready to be sent over the wire. > + """ > + return json.dumps(value, separators=(',', ':')).encode('utf-8') > + > + @classmethod > + def _deserialize(cls, data: bytes) -> Dict[str, object]: > + """ > + Deserialize JSON `bytes` into a native Python `dict`. > + > + :raise DeserializationError: > + If JSON deserialization fails for any reason. > + :raise UnexpectedTypeError: > + If the data does not represent a JSON object. > + > + :return: A `dict` representing this QMP message. > + """ > + try: > + obj = json.loads(data) > + except JSONDecodeError as err: > + emsg = "Failed to deserialize QMP message." > + raise DeserializationError(emsg, data) from err > + if not isinstance(obj, dict): > + raise UnexpectedTypeError( > + "QMP message is not a JSON object.", > + obj > + ) > + return obj > + > + > +class DeserializationError(ProtocolError): > + """ > + A QMP message was not understood as JSON. > + > + When this Exception is raised, ``__cause__`` will be set to the > + `json.JSONDecodeError` Exception, which can be interrogated for > + further details. > + > + :param error_message: Human-readable string describing the error. > + :param raw: The raw `bytes` that prompted the failure. > + """ > + def __init__(self, error_message: str, raw: bytes): > + super().__init__(error_message) > + #: The raw `bytes` that were not understood as JSON. > + self.raw: bytes = raw > + > + def __str__(self) -> str: > + return "\n".join([ > + super().__str__(), > + f" raw bytes were: {str(self.raw)}", > + ]) > + > + > +class UnexpectedTypeError(ProtocolError): > + """ > + A QMP message was JSON, but not a JSON object. > + > + :param error_message: Human-readable string describing the error. > + :param value: The deserialized JSON value that wasn't an object. > + """ > + def __init__(self, error_message: str, value: object): > + super().__init__(error_message) > + #: The JSON value that was expected to be an object. > + self.value: object = value > + > + def __str__(self) -> str: > + strval = json.dumps(self.value, indent=2) > + return "\n".join([ > + super().__str__(), > + f" json value was: {strval}", > + ]) > -- > 2.31.1 > > --000000000000a5b28605c689b0be Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


On Thu, Jul 1, 2021 at 9:43 AM John Snow <jsnow@redhat.com> wr= ote:
The Message= class is here primarily to serve as a solid type to use for
mypy static typing for unambiguous annotation and documentation.

We can also stuff JSON serialization and deserialization into this class itself so it can be re-used even outside this infrastructure.

Signed-off-by: John Snow <jsnow@redhat.com>
---
=C2=A0python/qemu/aqmp/__init__.py |=C2=A0 =C2=A04 +-
=C2=A0python/qemu/aqmp/message.py=C2=A0 | 207 +++++++++++++++++++++++++++++= ++++++
=C2=A02 files changed, 210 insertions(+), 1 deletion(-)
=C2=A0create mode 100644 python/qemu/aqmp/message.py

diff --git a/python/qemu/aqmp/__init__.py b/python/qemu/aqmp/__init__.py index 5c44fabeea..c1ec68a023 100644
--- a/python/qemu/aqmp/__init__.py
+++ b/python/qemu/aqmp/__init__.py
@@ -22,12 +22,14 @@
=C2=A0# the COPYING file in the top-level directory.

=C2=A0from .error import AQMPError, MultiException
+from .message import Message
=C2=A0from .protocol import ConnectError, Runstate


=C2=A0# The order of these fields impact the Sphinx documentation order. =C2=A0__all__ =3D (
-=C2=A0 =C2=A0 # Classes
+=C2=A0 =C2=A0 # Classes, most to least important
+=C2=A0 =C2=A0 'Message',
=C2=A0 =C2=A0 =C2=A0'Runstate',

=C2=A0 =C2=A0 =C2=A0# Exceptions, most generic to most explicit
diff --git a/python/qemu/aqmp/message.py b/python/qemu/aqmp/message.py
new file mode 100644
index 0000000000..3a4b283032
--- /dev/null
+++ b/python/qemu/aqmp/message.py
@@ -0,0 +1,207 @@
+"""
+QMP Message Format
+
+This module provides the `Message` class, which represents a single QMP +message sent to or from the server.
+"""
+
+import json
+from json import JSONDecodeError
+from typing import (
+=C2=A0 =C2=A0 Dict,
+=C2=A0 =C2=A0 Iterator,
+=C2=A0 =C2=A0 Mapping,
+=C2=A0 =C2=A0 MutableMapping,
+=C2=A0 =C2=A0 Optional,
+=C2=A0 =C2=A0 Union,
+)
+
+from .error import ProtocolError
+
+
+class Message(MutableMapping[str, object]):
+=C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 Represents a single QMP protocol message.
+
+=C2=A0 =C2=A0 QMP uses JSON objects as its basic communicative unit; so th= is
+=C2=A0 =C2=A0 Python object is a :py:obj:`~collections.abc.MutableMapping`= . It may
+=C2=A0 =C2=A0 be instantiated from either another mapping (like a `dict`),= or from
+=C2=A0 =C2=A0 raw `bytes` that still need to be deserialized.
+
+=C2=A0 =C2=A0 Once instantiated, it may be treated like any other MutableM= apping::
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 >>> msg =3D Message(b'{"hell= o": "world"}')
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 >>> assert msg['hello'] =3D= =3D 'world'
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 >>> msg['id'] =3D 'foobar= '
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 >>> print(msg)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "hello": "world", +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "id": "foobar"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+
+=C2=A0 =C2=A0 It can be converted to `bytes`::
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 >>> msg =3D Message({"hello"= ;: "world"})
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 >>> print(bytes(msg))
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 b'{"hello":"world",&qu= ot;id":"foobar"}'
+
+=C2=A0 =C2=A0 Or back into a garden-variety `dict`::
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0>>> dict(msg)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0{'hello': 'world'}
+
+
+=C2=A0 =C2=A0 :param value: Initial value, if any.
+=C2=A0 =C2=A0 :param eager:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 When `True`, attempt to serialize or deseriali= ze the initial value
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 immediately, so that conversion exceptions are= raised during
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 the call to ``__init__()``.
+=C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 # pylint: disable=3Dtoo-many-ancestors
+
+=C2=A0 =C2=A0 def __init__(self,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0value: Union= [bytes, Mapping[str, object]] =3D b'', *,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0eager: bool = =3D True):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._data: Optional[bytes] =3D None
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._obj: Optional[Dict[str, object]] =3D Non= e
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if isinstance(value, bytes):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self._data =3D value
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if eager:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self._obj =3D self= ._deserialize(self._data)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 else:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self._obj =3D dict(value)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if eager:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self._data =3D sel= f._serialize(self._obj)
+
+=C2=A0 =C2=A0 # Methods necessary to implement the MutableMapping interfac= e, see:
+=C2=A0 =C2=A0 # https://docs.python.org/3/library/collections.abc.html#collections.abc.Mu= tableMapping
+
+=C2=A0 =C2=A0 # We get pop, popitem, clear, update, setdefault, __contains= __,
+=C2=A0 =C2=A0 # keys, items, values, get, __eq__ and __ne__ for free.
+
+=C2=A0 =C2=A0 def __getitem__(self, key: str) -> object:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return self._object[key]
+
+=C2=A0 =C2=A0 def __setitem__(self, key: str, value: object) -> None: +=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._object[key] =3D value
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._data =3D None
+
+=C2=A0 =C2=A0 def __delitem__(self, key: str) -> None:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 del self._object[key]
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._data =3D None
+
+=C2=A0 =C2=A0 def __iter__(self) -> Iterator[str]:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return iter(self._object)
+
+=C2=A0 =C2=A0 def __len__(self) -> int:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return len(self._object)
+
+=C2=A0 =C2=A0 # Dunder methods not related to MutableMapping:
+
+=C2=A0 =C2=A0 def __repr__(self) -> str:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return f"Message({self._object!r})"<= br> +
+=C2=A0 =C2=A0 def __str__(self) -> str:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """Pretty-printed representatio= n of this QMP message."""
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return json.dumps(self._object, indent=3D2) +
+=C2=A0 =C2=A0 def __bytes__(self) -> bytes:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """bytes representing this QMP = message."""
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if self._data is None:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self._data =3D self._serialize(s= elf._obj or {})
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return self._data
+
+=C2=A0 =C2=A0 #
Is this something intentional?=C2=A0
+
+=C2=A0 =C2=A0 @property
+=C2=A0 =C2=A0 def _object(self) -> Dict[str, object]:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 A `dict` representing this QMP message.
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 Generated on-demand, if required. This propert= y is private
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 because it returns an object that could be use= d to invalidate
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 the internal state of the `Message` object. +=C2=A0 =C2=A0 =C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if self._obj is None:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self._obj =3D self._deserialize(= self._data or b'')
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return self._obj
+
+=C2=A0 =C2=A0 @classmethod
+=C2=A0 =C2=A0 def _serialize(cls, value: object) -> bytes:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 Serialize a JSON object as `bytes`.
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 :raise ValueError: When the object cannot be s= erialized.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 :raise TypeError: When the object cannot be se= rialized.
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 :return: `bytes` ready to be sent over the wir= e.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return json.dumps(value, separators=3D(',&= #39;, ':')).encode('utf-8')
+
+=C2=A0 =C2=A0 @classmethod
+=C2=A0 =C2=A0 def _deserialize(cls, data: bytes) -> Dict[str, object]:<= br> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 Deserialize JSON `bytes` into a native Python = `dict`.
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 :raise DeserializationError:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 If JSON deserialization fails fo= r any reason.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 :raise UnexpectedTypeError:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 If the data does not represent a= JSON object.
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 :return: A `dict` representing this QMP messag= e.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 try:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 obj =3D json.loads(data)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 except JSONDecodeError as err:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 emsg =3D "Failed to deseria= lize QMP message."
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 raise DeserializationError(emsg,= data) from err
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if not isinstance(obj, dict):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 raise UnexpectedTypeError(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "QMP message = is not a JSON object.",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 obj
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return obj
+
+
+class DeserializationError(ProtocolError):
+=C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 A QMP message was not understood as JSON.
+
+=C2=A0 =C2=A0 When this Exception is raised, ``__cause__`` will be set to = the
+=C2=A0 =C2=A0 `json.JSONDecodeError` Exception, which can be interrogated = for
+=C2=A0 =C2=A0 further details.
+
+=C2=A0 =C2=A0 :param error_message: Human-readable string describing the e= rror.
+=C2=A0 =C2=A0 :param raw: The raw `bytes` that prompted the failure.
+=C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 def __init__(self, error_message: str, raw: bytes):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 super().__init__(error_message)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 #: The raw `bytes` that were not understood as= JSON.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.raw: bytes =3D raw
+
+=C2=A0 =C2=A0 def __str__(self) -> str:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "\n".join([
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 super().__str__(),
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 f"=C2=A0 raw bytes were: {s= tr(self.raw)}",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 ])
+
+
+class UnexpectedTypeError(ProtocolError):
+=C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 A QMP message was JSON, but not a JSON object.
+
+=C2=A0 =C2=A0 :param error_message: Human-readable string describing the e= rror.
+=C2=A0 =C2=A0 :param value: The deserialized JSON value that wasn't an= object.
+=C2=A0 =C2=A0 """
+=C2=A0 =C2=A0 def __init__(self, error_message: str, value: object):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 super().__init__(error_message)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 #: The JSON value that was expected to be an o= bject.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.value: object =3D value
+
+=C2=A0 =C2=A0 def __str__(self) -> str:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 strval =3D json.dumps(self.value, indent=3D2)<= br> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "\n".join([
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 super().__str__(),
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 f"=C2=A0 json value was: {s= trval}",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 ])
--
2.31.1

--000000000000a5b28605c689b0be--