NGPIris 5.1.1.dev2__tar.gz → 5.2.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 (131) hide show
  1. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/cli/__init__.py +116 -10
  2. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/hcp/hcp.py +124 -50
  3. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris.egg-info/PKG-INFO +4 -111
  4. ngpiris-5.2.0/NGPIris.egg-info/SOURCES.txt +52 -0
  5. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris.egg-info/requires.txt +1 -0
  6. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/PKG-INFO +4 -111
  7. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/README.md +1 -109
  8. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/Tutorial.md +32 -41
  9. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/pyproject.toml +6 -1
  10. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/test_hci.py +5 -1
  11. ngpiris-5.2.0/tests/test_hcp.py +213 -0
  12. ngpiris-5.1.1.dev2/NGPIris.egg-info/SOURCES.txt +0 -127
  13. ngpiris-5.1.1.dev2/docs/_build/.DS_Store +0 -0
  14. ngpiris-5.1.1.dev2/docs/_build/doctrees/IRIS_5_changelog.doctree +0 -0
  15. ngpiris-5.1.1.dev2/docs/_build/doctrees/NGPIris.cli.doctree +0 -0
  16. ngpiris-5.1.1.dev2/docs/_build/doctrees/NGPIris.doctree +0 -0
  17. ngpiris-5.1.1.dev2/docs/_build/doctrees/NGPIris.hci.doctree +0 -0
  18. ngpiris-5.1.1.dev2/docs/_build/doctrees/NGPIris.hcp.doctree +0 -0
  19. ngpiris-5.1.1.dev2/docs/_build/doctrees/NGPIris.parse_credentials.doctree +0 -0
  20. ngpiris-5.1.1.dev2/docs/_build/doctrees/NGPIris.utils.doctree +0 -0
  21. ngpiris-5.1.1.dev2/docs/_build/doctrees/README.doctree +0 -0
  22. ngpiris-5.1.1.dev2/docs/_build/doctrees/Tutorial.doctree +0 -0
  23. ngpiris-5.1.1.dev2/docs/_build/doctrees/environment.pickle +0 -0
  24. ngpiris-5.1.1.dev2/docs/_build/doctrees/index.doctree +0 -0
  25. ngpiris-5.1.1.dev2/docs/_build/doctrees/modules.doctree +0 -0
  26. ngpiris-5.1.1.dev2/docs/_build/html/.buildinfo +0 -4
  27. ngpiris-5.1.1.dev2/docs/_build/html/IRIS_5_changelog.html +0 -462
  28. ngpiris-5.1.1.dev2/docs/_build/html/NGPIris.cli.html +0 -137
  29. ngpiris-5.1.1.dev2/docs/_build/html/NGPIris.hci.html +0 -280
  30. ngpiris-5.1.1.dev2/docs/_build/html/NGPIris.hcp.html +0 -357
  31. ngpiris-5.1.1.dev2/docs/_build/html/NGPIris.html +0 -236
  32. ngpiris-5.1.1.dev2/docs/_build/html/NGPIris.parse_credentials.html +0 -156
  33. ngpiris-5.1.1.dev2/docs/_build/html/NGPIris.utils.html +0 -143
  34. ngpiris-5.1.1.dev2/docs/_build/html/README.html +0 -681
  35. ngpiris-5.1.1.dev2/docs/_build/html/Tutorial.html +0 -796
  36. ngpiris-5.1.1.dev2/docs/_build/html/_downloads/ee00c4f6f22dc2671c766f728f00006f/credentials_template.json +0 -14
  37. ngpiris-5.1.1.dev2/docs/_build/html/_modules/NGPIris/cli.html +0 -338
  38. ngpiris-5.1.1.dev2/docs/_build/html/_modules/NGPIris/hci/exceptions.html +0 -106
  39. ngpiris-5.1.1.dev2/docs/_build/html/_modules/NGPIris/hci/hci.html +0 -250
  40. ngpiris-5.1.1.dev2/docs/_build/html/_modules/NGPIris/hci/helpers.html +0 -204
  41. ngpiris-5.1.1.dev2/docs/_build/html/_modules/NGPIris/hcp/exceptions.html +0 -117
  42. ngpiris-5.1.1.dev2/docs/_build/html/_modules/NGPIris/hcp/hcp.html +0 -655
  43. ngpiris-5.1.1.dev2/docs/_build/html/_modules/NGPIris/hcp/helpers.html +0 -148
  44. ngpiris-5.1.1.dev2/docs/_build/html/_modules/NGPIris/hcp/statistics.html +0 -137
  45. ngpiris-5.1.1.dev2/docs/_build/html/_modules/NGPIris/parse_credentials/parse_credentials.html +0 -170
  46. ngpiris-5.1.1.dev2/docs/_build/html/_modules/NGPIris/utils/utils.html +0 -115
  47. ngpiris-5.1.1.dev2/docs/_build/html/_modules/index.html +0 -108
  48. ngpiris-5.1.1.dev2/docs/_build/html/_sources/IRIS_5_changelog.md.txt +0 -43
  49. ngpiris-5.1.1.dev2/docs/_build/html/_sources/NGPIris.cli.rst.txt +0 -10
  50. ngpiris-5.1.1.dev2/docs/_build/html/_sources/NGPIris.hci.rst.txt +0 -37
  51. ngpiris-5.1.1.dev2/docs/_build/html/_sources/NGPIris.hcp.rst.txt +0 -45
  52. ngpiris-5.1.1.dev2/docs/_build/html/_sources/NGPIris.parse_credentials.rst.txt +0 -21
  53. ngpiris-5.1.1.dev2/docs/_build/html/_sources/NGPIris.rst.txt +0 -22
  54. ngpiris-5.1.1.dev2/docs/_build/html/_sources/NGPIris.utils.rst.txt +0 -21
  55. ngpiris-5.1.1.dev2/docs/_build/html/_sources/README.md.txt +0 -274
  56. ngpiris-5.1.1.dev2/docs/_build/html/_sources/Tutorial.md.txt +0 -326
  57. ngpiris-5.1.1.dev2/docs/_build/html/_sources/index.rst.txt +0 -21
  58. ngpiris-5.1.1.dev2/docs/_build/html/_sources/modules.rst.txt +0 -7
  59. ngpiris-5.1.1.dev2/docs/_build/html/_static/alabaster.css +0 -708
  60. ngpiris-5.1.1.dev2/docs/_build/html/_static/basic.css +0 -925
  61. ngpiris-5.1.1.dev2/docs/_build/html/_static/custom.css +0 -1
  62. ngpiris-5.1.1.dev2/docs/_build/html/_static/debug.css +0 -69
  63. ngpiris-5.1.1.dev2/docs/_build/html/_static/doctools.js +0 -156
  64. ngpiris-5.1.1.dev2/docs/_build/html/_static/documentation_options.js +0 -13
  65. ngpiris-5.1.1.dev2/docs/_build/html/_static/file.png +0 -0
  66. ngpiris-5.1.1.dev2/docs/_build/html/_static/language_data.js +0 -199
  67. ngpiris-5.1.1.dev2/docs/_build/html/_static/minus.png +0 -0
  68. ngpiris-5.1.1.dev2/docs/_build/html/_static/plus.png +0 -0
  69. ngpiris-5.1.1.dev2/docs/_build/html/_static/pygments.css +0 -83
  70. ngpiris-5.1.1.dev2/docs/_build/html/_static/scripts/furo-extensions.js +0 -0
  71. ngpiris-5.1.1.dev2/docs/_build/html/_static/scripts/furo.js +0 -3
  72. ngpiris-5.1.1.dev2/docs/_build/html/_static/scripts/furo.js.LICENSE.txt +0 -7
  73. ngpiris-5.1.1.dev2/docs/_build/html/_static/scripts/furo.js.map +0 -1
  74. ngpiris-5.1.1.dev2/docs/_build/html/_static/searchtools.js +0 -619
  75. ngpiris-5.1.1.dev2/docs/_build/html/_static/skeleton.css +0 -296
  76. ngpiris-5.1.1.dev2/docs/_build/html/_static/sphinx_highlight.js +0 -154
  77. ngpiris-5.1.1.dev2/docs/_build/html/_static/styles/furo-extensions.css +0 -2
  78. ngpiris-5.1.1.dev2/docs/_build/html/_static/styles/furo-extensions.css.map +0 -1
  79. ngpiris-5.1.1.dev2/docs/_build/html/_static/styles/furo.css +0 -2
  80. ngpiris-5.1.1.dev2/docs/_build/html/_static/styles/furo.css.map +0 -1
  81. ngpiris-5.1.1.dev2/docs/_build/html/genindex.html +0 -476
  82. ngpiris-5.1.1.dev2/docs/_build/html/index.html +0 -159
  83. ngpiris-5.1.1.dev2/docs/_build/html/modules.html +0 -148
  84. ngpiris-5.1.1.dev2/docs/_build/html/objects.inv +0 -0
  85. ngpiris-5.1.1.dev2/docs/_build/html/py-modindex.html +0 -188
  86. ngpiris-5.1.1.dev2/docs/_build/html/search.html +0 -120
  87. ngpiris-5.1.1.dev2/docs/_build/html/searchindex.js +0 -1
  88. ngpiris-5.1.1.dev2/tests/test_hcp.py +0 -216
  89. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/LICENSE +0 -0
  90. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/MANIFEST.in +0 -0
  91. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/__init__.py +0 -0
  92. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/hci/__init__.py +0 -0
  93. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/hci/exceptions.py +0 -0
  94. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/hci/hci.py +0 -0
  95. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/hci/helpers.py +0 -0
  96. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/hcp/__init__.py +0 -0
  97. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/hcp/exceptions.py +0 -0
  98. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/hcp/helpers.py +0 -0
  99. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/hcp/statistics.py +0 -0
  100. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/parse_credentials/__init__.py +0 -0
  101. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/parse_credentials/parse_credentials.py +0 -0
  102. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/utils/__init__.py +0 -0
  103. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris/utils/utils.py +0 -0
  104. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris.egg-info/dependency_links.txt +0 -0
  105. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris.egg-info/entry_points.txt +0 -0
  106. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/NGPIris.egg-info/top_level.txt +0 -0
  107. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/IRIS_5_changelog.md +0 -0
  108. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/Makefile +0 -0
  109. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/NGPIris.cli.rst +0 -0
  110. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/NGPIris.hci.rst +0 -0
  111. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/NGPIris.hcp.rst +0 -0
  112. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/NGPIris.parse_credentials.rst +0 -0
  113. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/NGPIris.rst +0 -0
  114. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/NGPIris.utils.rst +0 -0
  115. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/conf.py +0 -0
  116. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/index.rst +0 -0
  117. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/make.bat +0 -0
  118. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/docs/modules.rst +0 -0
  119. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/setup.cfg +0 -0
  120. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/data/80MB_test_file +0 -0
  121. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/data/a folder of data/80MB_test_file_0 +0 -0
  122. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/data/a folder of data/80MB_test_file_1 +0 -0
  123. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/data/complete_creds_0.json +0 -0
  124. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/data/complete_creds_1.json +0 -0
  125. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/data/complete_creds_2.json +0 -0
  126. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/data/incomplete_creds_0.json +0 -0
  127. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/data/incomplete_creds_1.json +0 -0
  128. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/data/incomplete_creds_2.json +0 -0
  129. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/data/incomplete_creds_3.json +0 -0
  130. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/data/incomplete_creds_4.json +0 -0
  131. {ngpiris-5.1.1.dev2 → ngpiris-5.2.0}/tests/test_parse_credentials.py +0 -0
@@ -19,14 +19,14 @@ def format_list(list_of_things : list) -> str:
19
19
  list_of_buckets = list(map(lambda s : s + "\n", list_of_things))
20
20
  return "".join(list_of_buckets).strip("\n")
21
21
 
22
- def _list_objects_generator(hcph : HCPHandler, name_only : bool) -> Generator[str, Any, None]:
22
+ def _list_objects_generator(hcph : HCPHandler, path : str, name_only : bool, files_only : bool) -> Generator[str, Any, None]:
23
23
  """
24
24
  Handle object list as a paginator that `click` can handle. It works slightly
25
25
  different from `list_objects` in `hcp.py` in order to make the output
26
26
  printable in a terminal
27
27
  """
28
28
  paginator : Paginator = hcph.s3_client.get_paginator("list_objects_v2")
29
- pages : PageIterator = paginator.paginate(Bucket = hcph.bucket_name)
29
+ pages : PageIterator = paginator.paginate(Bucket = hcph.bucket_name, Prefix = path)
30
30
  (nb_of_cols, _) = get_terminal_size()
31
31
  max_width = floor(nb_of_cols / 5)
32
32
  if (not name_only):
@@ -36,7 +36,11 @@ def _list_objects_generator(hcph : HCPHandler, name_only : bool) -> Generator[st
36
36
  tablefmt = "plain",
37
37
  stralign = "center"
38
38
  ) + "\n" + "-"*nb_of_cols + "\n"
39
- for object in pages.search("Contents[?!ends_with(Key, '/')][]"): # filter objects that does not end with "/"
39
+ if files_only:
40
+ filter_string = "Contents[?!ends_with(Key, '/')][]" # filter objects that does not end with "/"
41
+ else:
42
+ filter_string = "Contents[*][]"
43
+ for object in pages.search(filter_string):
40
44
  if name_only:
41
45
  yield str(object["Key"]) + "\n"
42
46
  else:
@@ -55,6 +59,11 @@ def _list_objects_generator(hcph : HCPHandler, name_only : bool) -> Generator[st
55
59
  def object_is_folder(object_path : str, hcph : HCPHandler) -> bool:
56
60
  return (object_path[-1] == "/") and (hcph.get_object(object_path)["ContentLength"] == 0)
57
61
 
62
+ def add_trailing_slash(path : str) -> str:
63
+ if not path[-1] == "/":
64
+ path += "/"
65
+ return path
66
+
58
67
  @click.group()
59
68
  @click.argument("credentials")
60
69
  @click.version_option(package_name = "NGPIris")
@@ -85,9 +94,13 @@ def upload(context : Context, bucket : str, source : str, destination : str):
85
94
  """
86
95
  hcph : HCPHandler = get_HCPHandler(context)
87
96
  hcph.mount_bucket(bucket)
97
+ destination = add_trailing_slash(destination)
88
98
  if Path(source).is_dir():
99
+ source = add_trailing_slash(source)
89
100
  hcph.upload_folder(source, destination)
90
101
  else:
102
+ file_name = Path(source).name
103
+ destination += file_name
91
104
  hcph.upload_file(source, destination)
92
105
 
93
106
  @cli.command()
@@ -198,22 +211,52 @@ def list_buckets(context : Context):
198
211
 
199
212
  @cli.command()
200
213
  @click.argument("bucket")
214
+ @click.argument("path", required = False)
201
215
  @click.option(
202
216
  "-no",
203
217
  "--name-only",
204
218
  help = "Output only the name of the objects instead of all the associated metadata",
205
- default = False
219
+ default = False,
220
+ is_flag = True
221
+ )
222
+ @click.option(
223
+ "-p",
224
+ "--pagination",
225
+ help = "Output as a paginator",
226
+ default = False,
227
+ is_flag = True
228
+ )
229
+ @click.option(
230
+ "-fo",
231
+ "--files-only",
232
+ help = "Output only file objects",
233
+ default = False,
234
+ is_flag = True
206
235
  )
207
236
  @click.pass_context
208
- def list_objects(context : Context, bucket : str, name_only : bool):
237
+ def list_objects(context : Context, bucket : str, path : str, name_only : bool, pagination : bool, files_only : bool):
209
238
  """
210
239
  List the objects in a certain bucket/namespace on the HCP.
211
240
 
212
241
  BUCKET is the name of the bucket in which to list its objects.
242
+
243
+ PATH is an optional argument for where to list the objects
213
244
  """
214
245
  hcph : HCPHandler = get_HCPHandler(context)
215
246
  hcph.mount_bucket(bucket)
216
- click.echo_via_pager(_list_objects_generator(hcph, name_only))
247
+ if path:
248
+ path_with_slash = add_trailing_slash(path)
249
+
250
+ if not hcph.object_exists(path_with_slash):
251
+ raise RuntimeError("Path does not exist")
252
+ else:
253
+ path_with_slash = ""
254
+
255
+ if pagination:
256
+ click.echo_via_pager(_list_objects_generator(hcph, path_with_slash, name_only, files_only))
257
+ else:
258
+ for obj in hcph.list_objects(path_with_slash, name_only, files_only):
259
+ click.echo(obj)
217
260
 
218
261
  @cli.command()
219
262
  @click.argument("bucket")
@@ -222,23 +265,86 @@ def list_objects(context : Context, bucket : str, name_only : bool):
222
265
  "-cs",
223
266
  "--case_sensitive",
224
267
  help = "Use case sensitivity? Default value is False",
225
- default = False
268
+ default = False,
269
+ is_flag = True
270
+ )
271
+ @click.option(
272
+ "-v",
273
+ "--verbose",
274
+ help = "Get a verbose output of files. Default value is False, since it might be slower",
275
+ default = False,
276
+ is_flag = True
226
277
  )
227
278
  @click.pass_context
228
- def simple_search(context : Context, bucket : str, search_string : str, case_sensitive : bool):
279
+ def simple_search(context : Context, bucket : str, search_string : str, case_sensitive : bool, verbose : bool):
229
280
  """
230
281
  Make simple search using substrings in a bucket/namespace on the HCP.
231
282
 
283
+ NOTE: This command does not use the HCI. Instead, it uses a linear search of
284
+ all the objects in the HCP. As such, this search might be slow.
285
+
286
+ BUCKET is the name of the bucket in which to make the search.
287
+
288
+ SEARCH_STRING is any string that is to be used for the search.
289
+ """
290
+ hcph : HCPHandler = get_HCPHandler(context)
291
+ hcph.mount_bucket(bucket)
292
+ list_of_results = hcph.search_in_bucket(
293
+ search_string,
294
+ name_only = (not verbose),
295
+ case_sensitive = case_sensitive
296
+ )
297
+ click.echo("Search results:")
298
+ for result in list_of_results:
299
+ click.echo(result)
300
+
301
+ @cli.command()
302
+ @click.argument("bucket")
303
+ @click.argument("search_string")
304
+ @click.option(
305
+ "-cs",
306
+ "--case_sensitive",
307
+ help = "Use case sensitivity? Default value is False",
308
+ default = False,
309
+ is_flag = True
310
+ )
311
+ @click.option(
312
+ "-v",
313
+ "--verbose",
314
+ help = "Get a verbose output of files. Default value is False, since it might be slower",
315
+ default = False,
316
+ is_flag = True
317
+ )
318
+ @click.option(
319
+ "-t",
320
+ "--threshold",
321
+ help = "Set the threshold for the fuzzy search score. Default value is 80",
322
+ default = 80
323
+ )
324
+ @click.pass_context
325
+ def fuzzy_search(context : Context, bucket : str, search_string : str, case_sensitive : bool, verbose : bool, threshold : int):
326
+ """
327
+ Make a fuzzy search using a search string in a bucket/namespace on the HCP.
328
+
329
+ NOTE: This command does not use the HCI. Instead, it uses the RapidFuzz
330
+ library in order to find objects in the HCP. As such, this search might
331
+ be slow.
332
+
232
333
  BUCKET is the name of the bucket in which to make the search.
233
334
 
234
335
  SEARCH_STRING is any string that is to be used for the search.
235
336
  """
236
337
  hcph : HCPHandler = get_HCPHandler(context)
237
338
  hcph.mount_bucket(bucket)
238
- list_of_results = hcph.search_objects_in_bucket(search_string, case_sensitive)
339
+ list_of_results = hcph.fuzzy_search_in_bucket(
340
+ search_string,
341
+ name_only = (not verbose),
342
+ case_sensitive = case_sensitive,
343
+ threshold = threshold
344
+ )
239
345
  click.echo("Search results:")
240
346
  for result in list_of_results:
241
- click.echo("- " + result)
347
+ click.echo(result)
242
348
 
243
349
  @cli.command()
244
350
  @click.argument("bucket")
@@ -31,9 +31,13 @@ from os import (
31
31
  from json import dumps
32
32
  from parse import (
33
33
  parse,
34
- search,
35
34
  Result
36
35
  )
36
+ from rapidfuzz import (
37
+ fuzz,
38
+ process,
39
+ utils
40
+ )
37
41
  from requests import get
38
42
  from urllib3 import disable_warnings
39
43
  from tqdm import tqdm
@@ -173,7 +177,9 @@ class HCPHandler:
173
177
  elif bucket_name:
174
178
  pass
175
179
  else:
176
- raise RuntimeError("No bucket selected. Either use `mount_bucket` first or supply the optional `bucket_name` paramter for `test_connection`")
180
+ raise RuntimeError("No bucket selected. Either use `mount_bucket` first or supply the optional `bucket_name` parameter for `test_connection`")
181
+
182
+ response = {}
177
183
  try:
178
184
  response = dict(self.s3_client.head_bucket(Bucket = bucket_name))
179
185
  except EndpointConnectionError as e: # pragma: no cover
@@ -230,7 +236,7 @@ class HCPHandler:
230
236
  return list_of_buckets
231
237
 
232
238
  @check_mounted
233
- def list_objects(self, path_key : str = "", name_only : bool = False) -> Generator:
239
+ def list_objects(self, path_key : str = "", name_only : bool = False, files_only : bool = False) -> Generator:
234
240
  """
235
241
  List all objects in the mounted bucket as a generator. If one wishes to
236
242
  get the result as a list, use :py:function:`list` to type cast the generator
@@ -239,16 +245,35 @@ class HCPHandler:
239
245
  :type path_key: str, optional
240
246
  :param name_only: If True, yield only a the object names. If False, yield the full metadata about each object. Defaults to False.
241
247
  :type name_only: bool, optional
248
+ :param files_only: If true, only yield file objects. Defaults to False
249
+ :type files_only: bool, optional
242
250
  :yield: A generator of all objects in a bucket
243
251
  :rtype: Generator
244
252
  """
245
253
  paginator : Paginator = self.s3_client.get_paginator("list_objects_v2")
246
- pages : PageIterator = paginator.paginate(Bucket = self.bucket_name)
247
- for object in pages.search("Contents[?starts_with(Key, '" + path_key + "')][]"):
248
- if name_only:
249
- yield str(object["Key"])
250
- else:
251
- yield object
254
+ pages : PageIterator = paginator.paginate(Bucket = self.bucket_name, Prefix = path_key)
255
+
256
+ if files_only:
257
+ filter_string = "Contents[?!ends_with(Key, '/')][]"
258
+ else:
259
+ filter_string = "Contents[*][]"
260
+
261
+ split_path_key = len(path_key.split("/")) + 1
262
+
263
+ pages_filtered = pages.search(filter_string)
264
+ for object in pages_filtered:
265
+ # Split the object key by "/"
266
+ split_object = object["Key"].split("/")
267
+ # Check if the object is within the specified path_key depth
268
+ if len(split_object) <= split_path_key:
269
+ # Skip objects that are not at the desired depth
270
+ if (len(split_object) == split_path_key) and split_object[-1]:
271
+ continue
272
+
273
+ if name_only:
274
+ yield str(object["Key"])
275
+ else:
276
+ yield object
252
277
 
253
278
  @check_mounted
254
279
  def get_object(self, key : str) -> dict:
@@ -378,6 +403,9 @@ class HCPHandler:
378
403
  file_name = Path(local_file_path).name
379
404
  key = file_name
380
405
 
406
+ if "\\" in local_file_path:
407
+ raise RuntimeError("The \"\\\" character is not allowed in the file path")
408
+
381
409
  if self.object_exists(key):
382
410
  raise ObjectAlreadyExist("The object \"" + key + "\" already exist in the mounted bucket")
383
411
  else:
@@ -426,25 +454,24 @@ class HCPHandler:
426
454
  :type verbose: bool, optional
427
455
  """
428
456
  object_list = []
457
+ does_not_exist = []
429
458
  for key in keys:
430
- object_list.append({"Key" : key})
431
-
432
- deletion_dict = {"Objects": object_list}
459
+ if self.object_exists(key):
460
+ object_list.append({"Key" : key})
461
+ else:
462
+ does_not_exist.append(key)
433
463
 
434
- response : dict = self.s3_client.delete_objects(
435
- Bucket = self.bucket_name,
436
- Delete = deletion_dict
437
- )
438
- if verbose:
439
- print(dumps(response, indent=4))
440
-
441
- deleted_dict_list : list[dict] = response["Deleted"]
442
- does_not_exist = []
443
- for deleted_dict in deleted_dict_list:
444
- if not "VersionId" in deleted_dict:
445
- does_not_exist.append("- " + key + "\n")
446
- if does_not_exist:
447
- print("The following could not be deleted because they didn't exist: \n" + "".join(does_not_exist))
464
+ if object_list:
465
+ deletion_dict = {"Objects": object_list}
466
+ response : dict = self.s3_client.delete_objects(
467
+ Bucket = self.bucket_name,
468
+ Delete = deletion_dict
469
+ )
470
+ if verbose:
471
+ print(dumps(response, indent=4))
472
+
473
+ if verbose and does_not_exist:
474
+ print("The following could not be deleted because they didn't exist: \n" + "\n".join(does_not_exist))
448
475
 
449
476
  @check_mounted
450
477
  def delete_object(self, key : str, verbose : bool = True) -> None:
@@ -461,51 +488,98 @@ class HCPHandler:
461
488
  @check_mounted
462
489
  def delete_folder(self, key : str, verbose : bool = True) -> None:
463
490
  """
464
- Delete a folder of objects in the mounted bucket. If there are subfolders, a RuntimeError is raisesd
491
+ Delete a folder of objects in the mounted bucket. If there are subfolders, a RuntimeError is raised
465
492
 
466
493
  :param key: The folder of objects to be deleted
467
494
  :type key: str
468
495
  :param verbose: Print the result of the deletion. defaults to True
469
496
  :type verbose: bool, optional
470
- :raises RuntimeError: If there are subfolders, a RuntimeError is raisesd
497
+ :raises RuntimeError: If there are subfolders, a RuntimeError is raised
471
498
  """
472
499
  if key[-1] != "/":
473
500
  key += "/"
474
- object_path_in_folder = []
475
- for s in self.search_objects_in_bucket(key):
476
- parse_object = parse(key + "{}", s)
477
- if type(parse_object) is Result:
478
- object_path_in_folder.append(s)
479
-
480
- for object_path in object_path_in_folder:
481
- if object_path[-1] == "/":
501
+
502
+ objects : list[str] = list(self.list_objects(key, name_only = True))
503
+
504
+ if not objects:
505
+ raise RuntimeError("\"" + key + "\"" + " is not a valid path") #TODO: change this error
506
+
507
+ for object_path in objects:
508
+ if (object_path[-1] == "/") and (not object_path == key): # `objects` might contain key, in which case everything is fine
482
509
  raise RuntimeError("There are subfolders in this folder. Please remove these first, before deleting this one")
483
- self.delete_objects(object_path_in_folder + [key], verbose = verbose)
510
+ self.delete_objects(objects, verbose = verbose)
484
511
 
485
512
  @check_mounted
486
- def search_objects_in_bucket(self, search_string : str, case_sensitive : bool = False) -> list[str]:
513
+ def search_in_bucket(
514
+ self,
515
+ search_string : str,
516
+ name_only : bool = True,
517
+ case_sensitive : bool = False
518
+ ) -> Generator:
487
519
  """
488
- Simple search method using substrings in order to find certain objects. Case insensitive by default. Does not utilise the HCI
520
+ Simple search method using exact substrings in order to find certain
521
+ objects. Case insensitive by default. Does not utilise the HCI
489
522
 
490
523
  :param search_string: Substring to be used in the search
491
524
  :type search_string: str
492
525
 
526
+ :param name_only: If True, yield only a the object names. If False, yield the full metadata about each object. Defaults to False.
527
+ :type name_only: bool, optional
528
+
493
529
  :param case_sensitive: Case sensitivity. Defaults to False
494
530
  :type case_sensitive: bool, optional
495
531
 
496
- :return: List of object names that match the in some way to the object names
497
- :rtype: list[str]
532
+ :return: A generator of objects based on the search string
533
+ :rtype: Generator
534
+ """
535
+ return self.fuzzy_search_in_bucket(search_string, name_only, case_sensitive, 100)
536
+
537
+ @check_mounted
538
+ def fuzzy_search_in_bucket(
539
+ self,
540
+ search_string : str,
541
+ name_only : bool = True,
542
+ case_sensitive : bool = False,
543
+ threshold : int = 80
544
+ ) -> Generator:
545
+ """
546
+ Fuzzy search implementation based on the `RapidFuzz` library.
547
+
548
+ :param search_string: Substring to be used in the search
549
+ :type search_string: str
550
+
551
+ :param name_only: If True, yield only a the object names. If False, yield the full metadata about each object. Defaults to False.
552
+ :type name_only: bool, optional
553
+
554
+ :param case_sensitive: Case sensitivity. Defaults to False
555
+ :type case_sensitive: bool, optional
556
+
557
+ :param threshold: The fuzzy search similarity score. Defaults to 80
558
+ :type threshold: int, optional
559
+
560
+ :return: A generator of objects based on the search string
561
+ :rtype: Generator
498
562
  """
499
- search_result : list[str] = []
500
- for key in self.list_objects(name_only = True):
501
- parse_object = search(
563
+
564
+ if case_sensitive:
565
+ processor = None
566
+ else:
567
+ processor = utils.default_process
568
+
569
+ if not name_only:
570
+ full_list = list(self.list_objects())
571
+
572
+ for item, score, index in process.extract_iter(
502
573
  search_string,
503
- key,
504
- case_sensitive = case_sensitive
505
- )
506
- if type(parse_object) is Result:
507
- search_result.append(key)
508
- return search_result
574
+ self.list_objects(name_only = True),
575
+ scorer = fuzz.partial_ratio,
576
+ processor = processor
577
+ ):
578
+ if score >= threshold:
579
+ if name_only:
580
+ yield item
581
+ else:
582
+ yield full_list[index] # type: ignore
509
583
 
510
584
  @check_mounted
511
585
  def get_object_acl(self, key : str) -> dict:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: NGPIris
3
- Version: 5.1.1.dev2
3
+ Version: 5.2.0
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
@@ -8,6 +8,7 @@ Requires-Dist: requests>=2.31.0
8
8
  Requires-Dist: urllib3==1.26.19
9
9
  Requires-Dist: boto3>=1.26.76
10
10
  Requires-Dist: parse>=1.19.1
11
+ Requires-Dist: RapidFuzz>=3.10.1
11
12
  Requires-Dist: tqdm>=4.66.2
12
13
  Requires-Dist: click>=8.1.7
13
14
  Requires-Dist: bitmath==1.3.3.1
@@ -164,116 +165,8 @@ Commands:
164
165
  test-connection Test the connection to a bucket/namespace.
165
166
  upload Upload files to an HCP bucket/namespace.
166
167
  ```
168
+ Each sub-command has its own help message and is displayed by `iris path/to/your/credentials.json sub-command --help`
167
169
 
168
- #### The `delete-folder` command
169
- ```
170
- Usage: iris CREDENTIALS delete-folder [OPTIONS] FOLDER BUCKET
171
-
172
- Delete a folder from an HCP bucket/namespace.
173
-
174
- FOLDER is the name of the folder to be deleted.
175
-
176
- BUCKET is the name of the bucket where the folder to be deleted exist.
177
-
178
- Options:
179
- --help Show this message and exit.
180
- ```
181
-
182
- #### The `delete-object` command
183
- ```
184
- Usage: iris CREDENTIALS delete-object [OPTIONS] OBJECT BUCKET
185
-
186
- Delete an object from an HCP bucket/namespace.
187
-
188
- OBJECT is the name of the object to be deleted.
189
-
190
- BUCKET is the name of the bucket where the object to be deleted exist.
191
-
192
- Options:
193
- --help Show this message and exit.
194
- ```
195
-
196
- #### The `download` command
197
- ```
198
- Usage: iris CREDENTIALS download [OPTIONS] OBJECT BUCKET LOCAL_PATH
199
-
200
- Download files from an HCP bucket/namespace.
201
-
202
- OBJECT is the name of the object to be downloaded.
203
-
204
- BUCKET is the name of the upload destination bucket.
205
-
206
- LOCAL_PATH is the path to where the downloaded objects are to be stored
207
- locally.
208
-
209
- Options:
210
- --help Show this message and exit.
211
- ```
212
-
213
- #### The `list-buckets` command
214
- ```
215
- Usage: iris CREDENTIALS list-buckets [OPTIONS]
216
-
217
- List the available buckets/namespaces on the HCP.
218
-
219
- Options:
220
- --help Show this message and exit.
221
- ```
222
-
223
- #### The `list-objects` command
224
- ```
225
- Usage: iris CREDENTIALS list-objects [OPTIONS] BUCKET
226
-
227
- List the objects in a certain bucket/namespace on the HCP.
228
-
229
- BUCKET is the name of the bucket in which to list its objects.
230
-
231
- Options:
232
- -no, --name-only BOOLEAN Output only the name of the objects instead of all
233
- the associated metadata
234
- --help Show this message and exit.
235
- ```
236
-
237
- #### The `simple-search` command
238
- ```
239
- Usage: iris CREDENTIALS simple-search [OPTIONS] BUCKET SEARCH_STRING
240
-
241
- Make simple search using substrings in a bucket/namespace on the HCP.
242
-
243
- BUCKET is the name of the bucket in which to make the search.
244
-
245
- SEARCH_STRING is any string that is to be used for the search.
246
-
247
- Options:
248
- -cs, --case_sensitive BOOLEAN Use case sensitivity?
249
- --help Show this message and exit.
250
- ```
251
-
252
- #### The `test-connection` command
253
- ```
254
- Usage: iris CREDENTIALS test-connection [OPTIONS] BUCKET
255
-
256
- Test the connection to a bucket/namespace.
257
-
258
- BUCKET is the name of the bucket for which a connection test should be made.
259
-
260
- Options:
261
- --help Show this message and exit.
262
- ```
263
-
264
- #### The `upload` command
265
- ```
266
- Usage: iris CREDENTIALS upload [OPTIONS] FILE_OR_FOLDER BUCKET
267
-
268
- Upload files to an HCP bucket/namespace.
269
-
270
- FILE-OR-FOLDER is the path to the file or folder of files to be uploaded.
271
-
272
- BUCKET is the name of the upload destination bucket.
273
-
274
- Options:
275
- --help Show this message and exit.
276
- ```
277
170
  ## Testing
278
171
  Assuming that the repository has been cloned, run the following tests:
279
172
  ```shell
@@ -0,0 +1,52 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ NGPIris/__init__.py
6
+ NGPIris.egg-info/PKG-INFO
7
+ NGPIris.egg-info/SOURCES.txt
8
+ NGPIris.egg-info/dependency_links.txt
9
+ NGPIris.egg-info/entry_points.txt
10
+ NGPIris.egg-info/requires.txt
11
+ NGPIris.egg-info/top_level.txt
12
+ NGPIris/cli/__init__.py
13
+ NGPIris/hci/__init__.py
14
+ NGPIris/hci/exceptions.py
15
+ NGPIris/hci/hci.py
16
+ NGPIris/hci/helpers.py
17
+ NGPIris/hcp/__init__.py
18
+ NGPIris/hcp/exceptions.py
19
+ NGPIris/hcp/hcp.py
20
+ NGPIris/hcp/helpers.py
21
+ NGPIris/hcp/statistics.py
22
+ NGPIris/parse_credentials/__init__.py
23
+ NGPIris/parse_credentials/parse_credentials.py
24
+ NGPIris/utils/__init__.py
25
+ NGPIris/utils/utils.py
26
+ docs/IRIS_5_changelog.md
27
+ docs/Makefile
28
+ docs/NGPIris.cli.rst
29
+ docs/NGPIris.hci.rst
30
+ docs/NGPIris.hcp.rst
31
+ docs/NGPIris.parse_credentials.rst
32
+ docs/NGPIris.rst
33
+ docs/NGPIris.utils.rst
34
+ docs/Tutorial.md
35
+ docs/conf.py
36
+ docs/index.rst
37
+ docs/make.bat
38
+ docs/modules.rst
39
+ tests/test_hci.py
40
+ tests/test_hcp.py
41
+ tests/test_parse_credentials.py
42
+ tests/data/80MB_test_file
43
+ tests/data/complete_creds_0.json
44
+ tests/data/complete_creds_1.json
45
+ tests/data/complete_creds_2.json
46
+ tests/data/incomplete_creds_0.json
47
+ tests/data/incomplete_creds_1.json
48
+ tests/data/incomplete_creds_2.json
49
+ tests/data/incomplete_creds_3.json
50
+ tests/data/incomplete_creds_4.json
51
+ tests/data/a folder of data/80MB_test_file_0
52
+ tests/data/a folder of data/80MB_test_file_1
@@ -2,6 +2,7 @@ requests>=2.31.0
2
2
  urllib3==1.26.19
3
3
  boto3>=1.26.76
4
4
  parse>=1.19.1
5
+ RapidFuzz>=3.10.1
5
6
  tqdm>=4.66.2
6
7
  click>=8.1.7
7
8
  bitmath==1.3.3.1