Skip to content

Commit 670966a

Browse files
author
brandon
committed
Add method for monthly usage
1 parent f01f560 commit 670966a

File tree

2 files changed

+257
-0
lines changed

2 files changed

+257
-0
lines changed

src/groundlight/client.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from groundlight_openapi_client.api.detectors_api import DetectorsApi
1313
from groundlight_openapi_client.api.image_queries_api import ImageQueriesApi
1414
from groundlight_openapi_client.api.labels_api import LabelsApi
15+
from groundlight_openapi_client.api.month_to_date_account_info_api import MonthToDateAccountInfoApi
1516
from groundlight_openapi_client.api.user_api import UserApi
1617
from groundlight_openapi_client.exceptions import NotFoundException, UnauthorizedException
1718
from groundlight_openapi_client.model.b_box_geometry_request import BBoxGeometryRequest
@@ -25,6 +26,7 @@
2526
from groundlight_openapi_client.model.status_enum import StatusEnum
2627
from model import (
2728
ROI,
29+
AccountMonthToDateInfo,
2830
BBoxGeometry,
2931
BinaryClassificationResult,
3032
Detector,
@@ -186,6 +188,7 @@ def __init__(
186188
self.image_queries_api = ImageQueriesApi(self.api_client)
187189
self.user_api = UserApi(self.api_client)
188190
self.labels_api = LabelsApi(self.api_client)
191+
self.month_to_date_api = MonthToDateAccountInfoApi(self.api_client)
189192
self.logged_in_user = "(not-logged-in)"
190193
self._verify_connectivity()
191194

@@ -625,6 +628,28 @@ def list_image_queries(
625628
image_queries.results = [self._fixup_image_query(iq) for iq in image_queries.results]
626629
return image_queries
627630

631+
def get_month_to_date_usage(self) -> AccountMonthToDateInfo:
632+
"""
633+
Get the account's month-to-date usage information including image queries (IQs),
634+
escalations, and active detectors with their respective limits.
635+
636+
**Example Usage:**
637+
638+
gl = Groundlight()
639+
640+
# Get month-to-date usage information
641+
usage = gl.get_month_to_date_usage()
642+
643+
# Access usage metrics
644+
print(f"Image queries used: {usage.iqs} / {usage.iqs_limit}")
645+
print(f"Escalations used: {usage.escalations} / {usage.escalations_limit}")
646+
print(f"Active detectors: {usage.active_detectors} / {usage.active_detectors_limit}")
647+
648+
:return: AccountMonthToDateInfo object containing usage metrics and limits
649+
"""
650+
obj = self.month_to_date_api.month_to_date_account_info(_request_timeout=DEFAULT_REQUEST_TIMEOUT)
651+
return AccountMonthToDateInfo.model_validate(obj.to_dict())
652+
628653
def submit_image_query( # noqa: PLR0913 # pylint: disable=too-many-arguments, too-many-locals
629654
self,
630655
detector: Union[Detector, str],
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import pytest
2+
from unittest.mock import Mock, patch
3+
from model import AccountMonthToDateInfo
4+
5+
6+
def test_get_month_to_date_usage_success(gl):
7+
"""Test successful retrieval of month-to-date usage information."""
8+
usage = gl.get_month_to_date_usage()
9+
10+
# Verify the return type
11+
assert isinstance(usage, AccountMonthToDateInfo)
12+
13+
# Verify all required fields are present and have correct types
14+
assert isinstance(usage.iqs, int)
15+
assert isinstance(usage.escalations, int)
16+
assert isinstance(usage.active_detectors, int)
17+
18+
# Handle case where limits might be None (API may not provide limits)
19+
if usage.iqs_limit is not None:
20+
assert isinstance(usage.iqs_limit, int)
21+
assert usage.iqs_limit >= 0
22+
if usage.escalations_limit is not None:
23+
assert isinstance(usage.escalations_limit, int)
24+
assert usage.escalations_limit >= 0
25+
if usage.active_detectors_limit is not None:
26+
assert isinstance(usage.active_detectors_limit, int)
27+
assert usage.active_detectors_limit >= 0
28+
29+
# Verify usage values are non-negative
30+
assert usage.iqs >= 0
31+
assert usage.escalations >= 0
32+
assert usage.active_detectors >= 0
33+
34+
35+
def test_get_month_to_date_usage_with_mock():
36+
"""Test get_month_to_date_usage with mocked API response."""
37+
from groundlight import Groundlight
38+
39+
# Create mock response data
40+
mock_response_data = {
41+
"iqs": 150,
42+
"escalations": 25,
43+
"active_detectors": 10,
44+
"iqs_limit": 1000,
45+
"escalations_limit": 100,
46+
"active_detectors_limit": 50,
47+
}
48+
49+
# Create mock API response object
50+
mock_api_response = Mock()
51+
mock_api_response.to_dict.return_value = mock_response_data
52+
53+
# Create mock month_to_date_api
54+
mock_month_to_date_api = Mock()
55+
mock_month_to_date_api.month_to_date_account_info.return_value = mock_api_response
56+
57+
# Create Groundlight instance and patch the month_to_date_api
58+
gl = Groundlight()
59+
gl.month_to_date_api = mock_month_to_date_api
60+
61+
# Call the method
62+
usage = gl.get_month_to_date_usage()
63+
64+
# Verify the API was called correctly
65+
mock_month_to_date_api.month_to_date_account_info.assert_called_once_with(
66+
_request_timeout=10 # DEFAULT_REQUEST_TIMEOUT
67+
)
68+
69+
# Verify the response parsing
70+
assert isinstance(usage, AccountMonthToDateInfo)
71+
assert usage.iqs == 150
72+
assert usage.escalations == 25
73+
assert usage.active_detectors == 10
74+
assert usage.iqs_limit == 1000
75+
assert usage.escalations_limit == 100
76+
assert usage.active_detectors_limit == 50
77+
78+
79+
def test_get_month_to_date_usage_api_error():
80+
"""Test get_month_to_date_usage when API returns an error."""
81+
from groundlight import Groundlight
82+
from groundlight_openapi_client.exceptions import ApiException
83+
84+
# Create mock month_to_date_api that raises an exception
85+
mock_month_to_date_api = Mock()
86+
mock_month_to_date_api.month_to_date_account_info.side_effect = ApiException(
87+
status=500, reason="Internal Server Error"
88+
)
89+
90+
# Create Groundlight instance and patch the month_to_date_api
91+
gl = Groundlight()
92+
gl.month_to_date_api = mock_month_to_date_api
93+
94+
# Verify the exception is raised
95+
with pytest.raises(ApiException) as exc_info:
96+
gl.get_month_to_date_usage()
97+
98+
assert exc_info.value.status == 500
99+
assert "Internal Server Error" in str(exc_info.value)
100+
101+
102+
def test_get_month_to_date_usage_unauthorized():
103+
"""Test get_month_to_date_usage when unauthorized."""
104+
from groundlight import Groundlight
105+
from groundlight_openapi_client.exceptions import UnauthorizedException
106+
107+
# Create mock month_to_date_api that raises an unauthorized exception
108+
mock_month_to_date_api = Mock()
109+
mock_month_to_date_api.month_to_date_account_info.side_effect = UnauthorizedException(
110+
status=401, reason="Unauthorized"
111+
)
112+
113+
# Create Groundlight instance and patch the month_to_date_api
114+
gl = Groundlight()
115+
gl.month_to_date_api = mock_month_to_date_api
116+
117+
# Verify the exception is raised
118+
with pytest.raises(UnauthorizedException) as exc_info:
119+
gl.get_month_to_date_usage()
120+
121+
assert exc_info.value.status == 401
122+
assert "Unauthorized" in str(exc_info.value)
123+
124+
125+
def test_get_month_to_date_usage_zero_values():
126+
"""Test get_month_to_date_usage with zero values (new account scenario)."""
127+
from groundlight import Groundlight
128+
129+
# Create mock response data with zero values
130+
mock_response_data = {
131+
"iqs": 0,
132+
"escalations": 0,
133+
"active_detectors": 0,
134+
"iqs_limit": 100,
135+
"escalations_limit": 10,
136+
"active_detectors_limit": 5,
137+
}
138+
139+
# Create mock API response object
140+
mock_api_response = Mock()
141+
mock_api_response.to_dict.return_value = mock_response_data
142+
143+
# Create mock month_to_date_api
144+
mock_month_to_date_api = Mock()
145+
mock_month_to_date_api.month_to_date_account_info.return_value = mock_api_response
146+
147+
# Create Groundlight instance and patch the month_to_date_api
148+
gl = Groundlight()
149+
gl.month_to_date_api = mock_month_to_date_api
150+
151+
# Call the method
152+
usage = gl.get_month_to_date_usage()
153+
154+
# Verify the response parsing with zero values
155+
assert isinstance(usage, AccountMonthToDateInfo)
156+
assert usage.iqs == 0
157+
assert usage.escalations == 0
158+
assert usage.active_detectors == 0
159+
assert usage.iqs_limit == 100
160+
assert usage.escalations_limit == 10
161+
assert usage.active_detectors_limit == 5
162+
163+
164+
def test_get_month_to_date_usage_usage_limits(gl):
165+
"""Test that usage values don't exceed their limits."""
166+
usage = gl.get_month_to_date_usage()
167+
168+
# Verify usage doesn't exceed limits (only if limits are provided)
169+
if usage.iqs_limit is not None:
170+
assert usage.iqs <= usage.iqs_limit
171+
if usage.escalations_limit is not None:
172+
assert usage.escalations <= usage.escalations_limit
173+
if usage.active_detectors_limit is not None:
174+
assert usage.active_detectors <= usage.active_detectors_limit
175+
176+
177+
def test_get_month_to_date_usage_documentation_example(gl):
178+
"""Test the example usage shown in the method's docstring."""
179+
usage = gl.get_month_to_date_usage()
180+
181+
# Test the example from the docstring
182+
print(f"Image queries used: {usage.iqs} / {usage.iqs_limit}")
183+
print(f"Escalations used: {usage.escalations} / {usage.escalations_limit}")
184+
print(f"Active detectors: {usage.active_detectors} / {usage.active_detectors_limit}")
185+
186+
# Verify the format is correct (this test mainly ensures the example runs without errors)
187+
assert isinstance(usage.iqs, int)
188+
# Handle case where limits might be None
189+
if usage.iqs_limit is not None:
190+
assert isinstance(usage.iqs_limit, int)
191+
if usage.escalations_limit is not None:
192+
assert isinstance(usage.escalations_limit, int)
193+
if usage.active_detectors_limit is not None:
194+
assert isinstance(usage.active_detectors_limit, int)
195+
assert isinstance(usage.escalations, int)
196+
assert isinstance(usage.active_detectors, int)
197+
198+
199+
@pytest.mark.integration
200+
def test_get_month_to_date_usage_integration(gl):
201+
"""Integration test for get_month_to_date_usage with real API call."""
202+
usage = gl.get_month_to_date_usage()
203+
204+
# Verify the response structure
205+
assert isinstance(usage, AccountMonthToDateInfo)
206+
207+
# Verify all fields are present
208+
assert hasattr(usage, "iqs")
209+
assert hasattr(usage, "escalations")
210+
assert hasattr(usage, "active_detectors")
211+
assert hasattr(usage, "iqs_limit")
212+
assert hasattr(usage, "escalations_limit")
213+
assert hasattr(usage, "active_detectors_limit")
214+
215+
# Verify data types
216+
assert isinstance(usage.iqs, int)
217+
assert isinstance(usage.escalations, int)
218+
assert isinstance(usage.active_detectors, int)
219+
220+
# Handle case where limits might be None
221+
if usage.iqs_limit is not None:
222+
assert isinstance(usage.iqs_limit, int)
223+
if usage.escalations_limit is not None:
224+
assert isinstance(usage.escalations_limit, int)
225+
if usage.active_detectors_limit is not None:
226+
assert isinstance(usage.active_detectors_limit, int)
227+
228+
# Verify logical constraints
229+
assert usage.iqs >= 0
230+
assert usage.escalations >= 0
231+
assert usage.active_detectors >= 0
232+
# Note: limits might be None, so we can't assert they're > 0

0 commit comments

Comments
 (0)