otava/grafana.py (79 lines of code) (raw):
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from dataclasses import asdict, dataclass
from datetime import datetime
from typing import List, Optional
import requests
from pytz import UTC
from requests.exceptions import HTTPError
@dataclass
class GrafanaConfig:
url: str
user: str
password: str
@dataclass
class GrafanaError(Exception):
message: str
@dataclass
class Annotation:
id: Optional[int]
time: datetime
text: str
tags: List[str]
class Grafana:
url: str
__user: str
__password: str
def __init__(self, grafana_conf: GrafanaConfig):
self.url = grafana_conf.url
self.__user = grafana_conf.user
self.__password = grafana_conf.password
def fetch_annotations(
self, start: Optional[datetime], end: Optional[datetime], tags: List[str] = None
) -> List[Annotation]:
"""
Reference:
- https://grafana.com/docs/grafana/latest/http_api/annotations/#find-annotations
"""
url = f"{self.url}api/annotations"
query_parameters = {}
if start is not None:
query_parameters["from"] = int(start.timestamp() * 1000)
if end is not None:
query_parameters["to"] = int(end.timestamp() * 1000)
if tags is not None:
query_parameters["tags"] = tags
try:
response = requests.get(
url=url, params=query_parameters, auth=(self.__user, self.__password)
)
response.raise_for_status()
json = response.json()
annotations = []
for annotation_json in json:
annotation = Annotation(
id=annotation_json["id"],
time=datetime.fromtimestamp(float(annotation_json["time"]) / 1000, tz=UTC),
text=annotation_json["text"],
tags=annotation_json["tags"],
)
annotations.append(annotation)
return annotations
except KeyError as err:
raise GrafanaError(f"Missing field {err.args[0]}")
except HTTPError as err:
raise GrafanaError(str(err))
def delete_annotations(self, *ids: int):
"""
Reference:
- https://grafana.com/docs/grafana/latest/http_api/annotations/#delete-annotation-by-id
"""
url = f"{self.url}api/annotations"
for annotation_id in ids:
annotation_url = f"{url}/{annotation_id}"
try:
response = requests.delete(url=annotation_url, auth=(self.__user, self.__password))
response.raise_for_status()
except HTTPError as err:
raise GrafanaError(str(err))
def create_annotations(self, *annotations: Annotation):
"""
Reference:
- https://grafana.com/docs/grafana/latest/http_api/annotations/#create-annotation
"""
try:
url = f"{self.url}api/annotations"
for annotation in annotations:
data = asdict(annotation)
data["time"] = int(annotation.time.timestamp() * 1000)
del data["id"]
response = requests.post(url=url, json=data, auth=(self.__user, self.__password))
response.raise_for_status()
except HTTPError as err:
raise GrafanaError(str(err))