ciocore 5.1.1__py2.py3-none-any.whl → 10.0.0b3__py2.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.
- ciocore/VERSION +1 -1
- ciocore/__init__.py +23 -1
- ciocore/api_client.py +655 -160
- ciocore/auth/__init__.py +5 -3
- ciocore/cli.py +501 -0
- ciocore/common.py +15 -13
- ciocore/conductor_submit.py +77 -60
- ciocore/config.py +127 -13
- ciocore/data.py +162 -77
- ciocore/docsite/404.html +746 -0
- ciocore/docsite/apidoc/api_client/index.html +3605 -0
- ciocore/docsite/apidoc/apidoc/index.html +909 -0
- ciocore/docsite/apidoc/config/index.html +1652 -0
- ciocore/docsite/apidoc/data/index.html +1553 -0
- ciocore/docsite/apidoc/hardware_set/index.html +2460 -0
- ciocore/docsite/apidoc/package_environment/index.html +1507 -0
- ciocore/docsite/apidoc/package_tree/index.html +2386 -0
- ciocore/docsite/assets/_mkdocstrings.css +16 -0
- ciocore/docsite/assets/images/favicon.png +0 -0
- ciocore/docsite/assets/javascripts/bundle.471ce7a9.min.js +29 -0
- ciocore/docsite/assets/javascripts/bundle.471ce7a9.min.js.map +7 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.ar.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.da.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.de.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.du.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.el.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.es.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.fi.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.fr.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.he.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.hi.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.hu.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.hy.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.it.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.ja.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.jp.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.kn.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.ko.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.multi.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.nl.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.no.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.pt.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.ro.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.ru.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.sa.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.sv.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.ta.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.te.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.th.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.tr.min.js +18 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.vi.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/min/lunr.zh.min.js +1 -0
- ciocore/docsite/assets/javascripts/lunr/tinyseg.js +206 -0
- ciocore/docsite/assets/javascripts/lunr/wordcut.js +6708 -0
- ciocore/docsite/assets/javascripts/workers/search.b8dbb3d2.min.js +42 -0
- ciocore/docsite/assets/javascripts/workers/search.b8dbb3d2.min.js.map +7 -0
- ciocore/docsite/assets/stylesheets/main.3cba04c6.min.css +1 -0
- ciocore/docsite/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
- ciocore/docsite/assets/stylesheets/palette.06af60db.min.css +1 -0
- ciocore/docsite/assets/stylesheets/palette.06af60db.min.css.map +1 -0
- ciocore/docsite/cmdline/docs/index.html +871 -0
- ciocore/docsite/cmdline/downloader/index.html +934 -0
- ciocore/docsite/cmdline/packages/index.html +878 -0
- ciocore/docsite/cmdline/uploader/index.html +995 -0
- ciocore/docsite/how-to-guides/index.html +869 -0
- ciocore/docsite/index.html +895 -0
- ciocore/docsite/logo.png +0 -0
- ciocore/docsite/objects.inv +0 -0
- ciocore/docsite/search/search_index.json +1 -0
- ciocore/docsite/sitemap.xml +3 -0
- ciocore/docsite/sitemap.xml.gz +0 -0
- ciocore/docsite/stylesheets/extra.css +26 -0
- ciocore/docsite/stylesheets/tables.css +167 -0
- ciocore/downloader/base_downloader.py +644 -0
- ciocore/downloader/download_runner_base.py +47 -0
- ciocore/downloader/job_downloader.py +119 -0
- ciocore/{downloader.py → downloader/legacy_downloader.py} +12 -9
- ciocore/downloader/log.py +73 -0
- ciocore/downloader/logging_download_runner.py +87 -0
- ciocore/downloader/perpetual_downloader.py +63 -0
- ciocore/downloader/registry.py +97 -0
- ciocore/downloader/reporter.py +135 -0
- ciocore/exceptions.py +8 -2
- ciocore/file_utils.py +51 -50
- ciocore/hardware_set.py +449 -0
- ciocore/loggeria.py +89 -20
- ciocore/package_environment.py +110 -48
- ciocore/package_query.py +182 -0
- ciocore/package_tree.py +319 -258
- ciocore/retry.py +0 -0
- ciocore/uploader/_uploader.py +547 -364
- ciocore/uploader/thread_queue_job.py +176 -0
- ciocore/uploader/upload_stats/__init__.py +3 -4
- ciocore/uploader/upload_stats/stats_formats.py +10 -4
- ciocore/validator.py +34 -2
- ciocore/worker.py +174 -151
- ciocore-10.0.0b3.dist-info/METADATA +928 -0
- ciocore-10.0.0b3.dist-info/RECORD +128 -0
- {ciocore-5.1.1.dist-info → ciocore-10.0.0b3.dist-info}/WHEEL +1 -1
- ciocore-10.0.0b3.dist-info/entry_points.txt +2 -0
- tests/instance_type_fixtures.py +175 -0
- tests/package_fixtures.py +205 -0
- tests/test_api_client.py +297 -12
- tests/test_base_downloader.py +104 -0
- tests/test_cli.py +149 -0
- tests/test_common.py +1 -7
- tests/test_config.py +40 -18
- tests/test_data.py +162 -173
- tests/test_downloader.py +118 -0
- tests/test_hardware_set.py +139 -0
- tests/test_job_downloader.py +213 -0
- tests/test_package_query.py +38 -0
- tests/test_package_tree.py +91 -291
- tests/test_submit.py +44 -18
- tests/test_uploader.py +1 -4
- ciocore/__about__.py +0 -10
- ciocore/cli/conductor.py +0 -191
- ciocore/compat.py +0 -15
- ciocore-5.1.1.data/scripts/conductor +0 -19
- ciocore-5.1.1.data/scripts/conductor.bat +0 -13
- ciocore-5.1.1.dist-info/METADATA +0 -408
- ciocore-5.1.1.dist-info/RECORD +0 -47
- tests/mocks/api_client_mock.py +0 -51
- /ciocore/{cli → downloader}/__init__.py +0 -0
- {ciocore-5.1.1.dist-info → ciocore-10.0.0b3.dist-info}/top_level.txt +0 -0
ciocore/conductor_submit.py
CHANGED
|
@@ -6,18 +6,18 @@ import getpass
|
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
|
-
import re
|
|
10
9
|
import threading
|
|
10
|
+
import traceback
|
|
11
11
|
|
|
12
12
|
from ciocore import config
|
|
13
|
-
from ciocore import
|
|
14
|
-
|
|
13
|
+
from ciocore import file_utils, api_client, uploader, exceptions
|
|
15
14
|
from ciocore.common import CONDUCTOR_LOGGER_NAME
|
|
16
|
-
|
|
15
|
+
|
|
17
16
|
logger = logging.getLogger(CONDUCTOR_LOGGER_NAME)
|
|
18
17
|
|
|
19
18
|
FEATURE_DEV = int(os.environ.get("CIO_FEATURE_DEV", 0))
|
|
20
19
|
|
|
20
|
+
|
|
21
21
|
class Submit(object):
|
|
22
22
|
"""Conductor Submission object."""
|
|
23
23
|
|
|
@@ -33,7 +33,7 @@ class Submit(object):
|
|
|
33
33
|
self.enforced_md5s = args.get("enforced_md5s", {})
|
|
34
34
|
self.database_filepath = args.get("database_filepath", "")
|
|
35
35
|
self.api_client = api_client.ApiClient()
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
self.progress_handler = None
|
|
38
38
|
self.uploader_ = None
|
|
39
39
|
|
|
@@ -70,16 +70,25 @@ class Submit(object):
|
|
|
70
70
|
self.payload[arg] = args[arg]
|
|
71
71
|
except KeyError:
|
|
72
72
|
if default is None:
|
|
73
|
-
logger.error(
|
|
73
|
+
logger.error(
|
|
74
|
+
"Submit: You must provide the '{}' argument.".format(arg)
|
|
75
|
+
)
|
|
74
76
|
raise
|
|
75
77
|
|
|
78
|
+
# If no upload paths are provided, make sure the backend does not expect a daemon to be running.
|
|
79
|
+
if not self.upload_paths:
|
|
80
|
+
self.payload["local_upload"] = True
|
|
81
|
+
|
|
76
82
|
# HACK: Posix -> Windows submission - must windowize output_path. Only available for
|
|
77
83
|
# developers. If a customer tries to submit from Mac to Windows, then they have access to
|
|
78
84
|
# Windows instances by mistake. Yes this code could get them out of a bind, but it will
|
|
79
85
|
# generate support tickets when they try to use the uploader daemon for example.
|
|
80
|
-
self.ensure_windows_drive_letters = FEATURE_DEV and self.payload[
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
self.ensure_windows_drive_letters = FEATURE_DEV and self.payload[
|
|
87
|
+
"instance_type"
|
|
88
|
+
].endswith("-w")
|
|
89
|
+
self.payload["output_path"] = self._ensure_windows_drive_letter(
|
|
90
|
+
self.payload["output_path"]
|
|
91
|
+
)
|
|
83
92
|
|
|
84
93
|
self.payload["notify"] = {"emails": self.payload["notify"]}
|
|
85
94
|
|
|
@@ -88,25 +97,25 @@ class Submit(object):
|
|
|
88
97
|
logger.debug("{}:{}".format(arg, self.payload[arg]))
|
|
89
98
|
|
|
90
99
|
def upload_progress_callback(self, upload_stats):
|
|
91
|
-
|
|
100
|
+
"""
|
|
92
101
|
Call the progress handler
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
"""
|
|
103
|
+
|
|
95
104
|
if self.progress_handler:
|
|
96
|
-
logger.debug("Sending progress update to {}".format(self.progress_handler))
|
|
105
|
+
logger.debug("Sending progress update to {}".format(self.progress_handler))
|
|
97
106
|
self.progress_handler(upload_stats)
|
|
98
107
|
|
|
99
108
|
def stop_work(self):
|
|
100
|
-
|
|
109
|
+
"""
|
|
101
110
|
Cancel the submission process
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
"""
|
|
112
|
+
|
|
104
113
|
logger.debug("Submitter was requested to stop work.")
|
|
105
114
|
|
|
106
115
|
if self.uploader_:
|
|
107
|
-
logger.debug("Uploader set to cancel.")
|
|
108
|
-
self.uploader_.cancel=True
|
|
109
|
-
|
|
116
|
+
logger.debug("Uploader set to cancel.")
|
|
117
|
+
self.uploader_.cancel = True
|
|
118
|
+
|
|
110
119
|
def main(self):
|
|
111
120
|
"""
|
|
112
121
|
Submit the job
|
|
@@ -118,39 +127,42 @@ class Submit(object):
|
|
|
118
127
|
2. local_upload=False: md5 calcs and uploads are performed on on any machine with access to
|
|
119
128
|
the filesystem on which the files reside, and by the same paths as the submission machine.
|
|
120
129
|
"""
|
|
121
|
-
|
|
122
|
-
self._log_threads(message_template="{thread_count} threads before starting upload")
|
|
123
130
|
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
self._log_threads(
|
|
132
|
+
message_template="{thread_count} threads before starting upload"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if self.upload_paths:
|
|
126
136
|
|
|
127
|
-
|
|
128
|
-
file_map =
|
|
129
|
-
|
|
130
|
-
elif self.enforced_md5s:
|
|
131
|
-
file_map = self._enforce_md5s(file_map)
|
|
137
|
+
processed_filepaths = file_utils.process_upload_filepaths(self.upload_paths)
|
|
138
|
+
file_map = {path: None for path in processed_filepaths}
|
|
132
139
|
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
if self.payload["local_upload"]:
|
|
141
|
+
file_map = self._handle_local_upload(file_map)
|
|
135
142
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
self.payload["upload_size"] += expanded["st_size"]
|
|
143
|
+
elif self.enforced_md5s:
|
|
144
|
+
file_map = self._enforce_md5s(file_map)
|
|
139
145
|
|
|
146
|
+
for path in file_map:
|
|
147
|
+
expanded = self._expand_stats(path, file_map[path])
|
|
148
|
+
self.payload["upload_files"].append(expanded)
|
|
149
|
+
self.payload["upload_size"] += expanded["st_size"]
|
|
140
150
|
|
|
141
151
|
self._log_threads(message_template="{thread_count} threads after upload")
|
|
142
|
-
|
|
152
|
+
|
|
143
153
|
logger.info("Sending Job...")
|
|
144
|
-
|
|
154
|
+
|
|
145
155
|
response, response_code = self.api_client.make_request(
|
|
146
|
-
uri_path="jobs/",
|
|
147
|
-
data=json.dumps(self.payload),
|
|
148
|
-
raise_on_error=False,
|
|
149
|
-
use_api_key=True
|
|
156
|
+
uri_path="jobs/",
|
|
157
|
+
data=json.dumps(self.payload),
|
|
158
|
+
raise_on_error=False,
|
|
159
|
+
use_api_key=True,
|
|
150
160
|
)
|
|
151
|
-
|
|
161
|
+
|
|
152
162
|
if response_code not in [201, 204]:
|
|
153
|
-
raise Exception(
|
|
163
|
+
raise Exception(
|
|
164
|
+
"Job Submission failed: Error %s ...\n%s" % (response_code, response)
|
|
165
|
+
)
|
|
154
166
|
|
|
155
167
|
return json.loads(response), response_code
|
|
156
168
|
|
|
@@ -160,34 +172,42 @@ class Submit(object):
|
|
|
160
172
|
|
|
161
173
|
Returns {"path1': md5_1, path2: md5_2}
|
|
162
174
|
"""
|
|
163
|
-
|
|
164
175
|
cfg = config.config().config
|
|
165
176
|
api_client.read_conductor_credentials(use_api_key=True)
|
|
166
|
-
|
|
177
|
+
|
|
167
178
|
# Don't use more threads than there are files
|
|
168
179
|
thread_count = min(len(file_map), cfg["thread_count"])
|
|
169
180
|
logger.info("Using {} threads for the uploader".format(thread_count))
|
|
170
|
-
|
|
181
|
+
|
|
171
182
|
uploader_args = {
|
|
172
183
|
"location": self.payload["location"],
|
|
173
184
|
"database_filepath": self.database_filepath,
|
|
174
185
|
"thread_count": thread_count,
|
|
175
186
|
"md5_caching": self.md5_caching,
|
|
176
187
|
}
|
|
177
|
-
|
|
188
|
+
|
|
178
189
|
self.uploader_ = uploader.Uploader(uploader_args)
|
|
179
190
|
|
|
180
191
|
self.uploader_.progress_callback = self.upload_progress_callback
|
|
181
|
-
|
|
192
|
+
|
|
182
193
|
self.uploader_.handle_upload_response(self.payload["project"], file_map)
|
|
183
|
-
|
|
194
|
+
|
|
184
195
|
if self.uploader_.cancel:
|
|
185
|
-
raise exceptions.UserCanceledError(
|
|
196
|
+
raise exceptions.UserCanceledError(
|
|
197
|
+
"Job submission was cancelled by the user"
|
|
198
|
+
)
|
|
186
199
|
|
|
187
200
|
if self.uploader_.error_messages:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
201
|
+
error_message = ""
|
|
202
|
+
for cnt, err in enumerate(self.uploader_.error_messages):
|
|
203
|
+
error_message += "Error {}:\n{}\n\n".format(
|
|
204
|
+
cnt + 1, "".join(traceback.format_exception(*err))
|
|
205
|
+
)
|
|
206
|
+
raise Exception(
|
|
207
|
+
"\n\nCould not upload files, encountered %s errors:\n\n%s"
|
|
208
|
+
% (len(self.uploader_.error_messages), error_message)
|
|
209
|
+
)
|
|
210
|
+
|
|
191
211
|
# Get the resulting dictionary of the file's and their corresponding md5 hashes
|
|
192
212
|
upload_md5s = self.uploader_.return_md5s()
|
|
193
213
|
for path in upload_md5s:
|
|
@@ -202,13 +222,13 @@ class Submit(object):
|
|
|
202
222
|
|
|
203
223
|
Returns {"path1': enforced_md5_1, path2: enforced_md5_2}
|
|
204
224
|
"""
|
|
205
|
-
|
|
225
|
+
|
|
206
226
|
progress_title = "Processing MD5 of local files"
|
|
207
227
|
file_count = len(self.enforced_md5s)
|
|
208
228
|
|
|
209
229
|
for cnt, filepath in enumerate(self.enforced_md5s):
|
|
210
|
-
percentage_complete = float(cnt)/float(file_count)
|
|
211
|
-
|
|
230
|
+
percentage_complete = float(cnt) / float(file_count)
|
|
231
|
+
|
|
212
232
|
md5 = self.enforced_md5s[filepath]
|
|
213
233
|
logger.debug("filepath is %s" % filepath)
|
|
214
234
|
processed_filepaths = file_utils.process_upload_filepath(filepath)
|
|
@@ -219,7 +239,6 @@ class Submit(object):
|
|
|
219
239
|
|
|
220
240
|
return file_map
|
|
221
241
|
|
|
222
|
-
|
|
223
242
|
def _expand_stats(self, file, md5):
|
|
224
243
|
filestat = os.stat(file)
|
|
225
244
|
|
|
@@ -240,16 +259,15 @@ class Submit(object):
|
|
|
240
259
|
"st_mtime": filestat.st_mtime,
|
|
241
260
|
"st_ctime": filestat.st_ctime,
|
|
242
261
|
}
|
|
243
|
-
|
|
262
|
+
|
|
244
263
|
def _log_threads(self, message_template):
|
|
245
264
|
|
|
246
265
|
threads = list(threading.enumerate())
|
|
247
|
-
|
|
266
|
+
|
|
248
267
|
for t in threads:
|
|
249
268
|
logger.debug(t)
|
|
250
269
|
|
|
251
|
-
logger.debug(message_template.format(thread_count=len(threads)))
|
|
252
|
-
|
|
270
|
+
logger.debug(message_template.format(thread_count=len(threads)))
|
|
253
271
|
|
|
254
272
|
def _ensure_windows_drive_letter(self, filepath):
|
|
255
273
|
"""
|
|
@@ -261,4 +279,3 @@ class Submit(object):
|
|
|
261
279
|
logger.debug("Windows dev hack! Setting {0} to 'X:{0}'".format(filepath))
|
|
262
280
|
return "X:{}".format(filepath)
|
|
263
281
|
return filepath
|
|
264
|
-
|
ciocore/config.py
CHANGED
|
@@ -1,49 +1,135 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Config is
|
|
2
|
+
Config is a configuration object implemented as a module-level singleton.
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
Configuration variables can be shared by importing the module. If there are changes in environment variables or other sources, the config can be refreshed.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
|
-
import multiprocessing
|
|
9
|
+
# import multiprocessing
|
|
10
10
|
import base64
|
|
11
11
|
import json
|
|
12
12
|
import re
|
|
13
|
-
|
|
13
|
+
import platform
|
|
14
14
|
|
|
15
15
|
from ciocore.common import CONDUCTOR_LOGGER_NAME
|
|
16
16
|
|
|
17
17
|
logger = logging.getLogger(CONDUCTOR_LOGGER_NAME)
|
|
18
18
|
|
|
19
|
-
#https://stackoverflow.com/a/3809435/179412
|
|
20
|
-
|
|
19
|
+
# https://stackoverflow.com/a/3809435/179412
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
USER_DIRS = {
|
|
23
|
+
"Linux": os.path.expanduser(os.path.join("~", ".conductor")),
|
|
24
|
+
"Darwin": os.path.expanduser(os.path.join("~",".conductor")),
|
|
25
|
+
"Windows": os.path.expanduser(os.path.join("~", "AppData", "Local", "Conductor")),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
DEFAULT_USER_DIR = USER_DIRS.get(platform.system(), USER_DIRS["Linux"])
|
|
29
|
+
|
|
30
|
+
URL_REGEX = re.compile(
|
|
31
|
+
r"https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)"
|
|
32
|
+
)
|
|
21
33
|
|
|
22
34
|
__config__ = None
|
|
23
35
|
|
|
36
|
+
|
|
24
37
|
def config(force=False):
|
|
38
|
+
"""
|
|
39
|
+
Instantiate a config object if necessary.
|
|
40
|
+
|
|
41
|
+
Deprecated:
|
|
42
|
+
Use [get()](#get) instead.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
force (bool): Discards any existing config object and instantiate a new one -- Defaults to `False`.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
dict: A dictionary containing configuration values.
|
|
49
|
+
"""
|
|
50
|
+
|
|
25
51
|
global __config__
|
|
26
52
|
if force or not __config__:
|
|
27
53
|
__config__ = Config()
|
|
28
54
|
return __config__
|
|
29
55
|
|
|
56
|
+
|
|
57
|
+
def get(force=False):
|
|
58
|
+
"""
|
|
59
|
+
Instantiate a config object if necessary and return the dictionary.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
force (bool): Discards any existing config object and instantiate a new one -- Defaults to `False`.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
dict: A dictionary containing configuration values.
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
>>> from ciocore import config
|
|
69
|
+
>>> config.get()
|
|
70
|
+
{
|
|
71
|
+
'thread_count': 16,
|
|
72
|
+
'priority': 5,
|
|
73
|
+
'md5_caching': True,
|
|
74
|
+
'log_level': 'INFO',
|
|
75
|
+
'url': 'https://dashboard.conductortech.com',
|
|
76
|
+
'auth_url': 'https://dashboard.conductortech.com',
|
|
77
|
+
'api_url': 'https://api.conductortech.com',
|
|
78
|
+
'api_key': None,
|
|
79
|
+
'downloader_page_size': 50,
|
|
80
|
+
}
|
|
81
|
+
"""
|
|
82
|
+
global __config__
|
|
83
|
+
if force or not __config__:
|
|
84
|
+
__config__ = Config()
|
|
85
|
+
return __config__.config
|
|
86
|
+
|
|
87
|
+
|
|
30
88
|
class Config(object):
|
|
31
89
|
def __init__(self):
|
|
90
|
+
"""
|
|
91
|
+
Initialize the config object.
|
|
92
|
+
|
|
93
|
+
A config object is a dictionary containing configuration values. It is a singleton, so there is only one instance of it. It is instantiated the first time it is needed. It can be refreshed by calling get() with the `force` keyword argument set to `True`.
|
|
94
|
+
|
|
95
|
+
A Config object has the following properties:
|
|
96
|
+
|
|
97
|
+
* `thread_count` The number of threads to use for downloading files. Defaults to the number of CPUs on the system times 2. It can be overridden by the `CONDUCTOR_THREAD_COUNT` environment variable.
|
|
98
|
+
* `priority` Set the priority for submissions. Defaults to 5. It can be overridden by the `CONDUCTOR_PRIORITY` environment variable.
|
|
99
|
+
* `md5_caching` Whether to cache MD5s. Defaults to `True`. It can be overridden by the `CONDUCTOR_MD5_CACHING` environment variable. Cachine MD5s significantly improves submission performance, but on rare occasions it can cause submissions to fail. If you experience this, set `md5_caching` to `False`.
|
|
100
|
+
* `log_level` The logging level. Defaults to `INFO`. It can be overridden by the `CONDUCTOR_LOG_LEVEL` environment variable.
|
|
101
|
+
* `url` The URL of the Conductor dashboard. Defaults to `https://dashboard.conductortech.com`. It can be overridden by the `CONDUCTOR_URL` environment variable.
|
|
102
|
+
* `auth_url` The URL of the Conductor dashboard. Defaults to `https://dashboard.conductortech.com`. It can be overridden by the `CONDUCTOR_AUTH_URL` environment variable. This is deprecated. Use `url` instead.
|
|
103
|
+
* `api_url` The URL of the Conductor API. Defaults to `https://api.conductortech.com`. It can be overridden by the `CONDUCTOR_API_URL` environment variable.
|
|
104
|
+
* `api_key` The API key. The API key can be acquired from the Conductor dashboard, and can be stored in an environment variable or a file. In both cases the API KEY can be a JSON object or a base64 encoded JSON object. If it is base64 encoded, it can be a string or bytes. If it is a string, it will be decoded as ASCII. If it is bytes, it will be decoded as UTF-8.
|
|
105
|
+
* Environment variable: The `CONDUCTOR_API_KEY` variable can hold the API KEY directly.
|
|
106
|
+
* File: The `CONDUCTOR_API_KEY_PATH` variable can hold the path to a file containing the API KEY.
|
|
107
|
+
* `downloader_page_size` The number of files to request from the Conductor API at a time. Defaults to 50. It can be overridden by the `CONDUCTOR_DOWNLOADER_PAGE_SIZE` environment variable.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Config: A config object.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ValueError -- Invalid inputs, such as badly formed URLs.
|
|
114
|
+
"""
|
|
115
|
+
default_downloader_page_size = 50
|
|
116
|
+
|
|
117
|
+
|
|
32
118
|
|
|
33
119
|
try:
|
|
34
|
-
default_thread_count = min(
|
|
120
|
+
default_thread_count = min( os.cpu_count() - 1, 15)
|
|
35
121
|
except NotImplementedError:
|
|
36
|
-
default_thread_count =
|
|
122
|
+
default_thread_count = 15
|
|
37
123
|
|
|
38
124
|
url = os.environ.get("CONDUCTOR_URL", "https://dashboard.conductortech.com")
|
|
39
125
|
|
|
40
126
|
if not URL_REGEX.match(url):
|
|
41
127
|
raise ValueError("CONDUCTOR_URL is not valid '{}'".format(url))
|
|
42
|
-
|
|
128
|
+
|
|
43
129
|
api_url = os.environ.get("CONDUCTOR_API_URL", url.replace("dashboard", "api"))
|
|
44
130
|
if not URL_REGEX.match(api_url):
|
|
45
131
|
raise ValueError("CONDUCTOR_API_URL is not valid '{}'".format(api_url))
|
|
46
|
-
|
|
132
|
+
|
|
47
133
|
falsy = ["false", "no", "off", "0"]
|
|
48
134
|
|
|
49
135
|
log_level = os.environ.get("CONDUCTOR_LOG_LEVEL", "INFO")
|
|
@@ -51,23 +137,41 @@ class Config(object):
|
|
|
51
137
|
log_level = "INFO"
|
|
52
138
|
|
|
53
139
|
self.config = {
|
|
54
|
-
"thread_count": int(
|
|
140
|
+
"thread_count": int(
|
|
141
|
+
os.environ.get("CONDUCTOR_THREAD_COUNT", default_thread_count)
|
|
142
|
+
),
|
|
143
|
+
"downloader_page_size": int(
|
|
144
|
+
os.environ.get(
|
|
145
|
+
"CONDUCTOR_DOWNLOADER_PAGE_SIZE", default_downloader_page_size
|
|
146
|
+
)
|
|
147
|
+
),
|
|
55
148
|
"priority": int(os.environ.get("CONDUCTOR_PRIORITY", 5)),
|
|
56
149
|
"md5_caching": False
|
|
57
150
|
if os.environ.get("CONDUCTOR_MD5_CACHING", "True").lower() in falsy
|
|
58
151
|
else True,
|
|
59
152
|
"log_level": log_level,
|
|
60
153
|
"url": url,
|
|
61
|
-
# Keep "auth_url" for backwwards compatibillity only.
|
|
62
|
-
# Clients should use "url" moving forward.
|
|
154
|
+
# Keep "auth_url" for backwwards compatibillity only.
|
|
155
|
+
# Clients should use "url" moving forward.
|
|
63
156
|
# Remove "auth_url" on the next major version bump.
|
|
64
157
|
"auth_url": url,
|
|
65
158
|
"api_url": api_url,
|
|
66
159
|
"api_key": self.get_api_key_from_variable() or self.get_api_key_from_file(),
|
|
160
|
+
"user_dir": os.environ.get('CONDUCTOR_USER_DIR', DEFAULT_USER_DIR)
|
|
67
161
|
}
|
|
68
162
|
|
|
69
163
|
@staticmethod
|
|
70
164
|
def get_api_key_from_variable():
|
|
165
|
+
"""
|
|
166
|
+
Attempt to get an API key from the `CONDUCTOR_API_KEY` environment variable.
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
ValueError: An error occurred while reading or loading the key into JSON.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
str: JSON object containing the key - base 64 decoded if necessary.
|
|
173
|
+
|
|
174
|
+
"""
|
|
71
175
|
api_key = os.environ.get("CONDUCTOR_API_KEY")
|
|
72
176
|
if not api_key:
|
|
73
177
|
return
|
|
@@ -88,6 +192,16 @@ class Config(object):
|
|
|
88
192
|
|
|
89
193
|
@staticmethod
|
|
90
194
|
def get_api_key_from_file():
|
|
195
|
+
"""
|
|
196
|
+
Attempt to get an API key from the file in the CONDUCTOR_API_KEY_PATH environment variable.
|
|
197
|
+
|
|
198
|
+
Raises:
|
|
199
|
+
ValueError: An error occurred while reading or loading the key into JSON.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
str: JSON object containing the key - base 64 decoded if necessary.
|
|
203
|
+
|
|
204
|
+
"""
|
|
91
205
|
api_key_path = os.environ.get("CONDUCTOR_API_KEY_PATH")
|
|
92
206
|
if not api_key_path:
|
|
93
207
|
return
|