diff --git a/CHANGELOG.md b/CHANGELOG.md index 34a2f97..d0008df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ -## 0.25.0 (August 19th, 2025) +## 0.25.0 (August 28th, 2025) ENHANCEMENTS: -* Add Usage Alerts (account): create/get/patch/delete/list; client-side validation; examples. Now accessible via alerts().usage. +* Adds support for new Alerts ## 0.24.0 (March 20th, 2025) diff --git a/examples/usage_alerts.py b/examples/usage_alerts.py index 729490a..19edfe1 100644 --- a/examples/usage_alerts.py +++ b/examples/usage_alerts.py @@ -28,7 +28,9 @@ # If no real API key is set, we'll get appropriate errors # This is just an example to show the usage pattern if not os.environ.get("NS1_APIKEY"): - print("Using a mock endpoint - for real usage, set the NS1_APIKEY environment variable") + print( + "Using a mock endpoint - for real usage, set the NS1_APIKEY environment variable" + ) # Usage Alerts API Examples diff --git a/ns1/__init__.py b/ns1/__init__.py index 0d00cc0..46a1726 100644 --- a/ns1/__init__.py +++ b/ns1/__init__.py @@ -242,7 +242,6 @@ def alerts(self): return ns1.rest.alerts.Alerts(self.config) - def billing_usage(self): """ Return a new raw REST interface to BillingUsage resources diff --git a/ns1/alerting/usage_alerts.py b/ns1/alerting/usage_alerts.py index debb93f..272d1d5 100644 --- a/ns1/alerting/usage_alerts.py +++ b/ns1/alerting/usage_alerts.py @@ -24,9 +24,9 @@ def _validate(name: str, subtype: str, alert_at_percent: int) -> None: class UsageAlertsAPI: """ Account-scoped usage alerts. Triggers when usage ≥ alert_at_percent. - - Server rules: - - Always type='account' + + Server rules: + - Always type='account' - data.alert_at_percent must be in 1..100 - PATCH must not include type/subtype - zone_names/notifier_list_ids may be empty ([]) diff --git a/ns1/rest/alerts.py b/ns1/rest/alerts.py index 07c00d2..c421489 100644 --- a/ns1/rest/alerts.py +++ b/ns1/rest/alerts.py @@ -16,53 +16,53 @@ class Alerts(resource.BaseResource): "record_ids", "zone_names", ] - + # Forward HTTP methods needed by UsageAlertsAPI def _get(self, path, params=None): """Forward GET requests to make_request""" # Fix path to start with /alerting/v1/ if needed - if path.startswith('/'): + if path.startswith("/"): path = path[1:] # Remove leading slash if not path.startswith("alerting/v1/"): # Alerting endpoints should have this prefix path = f"{self.ROOT}/{path.split('/')[-1]}" return self._make_request("GET", path, params=params) - + def _post(self, path, json=None): """Forward POST requests to make_request""" - if path.startswith('/'): + if path.startswith("/"): path = path[1:] # Remove leading slash if not path.startswith("alerting/v1/"): path = f"{self.ROOT}" return self._make_request("POST", path, body=json) - + def _patch(self, path, json=None): """Forward PATCH requests to make_request""" - if path.startswith('/'): + if path.startswith("/"): path = path[1:] # Remove leading slash if not path.startswith("alerting/v1/"): - parts = path.split('/') + parts = path.split("/") path = f"{self.ROOT}/{parts[-1]}" return self._make_request("PATCH", path, body=json) - + def _delete(self, path): """Forward DELETE requests to make_request""" - if path.startswith('/'): + if path.startswith("/"): path = path[1:] # Remove leading slash if not path.startswith("alerting/v1/"): - parts = path.split('/') + parts = path.split("/") path = f"{self.ROOT}/{parts[-1]}" return self._make_request("DELETE", path) - + def __init__(self, config): super(Alerts, self).__init__(config) self._usage_api = None - + @property def usage(self): """ Return interface to usage alerts operations - + :return: :py:class:`ns1.alerting.UsageAlertsAPI` """ if self._usage_api is None: diff --git a/tests/unit/test_alerts.py b/tests/unit/test_alerts.py index 716f754..a1ac9f9 100644 --- a/tests/unit/test_alerts.py +++ b/tests/unit/test_alerts.py @@ -85,6 +85,24 @@ def test_rest_alert_list(alerts_config): "data": {"min": 20, "max": 80}, }, ), + ( + "sso_alert", + "account", + "saml_certificate_expired", + "../alerting/v1/alerts", + { + "notifier_list_ids": ["6707da567cd4f300012cd7e4"], + }, + ), + ( + "redirect_alert", + "redirects", + "certificate_renewal_failed", + "../alerting/v1/alerts", + { + "notifier_list_ids": ["6707da567cd4f300012cd7e4"], + }, + ), ], ) def test_rest_alert_create( diff --git a/tests/unit/test_datasets.py b/tests/unit/test_datasets.py index c50de2d..1745152 100644 --- a/tests/unit/test_datasets.py +++ b/tests/unit/test_datasets.py @@ -28,7 +28,7 @@ def datasets_config(config): return config -@pytest.mark.parametrize("url", [("datasets")]) +@pytest.mark.parametrize("url", ["datasets"]) def test_rest_datasets_list(datasets_config, url): z = NS1(config=datasets_config).datasets() z._make_request = mock.MagicMock() @@ -62,7 +62,7 @@ def test_rest_dataset_retrieve(datasets_config, dtId, url): ) -@pytest.mark.parametrize("url", [("datasets")]) +@pytest.mark.parametrize("url", ["datasets"]) def test_rest_dataset_create(datasets_config, url): z = NS1(config=datasets_config).datasets() z._make_request = mock.MagicMock() diff --git a/tests/unit/test_usage_alerts.py b/tests/unit/test_usage_alerts.py index 40bdf3a..c0ebb9d 100644 --- a/tests/unit/test_usage_alerts.py +++ b/tests/unit/test_usage_alerts.py @@ -52,14 +52,14 @@ def test_create_usage_alert(usage_alerts_client): # Get the usage API and directly set its client usage_api = client.alerts().usage usage_api._c = client - + # Make the API call alert = usage_api.create( - name="Test Alert", - subtype="query_usage", - alert_at_percent=85, - notifier_list_ids=["n1"], - ) + name="Test Alert", + subtype="query_usage", + alert_at_percent=85, + notifier_list_ids=["n1"], + ) # Verify _post was called with correct arguments expected_body = { @@ -102,7 +102,7 @@ def test_get_usage_alert(usage_alerts_client): # Get the usage API and directly set its client usage_api = client.alerts().usage usage_api._c = client - + # Make the API call alert = usage_api.get(alert_id) @@ -135,7 +135,7 @@ def test_patch_usage_alert(usage_alerts_client): # Get the usage API and directly set its client usage_api = client.alerts().usage usage_api._c = client - + # Make the API call alert = usage_api.patch( alert_id, name="Updated Alert", alert_at_percent=90 @@ -169,7 +169,7 @@ def test_delete_usage_alert(usage_alerts_client): # Get the usage API and directly set its client usage_api = client.alerts().usage usage_api._c = client - + # Make the API call usage_api.delete(alert_id) @@ -201,7 +201,7 @@ def test_list_usage_alerts(usage_alerts_client): # Get the usage API and directly set its client usage_api = client.alerts().usage usage_api._c = client - + # Make the API call response = usage_api.list(limit=1, order_descending=True)