qarnot 2.18.0__py3-none-any.whl → 2.20.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
qarnot/__init__.py CHANGED
@@ -62,6 +62,7 @@ def get_url(key, **kwargs):
62
62
  'task update': '/tasks/{uuid}', # GET->result; DELETE -> abort, PATCH -> update resources
63
63
  'task snapshot': '/tasks/{uuid}/snapshot/periodic', # POST -> snapshots
64
64
  'task instant': '/tasks/{uuid}/snapshot', # POST -> get a snapshot
65
+ 'task instant status': '/tasks/{uuid}/snapshot/{snapshotId}', # GET -> get a snapshot status
65
66
  'task stdout': '/tasks/{uuid}/stdout', # GET -> task stdout
66
67
  'task stderr': '/tasks/{uuid}/stderr', # GET -> task stderr
67
68
  'task instance stdout': '/tasks/{uuid}/stdout/{instanceId}', # GET -> task instance stdout
qarnot/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-05-22T14:41:22+0200",
11
+ "date": "2026-02-06T11:26:35+0100",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "9361585630d3dbcc8fa50ecbb9d20d592234a808",
15
- "version": "v2.18.0"
14
+ "full-revisionid": "a21d5058a77a68da123d95dd64550400fafb2bd2",
15
+ "version": "v2.20.1"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -70,7 +70,7 @@ class UserReservedSchedulingQuota(UserSchedulingQuota):
70
70
  """Describes a reserved scheduling quota for the user.
71
71
  """
72
72
 
73
- def __init__(self, machine_key: str, max_cores: int, running_cores_count: int, max_instances: int, running_instances_count: int):
73
+ def __init__(self, reservation_name: str, machine_key: str, max_cores: int, running_cores_count: int, max_instances: int, running_instances_count: int):
74
74
  """Create a new UserReservedSchedulingQuota object describing a reserved scheduling quota for the user.
75
75
 
76
76
  :param str machine_key: Machine key of the reservation.
@@ -86,6 +86,11 @@ class UserReservedSchedulingQuota(UserSchedulingQuota):
86
86
 
87
87
  Machine key of the reservation.
88
88
  """
89
+ self.reservation_name = reservation_name
90
+ """:type: :class:`str`
91
+
92
+ Name of the reservation.
93
+ """
89
94
 
90
95
  @classmethod
91
96
  def from_json(cls, json: Dict[str, Any]):
@@ -97,6 +102,7 @@ class UserReservedSchedulingQuota(UserSchedulingQuota):
97
102
  if json is None:
98
103
  return None
99
104
  return cls(
105
+ json.get('reservationName'),
100
106
  json.get('machineKey'),
101
107
  json.get('maxCores'),
102
108
  json.get('runningCoresCount'),
@@ -142,7 +148,7 @@ class UserComputingQuotas(object):
142
148
  return cls(
143
149
  UserSchedulingQuota.from_json(json.get('flex')),
144
150
  UserSchedulingQuota.from_json(json.get('onDemand')),
145
- [UserReservedSchedulingQuota.from_json(v) for v in json.get('reserved', []) if v is not None]
151
+ [UserReservedSchedulingQuota.from_json(v) for v in (json.get('reserved') if json.get('reserved') is not None else []) if v is not None]
146
152
  )
147
153
 
148
154
 
@@ -201,7 +207,7 @@ class OrganizationReservedSchedulingQuota(OrganizationSchedulingQuota):
201
207
  """Describes a reserved scheduling quota for the organization.
202
208
  """
203
209
 
204
- def __init__(self, machine_key: str, max_cores: int, running_cores_count: int, max_instances: int, running_instances_count: int):
210
+ def __init__(self, reservation_name: str, machine_key: str, max_cores: int, running_cores_count: int, max_instances: int, running_instances_count: int):
205
211
  """Create a new OrganizationReservedSchedulingQuota object describing a reserved scheduling quota for the organization.
206
212
 
207
213
  :param str machine_key: Machine key of the reservation.
@@ -217,6 +223,11 @@ class OrganizationReservedSchedulingQuota(OrganizationSchedulingQuota):
217
223
 
218
224
  Machine key of the reservation.
219
225
  """
226
+ self.reservation_name = reservation_name
227
+ """:type: :class:`str`
228
+
229
+ Name of the reservation.
230
+ """
220
231
 
221
232
  @classmethod
222
233
  def from_json(cls, json: Dict[str, Any]):
@@ -228,6 +239,7 @@ class OrganizationReservedSchedulingQuota(OrganizationSchedulingQuota):
228
239
  if json is None:
229
240
  return None
230
241
  return cls(
242
+ json.get('reservationName'),
231
243
  json.get('machineKey'),
232
244
  json.get('maxCores'),
233
245
  json.get('runningCoresCount'),
@@ -279,7 +291,7 @@ class OrganizationComputingQuotas(object):
279
291
  json.get('name'),
280
292
  OrganizationSchedulingQuota.from_json(json.get('flex')),
281
293
  OrganizationSchedulingQuota.from_json(json.get('onDemand')),
282
- [OrganizationReservedSchedulingQuota.from_json(v) for v in json.get('reserved', []) if v is not None]
294
+ [OrganizationReservedSchedulingQuota.from_json(v) for v in (json.get('reserved') if json.get('reserved') is not None else []) if v is not None]
283
295
  )
284
296
 
285
297
 
@@ -20,7 +20,18 @@ class ForcedNetworkRule(object):
20
20
  priority: str = None,
21
21
  description: str = None,
22
22
  to_qbox: Optional[bool] = None,
23
- to_payload: Optional[bool] = None):
23
+ to_payload: Optional[bool] = None,
24
+ name: str = None,
25
+ application_type: str = None):
26
+
27
+ self.name = name
28
+ """:type: :class:`str`
29
+
30
+ Name of the associated rule."""
31
+ self.application_type = application_type
32
+ """:type: :class:`str`
33
+
34
+ Application layer protocol used / hint about it (e.g. ssh, http, https...)."""
24
35
  self.inbound = inbound
25
36
  """:type: :class:`bool`
26
37
 
@@ -84,6 +95,14 @@ class ForcedNetworkRule(object):
84
95
  :returns: The created :class:`~qarnot.forced_network_rule.ForcedNetworkRule`
85
96
  """
86
97
 
98
+ name: str = None
99
+ if 'name' in json:
100
+ name = str(json.get("name"))
101
+
102
+ application_type: str = None
103
+ if 'applicationType' in json:
104
+ application_type = str(json.get("applicationType"))
105
+
87
106
  inbound: bool = bool(json.get("inbound"))
88
107
  proto: str = str(json.get("proto"))
89
108
 
@@ -96,12 +115,12 @@ class ForcedNetworkRule(object):
96
115
  to = str(json.get("to"))
97
116
 
98
117
  public_host: str = None
99
- if 'public_host' in json:
100
- public_host = str(json.get("public_host"))
118
+ if 'publicHost' in json:
119
+ public_host = str(json.get("publicHost"))
101
120
 
102
121
  public_port: str = None
103
- if 'public_port' in json:
104
- public_port = str(json.get("public_port"))
122
+ if 'publicPort' in json:
123
+ public_port = str(json.get("publicPort"))
105
124
 
106
125
  forwarder: str = None
107
126
  if 'forwarder' in json:
@@ -116,12 +135,12 @@ class ForcedNetworkRule(object):
116
135
  description = str(json.get("description"))
117
136
 
118
137
  to_qbox: Optional[bool] = None
119
- if 'to_qbox' in json:
120
- to_qbox = bool(json.get("to_qbox"))
138
+ if 'toQBox' in json:
139
+ to_qbox = bool(json.get("toQBox"))
121
140
 
122
141
  to_payload: Optional[bool] = None
123
- if 'to_payload' in json:
124
- to_payload = bool(json.get("to_payload"))
142
+ if 'toPayload' in json:
143
+ to_payload = bool(json.get("toPayload"))
125
144
 
126
145
  return ForcedNetworkRule(
127
146
  inbound,
@@ -134,7 +153,9 @@ class ForcedNetworkRule(object):
134
153
  priority,
135
154
  description,
136
155
  to_qbox,
137
- to_payload)
156
+ to_payload,
157
+ name,
158
+ application_type)
138
159
 
139
160
  def to_json(self):
140
161
  result: Dict[str, Union[str, bool]] = {
@@ -142,6 +163,12 @@ class ForcedNetworkRule(object):
142
163
  "proto": self.proto,
143
164
  }
144
165
 
166
+ if self.name is not None:
167
+ result["name"] = self.name
168
+
169
+ if self.application_type is not None:
170
+ result["applicationType"] = self.application_type
171
+
145
172
  if self.port is not None:
146
173
  result["port"] = self.port
147
174
 
@@ -149,10 +176,10 @@ class ForcedNetworkRule(object):
149
176
  result["to"] = self.to
150
177
 
151
178
  if self.public_host is not None:
152
- result["public_host"] = self.public_host
179
+ result["publicHost"] = self.public_host
153
180
 
154
181
  if self.public_port is not None:
155
- result["public_port"] = self.public_port
182
+ result["publicPort"] = self.public_port
156
183
 
157
184
  if self.forwarder is not None:
158
185
  result["forwarder"] = self.forwarder
@@ -164,9 +191,9 @@ class ForcedNetworkRule(object):
164
191
  result["description"] = self.description
165
192
 
166
193
  if self.to_qbox is not None:
167
- result["to_qbox"] = self.to_qbox
194
+ result["toQBox"] = self.to_qbox
168
195
 
169
196
  if self.to_payload is not None:
170
- result["to_payload"] = self.to_payload
197
+ result["toPayload"] = self.to_payload
171
198
 
172
199
  return result
@@ -0,0 +1,48 @@
1
+ """Multi Slots Settings that can be used when creating a pool"""
2
+
3
+ from typing import Dict
4
+
5
+
6
+ class MultiSlotsSettings(object):
7
+ """Represents task multi slots settings."""
8
+
9
+ _slotsPerNode: int = None
10
+
11
+ def __init__(self, slotsPerNode: int = None):
12
+ """Create a new :class:`~qarnot.multi_slots_settings.MultiSlotsSettings`.
13
+
14
+ :param slotsPerNode: slots per node
15
+ :type slotsPerNode: int
16
+ """
17
+ self._slotsPerNode = slotsPerNode
18
+
19
+ @classmethod
20
+ def from_json(cls, json: Dict[str, int]):
21
+ """Create the multi slots settings from json.
22
+
23
+ :param dict json: Dictionary representing the multi slots settings
24
+ :returns: The created :class:`~qarnot.multi_slots_settings.MultiSlotsSettings`
25
+ """
26
+ slotsPerNode: int = json.get("slotsPerNode")
27
+ return MultiSlotsSettings(slotsPerNode)
28
+
29
+ def to_json(self) -> Dict[str, int]:
30
+ """Get a dict ready to be json packed.
31
+
32
+ :return: the json elements of the class.
33
+ :rtype: `dict`
34
+ """
35
+ return {
36
+ "slotsPerNode": self._slotsPerNode
37
+ }
38
+
39
+ def __eq__(self, other):
40
+ if other is None or not isinstance(other, MultiSlotsSettings):
41
+ return False
42
+ return self._slotsPerNode == other._slotsPerNode
43
+
44
+ def __str__(self) -> str:
45
+ return "multi slots settings: slotsPerNode {}.".format(self._slotsPerNode)
46
+
47
+ def __repr__(self) -> str:
48
+ return "multi_slots_settings.MultiSlotsSettings(slotsPerNode: {})".format(self._slotsPerNode)
qarnot/pool.py CHANGED
@@ -19,6 +19,7 @@ from typing import Dict, List, Optional
19
19
 
20
20
  from qarnot.carbon_facts import CarbonClient, CarbonFacts
21
21
  from qarnot.retry_settings import RetrySettings
22
+ from qarnot.multi_slots_settings import MultiSlotsSettings
22
23
  from qarnot.forced_network_rule import ForcedNetworkRule
23
24
  from qarnot.secrets import SecretsAccessRights
24
25
 
@@ -86,6 +87,7 @@ class Pool(object):
86
87
  self._update_cache_time = 5
87
88
  self._scheduling_type = scheduling_type
88
89
  self._targeted_reserved_machine_key: str = None
90
+ self._targeted_reservation_name: str = None
89
91
 
90
92
  self._last_cache = time.time()
91
93
  self._instancecount = instancecount
@@ -128,6 +130,7 @@ class Pool(object):
128
130
  self._default_resources_cache_ttl_sec: Optional[int] = None
129
131
  self._privileges: Privileges = Privileges()
130
132
  self._default_retry_settings: RetrySettings = RetrySettings()
133
+ self._multi_slots_settings: Optional[MultiSlotsSettings] = None
131
134
  self._forced_network_rules: List[ForcedNetworkRule] = []
132
135
  self._secrets_access_rights: SecretsAccessRights = SecretsAccessRights()
133
136
 
@@ -238,10 +241,13 @@ class Pool(object):
238
241
  self._hardware_constraints = [HardwareConstraint.from_json(hw_constraint_dict) for hw_constraint_dict in json_pool.get("hardwareConstraints", [])]
239
242
  self._default_resources_cache_ttl_sec = json_pool.get("defaultResourcesCacheTTLSec", None)
240
243
  self._targeted_reserved_machine_key = json_pool.get("targetedReservedMachineKey", None)
244
+ self._targeted_reservation_name = json_pool.get("targetedReservationName", None)
241
245
  if 'privileges' in json_pool:
242
246
  self._privileges = Privileges.from_json(json_pool.get("privileges"))
243
247
  if 'defaultRetrySettings' in json_pool:
244
248
  self._default_retry_settings = RetrySettings.from_json(json_pool.get("defaultRetrySettings"))
249
+ if 'multiSlotsSettings' in json_pool:
250
+ self._multi_slots_settings = MultiSlotsSettings.from_json(json_pool.get("multiSlotsSettings"))
245
251
  if 'schedulingType' in json_pool:
246
252
  self._scheduling_type = SchedulingType.from_string(json_pool.get("schedulingType"))
247
253
  self._forced_network_rules = [ForcedNetworkRule.from_json(forced_network_dict) for forced_network_dict in json_pool.get("forcedNetworkRules", [])]
@@ -309,12 +315,18 @@ class Pool(object):
309
315
  if self._targeted_reserved_machine_key is not None:
310
316
  json_pool['targetedReservedMachineKey'] = self._targeted_reserved_machine_key
311
317
 
318
+ if self._targeted_reservation_name is not None:
319
+ json_pool['targetedReservationName'] = self._targeted_reservation_name
320
+
312
321
  if self._forced_network_rules is not None:
313
322
  json_pool['forcedNetworkRules'] = [x.to_json() for x in self._forced_network_rules]
314
323
 
315
324
  if self._secrets_access_rights:
316
325
  json_pool['secretsAccessRights'] = self._secrets_access_rights.to_json()
317
326
 
327
+ if self._multi_slots_settings:
328
+ json_pool['multiSlotsSettings'] = self._multi_slots_settings.to_json()
329
+
318
330
  return json_pool
319
331
 
320
332
  def submit(self):
@@ -1279,6 +1291,24 @@ class Pool(object):
1279
1291
  raise AttributeError("can't set attribute on a submitted job")
1280
1292
  self._completion_time_to_live = _util.parse_to_timespan_string(value)
1281
1293
 
1294
+ @property
1295
+ def multi_slots_settings(self):
1296
+ """
1297
+ :getter: Returns this pool's multi slots settings.
1298
+ :type: :class:`~qarnot.multi_slots_settings.MultiSlotsSettings`
1299
+ :default_value: None
1300
+ """
1301
+ self._update_if_summary()
1302
+ return self._multi_slots_settings
1303
+
1304
+ @multi_slots_settings.setter
1305
+ def multi_slots_settings(self, value):
1306
+ """Setter for multi_slots_settings, this can only be set before pool's submission"""
1307
+ self._update_if_summary()
1308
+ if self._multi_slots_settings is not None:
1309
+ raise AttributeError("can't set attribute on a submitted pool")
1310
+ self._multi_slots_settings = value
1311
+
1282
1312
  @property
1283
1313
  def previous_state(self):
1284
1314
  """
@@ -1497,6 +1527,9 @@ class Pool(object):
1497
1527
 
1498
1528
  :getter: The reserved machine key when using the "reserved" scheduling type
1499
1529
 
1530
+ .. deprecated:: v2.19.0
1531
+ Use `self.targeted_reservation_name` instead.
1532
+
1500
1533
  :raises AttributeError: trying to set this after the pool is submitted
1501
1534
  """
1502
1535
  return self._targeted_reserved_machine_key
@@ -1510,6 +1543,25 @@ class Pool(object):
1510
1543
 
1511
1544
  self._targeted_reserved_machine_key = value
1512
1545
 
1546
+ @property
1547
+ def targeted_reservation_name(self) -> str:
1548
+ """:type: :class:`str`
1549
+
1550
+ :getter: The name of the reservation that describes the targeted machines when using the "reserved" scheduling type
1551
+
1552
+ :raises AttributeError: trying to set this after the task is submitted
1553
+ """
1554
+ return self._targeted_reservation_name
1555
+
1556
+ @targeted_reservation_name.setter
1557
+ def targeted_reservation_name(self, value: str):
1558
+ """Setted for targeted_reservation_name
1559
+ """
1560
+ if self.uuid is not None:
1561
+ raise AttributeError("can't set attribute on a launched task")
1562
+
1563
+ self._targeted_reservation_name = value
1564
+
1513
1565
  def __repr__(self):
1514
1566
  return '{0} - {1} - {2} - {3} - {5} - InstanceCount : {4} - Resources : {6} '\
1515
1567
  'Tag {7} - IsElastic {8} - ElasticMin {9} - ElasticMax {10} - ElasticMinIdle {11} -'\
qarnot/snapshot.py ADDED
@@ -0,0 +1,244 @@
1
+ """Module to handle snapshot"""
2
+
3
+ # Copyright 2017 Qarnot computing
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ from datetime import datetime
18
+ from typing import Optional, Dict, Any, Union
19
+
20
+
21
+ class SnapshotConfiguration():
22
+ """Configuration used for a task snapshot"""
23
+
24
+ _whitelist: Optional[str] = None
25
+ _blacklist: Optional[str] = None
26
+ _bucket_name: Optional[str] = None
27
+ _bucket_prefix: Optional[str] = None
28
+
29
+ def __init__(self,
30
+ whitelist: Optional[str] = None,
31
+ blacklist: Optional[str] = None,
32
+ bucket_name: Optional[str] = None,
33
+ bucket_prefix: Optional[str] = None):
34
+ """The SnapshotConfiguration constructor.
35
+
36
+ :param str whitelist: Whitelist filter for the snapshot.
37
+ :param str blacklist: Blacklist filter for the snapshot.
38
+ :param str bucket_name: Name of the bucket in which to upload the snapshot.
39
+ :param str bucket_prefix: Prefix added to the files uploaded for the snapshot.
40
+ :returns: The created :class:`~qarnot.snapshot.SnapshotConfiguration`.
41
+ """
42
+ self._whitelist = whitelist
43
+ self._blacklist = blacklist
44
+ self._bucket_name = bucket_name
45
+ self._bucket_prefix = bucket_prefix
46
+
47
+ def to_json(self) -> Dict[str, Optional[Union[str, int]]]:
48
+ """Get a SnapshotConfiguration ready to be json packed.
49
+
50
+ :return: the json representation of a snapshot configuration.
51
+ :rtype: Dict[str, Optional[str|int]]
52
+ """
53
+ return {
54
+ "whitelist": self._whitelist,
55
+ "blacklist": self._blacklist,
56
+ "bucket": self._bucket_name,
57
+ "bucketPrefix": self._bucket_prefix
58
+ }
59
+
60
+ @classmethod
61
+ def from_json(cls, json: Dict[str, Optional[str]]):
62
+ """Create a SnapshotConfiguration from a json representation
63
+
64
+ :param json: the json to use to create the SnapshotConfiguration object.
65
+ :type json: `Dict[str, Optional[str]]`
66
+ :returns: The created :class:`~qarnot.snapshot.SnapshotConfiguration`.
67
+ """
68
+ if json is None:
69
+ return None
70
+
71
+ whitelist = json.get("whitelist")
72
+ blacklist = json.get("blacklist")
73
+ bucket_name = json.get("bucket")
74
+ bucket_prefix = json.get("bucketPrefix")
75
+
76
+ return SnapshotConfiguration(whitelist, blacklist, bucket_name, bucket_prefix)
77
+
78
+
79
+ class PeriodicSnapshotConfiguration(SnapshotConfiguration):
80
+ """Configuration used for a task periodic snapshot"""
81
+ _interval: int = None
82
+
83
+ def __init__(self,
84
+ interval: int,
85
+ whitelist: Optional[str] = None,
86
+ blacklist: Optional[str] = None,
87
+ bucket_name: Optional[str] = None,
88
+ bucket_prefix: Optional[str] = None):
89
+ """The PeriodicSnapshotConfiguration constructor.
90
+
91
+ :param int interval: Interval (in seconds) between two periodic snapshots.
92
+ :param str whitelist: Whitelist filter for the snapshot.
93
+ :param str blacklist: Blacklist filter for the snapshot.
94
+ :param str bucket_name: Bucket where to upload the snapshot.
95
+ :param str bucket_prefix: Prefix added to the files uploaded for the snapshot.
96
+ :returns: The created :class:`~qarnot.snapshot.SnapshotConfiguration`.
97
+ """
98
+ super().__init__(whitelist, blacklist, bucket_name, bucket_prefix)
99
+ self._interval = interval
100
+
101
+ def to_json(self) -> Dict[str, Optional[Union[str, int]]]:
102
+ """Get a PeriodicSnapshotConfiguration ready to be json packed.
103
+
104
+ :return: the json representation of a periodic snapshot configuration.
105
+ :rtype: Dict[str, Optional[str|int]]
106
+ """
107
+ json: Dict[str, Optional[Union[str, int]]] = super().to_json()
108
+ json["interval"] = int(self._interval)
109
+ return json
110
+
111
+
112
+ class SnapshotStatus():
113
+ """Represents a Snapshot of a task."""
114
+ status: str = None
115
+ is_completed: bool = False
116
+
117
+ @classmethod
118
+ def from_string(cls, status: str):
119
+ """Create a snapshot status from string.
120
+
121
+ :returns: The created :class:`~qarnot.snapshot.SnapshotStatus`.
122
+ """
123
+
124
+ if status is None:
125
+ return TriggeredStatus()
126
+
127
+ status = str(status)
128
+
129
+ if status.lower() == TriggeredStatus.status.lower():
130
+ return TriggeredStatus()
131
+ elif status.lower() == InProgressStatus.status.lower():
132
+ return InProgressStatus()
133
+ elif status.lower() == SuccessStatus.status.lower():
134
+ return SuccessStatus()
135
+ elif status.lower() == FailedStatus.status.lower():
136
+ return FailedStatus()
137
+ else:
138
+ return TriggeredStatus()
139
+
140
+ def __str__(self) -> str:
141
+ return "snapshot status {}.".format(self.status)
142
+
143
+ def __repr__(self) -> str:
144
+ return str(self.status)
145
+
146
+
147
+ class TriggeredStatus(SnapshotStatus):
148
+ """Represents a snapshot triggered status """
149
+ status: str = "Triggered"
150
+ is_completed: bool = False
151
+
152
+ def __init__(self):
153
+ """ Create a new snapshot triggered status."""
154
+
155
+
156
+ class InProgressStatus(SnapshotStatus):
157
+ """Represents a snapshot in progress status"""
158
+ status: str = "InProgress"
159
+ is_completed: bool = False
160
+
161
+ def __init__(self):
162
+ """ Create a new snapshot in progress status."""
163
+
164
+
165
+ class SuccessStatus(SnapshotStatus):
166
+ """Represents a snapshot success status"""
167
+ status: str = "Success"
168
+ is_completed: bool = True
169
+
170
+ def __init__(self):
171
+ """ Create a new snapshot success status."""
172
+
173
+
174
+ class FailedStatus(SnapshotStatus):
175
+ """Represents snapshot failed status"""
176
+ status: str = "Failure"
177
+ is_completed: bool = True
178
+
179
+ def __init__(self):
180
+ """ Create a new snapshot failed status."""
181
+
182
+
183
+ class Snapshot():
184
+ """Represents a Snapshot of a task."""
185
+ _id: str = None
186
+ _task_uuid: str = None
187
+ _trigger_date: datetime = None
188
+ _last_update_date: Optional[datetime] = None
189
+ _snapshot_config: SnapshotConfiguration = None
190
+ _status: SnapshotStatus = None
191
+ _size_to_upload: Optional[int] = None
192
+ _transferred_size: Optional[int] = None
193
+
194
+ def __init__(self,
195
+ uid: str = None,
196
+ task_uuid: str = None,
197
+ trigger_date: datetime = None,
198
+ last_update_date: Optional[datetime] = None,
199
+ snapshot_config: SnapshotConfiguration = None,
200
+ status: SnapshotStatus = None,
201
+ size_to_upload: Optional[int] = None,
202
+ transferred_size: Optional[int] = None):
203
+ """The Snapshot constructor.
204
+
205
+ :param str uid: identifier of the snapshot.
206
+ :param str task_uuid: identifier of the task hat have been snapshoted.
207
+ :param datetime trigger_date: the date (utc) when the snapshot was requested.
208
+ :param datetime last_update_date: date (utc) of the last update of the current state of the snapshot.
209
+ :param `~qarnot.snapshot.SnapshotConfiguration` snapshot_config: the filter and output configuration of the snapshot.
210
+ :param `~qarnot.snapshot.SnapshotStatus` status: Current progress status of the snapshot.
211
+ :param int size_to_upload: total size (in bytes) expected to upload for the snapshot.
212
+ :param int transferred_size: current size (in bytes) of the upload for the snapshot.
213
+ :returns: The created :class:`~qarnot.snapshot.Snapshot`.
214
+ """
215
+ self._id = uid
216
+ self._task_uuid = task_uuid
217
+ self._trigger_date = trigger_date
218
+ self._last_update_date = last_update_date
219
+ self._snapshot_config = snapshot_config
220
+ self._status = status
221
+ self._size_to_upload = size_to_upload
222
+ self._transferred_size = transferred_size
223
+
224
+ @classmethod
225
+ def from_json(cls, json: Dict[str, Any]):
226
+ """Create a Snapshot from a json representation
227
+
228
+ :param json: the json to use to create the Snapshot object.
229
+ :type json: `Dict[str, Any]`
230
+ :returns: The created :class:`~qarnot.snapshot.Snapshot`.
231
+ """
232
+ if json is None:
233
+ return None
234
+
235
+ uid = json.get("id")
236
+ task_uuid = json.get("taskUuid")
237
+ trigger_date = json.get("triggerDate")
238
+ last_update_date = json.get("lastUpdateDate")
239
+ snapshot_config = SnapshotConfiguration.from_json(json.get("snapshotConfig"))
240
+ status = SnapshotStatus.from_string(json.get("status"))
241
+ size_to_upload = json.get("sizeToUpload")
242
+ transferred_size = json.get("transferredSize")
243
+
244
+ return Snapshot(uid, task_uuid, trigger_date, last_update_date, snapshot_config, status, size_to_upload, transferred_size)
qarnot/task.py CHANGED
@@ -31,6 +31,7 @@ from .status import Status
31
31
  from .hardware_constraint import HardwareConstraint
32
32
  from .forced_constant import ForcedConstant
33
33
  from .scheduling_type import SchedulingType
34
+ from .snapshot import SnapshotConfiguration, PeriodicSnapshotConfiguration, Snapshot
34
35
  from .privileges import Privileges
35
36
  from .bucket import Bucket
36
37
  from .pool import Pool
@@ -132,6 +133,7 @@ class Task(object):
132
133
  self._secrets_access_rights: SecretsAccessRights = SecretsAccessRights()
133
134
  self._scheduling_type = scheduling_type
134
135
  self._targeted_reserved_machine_key: str = None
136
+ self._targeted_reservation_name: str = None
135
137
  self._dependentOn: List[Uuid] = []
136
138
 
137
139
  self._auto_update = True
@@ -521,6 +523,7 @@ class Task(object):
521
523
  self._hardware_constraints = [HardwareConstraint.from_json(hw_constraint_dict) for hw_constraint_dict in json_task.get("hardwareConstraints", [])]
522
524
  self._default_resources_cache_ttl_sec = json_task.get("defaultResourcesCacheTTLSec", None)
523
525
  self._targeted_reserved_machine_key = json_task.get("targetedReservedMachineKey", None)
526
+ self._targeted_reservation_name = json_task.get("targetedReservationName", None)
524
527
  if 'privileges' in json_task:
525
528
  self._privileges = Privileges.from_json(json_task.get("privileges"))
526
529
  if 'retrySettings' in json_task:
@@ -661,7 +664,7 @@ class Task(object):
661
664
 
662
665
  return self.state
663
666
 
664
- def snapshot(self, interval: int) -> None:
667
+ def snapshot(self, interval: int, whitelist: Optional[str] = None, blacklist: Optional[str] = None, bucket: BucketType = None, bucket_prefix: Optional[str] = None) -> None:
665
668
  """Start snapshooting results.
666
669
  If called, this task's results will be periodically
667
670
  updated, instead of only being available at the end.
@@ -670,6 +673,10 @@ class Task(object):
670
673
  the task is submitted.
671
674
 
672
675
  :param int interval: the interval in seconds at which to take snapshots
676
+ :param str whitelist: whitelist filter for the snapshot.
677
+ :param str blacklist: blacklist filter for the snapshot.
678
+ :param ~qarnot.bucket.Bucket bucket: bucket to upload the snapshot.
679
+ :param str bucket_prefix: prefix added to the files uploaded for the snapshot.
673
680
 
674
681
  :raises ~qarnot.exceptions.QarnotGenericException: API general error, see message for details
675
682
  :raises ~qarnot.exceptions.UnauthorizedException: invalid credentials
@@ -683,8 +690,10 @@ class Task(object):
683
690
  if self._uuid is None:
684
691
  self._snapshots = interval
685
692
  return
693
+
694
+ snapshot_config = PeriodicSnapshotConfiguration(interval, whitelist, blacklist, bucket, bucket_prefix)
686
695
  resp = self._connection._post(get_url('task snapshot', uuid=self._uuid),
687
- json={"interval": interval})
696
+ json=snapshot_config.to_json())
688
697
 
689
698
  if resp.status_code == 400:
690
699
  raise ValueError(interval)
@@ -697,8 +706,13 @@ class Task(object):
697
706
 
698
707
  self._snapshots = True
699
708
 
700
- def instant(self) -> None:
701
- """Make a snapshot of the current task.
709
+ def instant(self, whitelist: Optional[str] = None, blacklist: Optional[str] = None, bucket: BucketType = None, bucket_prefix: Optional[str] = None) -> str:
710
+ """Make an instant snapshot of the current task.
711
+
712
+ :param str whitelist: Whitelist filter for the snapshot.
713
+ :param str blacklist: Blacklist filter for the snapshot.
714
+ :param ~qarnot.bucket.Bucket bucket: bucket where to upload the snapshot.
715
+ :param str bucket_prefix: Prefix added to the files uploaded for the snapshot.
702
716
 
703
717
  :raises ~qarnot.exceptions.QarnotGenericException: API general error, see message for details
704
718
  :raises ~qarnot.exceptions.UnauthorizedException: invalid credentials
@@ -708,10 +722,13 @@ class Task(object):
708
722
  .. note:: To get the temporary results, call :meth:`download_results`.
709
723
  """
710
724
  if self._uuid is None:
711
- return
725
+ return None
726
+
727
+ bucket_name = (bucket.uuid if bucket is not None else None)
728
+ snapshot_config = SnapshotConfiguration(whitelist, blacklist, bucket_name, bucket_prefix)
712
729
 
713
730
  resp = self._connection._post(get_url('task instant', uuid=self._uuid),
714
- json=None)
731
+ json=snapshot_config.to_json())
715
732
 
716
733
  if resp.status_code == 404:
717
734
  raise MissingTaskException(_util.get_error_message_from_http_response(resp))
@@ -720,6 +737,80 @@ class Task(object):
720
737
  raise_on_error(resp)
721
738
 
722
739
  self.update(True)
740
+ return resp.json().get("id")
741
+
742
+ def snapshot_status(self, snapshot_id: str) -> Snapshot:
743
+ """Get the status of a specific instant snapshot.
744
+
745
+ :param str snapshot_id: id of the instant snapshot we want the status.
746
+
747
+ :raises ~qarnot.exceptions.QarnotGenericException: API general error, see message for details
748
+ :raises ~qarnot.exceptions.UnauthorizedException: invalid credentials
749
+ :raises ~qarnot.exceptions.UnauthorizedException: invalid operation on non running task
750
+ :raises ~qarnot.exceptions.MissingTaskException: task does not exist
751
+
752
+ .. note:: To get the temporary results, call :meth:`download_results`.
753
+ """
754
+ if self._uuid is None:
755
+ return None
756
+
757
+ resp = self._connection._get(get_url('task instant status', uuid=self._uuid, snapshotId=snapshot_id),
758
+ json=None)
759
+
760
+ if resp.status_code == 404:
761
+ raise MissingTaskException(_util.get_error_message_from_http_response(resp))
762
+ elif resp.status_code == 403:
763
+ raise UnauthorizedException(_util.get_error_message_from_http_response(resp))
764
+ raise_on_error(resp)
765
+
766
+ return Snapshot.from_json(resp.json())
767
+
768
+ def wait_snapshot(self, snapshot_id: str, timeout: float = None, follow_status: bool = False) -> bool:
769
+ """Wait for the task snapshot to complete.
770
+
771
+ :param float timeout: maximum time (in seconds) to wait before returning
772
+ (None => no timeout)
773
+
774
+ :rtype: :class:`bool`
775
+ :returns: Is the snapshot finished
776
+
777
+ :raises ~qarnot.exceptions.QarnotGenericException: API general error, see message for details
778
+ :raises ~qarnot.exceptions.UnauthorizedException: invalid credentials
779
+ :raises ~qarnot.exceptions.MissingTaskException: task does not represent a valid
780
+ one
781
+ """
782
+
783
+ start = time.time()
784
+ if self._uuid is None:
785
+ self.update(True)
786
+ return False
787
+
788
+ if snapshot_id is None or snapshot_id == "":
789
+ return False
790
+
791
+ nap = min(10, timeout) if timeout is not None else 10
792
+
793
+ last_status = None
794
+ snapshot_status = self.snapshot_status(snapshot_id=snapshot_id)
795
+ if snapshot_status is None:
796
+ return False
797
+
798
+ while snapshot_status is None or not snapshot_status._status.is_completed:
799
+ time.sleep(nap)
800
+ snapshot_status = self.snapshot_status(snapshot_id)
801
+ if follow_status:
802
+ if snapshot_status._status.status != last_status:
803
+ last_status = snapshot_status._status.status
804
+ self._connection.logger.info("** Snapshot %s Status| %s" % (snapshot_id, last_status))
805
+
806
+ if timeout is not None:
807
+ elapsed = time.time() - start
808
+ if timeout <= elapsed:
809
+ self.update()
810
+ return False
811
+ else:
812
+ nap = min(10, timeout - elapsed)
813
+ return True
723
814
 
724
815
  @property
725
816
  def state(self):
@@ -1641,6 +1732,9 @@ class Task(object):
1641
1732
 
1642
1733
  :getter: The reserved machine key when using the "reserved" scheduling type
1643
1734
 
1735
+ .. deprecated:: v2.19.0
1736
+ Use `self.targeted_reservation_name` instead.
1737
+
1644
1738
  :raises AttributeError: trying to set this after the task is submitted
1645
1739
  """
1646
1740
  return self._targeted_reserved_machine_key
@@ -1654,6 +1748,25 @@ class Task(object):
1654
1748
 
1655
1749
  self._targeted_reserved_machine_key = value
1656
1750
 
1751
+ @property
1752
+ def targeted_reservation_name(self) -> str:
1753
+ """:type: :class:`str`
1754
+
1755
+ :getter: The name of the reservation that describes the targeted machines when using the "reserved" scheduling type
1756
+
1757
+ :raises AttributeError: trying to set this after the task is submitted
1758
+ """
1759
+ return self._targeted_reservation_name
1760
+
1761
+ @targeted_reservation_name.setter
1762
+ def targeted_reservation_name(self, value: str):
1763
+ """Setted for targeted_reservation_name
1764
+ """
1765
+ if self.uuid is not None:
1766
+ raise AttributeError("can't set attribute on a launched task")
1767
+
1768
+ self._targeted_reservation_name = value
1769
+
1657
1770
  @property
1658
1771
  def auto_delete(self):
1659
1772
  """Autodelete this Task if it is finished and your max number of task is reach
@@ -1892,6 +2005,9 @@ class Task(object):
1892
2005
  if self._targeted_reserved_machine_key is not None:
1893
2006
  json_task["targetedReservedMachineKey"] = self._targeted_reserved_machine_key
1894
2007
 
2008
+ if self._targeted_reservation_name is not None:
2009
+ json_task["targetedReservationName"] = self._targeted_reservation_name
2010
+
1895
2011
  if self._forced_network_rules is not None:
1896
2012
  json_task['forcedNetworkRules'] = [x.to_json() for x in self._forced_network_rules]
1897
2013
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qarnot
3
- Version: 2.18.0
3
+ Version: 2.20.1
4
4
  Summary: Qarnot Computing SDK
5
5
  Home-page: https://computing.qarnot.com
6
6
  Author: Qarnot computing
@@ -1,31 +1,33 @@
1
- qarnot/__init__.py,sha256=BCIFvWIe1EbzdZAMeKnyK9HYIqNHlSsbaaDMIL7VghA,5626
1
+ qarnot/__init__.py,sha256=9YYIJJ1Y-PjWXnOBq7z04IocK_OskerJxc-2mOBI5qY,5728
2
2
  qarnot/_filter.py,sha256=J--0lOY2rverPEE3zrvuipYkOd9T_4HrW4eVNJqheac,10109
3
3
  qarnot/_retry.py,sha256=4QdtI5Y_Jshja8zDxhx_g17dzABCIUGXQsjcPl65u7g,890
4
4
  qarnot/_util.py,sha256=fmAq55CmfDTTB7yaOLspayC3Mvre7SCzp0YwWsnwpDA,5673
5
- qarnot/_version.py,sha256=7tz02m0CSFBqrG9O7MBXuCGhUSa4292fCRUFrZqcSI4,499
5
+ qarnot/_version.py,sha256=tYo4IXXQVfmjbR_GHcGEdgj4ocZabWCvPK6yI4dAjKM,499
6
6
  qarnot/advanced_bucket.py,sha256=dfhHOz0foJfEQoaCdGiS_-28w4Y6rq36ZloPgoEQiLE,7927
7
7
  qarnot/bucket.py,sha256=aNuJqfYUm2YCQ0m8SBl384xspYkc9SJy6_G-FEKs8nk,24449
8
8
  qarnot/carbon_facts.py,sha256=E_tBMsa2byEMgKAEilIFRsCE6_T978Zl7-ZXk4--PPI,10292
9
- qarnot/computing_quotas.py,sha256=qK-aJFKKi8TGarQ_QX2rM4rjDsg3iMV__hgxikIgNy0,15418
9
+ qarnot/computing_quotas.py,sha256=gXKAZ1QqnXGzR17yTYNmjDF1TBEXALtCHzEe51wk4to,15882
10
10
  qarnot/connection.py,sha256=x71AuXeO33iyxbdoWEQecjsyI_8VSvX7PXztzsNpzLE,46022
11
11
  qarnot/error.py,sha256=WCkkILJzOi06Q5QRBfacU41D0MQeFCPsQc9Ub1Y6SXw,734
12
12
  qarnot/exceptions.py,sha256=yt_iwCw_9pFdoKeOTxsr05kqW5Gu-th3gSfos5zI26g,2729
13
13
  qarnot/forced_constant.py,sha256=-i4b_JO10YiWuJ7Q0bmWE_TEwtf8qSeLlkMTAbH55EM,1309
14
- qarnot/forced_network_rule.py,sha256=g83LOCRy3c6e5WayQMlk-eYUXQFZICROLh6FDQ8G73k,4515
14
+ qarnot/forced_network_rule.py,sha256=gAalmj5p3MDXEG_jRfszhUTpOSKDMH_BFiXDuSnnyOc,5306
15
15
  qarnot/hardware_constraint.py,sha256=YZi4FgaJQ84mxVDT4u2WxqJ-i_bikwssidTRHRc3JHA,13694
16
16
  qarnot/helper.py,sha256=HWy0ollEMoGvCXbAY_FfgkhJjpHRRmkNtavvrJba2PY,1564
17
17
  qarnot/job.py,sha256=bag9NbugWCSf18J2c6nFKmWFFusXP58dfYYxd8CMsy8,19508
18
+ qarnot/multi_slots_settings.py,sha256=RsSC-3YGu0wBsQlOgvJ8oMKFvApN6XEGdC5ozgQyXXs,1542
18
19
  qarnot/paginate.py,sha256=DaUYDPAS0M8hf0hph8GuMSzTASymSUOpp-WqbfevX-s,1387
19
- qarnot/pool.py,sha256=dUSOOkuFhNyR3Yr07svLKNBlzITddyoJR1wOtmKgpxA,57033
20
+ qarnot/pool.py,sha256=IaRYjCamrLHm9BWpjDmbxRhNIjb8f7m88wDIi6gGRa8,59165
20
21
  qarnot/privileges.py,sha256=6j3n8q_RNdZ8bBPD9misHEpS0CbcQByqzxlOu8CS-rU,1896
21
22
  qarnot/retry_settings.py,sha256=Illobh-1U8hPdzkffJu5TFKM_NdzCNYS2j1teBljX94,2512
22
23
  qarnot/scheduling_type.py,sha256=j90APc1ji7xe2Z9OH9l81tjO12k0Wf9bSaG-_UCyeNI,2223
23
24
  qarnot/secrets.py,sha256=v6-1UNhCnnW71eJYMeqhcUYLHnsnONGi4Qa3aoiG9qc,12354
25
+ qarnot/snapshot.py,sha256=S8YZDSlzngmBHjmditkZQdrHWiO0nozXbP8y_zMsrV4,9137
24
26
  qarnot/status.py,sha256=dCVsh9_ewIASZcreATbUd2qJ-cUMk0cRkyVLdrOot3Q,12348
25
27
  qarnot/storage.py,sha256=jAist_J_6yzmRrkF5jqYNG3mhEP_y7KqCdNv4dJcHuM,6929
26
- qarnot/task.py,sha256=Opg7QwZP2dZjPA4tCWkobqayoEHrmHCDc_-L_nSFQCo,76133
27
- qarnot-2.18.0.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
28
- qarnot-2.18.0.dist-info/METADATA,sha256=AnWFGHWQ2zUU8IruRuLng4hMokzfhsyn7bDE6aMbCnQ,2543
29
- qarnot-2.18.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
30
- qarnot-2.18.0.dist-info/top_level.txt,sha256=acRyoLZNyf_kuGTwHQgfZv2MfdTcZstyNjBhOxFtHzU,7
31
- qarnot-2.18.0.dist-info/RECORD,,
28
+ qarnot/task.py,sha256=LQYslV90T4rpo76sPUqG2IhpIMu19QJtI5Jp7jCvJCI,81443
29
+ qarnot-2.20.1.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
30
+ qarnot-2.20.1.dist-info/METADATA,sha256=YjtHFPdEP7SHgv5zsdAMFNQmc4vvB-aU8RRUco-8xNk,2543
31
+ qarnot-2.20.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
32
+ qarnot-2.20.1.dist-info/top_level.txt,sha256=acRyoLZNyf_kuGTwHQgfZv2MfdTcZstyNjBhOxFtHzU,7
33
+ qarnot-2.20.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5