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/hardware_set.py
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the **HardwareSet** class.
|
|
3
|
+
|
|
4
|
+
It is designed to allow submission tools and other clients that consume instance types to be able to display them in categories. This is particularly useful for UIs that utilize combo-boxes, since they can be orgnaized into a nested structure and displayed in a tree-like fashion.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
import copy
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# {
|
|
15
|
+
# "cores": 32,
|
|
16
|
+
# "description": "32 core, 120GB Mem (8 V100 GPUs with 16GB Mem)",
|
|
17
|
+
# "gpu": {
|
|
18
|
+
# "gpu_architecture": "NVIDIA Volta",
|
|
19
|
+
# "gpu_count": 8,
|
|
20
|
+
# "gpu_cuda_cores": 5120,
|
|
21
|
+
# "gpu_memory": "16",
|
|
22
|
+
# "gpu_model": "V100",
|
|
23
|
+
# "total_gpu_cuda_cores": 40960,
|
|
24
|
+
# "total_gpu_memory": "128"
|
|
25
|
+
# },
|
|
26
|
+
# "memory": "120",
|
|
27
|
+
# "name": "n1-standard-32-v1-8",
|
|
28
|
+
# "operating_system": "linux"
|
|
29
|
+
# },
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
DESCRIPTION_TEMPLATE_OS = {
|
|
33
|
+
"cpu": "{operating_system} {cores} core {memory}GB Mem",
|
|
34
|
+
"gpu": "{operating_system} {cores} core {memory}GB Mem ({gpu_count} {gpu_model} GPUs {gpu_memory}GB Mem)",
|
|
35
|
+
}
|
|
36
|
+
DESCRIPTION_TEMPLATE = {
|
|
37
|
+
"cpu": "{cores} core {memory}GB Mem",
|
|
38
|
+
"gpu": "{cores} core {memory}GB Mem ({gpu_count} {gpu_model} GPUs {gpu_memory}GB Mem)",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def flatten_dict(d):
|
|
42
|
+
flat_dict = {}
|
|
43
|
+
for key, value in d.items():
|
|
44
|
+
if isinstance(value, dict):
|
|
45
|
+
nested_dict = flatten_dict(value)
|
|
46
|
+
for nested_key, nested_value in nested_dict.items():
|
|
47
|
+
flat_dict[nested_key] = nested_value
|
|
48
|
+
else:
|
|
49
|
+
flat_dict[key] = value
|
|
50
|
+
return flat_dict
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class HardwareSet(object):
|
|
54
|
+
"""A class to manage categorized instance types.
|
|
55
|
+
|
|
56
|
+
A HardwareSet encapsulates the instance types available to an account. It accepts a flat list of instance types and builds a nested structure where those instance types exist in categories.
|
|
57
|
+
|
|
58
|
+
It keeps a dictionary of instance types (`instance_types`) with the name field as key. This allows easy lookup by name.
|
|
59
|
+
|
|
60
|
+
In addition, it keeps the nested structure of categories (`categories`) that contain the instance types. Each category is a dictionary with keys: `label`, `content`, and `order`.
|
|
61
|
+
|
|
62
|
+
`content` is a list of instance types in the category. The order is used to sort the categories. The order of the instance types within a category is determined by the number of cores and memory.
|
|
63
|
+
|
|
64
|
+
If all instance_types have not been assigned any categories, then the structure is built with two default categories: CPU and GPU.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, instance_types):
|
|
68
|
+
"""Initialize the HardwareSet with a list of instance types.
|
|
69
|
+
Typically, you would access the HardwareSet through the ciocore.data.data() function, which initializes it for you. However, you can also initialize it directly with a list of instance types straight from ciocore.api_client. The difference being that the latter contains all instance types, whereas the former contains only the instance types compatible with the products you have specified, as well as being cached.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
instance_types (list): A list of instance types.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
HardwareSet: The initialized HardwareSet.
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
### Initialize with a list of instance types
|
|
79
|
+
>>> from ciocore import api_client
|
|
80
|
+
>>> from ciocore.hardware_set import HardwareSet
|
|
81
|
+
>>> instance_types = api_client.request_instance_types()
|
|
82
|
+
>>> hardware_set = HardwareSet(instance_types)
|
|
83
|
+
<ciocore.hardware_set.HardwareSet object at 0x104c43d30>
|
|
84
|
+
|
|
85
|
+
### Initialize implicitly with a list of instance types from ciocore.data (recommended).
|
|
86
|
+
>>> from ciocore import data as coredata
|
|
87
|
+
>>> coredata.init("cinema4d")
|
|
88
|
+
>>> hardware_set = coredata.data()["instance_types"]
|
|
89
|
+
<ciocore.hardware_set.HardwareSet object at 0x104c43ee0>
|
|
90
|
+
|
|
91
|
+
!!! note
|
|
92
|
+
To avoid repetition, we use the implicit initialization for the examples below.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
self.instance_types = self._build_unique(instance_types)
|
|
96
|
+
self.categories = self._build_categories()
|
|
97
|
+
self.provider = self._get_provider()
|
|
98
|
+
|
|
99
|
+
def labels(self):
|
|
100
|
+
"""Get the list of category labels.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
list: A list of category labels.
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
>>> from ciocore import data as coredata
|
|
107
|
+
>>> coredata.init()
|
|
108
|
+
>>> hardware_set = coredata.data()["instance_types"]
|
|
109
|
+
>>> hardware_set.labels()
|
|
110
|
+
['CPU', 'GPU']
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
return [c["label"] for c in self.categories]
|
|
114
|
+
|
|
115
|
+
def number_of_categories(self):
|
|
116
|
+
"""Get the number of categories in the data.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
int: The number of categories.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
>>> from ciocore import data as coredata
|
|
123
|
+
>>> coredata.init()
|
|
124
|
+
>>> hardware_set = coredata.data()["instance_types"]
|
|
125
|
+
>>> hardware_set.number_of_categories()
|
|
126
|
+
2
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
return len(self.categories)
|
|
130
|
+
|
|
131
|
+
def recategorize(self, partitioner):
|
|
132
|
+
"""Recategorize the instance types.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
partitioner (function): A function that takes an instance type and returns a list of categories to assign to it. The function should return an empty list if the instance type should not be categorized.
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
# Confirm current categories
|
|
139
|
+
>>> from ciocore import data as coredata
|
|
140
|
+
>>> coredata.init()
|
|
141
|
+
>>> hardware_set = coredata.data()["instance_types"]
|
|
142
|
+
>>> print(hardware_set.labels()
|
|
143
|
+
['CPU', 'GPU']
|
|
144
|
+
|
|
145
|
+
# Recategorize
|
|
146
|
+
>>> hardware_set.recategorize(lambda x: [{'label': 'Low cores', 'order': 10}] if x["cores"] < 16 else [{'label': 'High cores', 'order': 20}])
|
|
147
|
+
>>> print(hardware_set.labels()
|
|
148
|
+
['Low cores', 'High cores']
|
|
149
|
+
"""
|
|
150
|
+
for key in self.instance_types:
|
|
151
|
+
self.instance_types[key]["categories"] = partitioner(
|
|
152
|
+
self.instance_types[key]
|
|
153
|
+
)
|
|
154
|
+
self.categories = self._build_categories()
|
|
155
|
+
|
|
156
|
+
def find(self, name, category=None):
|
|
157
|
+
"""Find an instance type by its name (sku).
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
name (str): The name of the instance type.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
dict: The instance type or None if not found.
|
|
164
|
+
Example:
|
|
165
|
+
>>> from ciocore import data as coredata
|
|
166
|
+
>>> coredata.init()
|
|
167
|
+
>>> hardware_set = coredata.data()["instance_types"]
|
|
168
|
+
>>> hardware_set.find("n2-highmem-80")
|
|
169
|
+
{
|
|
170
|
+
'cores': 80,
|
|
171
|
+
'description': '80 core, 640GB Mem',
|
|
172
|
+
'gpu': None,
|
|
173
|
+
'memory': '640',
|
|
174
|
+
'name': 'n2-highmem-80',
|
|
175
|
+
'operating_system': 'linux',
|
|
176
|
+
'categories': [
|
|
177
|
+
{'label': 'High cores', 'order': 20}
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
if not category:
|
|
183
|
+
return self.instance_types.get(name)
|
|
184
|
+
|
|
185
|
+
return self.find_first(
|
|
186
|
+
lambda x: x["name"] == name
|
|
187
|
+
and category in [c["label"] for c in x["categories"]]
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def find_category(self, label):
|
|
191
|
+
"""Find a category by label.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
label (str): The label of the category.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
dict: The category or None if not found.
|
|
198
|
+
Example:
|
|
199
|
+
>>> from ciocore import data as coredata
|
|
200
|
+
>>> coredata.init()
|
|
201
|
+
>>> hardware_set = coredata.data()["instance_types"]
|
|
202
|
+
>>> hardware_set.find_category("High cores")
|
|
203
|
+
{
|
|
204
|
+
"label": "Low cores",
|
|
205
|
+
"content": [
|
|
206
|
+
{
|
|
207
|
+
"cores": 8,
|
|
208
|
+
"description": "8 core, 52GB Mem",
|
|
209
|
+
"gpu": None,
|
|
210
|
+
"memory": "52",
|
|
211
|
+
"name": "n1-highmem-8",
|
|
212
|
+
"operating_system": "linux",
|
|
213
|
+
"categories": [{"label": "Low cores", "order": 10}],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"cores": 8,
|
|
217
|
+
"description": "8 core, 7.2GB Mem",
|
|
218
|
+
"gpu": None,
|
|
219
|
+
"memory": "7.2",
|
|
220
|
+
"name": "n1-highcpu-8",
|
|
221
|
+
"operating_system": "linux",
|
|
222
|
+
"categories": [{"label": "Low cores", "order": 10}],
|
|
223
|
+
},
|
|
224
|
+
...
|
|
225
|
+
],
|
|
226
|
+
"order": 10
|
|
227
|
+
}
|
|
228
|
+
"""
|
|
229
|
+
return next((c for c in self.categories if c["label"] == label), None)
|
|
230
|
+
|
|
231
|
+
def find_all(self, condition):
|
|
232
|
+
"""Find all instance types that match a condition.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
condition (function): A function that takes an instance type and returns True or False.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
list: A list of instance types that match the condition.
|
|
239
|
+
|
|
240
|
+
Example:
|
|
241
|
+
>>> from ciocore import data as coredata
|
|
242
|
+
>>> coredata.init()
|
|
243
|
+
>>> hardware_set = coredata.data()["instance_types"]
|
|
244
|
+
>>> hardware_set.find_all(lambda x: x["gpu"])
|
|
245
|
+
[
|
|
246
|
+
{
|
|
247
|
+
"cores": 4,
|
|
248
|
+
"description": "4 core, 15GB Mem (1 T4 Tensor GPU with 16GB Mem)",
|
|
249
|
+
"gpu": {
|
|
250
|
+
"gpu_architecture": "NVIDIA Turing",
|
|
251
|
+
"gpu_count": 1,
|
|
252
|
+
"gpu_cuda_cores": 2560,
|
|
253
|
+
"gpu_memory": "16",
|
|
254
|
+
"gpu_model": "T4 Tensor",
|
|
255
|
+
"gpu_rt_cores": 0,
|
|
256
|
+
"gpu_tensor_cores": 0,
|
|
257
|
+
"total_gpu_cuda_cores": 2560,
|
|
258
|
+
"total_gpu_memory": "16",
|
|
259
|
+
"total_gpu_rt_cores": 0,
|
|
260
|
+
"total_gpu_tensor_cores": 0,
|
|
261
|
+
},
|
|
262
|
+
"memory": "15",
|
|
263
|
+
"name": "n1-standard-4-t4-1",
|
|
264
|
+
"operating_system": "linux",
|
|
265
|
+
"categories": [{"label": "Low cores", "order": 10}],
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"cores": 8,
|
|
269
|
+
"description": "8 core, 30GB Mem (1 T4 Tensor GPU with 16GB Mem)",
|
|
270
|
+
"gpu": {
|
|
271
|
+
"gpu_architecture": "NVIDIA Turing",
|
|
272
|
+
"gpu_count": 1,
|
|
273
|
+
"gpu_cuda_cores": 2560,
|
|
274
|
+
"gpu_memory": "16",
|
|
275
|
+
"gpu_model": "T4 Tensor",
|
|
276
|
+
"gpu_rt_cores": 0,
|
|
277
|
+
"gpu_tensor_cores": 0,
|
|
278
|
+
"total_gpu_cuda_cores": 2560,
|
|
279
|
+
"total_gpu_memory": "16",
|
|
280
|
+
"total_gpu_rt_cores": 0,
|
|
281
|
+
"total_gpu_tensor_cores": 0,
|
|
282
|
+
},
|
|
283
|
+
"memory": "30",
|
|
284
|
+
"name": "n1-standard-8-t4-1",
|
|
285
|
+
"operating_system": "linux",
|
|
286
|
+
"categories": [{"label": "Low cores", "order": 10}],
|
|
287
|
+
},
|
|
288
|
+
...
|
|
289
|
+
]
|
|
290
|
+
"""
|
|
291
|
+
result = []
|
|
292
|
+
for key in self.instance_types:
|
|
293
|
+
if condition(self.instance_types[key]):
|
|
294
|
+
result.append(self.instance_types[key])
|
|
295
|
+
return result
|
|
296
|
+
|
|
297
|
+
def find_first(self, condition):
|
|
298
|
+
"""Find the first instance type that matches a condition.
|
|
299
|
+
|
|
300
|
+
Please see find_all() above for more details. This method is just a convenience wrapper around find_all() that returns the first result or None if not found.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
condition (function): A function that takes an instance type and returns True or False.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
dict: The first instance type that matches the condition or None if not found.
|
|
307
|
+
"""
|
|
308
|
+
return next(iter(self.find_all(condition)), None)
|
|
309
|
+
|
|
310
|
+
# DEPRECATED
|
|
311
|
+
def get_model(self, with_misc=False):
|
|
312
|
+
"""Get the categories structure with renaming ready for some specific widget, such as a Qt Combobox.
|
|
313
|
+
|
|
314
|
+
Deprecated:
|
|
315
|
+
The get_model() method is deprecated. The `with_misc` parameter is no longer used, which means that this function only serves to rename a few keys. What's more, the init function ensures that every instance type has a category. This function is no longer needed. Submitters that use it will work but should be updated to use the categories structure directly as it minimizes the levels of indirection necessary to work with it.
|
|
316
|
+
"""
|
|
317
|
+
if with_misc:
|
|
318
|
+
logger.warning("with_misc is no longer used")
|
|
319
|
+
result = []
|
|
320
|
+
for category in self.categories:
|
|
321
|
+
result.append(
|
|
322
|
+
{
|
|
323
|
+
"label": category["label"],
|
|
324
|
+
"content": [
|
|
325
|
+
{"label": k["description"], "value": k["name"]}
|
|
326
|
+
for k in category["content"]
|
|
327
|
+
],
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
return result
|
|
332
|
+
|
|
333
|
+
# PRIVATE METHODS
|
|
334
|
+
@classmethod
|
|
335
|
+
def _build_unique(cls, instance_types):
|
|
336
|
+
"""Build a dictionary of instance types using the name field as key. This allows fast lookup by name.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
instance_types (list): A list of instance types.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
dict: A dictionary of instance types with the name field as key.
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
instance_types = cls._rewrite_descriptions(instance_types)
|
|
346
|
+
categories = [
|
|
347
|
+
category
|
|
348
|
+
for it in instance_types
|
|
349
|
+
for category in (it.get("categories") or [])
|
|
350
|
+
]
|
|
351
|
+
result = {}
|
|
352
|
+
for it in instance_types:
|
|
353
|
+
is_gpu = it.get("gpu", False)
|
|
354
|
+
if categories:
|
|
355
|
+
if it.get("categories") in [[], None]:
|
|
356
|
+
continue
|
|
357
|
+
else:
|
|
358
|
+
# make our own categories GPU/CPU
|
|
359
|
+
it["categories"] = (
|
|
360
|
+
[{"label": "GPU", "order": 2}]
|
|
361
|
+
if is_gpu
|
|
362
|
+
else [{"label": "CPU", "order": 1}]
|
|
363
|
+
)
|
|
364
|
+
result[it["name"]] = it
|
|
365
|
+
|
|
366
|
+
return result
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@classmethod
|
|
372
|
+
def _rewrite_descriptions(cls, instance_types):
|
|
373
|
+
"""Rewrite the descriptions of the instance types.
|
|
374
|
+
|
|
375
|
+
If there are both OS types, then the descriptions are prefixed with the OS type.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
instance_types (list): A list of instance types.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
list: A list of instance types with rewritten descriptions.
|
|
382
|
+
"""
|
|
383
|
+
if not instance_types:
|
|
384
|
+
return instance_types
|
|
385
|
+
|
|
386
|
+
first_os = instance_types[0]["operating_system"]
|
|
387
|
+
dual_platforms = next((it for it in instance_types if it["operating_system"] != first_os), False)
|
|
388
|
+
|
|
389
|
+
if dual_platforms:
|
|
390
|
+
for it in instance_types:
|
|
391
|
+
flat_dict = flatten_dict(it)
|
|
392
|
+
is_gpu = "gpu_count" in flat_dict
|
|
393
|
+
if is_gpu:
|
|
394
|
+
it["description"] = DESCRIPTION_TEMPLATE_OS["gpu"].format(**flat_dict)
|
|
395
|
+
else:
|
|
396
|
+
it["description"] = DESCRIPTION_TEMPLATE_OS["cpu"].format(**flat_dict)
|
|
397
|
+
else:
|
|
398
|
+
for it in instance_types:
|
|
399
|
+
flat_dict = flatten_dict(it)
|
|
400
|
+
is_gpu = "gpu_count" in flat_dict
|
|
401
|
+
if is_gpu:
|
|
402
|
+
it["description"] = DESCRIPTION_TEMPLATE["gpu"].format(**flat_dict)
|
|
403
|
+
else:
|
|
404
|
+
it["description"] = DESCRIPTION_TEMPLATE["cpu"].format(**flat_dict)
|
|
405
|
+
|
|
406
|
+
return instance_types
|
|
407
|
+
|
|
408
|
+
def _build_categories(self):
|
|
409
|
+
"""Build a sorted list of categories where each category contains a sorted list of machines.
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
list: A list of categories where each category is a dictionary with keys: `label`, `content`, and `order`.
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
dikt = {}
|
|
416
|
+
for key in self.instance_types:
|
|
417
|
+
it = self.instance_types[key]
|
|
418
|
+
categories = it["categories"]
|
|
419
|
+
for category in categories:
|
|
420
|
+
label = category["label"]
|
|
421
|
+
if label not in dikt:
|
|
422
|
+
dikt[label] = {
|
|
423
|
+
"label": label,
|
|
424
|
+
"content": [],
|
|
425
|
+
"order": category["order"],
|
|
426
|
+
}
|
|
427
|
+
dikt[label]["content"].append(it)
|
|
428
|
+
|
|
429
|
+
result = []
|
|
430
|
+
for label in dikt:
|
|
431
|
+
category = dikt[label]
|
|
432
|
+
category["content"].sort(key=lambda k: (k["cores"], k["memory"]))
|
|
433
|
+
result.append(category)
|
|
434
|
+
return sorted(result, key=lambda k: k["order"])
|
|
435
|
+
|
|
436
|
+
def _get_provider(self):
|
|
437
|
+
"""Get the provider from the first instance type.
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
str: The provider.
|
|
441
|
+
"""
|
|
442
|
+
first_name = next(iter(self.instance_types))
|
|
443
|
+
if not first_name:
|
|
444
|
+
return None
|
|
445
|
+
if first_name.startswith("cw-"):
|
|
446
|
+
return "cw"
|
|
447
|
+
if "." in first_name:
|
|
448
|
+
return "aws"
|
|
449
|
+
return "gcp"
|
ciocore/loggeria.py
CHANGED
|
@@ -3,10 +3,11 @@ import logging.handlers
|
|
|
3
3
|
import multiprocessing
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
|
+
import tempfile
|
|
6
7
|
import threading
|
|
7
8
|
import traceback
|
|
8
|
-
|
|
9
|
-
from
|
|
9
|
+
|
|
10
|
+
from . import config
|
|
10
11
|
|
|
11
12
|
LEVEL_CRITICAL = "CRITICAL"
|
|
12
13
|
LEVEL_ERROR = "ERROR"
|
|
@@ -29,12 +30,15 @@ LEVEL_MAP = {
|
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
FORMATTER_LIGHT = logging.Formatter("%(asctime)s %(name)s: %(message)s", "%Y-%m-%d %H:%M:%S")
|
|
32
|
-
FORMATTER_VERBOSE = logging.Formatter("%(asctime)s
|
|
33
|
+
FORMATTER_VERBOSE = logging.Formatter("%(asctime)s %(name)s%(levelname)9s %(filename)s-%(lineno)d %(threadName)s: %(message)s")
|
|
33
34
|
DEFAULT_LEVEL_CONSOLE = LEVEL_MAP[LEVEL_INFO]
|
|
34
35
|
DEFAULT_LEVEL_FILE = LEVEL_MAP[LEVEL_DEBUG]
|
|
35
36
|
DEFAULT_LEVEL_LOGGER = LEVEL_MAP[LEVEL_INFO]
|
|
36
37
|
|
|
37
38
|
CONDUCTOR_LOGGER_NAME = "conductor"
|
|
39
|
+
__logger__ = None
|
|
40
|
+
|
|
41
|
+
LOG_PATH = None
|
|
38
42
|
|
|
39
43
|
|
|
40
44
|
class LogLevelFilter(logging.Filter):
|
|
@@ -56,16 +60,19 @@ class LogLevelFilter(logging.Filter):
|
|
|
56
60
|
return int(record.levelno < logging.ERROR)
|
|
57
61
|
|
|
58
62
|
|
|
59
|
-
def setup_conductor_logging(
|
|
63
|
+
def setup_conductor_logging(
|
|
60
64
|
logger_level=DEFAULT_LEVEL_LOGGER,
|
|
61
65
|
console_level=None,
|
|
62
66
|
console_formatter=FORMATTER_LIGHT,
|
|
63
|
-
|
|
67
|
+
log_dirpath=None,
|
|
64
68
|
file_level=None,
|
|
65
69
|
file_formatter=FORMATTER_VERBOSE,
|
|
66
70
|
multiproc=False,
|
|
67
71
|
disable_console_logging = False,
|
|
68
|
-
propagate = True
|
|
72
|
+
propagate = True,
|
|
73
|
+
log_filename=None,
|
|
74
|
+
use_system_log=False
|
|
75
|
+
|
|
69
76
|
):
|
|
70
77
|
"""The is convenience function to help set up logging.
|
|
71
78
|
|
|
@@ -87,6 +94,11 @@ def setup_conductor_logging(
|
|
|
87
94
|
multiproc: bool. If True, a custom file handler will be used that handles multiprocess logging
|
|
88
95
|
correctly. This file handler creates an additional Process.
|
|
89
96
|
"""
|
|
97
|
+
global __logger__
|
|
98
|
+
|
|
99
|
+
if __logger__:
|
|
100
|
+
return
|
|
101
|
+
|
|
90
102
|
# Get the top/parent conductor logger
|
|
91
103
|
logger = get_conductor_logger()
|
|
92
104
|
|
|
@@ -118,38 +130,71 @@ def setup_conductor_logging(
|
|
|
118
130
|
console_handler_out.setFormatter(console_formatter)
|
|
119
131
|
console_handler_err.setFormatter(console_formatter)
|
|
120
132
|
|
|
121
|
-
# Create a file handler
|
|
122
|
-
if
|
|
133
|
+
# Create a file handler. Use the given path or fallback to the default path.
|
|
134
|
+
if log_filename:
|
|
123
135
|
if file_level:
|
|
124
136
|
assert file_level in LEVEL_MAP.values(), "Not a valid log level: %s" % file_level
|
|
137
|
+
|
|
138
|
+
if use_system_log and not log_dirpath:
|
|
139
|
+
log_dirpath = get_system_log_dir()
|
|
140
|
+
|
|
141
|
+
log_dirpath = log_dirpath or get_user_log_dir()
|
|
142
|
+
|
|
125
143
|
# Rotating file handler. Rotates every day (24 hours). Stores 7 days at a time.
|
|
126
144
|
file_handler = create_file_handler(
|
|
127
|
-
|
|
145
|
+
filepath=log_dirpath,
|
|
146
|
+
filename=log_filename,
|
|
147
|
+
level=file_level,
|
|
148
|
+
formatter=file_formatter,
|
|
149
|
+
multiproc=multiproc,
|
|
150
|
+
use_fallback_path=True
|
|
128
151
|
)
|
|
129
152
|
logger.addHandler(file_handler)
|
|
130
153
|
|
|
154
|
+
__logger__ = logger
|
|
131
155
|
|
|
132
|
-
|
|
133
|
-
|
|
156
|
+
|
|
157
|
+
def create_file_handler(filepath, filename, level=None, formatter=None, multiproc=False, use_fallback_path=True):
|
|
158
|
+
"""Create a file handler object in the given filepath with filename.
|
|
134
159
|
|
|
135
160
|
This is a ROTATING file handler, which rotates every day (24 hours) and stores up to 7 days of
|
|
136
161
|
logs at a time (equaling up to as many as 7 log files at a given time.
|
|
162
|
+
|
|
163
|
+
If writing to the given filepath fails due to permissions and use_fallback path is True, the
|
|
164
|
+
logger will try to use the users home folder.
|
|
137
165
|
"""
|
|
166
|
+
|
|
138
167
|
when = "h" # rotate unit is "h" (hours)
|
|
139
168
|
interval = 24 # rotate every 24 units (24 hours)
|
|
140
169
|
backupCount = 7 # Retain up to 7 log files (7 days of log files)
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
if not os.path.exists(filepath):
|
|
173
|
+
os.makedirs(filepath)
|
|
174
|
+
|
|
175
|
+
else:
|
|
176
|
+
tempfile.TemporaryFile(dir=filepath)
|
|
177
|
+
|
|
178
|
+
except PermissionError as error_msg:
|
|
141
179
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
180
|
+
print("Unable to use {} for logs. ({})".format(filepath, error_msg))
|
|
181
|
+
|
|
182
|
+
if filepath != get_user_log_dir and use_fallback_path:
|
|
183
|
+
return create_file_handler(filepath=get_user_log_dir(),
|
|
184
|
+
filename=filename,
|
|
185
|
+
formatter=formatter,
|
|
186
|
+
multiproc=multiproc)
|
|
187
|
+
|
|
188
|
+
else:
|
|
189
|
+
raise
|
|
145
190
|
|
|
146
191
|
if multiproc:
|
|
147
192
|
# Use custom rotating file handler that handles multiprocessing properly
|
|
148
|
-
handler = MPFileHandler(filepath, when=when, interval=interval, backupCount=backupCount)
|
|
193
|
+
handler = MPFileHandler(os.path.join(filepath, filename), when=when, interval=interval, backupCount=backupCount)
|
|
149
194
|
else:
|
|
150
195
|
# Rotating file handler. Rotates every day (24 hours). Stores 7 days at a time.
|
|
151
196
|
handler = logging.handlers.TimedRotatingFileHandler(
|
|
152
|
-
filepath, when=when, interval=interval, backupCount=backupCount
|
|
197
|
+
os.path.join(filepath, filename), when=when, interval=interval, backupCount=backupCount
|
|
153
198
|
)
|
|
154
199
|
if formatter:
|
|
155
200
|
handler.setFormatter(formatter)
|
|
@@ -157,6 +202,9 @@ def create_file_handler(filepath, level=None, formatter=None, multiproc=False):
|
|
|
157
202
|
if level:
|
|
158
203
|
handler.setLevel(level)
|
|
159
204
|
|
|
205
|
+
global LOG_PATH
|
|
206
|
+
LOG_PATH=os.path.join(filepath, filename)
|
|
207
|
+
|
|
160
208
|
return handler
|
|
161
209
|
|
|
162
210
|
|
|
@@ -184,7 +232,7 @@ class MPFileHandler(logging.Handler):
|
|
|
184
232
|
):
|
|
185
233
|
"""See TimedRotatingFileHandler for arg docs."""
|
|
186
234
|
logging.Handler.__init__(self)
|
|
187
|
-
self._handler = TimedRotatingFileHandler(
|
|
235
|
+
self._handler = logging.handlers.TimedRotatingFileHandler(
|
|
188
236
|
filename,
|
|
189
237
|
when=when,
|
|
190
238
|
interval=interval,
|
|
@@ -359,9 +407,30 @@ class TableStr(object):
|
|
|
359
407
|
def get_footer(self):
|
|
360
408
|
return self.footer
|
|
361
409
|
|
|
362
|
-
def get_default_log_dir(platform=None):
|
|
410
|
+
def get_default_log_dir(platform=None, system_log=False):
|
|
411
|
+
|
|
412
|
+
if system_log:
|
|
413
|
+
log_dir = get_system_log_dir(platform)
|
|
414
|
+
|
|
415
|
+
if os.access(log_dir, os.W_OK|os.X_OK):
|
|
416
|
+
return log_dir
|
|
417
|
+
|
|
418
|
+
return get_user_log_dir(platform=platform)
|
|
419
|
+
|
|
420
|
+
def get_system_log_dir(platform=None):
|
|
421
|
+
|
|
422
|
+
platform = platform or sys.platform
|
|
423
|
+
|
|
424
|
+
if platform.startswith('win'):
|
|
425
|
+
return os.path.expandvars(os.path.join("%programdata%", "conductor", "logs"))
|
|
363
426
|
|
|
364
|
-
|
|
427
|
+
else:
|
|
428
|
+
return "/var/log/conductor"
|
|
429
|
+
|
|
430
|
+
def get_user_log_dir(platform=None):
|
|
365
431
|
|
|
366
|
-
|
|
432
|
+
root_path = config.get()['user_dir']
|
|
433
|
+
return os.path.expanduser(os.path.join(root_path, "logs"))
|
|
434
|
+
|
|
435
|
+
|
|
367
436
|
|