dtlpy 1.107.8__py3-none-any.whl → 1.109.19__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 (40) hide show
  1. dtlpy/__init__.py +1 -7
  2. dtlpy/__version__.py +1 -1
  3. dtlpy/entities/__init__.py +5 -4
  4. dtlpy/entities/annotation.py +28 -57
  5. dtlpy/entities/annotation_definitions/base_annotation_definition.py +6 -14
  6. dtlpy/entities/app.py +1 -1
  7. dtlpy/entities/command.py +10 -7
  8. dtlpy/entities/compute.py +77 -94
  9. dtlpy/entities/dataset.py +29 -14
  10. dtlpy/entities/dpk.py +1 -0
  11. dtlpy/entities/filters.py +7 -6
  12. dtlpy/entities/item.py +7 -14
  13. dtlpy/entities/node.py +0 -12
  14. dtlpy/entities/service.py +0 -9
  15. dtlpy/entities/service_driver.py +118 -0
  16. dtlpy/entities/trigger.py +1 -1
  17. dtlpy/new_instance.py +1 -1
  18. dtlpy/repositories/__init__.py +2 -1
  19. dtlpy/repositories/apps.py +8 -4
  20. dtlpy/repositories/collections.py +86 -34
  21. dtlpy/repositories/commands.py +14 -4
  22. dtlpy/repositories/computes.py +173 -127
  23. dtlpy/repositories/datasets.py +20 -9
  24. dtlpy/repositories/downloader.py +20 -8
  25. dtlpy/repositories/dpks.py +26 -1
  26. dtlpy/repositories/items.py +5 -2
  27. dtlpy/repositories/service_drivers.py +213 -0
  28. dtlpy/repositories/services.py +6 -0
  29. dtlpy/repositories/uploader.py +4 -0
  30. dtlpy-1.109.19.dist-info/METADATA +172 -0
  31. {dtlpy-1.107.8.dist-info → dtlpy-1.109.19.dist-info}/RECORD +39 -37
  32. tests/features/environment.py +16 -15
  33. dtlpy-1.107.8.dist-info/METADATA +0 -69
  34. {dtlpy-1.107.8.data → dtlpy-1.109.19.data}/scripts/dlp +0 -0
  35. {dtlpy-1.107.8.data → dtlpy-1.109.19.data}/scripts/dlp.bat +0 -0
  36. {dtlpy-1.107.8.data → dtlpy-1.109.19.data}/scripts/dlp.py +0 -0
  37. {dtlpy-1.107.8.dist-info → dtlpy-1.109.19.dist-info}/LICENSE +0 -0
  38. {dtlpy-1.107.8.dist-info → dtlpy-1.109.19.dist-info}/WHEEL +0 -0
  39. {dtlpy-1.107.8.dist-info → dtlpy-1.109.19.dist-info}/entry_points.txt +0 -0
  40. {dtlpy-1.107.8.dist-info → dtlpy-1.109.19.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,5 @@
1
- from venv import logger
2
- from dtlpy import entities, exceptions, repositories
3
- from dtlpy.entities.dataset import Dataset
4
- from dtlpy.entities.filters import FiltersMethod
5
- from dtlpy.services.api_client import ApiClient
1
+ from .. import entities, exceptions
2
+ from ..services.api_client import ApiClient
6
3
  from typing import List
7
4
 
8
5
  class Collections:
@@ -15,6 +12,26 @@ class Collections:
15
12
  self._dataset = dataset
16
13
  self._item = item
17
14
 
15
+ @property
16
+ def dataset(self) -> entities.Dataset:
17
+ if self._dataset is None:
18
+ raise ValueError("Must set dataset for this action.")
19
+ return self._dataset
20
+
21
+ @dataset.setter
22
+ def dataset(self, dataset: entities.Dataset):
23
+ self._dataset = dataset
24
+
25
+ @property
26
+ def item(self) -> entities.Item:
27
+ if self._item is None:
28
+ raise ValueError("Must set item for this action.")
29
+ return self._item
30
+
31
+ @item.setter
32
+ def item(self, item: entities.Item):
33
+ self._item = item
34
+
18
35
  def create(self, name: str) -> entities.Collection:
19
36
  """
20
37
  Creates a new collection in the dataset.
@@ -22,12 +39,11 @@ class Collections:
22
39
  :param name: The name of the new collection.
23
40
  :return: The created collection details.
24
41
  """
25
- dataset_id = self._dataset.id
26
42
  self.validate_max_collections()
27
43
  self.validate_collection_name(name)
28
44
  payload = {"name": name}
29
45
  success, response = self._client_api.gen_request(
30
- req_type="post", path=f"/datasets/{dataset_id}/items/collections", json_req=payload
46
+ req_type="post", path=f"/datasets/{self.dataset.id}/items/collections", json_req=payload
31
47
  )
32
48
  if success:
33
49
  collection_json = self._single_collection(data=response.json(), name=name)
@@ -43,11 +59,10 @@ class Collections:
43
59
  :param new_name: The new name for the collection.
44
60
  :return: The updated collection details.
45
61
  """
46
- dataset_id = self._dataset.id
47
62
  self.validate_collection_name(new_name)
48
63
  payload = {"name": new_name}
49
64
  success, response = self._client_api.gen_request(
50
- req_type="patch", path=f"/datasets/{dataset_id}/items/collections/{collection_name}", json_req=payload
65
+ req_type="patch", path=f"/datasets/{self.dataset.id}/items/collections/{collection_name}", json_req=payload
51
66
  )
52
67
  if success:
53
68
  collection_json = self._single_collection(data=response.json(), name=new_name)
@@ -61,9 +76,8 @@ class Collections:
61
76
 
62
77
  :param collection_name: The name of the collection to delete.
63
78
  """
64
- dataset_id = self._dataset.id
65
79
  success, response = self._client_api.gen_request(
66
- req_type="delete", path=f"/datasets/{dataset_id}/items/collections/{collection_name}"
80
+ req_type="delete", path=f"/datasets/{self.dataset.id}/items/collections/{collection_name}"
67
81
  )
68
82
  if success:
69
83
  # Wait for the split operation to complete
@@ -74,7 +88,7 @@ class Collections:
74
88
  else:
75
89
  raise exceptions.PlatformException(response)
76
90
 
77
- def clone(self, collection_name: str) -> dict:
91
+ def clone(self, collection_name: str) -> entities.Collection:
78
92
  """
79
93
  Clones an existing collection, creating a new one with a unique name.
80
94
 
@@ -99,7 +113,10 @@ class Collections:
99
113
 
100
114
  # Create the cloned collection
101
115
  cloned_collection = self.create(name=clone_name)
102
- self.assign(dataset_id=self._dataset.id, collections=[cloned_collection.name], collection_key=original_collection['key'])
116
+ filters = entities.Filters()
117
+ filters.add(field=f'metadata.system.collections.{original_collection["key"]}', values=True)
118
+ self.assign(collections=[cloned_collection.name],
119
+ filters=filters)
103
120
  return cloned_collection
104
121
 
105
122
 
@@ -109,9 +126,8 @@ class Collections:
109
126
 
110
127
  :return: A list of collections in the dataset.
111
128
  """
112
- dataset_id = self._dataset.id
113
129
  success, response = self._client_api.gen_request(
114
- req_type="GET", path=f"/datasets/{dataset_id}/items/collections"
130
+ req_type="GET", path=f"/datasets/{self.dataset.id}/items/collections"
115
131
  )
116
132
  if success:
117
133
  data = response.json()
@@ -140,6 +156,17 @@ class Collections:
140
156
  if len(collections) >= 10:
141
157
  raise ValueError("The dataset already has the maximum number of collections (10).")
142
158
 
159
+ def list_missing_collections(self) -> List[str]:
160
+ """
161
+ List all items in the dataset that are not assigned to any collection.
162
+
163
+ :return: A list of item IDs that are not part of any collection.
164
+ """
165
+ filters = entities.Filters()
166
+ filters.add(field='metadata.system.collections', values=None)
167
+ filters.add(field='datasetId', values=self._dataset.id)
168
+ return self._dataset.items.list(filters=filters)
169
+
143
170
  def list_unassigned_items(self) -> list:
144
171
  """
145
172
  List unassigned items in a dataset (items where all collection fields are false).
@@ -147,7 +174,7 @@ class Collections:
147
174
  :return: List of unassigned item IDs
148
175
  :rtype: list
149
176
  """
150
- filters = entities.Filters(method=FiltersMethod.AND) # Use AND method for all conditions
177
+ filters = entities.Filters(method=entities.FiltersMethod.AND) # Use AND method for all conditions
151
178
  collection_fields = [
152
179
  "collections0",
153
180
  "collections1",
@@ -163,7 +190,7 @@ class Collections:
163
190
 
164
191
  # Add each field to the filter with a value of False
165
192
  for field in collection_fields:
166
- filters.add(field=field, values=False, method=FiltersMethod.AND)
193
+ filters.add(field=field, values=False, method=entities.FiltersMethod.AND)
167
194
 
168
195
  missing_ids = []
169
196
  pages = self._dataset.items.list(filters=filters)
@@ -176,31 +203,33 @@ class Collections:
176
203
 
177
204
  def assign(
178
205
  self,
179
- dataset_id: str,
180
206
  collections: List[str],
207
+ dataset_id: str = None,
181
208
  item_id: str = None,
182
- collection_key: str = None
209
+ filters: entities.Filters = None
183
210
  ) -> bool:
184
211
  """
185
212
  Assign an item to a collection. Creates the collection if it does not exist.
186
213
 
187
- :param dataset_id: ID of the dataset.
188
214
  :param collections: List of the collections to assign the item to.
215
+ :param dataset_id: ID of the dataset.
189
216
  :param item_id: (Optional) ID of the item to assign. If not provided, all items in the dataset will be updated.
190
- :param collection_key: (Optional) Key for the bulk assignment. If not provided, no specific metadata will be updated.
217
+ :param filters: (Optional) Filters of items to assign to the collections.
191
218
  :return: True if the assignment was successful, otherwise raises an exception.
192
219
  """
220
+ if not isinstance(collections, list):
221
+ raise ValueError("collections must be a list.")
222
+ if dataset_id is None and self._dataset is not None:
223
+ dataset_id = self.dataset.id
224
+ if item_id is None and self._item is not None:
225
+ item_id = self.item.id
193
226
  # Build the query structure
194
- if collection_key:
195
- query = {
196
- "filter": {
197
- f"metadata.system.collections.{collection_key}": True
198
- }
199
- }
200
- elif item_id:
227
+ if item_id is not None:
201
228
  query = {
202
229
  "id": {"$eq": item_id}
203
230
  }
231
+ elif filters is not None:
232
+ query = filters.prepare().get("filter")
204
233
  else:
205
234
  raise ValueError("Either collection_key or item_id must be provided.")
206
235
 
@@ -226,16 +255,39 @@ class Collections:
226
255
  raise exceptions.PlatformException(f"Failed to assign item to collections: {response}")
227
256
 
228
257
 
229
- def unassign(self, dataset_id: str, item_id: str, collections: List[str]) -> bool:
258
+ def unassign(self,
259
+ collections: List[str],
260
+ dataset_id: str = None,
261
+ item_id: str = None,
262
+ filters: entities.Filters = None) -> bool:
230
263
  """
231
264
  Unassign an item from a collection.
232
- :param item_id: ID of the item.
233
265
  :param collections: List of collection names to unassign.
266
+ :param dataset_id: ID of the dataset.
267
+ :param item_id: ID of the item.
268
+ :param filters: (Optional) Filters of items to unassign from the collections.
234
269
  """
235
- payload = {
236
- "query": {"id": {"$eq": item_id}},
237
- "collections": collections,
238
- }
270
+ if not isinstance(collections, list):
271
+ raise ValueError("collections must be a list.")
272
+ # build the context
273
+ if dataset_id is None and self._dataset is not None:
274
+ dataset_id = self._dataset.id
275
+ if item_id is None and self._item is not None:
276
+ item_id = self._item.id
277
+
278
+ # build the payload
279
+ if item_id is not None and filters is None:
280
+ payload = {
281
+ "query": {"id": {"$eq": item_id}},
282
+ "collections": collections,
283
+ }
284
+ elif filters is not None and item_id is None:
285
+ payload = {
286
+ "query": filters.prepare().get("filter"),
287
+ "collections": collections,
288
+ }
289
+ else:
290
+ raise ValueError("Either item_id or filters must be provided but not both.")
239
291
  success, response = self._client_api.gen_request(
240
292
  req_type="post",
241
293
  path=f"/datasets/{dataset_id}/items/collections/bulk-remove",
@@ -70,7 +70,7 @@ class Commands:
70
70
  return entities.Command.from_json(client_api=self._client_api,
71
71
  _json=response.json())
72
72
 
73
- def wait(self, command_id, timeout=0, step=None, url=None, backoff_factor=1):
73
+ def wait(self, command_id, timeout=0, step=None, url=None, backoff_factor=1, iteration_callback=None):
74
74
  """
75
75
  Wait for command to finish
76
76
 
@@ -84,6 +84,7 @@ class Commands:
84
84
  :param int timeout: int, seconds to wait until TimeoutError is raised. if 0 - wait until done
85
85
  :param int step: int, seconds between polling
86
86
  :param str url: url to the command
87
+ :param function iteration_callback: function to call on each iteration
87
88
  :param float backoff_factor: A backoff factor to apply between attempts after the second try
88
89
  :return: Command object
89
90
  """
@@ -112,9 +113,18 @@ class Commands:
112
113
  elapsed = time.time() - start
113
114
  sleep_time = np.min([timeout - elapsed, backoff_factor * (2 ** num_tries), MAX_SLEEP_TIME])
114
115
  num_tries += 1
115
- logger.debug("Command {!r} is running for {:.2f}[s] and now Going to sleep {:.2f}[s]".format(command.id,
116
- elapsed,
117
- sleep_time))
116
+ logger.debug(
117
+ "Command {!r} is running for {:.2f}[s] and now Going to sleep {:.2f}[s]".format(
118
+ command.id,
119
+ elapsed,
120
+ sleep_time
121
+ )
122
+ )
123
+ if iteration_callback is not None:
124
+ try:
125
+ iteration_callback()
126
+ except Exception as e:
127
+ logger.warning('iteration_callback failed: {}'.format(e.__str__()))
118
128
  time.sleep(sleep_time)
119
129
  pbar.close()
120
130
  if command is None:
@@ -2,11 +2,16 @@ import base64
2
2
  import datetime
3
3
  import json
4
4
 
5
+ from dtlpy import miscellaneous
6
+
5
7
  from ..services.api_client import ApiClient
6
8
  from .. import exceptions, entities, repositories
7
9
  from typing import List, Optional, Dict
8
- from ..entities import ComputeCluster, ComputeContext, ComputeType, Project
10
+ from ..entities import ComputeCluster, ComputeContext, ComputeType
9
11
  from ..entities.integration import IntegrationType
12
+ import logging
13
+
14
+ logger = logging.getLogger(name='dtlpy')
10
15
 
11
16
 
12
17
  class Computes:
@@ -17,6 +22,7 @@ class Computes:
17
22
  self._commands = None
18
23
  self._projects = None
19
24
  self._organizations = None
25
+ self.log_cache = dict()
20
26
 
21
27
  @property
22
28
  def commands(self) -> repositories.Commands:
@@ -46,7 +52,8 @@ class Computes:
46
52
  is_global: Optional[bool] = False,
47
53
  features: Optional[Dict] = None,
48
54
  wait=True,
49
- status: entities.ComputeStatus = None
55
+ status: entities.ComputeStatus = None,
56
+ settings: entities.ComputeSettings = None
50
57
  ):
51
58
  """
52
59
  Create a new compute
@@ -60,18 +67,25 @@ class Computes:
60
67
  :param features: Features
61
68
  :param wait: Wait for compute creation
62
69
  :param status: Compute status
70
+ :param settings: Compute settings
63
71
  :return: Compute
72
+ :rtype: dl.entities.compute.Compute
64
73
  """
65
74
 
75
+ shared_contexts_json = []
76
+ for shared_context in shared_contexts:
77
+ src_json = shared_context.to_json() if isinstance(shared_context, entities.ComputeContext) else shared_context
78
+ shared_contexts_json.append(src_json)
66
79
  payload = {
67
80
  'name': name,
68
81
  'context': context.to_json(),
69
82
  'type': type.value,
70
83
  'global': is_global,
71
84
  'features': features,
72
- 'shared_contexts': [sc.to_json() for sc in shared_contexts],
85
+ 'sharedContexts': shared_contexts_json,
73
86
  'cluster': cluster.to_json(),
74
- 'status': status
87
+ 'status': status,
88
+ "settings": settings.to_json() if isinstance(settings, entities.ComputeSettings) else settings
75
89
  }
76
90
 
77
91
  # request
@@ -84,26 +98,69 @@ class Computes:
84
98
  if not success:
85
99
  raise exceptions.PlatformException(response)
86
100
 
87
- compute = entities.Compute.from_json(
88
- _json=response.json(),
89
- client_api=self._client_api
90
- )
101
+ compute = self._build_compute_by_type(response.json())
91
102
 
92
103
  if wait:
93
104
  command_id = compute.metadata.get('system', {}).get('commands', {}).get('create', None)
94
105
  if command_id is not None:
95
106
  command = self.commands.get(command_id=command_id, url='api/v1/commands/faas/{}'.format(command_id))
96
- command.wait()
107
+ try:
108
+ command.wait(iteration_callback=self.__get_log_compute_progress_callback(compute.id))
109
+ except Exception as e:
110
+ self.log_cache.pop(compute.id, None)
111
+ raise e
97
112
  compute = self.get(compute_id=compute.id)
98
113
 
99
114
  return compute
100
115
 
116
+ def _build_compute_by_type(self, _json):
117
+ if _json.get('type') == 'kubernetes':
118
+ compute = entities.KubernetesCompute.from_json(
119
+ _json=_json,
120
+ client_api=self._client_api
121
+ )
122
+ else:
123
+ compute = entities.Compute.from_json(
124
+ _json=_json,
125
+ client_api=self._client_api
126
+ )
127
+ return compute
128
+
129
+ def __get_log_compute_progress_callback(self, compute_id: str):
130
+ def func():
131
+ compute = self.get(compute_id=compute_id)
132
+ bootstrap_progress = compute.metadata.get('system', {}).get('bootstrapProcess', {}).get('progress', None)
133
+ bootstrap_logs = compute.metadata.get('system', {}).get('bootstrapProcess', {}).get('logs', None)
134
+ validation_progress = compute.metadata.get('system', {}).get('validation', {}).get('progress', None)
135
+ validation_logs = compute.metadata.get('system', {}).get('validation', {}).get('logs', None)
136
+ if bootstrap_progress not in [None, 100]:
137
+ logger.info(f"Bootstrap in progress: {bootstrap_progress}%")
138
+ last_index = len(self.log_cache.get(compute_id, {}).get('bootstrap', []))
139
+ new_logs = bootstrap_logs[last_index:]
140
+ if new_logs:
141
+ logger.info("Bootstrap Logs: {}".format('\n'.join(new_logs)))
142
+ if compute_id not in self.log_cache:
143
+ self.log_cache[compute_id] = {}
144
+ self.log_cache[compute_id]['bootstrap'] = bootstrap_logs
145
+ if validation_progress not in [None, 100]:
146
+ logger.info(f"Validating created compute. Progress: {validation_progress}%")
147
+ last_index = len(self.log_cache.get(compute_id, {}).get('validation', []))
148
+ new_logs = validation_logs[last_index:]
149
+ if new_logs:
150
+ logger.info("Validation Logs: {}".format('\n'.join(new_logs)))
151
+ if compute_id not in self.log_cache:
152
+ self.log_cache[compute_id] = {}
153
+ self.log_cache[compute_id]['validation'] = validation_logs
154
+ return func
155
+
156
+
101
157
  def get(self, compute_id: str):
102
158
  """
103
159
  Get a compute
104
160
 
105
161
  :param compute_id: Compute ID
106
162
  :return: Compute
163
+ :rtype: dl.entities.compute.Compute
107
164
  """
108
165
 
109
166
  # request
@@ -115,10 +172,7 @@ class Computes:
115
172
  if not success:
116
173
  raise exceptions.PlatformException(response)
117
174
 
118
- compute = entities.Compute.from_json(
119
- _json=response.json(),
120
- client_api=self._client_api
121
- )
175
+ compute = self._build_compute_by_type(response.json())
122
176
 
123
177
  return compute
124
178
 
@@ -128,6 +182,7 @@ class Computes:
128
182
 
129
183
  :param compute: Compute
130
184
  :return: Compute
185
+ :rtype: dl.entities.compute.Compute
131
186
  """
132
187
 
133
188
  # request
@@ -140,10 +195,7 @@ class Computes:
140
195
  if not success:
141
196
  raise exceptions.PlatformException(response)
142
197
 
143
- compute = entities.Compute.from_json(
144
- _json=response.json(),
145
- client_api=self._client_api
146
- )
198
+ compute = self._build_compute_by_type(response.json())
147
199
 
148
200
  return compute
149
201
 
@@ -165,6 +217,60 @@ class Computes:
165
217
 
166
218
  return True
167
219
 
220
+ def validate(self, compute_id: str, wait: bool = True):
221
+ """
222
+ Validate a compute
223
+
224
+ :param str compute_id: Compute ID
225
+ :param bool wait: Wait for validation
226
+ :return: Compute
227
+ :rtype: dl.entities.compute.Compute
228
+ """
229
+
230
+ # request
231
+ success, response = self._client_api.gen_request(
232
+ req_type='post',
233
+ path=self._base_url + '/{}/validate'.format(compute_id)
234
+ )
235
+
236
+ if not success:
237
+ raise exceptions.PlatformException(response)
238
+
239
+ compute = self._build_compute_by_type(response.json())
240
+
241
+ if wait:
242
+ command_id = compute.metadata.get('system', {}).get('commands', {}).get('validate', None)
243
+ if command_id is not None:
244
+ command = self.commands.get(command_id=command_id, url='api/v1/commands/faas/{}'.format(command_id))
245
+ try:
246
+ command.wait(iteration_callback=self.__get_log_compute_progress_callback(compute.id))
247
+ except Exception as e:
248
+ self.log_cache.pop(compute.id, None)
249
+ raise e
250
+ compute = self.get(compute_id=compute.id)
251
+
252
+ return compute
253
+
254
+ def list_global(self):
255
+ """
256
+ List computes
257
+
258
+ :return: List of computes
259
+ :rtype: list[str]
260
+ """
261
+
262
+ # request
263
+ success, response = self._client_api.gen_request(
264
+ req_type='get',
265
+ path=self._base_url + '/globals',
266
+ )
267
+
268
+ if not success:
269
+ raise exceptions.PlatformException(response)
270
+
271
+
272
+ return response.json()
273
+
168
274
  @staticmethod
169
275
  def read_file(file_path):
170
276
  try:
@@ -205,7 +311,9 @@ class Computes:
205
311
  [],
206
312
  cluster,
207
313
  ComputeType.KUBERNETES,
208
- status=config['config'].get('status', None))
314
+ status=config['config'].get('status', None),
315
+ settings=config['config'].get('settings', None))
316
+
209
317
  return compute
210
318
 
211
319
  def create_from_config_file(self, config_file_path, org_id, project_name: Optional[str] = None):
@@ -221,121 +329,59 @@ class Computes:
221
329
  return compute
222
330
 
223
331
 
224
- class ServiceDrivers:
225
-
226
- def __init__(self, client_api: ApiClient):
227
- self._client_api = client_api
228
- self._base_url = '/serviceDrivers'
229
-
230
- def create(
231
- self,
232
- name: str,
233
- compute_id: str,
234
- context: entities.ComputeContext
235
- ):
236
- """
237
- Create a new service driver
238
-
239
- :param name: Service driver name
240
- :param compute_id: Compute ID
241
- :param context: Compute context
242
- :return: Service driver
243
-
244
- """
245
-
246
- payload = {
247
- 'name': name,
248
- 'computeId': compute_id,
249
- 'context': context.to_json()
250
- }
251
-
252
- # request
253
- success, response = self._client_api.gen_request(
254
- req_type='post',
255
- path=self._base_url,
256
- json_req=payload
257
- )
258
-
259
- if not success:
260
- raise exceptions.PlatformException(response)
261
-
262
- service_driver = entities.ServiceDriver.from_json(
263
- _json=response.json(),
264
- client_api=self._client_api
265
- )
266
-
267
- return service_driver
268
-
269
- def get(self, service_driver_id: str):
270
- """
271
- Get a service driver
272
-
273
- :param service_driver_id: Service driver ID
274
- :return: Service driver
275
- """
276
-
277
- # request
278
- success, response = self._client_api.gen_request(
279
- req_type='get',
280
- path=self._base_url + '/{}'.format(service_driver_id)
281
- )
282
-
332
+ def _list(self, filters: entities.Filters):
333
+ url = self._base_url + '/query'
334
+ success, response = self._client_api.gen_request(req_type='POST',
335
+ path=url,
336
+ json_req=filters.prepare())
283
337
  if not success:
284
338
  raise exceptions.PlatformException(response)
285
339
 
286
- service_driver = entities.ServiceDriver.from_json(
287
- _json=response.json(),
288
- client_api=self._client_api
289
- )
290
-
291
- return service_driver
292
-
293
- def delete(self, service_driver_id: str):
340
+ return response.json()
341
+
342
+ def _build_entities_from_response(self, response_items) -> miscellaneous.List[entities.Compute]:
343
+ pool = self._client_api.thread_pools(pool_name='entity.create')
344
+ jobs = [None for _ in range(len(response_items))]
345
+ for i_item, item in enumerate(response_items):
346
+ jobs[i_item] = pool.submit(entities.Compute._protected_from_json,
347
+ **{'client_api': self._client_api,
348
+ '_json': item})
349
+ results = [j.result() for j in jobs]
350
+ _ = [logger.warning(r[1]) for r in results if r[0] is False]
351
+ items = miscellaneous.List([r[1] for r in results if r[0] is True])
352
+ return items
353
+
354
+ def list(self, filters: entities.Filters = None) -> entities.PagedEntities:
294
355
  """
295
- Delete a service driver
296
-
297
- :param service_driver_id: Service driver ID
298
- """
299
-
300
- # request
301
- success, response = self._client_api.gen_request(
302
- req_type='delete',
303
- path=self._base_url + '/{}'.format(service_driver_id)
304
- )
305
-
306
- if not success:
307
- raise exceptions.PlatformException(response)
356
+ List all services drivers
308
357
 
309
- return True
358
+ :param dtlpy.entities.filters.Filters filters: Filters entity or a dictionary containing filters parameters
359
+ :return: Paged entity
360
+ :rtype: dtlpy.entities.paged_entities.PagedEntities
310
361
 
311
- def set_default(self, service_driver_id: str, org_id: str, update_existing_services=False):
312
- """
313
- Set a service driver as default
362
+ **Example**:
314
363
 
315
- :param service_driver_id: Compute name
316
- :param org_id: Organization ID
317
- :param update_existing_services: Update existing services
364
+ .. code-block:: python
318
365
 
319
- :return: Service driver
366
+ services = dl.service_drivers.list()
320
367
  """
321
-
322
- # request
323
- success, response = self._client_api.gen_request(
324
- req_type='post',
325
- path=self._base_url + '/default',
326
- json_req={
327
- 'organizationId': org_id,
328
- 'updateExistingServices': update_existing_services,
329
- 'driverName': service_driver_id
330
- }
331
- )
332
-
333
- if not success:
334
- raise exceptions.PlatformException(response)
335
-
336
- service_driver = entities.ServiceDriver.from_json(
337
- _json=response.json(),
338
- client_api=self._client_api
339
- )
340
-
341
- return service_driver
368
+ # default filters
369
+ if filters is None:
370
+ filters = entities.Filters(resource=entities.FiltersResource.COMPUTE)
371
+
372
+ if filters.resource != entities.FiltersResource.COMPUTE:
373
+ raise exceptions.PlatformException(
374
+ error='400',
375
+ message='Filters resource must to be FiltersResource.COMPUTE. Got: {!r}'.format(
376
+ filters.resource))
377
+
378
+ if not isinstance(filters, entities.Filters):
379
+ raise exceptions.PlatformException('400', 'Unknown filters type')
380
+
381
+ paged = entities.PagedEntities(items_repository=self,
382
+ filters=filters,
383
+ page_offset=filters.page,
384
+ page_size=filters.page_size,
385
+ client_api=self._client_api)
386
+ paged.get_page()
387
+ return paged