From d7fa40699e8fc63447e3c31346d593bf43a17ea4 Mon Sep 17 00:00:00 2001 From: Andreas Eknes Lie Date: Mon, 29 Apr 2024 16:48:14 +0200 Subject: [PATCH 01/10] Unpin pylint --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ffdd0f270..d0c92ae83 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ "dash[testing]", "isort", "mypy", - "pylint<=2.13.9", # Locked due to https://github.com/equinor/webviz-subsurface/issues/1052 + "pylint", "pytest-mock", "pytest-xdist", "pytest-forked", From 52848f681aa6c19afa624f7be10d1db0c28b74e5 Mon Sep 17 00:00:00 2001 From: Andreas Eknes Lie Date: Mon, 29 Apr 2024 17:03:18 +0200 Subject: [PATCH 02/10] Avoid using non-existant variable --- .../_volumetric_analysis/controllers/tornado_controllers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webviz_subsurface/plugins/_volumetric_analysis/controllers/tornado_controllers.py b/webviz_subsurface/plugins/_volumetric_analysis/controllers/tornado_controllers.py index 5263616d8..4d48939d6 100644 --- a/webviz_subsurface/plugins/_volumetric_analysis/controllers/tornado_controllers.py +++ b/webviz_subsurface/plugins/_volumetric_analysis/controllers/tornado_controllers.py @@ -73,6 +73,7 @@ def _update_tornado_pages( if page_selected == "torn_bulk_inplace" else [selections["Response"]] ) + realplot = None for response in responses: if not (response == "BULK" and page_selected == "torn_bulk_inplace"): if selections["Reference"] not in selections["Sensitivities"]: @@ -132,7 +133,7 @@ def _update_tornado_pages( height="39vh", table_id={"table_id": f"{page_selected}-torntable"}, ) - elif selections["bottom_viz"] == "realplot" and figures: + elif realplot and selections["bottom_viz"] == "realplot" and figures: bottom_display = [ wcc.Graph( config={"displayModeBar": False}, From a14c15ab365ae000a7153cf750d7af20b48f79a2 Mon Sep 17 00:00:00 2001 From: Andreas Eknes Lie Date: Tue, 30 Apr 2024 13:08:18 +0200 Subject: [PATCH 03/10] Remove useless pylint control --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 29b802eb4..8ee316a63 100644 --- a/.pylintrc +++ b/.pylintrc @@ -6,7 +6,7 @@ init-hook = "import astroid; astroid.context.InferenceContext.max_inferred = 500 [MESSAGES CONTROL] disable = bad-continuation, missing-docstring, duplicate-code, logging-fstring-interpolation, unspecified-encoding -enable = useless-suppression, no-print +enable = useless-suppression [DESIGN] From 6e7652645c4be4ae5e31fd4ee54c91cf4fb79a8b Mon Sep 17 00:00:00 2001 From: Andreas Eknes Lie Date: Tue, 30 Apr 2024 13:08:51 +0200 Subject: [PATCH 04/10] Fix warning string --- webviz_subsurface/_components/color_picker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webviz_subsurface/_components/color_picker.py b/webviz_subsurface/_components/color_picker.py index 2769d5db4..13bedd29a 100644 --- a/webviz_subsurface/_components/color_picker.py +++ b/webviz_subsurface/_components/color_picker.py @@ -73,7 +73,7 @@ def get_color(self, color_list: List, filter_query: Dict[str, str]) -> str: warnings.warn("No color found for filter!") return "#ffffff" if len(df["COLOR"].unique()) > 1: - warnings.warn("Multiple colors found for filter. " "Return first color.") + warnings.warn(f"Multiple colors found for filter, using first color: {color_list[df.index[0]]}") return color_list[df.index[0]] @property From 1d124716806a53c049f27ab992c36f3aef9a9a93 Mon Sep 17 00:00:00 2001 From: Andreas Eknes Lie Date: Tue, 30 Apr 2024 13:10:04 +0200 Subject: [PATCH 05/10] Replace range calculation with quadratic calculation --- webviz_subsurface/_figures/px_figure.py | 10 ++++++---- .../utils/table_and_figure_utils.py | 10 +++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/webviz_subsurface/_figures/px_figure.py b/webviz_subsurface/_figures/px_figure.py index 29b5f4cfd..6cca98640 100644 --- a/webviz_subsurface/_figures/px_figure.py +++ b/webviz_subsurface/_figures/px_figure.py @@ -1,4 +1,5 @@ import inspect +import math from typing import Any, Callable, List, Optional, Union import pandas as pd @@ -40,11 +41,12 @@ def set_default_args(**plotargs: Any) -> dict: if plotargs.get("facet_col") is not None: facet_cols = plotargs["data_frame"][plotargs["facet_col"]].nunique() + + x = math.ceil((math.sqrt(1 + 4 * facet_cols) - 1) / 2) + facet_col_wrap = min(x, 20) + plotargs.update( - facet_col_wrap=min( - min([x for x in range(100) if (x * (x + 1)) >= facet_cols]), - 20, - ), + facet_col_wrap=facet_col_wrap, facet_row_spacing=max((0.08 - (0.00071 * facet_cols)), 0.03), facet_col_spacing=max((0.06 - (0.00071 * facet_cols)), 0.03), ) diff --git a/webviz_subsurface/plugins/_volumetric_analysis/utils/table_and_figure_utils.py b/webviz_subsurface/plugins/_volumetric_analysis/utils/table_and_figure_utils.py index e1ebb4aba..032c13bb3 100644 --- a/webviz_subsurface/plugins/_volumetric_analysis/utils/table_and_figure_utils.py +++ b/webviz_subsurface/plugins/_volumetric_analysis/utils/table_and_figure_utils.py @@ -141,14 +141,14 @@ def add_correlation_line(figure: go.Figure, xy_min: float, xy_max: float) -> go. def create_figure_matrix(figures: List[go.Figure]) -> List[List[go.Figure]]: """Convert a list of figures into a matrix for display""" - figs_in_row = min( - min([x for x in range(100) if (x * (x + 1)) > len(figures)]), - 20, - ) + + x = math.ceil((math.sqrt(1 + 4 * len(figures)) - 1) / 2) + figs_in_row = min(x, 20) + len_of_matrix = figs_in_row * math.ceil(len(figures) / figs_in_row) # extend figure list with None to fit size of matrix figures.extend([None] * (len_of_matrix - len(figures))) - return [figures[i : i + figs_in_row] for i in range(0, len_of_matrix, figs_in_row)] + return [figures[i: i + figs_in_row] for i in range(0, len_of_matrix, figs_in_row)] def update_tornado_figures_xaxis(figures: List[go.Figure]) -> None: From 4107b5106e4f18e9ca4d1b194b6809a5af674a63 Mon Sep 17 00:00:00 2001 From: Andreas Eknes Lie Date: Tue, 30 Apr 2024 13:10:39 +0200 Subject: [PATCH 06/10] Actually raise exceptions if failures occur --- webviz_subsurface/plugins/_prod_misfit/_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webviz_subsurface/plugins/_prod_misfit/_plugin.py b/webviz_subsurface/plugins/_prod_misfit/_plugin.py index b9afa8e57..c71fbf8e3 100644 --- a/webviz_subsurface/plugins/_prod_misfit/_plugin.py +++ b/webviz_subsurface/plugins/_prod_misfit/_plugin.py @@ -439,7 +439,7 @@ def _get_wells_vectors_phases( wells, vectors = sorted(wells), sorted(vectors) if not vectors: - RuntimeError("No WOPT, WWPT or WGPT vectors found.") + raise RuntimeError("No WOPT, WWPT or WGPT vectors found.") if drop_list: logging.debug( @@ -496,7 +496,7 @@ def _get_well_collections_from_attr( df_well_groups = well_attributes.dataframe_melted.dropna() df_cols = df_well_groups.columns if "WELL" not in df_cols or "VALUE" not in df_cols: - RuntimeError( + raise RuntimeError( f"The {well_attributes.file_name} file must contain the columns" " 'WELL' and 'VALUE'" ) From 7201cb87a2d9fb5a86609aaf2e34d4e509457f0f Mon Sep 17 00:00:00 2001 From: Andreas Eknes Lie Date: Tue, 30 Apr 2024 13:17:27 +0200 Subject: [PATCH 07/10] Replace badly chained expression --- webviz_subsurface/plugins/_well_cross_section_fmu.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/webviz_subsurface/plugins/_well_cross_section_fmu.py b/webviz_subsurface/plugins/_well_cross_section_fmu.py index 99b5817e4..798fa668f 100644 --- a/webviz_subsurface/plugins/_well_cross_section_fmu.py +++ b/webviz_subsurface/plugins/_well_cross_section_fmu.py @@ -104,10 +104,6 @@ def __init__( ): super().__init__() - if wellfiles is not None == wellfolder is not None: - raise ValueError( - 'Incorrent arguments. Either provide "wellfiles" or "wellfolder"' - ) self.wellfolder = wellfolder self.wellsuffix = wellsuffix self.wellfiles: List[str] @@ -115,6 +111,10 @@ def __init__( self.wellfiles = json.load(find_files(wellfolder, wellsuffix)) elif wellfiles is not None: self.wellfiles = [str(well) for well in wellfiles] + else: + raise ValueError( + "Incorrect arguments, either provide wellfiles or wellfolder" + ) self.surfacefolder = surfacefolder self.surfacefiles = surfacefiles From 7ff565a4bd0be715f0de239611a87d515f6b3b82 Mon Sep 17 00:00:00 2001 From: Andreas Eknes Lie Date: Tue, 30 Apr 2024 13:26:10 +0200 Subject: [PATCH 08/10] Formatting with black --- webviz_subsurface/_components/color_picker.py | 4 +- webviz_subsurface/_figures/px_figure.py | 56 ++++++++++------- .../controllers/tornado_controllers.py | 60 +++++++++++-------- .../utils/table_and_figure_utils.py | 2 +- .../plugins/_well_cross_section_fmu.py | 8 ++- 5 files changed, 76 insertions(+), 54 deletions(-) diff --git a/webviz_subsurface/_components/color_picker.py b/webviz_subsurface/_components/color_picker.py index 13bedd29a..acd632898 100644 --- a/webviz_subsurface/_components/color_picker.py +++ b/webviz_subsurface/_components/color_picker.py @@ -73,7 +73,9 @@ def get_color(self, color_list: List, filter_query: Dict[str, str]) -> str: warnings.warn("No color found for filter!") return "#ffffff" if len(df["COLOR"].unique()) > 1: - warnings.warn(f"Multiple colors found for filter, using first color: {color_list[df.index[0]]}") + warnings.warn( + f"Multiple colors found for filter, using first color: {color_list[df.index[0]]}" + ) return color_list[df.index[0]] @property diff --git a/webviz_subsurface/_figures/px_figure.py b/webviz_subsurface/_figures/px_figure.py index 6cca98640..80fe4fa89 100644 --- a/webviz_subsurface/_figures/px_figure.py +++ b/webviz_subsurface/_figures/px_figure.py @@ -79,16 +79,20 @@ def update_xaxes(figure: go.Figure, plot_type: str, **kwargs: Any) -> go.Figure: linewidth=2, linecolor="black", mirror=True, - title=None - if facet_col is not None or not isinstance(kwargs.get("x"), str) - else kwargs.get("x"), - showticklabels=(data_frame[facet_col].nunique() <= 100) - if facet_col is not None - else None, + title=( + None + if facet_col is not None or not isinstance(kwargs.get("x"), str) + else kwargs.get("x") + ), + showticklabels=( + (data_frame[facet_col].nunique() <= 100) if facet_col is not None else None + ), tickangle=0, - tickfont_size=max((20 - (0.4 * data_frame[facet_col].nunique())), 10) - if facet_col is not None - else None, + tickfont_size=( + max((20 - (0.4 * data_frame[facet_col].nunique())), 10) + if facet_col is not None + else None + ), fixedrange=plot_type == "distribution", ).update_xaxes(**kwargs.get("xaxis", {})) @@ -118,19 +122,23 @@ def update_traces(figure: go.Figure, **kwargs: Any) -> go.Figure: facet_col = kwargs.get("facet_col") return ( figure.update_traces( - marker_size=max((20 - (1.5 * data_frame[facet_col].nunique())), 5) - if facet_col is not None - else 20, + marker_size=( + max((20 - (1.5 * data_frame[facet_col].nunique())), 5) + if facet_col is not None + else 20 + ), selector=lambda t: t["type"] in ["scatter", "scattergl"], ) .update_traces(textposition="inside", selector=dict(type="pie")) .for_each_trace(lambda t: set_marker_color(t)) .for_each_trace( - lambda t: t.update( - xbins_size=(t["x"].max() - t["x"].min()) / kwargs.get("nbins", 15) - ) - if is_numeric_dtype(t["x"]) - else None, + lambda t: ( + t.update( + xbins_size=(t["x"].max() - t["x"].min()) / kwargs.get("nbins", 15) + ) + if is_numeric_dtype(t["x"]) + else None + ), selector=dict(type="histogram"), ) ) @@ -158,12 +166,14 @@ def for_each_annotation(figure: go.Figure, **kwargs: Any) -> go.Figure: return figure.for_each_annotation( lambda a: a.update( text=(a.text.split("=")[-1]), - visible=data_frame[facet_col].nunique() <= 42 - if facet_col is not None - else None, - font_size=max((18 - (0.4 * data_frame[facet_col].nunique())), 10) - if facet_col is not None - else None, + visible=( + data_frame[facet_col].nunique() <= 42 if facet_col is not None else None + ), + font_size=( + max((18 - (0.4 * data_frame[facet_col].nunique())), 10) + if facet_col is not None + else None + ), ) ) diff --git a/webviz_subsurface/plugins/_volumetric_analysis/controllers/tornado_controllers.py b/webviz_subsurface/plugins/_volumetric_analysis/controllers/tornado_controllers.py index 4d48939d6..3fa1660cf 100644 --- a/webviz_subsurface/plugins/_volumetric_analysis/controllers/tornado_controllers.py +++ b/webviz_subsurface/plugins/_volumetric_analysis/controllers/tornado_controllers.py @@ -135,27 +135,31 @@ def _update_tornado_pages( ) elif realplot and selections["bottom_viz"] == "realplot" and figures: bottom_display = [ - wcc.Graph( - config={"displayModeBar": False}, - style={"height": "40vh"}, - figure=realplot, + ( + wcc.Graph( + config={"displayModeBar": False}, + style={"height": "40vh"}, + figure=realplot, + ) + if not subplots + else "Realization plot not available when `Subplots` is active" ) - if not subplots - else "Realization plot not available when `Subplots` is active" ] return update_relevant_components( id_list=id_list, update_info=[ { - "new_value": tornado_plots_layout( - figures=figures, bottom_display=bottom_display - ) - if figures - else tornado_error_layout( - "No data left after filtering" - if dframe.empty - else f"Reference sensitivity '{selections['Reference']}' not in input data" + "new_value": ( + tornado_plots_layout( + figures=figures, bottom_display=bottom_display + ) + if figures + else tornado_error_layout( + "No data left after filtering" + if dframe.empty + else f"Reference sensitivity '{selections['Reference']}' not in input data" + ) ), "conditions": {"page": page_selected}, } @@ -210,9 +214,9 @@ def _update_tornado_selections( ] settings["Response"] = { "options": [{"label": i, "value": i} for i in volume_options], - "value": volume_options[0] - if initial_page_load - else selections["Response"], + "value": ( + volume_options[0] if initial_page_load else selections["Response"] + ), "disabled": len(volume_options) == 1, } else: @@ -289,10 +293,12 @@ def tornado_figure_and_table( ).figure figure.update_xaxes(side="bottom", title=None).update_layout( - title_text=f"Tornadoplot for {response}
" - + f"Fluid zone: {(' + ').join(selections['filters']['FLUID_ZONE'])}" - if group is None - else f"{response} {group}", + title_text=( + f"Tornadoplot for {response}
" + + f"Fluid zone: {(' + ').join(selections['filters']['FLUID_ZONE'])}" + if group is None + else f"{response} {group}" + ), title_font_size=font_size, margin={"t": 70}, ) @@ -329,12 +335,14 @@ def create_realplot(df: pd.DataFrame, sensitivity_colors: dict) -> go.Figure: .update_layout(legend_title_text="") .for_each_trace( lambda t: ( - t.update(marker_line_color="black") - if t["customdata"][0][0] == "high" - else t.update(marker_line_color="white", marker_line_width=2) + ( + t.update(marker_line_color="black") + if t["customdata"][0][0] == "high" + else t.update(marker_line_color="white", marker_line_width=2) + ) + if t["customdata"][0][0] != "mc" + else None ) - if t["customdata"][0][0] != "mc" - else None ) ) diff --git a/webviz_subsurface/plugins/_volumetric_analysis/utils/table_and_figure_utils.py b/webviz_subsurface/plugins/_volumetric_analysis/utils/table_and_figure_utils.py index 032c13bb3..0d6555bd8 100644 --- a/webviz_subsurface/plugins/_volumetric_analysis/utils/table_and_figure_utils.py +++ b/webviz_subsurface/plugins/_volumetric_analysis/utils/table_and_figure_utils.py @@ -148,7 +148,7 @@ def create_figure_matrix(figures: List[go.Figure]) -> List[List[go.Figure]]: len_of_matrix = figs_in_row * math.ceil(len(figures) / figs_in_row) # extend figure list with None to fit size of matrix figures.extend([None] * (len_of_matrix - len(figures))) - return [figures[i: i + figs_in_row] for i in range(0, len_of_matrix, figs_in_row)] + return [figures[i : i + figs_in_row] for i in range(0, len_of_matrix, figs_in_row)] def update_tornado_figures_xaxis(figures: List[go.Figure]) -> None: diff --git a/webviz_subsurface/plugins/_well_cross_section_fmu.py b/webviz_subsurface/plugins/_well_cross_section_fmu.py index 798fa668f..040b6da88 100644 --- a/webviz_subsurface/plugins/_well_cross_section_fmu.py +++ b/webviz_subsurface/plugins/_well_cross_section_fmu.py @@ -243,9 +243,11 @@ def ensemble_layout(self) -> html.Div: def seismic_layout(self) -> html.Div: if self.segyfiles: return html.Div( - style=self.set_style(marginTop="20px") - if self.segyfiles - else {"display": "none"}, + style=( + self.set_style(marginTop="20px") + if self.segyfiles + else {"display": "none"} + ), children=html.Label( children=[ html.Span("Seismic:", style={"font-weight": "bold"}), From cba35c2e44612889bc18cb1ac0f85986b6833fec Mon Sep 17 00:00:00 2001 From: Andreas Eknes Lie Date: Tue, 30 Apr 2024 13:43:42 +0200 Subject: [PATCH 09/10] Lower pylint standards slightly --- .pylintrc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.pylintrc b/.pylintrc index 8ee316a63..3b742540e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -3,6 +3,9 @@ # As a temporary workaround for https://github.com/PyCQA/pylint/issues/4577 init-hook = "import astroid; astroid.context.InferenceContext.max_inferred = 500" +# Specify a score threshold to be exceeded before program exits with error. +fail-under=9.9 + [MESSAGES CONTROL] disable = bad-continuation, missing-docstring, duplicate-code, logging-fstring-interpolation, unspecified-encoding @@ -27,6 +30,16 @@ good-names = i, df, _ + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. From a03bf6fa9953a540b7ecb514e6abe67cf9664eb0 Mon Sep 17 00:00:00 2001 From: Andreas Eknes Lie Date: Thu, 2 May 2024 09:20:35 +0200 Subject: [PATCH 10/10] Use bash exit codes to determine pylint status --- .github/workflows/subsurface.yml | 13 ++++++++++++- .pylintrc | 12 ------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.github/workflows/subsurface.yml b/.github/workflows/subsurface.yml index 711da80e3..2c4af67d4 100644 --- a/.github/workflows/subsurface.yml +++ b/.github/workflows/subsurface.yml @@ -72,7 +72,18 @@ jobs: if: github.event_name != 'release' run: | black --check webviz_subsurface tests setup.py - pylint webviz_subsurface tests setup.py + + pylint_exit_code=0 + pylint webviz_subsurface tests setup.py || pylint_exit_code=$? + + error_bitmask=$((1 | 2 | 4)) # Fatal, Error, Warning + result=$(($pylint_exit_code & $error_bitmask)) + + if [[ $result != 0 ]]; then + echo "Error: Pylint returned exit code $pylint_exit_code" + exit $pylint_exit_code + fi + bandit -r -c ./bandit.yml webviz_subsurface tests setup.py isort --check-only webviz_subsurface tests setup.py mypy --package webviz_subsurface diff --git a/.pylintrc b/.pylintrc index 3b742540e..33e394749 100644 --- a/.pylintrc +++ b/.pylintrc @@ -3,9 +3,6 @@ # As a temporary workaround for https://github.com/PyCQA/pylint/issues/4577 init-hook = "import astroid; astroid.context.InferenceContext.max_inferred = 500" -# Specify a score threshold to be exceeded before program exits with error. -fail-under=9.9 - [MESSAGES CONTROL] disable = bad-continuation, missing-docstring, duplicate-code, logging-fstring-interpolation, unspecified-encoding @@ -31,15 +28,6 @@ good-names = i, _ -[REPORTS] - -# Python expression which should return a score less than or equal to 10. You -# have access to the variables 'error', 'warning', 'refactor', and 'convention' -# which contain the number of messages in each category, as well as 'statement' -# which is the total number of statements analyzed. This score is used by the -# global evaluation report (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma.