-
Notifications
You must be signed in to change notification settings - Fork 8
/
restservice.py
498 lines (407 loc) · 16.7 KB
/
restservice.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
import flask
import json
import logging
import model
import sys
import datetime
import dateutil.parser
import random
import requests
from gevent.pywsgi import WSGIServer
from flask import render_template, render_template_string
from bokeh.client import pull_session
from bokeh.embed import server_session,server_document
'''
TODO:
have a key to add the time in the answer when query data
- you can put the variable for time if you know it
- you can say "addTimeValue=True" in the query to get the time as "time"=....., sometimes you don't know what is the time variable, you just have the variables
'''
#init the logger
logger = logging.getLogger("restservice")
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logfile = logging.FileHandler("./log/restservice.log")
logfile.setFormatter(formatter)
logger.addHandler(logfile)
logger.setLevel(logging.DEBUG)
bokehPath = "http://localhost:5006/"
web = flask.Flask(__name__)
'''
WEBCODES ---------------------
200 ok
201 ok, created
400 bad request malformed
404 not found
405 not allowed
WEBAPI -----------------------
path REQUEST BOY RESPONSE BODY
POST /_create [<createnode.json>] [<node.json>] ## give a list of new nodes, result contains all the created nodes
POST /_delete [<nodedescriptor>] [<nodedescriptor>] ## give list of deleted nodes back
POST /_getall - [<node.json>] ## give the whole address space
POST /setProperties [<node.json>] [<node.json>] ## the node.json only contains properties to change
POST /_get [<nodescriptor>] [<node.json>] ## get node including children as json
POST /_getvalue [<nodedescriptor>] [<values>] ## get a list of values, not available are returned as none
POST /_getleaves <nodedescriptor> [<node.json>] ## get the leaves of a referencer
GET /pipelines - [<pipeline.json>] ## get the pipelines
POST /_load fileName (str) -
POST /_save fileName (str) -
POST /_getdata <dataquery.json>]
POST /_appendRow [<data.json>]
POST /_references <referencequery.json>
POST /_execute <nodedescriptor> //nothing ## execute a function
GET /templates - [templatename] ## get all available templates to be created
POST /_createTemplate <createtemplate.json> - #create a template at a path given
GET /models - [string] # a list of available models from the /model folder
GET /modelinfo - <modelinfo.json> # get the current name of the model, path etc
GEt /embedbokeh <urlstring> <bokeh session> # pull a bokeh session for forwarding to a div, url string is eg. http://localhost:5006/bokeh_web
data:
JSONS ------------------------
nodedescriptor // a string with the nodeid (number) or the browsepath
node.json =
{
"name": "myname",
"id": nodeid ##ignored on create request
"parent": <nodedescriptor>
"type":"variable",
"value":1234,
"children":[<node.json>] ##not given on responses
}
createnode.json =
{
"browsePath":"root.myder..." #mandatory the path
"type": #mandatory
"value": #optional
######
dont't put "children"
}
createtemplate.json
{
"browsePath":"root.myfolder..." # the path of the root of the template, this is a node to be created!
"type": "templates.button" # a well-known template name
}
dataquery.json
{
nodes :[<nodedescriptor>],
startTime : 2018.01.01T00:10:08.445+02:00 //optional
endTime : 2018.01.01T01:10:08.445+02:00 //optional
bins : 300
includeTimeStamps: xxx //optional: if this is includes the timestamps are delivered as variable "time" in epoch style, todo: we can have different formats required in xxx
}
#example
{
"nodes" :["root.variables.f0","root.variables.f1"],
"startTime" : "2018.01.01T00:10:08.445+02:00",
"endTime" : "2018.01.01T01:10:08.445+02:00",
"bins" : 300
}
data.json
{
"root.folder1.var1": 1.3456,
"root.folder1.var2": 15,
"root.folder1.var3": -0.4,
"root.folder1.time": 1546437120.22644
}
dataresponse.json
{
root.myfolder.mynode: 1.345
root.myfolder.mynode2: -10
time : 2018.5.6T5:9:8
epochTime :
}
pipelines.json
{
"mypipeline":{"url":"http://localhost:5005"}, //one line has the name and all the properties (we return the const-children in the model)
"mypipeline":{"url":"http://localhost:5005","anothertab","whatistthi"}
}
referencequery.json
{
"node": <nodedescriptor> # must be a referencer
"add": [<nodedescriptors>]
"deleteExisting" : one of True/False # if set, all existings references are deleted
"remove" :[<nodedescriptors>]
}
modelinfo.json
{
"name" : "myfirstModel" # the name of the model
}
'''
#@web.route('/',defaults={'path':''},methods=['GET','POST'])
@web.route('/<path:path>', methods=['GET', 'POST'])
def all(path):
#print("here")
logger.info("http request "+str(flask.request.method) +"'"+str(path)+"'" +" DATA="+str(flask.request.data))
data = None
response="{}" #default is empty
responseCode = 404 # default is not found
try:
#parse the incoming data
data = flask.request.data.decode('utf-8') # just the string
data = json.loads(flask.request.data.decode('utf-8'))
except:
pass
#if the path has ending /, we remove it
if path[-1]=='/':
path = path[:-1]
# server all the frontend stuff: js, css etc
if any(extension in str(path) for extension in ['.js','.css','.htm','.img','.ico','.png','.gif','.map','.svg','.wof','.ttf']):
logger.debug(" serve html "+ str(path))
if "styles.css" in path:
print("hier")
for sourceFolder in ["web","bokeh_web","bokeh_web/templates"]:
try:
obj = flask.send_from_directory(sourceFolder,path)
return obj
except Exception as exc:
logger.error("file "+ str(path)+" not found on folder"+sourceFolder)
code = 404
elif (str(path) == "_getall") and str(flask.request.method) in ["POST","GET"]:
logger.debug("execute getall")
mymodel = m.get_model_for_web()
response = json.dumps(mymodel,indent=4)# some pretty printing for debug
responseCode = 200
elif (str(path) == "pipelines") and str(flask.request.method) in ["GET"]:
logger.debug("execute get pipelines")
try:
pipelinesNodeIds= m.get_node('root.visualization.pipelines').get_children()
pipelines = {}
for pipeNode in pipelinesNodeIds:
pipelines[pipeNode.get_name()]= {"url":pipeNode.get_child("url").get_property("value")}
response = json.dumps(pipelines,indent=4)# some pretty printing for debug
responseCode = 200
except:
logger.error("I have no pipelines")
responseCode = 404
elif (str(path) == "modelinfo") and str(flask.request.method) in ["GET"]:
logger.debug("get modelinfo")
response = json.dumps(m.get_info(), indent=4)
responseCode = 200
elif (str(path) == "templates") and str(flask.request.method) in ["GET"]:
logger.debug(" get templates")
templates = list(m.get_templates().keys())
logger.debug(" templates are "+str(templates))
response = json.dumps(templates)
responseCode = 200
elif (str(path) == "models") and str(flask.request.method) in ["GET"]:
logger.debug(" get templates")
models = m.get_models()
response = json.dumps(models)
responseCode = 200
elif(str(path) == "_getleaves") and str(flask.request.method) in ["POST", "GET"]:
logger.debug("execute get forward")
nodes = m.get_leaves(data)
response = json.dumps(nodes, indent=4)
responseCode = 200
elif(str(path) == "_delete") and str(flask.request.method) in ["POST"]:
logger.debug("delete nodes")
result = []
responseCode = 200
for nodePath in data:
delResult = m.delete_node(nodePath)
logger.debug("deleted " +nodePath + " result: "+str(delResult))
result.append(delResult)
if not delResult:
responseCode = 401
response = json.dumps(result, indent=4)
elif (str(path) == "_get") and str(flask.request.method) in ["POST", "GET"]:
logger.debug("execute get"+str(data))
nodes = []
for nodeDesc in data:
resNodes = m.get_node_with_children(nodeDesc)
print(resNodes)
nodes.append(resNodes)
response = json.dumps(nodes, indent=4) # some pretty printing for debug
logger.debug("sending"+str(len(response)))
responseCode = 200
elif (str(path) == "_getvalue") and str(flask.request.method) in ["POST", "GET"]:
logger.debug("execute getvalue")
values = []
for nodeDesc in data:
values.append(m.get_value(nodeDesc))
response = json.dumps(values, indent=4) # some pretty printing for debug
logger.debug("sending"+response)
responseCode = 200
elif (str(path) == "_load") and str(flask.request.method) in ["POST"]:
logger.debug("load model:" + data)
result = m.load(data)
if result:
responseCode = 200
else:
responseCode = 404
elif (str(path) == "_save") and str(flask.request.method) in ["POST"]:
logger.debug("save to model:" + data)
result = m.save(data)
if result:
responseCode = 200
else:
responseCode = 404
elif (str(path)=="_appendRow"):
logger.debug("writeRow")
for blob in data:
result = m.append_table(blob)
if not result:
responseCode = 400
break
responseCode = 200
m.show()
elif (str(path)=="_getdata"):
logger.debug("get data")
startTime = None
endTime = None
if set(["bins","nodes"]).issubset(set(data.keys())):
#we have both bins and nodes
startTime = 0
endTime = 0
if "startTime" in data:
#try to parse it, we support iso format or epoch
if type(data["startTime"]) is str:
try:
startTime = dateutil.parser.parse(data["startTime"])
except:
logger.info("cant parse start time")
else:
startTime = data["startTime"]
if "endTime" in data:
#try to parse it, we support iso format or epoch
if type(data["endTime"]) is str:
try:
startTime = dateutil.parser.parse(data["endTime"])
except:
logger.info("cant parse end time")
else:
endTime = data["endTime"]
if "includeTimeStamps" in data:
includeTimeStamps = data["includeTimeStamps"]
else:
includeTimeStamps= None
if "includeBackGround" in data:
includeBackGround = data["includeBackGround"]
else:
includeBackGround = None
try:
result = m.get_timeseries_table(data["nodes"],startTime=startTime,endTime=endTime,noBins=int(data["bins"]),includeTimeStamps=includeTimeStamps,format="dict",includeBackGround=includeBackGround)
if type(result) != type(None):
if includeTimeStamps:
pass #XXX todo: include the timestamps converted to a certain format
#data["nodes"].append("time")
response = json.dumps(result,indent = 4)
responseCode = 200
else:
responseData = 400
except:
logger.warn("get time series tables failed",sys.exc_info())
responseCode = 404
else:
responseCode = 400 # malformed
elif (str(path) == "_create"):
logger.debug("create ")
result = []
responseCode = 201
for blob in data:
newNodeId = m.create_node_from_path(blob["browsePath"],blob)
logger.debug('creating'+blob["browsePath"]+', result:'+str(newNodeId))
result.append(newNodeId)
if not newNodeId:
responseCode = 400
response = json.dumps(result)
responseCode = 201
elif (str(path) == "_createTemplate") and str(flask.request.method) in ["POST"]:
logger.debug("craete Template ")
templates = m.get_templates()
if data["type"] in templates:
m.create_template_from_path(data["browsePath"],templates[data["type"]])
responseCode = 201
else:
responseCode = 404
elif (str(path) == "setProperties"):
logger.debug("set properties ")
responseCode = 201
result = True
for blob in data:
result = result and m.set_properties(blob)
if not result:
responseCode = 400
elif (str(path) == "_references"):
logger.debug("set new references")
result = []
if "deleteExisting" in data and data["deleteExisting"] == True:
m.remove_forward_refs(data["parent"])
if "add" in data:
result = m.add_forward_refs(data["parent"],data["add"])
if "remove" in data:
result = m.remove_forward_refs(data["parent"], data["remove"])
responseCode = 201
elif (str(path) == "_execute"):
logger.debug("execute function")
result =[]
launch = m.execute_function(data)
if launch:
responseCode = 200
else:
responseCode = 404
elif (str(path) == "_diffUpdate") and str(flask.request.method) in ["GET","POST"]:
logger.debug("get differential update")
if not data or data["handle"] is None:
#this is for creating a handle
curModel = m.get_model_for_web()
handle = m.create_differential_handle()
res = {"handle":handle,"model":curModel}
response = json.dumps(res)
responseCode = 200
else:
#we have a handle
res = m.get_differential_update(data["handle"])
if res:
response = json.dumps(res)
responseCode = 200
else:
logger.error("requested handle does not exist")
response ="requested handle does not exist"
responseCode = 404
elif (str(path)=='embedbokeh'):
# here, we must have the correct html file for rendering including css and html coloring
# as the index.html is not relevant anymore
embed = """
<!doctype html>
<body>
<link rel="stylesheet" href="templates/styles.css">
{{ script|safe }}
</body>
</html>
"""
app_url = data['url']#'http://localhost:5006/bokeh_web'
try:
with pull_session(url=app_url) as session:
# customize session here
script = server_session(session_id='12345', url=app_url)
#script = server_document('http://localhost:5006/bokeh_web')
temp = render_template_string(embed, script=script)
return temp
except:
logger.error("pulling session failed")
responseCode = 404
else:
logger.warning("CANNOT HANDLE REQUEST, is unknown"+str(path))
responseCode = 404
logger.info("response len is"+str(len(response)))
return flask.Response(response, mimetype="text/html"), responseCode
if __name__ == '__main__':
m = model.Model()
if len(sys.argv) > 1:
if sys.argv[1] == "occupancy":
print("starting occupany demo")
m.create_test(2)
elif sys.argv[1] == "dynamictest":
print("starting the dynamic test")
m.create_test(3)
else:
print("load model from disk: "+sys.argv[1])
m.load(sys.argv[1])
else:
m.create_test(1)
web.run(host='0.0.0.0', port=6001, debug=False)
#enable this to use wsgi web server instead
#http_server = WSGIServer(('0.0.0.0', 6001), web)
#http_server.serve_forever()