Skip to content

Commit bba4638

Browse files
committed
ENH: add implementations for add_node, delete_nodes, get_children API
1 parent e3838fd commit bba4638

File tree

5 files changed

+162
-16
lines changed

5 files changed

+162
-16
lines changed

src/save_and_restore_api/_api_async.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,17 @@ async def login(self, *, username=None, password=None):
4646
async def get_node(self, node_uid):
4747
method, url = self._prepare_get_node(node_uid=node_uid)
4848
return await self.send_request(method, url)
49+
50+
async def add_node(self, parentNodeId, *, name, nodeType, **kwargs):
51+
method, url, params = self._prepare_add_node(
52+
parentNodeId=parentNodeId, name=name, nodeType=nodeType, **kwargs
53+
)
54+
return await self.send_request(method, url, params=params)
55+
56+
async def delete_nodes(self, uniqueIds):
57+
method, url, params = self._prepare_delete_nodes(uniqueIds=uniqueIds)
58+
return await self.send_request(method, url, params=params)
59+
60+
async def get_children(self, node_uid):
61+
method, url = self._prepare_get_children(node_uid=node_uid)
62+
return await self.send_request(method, url)

src/save_and_restore_api/_api_base.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def set_auth(self, *, username, password):
8686

8787
def _process_response(self, *, client_response):
8888
client_response.raise_for_status()
89-
response = client_response.json()
89+
response = client_response.json() if client_response.content else ""
9090
return response
9191

9292
def _process_comm_exception(self, *, method, params, client_response):
@@ -147,8 +147,23 @@ def _prepare_get_node(self, *, node_uid):
147147
method, url = "GET", f"/node/{node_uid}"
148148
return method, url
149149

150-
def get_children(self, node_uid):
151-
return self.send_request("GET", f"/node/{node_uid}/children")
150+
def _prepare_add_node(self, *, parentNodeId, name, nodeType, **kwargs):
151+
node_types = ("FOLDER", "CONFIGURATION")
152+
if nodeType not in node_types:
153+
raise self.RequestParameterError(f"Invalid nodeType: {nodeType!r}. Supported types: {node_types}.")
154+
method, url = "PUT", f"/node?parentNodeId={parentNodeId}"
155+
params = kwargs
156+
params.update({"name": name, "nodeType": nodeType})
157+
return method, url, params
158+
159+
def _prepare_delete_nodes(self, *, uniqueIds):
160+
method, url = "DELETE", "/node"
161+
params = uniqueIds
162+
return method, url, params
163+
164+
def _prepare_get_children(self, *, node_uid):
165+
method, url = "GET", f"/node/{node_uid}/children"
166+
return method, url
152167

153168
def create_config(self, parent_node_uid, name, pv_list):
154169
config_dict = {

src/save_and_restore_api/_api_threads.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,22 @@ def login(self, *, username=None, password=None):
4444
return self.send_request(method, url, params=params)
4545

4646
def get_node(self, node_uid):
47+
"""
48+
Returns the node with specified node UID.
49+
"""
4750
method, url = self._prepare_get_node(node_uid=node_uid)
4851
return self.send_request(method, url)
52+
53+
def add_node(self, parentNodeId, *, name, nodeType, **kwargs):
54+
method, url, params = self._prepare_add_node(
55+
parentNodeId=parentNodeId, name=name, nodeType=nodeType, **kwargs
56+
)
57+
return self.send_request(method, url, params=params)
58+
59+
def delete_nodes(self, uniqueIds):
60+
method, url, params = self._prepare_delete_nodes(uniqueIds=uniqueIds)
61+
return self.send_request(method, url, params=params)
62+
63+
def get_children(self, node_uid):
64+
method, url = self._prepare_get_children(node_uid=node_uid)
65+
return self.send_request(method, url)

tests/common.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,53 @@
1+
import pytest
2+
3+
from save_and_restore_api import SaveRestoreAPI
4+
5+
base_url = "http://localhost:8080/save-restore"
6+
7+
admin_username, admin_password = "admin", "adminPass"
8+
user_username, user_password = "user", "userPass"
9+
read_username, read_password = "johndoe", "1234"
10+
11+
112
def _is_async(library):
213
if library == "ASYNC":
314
return True
415
elif library == "THREADS":
516
return False
617
else:
718
raise ValueError(f"Unknown library: {library!r}")
19+
20+
21+
@pytest.fixture
22+
def clear_sar():
23+
"""
24+
Clear save-and-restore database before and after the experiment.
25+
"""
26+
27+
def _clear():
28+
"""
29+
Remove all nodes from the database.
30+
"""
31+
with SaveRestoreAPI(base_url=base_url, timeout=2) as SR:
32+
SR.set_auth(username=user_username, password=user_password)
33+
34+
# Create all nodes. Children always follow the parent
35+
n_uid = 0
36+
uids = [SR.ROOT_NODE_UID]
37+
while n_uid < len(uids):
38+
uid = uids[n_uid]
39+
res_1 = SR.get_node(uid)
40+
if res_1["nodeType"] == "FOLDER":
41+
res_2 = SR.get_children(uid)
42+
ch_uids = [_["uniqueId"] for _ in res_2]
43+
if ch_uids:
44+
uids.extend(ch_uids)
45+
n_uid += 1
46+
47+
# Delete all nodes starting with children
48+
for uid in reversed(uids[1:]):
49+
SR.delete_nodes([uid])
50+
51+
_clear()
52+
yield
53+
_clear()

tests/test_package.py

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@
99
from save_and_restore_api import SaveRestoreAPI as SaveRestoreAPI_Threads
1010
from save_and_restore_api.aio import SaveRestoreAPI as SaveRestoreAPI_Async
1111

12-
from .common import _is_async
13-
14-
admin_username, admin_password = "admin", "adminPass"
15-
user_username, user_password = "user", "userPass"
16-
read_username, read_password = "johndoe", "1234"
17-
18-
base_url = "http://localhost:8080/save-restore"
12+
from .common import (
13+
_is_async,
14+
admin_password,
15+
admin_username,
16+
base_url,
17+
clear_sar, # noqa: F401
18+
read_password,
19+
read_username,
20+
user_password,
21+
user_username,
22+
)
1923

2024

2125
def test_version_01():
@@ -77,7 +81,6 @@ def test_login_01(username, password, roles, library, code):
7781
"""
7882
if not _is_async(library):
7983
with SaveRestoreAPI_Threads(base_url=base_url, timeout=2) as SR:
80-
SR.open()
8184
if code == 200:
8285
response = SR.login(username=username, password=password)
8386
assert response["userName"] == username
@@ -88,7 +91,6 @@ def test_login_01(username, password, roles, library, code):
8891
else:
8992
async def testing():
9093
async with SaveRestoreAPI_Async(base_url=base_url, timeout=2) as SR:
91-
SR.open()
9294
if code == 200:
9395
response = await SR.login(username=username, password=password)
9496
assert response["userName"] == username
@@ -107,13 +109,12 @@ async def testing():
107109
("abc", 404),
108110
])
109111
# fmt: on
110-
def test_get_node_01(node_uid, library, code):
112+
def test_get_node_01(clear_sar, node_uid, library, code): # noqa: F811
111113
"""
112114
Tests for the 'login' API.
113115
"""
114116
if not _is_async(library):
115117
with SaveRestoreAPI_Threads(base_url=base_url, timeout=2) as SR:
116-
SR.open()
117118
if code == 200:
118119
response = SR.get_node(node_uid)
119120
assert response["uniqueId"] == node_uid
@@ -123,7 +124,6 @@ def test_get_node_01(node_uid, library, code):
123124
else:
124125
async def testing():
125126
async with SaveRestoreAPI_Async(base_url=base_url, timeout=2) as SR:
126-
SR.open()
127127
if code == 200:
128128
response = await SR.get_node(node_uid)
129129
assert response["uniqueId"] == node_uid
@@ -134,8 +134,62 @@ async def testing():
134134
asyncio.run(testing())
135135

136136

137+
# fmt: off
138+
@pytest.mark.parametrize("library", ["THREADS", "ASYNC"])
139+
# fmt: on
140+
def test_add_node_01(clear_sar, library): # noqa: F811
141+
"""
142+
Tests for the 'add_node' API.
143+
"""
144+
if not _is_async(library):
145+
with SaveRestoreAPI_Threads(base_url=base_url, timeout=2) as SR:
146+
SR.set_auth(username=user_username, password=user_password)
147+
148+
response = SR.add_node(SR.ROOT_NODE_UID, name="Test Folder", nodeType="FOLDER")
149+
assert response["name"] == "Test Folder"
150+
assert response["nodeType"] == "FOLDER"
151+
folder_uid = response["uniqueId"]
152+
153+
response = SR.add_node(folder_uid, name="Test Config 1", nodeType="CONFIGURATION")
154+
assert response["name"] == "Test Config 1"
155+
assert response["nodeType"] == "CONFIGURATION"
156+
node_uid_1 = response["uniqueId"]
157+
158+
response = SR.add_node(folder_uid, name="Test Config 2", nodeType="CONFIGURATION")
159+
assert response["name"] == "Test Config 2"
160+
assert response["nodeType"] == "CONFIGURATION"
161+
node_uid_2 = response["uniqueId"]
162+
163+
SR.delete_nodes([node_uid_1, node_uid_2])
164+
SR.delete_nodes([folder_uid])
165+
166+
else:
167+
async def testing():
168+
async with SaveRestoreAPI_Async(base_url=base_url, timeout=2) as SR:
169+
SR.set_auth(username=user_username, password=user_password)
170+
171+
response = await SR.add_node(SR.ROOT_NODE_UID, name="Test Folder", nodeType="FOLDER")
172+
assert response["name"] == "Test Folder"
173+
assert response["nodeType"] == "FOLDER"
174+
folder_uid = response["uniqueId"]
175+
176+
response = await SR.add_node(folder_uid, name="Test Config 1", nodeType="CONFIGURATION")
177+
assert response["name"] == "Test Config 1"
178+
assert response["nodeType"] == "CONFIGURATION"
179+
node_uid_1 = response["uniqueId"]
180+
181+
response = await SR.add_node(folder_uid, name="Test Config 2", nodeType="CONFIGURATION")
182+
assert response["name"] == "Test Config 2"
183+
assert response["nodeType"] == "CONFIGURATION"
184+
node_uid_2 = response["uniqueId"]
185+
186+
await SR.delete_nodes([node_uid_1, node_uid_2])
187+
await SR.delete_nodes([folder_uid])
188+
189+
asyncio.run(testing())
190+
137191

138-
def test_comm():
192+
def test_comm(clear_sar): # noqa: F811
139193
SR = SaveRestoreAPI_Threads(base_url=base_url, timeout=2)
140194
SR.set_auth(username=user_username, password=user_password)
141195
SR.open()

0 commit comments

Comments
 (0)