pygui cleaned up error display by creating top level app methods for displaying exceptions and errors, logging exceptions, and making sure they work for background tasks

This commit is contained in:
Blake Harnden 2020-05-03 12:42:56 -07:00
parent 0999fabb14
commit 1dd45f4424
14 changed files with 57 additions and 82 deletions

View file

@ -1,11 +1,15 @@
import logging
import math import math
import time import time
import tkinter as tk import tkinter as tk
from tkinter import font, ttk from tkinter import font, ttk
from tkinter.ttk import Progressbar from tkinter.ttk import Progressbar
import grpc
from core.gui import appconfig, themes from core.gui import appconfig, themes
from core.gui.coreclient import CoreClient from core.gui.coreclient import CoreClient
from core.gui.dialogs.error import ErrorDialog
from core.gui.graph.graph import CanvasGraph from core.gui.graph.graph import CanvasGraph
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum, Images
from core.gui.menubar import Menubar from core.gui.menubar import Menubar
@ -138,6 +142,18 @@ class Application(ttk.Frame):
message = f"Task ran for {total:.3f} seconds" message = f"Task ran for {total:.3f} seconds"
self.statusbar.set_status(message) self.statusbar.set_status(message)
def show_grpc_exception(self, title: str, e: grpc.RpcError) -> None:
logging.exception("app grpc exception", exc_info=e)
message = e.details()
self.show_error(title, message)
def show_exception(self, title: str, e: Exception) -> None:
logging.exception("app exception", exc_info=e)
self.show_error(title, str(e))
def show_error(self, title: str, message: str) -> None:
self.after(0, lambda: ErrorDialog(self, self, title, message).show())
def on_closing(self): def on_closing(self):
self.menubar.prompt_save_running_session(True) self.menubar.prompt_save_running_session(True)

View file

@ -16,9 +16,9 @@ from core.api.grpc.mobility_pb2 import MobilityConfig
from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig, ServiceFileConfig from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig, ServiceFileConfig
from core.api.grpc.wlan_pb2 import WlanConfig from core.api.grpc.wlan_pb2 import WlanConfig
from core.gui import appconfig from core.gui import appconfig
from core.gui.dialogs.error import ErrorDialog
from core.gui.dialogs.mobilityplayer import MobilityPlayer from core.gui.dialogs.mobilityplayer import MobilityPlayer
from core.gui.dialogs.sessions import SessionsDialog from core.gui.dialogs.sessions import SessionsDialog
from core.gui.errors import show_grpc_error
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.graph.edges import CanvasEdge from core.gui.graph.edges import CanvasEdge
from core.gui.graph.node import CanvasNode from core.gui.graph.node import CanvasNode
@ -343,7 +343,7 @@ class CoreClient:
response = self.client.get_session_metadata(self.session_id) response = self.client.get_session_metadata(self.session_id)
self.parse_metadata(response.config) self.parse_metadata(response.config)
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.after(0, show_grpc_error, e, self.app, self.app) self.app.show_grpc_exception("Join Session Error", e)
# update ui to represent current state # update ui to represent current state
self.app.after(0, self.app.joined_session_update) self.app.after(0, self.app.joined_session_update)
@ -426,21 +426,16 @@ class CoreClient:
) )
self.join_session(response.session_id, query_location=False) self.join_session(response.session_id, query_location=False)
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.after(0, show_grpc_error, e, self.app, self.app) self.app.show_grpc_exception("New Session Error", e)
def delete_session(self, session_id: int = None, parent_frame=None): def delete_session(self, session_id: int = None):
if session_id is None: if session_id is None:
session_id = self.session_id session_id = self.session_id
try: try:
response = self.client.delete_session(session_id) response = self.client.delete_session(session_id)
logging.info("deleted session(%s), Result: %s", session_id, response) logging.info("deleted session(%s), Result: %s", session_id, response)
except grpc.RpcError as e: except grpc.RpcError as e:
# use the right master widget so the error dialog displays self.app.show_grpc_exception("Delete Session Error", e)
# right on top of it
master = self.app
if parent_frame:
master = parent_frame
self.app.after(0, show_grpc_error, e, master, self.app)
def setup(self): def setup(self):
""" """
@ -472,7 +467,9 @@ class CoreClient:
dialog = SessionsDialog(self.app, self.app, True) dialog = SessionsDialog(self.app, self.app, True)
dialog.show() dialog.show()
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e, self.app, self.app) logging.exception("core setup error")
dialog = ErrorDialog(self.app, self.app, "Setup Error", e.details())
dialog.show()
self.app.close() self.app.close()
def edit_node(self, core_node: core_pb2.Node): def edit_node(self, core_node: core_pb2.Node):
@ -481,7 +478,7 @@ class CoreClient:
self.session_id, core_node.id, core_node.position, source=GUI_SOURCE self.session_id, core_node.id, core_node.position, source=GUI_SOURCE
) )
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.after(0, show_grpc_error, e, self.app, self.app) self.app.show_grpc_exception("Edit Node Error", e)
def start_session(self) -> core_pb2.StartSessionResponse: def start_session(self) -> core_pb2.StartSessionResponse:
self.interfaces_manager.reset_mac() self.interfaces_manager.reset_mac()
@ -532,7 +529,7 @@ class CoreClient:
if response.result: if response.result:
self.set_metadata() self.set_metadata()
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.after(0, show_grpc_error, e, self.app, self.app) self.app.show_grpc_exception("Start Session Error", e)
return response return response
def stop_session(self, session_id: int = None) -> core_pb2.StartSessionResponse: def stop_session(self, session_id: int = None) -> core_pb2.StartSessionResponse:
@ -543,7 +540,7 @@ class CoreClient:
response = self.client.stop_session(session_id) response = self.client.stop_session(session_id)
logging.info("stopped session(%s), result: %s", session_id, response) logging.info("stopped session(%s), result: %s", session_id, response)
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.after(0, show_grpc_error, e, self.app, self.app) self.app.show_grpc_exception("Stop Session Error", e)
return response return response
def show_mobility_players(self): def show_mobility_players(self):
@ -597,7 +594,7 @@ class CoreClient:
logging.info("launching terminal %s", cmd) logging.info("launching terminal %s", cmd)
os.system(cmd) os.system(cmd)
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.after(0, show_grpc_error, e, self.app, self.app) self.app.show_grpc_exception("Node Terminal Error", e)
def save_xml(self, file_path: str): def save_xml(self, file_path: str):
""" """
@ -610,7 +607,7 @@ class CoreClient:
response = self.client.save_xml(self.session_id, file_path) response = self.client.save_xml(self.session_id, file_path)
logging.info("saved xml file %s, result: %s", file_path, response) logging.info("saved xml file %s, result: %s", file_path, response)
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.after(0, show_grpc_error, e, self.app, self.app) self.app.show_grpc_exception("Save XML Error", e)
def open_xml(self, file_path: str): def open_xml(self, file_path: str):
""" """
@ -621,7 +618,7 @@ class CoreClient:
logging.info("open xml file %s, response: %s", file_path, response) logging.info("open xml file %s, response: %s", file_path, response)
self.join_session(response.session_id) self.join_session(response.session_id)
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.after(0, show_grpc_error, e, self.app, self.app) self.app.show_grpc_exception("Open XML Error", e)
def get_node_service(self, node_id: int, service_name: str) -> NodeServiceData: def get_node_service(self, node_id: int, service_name: str) -> NodeServiceData:
response = self.client.get_node_service(self.session_id, node_id, service_name) response = self.client.get_node_service(self.session_id, node_id, service_name)

View file

@ -10,7 +10,6 @@ import grpc
from core.api.grpc.services_pb2 import ServiceValidationMode from core.api.grpc.services_pb2 import ServiceValidationMode
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll
@ -116,8 +115,8 @@ class ConfigServiceConfigDialog(Dialog):
self.modified_files.add(file) self.modified_files.add(file)
self.temp_service_files[file] = data self.temp_service_files[file] = data
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.show_grpc_exception("Get Config Service Error", e)
self.has_error = True self.has_error = True
show_grpc_error(e, self.app, self.app)
def draw(self): def draw(self):
self.top.columnconfigure(0, weight=1) self.top.columnconfigure(0, weight=1)
@ -323,22 +322,17 @@ class ConfigServiceConfigDialog(Dialog):
self.destroy() self.destroy()
return return
try: service_config = self.canvas_node.config_service_configs.setdefault(
service_config = self.canvas_node.config_service_configs.setdefault( self.service_name, {}
self.service_name, {} )
) if self.config_frame:
if self.config_frame: self.config_frame.parse_config()
self.config_frame.parse_config() service_config["config"] = {x.name: x.value for x in self.config.values()}
service_config["config"] = { templates_config = service_config.setdefault("templates", {})
x.name: x.value for x in self.config.values() for file in self.modified_files:
} templates_config[file] = self.temp_service_files[file]
templates_config = service_config.setdefault("templates", {}) all_current = current_listbox.get(0, tk.END)
for file in self.modified_files: current_listbox.itemconfig(all_current.index(self.service_name), bg="green")
templates_config[file] = self.temp_service_files[file]
all_current = current_listbox.get(0, tk.END)
current_listbox.itemconfig(all_current.index(self.service_name), bg="green")
except grpc.RpcError as e:
show_grpc_error(e, self.top, self.app)
self.destroy() self.destroy()
def handle_template_changed(self, event: tk.Event): def handle_template_changed(self, event: tk.Event):

View file

@ -9,7 +9,6 @@ from typing import TYPE_CHECKING, Any
import grpc import grpc
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum, Images
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame from core.gui.widgets import ConfigFrame
@ -78,7 +77,7 @@ class EmaneModelDialog(Dialog):
) )
self.draw() self.draw()
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e, self.app, self.app) self.app.show_grpc_exception("Get EMANE Config Error", e)
self.has_error = True self.has_error = True
self.destroy() self.destroy()

View file

@ -1,8 +1,6 @@
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import grpc
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum, Images
from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.themes import FRAME_PAD, PADX, PADY
@ -41,21 +39,3 @@ class ErrorDialog(Dialog):
button = ttk.Button(self.top, text="Close", command=lambda: self.destroy()) button = ttk.Button(self.top, text="Close", command=lambda: self.destroy())
button.grid(sticky="ew") button.grid(sticky="ew")
def show_exception(app: "Application", title: str, exception: Exception) -> None:
dialog = ErrorDialog(app, app, title, str(exception))
dialog.show()
def show_grpc_error(e: grpc.RpcError, master, app: "Application") -> None:
title = [x.capitalize() for x in e.code().name.lower().split("_")]
title = " ".join(title)
title = f"GRPC {title}"
dialog = ErrorDialog(master, app, title, e.details())
dialog.show()
def show_error(app: "Application", title: str, message: str) -> None:
dialog = ErrorDialog(app, app, title, message)
dialog.show()

View file

@ -7,7 +7,6 @@ from typing import TYPE_CHECKING
import grpc import grpc
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame from core.gui.widgets import ConfigFrame
@ -33,8 +32,8 @@ class MobilityConfigDialog(Dialog):
self.config = self.app.core.get_mobility_config(self.node.id) self.config = self.app.core.get_mobility_config(self.node.id)
self.draw() self.draw()
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.show_grpc_exception("Get Mobility Config Error", e)
self.has_error = True self.has_error = True
show_grpc_error(e, self.app, self.app)
self.destroy() self.destroy()
def draw(self): def draw(self):

View file

@ -6,7 +6,6 @@ import grpc
from core.api.grpc.mobility_pb2 import MobilityAction from core.api.grpc.mobility_pb2 import MobilityAction
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum, Images
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
@ -154,7 +153,7 @@ class MobilityPlayerDialog(Dialog):
session_id, self.node.id, MobilityAction.START session_id, self.node.id, MobilityAction.START
) )
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e, self.top, self.app) self.app.show_grpc_exception("Mobility Error", e)
def click_pause(self): def click_pause(self):
self.set_pause() self.set_pause()
@ -164,7 +163,7 @@ class MobilityPlayerDialog(Dialog):
session_id, self.node.id, MobilityAction.PAUSE session_id, self.node.id, MobilityAction.PAUSE
) )
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e, self.top, self.app) self.app.show_grpc_exception("Mobility Error", e)
def click_stop(self): def click_stop(self):
self.set_stop() self.set_stop()
@ -174,4 +173,4 @@ class MobilityPlayerDialog(Dialog):
session_id, self.node.id, MobilityAction.STOP session_id, self.node.id, MobilityAction.STOP
) )
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e, self.top, self.app) self.app.show_grpc_exception("Mobility Error", e)

View file

@ -9,7 +9,6 @@ import grpc
from core.api.grpc.services_pb2 import ServiceValidationMode from core.api.grpc.services_pb2 import ServiceValidationMode
from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum, Images
from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CodeText, ListboxScroll from core.gui.widgets import CodeText, ListboxScroll
@ -119,8 +118,8 @@ class ServiceConfigDialog(Dialog):
for file, data in file_configs.items(): for file, data in file_configs.items():
self.temp_service_files[file] = data self.temp_service_files[file] = data
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.show_grpc_exception("Get Node Service Error", e)
self.has_error = True self.has_error = True
show_grpc_error(e, self.master, self.app)
def draw(self): def draw(self):
self.top.columnconfigure(0, weight=1) self.top.columnconfigure(0, weight=1)
@ -484,7 +483,7 @@ class ServiceConfigDialog(Dialog):
) )
self.current_service_color("green") self.current_service_color("green")
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e, self.top, self.app) self.app.show_grpc_exception("Save Service Config Error", e)
self.destroy() self.destroy()
def display_service_file_data(self, event: tk.Event): def display_service_file_data(self, event: tk.Event):

View file

@ -5,7 +5,6 @@ from typing import TYPE_CHECKING
import grpc import grpc
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame from core.gui.widgets import ConfigFrame
@ -28,8 +27,8 @@ class SessionOptionsDialog(Dialog):
response = self.app.core.client.get_session_options(session_id) response = self.app.core.client.get_session_options(session_id)
return response.config return response.config
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.show_grpc_exception("Get Session Options Error", e)
self.has_error = True self.has_error = True
show_grpc_error(e, self.app, self.app)
self.destroy() self.destroy()
def draw(self): def draw(self):
@ -56,5 +55,5 @@ class SessionOptionsDialog(Dialog):
response = self.app.core.client.set_session_options(session_id, config) response = self.app.core.client.set_session_options(session_id, config)
logging.info("saved session config: %s", response) logging.info("saved session config: %s", response)
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e, self.top, self.app) self.app.show_grpc_exception("Set Session Options Error", e)
self.destroy() self.destroy()

View file

@ -7,7 +7,6 @@ import grpc
from core.api.grpc import core_pb2 from core.api.grpc import core_pb2
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum, Images
from core.gui.task import ProgressTask from core.gui.task import ProgressTask
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
@ -37,7 +36,7 @@ class SessionsDialog(Dialog):
logging.info("sessions: %s", response) logging.info("sessions: %s", response)
return response.sessions return response.sessions
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e, self.app, self.app) self.app.show_grpc_exception("Get Sessions Error", e)
self.destroy() self.destroy()
def draw(self) -> None: def draw(self) -> None:

View file

@ -4,7 +4,6 @@ from typing import TYPE_CHECKING
import grpc import grpc
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame from core.gui.widgets import ConfigFrame
@ -21,7 +20,7 @@ class WlanConfigDialog(Dialog):
self, master: "Application", app: "Application", canvas_node: "CanvasNode" self, master: "Application", app: "Application", canvas_node: "CanvasNode"
): ):
super().__init__( super().__init__(
master, app, f"{canvas_node.core_node.name} Wlan Configuration" master, app, f"{canvas_node.core_node.name} WLAN Configuration"
) )
self.canvas_node = canvas_node self.canvas_node = canvas_node
self.node = canvas_node.core_node self.node = canvas_node.core_node
@ -38,7 +37,7 @@ class WlanConfigDialog(Dialog):
self.init_draw_range() self.init_draw_range()
self.draw() self.draw()
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e, self.app, self.app) self.app.show_grpc_exception("WLAN Config Error", e)
self.has_error = True self.has_error = True
self.destroy() self.destroy()

View file

@ -14,7 +14,6 @@ from core.gui.dialogs.nodeconfig import NodeConfigDialog
from core.gui.dialogs.nodeconfigservice import NodeConfigServiceDialog from core.gui.dialogs.nodeconfigservice import NodeConfigServiceDialog
from core.gui.dialogs.nodeservice import NodeServiceDialog from core.gui.dialogs.nodeservice import NodeServiceDialog
from core.gui.dialogs.wlanconfig import WlanConfigDialog from core.gui.dialogs.wlanconfig import WlanConfigDialog
from core.gui.errors import show_grpc_error
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.graph.edges import CanvasEdge from core.gui.graph.edges import CanvasEdge
from core.gui.graph.tooltip import CanvasTooltip from core.gui.graph.tooltip import CanvasTooltip
@ -180,7 +179,7 @@ class CanvasNode:
output = self.app.core.run(self.core_node.id) output = self.app.core.run(self.core_node.id)
self.tooltip.text.set(output) self.tooltip.text.set(output)
except grpc.RpcError as e: except grpc.RpcError as e:
show_grpc_error(e, self.app, self.app) self.app.show_grpc_exception("Observer Error", e)
def on_leave(self, event: tk.Event): def on_leave(self, event: tk.Event):
self.tooltip.on_leave(event) self.tooltip.on_leave(event)

View file

@ -2,8 +2,6 @@ import logging
import threading import threading
from typing import Any, Callable, Tuple from typing import Any, Callable, Tuple
from core.gui.errors import show_exception
class ProgressTask: class ProgressTask:
def __init__( def __init__(
@ -33,7 +31,6 @@ class ProgressTask:
self.app.after(0, self.callback, *values) self.app.after(0, self.callback, *values)
except Exception as e: except Exception as e:
logging.exception("progress task exception") logging.exception("progress task exception")
args = (self.app, "Task Error", e) self.app.show_exception("Task Error", e)
self.app.after(0, show_exception, *args)
finally: finally:
self.app.after(0, self.app.progress_task_complete) self.app.after(0, self.app.progress_task_complete)

View file

@ -9,7 +9,6 @@ from core.api.grpc import core_pb2
from core.gui.dialogs.customnodes import CustomNodesDialog from core.gui.dialogs.customnodes import CustomNodesDialog
from core.gui.dialogs.marker import MarkerDialog from core.gui.dialogs.marker import MarkerDialog
from core.gui.dialogs.runtool import RunToolDialog from core.gui.dialogs.runtool import RunToolDialog
from core.gui.errors import show_error
from core.gui.graph.enums import GraphMode from core.gui.graph.enums import GraphMode
from core.gui.graph.shapeutils import ShapeType, is_marker from core.gui.graph.shapeutils import ShapeType, is_marker
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum, Images
@ -273,7 +272,7 @@ class Toolbar(ttk.Frame):
self.app.core.show_mobility_players() self.app.core.show_mobility_players()
else: else:
message = "\n".join(response.exceptions) message = "\n".join(response.exceptions)
show_error(self.app, "Start Session Error", message) self.app.show_error("Start Session Error", message)
def set_runtime(self): def set_runtime(self):
self.runtime_frame.tkraise() self.runtime_frame.tkraise()