qarnot 2.14.5__py3-none-any.whl → 2.16.0__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/_util.py CHANGED
@@ -20,11 +20,6 @@ from http.client import responses
20
20
 
21
21
  import re
22
22
 
23
- _IS_PY2 = bytes is str
24
-
25
- if not _IS_PY2:
26
- unicode = str
27
-
28
23
 
29
24
  def copy_docs(docs_source):
30
25
  def decorator(obj):
@@ -43,7 +38,7 @@ def decode(string, encoding='utf-8'):
43
38
 
44
39
  def is_string(x):
45
40
  """Check if x is a string (bytes or unicode)."""
46
- return isinstance(x, (str, unicode))
41
+ return isinstance(x, (str, bytes))
47
42
 
48
43
 
49
44
  def parse_to_timespan_string(value):
qarnot/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-03-29T14:48:51+0100",
11
+ "date": "2025-02-07T16:27:29+0100",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "ff28a98ad04060443b7c6b1f487e492c61e31be1",
15
- "version": "v2.14.5"
14
+ "full-revisionid": "23daecdc71207c207e56afae3e55190f0734bd0d",
15
+ "version": "v2.16.0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
qarnot/bucket.py CHANGED
@@ -242,6 +242,64 @@ class Bucket(Storage): # pylint: disable=W0223
242
242
  bucket = self._connection.s3resource.Bucket(self._uuid)
243
243
  return bucket.objects.filter(Prefix=directory)
244
244
 
245
+ def sync_remote_to_local(self, local_directoy, remote_directory=None):
246
+ """Synchronize a remote directory to a local directory.
247
+
248
+ :param str local_directoy: The local directory to use for synchronization
249
+ :param str remote_directory: path of the directory on remote node (defaults to whole bucket)
250
+
251
+ .. warning::
252
+ Distant changes are reflected on the local filesystem, a file not present on the
253
+ bucket but in the local directory might be deleted from the local filesystem.
254
+
255
+ .. note::
256
+ The following parameters are used to determine whether
257
+ synchronization is required :
258
+
259
+ * name
260
+ * size
261
+ * sha1sum
262
+ """
263
+
264
+ def get_key_for_local(remote_key: str) -> str:
265
+ if remote_directory:
266
+ return removeprefix(remote_key, remote_directory).lstrip('/')
267
+ return remote_key.lstrip('/')
268
+
269
+ def removeprefix(target_str: str, prefix: str) -> str:
270
+ if target_str.startswith(prefix):
271
+ return target_str[len(prefix):]
272
+ else:
273
+ return target_str[:]
274
+
275
+ try:
276
+ if remote_directory:
277
+ entries = self.directory(remote_directory)
278
+ else:
279
+ entries = self.list_files()
280
+
281
+ list_files_only = [x for x in entries if not x.key.endswith('/')]
282
+ list_directories_only = [x for x in entries if x.key.endswith('/')]
283
+ except self._connection.s3resource.meta.client.exceptions.NoSuchBucket as err:
284
+ raise MissingBucketException("Cannot synchronize. Bucket {} not found.".format(err.response['Error']['BucketName'])) from err
285
+
286
+ for directory in list_directories_only:
287
+ if not os.path.isdir(os.path.join(local_directoy, get_key_for_local(directory.key))):
288
+ os.makedirs(os.path.join(local_directoy, get_key_for_local(directory.key)), exist_ok=True)
289
+
290
+ for _, dupes in groupby(sorted(list_files_only, key=attrgetter('e_tag')), attrgetter('e_tag')):
291
+ file_info = next(dupes)
292
+ first_file = os.path.join(local_directoy, get_key_for_local(file_info.key))
293
+ self.get_file(file_info.get()['Body'], local=first_file) # avoids making a useless HEAD request
294
+
295
+ for dupe in dupes:
296
+ local = os.path.join(local_directoy, get_key_for_local(dupe.key))
297
+ directory = os.path.dirname(local)
298
+ if not os.path.exists(directory):
299
+ os.makedirs(directory)
300
+ if (os.path.abspath(os.path.realpath(local)) is not os.path.abspath(os.path.realpath(first_file))):
301
+ shutil.copy(first_file, local)
302
+
245
303
  def sync_directory(self, directory, verbose=False, remote=None):
246
304
  """Synchronize a local directory with the remote buckets.
247
305
 
@@ -433,28 +491,7 @@ class Bucket(Storage): # pylint: disable=W0223
433
491
 
434
492
  @_util.copy_docs(Storage.get_all_files)
435
493
  def get_all_files(self, output_dir, progress=None):
436
- try:
437
- list_files_only = [x for x in self.list_files() if not x.key.endswith('/')]
438
- list_directories_only = [x for x in self.list_files() if x.key.endswith('/')]
439
- except self._connection.s3resource.meta.client.exceptions.NoSuchBucket as err:
440
- raise MissingBucketException("Cannot get files. Bucket {} not found.".format(err.response['Error']['BucketName'])) from err
441
-
442
- for directory in list_directories_only:
443
- if not os.path.isdir(os.path.join(output_dir, directory.key.lstrip('/'))):
444
- os.makedirs(os.path.join(output_dir, directory.key.lstrip('/')))
445
-
446
- for _, dupes in groupby(sorted(list_files_only, key=attrgetter('e_tag')), attrgetter('e_tag')):
447
- file_info = next(dupes)
448
- first_file = os.path.join(output_dir, file_info.key.lstrip('/'))
449
- self.get_file(file_info.get()['Body'], local=first_file) # avoids making a useless HEAD request
450
-
451
- for dupe in dupes:
452
- local = os.path.join(output_dir, dupe.key.lstrip('/'))
453
- directory = os.path.dirname(local)
454
- if not os.path.exists(directory):
455
- os.makedirs(directory)
456
- if (os.path.abspath(os.path.realpath(local)) is not os.path.abspath(os.path.realpath(first_file))):
457
- shutil.copy(first_file, local)
494
+ self.sync_remote_to_local(output_dir, None)
458
495
 
459
496
  @_util.copy_docs(Storage.get_file)
460
497
  def get_file(self, remote, local=None, progress=None):
qarnot/connection.py CHANGED
@@ -196,7 +196,7 @@ class Connection(object):
196
196
 
197
197
  user = self.user_info
198
198
  session = boto3.session.Session()
199
- conf = botocore.config.Config(user_agent=self._version)
199
+ conf = botocore.config.Config(user_agent=self._version, request_checksum_calculation="when_required")
200
200
 
201
201
  should_verify_or_certificate_path = True
202
202
  if storage_unsafe:
@@ -0,0 +1,44 @@
1
+ from typing import Optional, Union, Dict
2
+ from enum import Enum
3
+
4
+
5
+ class ForcedConstantAccess(Enum):
6
+ ReadWrite = "ReadWrite"
7
+ ReadOnly = "ReadOnly"
8
+
9
+
10
+ class ForcedConstant(object):
11
+ """Forced Constant Information
12
+
13
+ .. note:: For internal usage only
14
+ """
15
+
16
+ def __init__(self, forced_value: str, force_export_in_environment: Optional[bool] = None, access: Optional[ForcedConstantAccess] = None):
17
+ self.forced_value = forced_value
18
+ """:type: :class:`str`
19
+
20
+ Forced value for the constant."""
21
+
22
+ self.force_export_in_environment = force_export_in_environment
23
+ """:type: :class:`bool`
24
+
25
+ Whether the constant should be forced in the execution environment or not."""
26
+
27
+ self.access = access
28
+ """:type: :class:`~qarnot.forced_constant.ForcedConstantAccess`
29
+
30
+ The access level of the constant: ReadOnly or ReadWrite."""
31
+
32
+ def to_json(self, name: str):
33
+ result: Dict[str, Union[str, bool]] = {
34
+ "constantName": name,
35
+ "forcedValue": self.forced_value,
36
+ }
37
+
38
+ if self.force_export_in_environment is not None:
39
+ result["forceExportInEnvironment"] = self.force_export_in_environment
40
+
41
+ if self.access is not None:
42
+ result["access"] = self.access.value
43
+
44
+ return result
@@ -81,7 +81,7 @@ class ForcedNetworkRule(object):
81
81
  """Create the forced network rule from json.
82
82
 
83
83
  :param dict json: Dictionary representing the forced network rule
84
- :returns: The created :class:`~qarnot.retry_settings.ForcedNetworkRule`
84
+ :returns: The created :class:`~qarnot.forced_network_rule.ForcedNetworkRule`
85
85
  """
86
86
 
87
87
  inbound: bool = bool(json["inbound"])
qarnot/pool.py CHANGED
@@ -23,6 +23,7 @@ from qarnot.secrets import SecretsAccessRights
23
23
 
24
24
  from . import raise_on_error, get_url, _util
25
25
  from .bucket import Bucket
26
+ from .forced_constant import ForcedConstant
26
27
  from .status import Status
27
28
  from .hardware_constraint import HardwareConstraint
28
29
  from .scheduling_type import SchedulingType
@@ -77,6 +78,7 @@ class Pool(object):
77
78
  with :meth:`qarnot.connection.Connection.retrieve_profile`.
78
79
  """
79
80
  self._constraints: Dict[str, str] = {}
81
+ self._forced_constants: Dict[str, ForcedConstant] = {}
80
82
  self._labels: Dict[str, str] = {}
81
83
  self._auto_update = True
82
84
  self._last_auto_update_state = self._auto_update
@@ -252,6 +254,10 @@ class Pool(object):
252
254
  {'key': key, 'value': value}
253
255
  for key, value in self._constraints.items()
254
256
  ]
257
+ forced_const_list = [
258
+ value.to_json(key)
259
+ for key, value in self._forced_constants.items()
260
+ ]
255
261
 
256
262
  elastic_dict = {
257
263
  "isElastic": self._is_elastic,
@@ -267,6 +273,7 @@ class Pool(object):
267
273
  'name': self._name,
268
274
  'profile': self._profile,
269
275
  'constants': const_list,
276
+ 'forcedConstants': forced_const_list,
270
277
  'constraints': constr_list,
271
278
  'instanceCount': self._instancecount,
272
279
  'tags': self._tags,
@@ -1099,6 +1106,31 @@ class Pool(object):
1099
1106
 
1100
1107
  self._secrets_access_rights = value
1101
1108
 
1109
+ @property
1110
+ def forced_constants(self):
1111
+ """:type: dictionary{:class:`str` : :class:`~qarnot.forced_constant.ForcedConstant`}
1112
+ :getter: Returns this pool's forced constants dictionary.
1113
+ :setter: set the pool's forced constants dictionary.
1114
+
1115
+ Update the forced constants if needed.
1116
+ Forced constants are reserved for internal use.
1117
+ """
1118
+ self._update_if_summary()
1119
+ if self._auto_update:
1120
+ self.update()
1121
+
1122
+ return self._forced_constants
1123
+
1124
+ @forced_constants.setter
1125
+ def forced_constants(self, value: Dict[str, "ForcedConstant"]):
1126
+ """Setter for forced_constants
1127
+ """
1128
+ self._update_if_summary()
1129
+ if self._auto_update:
1130
+ self.update()
1131
+
1132
+ self._forced_constants = value
1133
+
1102
1134
  @property
1103
1135
  def forced_network_rules(self):
1104
1136
  """:type: list{:class:`~qarnot.forced_network_rule.ForcedNetworkRule`}
@@ -1116,7 +1148,7 @@ class Pool(object):
1116
1148
 
1117
1149
  @forced_network_rules.setter
1118
1150
  def forced_network_rules(self, value: List["ForcedNetworkRule"]):
1119
- """Setter for forced_constants
1151
+ """Setter for forced_network_rules
1120
1152
  """
1121
1153
  if self.uuid is not None:
1122
1154
  raise AttributeError("can't set attribute on a launched pool")
qarnot/secrets.py CHANGED
@@ -97,7 +97,7 @@ class SecretsAccessRights(object):
97
97
 
98
98
  :param by_secret: the list of secrets the task will have access to, described using an exact key match
99
99
  :type by_secret: `List[~qarnot.secrets.SecretAccessRightBySecret]`
100
- :param by_prefix:the list of secrets the task will have access to, described using a prefix key match
100
+ :param by_prefix: the list of secrets the task will have access to, described using a prefix key match
101
101
  :type by_prefix: `List[~qarnot.secrets.SecretAccessRightByPrefix]`
102
102
  """
103
103
  self._by_secret: List[SecretAccessRightBySecret] = by_secret or []
qarnot/task.py CHANGED
@@ -19,7 +19,6 @@ from os import makedirs, path
19
19
  import time
20
20
  import warnings
21
21
  import sys
22
- from enum import Enum
23
22
  from typing import Dict, Optional, Union, List, Any, Callable
24
23
 
25
24
  from qarnot.retry_settings import RetrySettings
@@ -29,6 +28,7 @@ from qarnot.secrets import SecretsAccessRights
29
28
  from . import get_url, raise_on_error, _util
30
29
  from .status import Status
31
30
  from .hardware_constraint import HardwareConstraint
31
+ from .forced_constant import ForcedConstant
32
32
  from .scheduling_type import SchedulingType
33
33
  from .privileges import Privileges
34
34
  from .bucket import Bucket
@@ -458,6 +458,9 @@ class Task(object):
458
458
  if 'resultBucket' in json_task and json_task['resultBucket']:
459
459
  self._result_object_id = json_task['resultBucket']
460
460
 
461
+ if 'ResultsCacheTTLSec' in json_task and self._result_object is not None:
462
+ self._result_object._cache_ttl_sec = json_task['ResultsCacheTTLSec']
463
+
461
464
  if 'status' in json_task:
462
465
  self._status = json_task['status']
463
466
  self._creation_date = _util.parse_datetime(json_task['creationDate'])
@@ -1340,7 +1343,7 @@ class Task(object):
1340
1343
 
1341
1344
  @property
1342
1345
  def forced_constants(self):
1343
- """:type: dictionary{:class:`str` : :class:`~qarnot.task.ForcedConstant`}
1346
+ """:type: dictionary{:class:`str` : :class:`~qarnot.forced_constant.ForcedConstant`}
1344
1347
  :getter: Returns this task's forced constants dictionary.
1345
1348
  :setter: set the task's forced constants dictionary.
1346
1349
 
@@ -1380,7 +1383,7 @@ class Task(object):
1380
1383
 
1381
1384
  @forced_network_rules.setter
1382
1385
  def forced_network_rules(self, value: List["ForcedNetworkRule"]):
1383
- """Setter for forced_constants
1386
+ """Setter for forced_network_rules
1384
1387
  """
1385
1388
  if self.uuid is not None:
1386
1389
  raise AttributeError("can't set attribute on a launched task")
@@ -1815,6 +1818,8 @@ class Task(object):
1815
1818
 
1816
1819
  if self._result_object is not None:
1817
1820
  json_task['resultBucket'] = self._result_object.uuid
1821
+ if self._result_object._cache_ttl_sec is not None:
1822
+ json_task['ResultsCacheTTLSec'] = self._result_object._cache_ttl_sec
1818
1823
 
1819
1824
  if self._advanced_range is not None:
1820
1825
  json_task['advancedRanges'] = self._advanced_range
@@ -1975,45 +1980,3 @@ class BulkTaskResponse(object):
1975
1980
  return ', '.join("{0}={1}".format(key, val) for (key, val) in self.__dict__.items())
1976
1981
  else:
1977
1982
  return ', '.join("{0}={1}".format(key, val) for (key, val) in self.__dict__.iteritems()) # pylint: disable=no-member
1978
-
1979
-
1980
- class ForcedConstantAccess(Enum):
1981
- ReadWrite = "ReadWrite"
1982
- ReadOnly = "ReadOnly"
1983
-
1984
-
1985
- class ForcedConstant(object):
1986
- """Forced Constant Information
1987
-
1988
- .. note:: For internal usage only
1989
- """
1990
-
1991
- def __init__(self, forced_value: str, force_export_in_environment: Optional[bool] = None, access: Optional[ForcedConstantAccess] = None):
1992
- self.forced_value = forced_value
1993
- """:type: :class:`str`
1994
-
1995
- Forced value for the constant."""
1996
-
1997
- self.force_export_in_environment = force_export_in_environment
1998
- """:type: :class:`bool`
1999
-
2000
- Whether the constant should be forced in the execution environment or not."""
2001
-
2002
- self.access = access
2003
- """:type: :class:`~qarnot.task.ForcedConstantAccess`
2004
-
2005
- The access level of the constant: ReadOnly or ReadWrite."""
2006
-
2007
- def to_json(self, name: str):
2008
- result: Dict[str, Union[str, bool]] = {
2009
- "constantName": name,
2010
- "forcedValue": self.forced_value,
2011
- }
2012
-
2013
- if self.force_export_in_environment is not None:
2014
- result["forceExportInEnvironment"] = self.force_export_in_environment
2015
-
2016
- if self.access is not None:
2017
- result["access"] = self.access.value
2018
-
2019
- return result
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: qarnot
3
- Version: 2.14.5
3
+ Version: 2.16.0
4
4
  Summary: Qarnot Computing SDK
5
5
  Home-page: https://computing.qarnot.com
6
6
  Author: Qarnot computing
@@ -17,10 +17,19 @@ Classifier: License :: OSI Approved :: Apache Software License
17
17
  Requires-Python: >=3.6
18
18
  License-File: LICENSE
19
19
  Requires-Dist: requests
20
- Requires-Dist: boto3
20
+ Requires-Dist: boto3>=1.36
21
21
  Requires-Dist: wheel
22
22
  Requires-Dist: deprecation
23
23
  Requires-Dist: simplejson
24
+ Dynamic: author
25
+ Dynamic: author-email
26
+ Dynamic: classifier
27
+ Dynamic: description
28
+ Dynamic: home-page
29
+ Dynamic: license
30
+ Dynamic: requires-dist
31
+ Dynamic: requires-python
32
+ Dynamic: summary
24
33
 
25
34
  Qarnot computing Python SDK
26
35
  ===========================
@@ -1,28 +1,29 @@
1
1
  qarnot/__init__.py,sha256=pLDjFGU-0WROKi7nb73a7M3tafVYpgM4Tj9iOwIx-tE,5092
2
2
  qarnot/_filter.py,sha256=J--0lOY2rverPEE3zrvuipYkOd9T_4HrW4eVNJqheac,10109
3
3
  qarnot/_retry.py,sha256=4QdtI5Y_Jshja8zDxhx_g17dzABCIUGXQsjcPl65u7g,890
4
- qarnot/_util.py,sha256=e8Luej_ZNqjH3autsqgGz9xGw_vDl-m6qxzGNOi3kUQ,5734
5
- qarnot/_version.py,sha256=nt_FGe1ljkcgIy8ysOp0AqmO22RW1L8z7Tn3aHCViys,499
4
+ qarnot/_util.py,sha256=fmAq55CmfDTTB7yaOLspayC3Mvre7SCzp0YwWsnwpDA,5673
5
+ qarnot/_version.py,sha256=LyIkPD7PhRJmr2rXGj5uL8sQ6xl7teOzkl-Mv5wd4FE,499
6
6
  qarnot/advanced_bucket.py,sha256=_uKSxRULmpYj4IBbSzssfUu5lWj8ZICUjxs-MvI4lvs,7923
7
- qarnot/bucket.py,sha256=R4PHl5FQeQsGYmUqj2i6fqUXJH2wz_XmdEKgEM0-rjc,22958
8
- qarnot/connection.py,sha256=-CPPZnGAYNCTyTIsi_wBlX6dnAbIh6Llvt3WCQyQG1s,44848
7
+ qarnot/bucket.py,sha256=kXA16XwL2NxGqqN0z6SeywN18MucDGX8UFgISMKJj5k,24361
8
+ qarnot/connection.py,sha256=GM8Hi8bYiGgKsccx8GPBXejJ8CsjC10AjSIIYOkT4cE,44894
9
9
  qarnot/error.py,sha256=LB_fXUiFZMkF5dgSi7QKpw-arELV043G5qu4HN-yA1k,722
10
10
  qarnot/exceptions.py,sha256=yt_iwCw_9pFdoKeOTxsr05kqW5Gu-th3gSfos5zI26g,2729
11
- qarnot/forced_network_rule.py,sha256=SvKg41BEvmIT1f0WXWIvzaWJDAw0lWXQH1gVxEKFz3M,4466
11
+ qarnot/forced_constant.py,sha256=-i4b_JO10YiWuJ7Q0bmWE_TEwtf8qSeLlkMTAbH55EM,1309
12
+ qarnot/forced_network_rule.py,sha256=diGTZEjWOSR2QbtC00tjf_7zXw0c_D6OIV3ujVq4zvg,4471
12
13
  qarnot/hardware_constraint.py,sha256=qJukigRpv8kT9EAg5am0j3ypHtuLRsbcIQ5bUyrMS3c,13658
13
14
  qarnot/helper.py,sha256=SAMcXt2GEcPuhV_ZzTOYs1Ucy4OdYGHVur472k36uoI,1498
14
15
  qarnot/job.py,sha256=d5rH7LUY-QgQfZabsRV0jHVC6KhPezFy-mkMZw9XhKQ,19444
15
16
  qarnot/paginate.py,sha256=DaUYDPAS0M8hf0hph8GuMSzTASymSUOpp-WqbfevX-s,1387
16
- qarnot/pool.py,sha256=C6ps9O4NRBCNQvjlS9Ve-wOlxYo5bUaNGdMk-eraEnc,54167
17
+ qarnot/pool.py,sha256=NK341qpU37kGPEwZiyffGBUVSTYNBwNFhtehV4E2Byc,55251
17
18
  qarnot/privileges.py,sha256=yUnIbAyb9G410nifFdUXEGvm-FHR_PQYGh-OGq_iiwk,1892
18
19
  qarnot/retry_settings.py,sha256=NSJB54LLdjdD3w7t5G0gZVQD-SDl_2DUUwJphjcfgfg,2504
19
20
  qarnot/scheduling_type.py,sha256=YZgmeAgWo_tQorwM1aqHUHq2xEZytKWB3hTViYn-hwQ,2175
20
- qarnot/secrets.py,sha256=LJo4B0K5X2CPAIdapfU_HIv-eGoFPmMU3HcmmhA7PZ4,12329
21
+ qarnot/secrets.py,sha256=apGb6dD2kyNVMGRf402wo7kPQKF7EtRZabl8kxHxqRA,12330
21
22
  qarnot/status.py,sha256=zzB0bFeiWMv-XiHuDH7Zj12sWhp1TYFVmQtNNt3Gzmg,12084
22
23
  qarnot/storage.py,sha256=jAist_J_6yzmRrkF5jqYNG3mhEP_y7KqCdNv4dJcHuM,6929
23
- qarnot/task.py,sha256=1-NjnZ5bMc4IJk9RbKgVCqtlg53vqSjkcXMyW8msl2s,75101
24
- qarnot-2.14.5.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
25
- qarnot-2.14.5.dist-info/METADATA,sha256=Sy40-M4D2AXs3Pjgfj1MqkxEBfPqm8pgpZLazOCPxLU,2335
26
- qarnot-2.14.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
27
- qarnot-2.14.5.dist-info/top_level.txt,sha256=acRyoLZNyf_kuGTwHQgfZv2MfdTcZstyNjBhOxFtHzU,7
28
- qarnot-2.14.5.dist-info/RECORD,,
24
+ qarnot/task.py,sha256=FZd8sZeT7DZa9_-ldHTy2qfAzx1Q6Hd9l3dHXnVWLA8,74215
25
+ qarnot-2.16.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
26
+ qarnot-2.16.0.dist-info/METADATA,sha256=TSdbTmPqnwqJSC02Wrt4JoolHDb1fKQvc8HwYzuSVUM,2521
27
+ qarnot-2.16.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
28
+ qarnot-2.16.0.dist-info/top_level.txt,sha256=acRyoLZNyf_kuGTwHQgfZv2MfdTcZstyNjBhOxFtHzU,7
29
+ qarnot-2.16.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5