1"""
2This module provides REST API handlers for some requests concerning scenarios.
3"""
4import warnings
5import os
6import falcon
7
8from ..base_handler import BaseHandler
9from ..res_manager import ResourceManager
10from ...utils import get_temp_folder, pack_zip_archive
11from ...simulation import ScenarioSimulator, SensorConfig
12
13
[docs]
14class ScenarioManager(ResourceManager):
15 """
16 Class for managing all scenarios that are currently used by the REST API.
17 """
[docs]
18 def create(self, **kwds) -> str:
19 """
20 Creates a new scenario -- e.g. loading a given .inp file or
21 using a given scenario configuration.
22
23 Returns
24 -------
25 `str`
26 UUID of the new scenario.
27 """
28 return self.create_new_item(ScenarioSimulator(**kwds))
29
[docs]
30 def close_item(self, item: ScenarioSimulator) -> None:
31 item.close()
32
33
[docs]
34class ScenarioBaseHandler(BaseHandler):
35 """
36 Base class for all handlers concerning scenarios.
37
38 Parameters
39 ----------
40 scenario_mgr : :class:`~epyt_flow.rest_api.scenario.handlers.ScenarioManager`
41 Instance for managing all scenarios.
42 """
43 def __init__(self, scenario_mgr: ScenarioManager):
44 self.scenario_mgr = scenario_mgr
45
46
[docs]
47class ScenarioRemoveHandler(ScenarioBaseHandler):
48 """
49 Class for handling a DELETE request for a given scenario.
50 """
[docs]
51 def on_delete(self, _, resp: falcon.Response, scenario_id: str) -> None:
52 """
53 Deletes a given scenario.
54
55 Parameters
56 ----------
57 resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
58 Response instance.
59 scenario_id : `str`
60 UUID of the scenario.
61 """
62 try:
63 if self.scenario_mgr.validate_uuid(scenario_id) is False:
64 self.send_invalid_resource_id_error(resp)
65 return
66
67 self.scenario_mgr.remove(scenario_id)
68 except Exception as ex:
69 warnings.warn(str(ex))
70 resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
71
72
[docs]
73class ScenarioExportHandler(ScenarioBaseHandler):
74 """
75 Class for handling GET requests for exporting a given scenario to EPANET files
76 -- i.e. .inp and (otpionally) .msx files.
77 """
78 def __create_temp_file_path(self, scenario_id: str, file_ext: str) -> None:
79 """
80 Returns a path to a temporary file for storing the scenario.
81
82 Parameters
83 ----------
84 scenario_id : `str`
85 UUID of the scenario.
86 file_ext : `str`
87 File extension.
88 """
89 return os.path.join(get_temp_folder(), f"{scenario_id}.{file_ext}")
90
91 def __send_temp_file(self, resp: falcon.Response, tmp_file: str,
92 content_type: str = "application/octet-stream") -> None:
93 """
94 Sends a given file (`tmp_file`) to the the client.
95
96 Parameters
97 ----------
98 resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
99 Response instance.
100 tmp_file : `str`
101 Path to the temporary file to be send.
102 """
103 resp.status = falcon.HTTP_200
104 resp.content_type = content_type
105 with open(tmp_file, 'rb') as f:
106 resp.text = f.read()
107
[docs]
108 def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None:
109 """
110 Exports a given scenario to an .inp and (optionally) .msx file.
111
112 Parameters
113 ----------
114 resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
115 Response instance.
116 scenario_id : `str`
117 UUID of the scenario.
118 """
119 try:
120 if self.scenario_mgr.validate_uuid(scenario_id) is False:
121 self.send_invalid_resource_id_error(resp)
122 return
123
124 my_scenario = self.scenario_mgr.get(scenario_id)
125
126 f_inp_out = self.__create_temp_file_path(scenario_id, "inp")
127 f_msx_out = self.__create_temp_file_path(scenario_id, "msx")
128 my_scenario.save_to_epanet_file(f_inp_out, f_msx_out)
129
130 if os.path.isfile(f_msx_out):
131 f_out = self.__create_temp_file_path(scenario_id, "zip")
132 pack_zip_archive([f_inp_out, f_msx_out], f_out)
133
134 self.__send_temp_file(resp, f_out)
135 os.remove(f_out)
136 os.remove(f_msx_out)
137 else:
138 self.__send_temp_file(resp, f_inp_out)
139 os.remove(f_inp_out)
140 except Exception as ex:
141 warnings.warn(str(ex))
142 resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
143
144
[docs]
145class ScenarioConfigHandler(ScenarioBaseHandler):
146 """
147 Class for handling a GET request for getting the scenario configuration of a given scenario.
148 """
[docs]
149 def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None:
150 """
151 Gets the scenario configuration of a given scenario.
152
153 Parameters
154 ----------
155 resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
156 Response instance.
157 scenario_id : `str`
158 UUID of the scenario.
159 """
160 try:
161 if self.scenario_mgr.validate_uuid(scenario_id) is False:
162 self.send_invalid_resource_id_error(resp)
163 return
164
165 my_sceanrio_config = self.scenario_mgr.get(scenario_id).get_scenario_config()
166 self.send_json_response(resp, my_sceanrio_config)
167 except Exception as ex:
168 warnings.warn(str(ex))
169 resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
170
171
[docs]
172class ScenarioNewHandler(ScenarioBaseHandler):
173 """
174 Class for handling POST requests for creating a new scenario.
175 """
[docs]
176 def on_post(self, req: falcon.Request, resp: falcon.Response) -> None:
177 """
178 Creates/Loads a new scenario.
179
180 Parameters
181 ----------
182 req : `falcon.Request <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#request>`_
183 Request instance.
184 resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
185 Response instance.
186 scenario_id : `str`
187 UUID of the scenario.
188 """
189 try:
190 args = self.load_json_data_from_request(req)
191 scenario_id = self.scenario_mgr.create(**args)
192 self.send_json_response(resp, {"scenario_id": scenario_id})
193 except Exception as ex:
194 warnings.warn(str(ex))
195 resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
196
197
[docs]
198class ScenarioTopologyHandler(ScenarioBaseHandler):
199 """
200 Class for handling GET requests for getting the topology of a given scenario.
201 """
[docs]
202 def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None:
203 """
204 Gets the topology of a given scenario.
205
206 Parameters
207 ----------
208 resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
209 Response instance.
210 scenario_id : `str`
211 UUID of the scenario.
212 """
213 try:
214 if self.scenario_mgr.validate_uuid(scenario_id) is False:
215 self.send_invalid_resource_id_error(resp)
216 return
217
218 my_topology = self.scenario_mgr.get(scenario_id).get_topology()
219 self.send_json_response(resp, my_topology)
220 except Exception as ex:
221 warnings.warn(str(ex))
222 resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
223
224
[docs]
225class ScenarioGeneralParamsHandler(ScenarioBaseHandler):
226 """
227 Class for handling GET and POST requests for the general parameters of a given scenario.
228 """
[docs]
229 def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None:
230 """
231 Gets the general parameters (e.g. simulation duration, etc.) of a given scenario.
232
233 Parameters
234 ----------
235 resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
236 Response instance.
237 scenario_id : `str`
238 UUID of the scenario.
239 """
240 try:
241 if self.scenario_mgr.validate_uuid(scenario_id) is False:
242 self.send_invalid_resource_id_error(resp)
243 return
244
245 my_general_params = self.scenario_mgr.get(scenario_id).get_scenario_config().\
246 general_params
247 self.send_json_response(resp, my_general_params)
248 except Exception as ex:
249 warnings.warn(str(ex))
250 resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
251
[docs]
252 def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) -> None:
253 """
254 Sets the general parameters of a given scenario.
255
256 Parameters
257 ----------
258 req : `falcon.Request <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#request>`_
259 Request instance.
260 resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
261 Request instance.
262 scenario_id : `str`
263 UUID of the scenario.
264 """
265 try:
266 if self.scenario_mgr.validate_uuid(scenario_id) is False:
267 self.send_invalid_resource_id_error(resp)
268 return
269
270 general_params = self.load_json_data_from_request(req)
271 if not isinstance(general_params, dict):
272 self.send_json_parsing_error(resp)
273 return
274
275 self.scenario_mgr.get(scenario_id).set_general_parameters(**general_params)
276 except Exception as ex:
277 warnings.warn(str(ex))
278 resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
279
280
[docs]
281class ScenarioQualityParamsHandler(ScenarioBaseHandler):
282 """
283 Class for handling POST requests for specifying (EPANET) quality parameters of a given scenario.
284 """
[docs]
285 def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) -> None:
286 """
287 Specifies the (EPANET) quality parameters of a given scenario.
288
289 Parameters
290 ----------
291 req : `falcon.Request <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#request>`_
292 Request instance.
293 resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
294 Request instance.
295 scenario_id : `str`
296 UUID of the scenario.
297 """
298 try:
299 if self.scenario_mgr.validate_uuid(scenario_id) is False:
300 self.send_invalid_resource_id_error(resp)
301 return
302
303 quality_params = self.load_json_data_from_request(req)
304 if not isinstance(quality_params, dict):
305 self.send_json_parsing_error(resp)
306 return
307
308 self.scenario_mgr.get(scenario_id).set_quality_parameters(**quality_params)
309 except Exception as ex:
310 warnings.warn(str(ex))
311 resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
312
313
[docs]
314class ScenarioSensorConfigHandler(ScenarioBaseHandler):
315 """
316 Class for handling GET and POST requests for the sensor configuration of a given scenario.
317 """
[docs]
318 def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None:
319 """
320 Gets the sensor configuration of a given scenario.
321
322 Parameters
323 ----------
324 resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
325 Response instance.
326 scenario_id : `str`
327 UUID of the scenario.
328 """
329 try:
330 if self.scenario_mgr.validate_uuid(scenario_id) is False:
331 self.send_invalid_resource_id_error(resp)
332 return
333
334 my_sensor_config = self.scenario_mgr.get(scenario_id).sensor_config
335 self.send_json_response(resp, my_sensor_config)
336 except Exception as ex:
337 warnings.warn(str(ex))
338 resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
339
[docs]
340 def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) -> None:
341 """
342 Sets the sensor configuration of a given scenario.
343
344 Parameters
345 ----------
346 req : `falcon.Request <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#request>`_
347 Request instance.
348 resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
349 Request instance.
350 scenario_id : `str`
351 UUID of the scenario.
352 """
353 try:
354 if self.scenario_mgr.validate_uuid(scenario_id) is False:
355 self.send_invalid_resource_id_error(resp)
356 return
357
358 sensor_config = self.load_json_data_from_request(req)
359 if not isinstance(sensor_config, SensorConfig):
360 self.send_json_parsing_error(resp)
361 return
362
363 my_scenario = self.scenario_mgr.get(scenario_id)
364 my_scenario.sensor_config = sensor_config
365 except Exception as ex:
366 warnings.warn(str(ex))
367 resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
368
369
[docs]
370class ScenarioNodeDemandPatternHandler(ScenarioBaseHandler):
371 """
372 Class for handling POST requests for node demand patterns of a given scenario.
373 """
[docs]
374 def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str,
375 node_id: str) -> None:
376 """
377 Sets the demand pattern of a specific node in a given scenario.
378
379 Parameters
380 ----------
381 req : `falcon.Request <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#request>`_
382 Request instance.
383 resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
384 Response instance.
385 scenario_id : `str`
386 UUID of the scenario.
387 node_id : `str`
388 ID of the node.
389 """
390 try:
391 if self.scenario_mgr.validate_uuid(scenario_id) is False:
392 self.send_invalid_resource_id_error(resp)
393 return
394
395 params = self.load_json_data_from_request(req)
396
397 my_scenario = self.scenario_mgr.get(scenario_id)
398 my_scenario.set_node_demand_pattern(node_id, params["base_demand"],
399 params["demand_pattern_id"],
400 params["demand_pattern"])
401 except Exception as ex:
402 warnings.warn(str(ex))
403 resp.data = str(ex)
404 resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR