Skip to content

Commit 23c4635

Browse files
committed
Merge branch 'dev' into 224-prevent-use-of-run-update-methods-when-the-run-instance-has-not-been-initialised
2 parents 68ec1c4 + 855f365 commit 23c4635

23 files changed

+499
-215
lines changed

.github/workflows/test_client_macos.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ jobs:
2828
export SIMVUE_URL=${{ secrets.SIMVUE_URL }}
2929
export SIMVUE_TOKEN=${{ secrets.SIMVUE_TOKEN }}
3030
poetry install --all-extras
31-
poetry run pytest tests/unit/ tests/refactor/
31+
poetry run pytest tests/unit/ tests/refactor/ -m 'not scenario'

.github/workflows/test_client_ubuntu.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
export SIMVUE_URL=${{ secrets.SIMVUE_URL }}
3131
export SIMVUE_TOKEN=${{ secrets.SIMVUE_TOKEN }}
3232
poetry install --all-extras
33-
poetry run pytest --cov --cov-report=xml tests/unit/ tests/refactor/
33+
poetry run pytest --cov --cov-report=xml tests/unit/ tests/refactor/ -m 'not scenario'
3434
- name: Upload coverage reports to Codecov
3535
run: |
3636
curl -Os https://uploader.codecov.io/latest/linux/codecov

.github/workflows/test_client_windows.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ jobs:
2929
export SIMVUE_URL=${{ secrets.SIMVUE_URL }}
3030
export SIMVUE_TOKEN=${{ secrets.SIMVUE_TOKEN }}
3131
poetry install --all-extras
32-
poetry run pytest tests/unit/ tests/refactor/
32+
poetry run pytest tests/unit/ tests/refactor/ -m 'not scenario'

.github/workflows/test_multiple_python.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ jobs:
3232
export SIMVUE_URL=${{ secrets.SIMVUE_URL }}
3333
export SIMVUE_TOKEN=${{ secrets.SIMVUE_TOKEN }}
3434
poetry install --all-extras
35-
poetry run pytest --cov --cov-report=xml tests/unit/ tests/refactor/
35+
poetry run pytest tests/unit/ tests/refactor/ -m 'not scenario'

poetry.lock

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

pyproject.toml

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,25 @@ tenacity = "^8.2.3"
3939
PyJWT = "^2.8.0"
4040
psutil = "^5.9.8"
4141
pydantic = "^2.5.3"
42-
pandas = {version = "^2.2.0", optional = true}
42+
pandas = "^2.2.0"
4343
plotly = {version = "^5.18.0", optional = true}
44-
numpy = {version = "^1.26.3", optional = true}
44+
numpy = "^1.26.3"
4545
matplotlib = {version = "^3.8.2", optional = true}
4646
typing_extensions = { version = "^4.11.0", python = "<3.10" }
4747
toml = "^0.10.2"
4848
click = "^8.1.7"
49+
gitpython = "^3.1.43"
4950
humanfriendly = "^10.0"
5051
tabulate = "^0.9.0"
52+
torch = [
53+
{platform = "linux", version = "^2.0.0+cpu", source = "pytorch-cpu", optional=true},
54+
{platform = "darwin", version="^2.0.0", source="PyPI", optional=true},
55+
{platform = "win32", version="^2.0.0", source="PyPI", optional=true},
56+
]
5157

5258
[tool.poetry.extras]
53-
dataset = ["pandas", "numpy"]
5459
plot = ["matplotlib", "plotly"]
60+
torch = ["torch"]
5561

5662
[tool.poetry.scripts]
5763
simvue_sender = "simvue.bin.sender:run"
@@ -63,11 +69,7 @@ ruff = ">=0.3.2,<0.5.0"
6369
pytest-cov = ">=4.1,<6.0"
6470
pytest-mock = "^3.14.0"
6571
pytest-sugar = "^1.0.0"
66-
torch = [
67-
{platform = "linux", version = "^2.0.0+cpu", source = "pytorch-cpu"},
68-
{platform = "darwin", version="^2.0.0", source="PyPI"},
69-
{platform = "win32", version="^2.0.0", source="PyPI"},
70-
]
72+
pytest-xdist = "^3.6.1"
7173

7274
[build-system]
7375
requires = ["poetry-core"]
@@ -89,7 +91,7 @@ lint.extend-select = ["C901"]
8991
lint.mccabe.max-complexity = 11
9092

9193
[tool.pytest.ini_options]
92-
addopts = "-p no:warnings --cov=simvue"
94+
addopts = "-p no:warnings --cov=simvue -n auto"
9395
testpaths = [
9496
"tests"
9597
]
@@ -100,7 +102,8 @@ markers = [
100102
"run: test the simvue Run class",
101103
"utilities: test simvue utilities module",
102104
"scenario: test scenarios",
103-
"executor: tests of executors"
105+
"executor: tests of executors",
106+
"api: tests of RestAPI functionality"
104107
]
105108

106109
[tool.interrogate]

simvue/api.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@
1212
import typing
1313

1414
import requests
15-
from tenacity import retry, stop_after_attempt, wait_exponential
15+
from tenacity import (
16+
retry,
17+
stop_after_attempt,
18+
wait_exponential,
19+
retry_if_exception_type,
20+
)
21+
from .utilities import parse_validation_response
1622

1723
DEFAULT_API_TIMEOUT = 10
1824
RETRY_MULTIPLIER = 1
@@ -34,6 +40,7 @@ def set_json_header(headers: dict[str, str]) -> dict[str, str]:
3440
@retry(
3541
wait=wait_exponential(multiplier=RETRY_MULTIPLIER, min=RETRY_MIN, max=RETRY_MAX),
3642
stop=stop_after_attempt(RETRY_STOP),
43+
retry=retry_if_exception_type(RuntimeError),
3744
reraise=True,
3845
)
3946
def post(
@@ -73,6 +80,12 @@ def post(
7380
f"Authorization error [{response.status_code}]: {response.text}"
7481
)
7582

83+
if response.status_code == 422:
84+
_parsed_response = parse_validation_response(response.json())
85+
raise ValueError(
86+
f"Validation error [{response.status_code}]:\n{_parsed_response}"
87+
)
88+
7689
if response.status_code not in (200, 201, 409):
7790
raise RuntimeError(f"HTTP error [{response.status_code}]: {response.text}")
7891

@@ -81,6 +94,7 @@ def post(
8194

8295
@retry(
8396
wait=wait_exponential(multiplier=RETRY_MULTIPLIER, min=RETRY_MIN, max=RETRY_MAX),
97+
# retry=retry_if_not_exception_message(match="Validation error"), # if validation failed no point in retrying
8498
stop=stop_after_attempt(RETRY_STOP),
8599
)
86100
def put(

simvue/client.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,6 @@ def get_metric_values(
10151015
)
10161016

10171017
@check_extra("plot")
1018-
@check_extra("dataset")
10191018
def plot_metrics(
10201019
self,
10211020
run_ids: list[str],

simvue/converters.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@
77
"""
88

99
import typing
10+
import pandas
1011

1112
if typing.TYPE_CHECKING:
1213
from pandas import DataFrame
1314

14-
from .utilities import check_extra
15-
1615

1716
def aggregated_metrics_to_dataframe(
1817
request_response_data: dict[str, list[dict[str, float]]],
@@ -78,9 +77,6 @@ def aggregated_metrics_to_dataframe(
7877
)
7978

8079
if parse_to == "dataframe":
81-
check_extra("dataset")
82-
import pandas
83-
8480
_data_frame = pandas.DataFrame(result_dict)
8581
_data_frame.index.name = xaxis
8682
return _data_frame
@@ -174,9 +170,6 @@ def parse_run_set_metrics(
174170
result_dict[metric_name][step, run_label] = next_item.get("value")
175171

176172
if parse_to == "dataframe":
177-
check_extra("dataset")
178-
import pandas
179-
180173
_data_frame = pandas.DataFrame(
181174
result_dict,
182175
index=pandas.MultiIndex.from_product(
@@ -194,7 +187,6 @@ def to_dataframe(data):
194187
"""
195188
Convert runs to dataframe
196189
"""
197-
import pandas as pd
198190

199191
metadata = []
200192
for run in data:
@@ -236,7 +228,7 @@ def to_dataframe(data):
236228
else:
237229
columns["metadata.%s" % item].append(None)
238230

239-
return pd.DataFrame(data=columns)
231+
return pandas.DataFrame(data=columns)
240232

241233

242234
def metric_time_series_to_dataframe(
@@ -260,11 +252,10 @@ def metric_time_series_to_dataframe(
260252
DataFrame
261253
a Pandas DataFrame containing values for the metric and run at each
262254
"""
263-
import pandas as pd
264255

265256
_df_dict: dict[str, list[float]] = {
266257
xaxis: [v[xaxis] for v in data],
267258
name or "value": [v["value"] for v in data],
268259
}
269260

270-
return pd.DataFrame(_df_dict)
261+
return pandas.DataFrame(_df_dict)

simvue/metadata.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""
2+
Metadata
3+
========
4+
5+
Contains functions for extracting additional metadata about the current project
6+
7+
"""
8+
9+
import typing
10+
import git
11+
import json
12+
13+
14+
def git_info(repository: str) -> dict[str, typing.Any]:
15+
"""Retrieves metadata for the target git repository
16+
17+
This is a passive function which returns an empty dictionary if any
18+
metadata is missing. Exceptions are raised only if the repository
19+
does not have the expected form assumed by this retrieval method.
20+
21+
Parameters
22+
----------
23+
repository : str
24+
root directory of the git repository
25+
26+
Returns
27+
-------
28+
dict[str, typing.Any]
29+
metadata for the target repository
30+
"""
31+
try:
32+
git_repo = git.Repo(repository, search_parent_directories=True)
33+
except git.InvalidGitRepositoryError:
34+
return {}
35+
current_commit: git.Commit = git_repo.head.commit
36+
author_list: set[str] = set(
37+
email
38+
for commit in git_repo.iter_commits("--all")
39+
if "noreply" not in (email := (commit.author.email or ""))
40+
and "[bot]" not in (commit.author.name or "")
41+
)
42+
43+
ref: str = current_commit.hexsha
44+
45+
# In the case where the repository is dirty blame should point to the
46+
# current developer, not the person responsible for the latest commit
47+
if dirty := git_repo.is_dirty():
48+
blame = git_repo.config_reader().get_value("user", "email", "unknown")
49+
else:
50+
blame = current_commit.committer.email
51+
52+
for tag in git_repo.tags:
53+
if tag.commit == current_commit:
54+
ref = tag.name
55+
break
56+
57+
return {
58+
"git.authors": json.dumps(list(author_list)),
59+
"git.ref": ref,
60+
"git.msg": current_commit.message.strip(),
61+
"git.time_stamp": current_commit.committed_datetime.strftime(
62+
"%Y-%m-%d %H:%M:%S %z UTC"
63+
),
64+
"git.blame": blame,
65+
"git.url": git_repo.remote().url,
66+
"git.dirty": dirty,
67+
}
68+
69+
70+
if __name__ in "__main__":
71+
import os.path
72+
import json
73+
74+
print(json.dumps(git_info(os.path.dirname(__file__)), indent=2))

0 commit comments

Comments
 (0)