qarnot 2.14.5__tar.gz → 2.15.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.
- {qarnot-2.14.5/qarnot.egg-info → qarnot-2.15.0}/PKG-INFO +1 -1
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/_util.py +1 -6
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/_version.py +3 -3
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/bucket.py +59 -22
- {qarnot-2.14.5 → qarnot-2.15.0/qarnot.egg-info}/PKG-INFO +1 -1
- {qarnot-2.14.5 → qarnot-2.15.0}/requirements.txt +2 -2
- {qarnot-2.14.5 → qarnot-2.15.0}/LICENSE +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/MANIFEST.in +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/README.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/Makefile +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/make.bat +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/_static/qarnot.png +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/compute/computeindex.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/compute/hardware_constraint.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/compute/job.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/compute/paginate.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/compute/pool.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/compute/privileges.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/compute/retry_settings.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/compute/scheduling_type.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/compute/status.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/compute/task.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/connection.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/exceptions.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/storage/advanced_bucket.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/storage/bucket.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/storage/storage.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/api/storage/storageindex.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/basic.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/conf.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/index.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/installation.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/doc/source/qarnot.rst +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/pyproject.toml +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/__init__.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/_filter.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/_retry.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/advanced_bucket.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/connection.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/error.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/exceptions.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/forced_network_rule.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/hardware_constraint.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/helper.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/job.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/paginate.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/pool.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/privileges.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/retry_settings.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/scheduling_type.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/secrets.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/status.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/storage.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot/task.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot.egg-info/SOURCES.txt +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot.egg-info/dependency_links.txt +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot.egg-info/requires.txt +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/qarnot.egg-info/top_level.txt +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/requirements-doc.txt +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/requirements-lint.txt +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/requirements-optional.txt +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/requirements-test.txt +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/setup.cfg +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/setup.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_advanced_bucket.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_bucket.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_connection.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_hardware_constraints.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_import.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_job.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_paginate.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_pool.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_retry.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_secrets.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_status.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_task.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/test/test_util.py +0 -0
- {qarnot-2.14.5 → qarnot-2.15.0}/versioneer.py +0 -0
|
@@ -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,
|
|
41
|
+
return isinstance(x, (str, bytes))
|
|
47
42
|
|
|
48
43
|
|
|
49
44
|
def parse_to_timespan_string(value):
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2024-
|
|
11
|
+
"date": "2024-08-27T11:29:32+0200",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "v2.
|
|
14
|
+
"full-revisionid": "8fe176158583e7c2db929a8a25031075991e9e36",
|
|
15
|
+
"version": "v2.15.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -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
|
-
|
|
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):
|
|
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
|
|
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
|