From 0cdc932dad21258f4e1cc0569206d1f49c2c577d Mon Sep 17 00:00:00 2001 From: Titouan Date: Fri, 17 Oct 2025 11:43:51 +0200 Subject: [PATCH] feat: Add support for `/series` endpoint --- prometheus_api_client/prometheus_connect.py | 36 +++++++++++++++++++++ tests/test_prometheus_connect.py | 34 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/prometheus_api_client/prometheus_connect.py b/prometheus_api_client/prometheus_connect.py index 018efeb..a589417 100644 --- a/prometheus_api_client/prometheus_connect.py +++ b/prometheus_api_client/prometheus_connect.py @@ -117,6 +117,42 @@ def all_metrics(self, params: dict = None): self._all_metrics = self.get_label_values(label_name="__name__", params=params) return self._all_metrics + + def get_series(self, start: datetime, end: datetime, params: dict = None): + """ + Get a list series happening between start and end times. + + :param start: (int) Start time unix ts + :param end: (int) End time unix ts + :param params: (dict) Optional dictionary containing GET parameters to be + sent along with the API request, such as "start", "end" or "match[]". + :returns: (list) A list of labels from the specified prometheus host + :raises: + (RequestException) Raises an exception in case of a connection error + (PrometheusApiClientException) Raises in case of non 200 response status code + """ + params = params or {} + params["start"] = start.timestamp() + params["end"] = end.timestamp() + response = self._session.get( + "{0}/api/v1/series".format(self.url), + verify=self._session.verify, + headers=self.headers, + params=params, + auth=self.auth, + cert=self._session.cert, + timeout=self._timeout, + ) + + if response.status_code == 200: + labels = response.json()["data"] + else: + raise PrometheusApiClientException( + "HTTP Status Code {} ({!r})".format(response.status_code, response.content) + ) + return labels + + def get_label_names(self, params: dict = None): """ Get a list of all labels. diff --git a/tests/test_prometheus_connect.py b/tests/test_prometheus_connect.py index 52c194f..edc82c3 100644 --- a/tests/test_prometheus_connect.py +++ b/tests/test_prometheus_connect.py @@ -139,6 +139,17 @@ def test_get_label_names_method(self): # noqa D102 self.assertEqual(len(labels), 3) self.assertEqual(labels, ["__name__", "instance", "job"]) + def test_get_series(self): # noqa D102 + start_time = datetime.now() - timedelta(hours=1) + end_time = datetime.now() + series = self.pc.get_series(start=start_time, end=end_time, params={"match[]": "up"}) + self.assertIsInstance(series, list) + self.assertTrue(len(series) > 0, "no series data received from prometheus") + # Verify that each series entry is a dict with labels + for series_entry in series: + self.assertIsInstance(series_entry, dict) + self.assertIn("__name__", series_entry) + def test_get_scrape_pools(self): # noqa D102 scrape_pools = self.pc.get_scrape_pools() self.assertIsInstance(scrape_pools, list) @@ -251,6 +262,10 @@ def test_broken_responses(self): # noqa D102 self.pc.get_label_values("label_name") self.assertEqual("HTTP Status Code 403 (b'BOOM!')", str(exc.exception)) + with self.assertRaises(PrometheusApiClientException) as exc: + self.pc.get_series(start=datetime.now() - timedelta(hours=1), end=datetime.now()) + self.assertEqual("HTTP Status Code 403 (b'BOOM!')", str(exc.exception)) + with self.assertRaises(PrometheusApiClientException) as exc: self.pc.get_current_metric_value("metric") self.assertEqual("HTTP Status Code 403 (b'BOOM!')", str(exc.exception)) @@ -284,6 +299,25 @@ def test_all_metrics_method(self): # noqa D102 request = handler.requests[0] self.assertEqual(request.path_url, "/api/v1/label/__name__/values") + + def test_get_series_method(self): # noqa D102 + series_payload = {"status": "success", "data": [ + {"__name__": "up", "job": "prometheus", "instance": "localhost:9090"}, + {"__name__": "up", "job": "node", "instance": "localhost:9100"} + ]} + + with self.mock_response(series_payload) as handler: + start_time = datetime.now() - timedelta(hours=1) + end_time = datetime.now() + result = self.pc.get_series(start=start_time, end=end_time) + self.assertTrue(len(result) > 0) + self.assertEqual(handler.call_count, 1) + request = handler.requests[0] + self.assertTrue(request.path_url.startswith("/api/v1/series")) + # Verify that start and end parameters are included + self.assertIn("start", request.url) + self.assertIn("end", request.url) + def test_get_label_names_method(self): # noqa D102 all_metrics_payload = {"status": "success", "data": ["value1", "value2"]}