NGPIris 5.6.3.dev1__tar.gz → 5.6.5__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 (57) hide show
  1. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/cli/__init__.py +74 -38
  2. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/cli/helpers.py +26 -9
  3. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/hcp/exceptions.py +33 -0
  4. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/hcp/hcp.py +179 -152
  5. ngpiris-5.6.5/NGPIris/hcp/helpers.py +178 -0
  6. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/hcp/statistics.py +5 -5
  7. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris.egg-info/PKG-INFO +4 -6
  8. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris.egg-info/requires.txt +1 -1
  9. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/PKG-INFO +4 -6
  10. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/README.md +2 -4
  11. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/pyproject.toml +3 -3
  12. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/test_hcp.py +5 -5
  13. ngpiris-5.6.3.dev1/NGPIris/hcp/helpers.py +0 -70
  14. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/LICENSE +0 -0
  15. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/MANIFEST.in +0 -0
  16. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/__init__.py +0 -0
  17. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/cli/sections.py +0 -0
  18. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/hci/__init__.py +0 -0
  19. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/hci/exceptions.py +0 -0
  20. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/hci/hci.py +0 -0
  21. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/hci/helpers.py +0 -0
  22. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/hcp/__init__.py +0 -0
  23. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/parse_credentials/__init__.py +0 -0
  24. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/parse_credentials/parse_credentials.py +0 -0
  25. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/utils/__init__.py +0 -0
  26. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris/utils/utils.py +0 -0
  27. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris.egg-info/SOURCES.txt +0 -0
  28. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris.egg-info/dependency_links.txt +0 -0
  29. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris.egg-info/entry_points.txt +0 -0
  30. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/NGPIris.egg-info/top_level.txt +0 -0
  31. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/IRIS_5_changelog.md +0 -0
  32. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/Makefile +0 -0
  33. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/NGPIris.cli.rst +0 -0
  34. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/NGPIris.hci.rst +0 -0
  35. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/NGPIris.hcp.rst +0 -0
  36. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/NGPIris.parse_credentials.rst +0 -0
  37. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/NGPIris.rst +0 -0
  38. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/NGPIris.utils.rst +0 -0
  39. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/Tutorial.md +0 -0
  40. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/conf.py +0 -0
  41. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/index.rst +0 -0
  42. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/make.bat +0 -0
  43. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/docs/modules.rst +0 -0
  44. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/setup.cfg +0 -0
  45. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/data/80MB_test_file +0 -0
  46. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/data/a folder of data/80MB_test_file_0 +0 -0
  47. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/data/a folder of data/80MB_test_file_1 +0 -0
  48. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/data/complete_creds_0.json +0 -0
  49. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/data/complete_creds_1.json +0 -0
  50. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/data/complete_creds_2.json +0 -0
  51. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/data/incomplete_creds_0.json +0 -0
  52. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/data/incomplete_creds_1.json +0 -0
  53. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/data/incomplete_creds_2.json +0 -0
  54. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/data/incomplete_creds_3.json +0 -0
  55. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/data/incomplete_creds_4.json +0 -0
  56. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/test_hci.py +0 -0
  57. {ngpiris-5.6.3.dev1 → ngpiris-5.6.5}/tests/test_parse_credentials.py +0 -0
@@ -1,12 +1,12 @@
1
+ # ruff: noqa: SLF001
2
+
1
3
  import sys
2
- from collections.abc import Generator
3
4
  from json import dump
4
5
  from pathlib import Path
5
- from typing import Any
6
+ from typing import TYPE_CHECKING, Any
6
7
 
7
8
  import click
8
- import lazy_table as lt
9
- from click.core import Context
9
+ from bitmath import SI, Byte
10
10
  from tabulate import tabulate
11
11
 
12
12
  from NGPIris import HCPHandler
@@ -16,10 +16,19 @@ from NGPIris.cli.helpers import (
16
16
  download_file,
17
17
  download_folder,
18
18
  ensure_destination_dir,
19
- object_is_folder,
19
+ render_objects_table,
20
20
  )
21
21
  from NGPIris.cli.sections import SectionedGroup
22
- from NGPIris.hcp.exceptions import IsFolderObjectError, ObjectDoesNotExistError
22
+ from NGPIris.hcp.exceptions import (
23
+ IsFileObjectError,
24
+ IsFolderObjectError,
25
+ ObjectDoesNotExistError,
26
+ )
27
+
28
+ if TYPE_CHECKING:
29
+ from collections.abc import Generator
30
+
31
+ from click.core import Context
23
32
 
24
33
 
25
34
  @click.group(cls=SectionedGroup)
@@ -244,12 +253,12 @@ def delete(
244
253
  + '", the following file objects would have been deleted '
245
254
  + "(this list excludes any potential sub-folders):",
246
255
  )
247
- lt.stream(
256
+ render_objects_table(
248
257
  hcp_h.list_objects(
249
258
  hcp_object,
250
259
  files_only=True,
251
260
  ),
252
- headers="keys",
261
+ -1,
253
262
  )
254
263
 
255
264
 
@@ -308,26 +317,21 @@ def download( # noqa: PLR0913
308
317
 
309
318
  destination_path = ensure_destination_dir(destination)
310
319
 
311
- is_folder = object_is_folder(source, hcp_h)
320
+ is_folder = hcp_h._is_object_folder(source)
312
321
 
313
322
  if dry_run:
314
- if hcp_h.object_exists(source):
315
- if is_folder:
316
- click.echo(
317
- 'This command would have downloaded the folder "'
318
- + source
319
- + '". If you wish to know the contents of this folder, '
320
- + "use the 'list-objects' command",
321
- )
322
- else:
323
- click.echo(
324
- 'This command would have downloaded the object "'
325
- + source
326
- + '"',
327
- )
323
+ if is_folder:
324
+ click.echo(
325
+ 'This command would have downloaded the folder "'
326
+ + source
327
+ + '". If you wish to know the contents of this folder, '
328
+ + "use the 'list-objects' command",
329
+ )
328
330
  else:
329
331
  click.echo(
330
- '"' + source + '" does not exist',
332
+ 'This command would have downloaded the object "'
333
+ + source
334
+ + '"',
331
335
  )
332
336
  return
333
337
 
@@ -343,6 +347,14 @@ def download( # noqa: PLR0913
343
347
  )
344
348
  @click.argument("bucket")
345
349
  @click.argument("path", required=False)
350
+ @click.option(
351
+ "-bs",
352
+ "--batch_size",
353
+ help="Number of rows added to the output at a time. If value is zero or a "
354
+ "negative number, all rows will be displayed in one go. "
355
+ "Default value is 25",
356
+ default=25,
357
+ )
346
358
  @click.option(
347
359
  "-p",
348
360
  "--pagination",
@@ -369,6 +381,7 @@ def list_objects( # noqa: PLR0913
369
381
  context: Context,
370
382
  bucket: str,
371
383
  path: str,
384
+ batch_size: int,
372
385
  pagination: bool,
373
386
  files_only: bool,
374
387
  extended_information: bool,
@@ -381,12 +394,12 @@ def list_objects( # noqa: PLR0913
381
394
  PATH is an optional argument for where to list the objects
382
395
  """
383
396
 
384
- def list_objects_generator(
397
+ def list_objects_pagination_generator(
385
398
  hcp_h: HCPHandler,
386
399
  path: str,
387
- files_only: bool,
388
400
  output_mode: HCPHandler.ListObjectsOutputMode,
389
- ) -> Generator[str, Any, None]:
401
+ files_only: bool,
402
+ ) -> Generator[str, Any]:
390
403
  """
391
404
  Handle object list as a paginator that `click` can handle.
392
405
  It works slightly different from `list_objects` in `hcp.py` in order to
@@ -400,6 +413,24 @@ def list_objects( # noqa: PLR0913
400
413
  for obj in objects:
401
414
  yield str(obj) + "\n"
402
415
 
416
+ def list_objects_generator(
417
+ hcp_h: HCPHandler,
418
+ path: str,
419
+ output_mode: HCPHandler.ListObjectsOutputMode,
420
+ files_only: bool,
421
+ ) -> Generator[dict[str, Any], Any]:
422
+ objects = hcp_h.list_objects(
423
+ path,
424
+ output_mode=output_mode,
425
+ files_only=files_only,
426
+ )
427
+ for obj in objects:
428
+ if obj.get("Size", 0):
429
+ obj["Size"] = str(
430
+ Byte(obj.get("Size", 0)).best_prefix(system=SI)
431
+ )
432
+ yield obj
433
+
403
434
  hcp_h: HCPHandler = create_HCPHandler(context)
404
435
  hcp_h.mount_bucket(bucket)
405
436
  output_mode = (
@@ -408,6 +439,10 @@ def list_objects( # noqa: PLR0913
408
439
  else HCPHandler.ListObjectsOutputMode.SIMPLE
409
440
  )
410
441
 
442
+ if not hcp_h._is_object_folder(path):
443
+ msg = "The provided path is a file, which is not a valid path"
444
+ raise IsFileObjectError(msg)
445
+
411
446
  if path:
412
447
  path_with_slash = add_trailing_slash(path)
413
448
 
@@ -419,21 +454,22 @@ def list_objects( # noqa: PLR0913
419
454
 
420
455
  if pagination:
421
456
  click.echo_via_pager(
422
- list_objects_generator(
457
+ list_objects_pagination_generator(
423
458
  hcp_h,
424
459
  path_with_slash,
425
- files_only,
426
460
  output_mode,
461
+ files_only,
427
462
  ),
428
463
  )
429
464
  else:
430
- lt.stream(
431
- hcp_h.list_objects(
465
+ render_objects_table(
466
+ list_objects_generator(
467
+ hcp_h,
432
468
  path_with_slash,
433
- output_mode=output_mode,
434
- files_only=files_only,
469
+ output_mode,
470
+ files_only,
435
471
  ),
436
- headers="keys",
472
+ batch_size,
437
473
  )
438
474
 
439
475
 
@@ -689,9 +725,9 @@ def simple_search(
689
725
  case_sensitive=case_sensitive,
690
726
  )
691
727
  click.echo("Search results:")
692
- lt.stream(
728
+ render_objects_table(
693
729
  list_of_results,
694
- headers="keys",
730
+ -1,
695
731
  )
696
732
 
697
733
 
@@ -744,9 +780,9 @@ def fuzzy_search(
744
780
  threshold=threshold,
745
781
  )
746
782
  click.echo("Search results:")
747
- lt.stream(
783
+ render_objects_table(
748
784
  list_of_results,
749
- headers="keys",
785
+ -1,
750
786
  )
751
787
 
752
788
 
@@ -1,11 +1,13 @@
1
1
  import os
2
2
  import sys
3
3
  from pathlib import Path
4
+ from typing import Any, Generator
4
5
 
5
6
  import click
6
7
  from bitmath import Byte, TiB
7
8
  from boto3 import set_stream_logger
8
9
  from click.core import Context
10
+ from tabulate import tabulate
9
11
 
10
12
  from NGPIris import HCPHandler
11
13
 
@@ -89,15 +91,6 @@ def create_HCPHandler(context: Context) -> HCPHandler:
89
91
  return hcp_h
90
92
 
91
93
 
92
- def object_is_folder(object_path: str, hcp_h: HCPHandler) -> bool:
93
- """
94
- Predicate for checking if an HCP object is a folder or not.
95
- """
96
- return (object_path.endswith("/")) and (
97
- hcp_h.get_object(object_path)["ContentLength"] == 0
98
- )
99
-
100
-
101
94
  def ensure_destination_dir(destination: str) -> Path:
102
95
  """
103
96
  Ensure that `destination` exists and return it as a `Path`.
@@ -161,3 +154,27 @@ def download_file(
161
154
  " use the -f / --force option"
162
155
  )
163
156
  hcp_h.download_file(source, downloaded_source.as_posix())
157
+
158
+
159
+ def render_objects_table(
160
+ table_data: Generator[dict[str, Any], Any, None], batch_size: int
161
+ ) -> None:
162
+ """
163
+ Helper function for lazily displaying objects in a table with a given batch
164
+ size.
165
+ """
166
+ rows = []
167
+ positive_batch_size = batch_size > 0
168
+ for row in table_data:
169
+ rows.append(row)
170
+ if positive_batch_size and (
171
+ not len(rows) % batch_size
172
+ ): # Check if `len(rows)` is a mutliple of `batch_size`
173
+ click.echo(tabulate(rows, headers="keys"))
174
+ click.echo(
175
+ "(Press any key to get more rows or ctrl+c to abort...)",
176
+ nl=False,
177
+ )
178
+ click.getchar()
179
+ click.clear()
180
+ click.echo(tabulate(rows, headers="keys"))
@@ -19,11 +19,38 @@ class NotSufficientPermissionsError(Exception):
19
19
  The given permissions where not sufficient when making a MAPI request (403).
20
20
  """
21
21
 
22
+
22
23
  class NotFoundError(Exception):
23
24
  """
24
25
  The given request path was not found when making a MAPI request (404).
25
26
  """
26
27
 
28
+
29
+ # -------------- Operation response exceptions --------------
30
+ class MetadataCouldNotBeFoundError(Exception):
31
+ """
32
+ The response metadata could not be found.
33
+ """
34
+
35
+
36
+ class OperationNotPermittedError(Exception):
37
+ """
38
+ The user is not permitted to do that operation on this bucket (403).
39
+ """
40
+
41
+
42
+ class NoStatusCodeSuppliedError(Exception):
43
+ """
44
+ `HTTPStatusCode` in the response metadata was not supplied.
45
+ """
46
+
47
+
48
+ class UnknownStatusCodeError(Exception):
49
+ """
50
+ The status code recieved is not known.
51
+ """
52
+
53
+
27
54
  # -------------- Bucket exceptions --------------
28
55
  class NoBucketMountedError(Exception):
29
56
  """
@@ -64,6 +91,12 @@ class IsFolderObjectError(Exception):
64
91
  """
65
92
 
66
93
 
94
+ class IsFileObjectError(Exception):
95
+ """
96
+ The object on the mounted bucket is a file.
97
+ """
98
+
99
+
67
100
  class SubfolderError(Exception):
68
101
  """
69
102
  There is at least one subfolder in the given path on the mounted bucket.