qarnot 2.18.0__tar.gz → 2.19.0__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 (87) hide show
  1. {qarnot-2.18.0/qarnot.egg-info → qarnot-2.19.0}/PKG-INFO +1 -1
  2. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/_version.py +3 -3
  3. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/computing_quotas.py +16 -4
  4. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/forced_network_rule.py +41 -14
  5. qarnot-2.19.0/qarnot/multi_slots_settings.py +48 -0
  6. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/pool.py +52 -0
  7. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/task.py +27 -0
  8. {qarnot-2.18.0 → qarnot-2.19.0/qarnot.egg-info}/PKG-INFO +1 -1
  9. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot.egg-info/SOURCES.txt +1 -0
  10. {qarnot-2.18.0 → qarnot-2.19.0}/requirements.txt +3 -3
  11. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_pool.py +40 -1
  12. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_task.py +13 -1
  13. {qarnot-2.18.0 → qarnot-2.19.0}/LICENSE +0 -0
  14. {qarnot-2.18.0 → qarnot-2.19.0}/MANIFEST.in +0 -0
  15. {qarnot-2.18.0 → qarnot-2.19.0}/README.rst +0 -0
  16. {qarnot-2.18.0 → qarnot-2.19.0}/doc/Makefile +0 -0
  17. {qarnot-2.18.0 → qarnot-2.19.0}/doc/make.bat +0 -0
  18. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/_static/qarnot.png +0 -0
  19. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/carbon_facts.rst +0 -0
  20. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/computeindex.rst +0 -0
  21. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/computing_quotas.rst +0 -0
  22. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/forced_network_rule.rst +0 -0
  23. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/hardware_constraint.rst +0 -0
  24. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/job.rst +0 -0
  25. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/paginate.rst +0 -0
  26. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/pool.rst +0 -0
  27. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/privileges.rst +0 -0
  28. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/retry_settings.rst +0 -0
  29. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/scheduling_type.rst +0 -0
  30. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/secrets.rst +0 -0
  31. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/status.rst +0 -0
  32. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/compute/task.rst +0 -0
  33. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/connection.rst +0 -0
  34. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/exceptions.rst +0 -0
  35. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/storage/advanced_bucket.rst +0 -0
  36. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/storage/bucket.rst +0 -0
  37. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/storage/storage.rst +0 -0
  38. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/api/storage/storageindex.rst +0 -0
  39. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/basic.rst +0 -0
  40. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/conf.py +0 -0
  41. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/index.rst +0 -0
  42. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/installation.rst +0 -0
  43. {qarnot-2.18.0 → qarnot-2.19.0}/doc/source/qarnot.rst +0 -0
  44. {qarnot-2.18.0 → qarnot-2.19.0}/pyproject.toml +0 -0
  45. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/__init__.py +0 -0
  46. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/_filter.py +0 -0
  47. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/_retry.py +0 -0
  48. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/_util.py +0 -0
  49. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/advanced_bucket.py +0 -0
  50. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/bucket.py +0 -0
  51. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/carbon_facts.py +0 -0
  52. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/connection.py +0 -0
  53. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/error.py +0 -0
  54. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/exceptions.py +0 -0
  55. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/forced_constant.py +0 -0
  56. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/hardware_constraint.py +0 -0
  57. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/helper.py +0 -0
  58. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/job.py +0 -0
  59. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/paginate.py +0 -0
  60. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/privileges.py +0 -0
  61. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/retry_settings.py +0 -0
  62. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/scheduling_type.py +0 -0
  63. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/secrets.py +0 -0
  64. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/status.py +0 -0
  65. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot/storage.py +0 -0
  66. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot.egg-info/dependency_links.txt +0 -0
  67. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot.egg-info/requires.txt +0 -0
  68. {qarnot-2.18.0 → qarnot-2.19.0}/qarnot.egg-info/top_level.txt +0 -0
  69. {qarnot-2.18.0 → qarnot-2.19.0}/requirements-doc.txt +0 -0
  70. {qarnot-2.18.0 → qarnot-2.19.0}/requirements-lint.txt +0 -0
  71. {qarnot-2.18.0 → qarnot-2.19.0}/requirements-optional.txt +0 -0
  72. {qarnot-2.18.0 → qarnot-2.19.0}/requirements-test.txt +0 -0
  73. {qarnot-2.18.0 → qarnot-2.19.0}/setup.cfg +0 -0
  74. {qarnot-2.18.0 → qarnot-2.19.0}/setup.py +0 -0
  75. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_advanced_bucket.py +0 -0
  76. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_bucket.py +0 -0
  77. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_carbon_facts.py +0 -0
  78. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_connection.py +0 -0
  79. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_hardware_constraints.py +0 -0
  80. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_import.py +0 -0
  81. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_job.py +0 -0
  82. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_paginate.py +0 -0
  83. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_retry.py +0 -0
  84. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_secrets.py +0 -0
  85. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_status.py +0 -0
  86. {qarnot-2.18.0 → qarnot-2.19.0}/test/test_util.py +0 -0
  87. {qarnot-2.18.0 → qarnot-2.19.0}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qarnot
3
- Version: 2.18.0
3
+ Version: 2.19.0
4
4
  Summary: Qarnot Computing SDK
5
5
  Home-page: https://computing.qarnot.com
6
6
  Author: Qarnot computing
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-05-22T14:41:22+0200",
11
+ "date": "2025-09-04T17:06:17+0200",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "9361585630d3dbcc8fa50ecbb9d20d592234a808",
15
- "version": "v2.18.0"
14
+ "full-revisionid": "2436d80006a87be548b2235b12f0a6f041ef66c1",
15
+ "version": "v2.19.0"
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)
@@ -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} -'\
@@ -132,6 +132,7 @@ class Task(object):
132
132
  self._secrets_access_rights: SecretsAccessRights = SecretsAccessRights()
133
133
  self._scheduling_type = scheduling_type
134
134
  self._targeted_reserved_machine_key: str = None
135
+ self._targeted_reservation_name: str = None
135
136
  self._dependentOn: List[Uuid] = []
136
137
 
137
138
  self._auto_update = True
@@ -521,6 +522,7 @@ class Task(object):
521
522
  self._hardware_constraints = [HardwareConstraint.from_json(hw_constraint_dict) for hw_constraint_dict in json_task.get("hardwareConstraints", [])]
522
523
  self._default_resources_cache_ttl_sec = json_task.get("defaultResourcesCacheTTLSec", None)
523
524
  self._targeted_reserved_machine_key = json_task.get("targetedReservedMachineKey", None)
525
+ self._targeted_reservation_name = json_task.get("targetedReservationName", None)
524
526
  if 'privileges' in json_task:
525
527
  self._privileges = Privileges.from_json(json_task.get("privileges"))
526
528
  if 'retrySettings' in json_task:
@@ -1641,6 +1643,9 @@ class Task(object):
1641
1643
 
1642
1644
  :getter: The reserved machine key when using the "reserved" scheduling type
1643
1645
 
1646
+ .. deprecated:: v2.19.0
1647
+ Use `self.targeted_reservation_name` instead.
1648
+
1644
1649
  :raises AttributeError: trying to set this after the task is submitted
1645
1650
  """
1646
1651
  return self._targeted_reserved_machine_key
@@ -1654,6 +1659,25 @@ class Task(object):
1654
1659
 
1655
1660
  self._targeted_reserved_machine_key = value
1656
1661
 
1662
+ @property
1663
+ def targeted_reservation_name(self) -> str:
1664
+ """:type: :class:`str`
1665
+
1666
+ :getter: The name of the reservation that describes the targeted machines when using the "reserved" scheduling type
1667
+
1668
+ :raises AttributeError: trying to set this after the task is submitted
1669
+ """
1670
+ return self._targeted_reservation_name
1671
+
1672
+ @targeted_reservation_name.setter
1673
+ def targeted_reservation_name(self, value: str):
1674
+ """Setted for targeted_reservation_name
1675
+ """
1676
+ if self.uuid is not None:
1677
+ raise AttributeError("can't set attribute on a launched task")
1678
+
1679
+ self._targeted_reservation_name = value
1680
+
1657
1681
  @property
1658
1682
  def auto_delete(self):
1659
1683
  """Autodelete this Task if it is finished and your max number of task is reach
@@ -1892,6 +1916,9 @@ class Task(object):
1892
1916
  if self._targeted_reserved_machine_key is not None:
1893
1917
  json_task["targetedReservedMachineKey"] = self._targeted_reserved_machine_key
1894
1918
 
1919
+ if self._targeted_reservation_name is not None:
1920
+ json_task["targetedReservationName"] = self._targeted_reservation_name
1921
+
1895
1922
  if self._forced_network_rules is not None:
1896
1923
  json_task['forcedNetworkRules'] = [x.to_json() for x in self._forced_network_rules]
1897
1924
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qarnot
3
- Version: 2.18.0
3
+ Version: 2.19.0
4
4
  Summary: Qarnot Computing SDK
5
5
  Home-page: https://computing.qarnot.com
6
6
  Author: Qarnot computing
@@ -55,6 +55,7 @@ qarnot/forced_network_rule.py
55
55
  qarnot/hardware_constraint.py
56
56
  qarnot/helper.py
57
57
  qarnot/job.py
58
+ qarnot/multi_slots_settings.py
58
59
  qarnot/paginate.py
59
60
  qarnot/pool.py
60
61
  qarnot/privileges.py
@@ -1,6 +1,6 @@
1
- requests==2.32.3
2
- boto3==1.38.18
1
+ requests==2.32.5
2
+ boto3==1.40.21
3
3
  wheel==0.45.1
4
4
  deprecation==2.1.0
5
5
  simplejson==3.20.1
6
- setuptools==80.7.1
6
+ setuptools==80.9.0
@@ -3,6 +3,7 @@ import uuid
3
3
  import pytest
4
4
  import qarnot
5
5
  from qarnot.forced_network_rule import ForcedNetworkRule
6
+ from qarnot.multi_slots_settings import MultiSlotsSettings
6
7
  from qarnot.pool import Pool
7
8
  from qarnot.privileges import Privileges
8
9
  from qarnot.bucket import Bucket
@@ -263,6 +264,19 @@ class TestPoolProperties:
263
264
  assert {"prefix": "another/prefix"} in json_pool['secretsAccessRights']["byPrefix"]
264
265
 
265
266
 
267
+ def test_pool_multi_slots_settings_deserialization(self):
268
+ pool = Pool(self.conn, "pool-multi-slots-deserialization", "profile")
269
+ pool._update(default_json_pool)
270
+ assert MultiSlotsSettings(4) == pool.multi_slots_settings
271
+
272
+ def test_pool_multi_slots_settings_deserialization_none(self):
273
+ pool = Pool(self.conn, "pool-multi-slots-deserialization-none", "profile")
274
+
275
+ pool_json = copy.deepcopy(default_json_pool)
276
+ del pool_json["multiSlotsSettings"]
277
+ pool._update(pool_json)
278
+ assert None == pool.multi_slots_settings
279
+
266
280
  @pytest.mark.parametrize("json", [
267
281
  {
268
282
  "bySecret": [
@@ -388,6 +402,7 @@ class TestPoolProperties:
388
402
  def test_pool_reserved_scheduling_serialization(self):
389
403
  pool = Pool(self.conn, "pool-with-reserved-scheduling", "profile", scheduling_type=ReservedScheduling())
390
404
  pool.targeted_reserved_machine_key = "reservedMachine"
405
+ pool.targeted_reservation_name = "my-reservation"
391
406
  assert pool.scheduling_type is not None
392
407
  print(pool.scheduling_type)
393
408
  assert isinstance(pool.scheduling_type, ReservedScheduling)
@@ -412,10 +427,24 @@ class TestPoolProperties:
412
427
  assert isinstance(pool_from_json.scheduling_type, ReservedScheduling)
413
428
  assert pool_from_json.scheduling_type.schedulingType == ReservedScheduling.schedulingType
414
429
  assert pool.targeted_reserved_machine_key == "reservedMachine"
430
+ assert pool.targeted_reservation_name == "my-reservation"
431
+
432
+ def test_pool_multi_slots_settings_serialization(self):
433
+ pool = Pool(self.conn, "pool-test-multi-slots-settings-serialization", "profile")
434
+ pool.multi_slots_settings = MultiSlotsSettings(4)
435
+
436
+ json_pool = pool._to_json()
437
+ assert json_pool["multiSlotsSettings"]["slotsPerNode"] == 4
438
+
439
+ def test_pool_no_multi_slots_settings_serialization(self):
440
+ pool = Pool(self.conn, "pool-test-no-multi-slots-settings-serialization", "profile")
441
+
442
+ json_pool = pool._to_json()
443
+ assert "multiSlotsSettings" not in json_pool
415
444
 
416
445
  def test_pool_forced_network_rules_serialization(self):
417
446
  pool = Pool(self.conn, "pool-with-forced-network-rules", "profile")
418
- inbound_rule = ForcedNetworkRule(True, "tcp", "1234", "bound-to-be-alive", priority="1000", description="Inbound test")
447
+ inbound_rule = ForcedNetworkRule(True, "tcp", "1234", "bound-to-be-alive", priority="1000", description="Inbound test", name="my-living-network", application_type="https")
419
448
  outbound_rule = ForcedNetworkRule(False, "tcp", public_port="666", public_host="bound-to-the-devil", priority="1000", description="Outbound test")
420
449
  rules = [
421
450
  inbound_rule,
@@ -435,11 +464,17 @@ class TestPoolProperties:
435
464
  assert json_inbound_rule["to"] == inbound_rule.to
436
465
  assert json_inbound_rule["priority"] == inbound_rule.priority
437
466
  assert json_inbound_rule["description"] == inbound_rule.description
467
+ assert json_inbound_rule["name"] == inbound_rule.name
468
+ assert json_inbound_rule["applicationType"] == inbound_rule.application_type
438
469
  json_outbound_rule = json_pool['forcedNetworkRules'][1]
439
470
  assert json_outbound_rule["inbound"] == outbound_rule.inbound
440
471
  assert json_outbound_rule["proto"] == outbound_rule.proto
441
472
  assert json_outbound_rule["priority"] == outbound_rule.priority
442
473
  assert json_outbound_rule["description"] == outbound_rule.description
474
+ assert json_outbound_rule["publicHost"] == outbound_rule.public_host
475
+ assert json_outbound_rule["publicPort"] == outbound_rule.public_port
476
+ assert json_outbound_rule.get("name") is None
477
+ assert json_outbound_rule.get("applicationType") is None
443
478
 
444
479
  # fields that need to be non null for the deserialization to not fail
445
480
  json_pool['creationDate'] = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
@@ -459,8 +494,12 @@ class TestPoolProperties:
459
494
  assert inbound_from_json.to == inbound_rule.to
460
495
  assert inbound_from_json.priority == inbound_rule.priority
461
496
  assert inbound_from_json.description == inbound_rule.description
497
+ assert inbound_from_json.name == inbound_rule.name
498
+ assert inbound_from_json.application_type == inbound_rule.application_type
462
499
  outbound_from_json = pool_from_json.forced_network_rules[1]
463
500
  assert outbound_from_json.inbound == outbound_rule.inbound
464
501
  assert outbound_from_json.proto == outbound_rule.proto
465
502
  assert outbound_from_json.priority == outbound_rule.priority
466
503
  assert outbound_from_json.description == outbound_rule.description
504
+ assert outbound_from_json.public_host == outbound_rule.public_host
505
+ assert outbound_from_json.public_port == outbound_rule.public_port
@@ -368,6 +368,7 @@ class TestTaskProperties:
368
368
  def test_task_reserved_scheduling_serialization(self, mock_conn):
369
369
  task = Task(mock_conn, "task-with-reserved-scheduling", scheduling_type=ReservedScheduling())
370
370
  task.targeted_reserved_machine_key = "reservedMachine"
371
+ task.targeted_reservation_name = "my-reservation"
371
372
  assert task.scheduling_type is not None
372
373
  print(task.scheduling_type)
373
374
  assert isinstance(task.scheduling_type, ReservedScheduling)
@@ -392,6 +393,7 @@ class TestTaskProperties:
392
393
  assert isinstance(task_from_json.scheduling_type, ReservedScheduling)
393
394
  assert task_from_json.scheduling_type.schedulingType == ReservedScheduling.schedulingType
394
395
  assert task.targeted_reserved_machine_key == "reservedMachine"
396
+ assert task.targeted_reservation_name == "my-reservation"
395
397
 
396
398
  def test_task_secrets_access_rights_are_serialized_correctly(self, mock_conn):
397
399
  task = Task(mock_conn, "task-secrets-access-rights-serialization")
@@ -464,7 +466,7 @@ class TestTaskProperties:
464
466
 
465
467
  def test_task_forced_network_rules_serialization(self, mock_conn):
466
468
  task = Task(mock_conn, "task-with-forced-network-rules")
467
- inbound_rule = ForcedNetworkRule(True, "tcp", "1234", "bound-to-be-alive", priority="1000", description="Inbound test")
469
+ inbound_rule = ForcedNetworkRule(True, "tcp", "1234", "bound-to-be-alive", priority="1000", description="Inbound test", name="my-living-network", application_type="https")
468
470
  outbound_rule = ForcedNetworkRule(False, "tcp", public_port="666", public_host="bound-to-the-devil", priority="1000", description="Outbound test")
469
471
  rules = [
470
472
  inbound_rule,
@@ -484,11 +486,17 @@ class TestTaskProperties:
484
486
  assert json_inbound_rule["to"] == inbound_rule.to
485
487
  assert json_inbound_rule["priority"] == inbound_rule.priority
486
488
  assert json_inbound_rule["description"] == inbound_rule.description
489
+ assert json_inbound_rule["name"] == inbound_rule.name
490
+ assert json_inbound_rule["applicationType"] == inbound_rule.application_type
487
491
  json_outbound_rule = json_task['forcedNetworkRules'][1]
488
492
  assert json_outbound_rule["inbound"] == outbound_rule.inbound
489
493
  assert json_outbound_rule["proto"] == outbound_rule.proto
490
494
  assert json_outbound_rule["priority"] == outbound_rule.priority
491
495
  assert json_outbound_rule["description"] == outbound_rule.description
496
+ assert json_outbound_rule["publicHost"] == outbound_rule.public_host
497
+ assert json_outbound_rule["publicPort"] == outbound_rule.public_port
498
+ assert json_outbound_rule.get("name") is None
499
+ assert json_outbound_rule.get("applicationType") is None
492
500
 
493
501
  # fields that need to be non null for the deserialization to not fail
494
502
  json_task['creationDate'] = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
@@ -508,11 +516,15 @@ class TestTaskProperties:
508
516
  assert inbound_from_json.to == inbound_rule.to
509
517
  assert inbound_from_json.priority == inbound_rule.priority
510
518
  assert inbound_from_json.description == inbound_rule.description
519
+ assert inbound_from_json.name == inbound_rule.name
520
+ assert inbound_from_json.application_type == inbound_rule.application_type
511
521
  outbound_from_json = task_from_json.forced_network_rules[1]
512
522
  assert outbound_from_json.inbound == outbound_rule.inbound
513
523
  assert outbound_from_json.proto == outbound_rule.proto
514
524
  assert outbound_from_json.priority == outbound_rule.priority
515
525
  assert outbound_from_json.description == outbound_rule.description
526
+ assert outbound_from_json.public_host == outbound_rule.public_host
527
+ assert outbound_from_json.public_port == outbound_rule.public_port
516
528
 
517
529
  # WARNING: this test last at least 80s because task.wait() wait for 10s between each update calls and the task go through 8 different states
518
530
  # To make the test faster some states can be removed (the 7 first states are all the states that correspond to a non complete task and keep
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