Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion s2-specs
Submodule s2-specs updated 1 files
+8 −5 s2/v1/openapi.json
4 changes: 4 additions & 0 deletions src/s2_sdk/_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,10 @@ async def account_metrics(
) -> list[types.Scalar | types.Accumulation | types.Gauge | types.Label]:
"""Get account metrics.

``estimated-billable-usage`` returns estimated USD by billing dimension
and as a ``total`` series. Supports hour (default) and day intervals,
aggregating finalized hours. Daily points may be partial.

Args:
set: Metric set to query.
start: Start of the time range (epoch seconds).
Expand Down
2 changes: 2 additions & 0 deletions src/s2_sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ class MetricUnit(_DocEnum):

BYTES = "bytes"
OPERATIONS = "operations"
USD = "usd"


class TimeseriesInterval(_DocEnum):
Expand All @@ -457,6 +458,7 @@ class AccountMetricSet(_DocEnum):

ACTIVE_BASINS = "active-basins"
ACCOUNT_OPS = "account-ops"
ESTIMATED_BILLABLE_USAGE = "estimated-billable-usage"


class BasinMetricSet(_DocEnum):
Expand Down
61 changes: 59 additions & 2 deletions tests/test_metrics_ops.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import json
import time
from unittest.mock import AsyncMock, patch

import pytest

Expand All @@ -9,13 +11,65 @@
BasinMetricSet,
Gauge,
Label,
MetricUnit,
S2Basin,
S2ServerError,
S2Stream,
Scalar,
StreamMetricSet,
TimeseriesInterval,
)
from s2_sdk._client import HttpClient, Response


@pytest.mark.parametrize(
"interval", [None, TimeseriesInterval.HOUR, TimeseriesInterval.DAY]
)
async def test_account_metrics_estimated_billable_usage(
interval: TimeseriesInterval | None,
):
start, end = 1_700_000_000, 1_700_003_600
response_interval = interval or TimeseriesInterval.HOUR
response = {
"values": [
{
"accumulation": {
"name": name,
"unit": "usd",
"interval": response_interval.value,
"values": [[start, 0.012345]],
}
}
for name in ["transfer.writes_standard", "total"]
]
}
request = AsyncMock(return_value=Response(200, json.dumps(response).encode()))
with patch.object(HttpClient, "unary_request", request):
async with S2("token") as s2:
metrics = await s2.account_metrics(
set=AccountMetricSet.ESTIMATED_BILLABLE_USAGE,
start=start,
end=end,
interval=interval,
)

params: dict[str, str | int] = {
"set": "estimated-billable-usage",
"start": start,
"end": end,
}
if interval is not None:
params["interval"] = interval.value
request.assert_awaited_once_with("GET", "/v1/metrics", params=params)
assert metrics == [
Accumulation(
name=name,
unit=MetricUnit.USD,
interval=response_interval,
values=[(start, 0.012345)],
)
for name in ["transfer.writes_standard", "total"]
]


def _default_range() -> tuple[int, int]:
Expand Down Expand Up @@ -155,9 +209,12 @@ async def test_account_metrics_invalid_time_ranges(self, s2: S2):
set=AccountMetricSet.ACTIVE_BASINS, start=now + 3600, end=now
)

async def test_account_metrics_all_sets(self, s2: S2):
async def test_account_metrics_operational_sets(self, s2: S2):
start, end = _default_range()
for metric_set in AccountMetricSet:
for metric_set in (
AccountMetricSet.ACTIVE_BASINS,
AccountMetricSet.ACCOUNT_OPS,
):
metrics = await s2.account_metrics(set=metric_set, start=start, end=end)
assert isinstance(metrics, list)

Expand Down
Loading