From 2e5f934a09d55ae5807ca1a6e31ba77aac241d01 Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Mon, 12 Jun 2023 08:58:58 +0100 Subject: [PATCH 1/7] Intercept KeyError on direct navigation to metadata tab. Solves the first part (but not all of) of #83. If no config found, then just show a message explaining that there's no config loaded and return. Also the function didn't explicitly return if ``metadata_output_children`` so also fix that. --- wazp/callbacks/metadata.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/wazp/callbacks/metadata.py b/wazp/callbacks/metadata.py index 60e063b..f791689 100644 --- a/wazp/callbacks/metadata.py +++ b/wazp/callbacks/metadata.py @@ -2,6 +2,7 @@ import io import pathlib as pl import re +import warnings import dash import dash_bootstrap_components as dbc @@ -219,7 +220,7 @@ def get_callbacks(app: dash.Dash) -> None: ) def create_metadata_table_and_buttons( metadata_output_children: list, app_storage: dict - ) -> html.Div: + ) -> html.Div | None: """Generate html component with a table holding the metadata per video and with auxiliary buttons for common table manipulations. @@ -241,8 +242,25 @@ def create_metadata_table_and_buttons( html.Div html component holding the metadata dash_table and the auxiliary buttons for common table manipulations + + Warns + ----- + UserWarning + If no configuration is found (from navigating directly + to the ROI tab, for example). """ + try: + app_storage["config"] + except KeyError: + # we've likely navigated to the metadata page without an input + # config yaml file... not necessarily an error + warnings.warn("Configuration not yet loaded.") + return html.Div( + children="No configuration loaded. Please add a " + "configuration file from the 'Home' page." + ) + if not metadata_output_children: metadata_table = create_metadata_table_component_from_df( utils.df_from_metadata_yaml_files( @@ -373,6 +391,7 @@ def create_metadata_table_and_buttons( generate_yaml_tooltip, ] ) + return None @app.callback( Output("metadata-table", "data"), From 5ef2278199ab57267faa58565358a4bde10a0188 Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Mon, 12 Jun 2023 09:12:15 +0100 Subject: [PATCH 2/7] Fine, the uncool typing syntax. --- wazp/callbacks/metadata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wazp/callbacks/metadata.py b/wazp/callbacks/metadata.py index f791689..45dd5b5 100644 --- a/wazp/callbacks/metadata.py +++ b/wazp/callbacks/metadata.py @@ -3,6 +3,7 @@ import pathlib as pl import re import warnings +from typing import Union import dash import dash_bootstrap_components as dbc @@ -220,7 +221,7 @@ def get_callbacks(app: dash.Dash) -> None: ) def create_metadata_table_and_buttons( metadata_output_children: list, app_storage: dict - ) -> html.Div | None: + ) -> Union[html.Div, None]: """Generate html component with a table holding the metadata per video and with auxiliary buttons for common table manipulations. From 46be68a52f2c60dffaa1939e0f3d0102d1269324 Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Tue, 13 Jun 2023 12:10:12 +0100 Subject: [PATCH 3/7] Now covered by tests. --- tests/test_integration/test_common_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_integration/test_common_layout.py b/tests/test_integration/test_common_layout.py index 7b1e9d6..99b7fc2 100644 --- a/tests/test_integration/test_common_layout.py +++ b/tests/test_integration/test_common_layout.py @@ -70,7 +70,7 @@ def test_components_created( pytest.param(fx, marks=mark) for fx, mark in [ ("home_page_name_and_title", []), - ("metadata_page_name_and_title", unloaded_config_xfail), + ("metadata_page_name_and_title", []), ("roi_page_name_and_title", unloaded_config_xfail), ("pose_estimation_page_name_and_title", []), ("dashboard_page_name_and_title", unloaded_config_xfail), From 19d7a69ecde6353b8fbcf998259866ecf22db487 Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Thu, 15 Jun 2023 18:04:05 +0100 Subject: [PATCH 4/7] Guard against Windows server sending an empty list instead of a dict. --- wazp/callbacks/metadata.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wazp/callbacks/metadata.py b/wazp/callbacks/metadata.py index 45dd5b5..2ca8e5d 100644 --- a/wazp/callbacks/metadata.py +++ b/wazp/callbacks/metadata.py @@ -251,6 +251,12 @@ def create_metadata_table_and_buttons( to the ROI tab, for example). """ + # Windows sometimes gives a the State data as an empty _list_ (!) when + # there is nothing present, so guard against that here. + if isinstance(app_storage, list) and app_storage == []: + warnings.warn("Seems there is no data in app storage") + app_storage = {} + try: app_storage["config"] except KeyError: From 6f1c98a7a0e1afba64c5dbcb51ea7d155f514f0b Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Fri, 16 Jun 2023 09:10:28 +0100 Subject: [PATCH 5/7] Better condition and also fix ROI tab. --- tests/test_integration/test_common_layout.py | 2 +- wazp/callbacks/_common.py | 8 ++++++++ wazp/callbacks/metadata.py | 19 +++---------------- wazp/callbacks/roi.py | 5 +++-- 4 files changed, 15 insertions(+), 19 deletions(-) create mode 100644 wazp/callbacks/_common.py diff --git a/tests/test_integration/test_common_layout.py b/tests/test_integration/test_common_layout.py index 99b7fc2..deafe8d 100644 --- a/tests/test_integration/test_common_layout.py +++ b/tests/test_integration/test_common_layout.py @@ -71,7 +71,7 @@ def test_components_created( for fx, mark in [ ("home_page_name_and_title", []), ("metadata_page_name_and_title", []), - ("roi_page_name_and_title", unloaded_config_xfail), + ("roi_page_name_and_title", []), ("pose_estimation_page_name_and_title", []), ("dashboard_page_name_and_title", unloaded_config_xfail), ] diff --git a/wazp/callbacks/_common.py b/wazp/callbacks/_common.py new file mode 100644 index 0000000..497acc9 --- /dev/null +++ b/wazp/callbacks/_common.py @@ -0,0 +1,8 @@ +from dash import html + +NO_CONFIG_MESSAGE = html.Div( + children="No configuration loaded. Please add a " + "configuration file from the 'Home' page." +) + +VIDEO_TYPES = [".avi", ".mp4"] diff --git a/wazp/callbacks/metadata.py b/wazp/callbacks/metadata.py index 2ca8e5d..d2b7826 100644 --- a/wazp/callbacks/metadata.py +++ b/wazp/callbacks/metadata.py @@ -12,9 +12,7 @@ from dash import Input, Output, State, dash_table, dcc, html from wazp import utils - -# TODO: other video extensions? have this in project config file instead? -VIDEO_TYPES = [".avi", ".mp4"] +from wazp.callbacks._common import NO_CONFIG_MESSAGE, VIDEO_TYPES ########################## @@ -251,22 +249,11 @@ def create_metadata_table_and_buttons( to the ROI tab, for example). """ - # Windows sometimes gives a the State data as an empty _list_ (!) when - # there is nothing present, so guard against that here. - if isinstance(app_storage, list) and app_storage == []: - warnings.warn("Seems there is no data in app storage") - app_storage = {} - - try: - app_storage["config"] - except KeyError: + if not app_storage: # we've likely navigated to the metadata page without an input # config yaml file... not necessarily an error warnings.warn("Configuration not yet loaded.") - return html.Div( - children="No configuration loaded. Please add a " - "configuration file from the 'Home' page." - ) + return NO_CONFIG_MESSAGE if not metadata_output_children: metadata_table = create_metadata_table_component_from_df( diff --git a/wazp/callbacks/roi.py b/wazp/callbacks/roi.py index 0e92f8c..c1ec0d1 100644 --- a/wazp/callbacks/roi.py +++ b/wazp/callbacks/roi.py @@ -13,9 +13,8 @@ from PIL import Image from wazp import utils +from wazp.callbacks._common import VIDEO_TYPES -# TODO: other video extensions? have this in project config file instead? -VIDEO_TYPES = [".avi", ".mp4"] ROI_CMAP = px.colors.qualitative.Dark24 @@ -536,6 +535,8 @@ def update_frame_graph( int Maximum frame index (num_frames - 1) """ + if not roi_color_mapping: + return dash.no_update, "", "light", False # If a negative frame index is passed, it means that the video # could not be read correctly. So don't update the frame, From 9470ca7381fa4daae251d308fd7ca84da98803eb Mon Sep 17 00:00:00 2001 From: sfmig <33267254+sfmig@users.noreply.github.com> Date: Fri, 16 Jun 2023 09:57:46 +0100 Subject: [PATCH 6/7] initialise app storage with empty dict (rather than tuple) --- wazp/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wazp/app.py b/wazp/app.py index 29396f7..5d1abef 100644 --- a/wazp/app.py +++ b/wazp/app.py @@ -86,7 +86,7 @@ storage = dcc.Store( id="session-storage", storage_type="session", - data=tuple(), + data={}, ) ############### From cdc6aea4567f078638e2d0275f92f91627eaa595 Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Thu, 22 Jun 2023 08:42:00 +0100 Subject: [PATCH 7/7] Make black happy. --- tests/test_integration/test_common_layout.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/tests/test_integration/test_common_layout.py b/tests/test_integration/test_common_layout.py index deafe8d..5aed183 100644 --- a/tests/test_integration/test_common_layout.py +++ b/tests/test_integration/test_common_layout.py @@ -34,16 +34,12 @@ def test_components_created( # wait for sidebar to be rendered try: - dash_duo.wait_for_text_to_equal( - "#sidebar h2", "WAZP 🐝", timeout=timeout - ) + dash_duo.wait_for_text_to_equal("#sidebar h2", "WAZP 🐝", timeout=timeout) except selenium.common.exceptions.TimeoutException: pytest.fail("Sidebar component not generated") # check there are no errors in browser console - assert ( - dash_duo.get_logs() == [] - ), f"There are {len(dash_duo.get_logs())} errors" + assert dash_duo.get_logs() == [], f"There are {len(dash_duo.get_logs())} errors" " in the browser console!" @@ -133,6 +129,4 @@ def test_sidebar_links( # ... # NOTE: this is expected to fail for a few pages (hence the marked xfails) - assert ( - dash_duo.get_logs() == [] - ), "There are errors in the browser console!" + assert dash_duo.get_logs() == [], "There are errors in the browser console!"