Skip to content

Commit 5f19c17

Browse files
committed
wip-3
1 parent d1e8e9b commit 5f19c17

File tree

8 files changed

+357
-85
lines changed

8 files changed

+357
-85
lines changed

tests/e2e/internal/test_split.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import pytest
2-
from helpers.metrics import normalize_metrics_data
2+
from helpers.metrics import (
3+
FloatPointValue,
4+
assert_metric_mappings_equal,
5+
normalize_metrics_data,
6+
)
37

48
import neptune_query as npt
59
from neptune_query.exceptions import (
@@ -288,13 +292,18 @@ def test_fetch_float_series_values_retrieval(client, project, experiment_identif
288292
RunAttributeDefinition(
289293
run_identifier=identifiers.RunIdentifier(project.project_identifier, exp.sys_id),
290294
attribute_definition=identifiers.AttributeDefinition(key, "float_series"),
291-
): [(int(NOW.timestamp() * 1000), 1.0, value, False, 1.0)]
295+
): [
296+
FloatPointValue.create(
297+
step=1.0,
298+
value=value,
299+
)
300+
]
292301
for exp in exp_identifiers
293302
for key, value in attribute_data.items()
294303
}
295304
)
296305
assert thrown_e is None
297-
assert result == expected_values
306+
assert_metric_mappings_equal(result, expected_values)
298307
else:
299308
assert result is None
300309
assert thrown_e is not None

tests/e2e/v1/runs/test_runs_fetch_metrics.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
RUN_BY_ID,
1111
timestamp_for_step,
1212
)
13-
from tests.helpers.metrics import normalize_metrics_data
13+
from tests.helpers.metrics import (
14+
FloatPointValue,
15+
normalize_metrics_data,
16+
)
1417

1518
NEPTUNE_PROJECT = os.getenv("NEPTUNE_E2E_PROJECT")
1619

@@ -235,7 +238,15 @@ def create_expected_data(project, expected_metrics, include_time: str, type_suff
235238
rows = data.setdefault(attribute_run, [])
236239

237240
for step, value in values:
238-
rows.append((int(timestamp_for_step(step).timestamp() * 1000), step, value, False, 1.0))
241+
rows.append(
242+
FloatPointValue.create(
243+
timestamp_ms=int(timestamp_for_step(step).timestamp() * 1000),
244+
step=step,
245+
value=value,
246+
is_preview=False,
247+
completion_ratio=1.0,
248+
)
249+
)
239250

240251
sys_id_label_mapping = {identifiers.SysId(run): run for run, _ in expected_metrics.keys()}
241252

tests/e2e/v1/test_fetch_metrics.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,13 @@ def _to_run_attribute_definition(project, run, metric_name):
5454

5555

5656
def _to_float_point_value(step, value):
57-
return int(timestamp_for_step(step).timestamp() * 1000), step, value, False, 1.0
57+
return FloatPointValue.create(
58+
timestamp_ms=int(timestamp_for_step(step).timestamp() * 1000),
59+
step=step,
60+
value=value,
61+
is_preview=False,
62+
completion_ratio=1.0,
63+
)
5864

5965

6066
def _sys_id_label_mapping(experiments: list[ExperimentData]) -> dict[SysId, str]:
@@ -73,12 +79,12 @@ def _run_attribute_definition(
7379

7480

7581
def _float_point_value(step, value) -> FloatPointValue:
76-
return (
77-
int((NOW + timedelta(seconds=int(step))).timestamp()) * 1000,
78-
step,
79-
value,
80-
False,
81-
1.0,
82+
return FloatPointValue.create(
83+
timestamp_ms=int((NOW + timedelta(seconds=int(step))).timestamp()) * 1000,
84+
step=step,
85+
value=value,
86+
is_preview=False,
87+
completion_ratio=1.0,
8288
)
8389

8490

@@ -132,12 +138,12 @@ def create_expected_data(
132138
columns.add(f"{path}:float_series" if type_suffix_in_column_names else path)
133139
filtered_experiments.add(experiment.name)
134140
filtered.append(
135-
(
136-
int((NOW + timedelta(seconds=int(step))).timestamp()) * 1000,
137-
step,
138-
series[int(step)],
139-
False,
140-
1.0,
141+
FloatPointValue.create(
142+
timestamp_ms=int((NOW + timedelta(seconds=int(step))).timestamp()) * 1000,
143+
step=step,
144+
value=series[int(step)],
145+
is_preview=False,
146+
completion_ratio=1.0,
141147
)
142148
)
143149
limited = filtered[-tail_limit:] if tail_limit is not None else filtered

tests/fuzzy/data_generators.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@ def float_point_values(draw, *, min_size: int = 0, max_size: Optional[int] = Non
9393
preview_completion_list = [1.0] * size
9494

9595
return [
96-
(
97-
timestamp_millis,
98-
step,
99-
value,
100-
preview,
101-
preview_completion,
96+
FloatPointValue.create(
97+
timestamp_ms=timestamp_millis,
98+
step=step,
99+
value=value,
100+
is_preview=preview,
101+
completion_ratio=preview_completion,
102102
)
103103
for timestamp_millis, step, value, preview, preview_completion in zip(
104104
timestamp_millis_list, step_list, values_list, preview_list, preview_completion_list

tests/helpers/metrics.py

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from __future__ import annotations
22

3+
from dataclasses import dataclass
34
from typing import (
45
Mapping,
56
Sequence,
6-
Tuple,
77
Union,
88
)
99

@@ -12,44 +12,107 @@
1212
from neptune_query.internal.identifiers import RunAttributeDefinition
1313
from neptune_query.internal.retrieval.metrics import MetricValues
1414

15-
FloatPointValue = Tuple[float, float, float, bool, float]
15+
16+
@dataclass(frozen=True)
17+
class FloatPointValue:
18+
timestamp_ms: float | None
19+
step: float | None
20+
value: float | None
21+
is_preview: bool | None = None
22+
completion_ratio: float | None = None
23+
24+
@classmethod
25+
def create(
26+
cls,
27+
step: float | None,
28+
value: float | None,
29+
*,
30+
timestamp_ms: float | None = None,
31+
is_preview: bool | None = None,
32+
completion_ratio: float | None = None,
33+
) -> "FloatPointValue":
34+
return cls(
35+
timestamp_ms=timestamp_ms,
36+
step=step,
37+
value=value,
38+
is_preview=is_preview,
39+
completion_ratio=completion_ratio,
40+
)
41+
42+
def as_tuple(self) -> tuple[object, ...]:
43+
return self.timestamp_ms, self.step, self.value, self.is_preview, self.completion_ratio
44+
45+
def __iter__(self):
46+
return iter(self.as_tuple())
47+
48+
def __getitem__(self, index: int) -> object:
49+
return self.as_tuple()[index]
50+
51+
def __len__(self) -> int:
52+
return len(self.as_tuple())
53+
54+
def has_timestamp(self) -> bool:
55+
return self.timestamp_ms is not None
56+
57+
def has_preview_data(self) -> bool:
58+
return self.is_preview is not None and self.completion_ratio is not None
1659

1760

1861
def to_metric_values(points: Sequence[FloatPointValue]) -> MetricValues:
1962
size = len(points)
20-
include_timestamp = any(len(point) > 0 and point[0] is not None for point in points)
21-
include_preview = any(len(point) >= 4 for point in points)
63+
include_timestamp = any(point.has_timestamp() for point in points)
64+
include_preview = any(point.has_preview_data() for point in points)
2265

2366
metric_values = MetricValues.allocate(
2467
size=size, include_timestamp=include_timestamp, include_preview=include_preview
2568
)
2669

2770
for idx, point in enumerate(points):
28-
timestamp = point[0] if len(point) > 0 else None
29-
step = point[1] if len(point) > 1 else np.nan
30-
value = point[2] if len(point) > 2 else np.nan
31-
preview = point[3] if len(point) > 3 else False
32-
completion_ratio = point[4] if len(point) > 4 else 1.0
33-
34-
metric_values.steps[idx] = float(step) if step is not None else np.nan
35-
metric_values.values[idx] = float(value) if value is not None else np.nan
71+
metric_values.steps[idx] = float(point.step)
72+
metric_values.values[idx] = float(point.value)
3673

3774
if metric_values.timestamps is not None:
38-
metric_values.timestamps[idx] = float(timestamp) if timestamp is not None else np.nan
75+
metric_values.timestamps[idx] = float(point.timestamp_ms) if point.timestamp_ms is not None else np.nan
3976

4077
if metric_values.is_preview is not None:
41-
metric_values.is_preview[idx] = bool(preview)
78+
metric_values.is_preview[idx] = bool(point.is_preview) if point.is_preview is not None else False
4279

4380
if metric_values.completion_ratio is not None:
44-
metric_values.completion_ratio[idx] = float(completion_ratio) if completion_ratio is not None else np.nan
81+
metric_values.completion_ratio[idx] = (
82+
float(point.completion_ratio) if point.completion_ratio is not None else 1.0
83+
)
4584

4685
return metric_values
4786

4887

4988
def normalize_metrics_data(
50-
metrics_data: Mapping[RunAttributeDefinition, Union[MetricValues, Sequence[FloatPointValue]]],
89+
metrics_data: Mapping[
90+
RunAttributeDefinition,
91+
Union[MetricValues, Sequence[FloatPointValue]],
92+
],
5193
) -> dict[RunAttributeDefinition, MetricValues]:
5294
return {
5395
definition: value if isinstance(value, MetricValues) else to_metric_values(value)
5496
for definition, value in metrics_data.items()
5597
}
98+
99+
100+
def assert_metric_mappings_equal(
101+
actual: Mapping[RunAttributeDefinition, MetricValues],
102+
expected: Mapping[RunAttributeDefinition, MetricValues],
103+
) -> None:
104+
actual_keys = set(actual.keys())
105+
expected_keys = set(expected.keys())
106+
107+
if actual_keys != expected_keys:
108+
missing = expected_keys - actual_keys
109+
unexpected = actual_keys - expected_keys
110+
raise AssertionError(f"Metric definitions mismatch. Missing: {missing}, unexpected: {unexpected}")
111+
112+
for definition in expected_keys:
113+
actual_values = actual[definition]
114+
expected_values = expected[definition]
115+
if actual_values != expected_values:
116+
raise AssertionError(
117+
"Metric values differ for " f"{definition}: actual={actual_values!r}, expected={expected_values!r}"
118+
)

tests/performance/generate.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ def random_alnum_strings(count: int, length: int) -> list[str]:
2626

2727

2828
def float_point_value(i: int, exp: int) -> FloatPointValue:
29-
return (1234567890 + i * 1000.0, float(i) + exp, float(i) * 10, False, 1.0)
29+
return FloatPointValue.create(
30+
timestamp_ms=1234567890 + i * 1000.0,
31+
step=float(i) + exp,
32+
value=float(i) * 10,
33+
is_preview=False,
34+
completion_ratio=1.0,
35+
)
3036

3137

3238
EXPERIMENT_IDENTIFIER = RunIdentifier(ProjectIdentifier("project/abc"), SysId("XXX-1"))

tests/performance/test_perf_output_format.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
create_series_dataframe,
77
)
88
from neptune_query.internal.retrieval.series import SeriesValue
9-
from tests.helpers.metrics import normalize_metrics_data
9+
from tests.helpers.metrics import (
10+
FloatPointValue,
11+
normalize_metrics_data,
12+
)
1013

1114
from . import generate
1215
from .decorator import expected_benchmark
@@ -42,8 +45,14 @@ def test_perf_create_metrics_dataframe(benchmark, num_experiments, num_steps, nu
4245
for path in range(num_paths):
4346
run_attr_def = generate.run_attribute_definition(exp, path)
4447
metrics_data[run_attr_def] = [
45-
(None, float(step), float(step * exp), False, 1.0)
46-
for step in range(num_steps) # FloatPointValue as tuple
48+
FloatPointValue.create(
49+
timestamp_ms=None,
50+
step=float(step),
51+
value=float(step * exp),
52+
is_preview=False,
53+
completion_ratio=1.0,
54+
)
55+
for step in range(num_steps)
4756
]
4857

4958
sys_id_label_mapping = {SysId(f"sysid{exp}"): f"exp{exp}" for exp in range(num_experiments)}

0 commit comments

Comments
 (0)