Skip to content

Commit

Permalink
added error reporting for invalid layers
Browse files Browse the repository at this point in the history
  • Loading branch information
alexhroom committed Nov 8, 2024
1 parent 48d965a commit d241cd9
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 7 deletions.
42 changes: 39 additions & 3 deletions rascal2/widgets/project/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,45 @@ def show_edit_view(self) -> None:

def save_changes(self) -> None:
"""Save changes to the project."""
self.parent.presenter.edit_project(self.draft_project)
self.update_project_view()
self.show_project_view()
try:
self.validate_draft_project()
except ValueError as err:
self.parent.terminal_widget.write_error(f"Could not save draft project:\n {err}")
else:
self.parent.presenter.edit_project(self.draft_project)
self.update_project_view()
self.show_project_view()

def validate_draft_project(self):
"""Check that the draft project is valid."""
errors = []
if self.draft_project["model"] == LayerModels.StandardLayers and self.draft_project["layers"]:
layer_attrs = list(self.draft_project["layers"][0].model_fields)
layer_attrs.remove("name")
layer_attrs.remove("hydrate_with")
# ensure all layer parameters have been filled in, and all names are layers that exist
for i, layer in enumerate(self.draft_project["layers"]):
missing_params = [p for p in layer_attrs if getattr(layer, p) == ""]
invalid_params = [
(p, v)
for p in layer_attrs
if (v := getattr(layer, p)) not in [p.name for p in self.draft_project["parameters"]]
and p not in missing_params
]
if missing_params:
noun = "a parameter" if len(missing_params) == 1 else "parameters"
msg = f"Layer '{layer.name}' (row {i+1}) is missing {noun}: {', '.join(missing_params)}"
errors.append(msg)
if invalid_params:
noun = "an invalid value" if len(invalid_params) == 1 else "invalid values"
msg = (
f"Layer '{layer.name}' (row {i+1}) has {noun}: "
f"{', '.join(f'"{v}" for parameter {p}' for p, v in invalid_params)}"
)
errors.append(msg)

if errors:
raise ValueError("\n ".join(errors))

def cancel_changes(self) -> None:
"""Cancel changes to the project."""
Expand Down
11 changes: 11 additions & 0 deletions rascal2/widgets/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ def write_html(self, text: str):
"""
self.text_area.appendHtml(text.rstrip())

def write_error(self, text: str):
"""Append error text to the terminal and alert the user.
Parameters
----------
text : str
The text to append.
"""
self.write_html(f'<div style="color: crimson;white-space: pre-line;"><b>{text}</b></div>')

def clear(self):
"""Clear the text in the terminal."""
self.text_area.setPlainText("")
Expand Down
68 changes: 64 additions & 4 deletions tests/widgets/project/test_project.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from unittest.mock import MagicMock

import pydantic
Expand All @@ -12,10 +13,7 @@
ParametersModel,
ProjectFieldWidget,
)
from rascal2.widgets.project.project import (
ProjectTabWidget,
ProjectWidget,
)
from rascal2.widgets.project.project import ProjectTabWidget, ProjectWidget, create_draft_project


class MockModel(QtCore.QObject):
Expand Down Expand Up @@ -259,3 +257,65 @@ def test_project_tab_update_model(classlist, param_classlist, edit_mode):
for field in new_model:
assert tab.tables[field].model.classlist == new_model[field]
assert tab.tables[field].model.edit_mode == edit_mode


@pytest.mark.parametrize(
"input_params",
[
([0, 1, 1, 2, 1], [0, 0, 3, 0, 1]),
([0, 0, 0, 1, 0], [0, 0, 1, 1, 0]),
([3, 3, 3, 2, 0], [0, 0, 3, 0, 1]),
],
)
@pytest.mark.parametrize("absorption", [True, False])
def test_project_tab_validate_layers(input_params, absorption):
"""Test that the project tab produces the correct result for validating the layers tab."""
params = ["Param 1", "Param 2", "Invalid Param", ""]
if absorption:
attrs = ["thickness", "SLD_real", "SLD_imaginary", "roughness", "hydration"]
layer_class = RATapi.models.AbsorptionLayer
else:
attrs = ["thickness", "SLD", "roughness", "hydration"]
layer_class = RATapi.models.Layer
layers = RATapi.ClassList(
[
layer_class(**{attr: params[input_params[0][i]] for i, attr in enumerate(attrs)}),
layer_class(**{attr: params[input_params[1][i]] for i, attr in enumerate(attrs)}),
]
)

expected_err = []
for i, layer in enumerate(layers):
missing_params = [p for j, p in enumerate(attrs) if input_params[i][j] == 3]
invalid_params = [p for j, p in enumerate(attrs) if input_params[i][j] == 2]

if missing_params:
noun = "a parameter" if len(missing_params) == 1 else "parameters"
msg = f"Layer '{layer.name}' (row {i+1}) is missing {noun}: {', '.join(missing_params)}"
expected_err.append(msg)
if invalid_params:
noun = "an invalid value" if len(invalid_params) == 1 else "invalid values"
msg = (
f"Layer '{layer.name}' (row {i+1}) has {noun}: "
f"{', '.join(f'"Invalid Param" for parameter {p}' for p in invalid_params)}"
)
expected_err.append(msg)

draft = create_draft_project(RATapi.Project())
draft["layers"] = layers
draft["parameters"] = RATapi.ClassList(
[
RATapi.models.Parameter(name="Param 1"),
RATapi.models.Parameter(name="Param 2"),
]
)

project = ProjectWidget(parent)
project.draft_project = draft

if not expected_err:
project.validate_draft_project()
else:
with pytest.raises(ValueError, match=re.escape("\n ".join(expected_err))):
project.validate_draft_project()

0 comments on commit d241cd9

Please sign in to comment.