pytrms 0.9.2__py3-none-any.whl → 0.9.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pytrms/clients/db_api.py CHANGED
@@ -1,183 +1,200 @@
1
- import os
2
- import json
3
-
4
- import requests
5
-
6
- from . import _logging
7
- from .ssevent import SSEventListener
8
- from .._base import IoniClientBase
9
-
10
- log = _logging.getLogger(__name__)
11
-
12
- # TODO :: sowas waer auch ganz cool: die DBAPI bietes sich geradezu an,
13
- # da mehr object-oriented zu arbeiten:
14
- # currentVariable = get_component(currentComponentNameAction, ds)
15
- # currentVariable.save_value({'value': currentValue})
16
-
17
- class IoniConnect(IoniClientBase):
18
-
19
- @property
20
- def is_connected(self):
21
- '''Returns `True` if connection to IoniTOF could be established.'''
22
- try:
23
- self.get("/api/status")
24
- return True
25
- except:
26
- return False
27
-
28
- @property
29
- def is_running(self):
30
- '''Returns `True` if IoniTOF is currently acquiring data.'''
31
- raise NotImplementedError("is_running")
32
-
33
- def connect(self, timeout_s):
34
- pass
35
-
36
- def disconnect(self):
37
- pass
38
-
39
- def __init__(self, host='127.0.0.1', port=5066, session=None):
40
- super().__init__(host, port)
41
- self.url = f"http://{self.host}:{self.port}"
42
- if session is None:
43
- session = requests.sessions.Session()
44
- self.session = session
45
- # ??
46
- self.current_avg_endpoint = None
47
- self.comp_dict = dict()
48
-
49
- def get(self, endpoint, **kwargs):
50
- return self._get_object(endpoint, **kwargs).json()
51
-
52
- def post(self, endpoint, data, **kwargs):
53
- return self._create_object(endpoint, data, 'post', **kwargs).headers.get('Location')
54
-
55
- def put(self, endpoint, data, **kwargs):
56
- return self._create_object(endpoint, data, 'put', **kwargs).headers.get('Location')
57
-
58
- def upload(self, endpoint, filename):
59
- if not endpoint.startswith('/'):
60
- endpoint = '/' + endpoint
61
- with open(filename) as f:
62
- # Note (important!): this is a "form-data" entry, where the server
63
- # expects the "name" to be 'file' and rejects it otherwise:
64
- name = 'file'
65
- r = self.session.post(self.url + endpoint, files=[(name, (filename, f, ''))])
66
- r.raise_for_status()
67
-
68
- return r
69
-
70
- def _get_object(self, endpoint, **kwargs):
71
- if not endpoint.startswith('/'):
72
- endpoint = '/' + endpoint
73
- if 'headers' not in kwargs:
74
- kwargs['headers'] = {'content-type': 'application/hal+json'}
75
- elif 'content-type' not in (k.lower() for k in kwargs['headers']):
76
- kwargs['headers'].update({'content-type': 'application/hal+json'})
77
- r = self.session.request('get', self.url + endpoint, **kwargs)
78
- r.raise_for_status()
79
-
80
- return r
81
-
82
- def _create_object(self, endpoint, data, method='post', **kwargs):
83
- if not endpoint.startswith('/'):
84
- endpoint = '/' + endpoint
85
- if not isinstance(data, str):
86
- data = json.dumps(data, ensure_ascii=False) # default is `True`, escapes Umlaute!
87
- if 'headers' not in kwargs:
88
- kwargs['headers'] = {'content-type': 'application/hal+json'}
89
- elif 'content-type' not in (k.lower() for k in kwargs['headers']):
90
- kwargs['headers'].update({'content-type': 'application/hal+json'})
91
- r = self.session.request(method, self.url + endpoint, data=data, **kwargs)
92
- if not r.ok:
93
- log.error(f"POST {endpoint}\n{data}\n\nreturned [{r.status_code}]: {r.content}")
94
- r.raise_for_status()
95
-
96
- return r
97
-
98
- def sync(self, peaktable):
99
- """Compare and upload any differences in `peaktable` to the database."""
100
- from pytrms.peaktable import Peak, PeakTable
101
- from operator import attrgetter
102
-
103
- # Note: a `Peak` is a hashable object that serves as a key that
104
- # distinguishes between peaks as defined by PyTRMS:
105
- make_key = lambda peak: Peak(center=peak['center'], label=peak['name'], shift=peak['shift'])
106
-
107
- if isinstance(peaktable, str):
108
- log.info(f"loading peaktable '{peaktable}'...")
109
- peaktable = PeakTable.from_file(peaktable)
110
-
111
- # get the PyTRMS- and IoniConnect-peaks on the same page:
112
- conv = {
113
- 'name': attrgetter('label'),
114
- 'center': attrgetter('center'),
115
- 'kRate': attrgetter('k_rate'),
116
- 'low': lambda p: p.borders[0],
117
- 'high': lambda p: p.borders[1],
118
- 'shift': attrgetter('shift'),
119
- 'multiplier': attrgetter('multiplier'),
120
- }
121
- # normalize the input argument and create a hashable set:
122
- updates = dict()
123
- for peak in peaktable:
124
- payload = {k: conv[k](peak) for k in conv}
125
- updates[make_key(payload)] = {'payload': payload}
126
-
127
- log.info(f"fetching current peaktable from the server...")
128
- # create a comparable collection of peaks already on the database by
129
- # reducing the keys in the response to what we actually want to update:
130
- db_peaks = {make_key(p): {
131
- 'payload': {k: p[k] for k in conv.keys()},
132
- 'self': p['_links']['self'],
133
- 'parent': p['_links'].get('parent'),
134
- } for p in self.get('/api/peaks')['_embedded']['peaks']}
135
-
136
- to_update = updates.keys() & db_peaks.keys()
137
- to_upload = updates.keys() - db_peaks.keys()
138
- updated = 0
139
- for key in sorted(to_update):
140
- # check if an existing peak needs an update
141
- if db_peaks[key]['payload'] == updates[key]['payload']:
142
- # nothing to do..
143
- log.debug(f"up-to-date: {key}")
144
- continue
145
-
146
- self.put(db_peaks[key]['self']['href'], updates[key]['payload'])
147
- log.info(f"updated: {key}")
148
- updated += 1
149
-
150
- if len(to_upload):
151
- # Note: POSTing the embedded-collection is *miles faster*
152
- # than doing separate requests for each peak!
153
- payload = {'_embedded': {'peaks': [updates[key]['payload'] for key in sorted(to_upload)]}}
154
- self.post('/api/peaks', payload)
155
- for key in sorted(to_upload): log.info(f"added new: {key}")
156
-
157
- # Note: this disregards the peak-parent-relationship, but in
158
- # order to implement this correctly, one would need to check
159
- # if the parent-peak with a specific 'parentID' is already
160
- # uploaded and search it.. there's an endpoint
161
- # 'LINK /api/peaks/{parentID} Location: /api/peaks/{childID}'
162
- # to link a child to its parent, but it remains complicated.
163
- # TODO :: maybe later implement parent-peaks!?
164
-
165
- return {
166
- 'added': len(to_upload),
167
- 'updated': updated,
168
- 'up-to-date': len(to_update) - updated,
169
- }
170
-
171
- def iter_events(self, event_re=r".*"):
172
- """Follow the server-sent-events (SSE) on the DB-API.
173
-
174
- `event_re` a regular expression to filter events (default: matches everything)
175
-
176
- Note: This will block until a matching event is received.
177
- Especially, it cannot be cancelled by KeyboardInterrupt (due to the `requests`
178
- stream-implementation), unless the server sends a keep-alive at regular
179
- intervals (as every well-behaved server should be doing)!
180
- """
181
- yield from SSEventListener(event_re, host_url=self.url, endpoint="/api/events",
182
- session=self.session)
183
-
1
+ import os
2
+ import json
3
+
4
+ import requests
5
+
6
+ from . import _logging
7
+ from .ssevent import SSEventListener
8
+ from .._base import IoniClientBase
9
+
10
+ log = _logging.getLogger(__name__)
11
+
12
+ # TODO :: sowas waer auch ganz cool: die DBAPI bietes sich geradezu an,
13
+ # da mehr object-oriented zu arbeiten:
14
+ # currentVariable = get_component(currentComponentNameAction, ds)
15
+ # currentVariable.save_value({'value': currentValue})
16
+
17
+ class IoniConnect(IoniClientBase):
18
+
19
+ @property
20
+ def is_connected(self):
21
+ '''Returns `True` if connection to IoniTOF could be established.'''
22
+ try:
23
+ self.get("/api/status")
24
+ return True
25
+ except:
26
+ return False
27
+
28
+ @property
29
+ def is_running(self):
30
+ '''Returns `True` if IoniTOF is currently acquiring data.'''
31
+ raise NotImplementedError("is_running")
32
+
33
+ def connect(self, timeout_s):
34
+ pass
35
+
36
+ def disconnect(self):
37
+ pass
38
+
39
+ def __init__(self, host='127.0.0.1', port=5066, session=None):
40
+ super().__init__(host, port)
41
+ self.url = f"http://{self.host}:{self.port}"
42
+ if session is None:
43
+ session = requests.sessions.Session()
44
+ self.session = session
45
+ # ??
46
+ self.current_avg_endpoint = None
47
+ self.comp_dict = dict()
48
+
49
+ def get(self, endpoint, **kwargs):
50
+ return self._get_object(endpoint, **kwargs).json()
51
+
52
+ def post(self, endpoint, data, **kwargs):
53
+ return self._create_object(endpoint, data, 'post', **kwargs).headers.get('Location')
54
+
55
+ def put(self, endpoint, data, **kwargs):
56
+ return self._create_object(endpoint, data, 'put', **kwargs).headers.get('Location')
57
+
58
+ def upload(self, endpoint, filename):
59
+ if not endpoint.startswith('/'):
60
+ endpoint = '/' + endpoint
61
+ with open(filename) as f:
62
+ # Note (important!): this is a "form-data" entry, where the server
63
+ # expects the "name" to be 'file' and rejects it otherwise:
64
+ name = 'file'
65
+ r = self.session.post(self.url + endpoint, files=[(name, (filename, f, ''))])
66
+ r.raise_for_status()
67
+
68
+ return r
69
+
70
+ def _get_object(self, endpoint, **kwargs):
71
+ if not endpoint.startswith('/'):
72
+ endpoint = '/' + endpoint
73
+ if 'headers' not in kwargs:
74
+ kwargs['headers'] = {'content-type': 'application/hal+json'}
75
+ elif 'content-type' not in (k.lower() for k in kwargs['headers']):
76
+ kwargs['headers'].update({'content-type': 'application/hal+json'})
77
+ if 'timeout' not in kwargs:
78
+ # https://requests.readthedocs.io/en/latest/user/advanced/#timeouts
79
+ kwargs['timeout'] = (6.06, 27)
80
+ r = self.session.request('get', self.url + endpoint, **kwargs)
81
+ r.raise_for_status()
82
+
83
+ return r
84
+
85
+ def _create_object(self, endpoint, data, method='post', **kwargs):
86
+ if not endpoint.startswith('/'):
87
+ endpoint = '/' + endpoint
88
+ if not isinstance(data, str):
89
+ data = json.dumps(data, ensure_ascii=False) # default is `True`, escapes Umlaute!
90
+ if 'headers' not in kwargs:
91
+ kwargs['headers'] = {'content-type': 'application/hal+json'}
92
+ elif 'content-type' not in (k.lower() for k in kwargs['headers']):
93
+ kwargs['headers'].update({'content-type': 'application/hal+json'})
94
+ if 'timeout' not in kwargs:
95
+ # https://requests.readthedocs.io/en/latest/user/advanced/#timeouts
96
+ kwargs['timeout'] = (6.06, 27)
97
+ r = self.session.request(method, self.url + endpoint, data=data, **kwargs)
98
+ if not r.ok:
99
+ log.error(f"POST {endpoint}\n{data}\n\nreturned [{r.status_code}]: {r.content}")
100
+ r.raise_for_status()
101
+
102
+ return r
103
+
104
+ def sync(self, peaktable):
105
+ """Compare and upload any differences in `peaktable` to the database."""
106
+ from pytrms.peaktable import Peak, PeakTable
107
+ from operator import attrgetter
108
+
109
+ # Note: a `Peak` is a hashable object that serves as a key that
110
+ # distinguishes between peaks as defined by PyTRMS:
111
+ make_key = lambda peak: Peak(center=peak['center'], label=peak['name'], shift=peak['shift'])
112
+
113
+ if isinstance(peaktable, str):
114
+ log.info(f"loading peaktable '{peaktable}'...")
115
+ peaktable = PeakTable.from_file(peaktable)
116
+
117
+ # get the PyTRMS- and IoniConnect-peaks on the same page:
118
+ conv = {
119
+ 'name': attrgetter('label'),
120
+ 'center': attrgetter('center'),
121
+ 'kRate': attrgetter('k_rate'),
122
+ 'low': lambda p: p.borders[0],
123
+ 'high': lambda p: p.borders[1],
124
+ 'shift': attrgetter('shift'),
125
+ 'multiplier': attrgetter('multiplier'),
126
+ }
127
+ # normalize the input argument and create a hashable set:
128
+ updates = dict()
129
+ for peak in peaktable:
130
+ payload = {k: conv[k](peak) for k in conv}
131
+ updates[make_key(payload)] = {'payload': payload}
132
+
133
+ log.info(f"fetching current peaktable from the server...")
134
+ # create a comparable collection of peaks already on the database by
135
+ # reducing the keys in the response to what we actually want to update:
136
+ db_peaks = {make_key(p): {
137
+ 'payload': {k: p[k] for k in conv.keys()},
138
+ 'self': p['_links']['self'],
139
+ 'parent': p['_links'].get('parent'),
140
+ } for p in self.get('/api/peaks')['_embedded']['peaks']}
141
+
142
+ to_update = updates.keys() & db_peaks.keys()
143
+ to_upload = updates.keys() - db_peaks.keys()
144
+ updated = 0
145
+ for key in sorted(to_update):
146
+ # check if an existing peak needs an update
147
+ if db_peaks[key]['payload'] == updates[key]['payload']:
148
+ # nothing to do..
149
+ log.debug(f"up-to-date: {key}")
150
+ continue
151
+
152
+ self.put(db_peaks[key]['self']['href'], updates[key]['payload'])
153
+ log.info(f"updated: {key}")
154
+ updated += 1
155
+
156
+ if len(to_upload):
157
+ # Note: POSTing the embedded-collection is *miles faster*
158
+ # than doing separate requests for each peak!
159
+ payload = {'_embedded': {'peaks': [updates[key]['payload'] for key in sorted(to_upload)]}}
160
+ self.post('/api/peaks', payload)
161
+ for key in sorted(to_upload):
162
+ log.info(f"added new: {key}")
163
+
164
+ if len(peaktable.fitted):
165
+ # Note: until now, we disregarded the peak-parent-relationship, so
166
+ # make another request to the updated peak-table from the server...
167
+ db_peaks = {make_key(p): {
168
+ 'payload': {k: p[k] for k in conv.keys()},
169
+ 'self': p['_links']['self'],
170
+ 'parent': p['_links'].get('parent'),
171
+ } for p in self.get('/api/peaks')['_embedded']['peaks']}
172
+
173
+ for fitted in peaktable.fitted:
174
+ fitted_href = db_peaks[fitted]["self"]["href"]
175
+ parent_href = db_peaks[fitted.parent]["self"]["href"]
176
+ r = self.session.request('link', self.url + parent_href, headers={"location": fitted_href})
177
+ if not r.ok:
178
+ log.error(f"LINK {parent_href} to Location: {fitted_href} failed\n\n[{r.status_code}]: {r.content}")
179
+ r.raise_for_status()
180
+ log.debug(f"linked parent {parent_href} ~> {fitted_href}")
181
+
182
+ return {
183
+ 'added': len(to_upload),
184
+ 'updated': updated,
185
+ 'up-to-date': len(to_update) - updated,
186
+ }
187
+
188
+ def iter_events(self, event_re=r".*"):
189
+ """Follow the server-sent-events (SSE) on the DB-API.
190
+
191
+ `event_re` a regular expression to filter events (default: matches everything)
192
+
193
+ Note: This will block until a matching event is received.
194
+ Especially, it cannot be cancelled by KeyboardInterrupt (due to the `requests`
195
+ stream-implementation), unless the server sends a keep-alive at regular
196
+ intervals (as every well-behaved server should be doing)!
197
+ """
198
+ yield from SSEventListener(event_re, host_url=self.url, endpoint="/api/events",
199
+ session=self.session)
200
+
@@ -1,87 +1,87 @@
1
- #################################################
2
- # #
3
- # Ionicon WebAPI - Python client #
4
- # #
5
- # this module requires the 'requests' package #
6
- # #
7
- # >> pip install --user requests #
8
- # #
9
- #################################################
10
- import ntpath
11
- import time
12
-
13
- import requests
14
-
15
-
16
- class IoniClient:
17
- '''
18
- Access the Ionicon WebAPI.
19
-
20
- Usage:
21
- > client = IoniClient()
22
- > client.get('TPS_Pull_H')
23
- {'TPS_Pull_H': 123.45, ... }
24
-
25
- > client.set('TPS_Pull_H', 42)
26
- {'TPS_Pull_H': 42.0, ... }
27
-
28
- > client.start_measurement()
29
- ACK
30
-
31
- > client.host, client.port
32
- ('localhost', 8002)
33
-
34
- '''
35
- def __init__(self, host='localhost', port=8002):
36
- self.host = host
37
- self.port = port
38
-
39
- @property
40
- def baseurl(self):
41
- return f'http://{self.host}:{self.port}/Ic_WebAPI'
42
-
43
- def get(self, varname):
44
- uri = self.baseurl + '/WebAPI_Get'
45
- payload = {varname: '?'}
46
- r = requests.get(uri, params=payload, timeout=10)
47
-
48
- return r.text
49
-
50
- def get_many(self, varnames):
51
- uri = self.baseurl + '/WebAPI_Get'
52
- payload = {varname: '?' for varname in varnames}
53
- r = requests.get(uri, params=payload, timeout=10)
54
-
55
- return r.text
56
-
57
- def get_traces(self):
58
- uri = self.baseurl + '/TRACES_WebAPI_Get' + '?'
59
- r = requests.get(uri, timeout=10)
60
-
61
- return r.text
62
-
63
- def set(self, varname, value):
64
- uri = self.baseurl + '/WebAPI_Set'
65
- payload = {varname: value}
66
- r = requests.post(uri, data=payload, timeout=10)
67
-
68
- return r.text
69
-
70
- def set_many(self, key_value_pairs):
71
- uri = self.baseurl + '/WebAPI_Set'
72
- payload = dict(key_value_pairs)
73
- r = requests.post(uri, data=payload, timeout=10)
74
-
75
- return r.text
76
-
77
- def set_filename(self, filename):
78
- return self.set('ACQ_SRV_SetFullStorageFile', ntpath.normpath(filename))
79
-
80
- def start_measurement(self, filename=''):
81
- if filename:
82
- return self.set('ACQ_SRV_Start_Meas_Auto', ntpath.normpath(filename))
83
-
84
- return self.set('ACQ_SRV_Start_Meas_Quick', 1)
85
-
86
- def stop_measurement(self):
87
- return self.set('ACQ_SRV_Stop_Meas', 1)
1
+ #################################################
2
+ # #
3
+ # Ionicon WebAPI - Python client #
4
+ # #
5
+ # this module requires the 'requests' package #
6
+ # #
7
+ # >> pip install --user requests #
8
+ # #
9
+ #################################################
10
+ import ntpath
11
+ import time
12
+
13
+ import requests
14
+
15
+
16
+ class IoniClient:
17
+ '''
18
+ Access the Ionicon WebAPI.
19
+
20
+ Usage:
21
+ > client = IoniClient()
22
+ > client.get('TPS_Pull_H')
23
+ {'TPS_Pull_H': 123.45, ... }
24
+
25
+ > client.set('TPS_Pull_H', 42)
26
+ {'TPS_Pull_H': 42.0, ... }
27
+
28
+ > client.start_measurement()
29
+ ACK
30
+
31
+ > client.host, client.port
32
+ ('localhost', 8002)
33
+
34
+ '''
35
+ def __init__(self, host='localhost', port=8002):
36
+ self.host = host
37
+ self.port = port
38
+
39
+ @property
40
+ def baseurl(self):
41
+ return f'http://{self.host}:{self.port}/Ic_WebAPI'
42
+
43
+ def get(self, varname):
44
+ uri = self.baseurl + '/WebAPI_Get'
45
+ payload = {varname: '?'}
46
+ r = requests.get(uri, params=payload, timeout=10)
47
+
48
+ return r.text
49
+
50
+ def get_many(self, varnames):
51
+ uri = self.baseurl + '/WebAPI_Get'
52
+ payload = {varname: '?' for varname in varnames}
53
+ r = requests.get(uri, params=payload, timeout=10)
54
+
55
+ return r.text
56
+
57
+ def get_traces(self):
58
+ uri = self.baseurl + '/TRACES_WebAPI_Get' + '?'
59
+ r = requests.get(uri, timeout=10)
60
+
61
+ return r.text
62
+
63
+ def set(self, varname, value):
64
+ uri = self.baseurl + '/WebAPI_Set'
65
+ payload = {varname: value}
66
+ r = requests.post(uri, data=payload, timeout=10)
67
+
68
+ return r.text
69
+
70
+ def set_many(self, key_value_pairs):
71
+ uri = self.baseurl + '/WebAPI_Set'
72
+ payload = dict(key_value_pairs)
73
+ r = requests.post(uri, data=payload, timeout=10)
74
+
75
+ return r.text
76
+
77
+ def set_filename(self, filename):
78
+ return self.set('ACQ_SRV_SetFullStorageFile', ntpath.normpath(filename))
79
+
80
+ def start_measurement(self, filename=''):
81
+ if filename:
82
+ return self.set('ACQ_SRV_Start_Meas_Auto', ntpath.normpath(filename))
83
+
84
+ return self.set('ACQ_SRV_Start_Meas_Quick', 1)
85
+
86
+ def stop_measurement(self):
87
+ return self.set('ACQ_SRV_Stop_Meas', 1)