bumblebee-status/bumblebee/modules/spaceapi.py
Tobias Manske f53cd062ae
[modules/spaceapi] Performance optimization - JSON
The module now flattens the JSON structure when it is received from the
API endpoint instead of every time the statusbar is updated. This should
make the module much more performant.

Signed-off-by: Tobias Manske <tobias.manske@mailbox.org>
2019-01-17 01:03:11 +01:00

150 lines
4.5 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pylint: disable=C0111,R0903
"""Displays the state of a Space API endpoint
Space API is an API for hackspaces based on JSON. See spaceapi.io for
an example.
Requires the following libraries:
* requests
* regex
Parameters:
* spaceapi.url: String representation of the api endpoint
* spaceapi.format: Format string for the output
Format Strings:
* Format strings are indicated by double %%
* They represent a leaf in the JSON tree, layers seperated by "."
* Boolean values can be overwritten by appending "%true%false"
in the format string
* Example: to reference "open" in "{"state":{"open": true}}"
you would write "%%state.open%%", if you also want
to say "Open/Closed" depending on the boolean you
would write "%%state.open%Open%Closed%%"
"""
import bumblebee.input
import bumblebee.output
import bumblebee.engine
import requests
import threading
import re
import json
def formatStringBuilder(s, json):
"""
Parses Format Strings
Parameter:
s -> format string
json -> the spaceapi response object
"""
identifiers = re.findall("%%.*?%%", s)
for i in identifiers:
ic = i[2:-2] # Discard %%
j = ic.split("%")
# Only neither of, or both true AND false may be overwritten
if len(j) != 3 and len(j) != 1:
return "INVALID FORMAT STRING"
if len(j) == 1: # no overwrite
s = s.replace(i, json[j[0]])
elif json[j[0]]: # overwrite for True
s = s.replace(i, j[1])
else: # overwrite for False
s = s.replace(i, j[2])
return s
class Module(bumblebee.engine.Module):
def __init__(self, engine, config):
super(Module, self).__init__(
engine, config, bumblebee.output.Widget(full_text=self.getState)
)
engine.input.register_callback(
self, button=bumblebee.input.LEFT_MOUSE, cmd=self.__forceReload
)
self._data = {}
self._error = None
self._threadingCount = 0
# The URL representing the api endpoint
self._url = self.parameter("url", default="http://club.entropia.de/spaceapi")
self._format = self.parameter(
"format", default=u"%%space%%: %%state.open%Open%Closed%%"
)
def state(self, widget):
try:
if self._error is not None:
return ["critical"]
elif self._data["state.open"]:
return ["warning"]
else:
return []
except KeyError:
return ["critical"]
def update(self, widgets):
if self._threadingCount == 0:
thread = threading.Thread(target=self.get_api_async, args=())
thread.start()
self._threadingCount = (
0 if self._threadingCount > 300 else self._threadingCount + 1
)
def getState(self, widget):
text = self._format
if self._error is not None:
text = self._error
else:
try:
text = formatStringBuilder(self._format, self._data)
except KeyError:
text = "KeyError"
return text
def get_api_async(self):
try:
with requests.get(self._url, timeout=10) as request:
# Can't implement error handling for python2.7 if I use
# request.json() as it uses simplejson in newer versions
self._data = self.__flatten(json.loads(request.text))
self._error = None
except requests.exceptions.Timeout:
self._error = "Timeout"
except requests.exceptions.HTTPError:
self._error = "HTTP Error"
except ValueError:
self._error = "Not a JSON response"
# left_mouse_button handler
def __forceReload(self, event):
self._threadingCount += 300
self._error = "RELOADING"
# Flattens the JSON structure recursively, e.g. ["space"]["open"]
# becomes ["space.open"]
def __flatten(self, json):
out = {}
for key in json:
value = json[key]
if type(value) is dict:
flattened_key = self.__flatten(value)
for fk in flattened_key:
out[key + "." + fk] = flattened_key[fk]
else:
out[key] = value
return out
# Author: Tobias Manske <tobias@chaoswg.xyz>
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4