Skip to content

Commit 731b900

Browse files
authored
Merge pull request #368 from simvue-io/hotfix/add-set-tags-method
Moved `update_tags` to `set_tags` and created new update_tags method
2 parents 156f14b + b2cdebc commit 731b900

File tree

6 files changed

+156
-29
lines changed

6 files changed

+156
-29
lines changed

simvue/factory/proxy/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ def _error(self, message: str) -> None:
2828
self._logger.error(message)
2929
self._aborted = True
3030

31+
@abc.abstractmethod
32+
def list_tags(self) -> typing.Optional[list[str]]:
33+
pass
34+
3135
@abc.abstractmethod
3236
def create_run(
3337
self, data: dict[str, typing.Any]

simvue/factory/proxy/offline.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ def set_alert_state(
169169

170170
return _alert_data
171171

172+
@skip_if_failed("_aborted", "_suppress_errors", [])
173+
def list_tags(self) -> list[dict[str, typing.Any]]:
174+
raise NotImplementedError(
175+
"Retrieval of current tags is not implemented for offline running"
176+
)
177+
172178
@skip_if_failed("_aborted", "_suppress_errors", [])
173179
def list_alerts(self) -> list[dict[str, typing.Any]]:
174180
return [

simvue/factory/proxy/remote.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,34 @@ def __init__(
3535

3636
self._id = uniq_id
3737

38+
@skip_if_failed("_aborted", "_suppress_errors", None)
39+
def list_tags(self) -> list[str]:
40+
logger.debug("Retrieving existing tags")
41+
try:
42+
response = get(f"{self._url}/api/runs/{self._id}", self._headers)
43+
except Exception as err:
44+
self._error(f"Exception retrieving tags: {str(err)}")
45+
return []
46+
47+
logger.debug(
48+
'Got status code %d when retrieving tags: "%s"',
49+
response.status_code,
50+
response.text,
51+
)
52+
53+
if not (response_data := response.json()) or (
54+
(data := response_data.get("tags")) is None
55+
):
56+
self._error(
57+
"Expected key 'tags' in response from server during alert retrieval"
58+
)
59+
return []
60+
61+
if response.status_code == 200:
62+
return data
63+
64+
return []
65+
3866
@skip_if_failed("_aborted", "_suppress_errors", (None, None))
3967
def create_run(self, data) -> tuple[typing.Optional[str], typing.Optional[int]]:
4068
"""
@@ -342,8 +370,9 @@ def list_alerts(self) -> list[dict[str, typing.Any]]:
342370
if not (response_data := response.json()) or (
343371
(data := response_data.get("data")) is None
344372
):
373+
print(response_data)
345374
self._error(
346-
"Expected key 'data' in response from server during alert retrieval"
375+
"Expected key 'alerts' in response from server during alert retrieval"
347376
)
348377
return []
349378

simvue/run.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -880,8 +880,8 @@ def update_metadata(self, metadata: dict[str, typing.Any]) -> bool:
880880
@skip_if_failed("_aborted", "_suppress_errors", False)
881881
@check_run_initialised
882882
@pydantic.validate_call
883-
def update_tags(self, tags: list[str]) -> bool:
884-
"""Update tags for this run
883+
def set_tags(self, tags: list[str]) -> bool:
884+
"""Set tags for this run
885885
886886
Parameters
887887
----------
@@ -907,6 +907,37 @@ def update_tags(self, tags: list[str]) -> bool:
907907

908908
return False
909909

910+
@skip_if_failed("_aborted", "_suppress_errors", False)
911+
@check_run_initialised
912+
@pydantic.validate_call
913+
def update_tags(self, tags: list[str]) -> bool:
914+
"""Add additional tags to this run without duplication
915+
916+
Parameters
917+
----------
918+
tags : list[str]
919+
new set of tags to attach
920+
921+
Returns
922+
-------
923+
bool
924+
whether the update was successful
925+
"""
926+
if self._mode == "disabled":
927+
return True
928+
929+
if not self._simvue:
930+
return False
931+
current_tags: list[str] = self._simvue.list_tags() or []
932+
933+
try:
934+
self.set_tags(list(set(current_tags + tags)))
935+
except Exception as err:
936+
self._error(f"Failed to update tags: {err}")
937+
return False
938+
939+
return True
940+
910941
@skip_if_failed("_aborted", "_suppress_errors", False)
911942
@check_run_initialised
912943
@pydantic.validate_call

tests/refactor/test_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def test_multiple_metric_retrieval(
225225
metric_names=list(create_test_run[1]["metrics"]),
226226
xaxis=xaxis,
227227
aggregate=aggregate,
228-
output_format=format,
228+
output_format=output_format,
229229
)
230230
return
231231

@@ -234,5 +234,5 @@ def test_multiple_metric_retrieval(
234234
metric_names=list(create_test_run[1]["metrics"]),
235235
xaxis=xaxis,
236236
aggregate=aggregate,
237-
output_format=format,
237+
output_format=output_format,
238238
)

tests/refactor/test_run_class.py

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,18 @@ def test_check_run_initialised_decorator() -> None:
2727
getattr(run, method_name)()
2828
assert "Simvue Run must be initialised" in str(e.value)
2929

30+
3031
@pytest.mark.run
3132
@pytest.mark.parametrize("overload_buffer", (True, False), ids=("overload", "normal"))
32-
@pytest.mark.parametrize("visibility", ("bad_option", "tenant", "public", ["ciuser01"], None))
33+
@pytest.mark.parametrize(
34+
"visibility", ("bad_option", "tenant", "public", ["ciuser01"], None)
35+
)
3336
def test_log_metrics(
3437
overload_buffer: bool,
3538
setup_logging: "CountingLogHandler",
3639
mocker,
3740
request: pytest.FixtureRequest,
38-
visibility: typing.Union[typing.Literal["public", "tenant"], list[str], None]
41+
visibility: typing.Union[typing.Literal["public", "tenant"], list[str], None],
3942
) -> None:
4043
METRICS = {"a": 10, "b": 1.2}
4144

@@ -50,17 +53,23 @@ def test_log_metrics(
5053
with pytest.raises(RuntimeError):
5154
run.init(
5255
name=f"test_run_{str(uuid.uuid4()).split('-', 1)[0]}",
53-
tags=["simvue_client_unit_tests", request.node.name.replace("[", "_").replace("]", "_")],
56+
tags=[
57+
"simvue_client_unit_tests",
58+
request.node.name.replace("[", "_").replace("]", "_"),
59+
],
5460
folder="/simvue_unit_testing",
5561
retention_period="1 hour",
5662
visibility=visibility,
57-
resources_metrics_interval=1
63+
resources_metrics_interval=1,
5864
)
5965
return
6066

6167
run.init(
6268
name=f"test_run_{str(uuid.uuid4()).split('-', 1)[0]}",
63-
tags=["simvue_client_unit_tests", request.node.name.replace("[", "_").replace("]", "_")],
69+
tags=[
70+
"simvue_client_unit_tests",
71+
request.node.name.replace("[", "_").replace("]", "_"),
72+
],
6473
folder="/simvue_unit_testing",
6574
visibility=visibility,
6675
resources_metrics_interval=1,
@@ -146,7 +155,9 @@ def test_update_metadata_offline(
146155

147156
@pytest.mark.run
148157
@pytest.mark.parametrize("multi_threaded", (True, False), ids=("multi", "single"))
149-
def test_runs_multiple_parallel(multi_threaded: bool, request: pytest.FixtureRequest) -> None:
158+
def test_runs_multiple_parallel(
159+
multi_threaded: bool, request: pytest.FixtureRequest
160+
) -> None:
150161
N_RUNS: int = 2
151162
if multi_threaded:
152163

@@ -155,7 +166,10 @@ def thread_func(index: int) -> tuple[int, list[dict[str, typing.Any]], str]:
155166
run.config(suppress_errors=False)
156167
run.init(
157168
name=f"test_runs_multiple_{index + 1}",
158-
tags=["simvue_client_unit_tests", request.node.name.replace("[", "_").replace("]", "_")],
169+
tags=[
170+
"simvue_client_unit_tests",
171+
request.node.name.replace("[", "_").replace("]", "_"),
172+
],
159173
folder="/simvue_unit_testing",
160174
retention_period="1 hour",
161175
)
@@ -192,7 +206,10 @@ def thread_func(index: int) -> tuple[int, list[dict[str, typing.Any]], str]:
192206
run_1.config(suppress_errors=False)
193207
run_1.init(
194208
name="test_runs_multiple_unthreaded_1",
195-
tags=["simvue_client_unit_tests", request.node.name.replace("[", "_").replace("]", "_")],
209+
tags=[
210+
"simvue_client_unit_tests",
211+
request.node.name.replace("[", "_").replace("]", "_"),
212+
],
196213
folder="/simvue_unit_testing",
197214
retention_period="1 hour",
198215
)
@@ -246,7 +263,10 @@ def test_runs_multiple_series(request: pytest.FixtureRequest) -> None:
246263
run.config(suppress_errors=False)
247264
run.init(
248265
name=f"test_runs_multiple_series_{index}",
249-
tags=["simvue_client_unit_tests", request.node.name.replace("[", "_").replace("]", "_")],
266+
tags=[
267+
"simvue_client_unit_tests",
268+
request.node.name.replace("[", "_").replace("]", "_"),
269+
],
250270
folder="/simvue_unit_testing",
251271
retention_period="1 hour",
252272
)
@@ -296,8 +316,11 @@ def test_suppressed_errors(
296316
run.init(
297317
name="test_suppressed_errors",
298318
folder="/simvue_unit_testing",
299-
tags=["simvue_client_unit_tests", request.node.name.replace("[", "_").replace("]", "_")],
300-
retention_period="1 hour"
319+
tags=[
320+
"simvue_client_unit_tests",
321+
request.node.name.replace("[", "_").replace("]", "_"),
322+
],
323+
retention_period="1 hour",
301324
)
302325

303326
run.config(suppress_errors=True)
@@ -321,9 +344,12 @@ def test_bad_run_arguments() -> None:
321344

322345
def test_set_folder_details(request: pytest.FixtureRequest) -> None:
323346
with sv_run.Run() as run:
324-
folder_name: str ="/simvue_unit_tests"
347+
folder_name: str = "/simvue_unit_tests"
325348
description: str = "test description"
326-
tags: list[str] = ["simvue_client_unit_tests", request.node.name.replace("[", "_").replace("]", "_")]
349+
tags: list[str] = [
350+
"simvue_client_unit_tests",
351+
request.node.name.replace("[", "_").replace("]", "_"),
352+
]
327353
run.init(folder=folder_name)
328354
run.set_folder_details(path=folder_name, tags=tags, description=description)
329355

@@ -333,8 +359,12 @@ def test_set_folder_details(request: pytest.FixtureRequest) -> None:
333359

334360

335361
@pytest.mark.run
336-
@pytest.mark.parametrize("valid_mimetype", (True, False), ids=("valid_mime", "invalid_mime"))
337-
@pytest.mark.parametrize("preserve_path", (True, False), ids=("preserve_path", "modified_path"))
362+
@pytest.mark.parametrize(
363+
"valid_mimetype", (True, False), ids=("valid_mime", "invalid_mime")
364+
)
365+
@pytest.mark.parametrize(
366+
"preserve_path", (True, False), ids=("preserve_path", "modified_path")
367+
)
338368
@pytest.mark.parametrize("name", ("test_file", None), ids=("named", "nameless"))
339369
@pytest.mark.parametrize("allow_pickle", (True, False), ids=("pickled", "unpickled"))
340370
@pytest.mark.parametrize("empty_file", (True, False), ids=("empty", "content"))
@@ -345,19 +375,17 @@ def test_save_file(
345375
name: typing.Optional[str],
346376
allow_pickle: bool,
347377
empty_file: bool,
348-
capfd
378+
capfd,
349379
) -> None:
350380
simvue_run, _ = create_plain_run
351-
file_type: str = 'text/plain' if valid_mimetype else 'text/text'
381+
file_type: str = "text/plain" if valid_mimetype else "text/text"
352382
with tempfile.TemporaryDirectory() as tempd:
353383
with open(
354-
(
355-
out_name := pathlib.Path(tempd).joinpath("test_file.txt")
356-
),
384+
(out_name := pathlib.Path(tempd).joinpath("test_file.txt")),
357385
"w",
358386
) as out_f:
359387
out_f.write("test data entry" if not empty_file else "")
360-
388+
361389
if valid_mimetype:
362390
simvue_run.save_file(
363391
out_name,
@@ -372,14 +400,43 @@ def test_save_file(
372400
out_name,
373401
category="input",
374402
filetype=file_type,
375-
preserve_path=preserve_path
403+
preserve_path=preserve_path,
376404
)
377405
return
378-
406+
379407
variable = capfd.readouterr()
380408
with capfd.disabled():
381409
if empty_file:
382-
assert variable.out == "WARNING: saving zero-sized files not currently supported\n"
410+
assert (
411+
variable.out
412+
== "WARNING: saving zero-sized files not currently supported\n"
413+
)
414+
415+
416+
@pytest.mark.run
417+
def test_update_tags(
418+
create_plain_run: typing.Tuple[sv_run.Run, dict],
419+
request: pytest.FixtureRequest,
420+
) -> None:
421+
simvue_run, _ = create_plain_run
422+
423+
tags = [
424+
"simvue_client_unit_tests",
425+
request.node.name.replace("[", "_").replace("]", "_"),
426+
]
427+
428+
simvue_run.set_tags(tags)
429+
430+
time.sleep(1)
431+
client = sv_cl.Client()
432+
run_data = client.get_run(simvue_run._id)
433+
assert run_data["tags"] == tags
434+
435+
simvue_run.update_tags(["additional"])
436+
437+
time.sleep(1)
438+
run_data = client.get_run(simvue_run._id)
439+
assert sorted(run_data["tags"]) == sorted(tags + ["additional"])
383440

384441

385442
@pytest.mark.run

0 commit comments

Comments
 (0)