NGPIris 5.3.1__tar.gz → 5.4.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 (54) hide show
  1. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/cli/__init__.py +4 -35
  2. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/hcp/hcp.py +67 -36
  3. {ngpiris-5.3.1 → ngpiris-5.4.1/NGPIris.egg-info}/PKG-INFO +3 -2
  4. {ngpiris-5.3.1/NGPIris.egg-info → ngpiris-5.4.1}/PKG-INFO +3 -2
  5. {ngpiris-5.3.1 → ngpiris-5.4.1}/pyproject.toml +1 -1
  6. {ngpiris-5.3.1 → ngpiris-5.4.1}/LICENSE +0 -0
  7. {ngpiris-5.3.1 → ngpiris-5.4.1}/MANIFEST.in +0 -0
  8. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/__init__.py +0 -0
  9. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/hci/__init__.py +0 -0
  10. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/hci/exceptions.py +0 -0
  11. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/hci/hci.py +0 -0
  12. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/hci/helpers.py +0 -0
  13. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/hcp/__init__.py +0 -0
  14. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/hcp/exceptions.py +0 -0
  15. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/hcp/helpers.py +0 -0
  16. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/hcp/statistics.py +0 -0
  17. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/parse_credentials/__init__.py +0 -0
  18. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/parse_credentials/parse_credentials.py +0 -0
  19. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/utils/__init__.py +0 -0
  20. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris/utils/utils.py +0 -0
  21. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris.egg-info/SOURCES.txt +0 -0
  22. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris.egg-info/dependency_links.txt +0 -0
  23. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris.egg-info/entry_points.txt +0 -0
  24. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris.egg-info/requires.txt +0 -0
  25. {ngpiris-5.3.1 → ngpiris-5.4.1}/NGPIris.egg-info/top_level.txt +0 -0
  26. {ngpiris-5.3.1 → ngpiris-5.4.1}/README.md +0 -0
  27. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/IRIS_5_changelog.md +0 -0
  28. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/Makefile +0 -0
  29. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/NGPIris.cli.rst +0 -0
  30. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/NGPIris.hci.rst +0 -0
  31. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/NGPIris.hcp.rst +0 -0
  32. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/NGPIris.parse_credentials.rst +0 -0
  33. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/NGPIris.rst +0 -0
  34. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/NGPIris.utils.rst +0 -0
  35. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/Tutorial.md +0 -0
  36. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/conf.py +0 -0
  37. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/index.rst +0 -0
  38. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/make.bat +0 -0
  39. {ngpiris-5.3.1 → ngpiris-5.4.1}/docs/modules.rst +0 -0
  40. {ngpiris-5.3.1 → ngpiris-5.4.1}/setup.cfg +0 -0
  41. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/data/80MB_test_file +0 -0
  42. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/data/a folder of data/80MB_test_file_0 +0 -0
  43. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/data/a folder of data/80MB_test_file_1 +0 -0
  44. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/data/complete_creds_0.json +0 -0
  45. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/data/complete_creds_1.json +0 -0
  46. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/data/complete_creds_2.json +0 -0
  47. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/data/incomplete_creds_0.json +0 -0
  48. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/data/incomplete_creds_1.json +0 -0
  49. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/data/incomplete_creds_2.json +0 -0
  50. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/data/incomplete_creds_3.json +0 -0
  51. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/data/incomplete_creds_4.json +0 -0
  52. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/test_hci.py +0 -0
  53. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/test_hcp.py +0 -0
  54. {ngpiris-5.3.1 → ngpiris-5.4.1}/tests/test_parse_credentials.py +0 -0
@@ -4,11 +4,7 @@ from click.core import Context
4
4
  from json import dump
5
5
  from pathlib import Path
6
6
  from boto3 import set_stream_logger
7
- from botocore.paginate import PageIterator, Paginator
8
7
  from typing import Any, Generator
9
- from os import get_terminal_size
10
- from math import floor
11
- from tabulate import tabulate
12
8
  from bitmath import Byte, TiB
13
9
 
14
10
  from NGPIris.hcp import HCPHandler
@@ -26,36 +22,9 @@ def _list_objects_generator(hcph : HCPHandler, path : str, name_only : bool, fil
26
22
  different from `list_objects` in `hcp.py` in order to make the output
27
23
  printable in a terminal
28
24
  """
29
- paginator : Paginator = hcph.s3_client.get_paginator("list_objects_v2")
30
- pages : PageIterator = paginator.paginate(Bucket = hcph.bucket_name, Prefix = path)
31
- (nb_of_cols, _) = get_terminal_size()
32
- max_width = floor(nb_of_cols / 5)
33
- if (not name_only):
34
- yield tabulate(
35
- [],
36
- headers = ["Key", "LastModified", "ETag", "Size", "StorageClass"],
37
- tablefmt = "plain",
38
- stralign = "center"
39
- ) + "\n" + "-"*nb_of_cols + "\n"
40
- if files_only:
41
- filter_string = "Contents[?!ends_with(Key, '/')][]" # filter objects that does not end with "/"
42
- else:
43
- filter_string = "Contents[*][]"
44
- for object in pages.search(filter_string):
45
- if name_only:
46
- yield str(object["Key"]) + "\n"
47
- else:
48
- yield tabulate(
49
- [
50
- [str(object["Key"]),
51
- str(object["LastModified"]),
52
- str(object["ETag"]),
53
- str(object["Size"]),
54
- str(object["StorageClass"])]
55
- ],
56
- maxcolwidths = max_width,
57
- tablefmt = "plain"
58
- ) + "\n" + "-"*nb_of_cols + "\n"
25
+ objects = hcph.list_objects(path, name_only, files_only)
26
+ for obj in objects:
27
+ yield str(obj) + "\n"
59
28
 
60
29
  def object_is_folder(object_path : str, hcph : HCPHandler) -> bool:
61
30
  return (object_path[-1] == "/") and (hcph.get_object(object_path)["ContentLength"] == 0)
@@ -457,7 +426,7 @@ def iris_generate_credentials_file(path : str, name : str):
457
426
  file_path = path + name
458
427
 
459
428
  if not Path(path).is_dir():
460
- Path(path).mkdir()
429
+ Path(path).mkdir(parents=True)
461
430
  else:
462
431
  file_path = name
463
432
 
@@ -1,4 +1,3 @@
1
-
2
1
  from NGPIris.parse_credentials import CredentialsHandler
3
2
  from NGPIris.hcp.helpers import (
4
3
  raise_path_error,
@@ -41,7 +40,6 @@ from rapidfuzz import (
41
40
  from requests import get
42
41
  from urllib3 import disable_warnings
43
42
  from tqdm import tqdm
44
-
45
43
  from bitmath import TiB, Byte
46
44
 
47
45
  from typing import Generator
@@ -256,51 +254,75 @@ class HCPHandler:
256
254
  response = self.get_response("/namespaces")
257
255
  list_of_buckets : list[str] = response["name"]
258
256
  return list_of_buckets
259
-
257
+
260
258
  @check_mounted
261
- def list_objects(self, path_key : str = "", name_only : bool = False, files_only : bool = False) -> Generator:
259
+ def list_objects(
260
+ self,
261
+ path_key : str = "",
262
+ list_all_bucket_objects : bool = False,
263
+ name_only : bool = False,
264
+ files_only : bool = False
265
+ ) -> Generator:
262
266
  """
263
267
  List all objects in the mounted bucket as a generator. If one wishes to
264
268
  get the result as a list, use :py:function:`list` to type cast the generator
265
269
 
266
- :param path_key: Filter string for which keys to list, specifically for finding objects in certain folders.
270
+ :param path_key: Filter string for which keys to list, specifically for finding objects in certain folders. Defaults to \"the root\" of the bucket
267
271
  :type path_key: str, optional
272
+ :param list_all_bucket_objects: If True, the value of `path_key` will be ignored and instead will list all objects in the bucket. Defaults to False
273
+ :type list_all_bucket_objects: bool, optional
268
274
  :param name_only: If True, yield only a the object names. If False, yield the full metadata about each object. Defaults to False.
269
275
  :type name_only: bool, optional
270
- :param files_only: If true, only yield file objects. Defaults to False
276
+ :param files_only: If True, only yield file objects. Defaults to False
271
277
  :type files_only: bool, optional
272
- :yield: A generator of all objects in a bucket
278
+ :yield: A generator of all objects in specified folder in a bucket
273
279
  :rtype: Generator
274
280
  """
275
281
  paginator : Paginator = self.s3_client.get_paginator("list_objects_v2")
276
- pages : PageIterator = paginator.paginate(Bucket = self.bucket_name, Prefix = path_key)
277
-
278
- if files_only:
279
- filter_string = "Contents[?!ends_with(Key, '/')][]"
282
+ if list_all_bucket_objects:
283
+ pages : PageIterator = paginator.paginate(Bucket = self.bucket_name)
280
284
  else:
281
- filter_string = "Contents[*][]"
282
-
283
- split_path_key = len(path_key.split("/")) + 1
285
+ pages : PageIterator = paginator.paginate(Bucket = self.bucket_name, Prefix = path_key, Delimiter = "/")
284
286
 
285
- pages_filtered = pages.search(filter_string)
286
- for object in pages_filtered:
287
- # If there are no objects in the bucket, then `object` will be None,
288
- # which means that we should break out of the for loop
289
- if not object:
287
+ for page in pages:
288
+ page : dict | None
289
+ # Check if `page` is None
290
+ if not page:
290
291
  break
291
292
 
292
- # Split the object key by "/"
293
- split_object = object["Key"].split("/")
294
- # Check if the object is within the specified path_key depth
295
- if len(split_object) <= split_path_key:
296
- # Skip objects that are not at the desired depth
297
- if (len(split_object) == split_path_key) and split_object[-1]:
298
- continue
299
-
300
- if name_only:
301
- yield str(object["Key"])
302
- else:
303
- yield object
293
+ if files_only:
294
+ for file_object in page.get("Contents", []):
295
+ file_object : dict
296
+ if file_object["Key"] != path_key:
297
+ file_object["IsFile"] = True
298
+ if name_only:
299
+ yield file_object["Key"]
300
+ else:
301
+ yield file_object
302
+ else:
303
+ for folder_object in page.get("CommonPrefixes", []):
304
+ folder_object : dict
305
+ folder_object_metadata = self.get_object(folder_object["Prefix"])
306
+
307
+ if name_only:
308
+ yield folder_object["Prefix"]
309
+ else:
310
+ yield {
311
+ "Key" : folder_object["Prefix"],
312
+ "LastModified" : folder_object_metadata["LastModified"],
313
+ "ETag" : folder_object_metadata["ETag"],
314
+ "IsFile" : False,
315
+ }
316
+
317
+ for file_object in page.get("Contents", []):
318
+ file_object : dict
319
+ if file_object["Key"] != path_key:
320
+ file_object["IsFile"] = True
321
+ if name_only:
322
+ yield file_object["Key"]
323
+ else:
324
+ yield file_object
325
+
304
326
 
305
327
  @check_mounted
306
328
  def get_object(self, key : str) -> dict:
@@ -427,10 +449,18 @@ class HCPHandler:
427
449
  raise ObjectDoesNotExist("Could not find object", "\"" + folder_key + "\"", "in bucket", "\"" + str(self.bucket_name) + "\"")
428
450
  if Path(local_folder_path).is_dir():
429
451
  current_download_size_in_bytes = Byte(0) # For tracking download limit
430
- for object in self.list_objects(folder_key): # Build the tree with directories or add files
452
+ (Path(local_folder_path) / Path(folder_key)).mkdir(parents = True) # Create "base folder"
453
+ for object in self.list_objects(folder_key): # Build the tree with directories or add files:
431
454
  p = Path(local_folder_path) / Path(object["Key"])
432
- if object["Key"][-1] == "/": # If the object is a "folder"
433
- p.mkdir()
455
+ if not object["IsFile"]: # If the object is a "folder"
456
+ p.mkdir(parents = True)
457
+ self.download_folder(
458
+ folder_key = str(object["Key"]),
459
+ local_folder_path = local_folder_path,
460
+ use_download_limit = use_download_limit,
461
+ show_progress_bar = show_progress_bar,
462
+ download_limit_in_bytes = download_limit_in_bytes - current_download_size_in_bytes
463
+ )
434
464
  else: # If the object is a file
435
465
  current_download_size_in_bytes += Byte(object["Size"])
436
466
  if current_download_size_in_bytes >= download_limit_in_bytes and use_download_limit:
@@ -573,6 +603,7 @@ class HCPHandler:
573
603
  key += "/"
574
604
 
575
605
  objects : list[str] = list(self.list_objects(key, name_only = True))
606
+ objects.append(key) # Include the object "folder" path to be deleted
576
607
 
577
608
  if not objects:
578
609
  raise RuntimeError("\"" + key + "\"" + " is not a valid path") #TODO: change this error
@@ -640,11 +671,11 @@ class HCPHandler:
640
671
  processor = utils.default_process
641
672
 
642
673
  if not name_only:
643
- full_list = list(self.list_objects())
674
+ full_list = list(self.list_objects(list_all_bucket_objects = True))
644
675
 
645
676
  for item, score, index in process.extract_iter(
646
677
  search_string,
647
- self.list_objects(name_only = True),
678
+ self.list_objects(list_all_bucket_objects = True, name_only = True),
648
679
  scorer = fuzz.partial_ratio,
649
680
  processor = processor
650
681
  ):
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: NGPIris
3
- Version: 5.3.1
3
+ Version: 5.4.1
4
4
  Author-email: Erik Brink <erik.brink@gu.se>, Isak Sylvin <isak.sylvin@gu.se>
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE
@@ -13,6 +13,7 @@ Requires-Dist: tqdm>=4.66.2
13
13
  Requires-Dist: click>=8.1.7
14
14
  Requires-Dist: bitmath==1.3.3.1
15
15
  Requires-Dist: tabulate==0.9.0
16
+ Dynamic: license-file
16
17
 
17
18
  # NGP IRIS 👀
18
19
  NGP IRIS, or just Iris, is a tool for interacting with a Hitachi Content Platform (HCP) using S3 in the `boto3` package. NGP Iris is designed with two use cases in mind:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: NGPIris
3
- Version: 5.3.1
3
+ Version: 5.4.1
4
4
  Author-email: Erik Brink <erik.brink@gu.se>, Isak Sylvin <isak.sylvin@gu.se>
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE
@@ -13,6 +13,7 @@ Requires-Dist: tqdm>=4.66.2
13
13
  Requires-Dist: click>=8.1.7
14
14
  Requires-Dist: bitmath==1.3.3.1
15
15
  Requires-Dist: tabulate==0.9.0
16
+ Dynamic: license-file
16
17
 
17
18
  # NGP IRIS 👀
18
19
  NGP IRIS, or just Iris, is a tool for interacting with a Hitachi Content Platform (HCP) using S3 in the `boto3` package. NGP Iris is designed with two use cases in mind:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "NGPIris"
3
- version = "5.3.1"
3
+ version = "5.4.1"
4
4
  readme = "README.md"
5
5
  dependencies = [
6
6
  "requests >= 2.31.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