Skip to content

Commit a357a96

Browse files
committed
ENH: refactor of the 'upload' CLI
1 parent 3d10365 commit a357a96

File tree

2 files changed

+214
-123
lines changed

2 files changed

+214
-123
lines changed

src/save_and_restore_api/api.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import getpass
2+
3+
# import logging
4+
import pprint
5+
6+
import httpx
7+
8+
9+
class SaveRestoreAPI:
10+
def __init__(self, *, base_url, timeout):
11+
self._base_url = base_url
12+
self._timeout = timeout
13+
self._client = None
14+
self._root_node_uid = "44bef5de-e8e6-4014-af37-b8f6c8a939a2"
15+
16+
self._username = None
17+
self._password = None
18+
# self._username = "dgavrilov"
19+
# self._password = "zelenyi.gena.krokodil"
20+
21+
@property
22+
def ROOT_NODE_UID(self):
23+
return self._root_node_uid
24+
25+
def open(self):
26+
auth = httpx.BasicAuth(username=self._username, password=self._password)
27+
self._client = httpx.Client(base_url=self._base_url, timeout=self._timeout, auth=auth)
28+
29+
def close(self):
30+
self._client.close()
31+
self._client = None
32+
33+
def set_username_password(self, username=None, password=None):
34+
if not isinstance(username, str):
35+
print("Username: ", end="")
36+
username = input()
37+
if not isinstance(password, str):
38+
password = getpass.getpass()
39+
40+
self._username = username
41+
self._password = password
42+
43+
def login(self, *, username=None, password=None):
44+
params = {"username": self._username, "password": self._password}
45+
self.send_request("POST", "/login", json=params)
46+
47+
def send_request(self, method, url, **kwargs):
48+
response = self._client.request(method, url, **kwargs)
49+
50+
print(f"{response.request.url=}")
51+
print(f"{response.headers.get('content-type')=}")
52+
53+
if response.status_code != 200:
54+
print(f"Request failed: status code {response.status_code}")
55+
print(f"Error message: {response.text}")
56+
raise Exception(f"Request failed with code {response.status_code}")
57+
58+
if response.headers.get("content-type") == "application/json":
59+
data = response.json()
60+
else:
61+
data = {}
62+
63+
return data
64+
65+
def get_node(self, node_uid):
66+
return self.send_request("GET", f"/node/{node_uid}")
67+
68+
def get_children(self, node_uid):
69+
return self.send_request("GET", f"/node/{node_uid}/children")
70+
71+
def create_config(self, parent_node_uid, name, pv_list):
72+
config_dict = {
73+
"configurationNode": {
74+
"name": name,
75+
"nodeType": "CONFIGURATION",
76+
"userName": self._username,
77+
},
78+
"configurationData": {
79+
"pvList": pv_list,
80+
},
81+
}
82+
print(f"config_dict=\n{pprint.pformat(config_dict)}")
83+
return self.send_request("PUT", f"/config?parentNodeId={parent_node_uid}", json=config_dict)
84+
85+
def update_config(self, node_uid, name, pv_list):
86+
config_dict = {
87+
"configurationNode": {
88+
"name": name,
89+
"nodeType": "CONFIGURATION",
90+
"userName": self._username,
91+
"uniqueId": node_uid,
92+
},
93+
"configurationData": {
94+
"pvList": pv_list,
95+
},
96+
}
97+
print(f"config_dict=\n{pprint.pformat(config_dict)}")
98+
# return self.send_request("POST", f"/config/{node_uid}", json=config_dict)
99+
return self.send_request("POST", "/config", json=config_dict)
Lines changed: 115 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import getpass
1+
import argparse
2+
3+
# import getpass
24
import logging
3-
import pprint
5+
import os
6+
7+
# import pprint
8+
import save_and_restore_api
49

5-
import httpx
10+
version = save_and_restore_api.__version__
611

712
logger = logging.getLogger(__name__)
813

@@ -11,99 +16,6 @@
1116
file_name = "auto_settings.sav"
1217

1318

14-
class SaveRestoreAPI:
15-
def __init__(self, *, base_url, timeout):
16-
self._base_url = base_url
17-
self._timeout = timeout
18-
self._client = None
19-
self._root_node_uid = "44bef5de-e8e6-4014-af37-b8f6c8a939a2"
20-
21-
self._username = None
22-
self._password = None
23-
# self._username = "dgavrilov"
24-
# self._password = "zelenyi.gena.krokodil"
25-
26-
@property
27-
def ROOT_NODE_UID(self):
28-
return self._root_node_uid
29-
30-
def open(self):
31-
auth = httpx.BasicAuth(username=self._username, password=self._password)
32-
self._client = httpx.Client(base_url=self._base_url, timeout=timeout, auth=auth)
33-
34-
def close(self):
35-
self._client.close()
36-
self._client = None
37-
38-
def set_username_password(self, username=None, password=None):
39-
if not isinstance(username, str):
40-
print("Username: ", end="")
41-
username = input()
42-
if not isinstance(password, str):
43-
password = getpass.getpass()
44-
45-
self._username = username
46-
self._password = password
47-
48-
def login(self, *, username=None, password=None):
49-
params = {"username": self._username, "password": self._password}
50-
self.send_request("POST", "/login", json=params)
51-
52-
def send_request(self, method, url, **kwargs):
53-
response = self._client.request(method, url, **kwargs)
54-
55-
print(f"{response.request.url=}")
56-
print(f"{response.headers.get('content-type')=}")
57-
58-
if response.status_code != 200:
59-
print(f"Request failed: status code {response.status_code}")
60-
print(f"Error message: {response.text}")
61-
raise Exception(f"Request failed with code {response.status_code}")
62-
63-
if response.headers.get("content-type") == "application/json":
64-
data = response.json()
65-
else:
66-
data = {}
67-
68-
return data
69-
70-
def get_node(self, node_uid):
71-
return self.send_request("GET", f"/node/{node_uid}")
72-
73-
def get_children(self, node_uid):
74-
return self.send_request("GET", f"/node/{node_uid}/children")
75-
76-
def create_config(self, parent_node_uid, name, pv_list):
77-
config_dict = {
78-
"configurationNode": {
79-
"name": name,
80-
"nodeType": "CONFIGURATION",
81-
"userName": self._username,
82-
},
83-
"configurationData": {
84-
"pvList": pv_list,
85-
},
86-
}
87-
print(f"config_dict=\n{pprint.pformat(config_dict)}")
88-
return self.send_request("PUT", f"/config?parentNodeId={parent_node_uid}", json=config_dict)
89-
90-
def update_config(self, node_uid, name, pv_list):
91-
config_dict = {
92-
"configurationNode": {
93-
"name": name,
94-
"nodeType": "CONFIGURATION",
95-
"userName": self._username,
96-
"uniqueId": node_uid,
97-
},
98-
"configurationData": {
99-
"pvList": pv_list,
100-
},
101-
}
102-
print(f"config_dict=\n{pprint.pformat(config_dict)}")
103-
# return self.send_request("POST", f"/config/{node_uid}", json=config_dict)
104-
return self.send_request("POST", "/config", json=config_dict)
105-
106-
10719
def add_to_pv_list(pv_list, *, pv_name):
10820
pv_list.append({"pvName": pv_name})
10921

@@ -121,34 +33,114 @@ def load_pvs_from_autosave_file(file_name):
12133
return pv_names
12234

12335

36+
def split_config_name(config_name):
37+
if not config_name.startswith("/"):
38+
config_name = "/" + config_name
39+
_ = config_name.split("/")
40+
folders, name = _[1:-1], _[-1]
41+
return folders, name
42+
43+
12444
def main():
12545
logging.basicConfig(level=logging.WARNING)
126-
# logging.getLogger("bluesky_queueserver").setLevel("INFO")
46+
logging.getLogger("save-and-restore-api").setLevel("INFO")
47+
48+
def formatter(prog):
49+
# Set maximum width such that printed help mostly fits in the RTD theme code block (documentation).
50+
return argparse.RawDescriptionHelpFormatter(prog, max_help_position=20, width=90)
51+
52+
parser = argparse.ArgumentParser(
53+
description="save-and-restore-upload: create configuration based on a batch of PVs.\n"
54+
f"save-and-restore-api version {version}\n\n"
55+
"Read a batch of PVs from a file and creates a configuration in Save and Restore service.\n",
56+
formatter_class=formatter,
57+
)
58+
59+
parser.add_argument(
60+
"--file-name",
61+
"-f",
62+
dest="file_name",
63+
type=str,
64+
default=None,
65+
help="File name with PV names.",
66+
)
67+
68+
parser.add_argument(
69+
"--config-name",
70+
"-c",
71+
dest="config_name",
72+
type=str,
73+
default=None,
74+
help="Configuration name including folders, e.g. /detectors/imaging/eiger_config",
75+
)
76+
77+
parser.add_argument(
78+
"--create-folders",
79+
dest="create_folders",
80+
action="store_true",
81+
help="Configuration name including folders, e.g. /detectors/imaging/eiger_config",
82+
)
83+
84+
parser.add_argument(
85+
"--update",
86+
dest="config_update",
87+
action="store_true",
88+
help="Configuration name including folders, e.g. /detectors/imaging/eiger_config",
89+
)
90+
91+
args = parser.parse_args()
92+
file_name = args.file_name
93+
config_name = args.config_name
94+
create_folders = args.create_folders
95+
config_update = args.config_update
12796

128-
SR = SaveRestoreAPI(base_url=BASE_URL, timeout=timeout)
12997
try:
130-
pv_names = load_pvs_from_autosave_file(file_name)
131-
132-
SR.set_username_password()
133-
SR.open()
134-
SR.login()
135-
136-
data = SR.get_node(SR.ROOT_NODE_UID)
137-
print(f"data=\n{pprint.pformat(data)}")
138-
data = SR.get_children(data["uniqueId"])
139-
print(f"data=\n{pprint.pformat(data)}")
140-
parent_node_uid = data[0]["uniqueId"]
141-
name = "test5"
142-
pv_list = []
143-
for pv_name in pv_names:
144-
add_to_pv_list(pv_list, pv_name=pv_name)
145-
add_to_pv_list(pv_list, pv_name="13SIM1:{SimDetector-Cam:1}cam1:BinX")
146-
add_to_pv_list(pv_list, pv_name="13SIM1:{SimDetector-Cam:1}cam1:BinY")
147-
data = SR.create_config(parent_node_uid, name, pv_list)
148-
print(f"data=\n{pprint.pformat(data)}")
149-
node_uid = data["configurationNode"]["uniqueId"]
150-
data = SR.update_config(node_uid, name + "a", pv_list)
151-
print(f"data=\n{pprint.pformat(data)}")
152-
153-
finally:
154-
SR.close()
98+
if args.file_name is None:
99+
raise ValueError("Required '--file-name' ('-f') parameter is not specified")
100+
101+
if args.config_name is None:
102+
raise ValueError("Required '--config-name' ('-c') parameter is not specified")
103+
104+
file_name = os.path.abspath(os.path.expanduser(file_name))
105+
106+
print(f"file_name={file_name}")
107+
print(f"config_name={config_name}")
108+
print(f"create_folders={create_folders}")
109+
print(f"update={config_update}")
110+
111+
if not os.path.isfile(file_name):
112+
raise ValueError(f"Input file '{file_name}' does not exist")
113+
114+
folders, name = split_config_name(config_name)
115+
print(f"folders={folders}, name={name}")
116+
117+
except Exception as ex:
118+
logger.error(f"Failed: {ex}")
119+
120+
# SR = SaveRestoreAPI(base_url=BASE_URL, timeout=timeout)
121+
# try:
122+
# pv_names = load_pvs_from_autosave_file(file_name)
123+
124+
# SR.set_username_password()
125+
# SR.open()
126+
# SR.login()
127+
128+
# data = SR.get_node(SR.ROOT_NODE_UID)
129+
# print(f"data=\n{pprint.pformat(data)}")
130+
# data = SR.get_children(data["uniqueId"])
131+
# print(f"data=\n{pprint.pformat(data)}")
132+
# parent_node_uid = data[0]["uniqueId"]
133+
# name = "test5"
134+
# pv_list = []
135+
# for pv_name in pv_names:
136+
# add_to_pv_list(pv_list, pv_name=pv_name)
137+
# add_to_pv_list(pv_list, pv_name="13SIM1:{SimDetector-Cam:1}cam1:BinX")
138+
# add_to_pv_list(pv_list, pv_name="13SIM1:{SimDetector-Cam:1}cam1:BinY")
139+
# data = SR.create_config(parent_node_uid, name, pv_list)
140+
# print(f"data=\n{pprint.pformat(data)}")
141+
# node_uid = data["configurationNode"]["uniqueId"]
142+
# data = SR.update_config(node_uid, name + "a", pv_list)
143+
# print(f"data=\n{pprint.pformat(data)}")
144+
145+
# finally:
146+
# SR.close()

0 commit comments

Comments
 (0)