Re: [PATCH v5 2/5] python/aqmp-tui: Add AQMP TUI

2021-09-27 Thread John Snow
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

2021-08-24 Thread Niteesh G. S.
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

2021-08-23 Thread John Snow
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

2021-08-23 Thread G S Niteesh Babu
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: