qarnot 2.19.0__tar.gz → 2.20.1__tar.gz

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 (90) hide show
  1. {qarnot-2.19.0/qarnot.egg-info → qarnot-2.20.1}/PKG-INFO +1 -1
  2. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/__init__.py +1 -0
  3. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/_version.py +3 -3
  4. qarnot-2.20.1/qarnot/snapshot.py +244 -0
  5. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/task.py +95 -6
  6. {qarnot-2.19.0 → qarnot-2.20.1/qarnot.egg-info}/PKG-INFO +1 -1
  7. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot.egg-info/SOURCES.txt +2 -0
  8. qarnot-2.20.1/requirements.txt +6 -0
  9. qarnot-2.20.1/test/test_snapshot_status.py +74 -0
  10. qarnot-2.19.0/requirements.txt +0 -6
  11. {qarnot-2.19.0 → qarnot-2.20.1}/LICENSE +0 -0
  12. {qarnot-2.19.0 → qarnot-2.20.1}/MANIFEST.in +0 -0
  13. {qarnot-2.19.0 → qarnot-2.20.1}/README.rst +0 -0
  14. {qarnot-2.19.0 → qarnot-2.20.1}/doc/Makefile +0 -0
  15. {qarnot-2.19.0 → qarnot-2.20.1}/doc/make.bat +0 -0
  16. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/_static/qarnot.png +0 -0
  17. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/carbon_facts.rst +0 -0
  18. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/computeindex.rst +0 -0
  19. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/computing_quotas.rst +0 -0
  20. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/forced_network_rule.rst +0 -0
  21. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/hardware_constraint.rst +0 -0
  22. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/job.rst +0 -0
  23. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/paginate.rst +0 -0
  24. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/pool.rst +0 -0
  25. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/privileges.rst +0 -0
  26. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/retry_settings.rst +0 -0
  27. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/scheduling_type.rst +0 -0
  28. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/secrets.rst +0 -0
  29. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/status.rst +0 -0
  30. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/compute/task.rst +0 -0
  31. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/connection.rst +0 -0
  32. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/exceptions.rst +0 -0
  33. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/storage/advanced_bucket.rst +0 -0
  34. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/storage/bucket.rst +0 -0
  35. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/storage/storage.rst +0 -0
  36. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/api/storage/storageindex.rst +0 -0
  37. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/basic.rst +0 -0
  38. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/conf.py +0 -0
  39. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/index.rst +0 -0
  40. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/installation.rst +0 -0
  41. {qarnot-2.19.0 → qarnot-2.20.1}/doc/source/qarnot.rst +0 -0
  42. {qarnot-2.19.0 → qarnot-2.20.1}/pyproject.toml +0 -0
  43. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/_filter.py +0 -0
  44. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/_retry.py +0 -0
  45. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/_util.py +0 -0
  46. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/advanced_bucket.py +0 -0
  47. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/bucket.py +0 -0
  48. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/carbon_facts.py +0 -0
  49. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/computing_quotas.py +0 -0
  50. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/connection.py +0 -0
  51. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/error.py +0 -0
  52. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/exceptions.py +0 -0
  53. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/forced_constant.py +0 -0
  54. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/forced_network_rule.py +0 -0
  55. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/hardware_constraint.py +0 -0
  56. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/helper.py +0 -0
  57. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/job.py +0 -0
  58. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/multi_slots_settings.py +0 -0
  59. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/paginate.py +0 -0
  60. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/pool.py +0 -0
  61. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/privileges.py +0 -0
  62. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/retry_settings.py +0 -0
  63. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/scheduling_type.py +0 -0
  64. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/secrets.py +0 -0
  65. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/status.py +0 -0
  66. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot/storage.py +0 -0
  67. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot.egg-info/dependency_links.txt +0 -0
  68. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot.egg-info/requires.txt +0 -0
  69. {qarnot-2.19.0 → qarnot-2.20.1}/qarnot.egg-info/top_level.txt +0 -0
  70. {qarnot-2.19.0 → qarnot-2.20.1}/requirements-doc.txt +0 -0
  71. {qarnot-2.19.0 → qarnot-2.20.1}/requirements-lint.txt +0 -0
  72. {qarnot-2.19.0 → qarnot-2.20.1}/requirements-optional.txt +0 -0
  73. {qarnot-2.19.0 → qarnot-2.20.1}/requirements-test.txt +0 -0
  74. {qarnot-2.19.0 → qarnot-2.20.1}/setup.cfg +0 -0
  75. {qarnot-2.19.0 → qarnot-2.20.1}/setup.py +0 -0
  76. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_advanced_bucket.py +0 -0
  77. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_bucket.py +0 -0
  78. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_carbon_facts.py +0 -0
  79. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_connection.py +0 -0
  80. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_hardware_constraints.py +0 -0
  81. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_import.py +0 -0
  82. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_job.py +0 -0
  83. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_paginate.py +0 -0
  84. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_pool.py +0 -0
  85. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_retry.py +0 -0
  86. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_secrets.py +0 -0
  87. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_status.py +0 -0
  88. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_task.py +0 -0
  89. {qarnot-2.19.0 → qarnot-2.20.1}/test/test_util.py +0 -0
  90. {qarnot-2.19.0 → qarnot-2.20.1}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qarnot
3
- Version: 2.19.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
@@ -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
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-09-04T17:06:17+0200",
11
+ "date": "2026-02-06T11:26:35+0100",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "2436d80006a87be548b2235b12f0a6f041ef66c1",
15
- "version": "v2.19.0"
14
+ "full-revisionid": "a21d5058a77a68da123d95dd64550400fafb2bd2",
15
+ "version": "v2.20.1"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -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)
@@ -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
@@ -663,7 +664,7 @@ class Task(object):
663
664
 
664
665
  return self.state
665
666
 
666
- 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:
667
668
  """Start snapshooting results.
668
669
  If called, this task's results will be periodically
669
670
  updated, instead of only being available at the end.
@@ -672,6 +673,10 @@ class Task(object):
672
673
  the task is submitted.
673
674
 
674
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.
675
680
 
676
681
  :raises ~qarnot.exceptions.QarnotGenericException: API general error, see message for details
677
682
  :raises ~qarnot.exceptions.UnauthorizedException: invalid credentials
@@ -685,8 +690,10 @@ class Task(object):
685
690
  if self._uuid is None:
686
691
  self._snapshots = interval
687
692
  return
693
+
694
+ snapshot_config = PeriodicSnapshotConfiguration(interval, whitelist, blacklist, bucket, bucket_prefix)
688
695
  resp = self._connection._post(get_url('task snapshot', uuid=self._uuid),
689
- json={"interval": interval})
696
+ json=snapshot_config.to_json())
690
697
 
691
698
  if resp.status_code == 400:
692
699
  raise ValueError(interval)
@@ -699,8 +706,13 @@ class Task(object):
699
706
 
700
707
  self._snapshots = True
701
708
 
702
- def instant(self) -> None:
703
- """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.
704
716
 
705
717
  :raises ~qarnot.exceptions.QarnotGenericException: API general error, see message for details
706
718
  :raises ~qarnot.exceptions.UnauthorizedException: invalid credentials
@@ -710,10 +722,13 @@ class Task(object):
710
722
  .. note:: To get the temporary results, call :meth:`download_results`.
711
723
  """
712
724
  if self._uuid is None:
713
- 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)
714
729
 
715
730
  resp = self._connection._post(get_url('task instant', uuid=self._uuid),
716
- json=None)
731
+ json=snapshot_config.to_json())
717
732
 
718
733
  if resp.status_code == 404:
719
734
  raise MissingTaskException(_util.get_error_message_from_http_response(resp))
@@ -722,6 +737,80 @@ class Task(object):
722
737
  raise_on_error(resp)
723
738
 
724
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
725
814
 
726
815
  @property
727
816
  def state(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qarnot
3
- Version: 2.19.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
@@ -62,6 +62,7 @@ qarnot/privileges.py
62
62
  qarnot/retry_settings.py
63
63
  qarnot/scheduling_type.py
64
64
  qarnot/secrets.py
65
+ qarnot/snapshot.py
65
66
  qarnot/status.py
66
67
  qarnot/storage.py
67
68
  qarnot/task.py
@@ -81,6 +82,7 @@ test/test_paginate.py
81
82
  test/test_pool.py
82
83
  test/test_retry.py
83
84
  test/test_secrets.py
85
+ test/test_snapshot_status.py
84
86
  test/test_status.py
85
87
  test/test_task.py
86
88
  test/test_util.py
@@ -0,0 +1,6 @@
1
+ requests==2.32.5
2
+ boto3==1.42.34
3
+ wheel==0.46.3
4
+ deprecation==2.1.0
5
+ simplejson==3.20.2
6
+ setuptools==80.10.2
@@ -0,0 +1,74 @@
1
+ import pytest
2
+
3
+ from qarnot.snapshot import Snapshot, TriggeredStatus, InProgressStatus, SuccessStatus, FailedStatus
4
+
5
+ from .mock_snapshot_status import default_json_status
6
+
7
+ class TestStatusProperties:
8
+ @pytest.mark.parametrize("property_name, expected_value", [
9
+ ("_id", default_json_status["id"]),
10
+ ("_task_uuid", default_json_status["taskUuid"]),
11
+ ("_trigger_date", default_json_status["triggerDate"]),
12
+ ("_last_update_date", default_json_status["lastUpdateDate"]),
13
+ ("_size_to_upload", default_json_status["sizeToUpload"]),
14
+ ("_transferred_size", default_json_status["transferredSize"]),
15
+ ])
16
+ def test_create_snapshot_status_from_json_hydrate_property_values(self, property_name, expected_value):
17
+ snapshot_from_json = Snapshot.from_json(default_json_status)
18
+ assert getattr(snapshot_from_json, property_name) == expected_value
19
+
20
+ @pytest.mark.parametrize("property_names, expected_value", [
21
+ (["_snapshot_config", "_whitelist"], default_json_status["snapshotConfig"]["whitelist"]),
22
+ (["_snapshot_config", "_blacklist"], default_json_status["snapshotConfig"]["blacklist"]),
23
+ (["_snapshot_config", "_bucket_name"], default_json_status["snapshotConfig"]["bucket"]),
24
+ (["_snapshot_config", "_bucket_prefix"], default_json_status["snapshotConfig"]["bucketPrefix"]),
25
+ ])
26
+ def test_create_snapshot_status_from_json_hydrate_configuration_subproperty_values(self, property_names, expected_value):
27
+ snapshot_from_json = Snapshot.from_json(default_json_status)
28
+ value = getattr(snapshot_from_json, property_names[0])
29
+ for property_name in property_names[1:]:
30
+ if type(property_name) is int:
31
+ value = value[property_name]
32
+ else:
33
+ value = getattr(value, property_name)
34
+ assert value == expected_value
35
+
36
+ @pytest.mark.parametrize("json_value, expected_status_type", [
37
+ ("triggered", TriggeredStatus),
38
+ ("Triggered", TriggeredStatus),
39
+ ("inProgress", InProgressStatus),
40
+ ("InProgress", InProgressStatus),
41
+ ("success", SuccessStatus),
42
+ ("failure", FailedStatus)])
43
+ def test_snapshot_status_correctly_deserialized(self, json_value, expected_status_type: type):
44
+ json = { "status": json_value }
45
+ snapshot_from_json = Snapshot.from_json(json)
46
+ assert snapshot_from_json._status is not None
47
+ assert type(snapshot_from_json._status) == expected_status_type
48
+
49
+ def test_snapshot_status_with_null_values_does_not_throw_at_deserialization(self):
50
+ json = {
51
+ "id": None,
52
+ "taskUuid": None,
53
+ "triggerDate": None,
54
+ "lastUpdateDate": None,
55
+ "snapshotConfig": None,
56
+ "status": None,
57
+ "sizeToUpload": None,
58
+ "transferredSize": None,
59
+ }
60
+ snapshot_from_json = Snapshot.from_json(json)
61
+ assert snapshot_from_json._status is not None
62
+ assert type(snapshot_from_json._status) == TriggeredStatus
63
+
64
+ json = {
65
+ "snapshotConfig":
66
+ {
67
+ "whitelist": None,
68
+ "blacklist": None,
69
+ "bucket": None,
70
+ "bucketPrefix": None
71
+ }
72
+ }
73
+ snapshot_from_json = Snapshot.from_json(json)
74
+ assert snapshot_from_json._snapshot_config is not None
@@ -1,6 +0,0 @@
1
- requests==2.32.5
2
- boto3==1.40.21
3
- wheel==0.45.1
4
- deprecation==2.1.0
5
- simplejson==3.20.1
6
- setuptools==80.9.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes