Skip to content

Commit 9a560d1

Browse files
authored
Feat(alerts): add batch alert actions (#364)
* add batch alert actions * rename batch actions * unify alert model with another PR
1 parent 30ccf88 commit 9a560d1

5 files changed

Lines changed: 227 additions & 0 deletions

File tree

lago_python_client/models/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
from .activity_log import ActivityLog as ActivityLog, ActivityLogResponse as ActivityLogResponse
2+
from .alert import (
3+
Alert as Alert,
4+
AlertsList as AlertsList,
5+
AlertThreshold as AlertThreshold,
6+
AlertThresholdList as AlertThresholdList,
7+
AlertResponse as AlertResponse,
8+
AlertsResponseList as AlertsResponseList,
9+
AlertThresholdResponse as AlertThresholdResponse,
10+
AlertThresholdResponseList as AlertThresholdResponseList,
11+
)
212
from .api_log import ApiLog as ApiLog, ApiLogResponse as ApiLogResponse
313
from .applied_coupon import AppliedCoupon as AppliedCoupon
414
from .billable_metric import (

lago_python_client/models/alert.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from typing import List, Optional
2+
3+
from ..base_model import BaseModel, BaseResponseModel
4+
from .billable_metric import BillableMetricResponse
5+
6+
7+
class AlertThreshold(BaseModel):
8+
code: Optional[str]
9+
value: str
10+
recurring: Optional[bool]
11+
12+
13+
class AlertThresholdList(BaseModel):
14+
__root__: List[AlertThreshold]
15+
16+
17+
class Alert(BaseModel):
18+
alert_type: str
19+
code: str
20+
name: Optional[str]
21+
billable_metric_code: Optional[str]
22+
thresholds: AlertThresholdList
23+
24+
25+
class AlertsList(BaseModel):
26+
alerts: List[Alert]
27+
28+
29+
class AlertThresholdResponse(BaseResponseModel):
30+
code: Optional[str]
31+
value: str
32+
recurring: bool
33+
34+
35+
class AlertThresholdResponseList(BaseResponseModel):
36+
__root__: List[AlertThresholdResponse]
37+
38+
39+
class AlertResponse(BaseResponseModel):
40+
lago_id: str
41+
lago_organization_id: str
42+
external_subscription_id: str
43+
alert_type: str
44+
code: str
45+
name: Optional[str]
46+
previous_value: Optional[str]
47+
last_processed_at: Optional[str]
48+
thresholds: AlertThresholdResponseList
49+
created_at: str
50+
billable_metric: Optional[BillableMetricResponse]
51+
52+
53+
class AlertsResponseList(BaseResponseModel):
54+
alerts: List[AlertResponse]

lago_python_client/subscriptions/clients.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,24 @@
88
FindCommandMixin,
99
UpdateCommandMixin,
1010
)
11+
from ..models.alert import AlertResponse, AlertsList
1112
from ..models.fixed_charge import FixedChargeResponse
1213
from ..models.lifetime_usage import LifetimeUsageResponse
1314
from ..models.subscription import SubscriptionResponse
1415
from ..services.request import (
1516
make_headers,
1617
make_url,
18+
send_delete_request,
1719
send_get_request,
20+
send_post_request,
1821
send_put_request,
1922
)
2023
from ..services.response import (
2124
Response,
2225
get_response_data,
2326
prepare_index_response,
2427
prepare_object_response,
28+
verify_response,
2529
)
2630
from ..services.json import to_json
2731

@@ -71,6 +75,32 @@ def update_lifetime_usage(
7175
data=get_response_data(response=api_response, key="lifetime_usage"),
7276
)
7377

78+
def create_alerts(self, external_id: str, input_object: AlertsList) -> Mapping[str, Any]:
79+
api_response: Response = send_post_request(
80+
url=make_url(
81+
origin=self.base_url,
82+
path_parts=(self.API_RESOURCE, external_id, "alerts"),
83+
),
84+
content=to_json(input_object.dict()),
85+
headers=make_headers(api_key=self.api_key),
86+
)
87+
88+
return prepare_index_response(
89+
api_resource="alerts",
90+
response_model=AlertResponse,
91+
data=get_response_data(response=api_response),
92+
)
93+
94+
def delete_alerts(self, external_id: str) -> None:
95+
api_response: Response = send_delete_request(
96+
url=make_url(
97+
origin=self.base_url,
98+
path_parts=(self.API_RESOURCE, external_id, "alerts"),
99+
),
100+
headers=make_headers(api_key=self.api_key),
101+
)
102+
verify_response(api_response)
103+
74104
def find_all_fixed_charges(self, external_id: str) -> Mapping[str, Any]:
75105
api_response: Response = send_get_request(
76106
url=make_url(
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"alerts": [
3+
{
4+
"lago_id": "1a901a90-1a90-1a90-1a90-1a901a901a90",
5+
"lago_organization_id": "2b902b90-2b90-2b90-2b90-2b902b902b90",
6+
"external_subscription_id": "sub_1234",
7+
"alert_type": "current_usage_amount",
8+
"code": "alert1",
9+
"name": "First Alert",
10+
"previous_value": "0.0",
11+
"last_processed_at": null,
12+
"thresholds": [
13+
{
14+
"code": "warn",
15+
"value": "1000.0",
16+
"recurring": false
17+
}
18+
],
19+
"created_at": "2025-03-20T10:00:00Z"
20+
},
21+
{
22+
"lago_id": "3c903c90-3c90-3c90-3c90-3c903c903c90",
23+
"lago_organization_id": "2b902b90-2b90-2b90-2b90-2b902b902b90",
24+
"external_subscription_id": "sub_1234",
25+
"alert_type": "billable_metric_current_usage_amount",
26+
"code": "alert2",
27+
"name": "Second Alert",
28+
"previous_value": "0.0",
29+
"last_processed_at": null,
30+
"thresholds": [
31+
{
32+
"code": null,
33+
"value": "2000.0",
34+
"recurring": false
35+
}
36+
],
37+
"created_at": "2025-03-20T10:00:00Z"
38+
}
39+
]
40+
}

tests/test_subscription_client.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from lago_python_client.client import Client
77
from lago_python_client.exceptions import LagoApiError
88
from lago_python_client.models import Subscription
9+
from lago_python_client.models.alert import Alert, AlertThreshold, AlertsList
910

1011

1112
def create_subscription():
@@ -400,3 +401,95 @@ def test_fixed_charge_response_model_parsing():
400401
assert response.properties.graduated_ranges[0].from_value == 0
401402
assert response.properties.graduated_ranges[0].per_unit_amount == "100"
402403
assert response.properties.graduated_ranges[1].to_value is None
404+
405+
406+
def mock_subscription_alerts_response():
407+
this_dir = os.path.dirname(os.path.abspath(__file__))
408+
my_data_path = os.path.join(this_dir, "fixtures/subscription_alerts.json")
409+
410+
with open(my_data_path, "rb") as alerts_response:
411+
return alerts_response.read()
412+
413+
414+
def test_valid_create_alerts_request(httpx_mock: HTTPXMock):
415+
client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d")
416+
external_id = "sub_1234"
417+
418+
httpx_mock.add_response(
419+
method="POST",
420+
url="https://api.getlago.com/api/v1/subscriptions/" + external_id + "/alerts",
421+
content=mock_subscription_alerts_response(),
422+
)
423+
424+
input_object = AlertsList(
425+
alerts=[
426+
Alert(
427+
alert_type="current_usage_amount",
428+
code="alert1",
429+
name="First Alert",
430+
thresholds=[AlertThreshold(code="warn", value="1000")],
431+
),
432+
Alert(
433+
alert_type="billable_metric_current_usage_amount",
434+
code="alert2",
435+
billable_metric_code="storage",
436+
thresholds=[AlertThreshold(value="2000")],
437+
),
438+
]
439+
)
440+
441+
response = client.subscriptions.create_alerts(external_id, input_object)
442+
443+
assert len(response["alerts"]) == 2
444+
assert response["alerts"][0].lago_id == "1a901a90-1a90-1a90-1a90-1a901a901a90"
445+
assert response["alerts"][0].code == "alert1"
446+
assert response["alerts"][0].alert_type == "current_usage_amount"
447+
assert response["alerts"][1].lago_id == "3c903c90-3c90-3c90-3c90-3c903c903c90"
448+
assert response["alerts"][1].code == "alert2"
449+
450+
451+
def test_invalid_create_alerts_request(httpx_mock: HTTPXMock):
452+
client = Client(api_key="invalid")
453+
external_id = "sub_1234"
454+
455+
httpx_mock.add_response(
456+
method="POST",
457+
url="https://api.getlago.com/api/v1/subscriptions/" + external_id + "/alerts",
458+
status_code=422,
459+
content=b"",
460+
)
461+
462+
input_object = AlertsList(alerts=[])
463+
464+
with pytest.raises(LagoApiError):
465+
client.subscriptions.create_alerts(external_id, input_object)
466+
467+
468+
def test_valid_delete_alerts_request(httpx_mock: HTTPXMock):
469+
client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d")
470+
external_id = "sub_1234"
471+
472+
httpx_mock.add_response(
473+
method="DELETE",
474+
url="https://api.getlago.com/api/v1/subscriptions/" + external_id + "/alerts",
475+
status_code=200,
476+
content=b"",
477+
)
478+
479+
result = client.subscriptions.delete_alerts(external_id)
480+
assert result is None
481+
482+
483+
def test_invalid_delete_alerts_request(httpx_mock: HTTPXMock):
484+
client = Client(api_key="invalid")
485+
external_id = "invalid_sub"
486+
487+
httpx_mock.add_response(
488+
method="DELETE",
489+
url="https://api.getlago.com/api/v1/subscriptions/" + external_id + "/alerts",
490+
status_code=404,
491+
content=b"",
492+
)
493+
494+
with pytest.raises(LagoApiError):
495+
client.subscriptions.delete_alerts(external_id)

0 commit comments

Comments
 (0)