Skip to content

Commit 673b3c2

Browse files
committed
Created first fetch function for server retrieval
1 parent 03fbf68 commit 673b3c2

11 files changed

Lines changed: 155 additions & 54 deletions

File tree

‎pyproject.toml‎

Lines changed: 40 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,35 @@
22
name = "simvue"
33
version = "2.3.6"
44
description = "Simulation tracking and monitoring"
5-
authors = [
6-
{name = "Simvue Development Team", email = "info@simvue.io"}
7-
]
5+
authors = [{ name = "Simvue Development Team", email = "info@simvue.io" }]
86
license = "Apache v2"
97
requires-python = ">=3.10,<3.15"
108
readme = "README.md"
119
classifiers = [
12-
"Development Status :: 5 - Production/Stable",
13-
"Intended Audience :: Science/Research",
14-
"License :: OSI Approved :: Apache Software License",
15-
"Natural Language :: English",
16-
"Operating System :: Unix",
17-
"Operating System :: Microsoft :: Windows",
18-
"Programming Language :: Python :: 3",
19-
"Programming Language :: Python :: 3.10",
20-
"Programming Language :: Python :: 3.11",
21-
"Programming Language :: Python :: 3.12",
22-
"Programming Language :: Python :: 3.13",
23-
"Programming Language :: Python :: 3.14",
24-
"Topic :: Scientific/Engineering",
25-
"Topic :: System :: Monitoring",
26-
"Topic :: Utilities",
27-
"Typing :: Typed"
10+
"Development Status :: 5 - Production/Stable",
11+
"Intended Audience :: Science/Research",
12+
"License :: OSI Approved :: Apache Software License",
13+
"Natural Language :: English",
14+
"Operating System :: Unix",
15+
"Operating System :: Microsoft :: Windows",
16+
"Programming Language :: Python :: 3",
17+
"Programming Language :: Python :: 3.10",
18+
"Programming Language :: Python :: 3.11",
19+
"Programming Language :: Python :: 3.12",
20+
"Programming Language :: Python :: 3.13",
21+
"Programming Language :: Python :: 3.14",
22+
"Topic :: Scientific/Engineering",
23+
"Topic :: System :: Monitoring",
24+
"Topic :: Utilities",
25+
"Typing :: Typed",
2826
]
2927

3028
keywords = [
31-
"tracking",
32-
"monitoring",
33-
"metrics",
34-
"alerting",
35-
"metrics-gathering"
29+
"tracking",
30+
"monitoring",
31+
"metrics",
32+
"alerting",
33+
"metrics-gathering",
3634
]
3735
dependencies = [
3836
"requests (>=2.32.3,<3.0.0)",
@@ -93,26 +91,25 @@ extend-exclude = ["tests", "examples", "notebooks"]
9391

9492
[tool.pytest.ini_options]
9593
addopts = "-p no:warnings --no-cov -n 0"
96-
testpaths = [
97-
"tests"
98-
]
94+
testpaths = ["tests"]
9995
markers = [
100-
"eco: tests for emission metrics",
101-
"client: tests of Simvue client",
102-
"dispatch: test data dispatcher",
103-
"run: test the simvue Run class",
104-
"utilities: test simvue utilities module",
105-
"scenario: test scenarios",
106-
"executor: tests of executors",
107-
"config: tests of simvue configuration",
108-
"api: tests of RestAPI functionality",
109-
"unix: tests for UNIX systems only",
110-
"metadata: tests of metadata gathering functions",
111-
"online: tests for online functionality",
112-
"offline: tests for offline functionality",
113-
"local: tests of functionality which do not involve a server or writing to an offline cache file",
114-
"object_retrieval: tests relating to retrieval of objects from the server",
115-
"object_removal: tests relating to removal of objects from the server",
96+
"eco: tests for emission metrics",
97+
"client: tests of Simvue client",
98+
"dispatch: test data dispatcher",
99+
"run: test the simvue Run class",
100+
"utilities: test simvue utilities module",
101+
"scenario: test scenarios",
102+
"executor: tests of executors",
103+
"config: tests of simvue configuration",
104+
"api: tests of RestAPI functionality",
105+
"unix: tests for UNIX systems only",
106+
"metadata: tests of metadata gathering functions",
107+
"online: tests for online functionality",
108+
"offline: tests for offline functionality",
109+
"local: tests of functionality which do not involve a server or writing to an offline cache file",
110+
"object_retrieval: tests relating to retrieval of objects from the server",
111+
"object_removal: tests relating to removal of objects from the server",
112+
"experimental: tests of experimental future features",
116113
]
117114

118115
[tool.interrogate]

‎simvue/api/objects/base.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ def batch_create(
383383
@classmethod
384384
def ids(
385385
cls, count: int | None = None, offset: int | None = None, **kwargs
386-
) -> Generator[str, None, None]:
386+
) -> Generator[str]:
387387
"""Retrieve a list of all object identifiers.
388388
389389
Parameters

‎simvue/api/objects/metrics.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from collections.abc import Generator
1717

1818
from .base import SimvueObject
19-
from simvue.models import MetricSet
19+
from simvue.models import MetricSet, PerRunMetrics
2020
from simvue.api.request import get as sv_get, get_json_from_response
2121

2222
try:
@@ -87,7 +87,7 @@ def get(
8787
count: pydantic.PositiveInt | None = None,
8888
offset: pydantic.PositiveInt | None = None,
8989
**kwargs,
90-
) -> Generator[dict[str, dict[str, list[dict[str, float]]]]]:
90+
) -> Generator[dict[str, PerRunMetrics]]:
9191
"""Retrieve metrics from the server for a given set of runs.
9292
9393
Parameters

‎simvue/client.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from .serialization import deserialize_data
3131
from .simvue_types import DeserializedContent
3232
from .utilities import check_extra, prettify_pydantic
33-
from .models import FOLDER_REGEX, NAME_REGEX
33+
from .models import FOLDER_REGEX, NAME_REGEX, XAxis
3434
from .config.user import SimvueConfiguration
3535
from .api.request import get_json_from_response
3636
from .api.objects import (
@@ -786,7 +786,7 @@ def _get_run_metrics_from_server(
786786
def get_metric_values(
787787
self,
788788
metric_names: list[str],
789-
xaxis: typing.Literal["step", "time", "timestamp"],
789+
xaxis: XAxis,
790790
*,
791791
output_format: typing.Literal["dataframe", "dict"] = "dict",
792792
run_ids: list[str] | None = None,

‎simvue/exception.py‎

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

99

10-
class ObjectNotFoundError(Exception):
10+
class SimvueException(Exception):
11+
"""Base class for Simvue based exceptions."""
12+
13+
14+
class ObjectNotFoundError(SimvueException):
1115
"""For failure retrieving Simvue object from server"""
1216

1317
def __init__(self, obj_type: str, name: str, extra: str | None = None) -> None:
@@ -18,11 +22,11 @@ def __init__(self, obj_type: str, name: str, extra: str | None = None) -> None:
1822
)
1923

2024

21-
class SimvueRunError(RuntimeError):
25+
class SimvueRunError(SimvueException):
2226
"""A special sub-class of runtime error specifically for Simvue run errors"""
2327

2428

25-
class ObjectDispatchError(Exception):
29+
class ObjectDispatchError(SimvueException):
2630
"""Raised if object dispatch failed due to condition."""
2731

2832
def __init__(self, label: str, threshold: int | float, value: int | float) -> None:
@@ -31,3 +35,7 @@ def __init__(self, label: str, threshold: int | float, value: int | float) -> No
3135
+ f"of {value} exceeds maximum permitted value of {threshold}"
3236
)
3337
super().__init__(self.msg)
38+
39+
40+
class InvalidQueryError(SimvueException):
41+
"""Exception raised if the parameters for a query are invalid."""

‎simvue/experimental/__init__.py‎

Whitespace-only changes.

‎simvue/experimental/fetch.py‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Fetch Objects from the Simvue Server."""
2+
3+
import json
4+
import simvue.api.objects as sv_obj
5+
import pydantic as pyd
6+
import typing
7+
8+
from collections.abc import Generator
9+
from simvue.exception import InvalidQueryError
10+
from simvue.models import MetricKeyString, ObjectID, XAxis, PerRunMetrics
11+
12+
13+
@pyd.validate_call
14+
def get_metric_values(
15+
*,
16+
metric_names: typing.Annotated[
17+
list[MetricKeyString], pyd.conlist(str, min_length=1)
18+
],
19+
xaxis: XAxis,
20+
run_ids: list[ObjectID] | None = None,
21+
run_filters: list[str] | None = None,
22+
) -> Generator[tuple[ObjectID, PerRunMetrics]]:
23+
"""Retrieve all metric values for a given metric set and runs.
24+
25+
Parameters
26+
----------
27+
metric_names : list[str]
28+
list of metrics to retrieve by name
29+
xaxis : Literal['step', 'time', 'timestamp']
30+
xaxis value to use for metrics
31+
run_ids : list[str] | None, optional
32+
list of runs by ID to retrieve data for,
33+
if None then `run_filters` must be specified.
34+
run_filters : list[str] | None, optional
35+
list of filters to use for selecting runs for
36+
data retrieval, if None then `run_ids` must be specified.
37+
38+
Yields
39+
------
40+
str
41+
Run identifier
42+
dict[str, list[dict[str, float]]]
43+
Metric values for each run at all time intervals
44+
"""
45+
46+
if (not run_ids and not run_filters) or (run_ids and run_filters):
47+
raise InvalidQueryError(
48+
"Argument must be provided for either 'run_ids' or 'run_filters', "
49+
+ "but not both."
50+
)
51+
_query_arguments: dict[str, object] = {
52+
"xaxis": xaxis,
53+
"metrics": metric_names,
54+
}
55+
56+
if run_filters:
57+
_run_list: list[str] = []
58+
for run_id in sv_obj.Run.ids(filters=json.dumps(run_filters)):
59+
_run_list.append(run_id)
60+
if len(_run_list) > 99:
61+
for result in sv_obj.Metrics.get(
62+
runs=_run_list, metrics=metric_names, xaxis=xaxis
63+
):
64+
yield from result.items()
65+
_run_list = []
66+
if _run_list:
67+
for result in sv_obj.Metrics.get(
68+
runs=_run_list, metrics=metric_names, xaxis=xaxis
69+
):
70+
yield from result.items()
71+
72+
if run_ids:
73+
for result in sv_obj.Metrics.get(
74+
runs=run_ids, metrics=metric_names, xaxis=xaxis
75+
):
76+
yield from result.items()

‎simvue/models.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
str, pydantic.StringConstraints(pattern=METRIC_KEY_REGEX)
2020
]
2121
ObjectID = typing.Annotated[str, pydantic.StringConstraints(pattern=OBJECT_ID)]
22+
XAxis = typing.Literal["step", "time", "timestamp"]
23+
PerRunMetrics = dict[str, list[dict[typing.Literal[XAxis, "value"], float]]]
2224

2325

2426
def validate_timestamp(timestamp: str, raise_except: bool = True) -> bool:
@@ -85,7 +87,7 @@ class RunInput(pydantic.BaseModel):
8587

8688
class MetricSet(pydantic.BaseModel):
8789
model_config = pydantic.ConfigDict(extra="forbid")
88-
time: float | int
90+
time: pydantic.NonNegativeFloat | pydantic.NonNegativeInt
8991
timestamp: typing.Annotated[str | None, pydantic.BeforeValidator(simvue_timestamp)]
9092
step: pydantic.NonNegativeInt
9193
values: dict[str, int | float | bool]
@@ -95,7 +97,7 @@ class GridMetricSet(pydantic.BaseModel):
9597
model_config = pydantic.ConfigDict(
9698
arbitrary_types_allowed=True, extra="forbid", validate_default=True
9799
)
98-
time: float | int
100+
time: pydantic.NonNegativeFloat | pydantic.NonNegativeInt
99101
timestamp: typing.Annotated[str | None, pydantic.BeforeValidator(simvue_timestamp)]
100102
step: pydantic.NonNegativeInt
101103
array: list[float] | list[list[float]] | numpy.ndarray

‎simvue/utilities.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ def wrapper(self, *args, **kwargs) -> typing.Any:
178178

179179

180180
def parse_pydantic_error(error: pydantic.ValidationError) -> str:
181+
"""Parse the validation error returned by Pydantic."""
181182
out_table: list[str] = []
182183
for data in json.loads(error.json()):
183184
_input = data.get("input") if data["input"] is not None else "None"

‎tests/functional/test_client.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import tempfile
1212
import simvue.client as svc
1313
from simvue.exception import ObjectNotFoundError
14+
from simvue.models import XAxis
1415
import simvue.run as sv_run
1516
import simvue.api.objects as sv_api_obj
1617
from simvue.api.objects.alert.base import AlertBase
@@ -491,7 +492,7 @@ def test_multiple_metric_retrieval(
491492
create_test_run: tuple[sv_run.Run, dict],
492493
aggregate: bool,
493494
output_format: typing.Literal["dict", "dataframe"],
494-
xaxis: typing.Literal["step", "time", "timestamp"],
495+
xaxis: XAxis,
495496
) -> None:
496497
client = svc.Client()
497498
if output_format == "dataframe":

0 commit comments

Comments
 (0)