ciocore 6.3.2rc1__py2.py3-none-any.whl → 6.4.0__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.

Potentially problematic release.


This version of ciocore might be problematic. Click here for more details.

Files changed (91) hide show
  1. ciocore/VERSION +1 -1
  2. ciocore/__about__.py +3 -20
  3. ciocore/api_client.py +116 -236
  4. ciocore/cli/__init__.py +3 -0
  5. ciocore/cli/conductor.py +206 -0
  6. ciocore/config.py +44 -50
  7. ciocore/data.py +131 -85
  8. ciocore/downloader.py +64 -46
  9. ciocore/hardware_set.py +99 -326
  10. ciocore/package_environment.py +74 -48
  11. ciocore/package_tree.py +377 -300
  12. ciocore/uploader/_uploader.py +2 -6
  13. ciocore-6.4.0.data/scripts/conductor +19 -0
  14. ciocore-6.4.0.data/scripts/conductor.bat +13 -0
  15. {ciocore-6.3.2rc1.dist-info → ciocore-6.4.0.dist-info}/METADATA +12 -35
  16. ciocore-6.4.0.dist-info/RECORD +51 -0
  17. {ciocore-6.3.2rc1.dist-info → ciocore-6.4.0.dist-info}/WHEEL +1 -1
  18. tests/instance_type_fixtures.py +9 -43
  19. tests/mocks/api_client_mock.py +31 -0
  20. tests/test_hardware_set.py +4 -50
  21. ciocore/cli.py +0 -336
  22. ciocore/dev_inst_tagger.py +0 -120
  23. ciocore/docsite/404.html +0 -723
  24. ciocore/docsite/apidoc/api_client/index.html +0 -2590
  25. ciocore/docsite/apidoc/apidoc/index.html +0 -868
  26. ciocore/docsite/apidoc/config/index.html +0 -1562
  27. ciocore/docsite/apidoc/data/index.html +0 -1550
  28. ciocore/docsite/apidoc/hardware_set/index.html +0 -2324
  29. ciocore/docsite/apidoc/package_environment/index.html +0 -1430
  30. ciocore/docsite/apidoc/package_tree/index.html +0 -2310
  31. ciocore/docsite/assets/_mkdocstrings.css +0 -16
  32. ciocore/docsite/assets/images/favicon.png +0 -0
  33. ciocore/docsite/assets/javascripts/bundle.4e31edb1.min.js +0 -29
  34. ciocore/docsite/assets/javascripts/bundle.4e31edb1.min.js.map +0 -8
  35. ciocore/docsite/assets/javascripts/lunr/min/lunr.ar.min.js +0 -1
  36. ciocore/docsite/assets/javascripts/lunr/min/lunr.da.min.js +0 -18
  37. ciocore/docsite/assets/javascripts/lunr/min/lunr.de.min.js +0 -18
  38. ciocore/docsite/assets/javascripts/lunr/min/lunr.du.min.js +0 -18
  39. ciocore/docsite/assets/javascripts/lunr/min/lunr.es.min.js +0 -18
  40. ciocore/docsite/assets/javascripts/lunr/min/lunr.fi.min.js +0 -18
  41. ciocore/docsite/assets/javascripts/lunr/min/lunr.fr.min.js +0 -18
  42. ciocore/docsite/assets/javascripts/lunr/min/lunr.hi.min.js +0 -1
  43. ciocore/docsite/assets/javascripts/lunr/min/lunr.hu.min.js +0 -18
  44. ciocore/docsite/assets/javascripts/lunr/min/lunr.hy.min.js +0 -1
  45. ciocore/docsite/assets/javascripts/lunr/min/lunr.it.min.js +0 -18
  46. ciocore/docsite/assets/javascripts/lunr/min/lunr.ja.min.js +0 -1
  47. ciocore/docsite/assets/javascripts/lunr/min/lunr.jp.min.js +0 -1
  48. ciocore/docsite/assets/javascripts/lunr/min/lunr.kn.min.js +0 -1
  49. ciocore/docsite/assets/javascripts/lunr/min/lunr.ko.min.js +0 -1
  50. ciocore/docsite/assets/javascripts/lunr/min/lunr.multi.min.js +0 -1
  51. ciocore/docsite/assets/javascripts/lunr/min/lunr.nl.min.js +0 -18
  52. ciocore/docsite/assets/javascripts/lunr/min/lunr.no.min.js +0 -18
  53. ciocore/docsite/assets/javascripts/lunr/min/lunr.pt.min.js +0 -18
  54. ciocore/docsite/assets/javascripts/lunr/min/lunr.ro.min.js +0 -18
  55. ciocore/docsite/assets/javascripts/lunr/min/lunr.ru.min.js +0 -18
  56. ciocore/docsite/assets/javascripts/lunr/min/lunr.sa.min.js +0 -1
  57. ciocore/docsite/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +0 -1
  58. ciocore/docsite/assets/javascripts/lunr/min/lunr.sv.min.js +0 -18
  59. ciocore/docsite/assets/javascripts/lunr/min/lunr.ta.min.js +0 -1
  60. ciocore/docsite/assets/javascripts/lunr/min/lunr.te.min.js +0 -1
  61. ciocore/docsite/assets/javascripts/lunr/min/lunr.th.min.js +0 -1
  62. ciocore/docsite/assets/javascripts/lunr/min/lunr.tr.min.js +0 -18
  63. ciocore/docsite/assets/javascripts/lunr/min/lunr.vi.min.js +0 -1
  64. ciocore/docsite/assets/javascripts/lunr/min/lunr.zh.min.js +0 -1
  65. ciocore/docsite/assets/javascripts/lunr/tinyseg.js +0 -206
  66. ciocore/docsite/assets/javascripts/lunr/wordcut.js +0 -6708
  67. ciocore/docsite/assets/javascripts/workers/search.dfff1995.min.js +0 -42
  68. ciocore/docsite/assets/javascripts/workers/search.dfff1995.min.js.map +0 -8
  69. ciocore/docsite/assets/stylesheets/main.83068744.min.css +0 -1
  70. ciocore/docsite/assets/stylesheets/main.83068744.min.css.map +0 -1
  71. ciocore/docsite/assets/stylesheets/palette.ecc896b0.min.css +0 -1
  72. ciocore/docsite/assets/stylesheets/palette.ecc896b0.min.css.map +0 -1
  73. ciocore/docsite/cmdline/docs/index.html +0 -834
  74. ciocore/docsite/cmdline/downloader/index.html +0 -897
  75. ciocore/docsite/cmdline/packages/index.html +0 -841
  76. ciocore/docsite/cmdline/uploader/index.html +0 -950
  77. ciocore/docsite/how-to-guides/index.html +0 -831
  78. ciocore/docsite/index.html +0 -853
  79. ciocore/docsite/logo.png +0 -0
  80. ciocore/docsite/objects.inv +0 -0
  81. ciocore/docsite/search/search_index.json +0 -1
  82. ciocore/docsite/sitemap.xml +0 -3
  83. ciocore/docsite/sitemap.xml.gz +0 -0
  84. ciocore/docsite/stylesheets/extra.css +0 -26
  85. ciocore/docsite/stylesheets/tables.css +0 -170
  86. ciocore/package_query.py +0 -171
  87. ciocore-6.3.2rc1.dist-info/RECORD +0 -115
  88. ciocore-6.3.2rc1.dist-info/entry_points.txt +0 -2
  89. tests/test_cli.py +0 -161
  90. tests/test_downloader.py +0 -56
  91. {ciocore-6.3.2rc1.dist-info → ciocore-6.4.0.dist-info}/top_level.txt +0 -0
ciocore/data.py CHANGED
@@ -1,20 +1,16 @@
1
1
  """
2
- This module is a singleton that provides the data from Conductor endpoints. Specifically, it provides projects, instance types, and software package data. It is also possible to use cached fixture data for development purposes.
2
+ A singleton containing all the data from Conductor endpoints that's needed for writing submission
3
+ tools. Specifiically, it provides projects, instance types, and software package data.
4
+
5
+ It's also possible to use cached fixtures for development purposes.
3
6
 
4
- Since the data is stored at the module level, you can access it from anywhere in your code without the need to pass it around.
5
7
  """
6
8
 
7
9
  import json
8
10
  import os
9
- import requests
10
11
  from ciocore.package_tree import PackageTree
11
- from ciocore import api_client, dev_inst_tagger
12
+ from ciocore import api_client
12
13
  from ciocore.hardware_set import HardwareSet
13
-
14
-
15
-
16
- # USE_INST_TAGGER = int(os.environ.get("CIO_FEATURE_DEV_INST_TAGGER", 0))
17
-
18
14
  __data__ = {}
19
15
  __products__ = None
20
16
  __fixtures_dir__ = None
@@ -22,24 +18,35 @@ __platforms__ = None
22
18
 
23
19
  def init(*products, **kwargs):
24
20
  """
25
- Initialize the module and let it know what host products to provide.
21
+ Initialize and let the module know what host products to provide in the `software` property.
22
+
23
+ Arguments:
26
24
 
27
- Args:
28
- products (str): Provide a list of products for which to get software packages. If no products are given, the software data contains all products from the packages endpoint. If you provide more than one product, they must all be host level products.
25
+ * **`products`** -- Provide a list of products as a filter. If no products are given, the
26
+ software data structure is built using all products from the packages endpoint. If you provide
27
+ more than one product, they must all be host level products.
29
28
 
30
- Keyword Args:
31
- product (str): `DEPRECATED` Provide one product for which to get software packages.
29
+ Keyword Arguments:
32
30
 
33
- Examples:
34
- >>> from ciocore import data as coredata
35
- >>> coredata.init()
31
+ * **`product`** -- (DEPRECATED) You can provide one product with the product keyword, or you can
32
+ provide the string `"all"`. This is ignored if any product args are present. -- Defaults to
33
+ `None`.
34
+
35
+ ???+ example
36
+ ``` python
37
+
38
+ from ciocore import data as coredata
39
+ coredata.init()
36
40
  # OR
37
- >>> coredata.init("maya-io")
41
+ coredata.init("maya-io")
38
42
  # OR LEGACY
39
- >>> coredata.init(product="all")
43
+ coredata.init(product="all")
40
44
  # OR
41
- >>> coredata.init(product="maya-io")
45
+ coredata.init(product="maya-io")
46
+
47
+ ```
42
48
  """
49
+
43
50
  global __products__
44
51
  global __platforms__
45
52
  if products:
@@ -55,72 +62,76 @@ def init(*products, **kwargs):
55
62
  __products__ = [kwargs.get("product")]
56
63
  else:
57
64
  __products__ = []
58
- print("SETTING PLATFORMS")
59
- __platforms__ = set(kwargs.get("platforms", ["windows", "linux"]))
60
- print("PLATFORMS: ", __platforms__)
61
- print("DONE SETTING PLATFORMS")
62
65
 
66
+ __platforms__ = set(kwargs.get("platforms", ["windows", "linux"]))
67
+
68
+
63
69
  def data(force=False):
64
70
  """
65
- Provide projects, instance types, and software package data.
71
+ Provides projects, instance types, and software package data.
66
72
 
67
- Keyword Args:
68
- force: (bool) If `True`, then fetch fresh data -- Defaults to `False`.
73
+ If data has been fetched already, then return it, otherwise make requests to fetch it.
74
+ The user may have to authentiicate.
69
75
 
70
- Raises:
71
- ValueError: Module was not initialized with [init()](/data/#ciocore.data.init).
76
+ The set of instance types and software are each potentially pruned to match the available
77
+ platforms represented by the other. If the instance types come from an orchestrator that
78
+ provides both windows and linux machines, and the software product(s) are available on both
79
+ platforms, then no pruning occurs. However, if there are no windows machines, then any windows
80
+ software is removed from the package tree. Likewise, if a product is chosen that only runs on
81
+ Windows, then Linux instance types will be culled from the list of available hardware.
72
82
 
73
- Returns:
74
- dict: Keys are `projects`, `instance_types`, `software`.
83
+ Keyword Arguments:
75
84
 
76
- When you access the data, if it has already been fetched, it will be returned. Otherwise,
77
- requests will be made to fetch the data. You may need to authenticate in order to access the
78
- data.
85
+ * **`force`** -- If `True`, then fetch fresh data -- Defaults to `False`.
86
+
87
+ Raises:
79
88
 
80
- The set of instance types and software can be pruned to match the available platforms
81
- represented by each other. For example, if the instance types come from an orchestrator that
82
- provides both Windows and Linux machines, and the software product(s) are available on both
83
- platforms, no pruning occurs. However, if there are no Windows machines available, any Windows
84
- software will be removed from the package tree. Similarly, if a product is chosen that only runs
85
- on Windows, Linux instance types will not appearin the list of available hardware.
89
+ * **`ValueError`** -- Module was not initialized with the [init()](#init) method.
86
90
 
87
- Here is a breakdown of each key in the dictionary:
91
+ Returns:
88
92
 
89
- * **projects** is a list of project names for your authenticated account.
93
+ * A dictionary with 3 keys: `projects`, `instance_types`, `software`.
90
94
 
91
- * **instance_types** is an instance of HardwareSet, providing you with access to the list of
92
- available machines configurations.
95
+ * `projects` is a list of project names for the authenticated account.
96
+ * `instance_types` is an instace of HardwareSet, from which you can get the data in various forms.
97
+ * `software` is a [PackageTree](/developer/ciocore/package_tree) object containing either all
98
+ software available at conductor, or a subset according to the product specified on
99
+ initialization.
93
100
 
94
- * **software** is a PackageTree object containing either all
95
- the software available at Conductor, or a subset based on specified products.
96
101
 
102
+ ???+ example
103
+ ``` python
97
104
 
98
- Examples:
99
- >>> from ciocore import data as coredata
100
- >>> coredata.init(product="maya-io")
105
+ from ciocore import data as coredata
106
+ coredata.init(product="maya-io")
107
+ coredata.data()["software"]
101
108
 
102
- >>> coredata.data()["software"]
103
- <ciocore.package_tree.PackageTree object at 0x10e9a4040>
109
+ # Result:
110
+ # <ciocore.package_tree.PackageTree object at 0x10e9a4040>
104
111
 
105
- >>> coredata.data()["projects"][0]
106
- ATestForScott
112
+ coredata.data()["projects"][0]
107
113
 
108
- >>> coredata.data()["instance_types"]
109
- <ciocore.hardware_set.HardwareSet object at 0x0000028941CD9DC0>
114
+ # Result:
115
+ # ATestForScott
116
+
117
+ coredata.data()["instance_types"][-1]["description"]
118
+ # Result:
119
+ # 160 core, 3844GB Mem
120
+ ```
110
121
  """
111
122
 
112
123
  global __data__
113
124
  global __products__
125
+ global __fixtures_dir__
114
126
  global __platforms__
115
127
 
116
128
  if __products__ is None:
117
129
  raise ValueError(
118
130
  'Data must be initialized before use, e.g. data.init("maya-io") or data.init().'
119
131
  )
120
- products_copy = __products__.copy()
132
+
121
133
  if force:
122
134
  clear()
123
- init(*products_copy)
124
135
 
125
136
  if __data__ == {}:
126
137
  # PROJECTS
@@ -132,8 +143,6 @@ def data(force=False):
132
143
 
133
144
  # INST_TYPES
134
145
  instance_types = _get_json_fixture("instance_types")
135
- if not instance_types:
136
- instance_types = dev_inst_tagger.request_instance_types()
137
146
  if not instance_types:
138
147
  instance_types = api_client.request_instance_types()
139
148
 
@@ -154,13 +163,14 @@ def data(force=False):
154
163
  host_products = []
155
164
  kwargs["product"] = __products__[0]
156
165
 
166
+
157
167
  software_tree = PackageTree(software, *host_products, **kwargs)
158
168
 
159
169
  if software_tree:
160
170
  __data__["software"] = software_tree
161
171
  # Revisit instance types to filter out any that are not needed for any software package.
162
172
  sw_platforms = software_tree.platforms()
163
-
173
+
164
174
  instance_types = [
165
175
  it for it in instance_types if it["operating_system"] in sw_platforms
166
176
  ]
@@ -174,17 +184,25 @@ def data(force=False):
174
184
 
175
185
  def valid():
176
186
  """
177
- Check validity.
187
+ Check validity of the data.
178
188
 
179
189
  Returns:
180
- bool: True if `projects`, `instance_types`, and `software` are valid.
181
190
 
182
- Examples:
183
- >>> from ciocore import data as coredata
184
- >>> coredata.valid()
185
- True
186
- """
191
+ * True if `projects`, `instance_types`, and `software` exists.
192
+
193
+ ???+ example
194
+ ``` python
195
+
196
+ from ciocore import data as coredata
197
+ coredata.valid()
198
+
199
+ # Result:
200
+ # True
187
201
 
202
+
203
+ ```
204
+ """
205
+ global __data__
188
206
  if not __data__.get("projects"):
189
207
  return False
190
208
  if not __data__.get("instance_types"):
@@ -196,24 +214,45 @@ def valid():
196
214
 
197
215
  def clear():
198
216
  """
199
- Clear out data.
217
+ Clear out the data.
200
218
 
201
- [valid()](/data/#ciocore.data.valid) returns False after clear().
219
+ [valid()](#valid) returns False after clear().
202
220
  """
203
221
  global __data__
204
- global __products__
205
- global __platforms__
206
222
  __data__ = {}
207
223
  __products__ = None
208
224
  __platforms__ = None
225
+ """
226
+ Let the module know what host products to provide in the `software` property.
227
+
228
+ Keyword Arguments:
229
+
230
+ * **`products`** -- Product args, such as `maya-io, cinema4d` -- Defaults to `None` which means all.
231
+
232
+ Raises:
233
+
234
+ * **`ValueError`** -- _description_.
235
+
236
+ ???+ example
237
+ ``` python
238
+
239
+ from ciocore import data as coredata
240
+ coredata.init()
241
+ # OR
242
+ coredata.init("maya-io")
243
+
244
+ ```
245
+ """
209
246
 
210
247
 
211
248
  def products():
212
249
  """
213
250
 
214
251
  Returns:
215
- list(str): The product names. An empty list signifies all products.
252
+
253
+ * The product names. An empty array signifies all products.
216
254
  """
255
+ global __products__
217
256
  return __products__
218
257
 
219
258
 
@@ -222,9 +261,9 @@ def set_fixtures_dir(path):
222
261
  Specify a directory in which to find JSON files representing the three sets of data to provide.
223
262
  The individual filenames are:
224
263
 
225
- * `projects.json`
226
- * `instance_types.json`
227
- * `software.json`
264
+ * projects.json
265
+ * instance_types.json
266
+ * software.json
228
267
 
229
268
  These files could be used in an environment where machines can't access the internet. They are
230
269
  also useful as a cache for developers who need to reload often as it avoids waiting for the
@@ -232,16 +271,21 @@ def set_fixtures_dir(path):
232
271
 
233
272
  In order to get the content for the fixtures files, use the following Example
234
273
 
235
- Examples:
236
- >>> from ciocore import api_client
237
- >>> projects = api_client.request_projects()
238
- >>> instance_types = api_client.request_instance_types()
239
- >>> software = api_client.request_software_packages()
274
+ ???+ example
275
+ ``` python
276
+
277
+ from ciocore import api_client
240
278
 
241
- Write that data as JSON to the filenames listed above.
279
+ projects = api_client.request_projects()
280
+ instance_types = api_client.request_instance_types()
281
+ software = api_client.request_software_packages()
282
+
283
+ # Write that data as JSON to the filenames listed above.
284
+ ```
242
285
 
243
286
  Arguments:
244
- path (str): Directory in which to find the above files.
287
+
288
+ * **`path`** -- Directory in which to find the above files.
245
289
 
246
290
  """
247
291
 
@@ -250,6 +294,7 @@ def set_fixtures_dir(path):
250
294
 
251
295
 
252
296
  def _get_json_fixture(resource):
297
+ global __fixtures_dir__
253
298
  if __fixtures_dir__:
254
299
  cache_path = os.path.join(__fixtures_dir__, "{}.json".format(resource))
255
300
  if os.path.isfile(cache_path):
@@ -262,10 +307,11 @@ def _get_json_fixture(resource):
262
307
 
263
308
  def platforms():
264
309
  """
265
- The set of platforms that both software and instance types are valid on.
310
+ The set of platforms that are found in both software and instance types.
266
311
 
267
312
  Returns:
268
- set: A set containing platforms: windows and/or linux.
313
+
314
+ * A set containing platforms: windows and/or linux or neither.
269
315
  """
316
+ global __platforms__
270
317
  return __platforms__
271
-
ciocore/downloader.py CHANGED
@@ -18,10 +18,10 @@ import time
18
18
  import threading
19
19
  import hashlib
20
20
 
21
+ import ciocore
21
22
  from ciocore import config
22
23
  from ciocore import api_client, common, loggeria
23
24
  from ciocore.common import CONDUCTOR_LOGGER_NAME
24
- from cioseq.sequence import Sequence
25
25
 
26
26
  try:
27
27
  import Queue as queue
@@ -291,12 +291,12 @@ class Downloader(object):
291
291
  downloader.print_uptime()
292
292
 
293
293
  @classmethod
294
- def download_jobs(cls, job_ids, thread_count=None, output_dir=None):
294
+ def download_jobs(cls, job_ids, task_id=None, thread_count=None, output_dir=None):
295
295
  """
296
296
  Run the downloader for explicit jobs, and terminate afterwards.
297
297
  """
298
298
  downloader = cls(thread_count=thread_count, output_dir=output_dir)
299
- downloader.start(job_ids)
299
+ thread_states = downloader.start(job_ids, task_id=task_id)
300
300
  while not common.SIGINT_EXIT and (
301
301
  not downloader.pending_queue.empty() or not downloader.downloading_queue.empty()
302
302
  ):
@@ -306,7 +306,7 @@ class Downloader(object):
306
306
  downloader._print_download_history()
307
307
  downloader.print_uptime()
308
308
 
309
- def start(self, job_ids=None, summary_interval=10):
309
+ def start(self, job_ids=None, task_id=None, summary_interval=10):
310
310
  # Create new queues
311
311
  self.start_time = time.time()
312
312
  self.pending_queue = queue.Queue()
@@ -316,7 +316,7 @@ class Downloader(object):
316
316
  # If a job id has been specified then only load the queue up with that work
317
317
  if job_ids:
318
318
  self.history_queue_max = None
319
- self.get_jobs_downloads(job_ids)
319
+ self.get_jobs_downloads(job_ids, task_id)
320
320
 
321
321
  # otherwise create a queue thread the polls the app for wor
322
322
  else:
@@ -461,12 +461,10 @@ class Downloader(object):
461
461
  # Wait for the queue to be consumed before querying for more
462
462
  self.nap()
463
463
 
464
-
465
464
  def nap(self):
466
465
  while not common.SIGINT_EXIT:
467
466
 
468
467
  time.sleep(self.naptime)
469
- # Pretty sure this return should be unindented!
470
468
  return
471
469
 
472
470
  @common.dec_timer_exit(log_level=logging.DEBUG)
@@ -479,51 +477,24 @@ class Downloader(object):
479
477
  except Exception as e:
480
478
  logger.exception("Could not get next download")
481
479
 
482
- def get_jobs_downloads(self, job_ids):
480
+ def get_jobs_downloads(self, job_ids, task_id):
483
481
  """
484
- Download jobs, each with an optional task specification.
485
-
486
- Job ids are padded to 5 digits, and task ids are padded to 3 digits.
482
+ Get each job and optional comma-separated task list.
487
483
 
488
- job_ids: (tuple) may take any of the following forms:
489
- 1. A single job id, e.g. (01234,)
490
- 2. It doesn't have to be padded, e.g. (1234,)
491
- 3. A jobs with a task specification, e.g. (01234:1-3,5,7-9)
492
- 4. Several of jobs:tasks, e.g. (01234, 56789:1-3,5,7-9)
493
-
494
- """
495
- jobs = self._flatten(job_ids)
496
- for job in jobs:
497
- endpoint = self.endpoint_downloads_job % job["job_id"]
498
-
499
- for tid in job["tasks"] or [None]:
500
- downloads = _get_job_download(endpoint, self.api_client, job["job_id"], tid)
484
+ There will only be a task list if there is just one job, due to earlier arg validation.
485
+
486
+ If there is no task list, _get_job_download is called with tid=None (i.e. the whole job)
487
+ """
488
+ task_ids = [t for t in task_id.split(",") if t] if task_id else [None]
489
+
490
+ for job_id in job_ids:
491
+ endpoint = self.endpoint_downloads_job % job_id
492
+ for tid in task_ids:
493
+ downloads = _get_job_download(endpoint, self.api_client, job_id, tid)
501
494
  if downloads:
502
495
  for task_download in downloads.get("downloads", []):
503
496
  print("putting in queue: %s" % task_download)
504
497
  self.pending_queue.put(task_download, block=True)
505
-
506
- @staticmethod
507
- def _flatten(job_ids):
508
- """Create a list of job objects with keys: job_id and tasks.
509
-
510
- see: get_jobs_downloads() function see tests/test_downloader.py for examples.
511
- """
512
- result = []
513
- for job_id in job_ids:
514
- if ":" in job_id:
515
- job_id, range_spec = job_id.split(":")
516
- try:
517
- seq = Sequence.create(range_spec)
518
- tasks = seq.expand("###")
519
- except (ValueError, TypeError):
520
- tasks = None
521
- else:
522
- tasks = None
523
- result.append({"job_id": job_id.zfill(5), "tasks": tasks})
524
- return result
525
-
526
-
527
498
 
528
499
  @common.dec_catch_exception(raise_=True)
529
500
  def download_target(self, pending_queue, downloading_queue, task_download_state):
@@ -1249,6 +1220,53 @@ def _in_queue(the_queue, item_dict, key):
1249
1220
  if item[key] == item_dict[key]:
1250
1221
  return True
1251
1222
 
1223
+
1224
+ def run_downloader(args):
1225
+ """
1226
+ Start the downloader. If a job id(s) were given, exit the downloader upon completion.
1227
+ Otherwise, run the downloader indefinitely (daemon mode), polling the Conductor cloud app for
1228
+ files that need to be downloaded.
1229
+ """
1230
+
1231
+ # Set up logging
1232
+ log_level_name = args.get("log_level")
1233
+ log_level = loggeria.LEVEL_MAP.get(log_level_name)
1234
+ log_dirpath = args.get("log_dir")
1235
+ set_logging(log_level, log_dirpath)
1236
+
1237
+ api_client.ApiClient.register_client(client_name = Downloader.CLIENT_NAME, client_version=ciocore.__version__)
1238
+
1239
+ logger.debug("Downloader args: %s", args)
1240
+
1241
+ job_ids = args.get("job_id")
1242
+ thread_count = args.get("thread_count")
1243
+
1244
+ if job_ids:
1245
+ Downloader.download_jobs(
1246
+ job_ids,
1247
+ task_id=args.get("task_id"),
1248
+ thread_count=thread_count,
1249
+ output_dir=args.get("output"),
1250
+ )
1251
+
1252
+ else:
1253
+ Downloader.start_daemon(
1254
+ thread_count=thread_count, location=args.get("location"), output_dir=args.get("output")
1255
+ )
1256
+
1257
+
1258
+ def set_logging(level=None, log_dirpath=None):
1259
+ log_filepath = None
1260
+ if log_dirpath:
1261
+ log_filepath = os.path.join(log_dirpath, "conductor_dl_log")
1262
+ loggeria.setup_conductor_logging(
1263
+ logger_level=level,
1264
+ console_formatter=LOG_FORMATTER,
1265
+ file_formatter=LOG_FORMATTER,
1266
+ log_filepath=log_filepath,
1267
+ )
1268
+
1269
+
1252
1270
  def report_error(self, download_id, error_message):
1253
1271
  try:
1254
1272
  logger.error("failing upload due to: \n%s" % error_message)