|
1 | 1 | import json |
2 | | - |
| 2 | +from .metrics_shared import OpenPAYGOMetricsShared |
| 3 | +import copy |
| 4 | +from datetime import datetime, timedelta |
3 | 5 |
|
4 | 6 | class MetricsResponseHandler(object): |
5 | 7 |
|
6 | | - def __init__(self, received_metrics, data_format=None, secret_key=None): |
| 8 | + def __init__(self, received_metrics, data_format=None, secret_key=None, last_request_count=None, last_request_timestamp=None): |
7 | 9 | self.received_metrics = received_metrics |
8 | | - self.metrics_dict = json.loads(received_metrics) |
9 | | - self.answer_dict = {} |
| 10 | + self.request_dict = json.loads(received_metrics) |
| 11 | + # We convert the base variable names to simple |
| 12 | + self.request_dict = OpenPAYGOMetricsShared.convert_dict_keys_to_simple(self.request_dict) |
| 13 | + # We add the reception timestamp if not timestamp was provided |
| 14 | + if not self.request_dict.get('timestamp'): |
| 15 | + self.timestamp = int(datetime.now().timestamp()) |
| 16 | + else: |
| 17 | + self.timestamp = self.request_dict.get('timestamp') |
| 18 | + self.response_dict = {} |
| 19 | + self.secret_key = secret_key |
| 20 | + self.data_format = data_format |
| 21 | + self.last_request_count = last_request_count |
| 22 | + self.last_request_timestamp = last_request_timestamp |
| 23 | + if not self.data_format and self.request_dict.get('data_format'): |
| 24 | + self.data_format = self.request_dict.get('data_format') |
10 | 25 |
|
11 | 26 | def get_device_serial(self): |
12 | | - return self.received_metrics.get('sn', self.received_metrics.get('serial_number')) |
| 27 | + return self.request_dict.get('serial_number') |
| 28 | + |
| 29 | + def get_data_format_id(self): |
| 30 | + return self.request_dict.get('data_format_id') |
| 31 | + |
| 32 | + def data_format_available(self): |
| 33 | + return self.data_format != None |
13 | 34 |
|
14 | 35 | def set_device_parameters(self, secret_key, data_format): |
15 | | - pass |
| 36 | + self.secret_key = secret_key |
| 37 | + self.data_format = data_format |
16 | 38 |
|
17 | 39 | def is_auth_valid(self): |
18 | | - pass |
| 40 | + auth_string = self.request_dict.get('auth', None) |
| 41 | + if not auth_string: |
| 42 | + return True |
| 43 | + elif not self.secret_key: |
| 44 | + raise ValueError('Secret key is required to check the auth.') |
| 45 | + self.auth_method = auth_string[:2] |
| 46 | + new_signature = OpenPAYGOMetricsShared.generate_request_signature_from_data(self.request_dict, self.auth_method, self.secret_key) |
| 47 | + if auth_string == new_signature: |
| 48 | + request_count = self.request_dict.get('request_count') |
| 49 | + if request_count and self.last_request_count and request_count <= self.last_request_count: |
| 50 | + return False |
| 51 | + timestamp = self.request_dict.get('timestamp') |
| 52 | + if timestamp and self.last_request_timestamp and timestamp <= self.last_request_timestamp: |
| 53 | + return False |
| 54 | + return True |
| 55 | + return False |
19 | 56 |
|
20 | 57 | def get_simple_metrics(self): |
21 | | - pass |
| 58 | + # We start the process by making a copy of the dict to work with |
| 59 | + simple_dict = copy.deepcopy(self.request_dict) |
| 60 | + simple_dict.pop('auth') if 'auth' in simple_dict else None # We remove the auth |
| 61 | + # We process the data and replace it |
| 62 | + simple_dict['data'] = self._get_simple_data() |
| 63 | + # We process the historical data |
| 64 | + simple_dict['historical_data'] = self._get_simple_historical_data() |
| 65 | + # We fill in the timestamps for each time step |
| 66 | + simple_dict['historical_data'] = self._fill_timestamp_in_historical_data(simple_dict['historical_data']) |
| 67 | + return simple_dict |
22 | 68 |
|
23 | 69 | def expects_token_answer(self): |
24 | | - pass |
| 70 | + data = self._get_simple_data() |
| 71 | + return data.get('token_count') is not None |
25 | 72 |
|
26 | 73 | def add_tokens_to_answer(self, token_list): |
27 | | - pass |
| 74 | + self.response_dict['token_list'] = token_list |
28 | 75 |
|
29 | 76 | def expects_time_answer(self): |
30 | | - pass |
| 77 | + data = self._get_simple_data() |
| 78 | + if data.get('active_until_timestamp_requested', False) or data.get('active_seconds_left_requested', False): |
| 79 | + return True |
| 80 | + return False |
31 | 81 |
|
32 | 82 | def add_time_to_answer(self, target_datetime): |
33 | | - pass |
| 83 | + data = self._get_simple_data() |
| 84 | + if data.get('active_until_timestamp_requested', False): |
| 85 | + self.response_dict['active_until_timestamp'] = target_datetime.timestamp() |
| 86 | + elif data.get('active_seconds_left_requested', False): |
| 87 | + self.response_dict['active_seconds_left'] = (datetime.now() - target_datetime).total_seconds() |
| 88 | + else: |
| 89 | + raise ValueError('No time requested') |
| 90 | + |
| 91 | + def add_new_base_url_to_answer(self, new_base_url): |
| 92 | + self.add_settings_to_answer({'base_url': new_base_url}) |
34 | 93 |
|
35 | 94 | def add_settings_to_answer(self, settings_dict): |
36 | | - pass |
| 95 | + if not self.response_dict.get('settings'): |
| 96 | + self.response_dict['settings'] = {} |
| 97 | + self.response_dict['settings'].update(settings_dict) |
37 | 98 |
|
38 | 99 | def add_extra_data_to_answer(self, extra_data_dict): |
39 | | - pass |
| 100 | + if not self.response_dict.get('extra_data'): |
| 101 | + self.response_dict['extra_data'] = {} |
| 102 | + self.response_dict['extra_data'].update(extra_data_dict) |
40 | 103 |
|
41 | | - def get_answer(self): |
42 | | - pass |
| 104 | + def get_answer_payload(self): |
| 105 | + payload = self.get_answer_dict() |
| 106 | + return OpenPAYGOMetricsShared.convert_to_metrics_json(payload) |
43 | 107 |
|
44 | | - def _check_data_auth(self): |
45 | | - data = self.metrics_dict.get('data', None) |
46 | | - data_string = json.dumps(data, separators=(',', ':')) if data else '' |
47 | | - historical_data = self.metrics_dict.get('historical_data', None) |
48 | | - historical_data_string = json.dumps(historical_data, separators=(',', ':')) if historical_data else '' |
| 108 | + def get_answer_dict(self): |
| 109 | + # If there is not data format, we just return the full response |
| 110 | + condensed_answer = copy.deepcopy(self.response_dict) |
| 111 | + condensed_answer['auth'] = OpenPAYGOMetricsShared.generate_response_signature_from_data( |
| 112 | + serial_number=self.request_dict.get('serial_number'), |
| 113 | + request_count=self.request_dict.get('request_count'), |
| 114 | + data=condensed_answer, |
| 115 | + timestamp=self.request_dict.get('timestamp'), |
| 116 | + secret_key=self.secret_key |
| 117 | + ) |
| 118 | + return OpenPAYGOMetricsShared.convert_dict_keys_to_condensed(condensed_answer) |
49 | 119 |
|
| 120 | + def _get_simple_data(self): |
| 121 | + data = copy.deepcopy(self.request_dict.get('data')) |
| 122 | + # If no data or not condensed in list, we just return it |
| 123 | + if not data: |
| 124 | + return {} |
| 125 | + if not isinstance(data, list): |
| 126 | + return data |
| 127 | + data_order = self.data_format.get('data_order') |
| 128 | + if not data_order: |
| 129 | + raise ValueError('Data Format does not contain data_order') |
| 130 | + clean_data = {} |
| 131 | + data_len = len(data) |
| 132 | + for idx, var in enumerate(data_order): |
| 133 | + clean_data[var] = data[idx] if idx < data_len else None |
| 134 | + data = data[data_len:] |
| 135 | + if len(data) > 0: |
| 136 | + raise ValueError('Additional variables not present in the data format: '+str(data)) |
| 137 | + return OpenPAYGOMetricsShared.convert_dict_keys_to_simple(clean_data) |
| 138 | + |
| 139 | + def _get_simple_historical_data(self): |
| 140 | + historical_data = copy.deepcopy(self.request_dict.get('historical_data')) |
| 141 | + if not historical_data: |
| 142 | + return [] |
| 143 | + historical_data_order = self.data_format.get('historical_data_order') |
| 144 | + clean_historical_data = [] |
| 145 | + for time_step in historical_data: |
| 146 | + time_step_data = {} |
| 147 | + if isinstance(time_step, list): |
| 148 | + if not historical_data_order: |
| 149 | + raise ValueError('Data Format does not contain historical_data_order') |
| 150 | + timse_step_len = len(time_step) |
| 151 | + for idx, var in enumerate(historical_data_order): |
| 152 | + if idx < timse_step_len: |
| 153 | + time_step_data[var] = time_step[idx] |
| 154 | + time_step = time_step[timse_step_len:] |
| 155 | + if len(time_step) > 0: |
| 156 | + raise ValueError('Additional variables not present in the historical data format: '+str(time_step)) |
| 157 | + elif isinstance(time_step, dict): |
| 158 | + for key in time_step: |
| 159 | + if key.isdigit() and int(key) < len(historical_data_order): |
| 160 | + time_step_data[historical_data_order[int(key)]] = time_step[key] |
| 161 | + else: |
| 162 | + time_step_data[key] = time_step[key] |
| 163 | + else: |
| 164 | + raise ValueError('Invalid historical data step type: '+str(time_step)) |
| 165 | + clean_historical_data.append(time_step_data) |
| 166 | + return clean_historical_data |
| 167 | + |
| 168 | + def _fill_timestamp_in_historical_data(self, historical_data): |
| 169 | + if self.request_dict.get('data_collection_timestamp'): |
| 170 | + last_timestamp = datetime.fromtimestamp(self.request_dict.get('data_collection_timestamp')) |
| 171 | + else: |
| 172 | + last_timestamp = datetime.fromtimestamp(self.timestamp) |
| 173 | + for idx, time_step in enumerate(historical_data): |
| 174 | + if time_step.get('relative_time') is not None: |
| 175 | + last_timestamp = last_timestamp + timedelta(seconds=int(time_step.get('relative_time'))) |
| 176 | + historical_data[idx]['timestamp'] = int(last_timestamp.timestamp()) |
| 177 | + del historical_data[idx]['relative_time'] |
| 178 | + elif time_step.get('timestamp'): |
| 179 | + last_timestamp = datetime.fromtimestamp(time_step.get('timestamp')) |
| 180 | + else: |
| 181 | + if idx != 0: |
| 182 | + last_timestamp = last_timestamp + timedelta(seconds=int(self.data_format.get('historical_data_interval'))) |
| 183 | + historical_data[idx]['timestamp'] = int(last_timestamp.timestamp()) |
| 184 | + return historical_data |
0 commit comments