Ticket #1534: client.py

File client.py, 3.6 KB (added by Tristan Croll, 6 years ago)

ISOLDE REST client

Line 
1
2class IsoldeRESTClient:
3 '''
4 Client-side code to interface to ISOLDE's REST API for remote control from
5 another program. NOTE: this uses a non-secure HTTP connection which a
6 malicious user could in theory use to run arbitrary code on your machine.
7 Should be used only on your local machine or a local area network, protected
8 by a firewall.
9
10 On running the connect() function, the client instance will be populated
11 with methods based on those defined on the server. A dict giving the
12 available method names, required/optional arguments and docstrings can be
13 obtained with get_available_methods().
14 '''
15 def __init__(self, address, port, timeout=20):
16 self._address = address
17 self._port = port
18 self._timeout = timeout
19 self._headers = {'Content-type': 'application/json'}
20
21 def connect(self):
22 import http.client
23 self._connection = http.client.HTTPConnection('{}:{}'.format(self._address, self._port), timeout=self._timeout)
24 self.get_available_methods()
25
26 def _get_available_methods(self):
27 if not self.connected:
28 raise RuntimeError('Not connected to server!')
29 self._connection.request('GET', '')
30 method_dict = self._get_result()
31 return method_dict
32
33 def get_available_methods(self):
34 method_dict = self._get_available_methods()
35 self._method_factory(self._get_available_methods())
36 return method_dict
37
38 def _method_factory(self, method_dict):
39 cls = self.__class__
40 for fname, func_def in method_dict.items():
41 if not hasattr(cls, fname):
42 args = func_def.get('args', [])
43 kwargs = func_def.get('kwargs', {})
44 f_str = '''
45def server_method(self, {}):
46 """
47 {}
48 """
49 send_dict = dict()
50 args = [{}]
51 kwargs = {}
52
53 send_dict['cmd'] = "{}"
54 send_dict['args'] = args
55 send_dict['kwargs'] = kwargs
56
57 import json
58 self._connection.request('POST', '', json.dumps(send_dict).encode('utf-8'), self._headers)
59 result = self._get_result()
60 if 'error' in result.keys():
61 raise RuntimeError(result['error'] + '; Server traceback: \\n' + result.get('traceback', 'None provided'))
62 return result
63'''.format(
64 ', '.join((
65 ', '.join("{}:'{}'".format(arg, argtype['type']) for arg, argtype in args.items()),
66 ', '.join(["{}:'{}'={}".format(kw, val['type'], val['default']) for kw, val in kwargs.items()])
67 )),
68 func_def['docstring'],
69 ', '.join(args),
70 "dict( [{}] )".format(', '.join('("{}", {})'.format(kw, kw) for kw in kwargs.keys())),
71 fname,
72 )
73 # print(f_str)
74 exec(f_str, globals())
75 setattr(cls, fname, server_method)
76
77
78
79 @property
80 def connected(self):
81 return (hasattr(self, '_connection') and self._connection is not None)
82 @property
83 def server_address(self):
84 return self._address
85
86 @property
87 def server_port(self):
88 return self._port
89
90 def _get_result(self):
91 from cgi import parse_header
92 result = self._connection.getresponse()
93 ctype, pdict = parse_header(result.headers.get('content-type'))
94 if ctype != 'application/json':
95 err_string = ('Server returned a non-supported type, "{}". Only "{}" '
96 'is allowed.').format(ctype, 'application/json')
97 raise TypeError(err_string)
98 import json
99 return json.loads(result.read().decode('utf-8'))
100
101if __name__=='__main__':
102 ic = IsoldeRESTClient('localhost', '12365')
103 ic.connect()
104 print(ic.run(['open 1a0m structureFactors true']))