Mini Shell
"""Functions for Retrieving data from graphite"""
from typing import Union, List, Optional, Tuple
from datetime import datetime
import urllib.parse
import requests
from arrow.arrow import Arrow
import arrow
QUERY_FMT = '%H:%M_%Y%m%d'
class GraphiteError(Exception):
"""Raised on error when fetching graphite data"""
class Graphite:
"""Performs Graphite Queries"""
def __init__(
self,
render_url: Optional[str] = 'https://graphite.imhadmin.net/render/',
timeout: Optional[Union[float, Tuple[float]]] = 600,
):
self.render_url = render_url
self.timeout = timeout
@staticmethod
def _params(
target: Union[str, List[str]],
start: Optional[Union[datetime, Arrow, str, int]] = None,
stop: Optional[Union[datetime, Arrow, str, int]] = None,
tzname: Optional[str] = None,
) -> dict:
# int -> arrow
if isinstance(start, int):
start = arrow.get(start)
if isinstance(stop, int):
stop = arrow.get(stop)
# format datetimes
if isinstance(start, (datetime, Arrow)):
if tzname is None: # if tzname was not overridden
tzname = start.tzname()
start = start.strftime(QUERY_FMT)
if isinstance(stop, (datetime, Arrow)):
stop = stop.strftime(QUERY_FMT)
# graphite's pytz may be old
if tzname in ('PDT', 'PST'):
tzname = 'America/Los_Angeles'
if tzname in ('EST', 'EDT'):
tzname = 'America/New_York'
params = {'target': target}
if start is not None:
params['from'] = start
if stop is not None:
params['until'] = stop
if tzname is not None:
params['tz'] = tzname
return params
def query(
self,
target: Union[str, List[str]],
start: Optional[Union[datetime, Arrow, str, int]] = None,
stop: Optional[Union[datetime, Arrow, str, int]] = None,
tzname: Optional[str] = None,
) -> list:
"""Query data from graphite
Args:
target (str or list): graphite bucket(s) to fetch data for
start: start time to fetch data for. If not supplied, it'll
be 24 hours ago **(see note)**
stop: stop time to fetch data for. If not supplied, it'll be now
**(see note)**
tzname: timezone to use. If not supplied, the ``start`` arg's
timezone will be used. If the ``start`` arg is not timezone
aware, it'll use graphite defaults
Raises:
GraphiteError: on any error
Note:
``start`` and ``stop`` will accept ``arrow`` objects, ``datetimes``,
``int`` for unix timestamps, and ``str``. If it's a ``str``, it'll
be sent as a literal and handled per graphite's documentation
https://graphite.readthedocs.io/en/latest/render_api.html
"""
params = self._params(target, start, stop, tzname)
params['format'] = 'json'
try:
req = requests.get(
self.render_url,
timeout=self.timeout,
params=params,
)
except requests.RequestException as exc:
raise GraphiteError(exc) from exc
try:
return req.json()
except ValueError as exc:
if req.status_code != 200:
raise GraphiteError(
f"{req.url}\nHTTP {req.status_code}: {req.reason}"
) from exc
raise GraphiteError(
f"{req.url}\n{type(exc).__name__}: {exc}"
) from exc
def query_dict(
self,
target: Union[str, List[str]],
start: Optional[Union[datetime, Arrow, str, int]] = None,
stop: Optional[Union[datetime, Arrow, str, int]] = None,
tzname: Optional[str] = None,
) -> dict:
"""Just like ``query`` except it converts the data to a dict
Args:
target (str or list): graphite bucket(s) to fetch data for
start: start time to fetch data for. If not supplied, it'll
be 24 hours ago **(see note)**
stop: stop time to fetch data for. If not supplied, it'll be now
**(see note)**
tzname: timezone to use. If not supplied, the ``start`` arg's
timezone will be used. If the ``start`` arg is not timezone
aware, it'll use graphite defaults
Raises:
GraphiteError: on any error
Note:
``start`` and ``stop`` will accept ``arrow`` objects, ``datetimes``,
``int`` for unix timestamps, and ``str``. If it's a ``str``, it'll
be sent as a literal and handled per graphite's documentation
https://graphite.readthedocs.io/en/latest/render_api.html
"""
return {
x['target']: x['datapoints']
for x in self.query(target, start, stop, tzname)
}
def img_url(
self,
target: Union[str, List[str]],
start: Optional[Union[datetime, Arrow, str, int]] = None,
stop: Optional[Union[datetime, Arrow, str, int]] = None,
tzname: Optional[str] = None,
**kwargs,
) -> str:
"""Gets a graphite render URL for an image but does not fetch it
Args:
target (str or list): graphite bucket(s) to fetch data for
start: start time to fetch data for. If not supplied, it'll
be 24 hours ago **(see note)**
stop: stop time to fetch data for. If not supplied, it'll be now
**(see note)**
tzname: timezone to use. If not supplied, the ``start`` arg's
timezone will be used. If the ``start`` arg is not timezone
aware, it'll use graphite defaults
Raises:
GraphiteError: on any error
Note:
``start`` and ``stop`` will accept ``arrow`` objects, ``datetimes``,
``int`` for unix timestamps, and ``str``. If it's a ``str``, it'll
be sent as a literal and handled per graphite's documentation
https://graphite.readthedocs.io/en/latest/render_api.html
"""
params = self._params(target, start, stop, tzname)
params.update(kwargs)
args = urllib.parse.urlencode(query=params, doseq=True)
return f'{self.render_url}?{args}'
Zerion Mini Shell 1.0