git-svn-id: https://bucket.mit.edu/svn/nilm/nilmdb@10625 ddd99763-3ecb-0310-9145-efcb8ce7c51ftags/bxinterval-last
@@ -17,15 +17,16 @@ import pycurl | |||
class NilmCommError(Exception): | |||
"""Base exception for both ClientError and ServerError responses""" | |||
def __init__(self, code, message, body = None): | |||
# Whether to include the body in the __str__ expansion | |||
show_body = 1 | |||
def __init__(self, code, message, body = None, url = "no URL"): | |||
Exception.__init__(self, message) | |||
self.code = code | |||
self.body = body | |||
def __str__(self): | |||
return sprintf("[%03d] %s", self.code, self.message) | |||
def __repr__(self): | |||
s = self.__str__() | |||
if self.body: | |||
self.url = url | |||
def __str__(self): # pragma: no cover | |||
s = sprintf("[%03d] %s (%s)", self.code, self.message, self.url) | |||
if self.body and self.show_body: | |||
s += "\n" + self.body | |||
return s | |||
@@ -40,6 +41,7 @@ class MyCurl(object): | |||
"""If baseurl is supplied, all other functions that take | |||
a URL can be given a relative URL instead.""" | |||
self.baseurl = baseurl | |||
self.url = baseurl | |||
self.curl = pycurl.Curl() | |||
self.curl.setopt(pycurl.SSL_VERIFYHOST, 2) | |||
self.curl.setopt(pycurl.FOLLOWLOCATION, 1) | |||
@@ -50,16 +52,16 @@ class MyCurl(object): | |||
if params: | |||
url = urlparse.urljoin(url, "?" + urllib.urlencode(params, True)) | |||
self.curl.setopt(pycurl.URL, url) | |||
return url | |||
self.url = url | |||
def _check_error(self, body = None): | |||
code = self.curl.getinfo(pycurl.RESPONSE_CODE) | |||
if code >= 400 and code <= 499: | |||
raise ClientError(code, "HTTP client error", body) | |||
elif code >= 500 and code <= 599: | |||
raise ServerError(code, "HTTP server error", body) | |||
elif code != 200: | |||
raise NilmCommError(code, "Unknown error", body) | |||
raise ClientError(code, "HTTP client error", body, self.url) | |||
elif code >= 500 and code <= 599: # pragma: no cover | |||
raise ServerError(code, "HTTP server error", body, self.url) | |||
elif code != 200: # pragma: no cover | |||
raise NilmCommError(code, "Unknown error", body, self.url) | |||
def getjson(self, url, params = None): | |||
"""Simple GET that returns JSON string""" | |||
@@ -71,7 +73,7 @@ class MyCurl(object): | |||
try: | |||
self.curl.perform() | |||
except pycurl.error as e: | |||
raise ServerError(500, e[1]) | |||
raise ServerError(500, e[1], self.url) | |||
self._check_error(self._body) | |||
return json.loads(self._body) | |||
@@ -79,6 +81,10 @@ class Client(object): | |||
def __init__(self, url): | |||
self.curl = MyCurl(url) | |||
def _json_param(self, data): | |||
"""Return compact json-encoded version of parameter""" | |||
return json.dumps(data, separators=(',',':')) | |||
def version(self): | |||
return self.curl.getjson("version") | |||
@@ -96,11 +102,22 @@ class Client(object): | |||
params["key"] = keys | |||
return self.curl.getjson("stream/get_metadata", params) | |||
def stream_set_metadata(self): | |||
raise NotImplementedError | |||
def stream_update_metadata(self): | |||
raise NotImplementedError | |||
def stream_set_metadata(self, path, data): | |||
"""Set stream metadata from a dictionary, replacing all existing | |||
metadata.""" | |||
params = { | |||
"path": path, | |||
"data": self._json_param(data) | |||
} | |||
return self.curl.getjson("stream/set_metadata", params) | |||
def stream_update_metadata(self, path, data): | |||
"""Update stream metadata from a dictionary""" | |||
params = { | |||
"path": path, | |||
"data": self._json_param(data) | |||
} | |||
return self.curl.getjson("stream/update_metadata", params) | |||
def stream_create(self, path, layout, index = None): | |||
params = { "path": path, | |||
@@ -117,7 +117,7 @@ class NilmDB(object): | |||
cur.execute("PRAGMA user_version = {v:d}".format(v=version)) | |||
def stream_list(self, path = None, layout = None): | |||
"""Return list of (path, layout) tuples of all streams | |||
"""Return list of [path, layout] lists of all streams | |||
in the database. | |||
If path is specified, include only streams with a path that | |||
@@ -136,7 +136,8 @@ class NilmDB(object): | |||
params += (path,) | |||
result = self.con.execute("SELECT path, layout " + | |||
"FROM streams " + where, params).fetchall() | |||
return sorted(result) | |||
return sorted(list(x) for x in result) | |||
def stream_create(self, path, layout_name, index = None): | |||
"""Create a new table in the database. | |||
@@ -10,6 +10,7 @@ from nilmdb.printf import * | |||
import cherrypy | |||
import sys | |||
import os | |||
import json | |||
import traceback | |||
try: | |||
@@ -88,7 +89,7 @@ class Stream(NilmApp): | |||
"KeyError: " + e.message) | |||
# /stream/get_metadata?path=/newton/prep | |||
# /stream/get_metadata?path=/newton/prep?key=foo&key=bar | |||
# /stream/get_metadata?path=/newton/prep&key=foo&key=bar | |||
@cherrypy.expose | |||
@cherrypy.tools.json_out() | |||
def get_metadata(self, path, key=None): | |||
@@ -111,6 +112,35 @@ class Stream(NilmApp): | |||
result[k] = None | |||
return result | |||
# /stream/set_metadata?path=/newton/prep&data=<json> | |||
@cherrypy.expose | |||
@cherrypy.tools.json_out() | |||
def set_metadata(self, path, data): | |||
"""Set metadata for the named stream, replacing any | |||
existing metadata. Data should be a json-encoded | |||
dictionary""" | |||
try: | |||
data_dict = json.loads(data) | |||
self.db.stream_set_metadata(path, data_dict) | |||
except Exception as e: | |||
raise cherrypy.HTTPError("400 Bad Request", | |||
"Error: " + e.message) | |||
return "ok" | |||
# /stream/update_metadata?path=/newton/prep&data=<json> | |||
@cherrypy.expose | |||
@cherrypy.tools.json_out() | |||
def update_metadata(self, path, data): | |||
"""Update metadata for the named stream. Data | |||
should be a json-encoded dictionary""" | |||
try: | |||
data_dict = json.loads(data) | |||
self.db.stream_update_metadata(path, data_dict) | |||
except Exception as e: | |||
raise cherrypy.HTTPError("400 Bad Request", | |||
"Error: " + e.message) | |||
return "ok" | |||
# /stream/upload?path=/newton/prep | |||
@cherrypy.expose | |||
@cherrypy.tools.json_out() | |||
@@ -1,5 +1,6 @@ | |||
[nosetests] | |||
nocapture=1 | |||
nologcapture=1 # remove this to see cherrypy logs on failure | |||
with-coverage=1 | |||
cover-inclusive=1 | |||
cover-package=nilmdb | |||
@@ -10,7 +11,6 @@ stop=1 | |||
verbosity=2 | |||
#tests=tests/test_nilmdb.py | |||
#tests=tests/test_client.py | |||
#with-profile=1 | |||
#profile-sort=time | |||
#profile-restrict=10 # doesn't work right, treated as string or something |
@@ -88,20 +88,13 @@ class TestClient(object): | |||
client.stream_create("/newton/zzz/rawnotch", "RawNotchedData") | |||
# Verify we got 3 streams | |||
eq_(client.stream_list(), [ ("/newton/prep", "PrepData"), | |||
("/newton/raw", "RawData"), | |||
("/newton/zzz/rawnotch", "RawNotchedData") | |||
]) | |||
eq_(client.stream_list(), [ ["/newton/prep", "PrepData"], | |||
["/newton/raw", "RawData"], | |||
["/newton/zzz/rawnotch", "RawNotchedData"] | |||
]) | |||
# Match just one type or one path | |||
eq_(client.stream_list(layout="RawData"), [ ("/newton/raw", "RawData") ]) | |||
eq_(client.stream_list(path="/newton/raw"), [ ("/newton/raw", "RawData") ]) | |||
# Verify that columns were made right | |||
eq_(len(client.h5file.getNode("/newton/prep").cols), 9) | |||
eq_(len(client.h5file.getNode("/newton/raw").cols), 7) | |||
eq_(len(client.h5file.getNode("/newton/zzz/rawnotch").cols), 10) | |||
assert(client.h5file.getNode("/newton/prep").colindexed["timestamp"]) | |||
assert(not client.h5file.getNode("/newton/prep").colindexed["p1"]) | |||
eq_(client.stream_list(layout="RawData"), [ ["/newton/raw", "RawData"] ]) | |||
eq_(client.stream_list(path="/newton/raw"), [ ["/newton/raw", "RawData"] ]) | |||
# Set / get metadata | |||
eq_(client.stream_get_metadata("/newton/prep"), {}) | |||
@@ -116,4 +109,13 @@ class TestClient(object): | |||
client.stream_update_metadata("/newton/raw", meta3) | |||
eq_(client.stream_get_metadata("/newton/prep"), meta1) | |||
eq_(client.stream_get_metadata("/newton/raw"), meta1) | |||
eq_(client.stream_get_metadata("/newton/raw", [ "description" ] ), meta2) | |||
eq_(client.stream_get_metadata("/newton/raw", [ "description", | |||
"v_scale" ] ), meta1) | |||
# test wrong types (list instead of dict) | |||
with assert_raises(ClientError): | |||
client.stream_set_metadata("/newton/prep", [1,2,3]) | |||
with assert_raises(ClientError): | |||
client.stream_update_metadata("/newton/prep", [1,2,3]) | |||
@@ -59,13 +59,13 @@ class Test00Nilmdb(object): # named 00 so it runs first | |||
db.stream_create("/newton/zzz/rawnotch", "RawNotchedData") | |||
# Verify we got 3 streams | |||
eq_(db.stream_list(), [ ("/newton/prep", "PrepData"), | |||
("/newton/raw", "RawData"), | |||
("/newton/zzz/rawnotch", "RawNotchedData") | |||
eq_(db.stream_list(), [ ["/newton/prep", "PrepData"], | |||
["/newton/raw", "RawData"], | |||
["/newton/zzz/rawnotch", "RawNotchedData"] | |||
]) | |||
# Match just one type or one path | |||
eq_(db.stream_list(layout="RawData"), [ ("/newton/raw", "RawData") ]) | |||
eq_(db.stream_list(path="/newton/raw"), [ ("/newton/raw", "RawData") ]) | |||
eq_(db.stream_list(layout="RawData"), [ ["/newton/raw", "RawData"] ]) | |||
eq_(db.stream_list(path="/newton/raw"), [ ["/newton/raw", "RawData"] ]) | |||
# Verify that columns were made right | |||
eq_(len(db.h5file.getNode("/newton/prep").cols), 9) | |||