Skip to content

Commit a8c9cf6

Browse files
committed
Merge branch 'dev' into feature/toml-config
2 parents 5a11f73 + 09dcc4c commit a8c9cf6

23 files changed

+1071
-291
lines changed

.github/workflows/deploy.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979
name: python-package-distributions
8080
path: dist/
8181
- name: Sign the dists with Sigstore
82-
uses: sigstore/gh-action-sigstore-python@v2.1.1
82+
uses: sigstore/gh-action-sigstore-python@v3
8383
with:
8484
inputs: >-
8585
./dist/*.tar.gz

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ repos:
2323
args: [--branch, main, --branch, dev]
2424
- id: check-added-large-files
2525
- repo: https://github.com/astral-sh/ruff-pre-commit
26-
rev: v0.6.4
26+
rev: v0.6.7
2727
hooks:
2828
- id: ruff
2929
args: [ --fix, --exit-non-zero-on-fix, "--ignore=C901" ]

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Change log
22

3-
## Unreleased
3+
## [v1.0.4](https://github.com/simvue-io/client/releases/tag/v1.0.4) - 2024-09-24
4+
5+
* Set resource metrics to be recorded by default.
6+
7+
## [v1.0.3](https://github.com/simvue-io/client/releases/tag/v1.0.3) - 2024-09-23
48

59
* Fix issue of hanging threads when exception raised by script using the API.
610

CITATION.cff

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ keywords:
4242
- alerting
4343
- simulation
4444
license: Apache-2.0
45-
commit: dd47b0c04e5c634a5bb39b216c71a38987de4360
46-
version: 1.0.2
47-
date-released: '2024-08-21'
45+
commit: c08894da7a4c1519dc39e5caf62cc104ecf015dd
46+
version: 1.0.4
47+
date-released: '2024-09-24'

poetry.lock

Lines changed: 563 additions & 210 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "simvue"
3-
version = "1.0.2"
3+
version = "1.0.4"
44
description = "Simulation tracking and monitoring"
55
authors = ["Simvue Development Team <[email protected]>"]
66
license = "Apache v2"
@@ -54,6 +54,7 @@ gitpython = "^3.1.43"
5454
humanfriendly = "^10.0"
5555
tabulate = "^0.9.0"
5656
randomname = "^0.2.1"
57+
codecarbon = "^2.7.1"
5758

5859
[tool.poetry.extras]
5960
plot = ["matplotlib", "plotly"]
@@ -100,7 +101,9 @@ markers = [
100101
"executor: tests of executors",
101102
"config: tests of simvue configuration",
102103
"api: tests of RestAPI functionality",
103-
"unix: tests for UNIX systems only"
104+
"unix: tests for UNIX systems only",
105+
"metadata: tests of metadata gathering functions",
106+
"proxies: tests for remote/offline Simvue proxies"
104107
]
105108

106109
[tool.interrogate]

simvue/api.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import copy
1111
import json
1212
import typing
13+
import http
1314

1415
import requests
1516
from tenacity import (
@@ -75,18 +76,25 @@ def post(
7576
url, headers=headers, data=data_sent, timeout=DEFAULT_API_TIMEOUT
7677
)
7778

78-
if response.status_code in (401, 403):
79+
if response.status_code in (
80+
http.HTTPStatus.UNAUTHORIZED,
81+
http.HTTPStatus.FORBIDDEN,
82+
):
7983
raise RuntimeError(
8084
f"Authorization error [{response.status_code}]: {response.text}"
8185
)
8286

83-
if response.status_code == 422:
87+
if response.status_code == http.HTTPStatus.UNPROCESSABLE_ENTITY:
8488
_parsed_response = parse_validation_response(response.json())
8589
raise ValueError(
8690
f"Validation error for '{url}' [{response.status_code}]:\n{_parsed_response}"
8791
)
8892

89-
if response.status_code not in (200, 201, 409):
93+
if response.status_code not in (
94+
http.HTTPStatus.OK,
95+
http.HTTPStatus.CREATED,
96+
http.HTTPStatus.CONFLICT,
97+
):
9098
raise RuntimeError(
9199
f"HTTP error for '{url}' [{response.status_code}]: {response.text}"
92100
)

simvue/client.py

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import logging
1111
import os
1212
import typing
13+
import http
1314
import pydantic
1415
from concurrent.futures import ThreadPoolExecutor, as_completed
1516
from pandas import DataFrame
@@ -22,7 +23,7 @@
2223
parse_run_set_metrics,
2324
)
2425
from .serialization import deserialize_data
25-
from .types import DeserializedContent
26+
from .simvue_types import DeserializedContent
2627
from .utilities import check_extra, get_auth, prettify_pydantic
2728
from .models import FOLDER_REGEX, NAME_REGEX
2829

@@ -168,7 +169,7 @@ def get_run_id_from_name(
168169
)
169170

170171
json_response = self._get_json_from_response(
171-
expected_status=[200],
172+
expected_status=[http.HTTPStatus.OK],
172173
scenario="Retrieval of run ID from name",
173174
response=response,
174175
)
@@ -218,12 +219,12 @@ def get_run(self, run_id: str) -> typing.Optional[dict[str, typing.Any]]:
218219
)
219220

220221
json_response = self._get_json_from_response(
221-
expected_status=[200, 404],
222+
expected_status=[http.HTTPStatus.OK, http.HTTPStatus.NOT_FOUND],
222223
scenario=f"Retrieval of run '{run_id}'",
223224
response=response,
224225
)
225226

226-
if response.status_code == 404:
227+
if response.status_code == http.HTTPStatus.NOT_FOUND:
227228
return None
228229

229230
if not isinstance(json_response, dict):
@@ -339,7 +340,9 @@ def get_runs(
339340
raise ValueError("Invalid format specified")
340341

341342
json_response = self._get_json_from_response(
342-
expected_status=[200], scenario="Run retrieval", response=response
343+
expected_status=[http.HTTPStatus.OK],
344+
scenario="Run retrieval",
345+
response=response,
343346
)
344347

345348
if not isinstance(json_response, dict):
@@ -381,7 +384,7 @@ def delete_run(self, run_identifier: str) -> typing.Optional[dict]:
381384
)
382385

383386
json_response = self._get_json_from_response(
384-
expected_status=[200],
387+
expected_status=[http.HTTPStatus.OK],
385388
scenario=f"Deletion of run '{run_identifier}'",
386389
response=response,
387390
)
@@ -416,7 +419,7 @@ def _get_folder_id_from_path(self, path: str) -> typing.Optional[str]:
416419
)
417420

418421
if (
419-
response.status_code == 200
422+
response.status_code == http.HTTPStatus.OK
420423
and (response_data := response.json().get("data"))
421424
and (identifier := response_data[0].get("id"))
422425
):
@@ -458,7 +461,7 @@ def delete_runs(
458461
f"{self._url}/api/folders/{folder_id}", headers=self._headers, params=params
459462
)
460463

461-
if response.status_code == 200:
464+
if response.status_code == http.HTTPStatus.OK:
462465
if runs := response.json().get("runs", []):
463466
logger.debug(f"Runs from '{folder_path}' deleted successfully: {runs}")
464467
else:
@@ -523,7 +526,7 @@ def delete_folder(
523526
)
524527

525528
json_response = self._get_json_from_response(
526-
expected_status=[200, 404],
529+
expected_status=[http.HTTPStatus.OK, http.HTTPStatus.NOT_FOUND],
527530
scenario=f"Deletion of folder '{folder_path}'",
528531
response=response,
529532
)
@@ -551,7 +554,7 @@ def delete_alert(self, alert_id: str) -> None:
551554
f"{self._url}/api/alerts/{alert_id}", headers=self._headers
552555
)
553556

554-
if response.status_code == 200:
557+
if response.status_code == http.HTTPStatus.OK:
555558
logger.debug(f"Alert '{alert_id}' deleted successfully")
556559
return
557560

@@ -587,7 +590,7 @@ def list_artifacts(self, run_id: str) -> list[dict[str, typing.Any]]:
587590
)
588591

589592
json_response = self._get_json_from_response(
590-
expected_status=[200],
593+
expected_status=[http.HTTPStatus.OK],
591594
scenario=f"Retrieval of artifacts for run '{run_id}",
592595
response=response,
593596
)
@@ -613,7 +616,7 @@ def _retrieve_artifact_from_server(
613616
)
614617

615618
json_response = self._get_json_from_response(
616-
expected_status=[200, 404],
619+
expected_status=[http.HTTPStatus.OK, http.HTTPStatus.NOT_FOUND],
617620
scenario=f"Retrieval of artifact '{name}' for run '{run_id}'",
618621
response=response,
619622
)
@@ -652,7 +655,7 @@ def abort_run(self, run_id: str, reason: str) -> typing.Union[dict, list]:
652655
)
653656

654657
json_response = self._get_json_from_response(
655-
expected_status=[200, 404],
658+
expected_status=[http.HTTPStatus.OK, http.HTTPStatus.NOT_FOUND],
656659
scenario=f"Abort of run '{run_id}'",
657660
response=response,
658661
)
@@ -846,7 +849,7 @@ def get_artifacts_as_files(
846849
)
847850

848851
self._get_json_from_response(
849-
expected_status=[200],
852+
expected_status=[http.HTTPStatus.OK],
850853
scenario=f"Download of artifacts for run '{run_id}'",
851854
response=response,
852855
)
@@ -936,7 +939,9 @@ def get_folders(
936939
)
937940

938941
json_response = self._get_json_from_response(
939-
expected_status=[200], scenario="Retrieval of folders", response=response
942+
expected_status=[http.HTTPStatus.OK],
943+
scenario="Retrieval of folders",
944+
response=response,
940945
)
941946

942947
if not isinstance(json_response, dict):
@@ -979,7 +984,7 @@ def get_metrics_names(self, run_id: str) -> list[str]:
979984
)
980985

981986
json_response = self._get_json_from_response(
982-
expected_status=[200],
987+
expected_status=[http.HTTPStatus.OK],
983988
scenario=f"Request for metric names for run '{run_id}'",
984989
response=response,
985990
)
@@ -1013,7 +1018,7 @@ def _get_run_metrics_from_server(
10131018
)
10141019

10151020
json_response = self._get_json_from_response(
1016-
expected_status=[200],
1021+
expected_status=[http.HTTPStatus.OK],
10171022
scenario=f"Retrieval of metrics '{metric_names}' in " f"runs '{run_ids}'",
10181023
response=metrics_response,
10191024
)
@@ -1270,7 +1275,7 @@ def get_events(
12701275
)
12711276

12721277
json_response = self._get_json_from_response(
1273-
expected_status=[200],
1278+
expected_status=[http.HTTPStatus.OK],
12741279
scenario=f"Retrieval of events for run '{run_id}'",
12751280
response=response,
12761281
)
@@ -1285,16 +1290,19 @@ def get_events(
12851290
@prettify_pydantic
12861291
@pydantic.validate_call
12871292
def get_alerts(
1288-
self, run_id: str, critical_only: bool = True, names_only: bool = True
1293+
self,
1294+
run_id: typing.Optional[str] = None,
1295+
critical_only: bool = True,
1296+
names_only: bool = True,
12891297
) -> list[dict[str, typing.Any]]:
12901298
"""Retrieve alerts for a given run
12911299
12921300
Parameters
12931301
----------
1294-
run_id : str
1302+
run_id : str | None
12951303
The ID of the run to find alerts for
12961304
critical_only : bool, optional
1297-
Whether to only return details about alerts which are currently critical, by default True
1305+
If a run is specified, whether to only return details about alerts which are currently critical, by default True
12981306
names_only: bool, optional
12991307
Whether to only return the names of the alerts (otherwise return the full details of the alerts), by default True
13001308
@@ -1308,26 +1316,42 @@ def get_alerts(
13081316
RuntimeError
13091317
if there was a failure retrieving data from the server
13101318
"""
1311-
response = requests.get(f"{self._url}/api/runs/{run_id}", headers=self._headers)
1319+
if not run_id:
1320+
response = requests.get(f"{self._url}/api/alerts/", headers=self._headers)
13121321

1313-
json_response = self._get_json_from_response(
1314-
expected_status=[200],
1315-
scenario=f"Retrieval of alerts for run '{run_id}'",
1316-
response=response,
1317-
)
1322+
json_response = self._get_json_from_response(
1323+
expected_status=[http.HTTPStatus.OK],
1324+
scenario=f"Retrieval of alerts for run '{run_id}'",
1325+
response=response,
1326+
)
1327+
else:
1328+
response = requests.get(
1329+
f"{self._url}/api/runs/{run_id}", headers=self._headers
1330+
)
1331+
1332+
json_response = self._get_json_from_response(
1333+
expected_status=[200],
1334+
scenario=f"Retrieval of alerts for run '{run_id}'",
1335+
response=response,
1336+
)
13181337

13191338
if not isinstance(json_response, dict):
13201339
raise RuntimeError(
13211340
"Expected dictionary from JSON response when retrieving alerts"
13221341
)
13231342

1324-
if (alerts := json_response.get("alerts")) is None:
1343+
if run_id and (alerts := json_response.get("alerts")) is None:
13251344
raise RuntimeError(
13261345
"Expected key 'alerts' in response when retrieving "
13271346
f"alerts for run '{run_id}': {json_response}"
13281347
)
1348+
elif not run_id and (alerts := json_response.get("data")) is None:
1349+
raise RuntimeError(
1350+
"Expected key 'data' in response when retrieving "
1351+
f"alerts: {json_response}"
1352+
)
13291353

1330-
if critical_only:
1354+
if run_id and critical_only:
13311355
if names_only:
13321356
return [
13331357
alert["alert"].get("name")
@@ -1340,7 +1364,10 @@ def get_alerts(
13401364
for alert in alerts
13411365
if alert["status"].get("current") == "critical"
13421366
]
1343-
elif names_only:
1344-
return [alert["alert"].get("name") for alert in alerts]
1367+
if names_only:
1368+
if run_id:
1369+
return [alert["alert"].get("name") for alert in alerts]
1370+
else:
1371+
return [alert.get("name") for alert in alerts]
13451372

13461373
return alerts

0 commit comments

Comments
 (0)