Re: [PATCH v5 2/5] python/aqmp-tui: Add AQMP TUI
On Tue, Aug 24, 2021 at 6:15 AM Niteesh G. S. wrote: > > > On Tue, Aug 24, 2021 at 12:30 AM John Snow wrote: > >> >> >> On Mon, Aug 23, 2021 at 12:31 PM G S Niteesh Babu >> wrote: >> >>> Added AQMP TUI. >>> >>> Implements the follwing basic features: >>> 1) Command transmission/reception. >>> 2) Shows events asynchronously. >>> 3) Shows server status in the bottom status bar. >>> 4) Automatic retries on disconnects and error conditions. >>> >>> Also added type annotations and necessary pylint/mypy configurations. >>> >>> Signed-off-by: G S Niteesh Babu >>> --- >>> python/qemu/aqmp/aqmp_tui.py | 637 +++ >>> python/setup.cfg | 13 +- >>> 2 files changed, 649 insertions(+), 1 deletion(-) >>> create mode 100644 python/qemu/aqmp/aqmp_tui.py >>> >>> diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py >>> new file mode 100644 >>> index 00..d3180e38bf >>> --- /dev/null >>> +++ b/python/qemu/aqmp/aqmp_tui.py >>> @@ -0,0 +1,637 @@ >>> +# Copyright (c) 2021 >>> +# >>> +# Authors: >>> +# Niteesh Babu G S >>> +# >>> +# This work is licensed under the terms of the GNU GPL, version 2 or >>> +# later. See the COPYING file in the top-level directory. >>> +""" >>> +AQMP TUI >>> + >>> +AQMP TUI is an asynchronous interface built on top the of the AQMP >>> library. >>> +It is the successor of QMP-shell and is bought-in as a replacement for >>> it. >>> + >>> +Example Usage: aqmp-tui >>> +Full Usage: aqmp-tui --help >>> +""" >>> + >>> +import argparse >>> +import asyncio >>> +import logging >>> +from logging import Handler, LogRecord >>> +import signal >>> +from typing import ( >>> +List, >>> +Optional, >>> +Tuple, >>> +Type, >>> +Union, >>> +cast, >>> +) >>> + >>> +import urwid >>> +import urwid_readline >>> + >>> +from ..qmp import QEMUMonitorProtocol, QMPBadPortError >>> +from .error import ProtocolError >>> +from .message import DeserializationError, Message, UnexpectedTypeError >>> +from .protocol import ConnectError, Runstate >>> +from .qmp_client import ExecInterruptedError, QMPClient >>> +from .util import create_task, pretty_traceback >>> + >>> + >>> +# The name of the signal that is used to update the history list >>> +UPDATE_MSG: str = 'UPDATE_MSG' >>> + >>> + >>> +def format_json(msg: str) -> str: >>> +""" >>> +Formats given multi-line JSON message into a single-line message. >>> +Converting into single line is more asthetically pleasing when >>> looking >>> +along with error messages. >>> + >>> +Eg: >>> +Input: >>> + [ 1, >>> +true, >>> +3 ] >>> +The above input is not a valid QMP message and produces the >>> following error >>> +"QMP message is not a JSON object." >>> +When displaying this in TUI in multiline mode we get >>> + >>> +[ 1, >>> + true, >>> + 3 ]: QMP message is not a JSON object. >>> + >>> +whereas in singleline mode we get the following >>> + >>> +[1, true, 3]: QMP message is not a JSON object. >>> + >>> +The single line mode is more asthetically pleasing. >>> + >>> +:param msg: >>> +The message to formatted into single line. >>> + >>> +:return: Formatted singleline message. >>> + >>> +NOTE: We cannot use the JSON module here because it is only capable >>> of >>> +format valid JSON messages. But here the goal is to also format >>> invalid >>> +JSON messages. >>> +""" >>> +msg = msg.replace('\n', '') >>> +words = msg.split(' ') >>> +words = [word for word in words if word != ''] >>> >> >> try list(filter(None, words)) -- it's a little easier to read. >> > Thanks. Fixed. > >> >> >>> +return ' '.join(words) >>> + >>> + >>> +def has_tui_handler(logger: logging.Logger, >>> +handler_type: Type[Handler]) -> bool: >>> >> >> maybe has_handler_type(...), since you wrote something a bit more generic >> than just checking for the TUI handler. >> > Ahh yes. First I had hardcoded the TUILogHandler type but then decided to > make it more generic. > >> >> >>> +""" >>> +The Logger class has no interface to check if a certain type of >>> handler is >>> +installed or not. So we provide an interface to do so. >>> + >>> +:param logger: >>> +Logger object >>> +:param handler_type: >>> +The type of the handler to be checked. >>> + >>> +:return: returns True if handler of type `handler_type` is >>> installed else >>> + False. >>> >> >> If you wanted to fit this on one line, the "else False" is implied and >> could be omitted. >> > Omitted > >> >> >>> +""" >>> +handlers = logger.handlers >>> +for handler in handlers: >>> >> >> You could combine these lines if you wanted: for handler in >> logger.handlers: ... >> > Fixed. > >> >> >>> +if isinstance(handler, handler_type): >>> +return True >>> +return False >>> + >>> + >>> +class App(QMPClient): >>> +
Re: [PATCH v5 2/5] python/aqmp-tui: Add AQMP TUI
On Tue, Aug 24, 2021 at 12:30 AM John Snow wrote: > > > On Mon, Aug 23, 2021 at 12:31 PM G S Niteesh Babu > wrote: > >> Added AQMP TUI. >> >> Implements the follwing basic features: >> 1) Command transmission/reception. >> 2) Shows events asynchronously. >> 3) Shows server status in the bottom status bar. >> 4) Automatic retries on disconnects and error conditions. >> >> Also added type annotations and necessary pylint/mypy configurations. >> >> Signed-off-by: G S Niteesh Babu >> --- >> python/qemu/aqmp/aqmp_tui.py | 637 +++ >> python/setup.cfg | 13 +- >> 2 files changed, 649 insertions(+), 1 deletion(-) >> create mode 100644 python/qemu/aqmp/aqmp_tui.py >> >> diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py >> new file mode 100644 >> index 00..d3180e38bf >> --- /dev/null >> +++ b/python/qemu/aqmp/aqmp_tui.py >> @@ -0,0 +1,637 @@ >> +# Copyright (c) 2021 >> +# >> +# Authors: >> +# Niteesh Babu G S >> +# >> +# This work is licensed under the terms of the GNU GPL, version 2 or >> +# later. See the COPYING file in the top-level directory. >> +""" >> +AQMP TUI >> + >> +AQMP TUI is an asynchronous interface built on top the of the AQMP >> library. >> +It is the successor of QMP-shell and is bought-in as a replacement for >> it. >> + >> +Example Usage: aqmp-tui >> +Full Usage: aqmp-tui --help >> +""" >> + >> +import argparse >> +import asyncio >> +import logging >> +from logging import Handler, LogRecord >> +import signal >> +from typing import ( >> +List, >> +Optional, >> +Tuple, >> +Type, >> +Union, >> +cast, >> +) >> + >> +import urwid >> +import urwid_readline >> + >> +from ..qmp import QEMUMonitorProtocol, QMPBadPortError >> +from .error import ProtocolError >> +from .message import DeserializationError, Message, UnexpectedTypeError >> +from .protocol import ConnectError, Runstate >> +from .qmp_client import ExecInterruptedError, QMPClient >> +from .util import create_task, pretty_traceback >> + >> + >> +# The name of the signal that is used to update the history list >> +UPDATE_MSG: str = 'UPDATE_MSG' >> + >> + >> +def format_json(msg: str) -> str: >> +""" >> +Formats given multi-line JSON message into a single-line message. >> +Converting into single line is more asthetically pleasing when >> looking >> +along with error messages. >> + >> +Eg: >> +Input: >> + [ 1, >> +true, >> +3 ] >> +The above input is not a valid QMP message and produces the >> following error >> +"QMP message is not a JSON object." >> +When displaying this in TUI in multiline mode we get >> + >> +[ 1, >> + true, >> + 3 ]: QMP message is not a JSON object. >> + >> +whereas in singleline mode we get the following >> + >> +[1, true, 3]: QMP message is not a JSON object. >> + >> +The single line mode is more asthetically pleasing. >> + >> +:param msg: >> +The message to formatted into single line. >> + >> +:return: Formatted singleline message. >> + >> +NOTE: We cannot use the JSON module here because it is only capable >> of >> +format valid JSON messages. But here the goal is to also format >> invalid >> +JSON messages. >> +""" >> +msg = msg.replace('\n', '') >> +words = msg.split(' ') >> +words = [word for word in words if word != ''] >> > > try list(filter(None, words)) -- it's a little easier to read. > Thanks. Fixed. > > >> +return ' '.join(words) >> + >> + >> +def has_tui_handler(logger: logging.Logger, >> +handler_type: Type[Handler]) -> bool: >> > > maybe has_handler_type(...), since you wrote something a bit more generic > than just checking for the TUI handler. > Ahh yes. First I had hardcoded the TUILogHandler type but then decided to make it more generic. > > >> +""" >> +The Logger class has no interface to check if a certain type of >> handler is >> +installed or not. So we provide an interface to do so. >> + >> +:param logger: >> +Logger object >> +:param handler_type: >> +The type of the handler to be checked. >> + >> +:return: returns True if handler of type `handler_type` is installed >> else >> + False. >> > > If you wanted to fit this on one line, the "else False" is implied and > could be omitted. > Omitted > > >> +""" >> +handlers = logger.handlers >> +for handler in handlers: >> > > You could combine these lines if you wanted: for handler in > logger.handlers: ... > Fixed. > > >> +if isinstance(handler, handler_type): >> +return True >> +return False >> + >> + >> +class App(QMPClient): >> +""" >> +Implements the AQMP TUI. >> + >> +Initializes the widgets and starts the urwid event loop. >> +""" >> +def __init__(self, address: Union[str, Tuple[str, int]], >> num_retries: int, >> + retry_delay:
Re: [PATCH v5 2/5] python/aqmp-tui: Add AQMP TUI
On Mon, Aug 23, 2021 at 12:31 PM G S Niteesh Babu wrote: > Added AQMP TUI. > > Implements the follwing basic features: > 1) Command transmission/reception. > 2) Shows events asynchronously. > 3) Shows server status in the bottom status bar. > 4) Automatic retries on disconnects and error conditions. > > Also added type annotations and necessary pylint/mypy configurations. > > Signed-off-by: G S Niteesh Babu > --- > python/qemu/aqmp/aqmp_tui.py | 637 +++ > python/setup.cfg | 13 +- > 2 files changed, 649 insertions(+), 1 deletion(-) > create mode 100644 python/qemu/aqmp/aqmp_tui.py > > diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py > new file mode 100644 > index 00..d3180e38bf > --- /dev/null > +++ b/python/qemu/aqmp/aqmp_tui.py > @@ -0,0 +1,637 @@ > +# Copyright (c) 2021 > +# > +# Authors: > +# Niteesh Babu G S > +# > +# This work is licensed under the terms of the GNU GPL, version 2 or > +# later. See the COPYING file in the top-level directory. > +""" > +AQMP TUI > + > +AQMP TUI is an asynchronous interface built on top the of the AQMP > library. > +It is the successor of QMP-shell and is bought-in as a replacement for it. > + > +Example Usage: aqmp-tui > +Full Usage: aqmp-tui --help > +""" > + > +import argparse > +import asyncio > +import logging > +from logging import Handler, LogRecord > +import signal > +from typing import ( > +List, > +Optional, > +Tuple, > +Type, > +Union, > +cast, > +) > + > +import urwid > +import urwid_readline > + > +from ..qmp import QEMUMonitorProtocol, QMPBadPortError > +from .error import ProtocolError > +from .message import DeserializationError, Message, UnexpectedTypeError > +from .protocol import ConnectError, Runstate > +from .qmp_client import ExecInterruptedError, QMPClient > +from .util import create_task, pretty_traceback > + > + > +# The name of the signal that is used to update the history list > +UPDATE_MSG: str = 'UPDATE_MSG' > + > + > +def format_json(msg: str) -> str: > +""" > +Formats given multi-line JSON message into a single-line message. > +Converting into single line is more asthetically pleasing when looking > +along with error messages. > + > +Eg: > +Input: > + [ 1, > +true, > +3 ] > +The above input is not a valid QMP message and produces the following > error > +"QMP message is not a JSON object." > +When displaying this in TUI in multiline mode we get > + > +[ 1, > + true, > + 3 ]: QMP message is not a JSON object. > + > +whereas in singleline mode we get the following > + > +[1, true, 3]: QMP message is not a JSON object. > + > +The single line mode is more asthetically pleasing. > + > +:param msg: > +The message to formatted into single line. > + > +:return: Formatted singleline message. > + > +NOTE: We cannot use the JSON module here because it is only capable of > +format valid JSON messages. But here the goal is to also format > invalid > +JSON messages. > +""" > +msg = msg.replace('\n', '') > +words = msg.split(' ') > +words = [word for word in words if word != ''] > try list(filter(None, words)) -- it's a little easier to read. > +return ' '.join(words) > + > + > +def has_tui_handler(logger: logging.Logger, > +handler_type: Type[Handler]) -> bool: > maybe has_handler_type(...), since you wrote something a bit more generic than just checking for the TUI handler. > +""" > +The Logger class has no interface to check if a certain type of > handler is > +installed or not. So we provide an interface to do so. > + > +:param logger: > +Logger object > +:param handler_type: > +The type of the handler to be checked. > + > +:return: returns True if handler of type `handler_type` is installed > else > + False. > If you wanted to fit this on one line, the "else False" is implied and could be omitted. > +""" > +handlers = logger.handlers > +for handler in handlers: > You could combine these lines if you wanted: for handler in logger.handlers: ... > +if isinstance(handler, handler_type): > +return True > +return False > + > + > +class App(QMPClient): > +""" > +Implements the AQMP TUI. > + > +Initializes the widgets and starts the urwid event loop. > +""" > +def __init__(self, address: Union[str, Tuple[str, int]], num_retries: > int, > + retry_delay: Optional[int]) -> None: > +""" > +Initializes the TUI. > + > +:param address: > +Address of the server to connect to. > +:param num_retries: > +The number of times to retry before stopping to reconnect. > +:param retry_delay: > +The delay(sec) before each retry > +""" > Here and elsewhere, the init
[PATCH v5 2/5] python/aqmp-tui: Add AQMP TUI
Added AQMP TUI. Implements the follwing basic features: 1) Command transmission/reception. 2) Shows events asynchronously. 3) Shows server status in the bottom status bar. 4) Automatic retries on disconnects and error conditions. Also added type annotations and necessary pylint/mypy configurations. Signed-off-by: G S Niteesh Babu --- python/qemu/aqmp/aqmp_tui.py | 637 +++ python/setup.cfg | 13 +- 2 files changed, 649 insertions(+), 1 deletion(-) create mode 100644 python/qemu/aqmp/aqmp_tui.py diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py new file mode 100644 index 00..d3180e38bf --- /dev/null +++ b/python/qemu/aqmp/aqmp_tui.py @@ -0,0 +1,637 @@ +# Copyright (c) 2021 +# +# Authors: +# Niteesh Babu G S +# +# This work is licensed under the terms of the GNU GPL, version 2 or +# later. See the COPYING file in the top-level directory. +""" +AQMP TUI + +AQMP TUI is an asynchronous interface built on top the of the AQMP library. +It is the successor of QMP-shell and is bought-in as a replacement for it. + +Example Usage: aqmp-tui +Full Usage: aqmp-tui --help +""" + +import argparse +import asyncio +import logging +from logging import Handler, LogRecord +import signal +from typing import ( +List, +Optional, +Tuple, +Type, +Union, +cast, +) + +import urwid +import urwid_readline + +from ..qmp import QEMUMonitorProtocol, QMPBadPortError +from .error import ProtocolError +from .message import DeserializationError, Message, UnexpectedTypeError +from .protocol import ConnectError, Runstate +from .qmp_client import ExecInterruptedError, QMPClient +from .util import create_task, pretty_traceback + + +# The name of the signal that is used to update the history list +UPDATE_MSG: str = 'UPDATE_MSG' + + +def format_json(msg: str) -> str: +""" +Formats given multi-line JSON message into a single-line message. +Converting into single line is more asthetically pleasing when looking +along with error messages. + +Eg: +Input: + [ 1, +true, +3 ] +The above input is not a valid QMP message and produces the following error +"QMP message is not a JSON object." +When displaying this in TUI in multiline mode we get + +[ 1, + true, + 3 ]: QMP message is not a JSON object. + +whereas in singleline mode we get the following + +[1, true, 3]: QMP message is not a JSON object. + +The single line mode is more asthetically pleasing. + +:param msg: +The message to formatted into single line. + +:return: Formatted singleline message. + +NOTE: We cannot use the JSON module here because it is only capable of +format valid JSON messages. But here the goal is to also format invalid +JSON messages. +""" +msg = msg.replace('\n', '') +words = msg.split(' ') +words = [word for word in words if word != ''] +return ' '.join(words) + + +def has_tui_handler(logger: logging.Logger, +handler_type: Type[Handler]) -> bool: +""" +The Logger class has no interface to check if a certain type of handler is +installed or not. So we provide an interface to do so. + +:param logger: +Logger object +:param handler_type: +The type of the handler to be checked. + +:return: returns True if handler of type `handler_type` is installed else + False. +""" +handlers = logger.handlers +for handler in handlers: +if isinstance(handler, handler_type): +return True +return False + + +class App(QMPClient): +""" +Implements the AQMP TUI. + +Initializes the widgets and starts the urwid event loop. +""" +def __init__(self, address: Union[str, Tuple[str, int]], num_retries: int, + retry_delay: Optional[int]) -> None: +""" +Initializes the TUI. + +:param address: +Address of the server to connect to. +:param num_retries: +The number of times to retry before stopping to reconnect. +:param retry_delay: +The delay(sec) before each retry +""" +urwid.register_signal(type(self), UPDATE_MSG) +self.window = Window(self) +self.address = address +self.aloop: Optional[asyncio.AbstractEventLoop] = None +self.num_retries = num_retries +self.retry_delay = retry_delay if retry_delay else 2 +self.retry: bool = False +self.exiting: bool = False +super().__init__() + +def add_to_history(self, msg: str, level: Optional[str] = None) -> None: +""" +Appends the msg to the history list. + +:param msg: +The raw message to be appended in string type. +""" +urwid.emit_signal(self, UPDATE_MSG, msg, level) + +def _cb_outbound(self, msg: Message) -> Message: +""" +Callback: