Skip to content

Commit d6a684d

Browse files
committed
Merge branch 'dev' into kzscisoft/sender-refactor
2 parents 1ae29f8 + 1add445 commit d6a684d

File tree

10 files changed

+133
-23
lines changed

10 files changed

+133
-23
lines changed

simvue/api/objects/alert/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def __init__(self, identifier: str | None = None, **kwargs) -> None:
3131
"""Retrieve an alert from the Simvue server by identifier"""
3232
self._label = "alert"
3333
super().__init__(identifier=identifier, **kwargs)
34+
self._local_only_args = ["frequency", "pattern", "aggregation"]
3435

3536
def compare(self, other: "AlertBase") -> bool:
3637
"""Compare this alert to another"""

simvue/api/objects/alert/metrics.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ def __init__(self, identifier: str | None = None, **kwargs) -> None:
3030
"""Connect to a local or remote threshold alert by identifier"""
3131
self.alert = MetricThresholdAlertDefinition(self)
3232
super().__init__(identifier, **kwargs)
33+
self._local_only_args += [
34+
"rule",
35+
"window",
36+
"metric",
37+
"threshold",
38+
]
3339

3440
@classmethod
3541
def get(
@@ -117,6 +123,13 @@ def __init__(self, identifier: str | None = None, **kwargs) -> None:
117123
"""Connect to a local or remote threshold alert by identifier"""
118124
self.alert = MetricRangeAlertDefinition(self)
119125
super().__init__(identifier, **kwargs)
126+
self._local_only_args += [
127+
"rule",
128+
"window",
129+
"metric",
130+
"range_low",
131+
"range_high",
132+
]
120133

121134
def compare(self, other: "MetricsRangeAlert") -> bool:
122135
"""Compare two MetricRangeAlerts"""

simvue/api/objects/artifact/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def __init__(
5454
self._label = "artifact"
5555
self._endpoint = f"{self._label}s"
5656
super().__init__(identifier=identifier, _read_only=_read_only, **kwargs)
57+
self._local_only_args += ["storage", "file_path", "runs"]
5758

5859
# If the artifact is an online instance, need a place to store the response
5960
# from the initial creation

simvue/api/objects/artifact/file.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ def new(
8080
else:
8181
file_path = pathlib.Path(file_path)
8282
if snapshot:
83-
_user_config = SimvueConfiguration.fetch()
83+
_user_config = SimvueConfiguration.fetch(
84+
mode="offline" if offline else "online"
85+
)
8486

8587
_local_staging_dir: pathlib.Path = _user_config.offline.cache.joinpath(
8688
"artifacts"

simvue/api/objects/base.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,12 @@ def __init__(
196196
self._read_only: bool = _read_only
197197
self._is_set: bool = False
198198
self._endpoint: str = getattr(self, "_endpoint", f"{self._label}s")
199+
200+
# For simvue object initialisation, unlike the server there is no nested
201+
# arguments, however this means that there are extra keys during post which
202+
# need removing, this attribute handles that and should be set in subclasses.
203+
self._local_only_args: list[str] = []
204+
199205
self._identifier: str | None = (
200206
identifier if identifier is not None else f"offline_{uuid.uuid1()}"
201207
)
@@ -626,6 +632,10 @@ def _post_single(
626632
if not is_json:
627633
kwargs = msgpack.packb(data or kwargs, use_bin_type=True)
628634

635+
# Remove any extra keys
636+
for key in self._local_only_args:
637+
_ = (data or kwargs).pop(key, None)
638+
629639
_response = sv_post(
630640
url=f"{self._base_url}",
631641
headers=self._headers | {"Content-Type": "application/msgpack"},
@@ -663,6 +673,11 @@ def _post_single(
663673
def _put(self, **kwargs) -> dict[str, typing.Any]:
664674
if not self.url:
665675
raise RuntimeError(f"Identifier for instance of {self._label} Unknown")
676+
677+
# Remove any extra keys
678+
for key in self._local_only_args:
679+
_ = kwargs.pop(key, None)
680+
666681
_response = sv_put(
667682
url=f"{self.url}", headers=self._headers, data=kwargs, is_json=True
668683
)

simvue/api/objects/run.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -696,19 +696,9 @@ def on_reconnect(self, id_mapping: dict[str, str]) -> None:
696696
id_mapping: dict[str, str]
697697
A mapping from offline identifier to online identifier.
698698
"""
699-
online_alert_ids: list[str] = []
700-
for id in self._staging.get("alerts", []):
701-
try:
702-
online_alert_ids.append(id_mapping[id])
703-
except KeyError:
704-
raise KeyError(
705-
"Could not find alert ID in offline to online ID mapping."
706-
)
707-
# If run is offline, no alerts have been added yet, so add all alerts:
708-
if self._identifier is not None and self._identifier.startswith("offline"):
709-
self._staging["alerts"] = online_alert_ids
710-
# Otherwise, only add alerts which have not yet been added
711-
else:
712-
self._staging["alerts"] = [
713-
id for id in online_alert_ids if id not in list(self.alerts)
714-
]
699+
online_alert_ids: list[str] = list(
700+
set(id_mapping.get(_id) for _id in self._staging.get("alerts", []))
701+
)
702+
if not all(online_alert_ids):
703+
raise KeyError("Could not find alert ID in offline to online ID mapping.")
704+
self._staging["alerts"] = online_alert_ids

simvue/config/user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,9 @@ def check_valid_server(cls, values: "SimvueConfiguration") -> "SimvueConfigurati
151151
@sv_util.prettify_pydantic
152152
def fetch(
153153
cls,
154+
mode: typing.Literal["offline", "online", "disabled"],
154155
server_url: str | None = None,
155156
server_token: str | None = None,
156-
mode: typing.Literal["offline", "online", "disabled"] | None = None,
157157
) -> "SimvueConfiguration":
158158
"""Retrieve the Simvue configuration from this project
159159

simvue/run.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,6 @@ def init(
761761
self._sv_obj.alerts = []
762762
self._sv_obj.created = time.time()
763763
self._sv_obj.notifications = notification
764-
self._sv_obj._staging["folder_id"] = self._folder.id
765764

766765
if self._status == "running":
767766
self._sv_obj.system = get_system()

tests/functional/test_config.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,16 @@ def _mocked_find(file_names: list[str], *_, ppt_file=_ppt_file, conf_file=_confi
101101

102102
if not use_file and not use_env and not use_args:
103103
with pytest.raises(RuntimeError):
104-
simvue.config.user.SimvueConfiguration.fetch()
104+
simvue.config.user.SimvueConfiguration.fetch(mode="online")
105105
return
106106
elif use_args:
107107
_config = simvue.config.user.SimvueConfiguration.fetch(
108108
server_url=_arg_url,
109-
server_token=_arg_token
109+
server_token=_arg_token,
110+
mode="online"
110111
)
111112
else:
112-
_config = simvue.config.user.SimvueConfiguration.fetch()
113+
_config = simvue.config.user.SimvueConfiguration.fetch(mode="online")
113114

114115
if use_file and use_file != "pyproject.toml":
115116
assert _config.config_file() == _config_file

tests/functional/test_run_class.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1167,6 +1167,94 @@ def test_add_alerts() -> None:
11671167
)
11681168
for _id in _expected_alerts:
11691169
client.delete_alert(_id)
1170+
1171+
@pytest.mark.run
1172+
@pytest.mark.offline
1173+
def test_add_alerts_offline(monkeypatch) -> None:
1174+
_uuid = f"{uuid.uuid4()}".split("-")[0]
1175+
1176+
temp_d = tempfile.TemporaryDirectory()
1177+
monkeypatch.setenv("SIMVUE_OFFLINE_DIRECTORY", temp_d.name)
1178+
1179+
run = sv_run.Run(mode="offline")
1180+
run.init(
1181+
name="test_add_alerts_offline",
1182+
folder=f"/simvue_unit_testing/{_uuid}",
1183+
retention_period=os.environ.get("SIMVUE_TESTING_RETENTION_PERIOD", "2 mins"),
1184+
tags=[platform.system(), "test_add_alerts"],
1185+
visibility="tenant" if os.environ.get("CI") else None,
1186+
)
1187+
1188+
_expected_alerts = []
1189+
1190+
# Create alerts, have them attach to run automatically
1191+
_id = run.create_event_alert(
1192+
name=f"event_alert_{_uuid}",
1193+
pattern="test",
1194+
)
1195+
_expected_alerts.append(_id)
1196+
1197+
# Create another alert and attach to run
1198+
_id = run.create_metric_range_alert(
1199+
name=f"metric_range_alert_{_uuid}",
1200+
metric="test",
1201+
range_low=10,
1202+
range_high=100,
1203+
rule="is inside range",
1204+
)
1205+
_expected_alerts.append(_id)
1206+
1207+
# Create another alert, do not attach to run
1208+
_id = run.create_metric_threshold_alert(
1209+
name=f"metric_threshold_alert_{_uuid}",
1210+
metric="test",
1211+
threshold=10,
1212+
rule="is above",
1213+
attach_to_run=False,
1214+
)
1215+
1216+
# Try redefining existing alert again
1217+
_id = run.create_metric_range_alert(
1218+
name=f"metric_range_alert_{_uuid}",
1219+
metric="test",
1220+
range_low=10,
1221+
range_high=100,
1222+
rule="is inside range",
1223+
)
1224+
1225+
_id_mapping = sv_send.sender(os.environ["SIMVUE_OFFLINE_DIRECTORY"], 2, 10, throw_exceptions=True)
1226+
_online_run = RunObject(identifier=_id_mapping.get(run.id))
1227+
1228+
# Check that there is no duplication
1229+
assert sorted(_online_run.alerts) == sorted([_id_mapping.get(_id) for _id in _expected_alerts])
1230+
1231+
# Create another run without adding to run
1232+
_id = run.create_user_alert(name=f"user_alert_{_uuid}", attach_to_run=False)
1233+
_id_mapping = sv_send.sender(os.environ["SIMVUE_OFFLINE_DIRECTORY"], 2, 10, throw_exceptions=True)
1234+
1235+
# Check alert is not added
1236+
_online_run.refresh()
1237+
assert sorted(_online_run.alerts) == sorted([_id_mapping.get(_id) for _id in _expected_alerts])
1238+
1239+
# Try adding alerts with IDs, check there is no duplication
1240+
_expected_alerts.append(_id)
1241+
run.add_alerts(ids=_expected_alerts)
1242+
_id_mapping = sv_send.sender(os.environ["SIMVUE_OFFLINE_DIRECTORY"], 2, 10, throw_exceptions=True)
1243+
1244+
_online_run.refresh()
1245+
assert sorted(_online_run.alerts) == sorted([_id_mapping.get(_id) for _id in _expected_alerts])
1246+
1247+
run.close()
1248+
1249+
client = sv_cl.Client()
1250+
with contextlib.suppress(ObjectNotFoundError):
1251+
client.delete_folder(
1252+
f"/simvue_unit_testing/{_uuid}",
1253+
remove_runs=True,
1254+
recursive=True
1255+
)
1256+
for _id in [_id_mapping.get(_id) for _id in _expected_alerts]:
1257+
client.delete_alert(_id)
11701258

11711259

11721260
@pytest.mark.run
@@ -1457,7 +1545,7 @@ def test_run_environment_metadata(environment: str, mocker: pytest_mock.MockerFi
14571545
_target_dir = _data_dir
14581546
if "python" in environment:
14591547
_target_dir = _data_dir.joinpath(environment)
1460-
_config = SimvueConfiguration.fetch()
1548+
_config = SimvueConfiguration.fetch(mode="online")
14611549

14621550
with sv_run.Run(server_token=_config.server.token, server_url=_config.server.url) as run:
14631551
_uuid = f"{uuid.uuid4()}".split("-")[0]

0 commit comments

Comments
 (0)