Skip to content

Commit 7c5c90d

Browse files
committed
ENH: add error processing for 'send_request'
1 parent a357a96 commit 7c5c90d

File tree

6 files changed

+189
-114
lines changed

6 files changed

+189
-114
lines changed

container/save-and-restore.yml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
# Running elasticsearch in docker
2-
# sudo docker compose -f save-and-restore.yml up -d
3-
# Test:
4-
# curl -X GET "http://localhost:9200/"
5-
6-
# .env file:
7-
#
8-
# HOST_EXTERNAL_IP_ADDRESS=192.168.50.49
9-
101
services:
112
saveandrestore:
123
image: ghcr.io/controlsystemstudio/phoebus/service-save-and-restore:master

container/start-save-and-restore.sh

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,3 @@ set -x
33
python create_env_file.py
44
sudo docker compose -f save-and-restore.yml up -d
55
python wait_for_startup.py
6-
7-
# Wait until the service is started.
8-
#sleep 30

src/save_and_restore_api/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from __future__ import annotations
88

9+
from ._api import SaveRestoreAPI
910
from ._version import version as __version__
1011

11-
__all__ = ["__version__"]
12+
__all__ = ["__version__", "SaveRestoreAPI"]

src/save_and_restore_api/_api.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import getpass
2+
import pprint
3+
from collections.abc import Mapping
4+
5+
import httpx
6+
7+
8+
class RequestParameterError(Exception): ...
9+
10+
11+
class HTTPRequestError(httpx.RequestError): ...
12+
13+
14+
class HTTPClientError(httpx.HTTPStatusError): ...
15+
16+
17+
class HTTPServerError(httpx.HTTPStatusError): ...
18+
19+
20+
class RequestTimeoutError(TimeoutError):
21+
def __init__(self, msg, request):
22+
msg = f"Request timeout: {msg}"
23+
self.request = request
24+
super().__init__(msg)
25+
26+
27+
class RequestFailedError(Exception):
28+
def __init__(self, request, response):
29+
msg = response.get("msg", "") if isinstance(response, Mapping) else str(response)
30+
msg = msg or "(no error message)"
31+
msg = f"Request failed: {msg}"
32+
self.request = request
33+
self.response = response
34+
super().__init__(msg)
35+
36+
37+
class SaveRestoreAPI:
38+
RequestParameterError = RequestParameterError
39+
RequestTimeoutError = RequestTimeoutError
40+
RequestFailedError = RequestFailedError
41+
HTTPRequestError = HTTPRequestError
42+
HTTPClientError = HTTPClientError
43+
HTTPServerError = HTTPServerError
44+
45+
def __init__(self, *, base_url, timeout, request_fail_exceptions=True):
46+
self._base_url = base_url
47+
self._timeout = timeout
48+
self._client = None
49+
self._root_node_uid = "44bef5de-e8e6-4014-af37-b8f6c8a939a2"
50+
51+
self._username = None
52+
self._password = None
53+
# self._username = "dgavrilov"
54+
# self._password = "zelenyi.gena.krokodil"
55+
56+
@property
57+
def ROOT_NODE_UID(self):
58+
return self._root_node_uid
59+
60+
def open(self):
61+
auth = httpx.BasicAuth(username=self._username, password=self._password)
62+
self._client = httpx.Client(base_url=self._base_url, timeout=self._timeout, auth=auth)
63+
64+
def close(self):
65+
self._client.close()
66+
self._client = None
67+
68+
def set_username_password(self, username=None, password=None):
69+
if not isinstance(username, str):
70+
print("Username: ", end="")
71+
username = input()
72+
if not isinstance(password, str):
73+
password = getpass.getpass()
74+
75+
self._username = username
76+
self._password = password
77+
78+
# # TODO: rewrite the logic in this function
79+
# def _check_response(self, *, request, response):
80+
# """
81+
# Check if response is a dictionary and has ``"success": True``. Raise an exception
82+
# if the request is considered failed and exceptions are allowed. If response is
83+
# a dictionary and contains no ``"success"``, then it is considered successful.
84+
# """
85+
# if self._request_fail_exceptions:
86+
# # The response must be a list or a dictionary. If the response is a dictionary
87+
# # and the key 'success': False, then consider the request failed. If there
88+
# # is not 'success' key, then consider the request successful.
89+
# is_iterable = isinstance(response, Iterable) and not isinstance(response, str)
90+
# is_mapping = isinstance(response, Mapping)
91+
# if not any([is_iterable, is_mapping]) or (is_mapping and not response.get("success", True)):
92+
# raise self.RequestFailedError(request, response)
93+
94+
def _process_response(self, *, client_response):
95+
client_response.raise_for_status()
96+
response = client_response.json()
97+
return response
98+
99+
def _process_comm_exception(self, *, method, params, client_response):
100+
"""
101+
The function must be called from ``except`` block and returns response with an error message
102+
or raises an exception.
103+
"""
104+
try:
105+
raise
106+
107+
except httpx.TimeoutException as ex:
108+
raise self.RequestTimeoutError(ex, {"method": method, "params": params}) from ex
109+
110+
except httpx.RequestError as ex:
111+
raise self.HTTPRequestError(f"HTTP request error: {ex}") from ex
112+
113+
except httpx.HTTPStatusError as exc:
114+
common_params = {"request": exc.request, "response": exc.response}
115+
if client_response and (client_response.status_code < 500):
116+
# Include more detail that httpx does by default.
117+
message = (
118+
f"{exc.response.status_code}: "
119+
f"{exc.response.json()['detail'] if client_response.content else ''} "
120+
f"{exc.request.url}"
121+
)
122+
raise self.HTTPClientError(message, **common_params) from exc
123+
else:
124+
raise self.HTTPServerError(exc, **common_params) from exc
125+
126+
def send_request(self, method, url, *, params=None, url_params=None, headers=None, data=None, timeout=None):
127+
try:
128+
client_response = None
129+
kwargs = {}
130+
if params:
131+
kwargs.update({"json": params})
132+
if url_params:
133+
kwargs.update({"params": url_params})
134+
if headers:
135+
kwargs.update({"headers": headers})
136+
if data:
137+
kwargs.update({"data": data})
138+
if timeout is not None:
139+
kwargs.update({"timeout": self._adjust_timeout(timeout)})
140+
client_response = self._client.request(method, url, **kwargs)
141+
response = self._process_response(client_response=client_response)
142+
except Exception:
143+
response = self._process_comm_exception(method=method, params=params, client_response=client_response)
144+
145+
return response
146+
147+
def login(self, *, username=None, password=None):
148+
params = {"username": self._username, "password": self._password}
149+
self.send_request("POST", "/login", params=params)
150+
151+
def get_node(self, node_uid):
152+
return self.send_request("GET", f"/node/{node_uid}")
153+
154+
def get_children(self, node_uid):
155+
return self.send_request("GET", f"/node/{node_uid}/children")
156+
157+
def create_config(self, parent_node_uid, name, pv_list):
158+
config_dict = {
159+
"configurationNode": {
160+
"name": name,
161+
"nodeType": "CONFIGURATION",
162+
"userName": self._username,
163+
},
164+
"configurationData": {
165+
"pvList": pv_list,
166+
},
167+
}
168+
print(f"config_dict=\n{pprint.pformat(config_dict)}")
169+
return self.send_request("PUT", f"/config?parentNodeId={parent_node_uid}", json=config_dict)
170+
171+
def update_config(self, node_uid, name, pv_list):
172+
config_dict = {
173+
"configurationNode": {
174+
"name": name,
175+
"nodeType": "CONFIGURATION",
176+
"userName": self._username,
177+
"uniqueId": node_uid,
178+
},
179+
"configurationData": {
180+
"pvList": pv_list,
181+
},
182+
}
183+
print(f"config_dict=\n{pprint.pformat(config_dict)}")
184+
# return self.send_request("POST", f"/config/{node_uid}", json=config_dict)
185+
return self.send_request("POST", "/config", json=config_dict)

src/save_and_restore_api/api.py

Lines changed: 0 additions & 99 deletions
This file was deleted.

tests/test_package.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
import importlib.metadata
44

55
import save_and_restore_api as m
6-
from save_and_restore_api.tools.upload import SaveRestoreAPI
6+
from save_and_restore_api import SaveRestoreAPI
77

88

99
def test_version():
1010
assert importlib.metadata.version("save_and_restore_api") == m.__version__
1111

1212

1313
def test_import():
14-
from save_and_restore_api.tools.upload import SaveRestoreAPI # noqa: F401
14+
from save_and_restore_api import SaveRestoreAPI # noqa: F401
1515

1616

1717
def test_comm():

0 commit comments

Comments
 (0)