syvain-metrics-api-client 0.0.322__tar.gz → 0.0.332__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: syvain-metrics-api-client
3
- Version: 0.0.322
3
+ Version: 0.0.332
4
4
  Summary: Typed Python client for the Syvain Metrics REST API
5
5
  Requires-Dist: niquests>=3.16.0
6
6
  Requires-Dist: pydantic>=2.13.3
@@ -79,7 +79,11 @@ every metric in an experiment. In a metadata filter, omit `value` to select
79
79
  every series that contains the key.
80
80
 
81
81
  Set `stream=True` to parse an NDJSON response incrementally. The method still
82
- returns a complete `list[MetricSeriesColumnBatch]`.
82
+ returns a complete `list[MetricSeriesColumnBatch]`. It requests
83
+ `application/vnd.syvain.metric-series+ndjson` and verifies the final completion
84
+ record against the received batch and row counts. A stream that ends before
85
+ completion raises `MetricsApiRequestError`; partial results are not returned.
86
+ Deploy the API support for this media type before upgrading streaming clients.
83
87
 
84
88
  ## Authentication
85
89
 
@@ -65,7 +65,11 @@ every metric in an experiment. In a metadata filter, omit `value` to select
65
65
  every series that contains the key.
66
66
 
67
67
  Set `stream=True` to parse an NDJSON response incrementally. The method still
68
- returns a complete `list[MetricSeriesColumnBatch]`.
68
+ returns a complete `list[MetricSeriesColumnBatch]`. It requests
69
+ `application/vnd.syvain.metric-series+ndjson` and verifies the final completion
70
+ record against the received batch and row counts. A stream that ends before
71
+ completion raises `MetricsApiRequestError`; partial results are not returned.
72
+ Deploy the API support for this media type before upgrading streaming clients.
69
73
 
70
74
  ## Authentication
71
75
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "syvain-metrics-api-client"
3
- version = "0.0.322"
3
+ version = "0.0.332"
4
4
  description = "Typed Python client for the Syvain Metrics REST API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "syvain-metrics-api-client"
3
- version = "0.0.322"
3
+ version = "0.0.332"
4
4
  description = "Typed Python client for the Syvain Metrics REST API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -46,6 +46,7 @@ from syvain_metrics_api_client.types import (
46
46
  MetricsCatalogQueryItem,
47
47
  TrackingExperimentStatus,
48
48
  MetricSeriesColumnBatch,
49
+ MetricSeriesStreamCompletion,
49
50
  SeriesQueryOrder,
50
51
  SeriesQueryRequest,
51
52
  SeriesQuerySelection,
@@ -954,7 +955,8 @@ class SyvainMetricsApiClient:
954
955
  that experiment.
955
956
  order: Axis and direction used to order each returned batch.
956
957
  stream: Read an NDJSON response when true. This reduces response
957
- buffering but still returns a complete list.
958
+ buffering but still returns a complete list. Requires the API
959
+ completion record and rejects truncated responses.
958
960
 
959
961
  Returns:
960
962
  Column batches grouped by exact series identity.
@@ -1006,14 +1008,40 @@ class SyvainMetricsApiClient:
1006
1008
  use_idempotency_key=False,
1007
1009
  exclude_none_payload=True,
1008
1010
  stream_response=True,
1009
- accept="application/x-ndjson",
1011
+ accept="application/vnd.syvain.metric-series+ndjson",
1010
1012
  )
1011
1013
  batches: list[MetricSeriesColumnBatch] = []
1014
+ completion: MetricSeriesStreamCompletion | None = None
1015
+ row_count = 0
1016
+ frame_adapter = TypeAdapter(
1017
+ MetricSeriesColumnBatch | MetricSeriesStreamCompletion
1018
+ )
1012
1019
  try:
1013
1020
  for line in response.iter_lines():
1014
1021
  if not line:
1015
1022
  continue
1016
- batches.append(MetricSeriesColumnBatch.model_validate_json(line))
1023
+ if completion is not None:
1024
+ raise MetricsApiResponseError(
1025
+ "Metrics API returned data after stream completion."
1026
+ )
1027
+ frame = frame_adapter.validate_json(line)
1028
+ if isinstance(frame, MetricSeriesStreamCompletion):
1029
+ completion = frame
1030
+ else:
1031
+ batches.append(frame)
1032
+ row_count += len(frame.data.value)
1033
+ if completion is None:
1034
+ raise MetricsApiRequestError(
1035
+ "Metrics API stream ended without a completion record.",
1036
+ retryable=True,
1037
+ )
1038
+ if (
1039
+ completion.batch_count != len(batches)
1040
+ or completion.row_count != row_count
1041
+ ):
1042
+ raise MetricsApiResponseError(
1043
+ "Metrics API stream completion counts do not match received data."
1044
+ )
1017
1045
  except ValidationError as error:
1018
1046
  raise MetricsApiResponseError(
1019
1047
  "Metrics API response for query_metric_series did not match "
@@ -418,6 +418,16 @@ class SeriesQueryRequest(BaseModel):
418
418
  stream: bool = False
419
419
 
420
420
 
421
+ class MetricSeriesStreamCompletion(BaseModel):
422
+ """Required final record in the negotiated metric-series stream."""
423
+
424
+ model_config = ConfigDict(extra="forbid", strict=True)
425
+
426
+ kind: Literal["complete"]
427
+ batch_count: int = Field(alias="batchCount", ge=0)
428
+ row_count: int = Field(alias="rowCount", ge=0)
429
+
430
+
421
431
  class MetricSeriesDataColumns(BaseModel):
422
432
  """Parallel step, timestamp, and value columns for one metric series."""
423
433
 
@@ -427,7 +437,7 @@ class MetricSeriesDataColumns(BaseModel):
427
437
  allow_inf_nan=False,
428
438
  )
429
439
 
430
- steps: list[float]
440
+ steps: list[float | None]
431
441
  timestamp: list[float]
432
442
  value: list[float]
433
443