cvdlink 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. FeatureCloud/__init__.py +0 -0
  2. FeatureCloud/api/__init__.py +0 -0
  3. FeatureCloud/api/cli/__init__.py +0 -0
  4. FeatureCloud/api/cli/__main__.py +115 -0
  5. FeatureCloud/api/cli/app/__init__.py +0 -0
  6. FeatureCloud/api/cli/app/commands.py +182 -0
  7. FeatureCloud/api/cli/controller/__init__.py +0 -0
  8. FeatureCloud/api/cli/controller/commands.py +181 -0
  9. FeatureCloud/api/cli/test/__init__.py +0 -0
  10. FeatureCloud/api/cli/test/commands.py +251 -0
  11. FeatureCloud/api/cli/test/workflow/__init__.py +0 -0
  12. FeatureCloud/api/cli/test/workflow/commands.py +32 -0
  13. FeatureCloud/api/imp/__init__.py +0 -0
  14. FeatureCloud/api/imp/app/__init__.py +0 -0
  15. FeatureCloud/api/imp/app/commands.py +278 -0
  16. FeatureCloud/api/imp/controller/__init__.py +0 -0
  17. FeatureCloud/api/imp/controller/commands.py +246 -0
  18. FeatureCloud/api/imp/exceptions.py +29 -0
  19. FeatureCloud/api/imp/test/__init__.py +0 -0
  20. FeatureCloud/api/imp/test/api/__init__.py +0 -0
  21. FeatureCloud/api/imp/test/api/backend/__init__.py +0 -0
  22. FeatureCloud/api/imp/test/api/backend/auth.py +54 -0
  23. FeatureCloud/api/imp/test/api/backend/project.py +84 -0
  24. FeatureCloud/api/imp/test/api/controller.py +97 -0
  25. FeatureCloud/api/imp/test/commands.py +124 -0
  26. FeatureCloud/api/imp/test/helper.py +40 -0
  27. FeatureCloud/api/imp/util.py +45 -0
  28. FeatureCloud/app/__init__.py +0 -0
  29. FeatureCloud/app/api/__init__.py +0 -0
  30. FeatureCloud/app/api/http_ctrl.py +48 -0
  31. FeatureCloud/app/api/http_web.py +16 -0
  32. FeatureCloud/app/engine/__init__.py +0 -0
  33. FeatureCloud/app/engine/app.py +1214 -0
  34. FeatureCloud/app/engine/library.py +46 -0
  35. FeatureCloud/workflow/__init__.py +0 -0
  36. FeatureCloud/workflow/app.py +197 -0
  37. FeatureCloud/workflow/controller.py +17 -0
  38. FeatureCloud/workflow/example_wf.py +83 -0
  39. FeatureCloud/workflow/workflow.py +86 -0
  40. cvdlink-0.1.1.dist-info/METADATA +176 -0
  41. cvdlink-0.1.1.dist-info/RECORD +45 -0
  42. cvdlink-0.1.1.dist-info/WHEEL +5 -0
  43. cvdlink-0.1.1.dist-info/entry_points.txt +5 -0
  44. cvdlink-0.1.1.dist-info/licenses/LICENSE +201 -0
  45. cvdlink-0.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,246 @@
1
+ import tqdm
2
+ import time
3
+ import docker
4
+ import os
5
+
6
+ from FeatureCloud.api.imp.exceptions import FCException, ContainerNotFound
7
+ from FeatureCloud.api.imp.test.helper import http
8
+
9
+ from FeatureCloud.api.imp.util import getcwd_fslash, get_docker_client
10
+
11
+ DEFAULT_CONTROLLER_IMAGE = "featurecloud.ai/controller"
12
+ CONTROLLER_LABEL = "FCControllerLabel"
13
+ DEFAULT_PORT = 8000
14
+ DEFAULT_CONTROLLER_NAME = 'fc-controller'
15
+ DEFAULT_DATA_DIR = 'data'
16
+ LOG_FETCH_INTERVAL = 3 # seconds
17
+ LOG_LEVEL_CHOICES = ['debug', 'info', 'warn', 'error', 'fatal']
18
+
19
+
20
+ def start(
21
+ name: str,
22
+ port: int,
23
+ data_dir: str,
24
+ controller_image: str,
25
+ with_gpu: bool,
26
+ mount: str,
27
+ blockchain_address: str,
28
+ profile: str = None,
29
+ global_endpoint: str = "",
30
+ registry: str = "",
31
+ relay_address: str = "",
32
+ poll_interval: int = 0,
33
+ query_interval: int = 0,
34
+ config_file: str = "",
35
+ ):
36
+ client = get_docker_client()
37
+
38
+ data_dir = data_dir if data_dir else DEFAULT_DATA_DIR
39
+ if not controller_image:
40
+ if profile == "cvdlink":
41
+ controller_image = "fc.cvdlink-project.eu/controller:latest"
42
+ else:
43
+ controller_image = DEFAULT_CONTROLLER_IMAGE
44
+ # Create data dir if needed
45
+ try:
46
+ os.mkdir(data_dir)
47
+ except OSError as error:
48
+ pass
49
+
50
+ # cleanup unused controller containers and networks
51
+ prune_controllers()
52
+
53
+ # remove controller having the same name, if any
54
+ try:
55
+ client.api.remove_container(name, v=True, force=True)
56
+ except docker.errors.NotFound:
57
+ pass
58
+ except docker.errors.DockerException as e:
59
+ raise FCException(e)
60
+
61
+ # pull controller and display progress
62
+ try:
63
+ pull_proc = client.api.pull(repository=controller_image, stream=True)
64
+ for p in tqdm.tqdm(pull_proc, desc='Downloading...'):
65
+ pass
66
+ except docker.errors.DockerException as e:
67
+ raise FCException(e)
68
+
69
+ cont_name = name if name else DEFAULT_CONTROLLER_NAME
70
+ # forward slash works on all platforms
71
+ base_dir = getcwd_fslash()
72
+
73
+ # set the correct host folder to be mounted
74
+ if os.path.isabs(data_dir):
75
+ host_data_dir = data_dir
76
+ else:
77
+ host_data_dir = os.path.join(base_dir, data_dir)
78
+
79
+ volumes = [f'{host_data_dir}:/{data_dir}', '/var/run/docker.sock:/var/run/docker.sock']
80
+ if mount:
81
+ volumes.append(f'{mount}:/mnt')
82
+
83
+ # ---------- profile-based defaults ----------
84
+ # For featurecloud: use config.yml defaults unless user overrides via CLI.
85
+ # For cvdlink: provide sensible defaults if not explicitly set.
86
+ if profile == "cvdlink":
87
+ if not global_endpoint:
88
+ global_endpoint = "https://fc.cvdlink-project.eu/api/"
89
+ if not registry:
90
+ registry = "fc.cvdlink-project.eu"
91
+ if not relay_address:
92
+ relay_address = "relay.fc.cvdlink-project.eu"
93
+
94
+
95
+ # ---------- build controller command ----------
96
+ # NB: we keep the original style with a single command string.
97
+ cmd_parts = [
98
+ f"--host-root='{host_data_dir}'",
99
+ f"--internal-root=/{data_dir}",
100
+ f"--controller-name={cont_name}",
101
+ ]
102
+ device_requests = None
103
+ if with_gpu:
104
+ device_requests = [docker.types.DeviceRequest(count=-1, capabilities=[['gpu']])]
105
+ cmd_parts.append("--has-gpu")
106
+
107
+ if blockchain_address:
108
+ cmd_parts.append(f"--blockchain-address={blockchain_address}")
109
+
110
+ if config_file:
111
+ cmd_parts.append(f"--configfile={config_file}")
112
+
113
+ if global_endpoint:
114
+ cmd_parts.append(f"--endpoint='{global_endpoint}'")
115
+
116
+ if registry:
117
+ cmd_parts.append(f"--registry={registry}")
118
+
119
+ if relay_address:
120
+ cmd_parts.append(f"--address={relay_address}")
121
+
122
+ if poll_interval > 0:
123
+ cmd_parts.append(f"--poll-interval={poll_interval}")
124
+
125
+ if query_interval > 0:
126
+ cmd_parts.append(f"--query-interval={query_interval}")
127
+
128
+ command = " ".join(cmd_parts)
129
+
130
+ try:
131
+ client.containers.run(
132
+ controller_image,
133
+ detach=True,
134
+ name=cont_name,
135
+ ports={8000: port if port else DEFAULT_PORT},
136
+ volumes=volumes,
137
+ labels=[CONTROLLER_LABEL],
138
+ device_requests=device_requests,
139
+ command=command,
140
+ )
141
+ except docker.errors.DockerException as e:
142
+ raise FCException(e)
143
+ except Exception as e:
144
+ raise FCException(e)
145
+
146
+
147
+ def stop(name: str):
148
+ client = get_docker_client()
149
+
150
+ if not name:
151
+ name = DEFAULT_CONTROLLER_NAME
152
+
153
+ # Removing controllers filtered by name
154
+ removed = []
155
+ for container in client.containers.list(filters={"name": [name]}):
156
+ try:
157
+ client.api.remove_container(container.id, v=True, force=True)
158
+ removed.append(container.name)
159
+ except docker.errors.DockerException as e:
160
+ raise FCException(e)
161
+
162
+ return removed
163
+
164
+
165
+ def prune_controllers():
166
+ client = get_docker_client()
167
+
168
+ try:
169
+ client.containers.prune(filters={"label": [CONTROLLER_LABEL]})
170
+ except docker.errors.DockerException as e:
171
+ raise FCException(e)
172
+
173
+ # Remove the network
174
+ try:
175
+ # Attempt to remove the network
176
+ client.networks.get("fc-controller-internal").remove()
177
+ except docker.errors.NotFound:
178
+ pass
179
+ except docker.errors.APIError as e:
180
+ print(f"An API error occurred: {e}")
181
+ except Exception as e:
182
+ print(f"An unexpected error occurred: {e}")
183
+
184
+
185
+ def logs(name: str, tail: bool, log_level: str):
186
+ client = get_docker_client()
187
+
188
+ # get controller address
189
+ host_port = 0
190
+ try:
191
+ container = client.containers.get(name)
192
+ host_port = container.attrs['NetworkSettings']['Ports']['8000/tcp'][0]['HostPort']
193
+ except docker.errors.NotFound:
194
+ raise ContainerNotFound(name)
195
+ except docker.errors.DockerException as e:
196
+ raise FCException(e)
197
+
198
+ # Get logs content from controller
199
+ lines_fetched = 0
200
+ while True:
201
+ response = http.get(url=f'http://localhost:{host_port}/logs/?from={lines_fetched}')
202
+
203
+ if response.status_code == 200:
204
+ logs_content = response.json()
205
+ lines_fetched += len(logs_content)
206
+
207
+ log_level = valid_log_level(log_level)
208
+ filtered = filter_logs(logs_content, log_level)
209
+ for line in filtered:
210
+ yield line
211
+ else:
212
+ raise FCException(response.json('detail'))
213
+
214
+ if not tail:
215
+ break
216
+ time.sleep(LOG_FETCH_INTERVAL)
217
+
218
+
219
+ def status(name: str):
220
+ client = get_docker_client()
221
+ try:
222
+ return client.containers.get(name)
223
+ except docker.errors.NotFound:
224
+ raise ContainerNotFound(name)
225
+ except docker.errors.DockerException as e:
226
+ raise FCException(e)
227
+
228
+
229
+ def ls():
230
+ client = get_docker_client()
231
+ try:
232
+ return client.containers.list(filters={'label': [CONTROLLER_LABEL]})
233
+ except docker.errors.DockerException as e:
234
+ raise FCException(e)
235
+
236
+
237
+ def filter_logs(logs_json, log_level):
238
+ log_level_index = LOG_LEVEL_CHOICES.index(log_level)
239
+ return [line for line in logs_json if LOG_LEVEL_CHOICES.index(line['level']) >= log_level_index]
240
+
241
+
242
+ def valid_log_level(log_level):
243
+ if len(log_level) == 0 or log_level not in LOG_LEVEL_CHOICES:
244
+ log_level = LOG_LEVEL_CHOICES[0]
245
+
246
+ return log_level
@@ -0,0 +1,29 @@
1
+
2
+ class FCException(Exception):
3
+ """
4
+ Base class for FeatureCloud exceptions.
5
+ """
6
+ default_detail = 'an error occurred'
7
+
8
+ def __init__(self, detail=None):
9
+ if detail is None:
10
+ detail = self.default_detail
11
+
12
+ self.detail = detail
13
+
14
+ def __str__(self):
15
+ return str(self.detail)
16
+
17
+
18
+ class DockerNotAvailable(FCException):
19
+ default_detail = 'Docker daemon is not available. Please make sure Docker is started.'
20
+
21
+
22
+ class ControllerOffline(FCException):
23
+ def __init__(self, url=None):
24
+ super().__init__(f'Could not access controller on URL: {url}')
25
+
26
+
27
+ class ContainerNotFound(FCException):
28
+ def __init__(self, name=None):
29
+ super().__init__(f'Container not found: {name}')
File without changes
File without changes
File without changes
@@ -0,0 +1,54 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ from FeatureCloud.api.utils.cli.helper import http
5
+
6
+ home = str(Path.home())
7
+ url = 'http://127.0.0.1:7000'
8
+
9
+
10
+ def is_user_logged_in():
11
+ headers = create_authorization_header()
12
+ r = http.get(url=f'{url}/user/info/', headers=headers)
13
+ if r.status_code == 200:
14
+ return True
15
+ else:
16
+ print("No user is logged in. Please login first.")
17
+ exit()
18
+ return False
19
+
20
+
21
+ def login(email: str, password: str):
22
+ r = http.post(url=f'{url}/auth/login/',
23
+ json={'username': email,
24
+ 'password': password})
25
+ if r.status_code == 200:
26
+ refresh, access = r.json()['refresh'], r.json()['access']
27
+ login_details = {'email': email, 'password': password, 'refresh': refresh, 'access': access}
28
+ login_details = json.dumps(login_details)
29
+ file = open(f'{home}/.fc_cli', 'w')
30
+ file.write(login_details)
31
+ file.close()
32
+ return True
33
+ else:
34
+ return False
35
+
36
+
37
+ def logout():
38
+ r = http.post(url=f'{url}/auth/logout/',
39
+ json={'refresh': None})
40
+ if r.status_code == 200:
41
+ with open(f'{home}/.fc_cli', 'w') as file:
42
+ file.write('')
43
+ file.close()
44
+ return True
45
+ else:
46
+ print(r.content)
47
+ return False
48
+
49
+
50
+ def create_authorization_header():
51
+ with open(f'{home}/.fc_cli') as jsonFile:
52
+ access_token = json.load(jsonFile)['access']
53
+ jsonFile.close()
54
+ return {"Authorization": f'Bearer {access_token}'}
@@ -0,0 +1,84 @@
1
+ from pathlib import Path
2
+
3
+ from FeatureCloud.api.utils.cli.api.backend.auth import create_authorization_header
4
+ from FeatureCloud.api.utils.cli.helper import http
5
+
6
+ home = str(Path.home())
7
+ url = 'http://127.0.0.1:7000'
8
+
9
+
10
+ def info(project_id: str or int):
11
+ headers = create_authorization_header()
12
+ r = http.get(url=f'{url}/projects/{project_id}', headers=headers)
13
+ if r.status_code == 200:
14
+ return r.json()
15
+ else:
16
+ return False
17
+
18
+
19
+ def create(name: str, description: str):
20
+ headers = create_authorization_header()
21
+ r = http.post(url=f'{url}/projects/',
22
+ json={'name': name,
23
+ 'description': description},
24
+ headers=headers
25
+ )
26
+
27
+ if r.status_code == 200:
28
+ return r.json()['id']
29
+ else:
30
+ return False
31
+
32
+
33
+ def remove(proj_id: str or int):
34
+ headers = create_authorization_header()
35
+ r = http.delete(url=f'{url}/projects/{proj_id}/', headers=headers)
36
+
37
+ if r.status_code == 200:
38
+ return True
39
+ else:
40
+ return False
41
+
42
+
43
+ def list_projects():
44
+ headers = create_authorization_header()
45
+ r = http.get(url=f'{url}/projects/', headers=headers)
46
+
47
+ if r.status_code == 200:
48
+ return r.json()
49
+ else:
50
+ return False
51
+
52
+
53
+ def list_tokens(project_id):
54
+ headers = create_authorization_header()
55
+ r = http.get(url=f'{url}/project-tokens/{project_id}/', headers=headers)
56
+ if r.status_code == 200:
57
+ return r.json()
58
+ else:
59
+ return False
60
+
61
+
62
+ def create_token(project_id: str or int):
63
+ headers = create_authorization_header()
64
+ r = http.post(url=f'{url}/project-tokens/{project_id}/',
65
+ json={'cmd': 'create'},
66
+ headers=headers
67
+ )
68
+
69
+ if r.status_code == 200:
70
+ return r.json()
71
+ else:
72
+ return False
73
+
74
+
75
+ def remove_token(token_id: str or int):
76
+ headers = create_authorization_header()
77
+ r = http.delete(url=f'{url}/project-tokens/token/{token_id}/', headers=headers)
78
+
79
+ if r.status_code == 200:
80
+ return r.json()
81
+ else:
82
+ print(r.status_code)
83
+ print(r.text)
84
+ return False
@@ -0,0 +1,97 @@
1
+ import requests
2
+ from urllib.parse import urlencode
3
+
4
+ from FeatureCloud.api.imp.test.helper import http
5
+
6
+
7
+ def is_online(url: str = 'http://localhost:8000/'):
8
+ try:
9
+ requests.get(url=url)
10
+ return True
11
+ except requests.exceptions.ConnectionError:
12
+ return False
13
+
14
+
15
+ def start_test(url: str, docker_image: str, directories: [], generic_directory: str, is_local_relay: bool,
16
+ query_interval: int, download_results: str):
17
+ """
18
+ Start a new test
19
+ :param url:
20
+ :param docker_image:
21
+ :param directories:
22
+ :param generic_directory
23
+ :param is_local_relay:
24
+ :param query_interval:
25
+ :param download_results:
26
+ :return:
27
+ """
28
+
29
+ response = http.post(url=f'{url}/app/test/', json={'dockerImage': docker_image,
30
+ 'directories': list(directories),
31
+ 'genericDirectory': generic_directory,
32
+ 'isLocalRelay': is_local_relay,
33
+ 'queryInterval': query_interval,
34
+ 'downloadResults': download_results})
35
+ if response.status_code == 200:
36
+ return True, response.json()
37
+ else:
38
+ return False, response.json()
39
+
40
+
41
+ def delete_test(url: str, test_id: str or int):
42
+ response = http.delete(url=f'{url}/app/test/{test_id}/?delete=true')
43
+ if response.status_code == 200:
44
+ return True, response
45
+ else:
46
+ return False, response.json()
47
+
48
+
49
+ def stop_test(url: str, test_id: str or int):
50
+ response = http.delete(url=f'{url}/app/test/{test_id}/?delete=false')
51
+ if response.status_code == 200:
52
+ return True, response.content
53
+ else:
54
+ return False, response.json()
55
+
56
+
57
+ def get_tests(url: str):
58
+ response = http.get(url=f'{url}/app/tests/')
59
+
60
+ if response.status_code == 200:
61
+ return True, response.json()
62
+ else:
63
+ return False, response.json()
64
+
65
+
66
+ def delete_tests(url: str):
67
+ response = http.delete(url=f'{url}/app/tests/')
68
+ if response.status_code == 200:
69
+ return True, response.content
70
+ else:
71
+ return False, response.json()
72
+
73
+
74
+ def get_test(url: str, test_id: str or int):
75
+ response = http.get(url=f'{url}/app/test/{test_id}/')
76
+
77
+ if response.status_code == 200:
78
+ return True, response.json()
79
+ else:
80
+ return False, response.json()
81
+
82
+
83
+ def get_traffic(url: str, test_id: str or int):
84
+ response = http.get(url=f'{url}/app/test/{test_id}/traffic/')
85
+
86
+ if response.status_code == 200:
87
+ return True, response.json()
88
+ else:
89
+ return False, response.json()
90
+
91
+
92
+ def get_logs(url: str, test_id: str or int, instance_id: str or int, from_param: str):
93
+ response = http.get(url=f'{url}/app/test/{test_id}/instance/{instance_id}/logs/?from={from_param}')
94
+ if response.status_code == 200:
95
+ return True, response.json()
96
+ else:
97
+ return False, response.json()
@@ -0,0 +1,124 @@
1
+ from FeatureCloud.api.imp.test import helper
2
+ from FeatureCloud.api.imp.test.api import controller
3
+ from FeatureCloud.api.imp.exceptions import ControllerOffline, FCException
4
+
5
+
6
+ def help():
7
+ return (None, """For registering and testing your apps or using other apps, please visit
8
+ our
9
+ website: \n https://featurecloud.ai.\n And for more information about
10
+ FeatureCloud architecture: \n
11
+ The FeatureCloud AI Store for Federated Learning in Biomedicine and
12
+ Beyond\n
13
+ https://arxiv.org/abs/2105.05734 """)
14
+
15
+
16
+ def start(controller_host: str, client_dirs: str, generic_dir: str, app_image: str, channel: str, query_interval: int,
17
+ download_results: str):
18
+ if not controller.is_online(controller_host):
19
+ raise ControllerOffline(controller_host)
20
+
21
+ success, result = controller.start_test(controller_host,
22
+ app_image,
23
+ filter(None, client_dirs.split(',')),
24
+ generic_dir,
25
+ channel == 'local',
26
+ query_interval,
27
+ download_results)
28
+
29
+ if success:
30
+ return result['id']
31
+ else:
32
+ raise FCException(result['detail'])
33
+
34
+
35
+ def stop(controller_host: str, test_id: str or int):
36
+ if not controller.is_online(controller_host):
37
+ raise ControllerOffline(controller_host)
38
+
39
+ success, result = controller.stop_test(controller_host, test_id)
40
+
41
+ if success:
42
+ return test_id
43
+ else:
44
+ raise FCException(result['detail'])
45
+
46
+
47
+ def delete(controller_host: str, test_id: str or int, del_all: str):
48
+ if not controller.is_online(controller_host):
49
+ raise ControllerOffline(controller_host)
50
+
51
+ if test_id is not None and del_all is None:
52
+ success, result = controller.delete_test(controller_host, test_id)
53
+
54
+ if success:
55
+ return test_id
56
+ else:
57
+ raise FCException(result['detail'])
58
+
59
+ elif test_id is None and len(del_all) > 0:
60
+ if del_all.lower() == 'all':
61
+ success, result = controller.delete_tests(controller_host)
62
+
63
+ if success:
64
+ return 'all'
65
+ else:
66
+ raise FCException(result['detail'])
67
+ else:
68
+ raise FCException(f'Unsupported argument {del_all}')
69
+
70
+ else:
71
+ raise FCException('Wrong combination of parameters. To delete a single test use option --test-id. To delete all tests use the "all" argument.')
72
+
73
+
74
+ def list(controller_host: str, format: str = 'dataframe'):
75
+ if not controller.is_online(controller_host):
76
+ raise ControllerOffline(controller_host)
77
+
78
+ success, result = controller.get_tests(controller_host)
79
+ if success:
80
+ if format == 'json':
81
+ return result
82
+ else:
83
+ return helper.json_to_dataframe(result).set_index('id')
84
+ else:
85
+ raise FCException(result)
86
+
87
+
88
+ def info(controller_host: str, test_id: str or int, format: str = 'dataframe'):
89
+ if not controller.is_online(controller_host):
90
+ raise ControllerOffline(controller_host)
91
+
92
+ success, result = controller.get_test(controller_host, test_id)
93
+ if success:
94
+ if format == 'json':
95
+ return result
96
+ else:
97
+ return helper.json_to_dataframe(result, single_entry=True).set_index('id')
98
+ else:
99
+ raise FCException(result['detail'])
100
+
101
+
102
+ def traffic(controller_host: str, test_id: str or int, format: str):
103
+ if not controller.is_online(controller_host):
104
+ raise ControllerOffline(controller_host)
105
+
106
+ success, result = controller.get_traffic(controller_host, test_id)
107
+ if success:
108
+ if format == 'json':
109
+ return result
110
+ else:
111
+ return helper.json_to_dataframe(result)
112
+ else:
113
+ raise FCException(result['detail'])
114
+
115
+
116
+ def logs(controller_host: str, test_id: str or int, instance_id: str or int, from_param: str):
117
+ if not controller.is_online(controller_host):
118
+ raise ControllerOffline(controller_host)
119
+
120
+ success, result = controller.get_logs(controller_host, test_id, instance_id, from_param)
121
+ if success:
122
+ return result["logs"]
123
+ else:
124
+ raise FCException(result['detail'])
@@ -0,0 +1,40 @@
1
+ import pandas as pd
2
+ import requests
3
+ from requests.adapters import HTTPAdapter
4
+ from urllib3 import Retry
5
+
6
+ retry_strategy = Retry(
7
+ total=3,
8
+ status_forcelist=[429, 500, 502, 503, 504],
9
+ method_whitelist=["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"],
10
+ backoff_factor=1
11
+ )
12
+ adapter = HTTPAdapter(max_retries=retry_strategy)
13
+ http = requests.Session()
14
+ http.mount("https://", adapter)
15
+ http.mount("http://", adapter)
16
+
17
+
18
+ def json_to_dataframe(json, single_entry: bool = False, default_column: str = 'id'):
19
+ if len(json) == 0:
20
+ # return empty data frame
21
+ df = pd.DataFrame({default_column: []})
22
+ elif not single_entry:
23
+ df = pd.DataFrame.from_records(json)
24
+ else:
25
+ df = pd.DataFrame.from_dict(json, orient='index').T
26
+
27
+ try:
28
+ df.createdAt = pd.to_datetime(df.createdAt).apply(lambda x: str(x).split(".")[0])
29
+ except Exception:
30
+ pass
31
+ try:
32
+ df.finishedAt = pd.to_datetime(df.finishedAt).apply(lambda x: str(x).split(".")[0])
33
+ except Exception:
34
+ pass
35
+ pd.set_option('display.max_rows', None)
36
+ pd.set_option('display.max_columns', None)
37
+ pd.set_option('display.width', None)
38
+ pd.set_option('display.max_colwidth', None)
39
+
40
+ return df