NGPIris 5.4.4__tar.gz → 5.5.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.
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/cli/__init__.py +189 -88
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/hcp/hcp.py +136 -84
- {ngpiris-5.4.4 → ngpiris-5.5.0/NGPIris.egg-info}/PKG-INFO +3 -2
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris.egg-info/requires.txt +2 -1
- {ngpiris-5.4.4/NGPIris.egg-info → ngpiris-5.5.0}/PKG-INFO +3 -2
- {ngpiris-5.4.4 → ngpiris-5.5.0}/pyproject.toml +3 -2
- ngpiris-5.5.0/tests/test_hci.py +80 -0
- ngpiris-5.4.4/tests/test_hci.py +0 -73
- {ngpiris-5.4.4 → ngpiris-5.5.0}/LICENSE +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/MANIFEST.in +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/__init__.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/hci/__init__.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/hci/exceptions.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/hci/hci.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/hci/helpers.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/hcp/__init__.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/hcp/exceptions.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/hcp/helpers.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/hcp/statistics.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/parse_credentials/__init__.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/parse_credentials/parse_credentials.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/utils/__init__.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris/utils/utils.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris.egg-info/SOURCES.txt +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris.egg-info/dependency_links.txt +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris.egg-info/entry_points.txt +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/NGPIris.egg-info/top_level.txt +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/README.md +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/IRIS_5_changelog.md +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/Makefile +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/NGPIris.cli.rst +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/NGPIris.hci.rst +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/NGPIris.hcp.rst +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/NGPIris.parse_credentials.rst +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/NGPIris.rst +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/NGPIris.utils.rst +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/Tutorial.md +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/conf.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/index.rst +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/make.bat +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/docs/modules.rst +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/setup.cfg +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/data/80MB_test_file +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/data/a folder of data/80MB_test_file_0 +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/data/a folder of data/80MB_test_file_1 +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/data/complete_creds_0.json +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/data/complete_creds_1.json +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/data/complete_creds_2.json +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/data/incomplete_creds_0.json +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/data/incomplete_creds_1.json +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/data/incomplete_creds_2.json +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/data/incomplete_creds_3.json +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/data/incomplete_creds_4.json +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/test_hcp.py +0 -0
- {ngpiris-5.4.4 → ngpiris-5.5.0}/tests/test_parse_credentials.py +0 -0
|
@@ -6,37 +6,73 @@ from pathlib import Path
|
|
|
6
6
|
from boto3 import set_stream_logger
|
|
7
7
|
from typing import Any, Generator
|
|
8
8
|
from bitmath import Byte, TiB
|
|
9
|
+
import lazy_table as lt
|
|
9
10
|
import sys
|
|
11
|
+
import os
|
|
10
12
|
|
|
11
13
|
from NGPIris.hcp import HCPHandler
|
|
12
14
|
|
|
13
|
-
def get_HCPHandler(context : Context) -> HCPHandler:
|
|
14
|
-
return context.obj["hcph"]
|
|
15
|
-
|
|
16
|
-
def format_list(list_of_things : list) -> str:
|
|
17
|
-
list_of_buckets = list(map(lambda s : s + "\n", list_of_things))
|
|
18
|
-
return "".join(list_of_buckets).strip("\n")
|
|
19
|
-
|
|
20
|
-
def _list_objects_generator(hcph : HCPHandler, path : str, name_only : bool, files_only : bool) -> Generator[str, Any, None]:
|
|
21
|
-
"""
|
|
22
|
-
Handle object list as a paginator that `click` can handle. It works slightly
|
|
23
|
-
different from `list_objects` in `hcp.py` in order to make the output
|
|
24
|
-
printable in a terminal
|
|
25
|
-
"""
|
|
26
|
-
objects = hcph.list_objects(path, name_only, files_only)
|
|
27
|
-
for obj in objects:
|
|
28
|
-
yield str(obj) + "\n"
|
|
29
|
-
|
|
30
|
-
def object_is_folder(object_path : str, hcph : HCPHandler) -> bool:
|
|
31
|
-
return (object_path[-1] == "/") and (hcph.get_object(object_path)["ContentLength"] == 0)
|
|
32
|
-
|
|
33
15
|
def add_trailing_slash(path : str) -> str:
|
|
34
16
|
if not path[-1] == "/":
|
|
35
17
|
path += "/"
|
|
36
18
|
return path
|
|
37
19
|
|
|
20
|
+
def create_HCPHandler(context : Context) -> HCPHandler:
|
|
21
|
+
# hcp_credentials : str | dict[str, str], debug : bool, transfer_config : str
|
|
22
|
+
|
|
23
|
+
if context.parent:
|
|
24
|
+
parent_context = context.parent
|
|
25
|
+
else:
|
|
26
|
+
# Should never happen
|
|
27
|
+
click.echo("Something went wrong with the subcommand and parent command relation", err = True)
|
|
28
|
+
sys.exit(1)
|
|
29
|
+
|
|
30
|
+
credentials : str | None = parent_context.params.get("credentials")
|
|
31
|
+
|
|
32
|
+
if credentials:
|
|
33
|
+
hcp_credentials = credentials
|
|
34
|
+
elif os.environ.get("NGPIRIS_CREDENTIALS_PATH", None):
|
|
35
|
+
hcp_credentials = os.environ["NGPIRIS_CREDENTIALS_PATH"]
|
|
36
|
+
else:
|
|
37
|
+
endpoint : str = click.prompt(
|
|
38
|
+
"Please enter your tenant endpoint"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
aws_access_key_id : str = click.prompt(
|
|
42
|
+
"Please enter your base64 hashed aws_access_key_id"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
aws_secret_access_key : str = click.prompt(
|
|
46
|
+
"Please enter your md5 hashed aws_secret_access_key",
|
|
47
|
+
hide_input = True,
|
|
48
|
+
confirmation_prompt = True
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
hcp_credentials = {
|
|
52
|
+
"endpoint" : endpoint,
|
|
53
|
+
"aws_access_key_id" : aws_access_key_id,
|
|
54
|
+
"aws_secret_access_key" : aws_secret_access_key
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
debug : bool | None = parent_context.params.get("debug")
|
|
58
|
+
transfer_config : str | None = parent_context.params.get("transfer_config")
|
|
59
|
+
if transfer_config:
|
|
60
|
+
hcp_h = HCPHandler(hcp_credentials, custom_config_path = transfer_config)
|
|
61
|
+
else:
|
|
62
|
+
hcp_h = HCPHandler(hcp_credentials)
|
|
63
|
+
|
|
64
|
+
if debug:
|
|
65
|
+
set_stream_logger(name="")
|
|
66
|
+
click.echo(hcp_h.transfer_config.__dict__)
|
|
67
|
+
|
|
68
|
+
return hcp_h
|
|
69
|
+
|
|
38
70
|
@click.group()
|
|
39
|
-
@click.
|
|
71
|
+
@click.option(
|
|
72
|
+
"-c",
|
|
73
|
+
"--credentials",
|
|
74
|
+
help = "Path to a JSON file with credentials",
|
|
75
|
+
)
|
|
40
76
|
@click.option(
|
|
41
77
|
"--debug",
|
|
42
78
|
help = "Get the debug log for running a command",
|
|
@@ -45,7 +81,7 @@ def add_trailing_slash(path : str) -> str:
|
|
|
45
81
|
@click.option(
|
|
46
82
|
"-tc",
|
|
47
83
|
"--transfer_config",
|
|
48
|
-
help = "
|
|
84
|
+
help = "Path for using a custom transfer config for uploads or downloads",
|
|
49
85
|
)
|
|
50
86
|
@click.version_option(package_name = "NGPIris")
|
|
51
87
|
@click.pass_context
|
|
@@ -56,18 +92,47 @@ def cli(context : Context, credentials : str, debug : bool, transfer_config : st
|
|
|
56
92
|
CREDENTIALS refers to the path to the JSON credentials file.
|
|
57
93
|
"""
|
|
58
94
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
95
|
+
@cli.command(short_help = "This command returns a shell command that sets the `NGPIRIS_CREDENTIALS_PATH` enviroment variable depending on your shell")
|
|
96
|
+
@click.argument(
|
|
97
|
+
"credentials_path",
|
|
98
|
+
required = False
|
|
99
|
+
)
|
|
100
|
+
@click.option(
|
|
101
|
+
"-s",
|
|
102
|
+
"--shell",
|
|
103
|
+
type = click.Choice(
|
|
104
|
+
["bash", "fish", "zsh"],
|
|
105
|
+
case_sensitive = False
|
|
106
|
+
),
|
|
107
|
+
help = "Allows for selection of shell that the produced command should support",
|
|
108
|
+
default = "bash"
|
|
109
|
+
)
|
|
110
|
+
@click.pass_context
|
|
111
|
+
def shell_env(context : Context, credentials_path : str, shell : str):
|
|
112
|
+
"""
|
|
113
|
+
NGP IRIS will look for an enviroment variable called
|
|
114
|
+
`NGPIRIS_CREDENTIALS_PATH` when authenicating. This command returns a
|
|
115
|
+
shell command that sets the `NGPIRIS_CREDENTIALS_PATH` enviroment variable
|
|
116
|
+
depending on your shell.
|
|
117
|
+
|
|
118
|
+
NOTE: The enviroment variable will only last for this
|
|
119
|
+
shell session, but you could set the value of `NGPIRIS_CREDENTIALS_PATH`
|
|
120
|
+
permanently via other commands if you wish to do so.
|
|
65
121
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
122
|
+
CREDENTIALS PATH is the either absolute or relative path to your credentials
|
|
123
|
+
JSON file
|
|
124
|
+
"""
|
|
125
|
+
if not credentials_path:
|
|
126
|
+
click.prompt("Please enter the path to your credentials file")
|
|
127
|
+
|
|
128
|
+
click.echo("Copy and paste the following command in order to set your environment variable:")
|
|
129
|
+
match shell:
|
|
130
|
+
case "bash":
|
|
131
|
+
click.echo("export NGPIRIS_CREDENTIALS_PATH=" + credentials_path)
|
|
132
|
+
case "fish":
|
|
133
|
+
click.echo("set -x NGPIRIS_CREDENTIALS_PATH " + credentials_path)
|
|
134
|
+
case "zsh":
|
|
135
|
+
click.echo("export NGPIRIS_CREDENTIALS_PATH=" + credentials_path)
|
|
71
136
|
|
|
72
137
|
@cli.command()
|
|
73
138
|
@click.argument("bucket")
|
|
@@ -114,7 +179,7 @@ def upload(context : Context, bucket : str, source : str, destination : str, dry
|
|
|
114
179
|
|
|
115
180
|
upload_mode_choice = HCPHandler.UploadMode(upload_mode.lower())
|
|
116
181
|
|
|
117
|
-
hcph : HCPHandler =
|
|
182
|
+
hcph : HCPHandler = create_HCPHandler(context)
|
|
118
183
|
hcph.mount_bucket(bucket)
|
|
119
184
|
destination = add_trailing_slash(destination)
|
|
120
185
|
if Path(source).is_dir():
|
|
@@ -131,7 +196,7 @@ def upload(context : Context, bucket : str, source : str, destination : str, dry
|
|
|
131
196
|
else:
|
|
132
197
|
hcph.upload_file(source, destination, upload_mode = upload_mode_choice, equal_parts = equal_parts)
|
|
133
198
|
|
|
134
|
-
@cli.command()
|
|
199
|
+
@cli.command(short_help = "Download a file or folder from an HCP bucket/namespace.")
|
|
135
200
|
@click.argument("bucket")
|
|
136
201
|
@click.argument("source")
|
|
137
202
|
@click.argument("destination")
|
|
@@ -164,7 +229,10 @@ def download(context : Context, bucket : str, source : str, destination : str, f
|
|
|
164
229
|
|
|
165
230
|
DESTINATION is the folder where the downloaded object or object folder is to be stored locally.
|
|
166
231
|
"""
|
|
167
|
-
hcph : HCPHandler
|
|
232
|
+
def object_is_folder(object_path : str, hcph : HCPHandler) -> bool:
|
|
233
|
+
return (object_path[-1] == "/") and (hcph.get_object(object_path)["ContentLength"] == 0)
|
|
234
|
+
|
|
235
|
+
hcph : HCPHandler = create_HCPHandler(context)
|
|
168
236
|
hcph.mount_bucket(bucket)
|
|
169
237
|
if not Path(destination).exists():
|
|
170
238
|
Path(destination).mkdir()
|
|
@@ -227,15 +295,14 @@ def delete_object(context : Context, bucket : str, object : str, dry_run : bool)
|
|
|
227
295
|
|
|
228
296
|
OBJECT is the name of the object to be deleted.
|
|
229
297
|
"""
|
|
230
|
-
hcph : HCPHandler =
|
|
298
|
+
hcph : HCPHandler = create_HCPHandler(context)
|
|
231
299
|
hcph.mount_bucket(bucket)
|
|
232
300
|
if not dry_run:
|
|
233
|
-
hcph.delete_object(object)
|
|
301
|
+
click.echo(hcph.delete_object(object))
|
|
234
302
|
else:
|
|
235
303
|
click.echo("This command would delete:")
|
|
236
304
|
click.echo(list(hcph.list_objects(object))[0])
|
|
237
305
|
|
|
238
|
-
|
|
239
306
|
@cli.command()
|
|
240
307
|
@click.argument("bucket")
|
|
241
308
|
@click.argument("folder")
|
|
@@ -254,34 +321,47 @@ def delete_folder(context : Context, bucket : str, folder : str, dry_run : bool)
|
|
|
254
321
|
|
|
255
322
|
FOLDER is the name of the folder to be deleted.
|
|
256
323
|
"""
|
|
257
|
-
hcph : HCPHandler =
|
|
324
|
+
hcph : HCPHandler = create_HCPHandler(context)
|
|
258
325
|
hcph.mount_bucket(bucket)
|
|
259
326
|
if not dry_run:
|
|
260
|
-
hcph.delete_folder(folder)
|
|
327
|
+
click.echo(hcph.delete_folder(folder))
|
|
261
328
|
else:
|
|
262
329
|
click.echo("By deleting \"" + folder + "\", the following objects would have been deleted (not including objects in sub-folders):")
|
|
263
330
|
for obj in hcph.list_objects(folder):
|
|
264
331
|
click.echo(obj)
|
|
265
332
|
|
|
333
|
+
@cli.command()
|
|
334
|
+
@click.argument("bucket")
|
|
335
|
+
@click.option(
|
|
336
|
+
"-dr",
|
|
337
|
+
"--dry_run",
|
|
338
|
+
help = "Simulate the command execution without making actual changes. Useful for testing and verification",
|
|
339
|
+
is_flag = True
|
|
340
|
+
)
|
|
341
|
+
@click.pass_context
|
|
342
|
+
def delete_bucket(context : Context, bucket : str, dry_run : bool):
|
|
343
|
+
hcph : HCPHandler = create_HCPHandler(context)
|
|
344
|
+
if not dry_run:
|
|
345
|
+
click.echo(hcph.delete_bucket(bucket))
|
|
346
|
+
else:
|
|
347
|
+
click.echo("This command would have deleted the bucket called \"" + bucket + "\"")
|
|
348
|
+
|
|
266
349
|
@cli.command()
|
|
267
350
|
@click.pass_context
|
|
268
351
|
def list_buckets(context : Context):
|
|
269
352
|
"""
|
|
270
353
|
List the available buckets/namespaces on the HCP.
|
|
271
354
|
"""
|
|
272
|
-
hcph : HCPHandler =
|
|
273
|
-
click.echo(
|
|
355
|
+
hcph : HCPHandler = create_HCPHandler(context)
|
|
356
|
+
click.echo(
|
|
357
|
+
"".join(
|
|
358
|
+
list(map(lambda s : s + "\n", hcph.list_buckets()))
|
|
359
|
+
).strip("\n")
|
|
360
|
+
)
|
|
274
361
|
|
|
275
|
-
@cli.command()
|
|
362
|
+
@cli.command(short_help = "List the objects in a certain bucket/namespace on the HCP.")
|
|
276
363
|
@click.argument("bucket")
|
|
277
364
|
@click.argument("path", required = False)
|
|
278
|
-
@click.option(
|
|
279
|
-
"-no",
|
|
280
|
-
"--name-only",
|
|
281
|
-
help = "Output only the name of the objects instead of all the associated metadata",
|
|
282
|
-
default = False,
|
|
283
|
-
is_flag = True
|
|
284
|
-
)
|
|
285
365
|
@click.option(
|
|
286
366
|
"-p",
|
|
287
367
|
"--pagination",
|
|
@@ -296,8 +376,15 @@ def list_buckets(context : Context):
|
|
|
296
376
|
default = False,
|
|
297
377
|
is_flag = True
|
|
298
378
|
)
|
|
379
|
+
@click.option(
|
|
380
|
+
"-e",
|
|
381
|
+
"--extended-information",
|
|
382
|
+
help = "Output the fully exteded information for each object",
|
|
383
|
+
default = False,
|
|
384
|
+
is_flag = True
|
|
385
|
+
)
|
|
299
386
|
@click.pass_context
|
|
300
|
-
def list_objects(context : Context, bucket : str, path : str,
|
|
387
|
+
def list_objects(context : Context, bucket : str, path : str, pagination : bool, files_only : bool, extended_information : bool):
|
|
301
388
|
"""
|
|
302
389
|
List the objects in a certain bucket/namespace on the HCP.
|
|
303
390
|
|
|
@@ -305,8 +392,23 @@ def list_objects(context : Context, bucket : str, path : str, name_only : bool,
|
|
|
305
392
|
|
|
306
393
|
PATH is an optional argument for where to list the objects
|
|
307
394
|
"""
|
|
308
|
-
hcph : HCPHandler
|
|
395
|
+
def list_objects_generator(hcph : HCPHandler, path : str, files_only : bool, output_mode : HCPHandler.ListObjectsOutputMode) -> Generator[str, Any, None]:
|
|
396
|
+
"""
|
|
397
|
+
Handle object list as a paginator that `click` can handle. It works slightly
|
|
398
|
+
different from `list_objects` in `hcp.py` in order to make the output
|
|
399
|
+
printable in a terminal
|
|
400
|
+
"""
|
|
401
|
+
objects = hcph.list_objects(path, output_mode = output_mode, files_only = files_only)
|
|
402
|
+
for obj in objects:
|
|
403
|
+
yield str(obj) + "\n"
|
|
404
|
+
|
|
405
|
+
hcph : HCPHandler = create_HCPHandler(context)
|
|
309
406
|
hcph.mount_bucket(bucket)
|
|
407
|
+
output_mode = (
|
|
408
|
+
HCPHandler.ListObjectsOutputMode.EXTENDED if extended_information
|
|
409
|
+
else HCPHandler.ListObjectsOutputMode.SIMPLE
|
|
410
|
+
)
|
|
411
|
+
|
|
310
412
|
if path:
|
|
311
413
|
path_with_slash = add_trailing_slash(path)
|
|
312
414
|
|
|
@@ -316,12 +418,25 @@ def list_objects(context : Context, bucket : str, path : str, name_only : bool,
|
|
|
316
418
|
path_with_slash = ""
|
|
317
419
|
|
|
318
420
|
if pagination:
|
|
319
|
-
click.echo_via_pager(
|
|
421
|
+
click.echo_via_pager(
|
|
422
|
+
list_objects_generator(
|
|
423
|
+
hcph,
|
|
424
|
+
path_with_slash,
|
|
425
|
+
files_only,
|
|
426
|
+
output_mode
|
|
427
|
+
)
|
|
428
|
+
)
|
|
320
429
|
else:
|
|
321
|
-
|
|
322
|
-
|
|
430
|
+
lt.stream(
|
|
431
|
+
hcph.list_objects(
|
|
432
|
+
path_with_slash,
|
|
433
|
+
output_mode = output_mode,
|
|
434
|
+
files_only = files_only
|
|
435
|
+
),
|
|
436
|
+
headers = "keys"
|
|
437
|
+
)
|
|
323
438
|
|
|
324
|
-
@cli.command()
|
|
439
|
+
@cli.command(short_help = "Make simple search using substrings in a bucket/namespace on the HCP.")
|
|
325
440
|
@click.argument("bucket")
|
|
326
441
|
@click.argument("search_string")
|
|
327
442
|
@click.option(
|
|
@@ -331,15 +446,8 @@ def list_objects(context : Context, bucket : str, path : str, name_only : bool,
|
|
|
331
446
|
default = False,
|
|
332
447
|
is_flag = True
|
|
333
448
|
)
|
|
334
|
-
@click.option(
|
|
335
|
-
"-v",
|
|
336
|
-
"--verbose",
|
|
337
|
-
help = "Get a verbose output of files. Default value is False, since it might be slower",
|
|
338
|
-
default = False,
|
|
339
|
-
is_flag = True
|
|
340
|
-
)
|
|
341
449
|
@click.pass_context
|
|
342
|
-
def simple_search(context : Context, bucket : str, search_string : str, case_sensitive : bool
|
|
450
|
+
def simple_search(context : Context, bucket : str, search_string : str, case_sensitive : bool):
|
|
343
451
|
"""
|
|
344
452
|
Make simple search using substrings in a bucket/namespace on the HCP.
|
|
345
453
|
|
|
@@ -350,18 +458,19 @@ def simple_search(context : Context, bucket : str, search_string : str, case_sen
|
|
|
350
458
|
|
|
351
459
|
SEARCH_STRING is any string that is to be used for the search.
|
|
352
460
|
"""
|
|
353
|
-
hcph : HCPHandler =
|
|
461
|
+
hcph : HCPHandler = create_HCPHandler(context)
|
|
354
462
|
hcph.mount_bucket(bucket)
|
|
355
463
|
list_of_results = hcph.search_in_bucket(
|
|
356
|
-
search_string,
|
|
357
|
-
name_only = (not verbose),
|
|
464
|
+
search_string,
|
|
358
465
|
case_sensitive = case_sensitive
|
|
359
466
|
)
|
|
360
467
|
click.echo("Search results:")
|
|
361
|
-
|
|
362
|
-
|
|
468
|
+
lt.stream(
|
|
469
|
+
list_of_results,
|
|
470
|
+
headers = "keys"
|
|
471
|
+
)
|
|
363
472
|
|
|
364
|
-
@cli.command()
|
|
473
|
+
@cli.command(short_help = "Make a fuzzy search using a search string in a bucket/namespace on the HCP.")
|
|
365
474
|
@click.argument("bucket")
|
|
366
475
|
@click.argument("search_string")
|
|
367
476
|
@click.option(
|
|
@@ -371,13 +480,6 @@ def simple_search(context : Context, bucket : str, search_string : str, case_sen
|
|
|
371
480
|
default = False,
|
|
372
481
|
is_flag = True
|
|
373
482
|
)
|
|
374
|
-
@click.option(
|
|
375
|
-
"-v",
|
|
376
|
-
"--verbose",
|
|
377
|
-
help = "Get a verbose output of files. Default value is False, since it might be slower",
|
|
378
|
-
default = False,
|
|
379
|
-
is_flag = True
|
|
380
|
-
)
|
|
381
483
|
@click.option(
|
|
382
484
|
"-t",
|
|
383
485
|
"--threshold",
|
|
@@ -385,29 +487,30 @@ def simple_search(context : Context, bucket : str, search_string : str, case_sen
|
|
|
385
487
|
default = 80
|
|
386
488
|
)
|
|
387
489
|
@click.pass_context
|
|
388
|
-
def fuzzy_search(context : Context, bucket : str, search_string : str, case_sensitive : bool,
|
|
490
|
+
def fuzzy_search(context : Context, bucket : str, search_string : str, case_sensitive : bool, threshold : int):
|
|
389
491
|
"""
|
|
390
492
|
Make a fuzzy search using a search string in a bucket/namespace on the HCP.
|
|
391
493
|
|
|
392
494
|
NOTE: This command does not use the HCI. Instead, it uses the RapidFuzz
|
|
393
|
-
library in order to find objects in the HCP. As such, this search might
|
|
394
|
-
|
|
495
|
+
library in order to find objects in the HCP. As such, this search might be
|
|
496
|
+
slow.
|
|
395
497
|
|
|
396
498
|
BUCKET is the name of the bucket in which to make the search.
|
|
397
499
|
|
|
398
500
|
SEARCH_STRING is any string that is to be used for the search.
|
|
399
501
|
"""
|
|
400
|
-
hcph : HCPHandler =
|
|
502
|
+
hcph : HCPHandler = create_HCPHandler(context)
|
|
401
503
|
hcph.mount_bucket(bucket)
|
|
402
504
|
list_of_results = hcph.fuzzy_search_in_bucket(
|
|
403
505
|
search_string,
|
|
404
|
-
name_only = (not verbose),
|
|
405
506
|
case_sensitive = case_sensitive,
|
|
406
507
|
threshold = threshold
|
|
407
508
|
)
|
|
408
509
|
click.echo("Search results:")
|
|
409
|
-
|
|
410
|
-
|
|
510
|
+
lt.stream(
|
|
511
|
+
list_of_results,
|
|
512
|
+
headers = "keys"
|
|
513
|
+
)
|
|
411
514
|
|
|
412
515
|
@cli.command()
|
|
413
516
|
@click.argument("bucket")
|
|
@@ -418,7 +521,7 @@ def test_connection(context : Context, bucket : str):
|
|
|
418
521
|
|
|
419
522
|
BUCKET is the name of the bucket for which a connection test should be made.
|
|
420
523
|
"""
|
|
421
|
-
hcph : HCPHandler =
|
|
524
|
+
hcph : HCPHandler = create_HCPHandler(context)
|
|
422
525
|
click.echo(hcph.test_connection(bucket))
|
|
423
526
|
|
|
424
527
|
@click.command()
|
|
@@ -470,5 +573,3 @@ def iris_generate_credentials_file(path : str, name : str):
|
|
|
470
573
|
|
|
471
574
|
with open(file_path, "w") as f:
|
|
472
575
|
dump(credentials_dict, f, indent = 4)
|
|
473
|
-
|
|
474
|
-
|
|
@@ -20,8 +20,9 @@ from botocore.paginate import PageIterator, Paginator
|
|
|
20
20
|
from botocore.exceptions import EndpointConnectionError, ClientError
|
|
21
21
|
from boto3.s3.transfer import TransferConfig
|
|
22
22
|
from configparser import ConfigParser
|
|
23
|
-
|
|
24
23
|
from pathlib import Path
|
|
24
|
+
from itertools import islice
|
|
25
|
+
from more_itertools import peekable
|
|
25
26
|
|
|
26
27
|
from os import (
|
|
27
28
|
stat,
|
|
@@ -43,18 +44,18 @@ from tqdm import tqdm
|
|
|
43
44
|
from bitmath import TiB, Byte
|
|
44
45
|
|
|
45
46
|
from enum import Enum
|
|
46
|
-
from typing import Generator
|
|
47
|
+
from typing import Any, Generator
|
|
47
48
|
|
|
48
49
|
_KB = 1024
|
|
49
50
|
_MB = _KB * _KB
|
|
50
51
|
|
|
51
52
|
class HCPHandler:
|
|
52
|
-
def __init__(self,
|
|
53
|
+
def __init__(self, credentials : str | dict[str, str], use_ssl : bool = False, proxy_path : str = "", custom_config_path : str = "") -> None:
|
|
53
54
|
"""
|
|
54
55
|
Class for handling HCP requests.
|
|
55
56
|
|
|
56
|
-
:param
|
|
57
|
-
:type
|
|
57
|
+
:param credentials: If `credentials` is a `str`, then it will be interpreted as a path to the JSON credentials file. If `credentials` is a `dict`, then a dictionary with the appropriate HCP credentials is expected: ```{"endpoint" : "", "aws_access_key_id" : "", "aws_secret_access_key" : "" }```
|
|
58
|
+
:type credentials: str | dict
|
|
58
59
|
|
|
59
60
|
:param use_ssl: Boolean choice between using SSL, defaults to False
|
|
60
61
|
:type use_ssl: bool, optional
|
|
@@ -62,9 +63,18 @@ class HCPHandler:
|
|
|
62
63
|
:param custom_config_path: Path to a .ini file for customs settings regarding download and upload
|
|
63
64
|
:type custom_config_path: str, optional
|
|
64
65
|
"""
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
if type(credentials) is str:
|
|
67
|
+
credentials_handler = CredentialsHandler(credentials)
|
|
68
|
+
self.hcp = credentials_handler.hcp
|
|
69
|
+
|
|
70
|
+
self.endpoint = "https://" + self.hcp["endpoint"]
|
|
71
|
+
self.aws_access_key_id = self.hcp["aws_access_key_id"]
|
|
72
|
+
self.aws_secret_access_key = self.hcp["aws_secret_access_key"]
|
|
73
|
+
elif type(credentials) is dict:
|
|
74
|
+
self.endpoint = "https://" + credentials["endpoint"]
|
|
75
|
+
self.aws_access_key_id = credentials["aws_access_key_id"]
|
|
76
|
+
self.aws_secret_access_key = credentials["aws_secret_access_key"]
|
|
77
|
+
|
|
68
78
|
|
|
69
79
|
# A lookup table for GMC names to HCP tenant names
|
|
70
80
|
gmc_tenant_map = {
|
|
@@ -98,8 +108,6 @@ class HCPHandler:
|
|
|
98
108
|
if not self.tenant:
|
|
99
109
|
raise RuntimeError("Unable to parse endpoint, \"" + self.endpoint + "\". Make sure that you have entered the correct endpoint in your credentials JSON file. Hints:\n - The endpoint should *not* contain \"https://\" or port numbers\n - Is the endpoint spelled correctly?")
|
|
100
110
|
self.base_request_url = self.endpoint + ":9090/mapi/tenants/" + self.tenant
|
|
101
|
-
self.aws_access_key_id = self.hcp["aws_access_key_id"]
|
|
102
|
-
self.aws_secret_access_key = self.hcp["aws_secret_access_key"]
|
|
103
111
|
self.token = self.aws_access_key_id + ":" + self.aws_secret_access_key
|
|
104
112
|
self.bucket_name = None
|
|
105
113
|
self.use_ssl = use_ssl
|
|
@@ -204,8 +212,7 @@ class HCPHandler:
|
|
|
204
212
|
try:
|
|
205
213
|
response = dict(self.s3_client.head_bucket(Bucket = bucket_name))
|
|
206
214
|
except EndpointConnectionError as e: # pragma: no cover
|
|
207
|
-
|
|
208
|
-
raise VPNConnectionError("Please check your connection and that you have your VPN enabled")
|
|
215
|
+
raise e
|
|
209
216
|
except ClientError as e:
|
|
210
217
|
status_code = e.response["ResponseMetadata"].get("HTTPStatusCode", -1)
|
|
211
218
|
match status_code:
|
|
@@ -256,26 +263,36 @@ class HCPHandler:
|
|
|
256
263
|
list_of_buckets : list[str] = response["name"]
|
|
257
264
|
return list_of_buckets
|
|
258
265
|
|
|
266
|
+
class ListObjectsOutputMode(Enum):
|
|
267
|
+
SIMPLE = "simple"
|
|
268
|
+
EXTENDED = "extended"
|
|
269
|
+
NAME_ONLY = "name_only"
|
|
270
|
+
|
|
259
271
|
@check_mounted
|
|
260
272
|
def list_objects(
|
|
261
273
|
self,
|
|
262
274
|
path_key : str = "",
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
) -> Generator:
|
|
275
|
+
output_mode : ListObjectsOutputMode = ListObjectsOutputMode.EXTENDED,
|
|
276
|
+
files_only : bool = False,
|
|
277
|
+
list_all_bucket_objects : bool = False
|
|
278
|
+
) -> Generator[dict[str, Any], Any, None]:
|
|
267
279
|
"""
|
|
268
280
|
List all objects in the mounted bucket as a generator. If one wishes to
|
|
269
281
|
get the result as a list, use :py:function:`list` to type cast the generator
|
|
270
282
|
|
|
271
283
|
:param path_key: Filter string for which keys to list, specifically for finding objects in certain folders. Defaults to \"the root\" of the bucket
|
|
272
284
|
:type path_key: str, optional
|
|
273
|
-
:param
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
285
|
+
:param output_mode:
|
|
286
|
+
The upload mode of the transfer is any of the following:\n
|
|
287
|
+
HCPHandler.ListObjectsOutputMode.SIMPLE,\n
|
|
288
|
+
HCPHandler.ListObjectsOutputMode.EXTENDED,\n
|
|
289
|
+
HCPHandler.ListObjectsOutputMode.NAME_ONLY\n
|
|
290
|
+
Default is EXTENDED
|
|
291
|
+
:type output_mode: ListObjectsOutputMode, optional
|
|
277
292
|
:param files_only: If True, only yield file objects. Defaults to False
|
|
278
293
|
:type files_only: bool, optional
|
|
294
|
+
: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
|
|
295
|
+
:type list_all_bucket_objects: bool, optional
|
|
279
296
|
:yield: A generator of all objects in specified folder in a bucket
|
|
280
297
|
:rtype: Generator
|
|
281
298
|
"""
|
|
@@ -291,39 +308,45 @@ class HCPHandler:
|
|
|
291
308
|
if not page:
|
|
292
309
|
break
|
|
293
310
|
|
|
294
|
-
if files_only:
|
|
295
|
-
|
|
296
|
-
file_object : dict
|
|
297
|
-
if file_object["Key"] != path_key:
|
|
298
|
-
file_object["IsFile"] = True
|
|
299
|
-
if name_only:
|
|
300
|
-
yield file_object["Key"]
|
|
301
|
-
else:
|
|
302
|
-
yield file_object
|
|
303
|
-
else:
|
|
311
|
+
if not files_only: # Hide folder objects when flag `files_only` is True
|
|
312
|
+
# Handle folder objects before file objects
|
|
304
313
|
for folder_object in page.get("CommonPrefixes", []):
|
|
305
314
|
folder_object : dict
|
|
306
315
|
folder_object_metadata = self.get_object(folder_object["Prefix"])
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
316
|
+
match output_mode:
|
|
317
|
+
case HCPHandler.ListObjectsOutputMode.EXTENDED:
|
|
318
|
+
yield {
|
|
319
|
+
"Key" : folder_object["Prefix"],
|
|
320
|
+
"LastModified" : folder_object_metadata["LastModified"],
|
|
321
|
+
"ETag" : folder_object_metadata["ETag"],
|
|
322
|
+
"IsFile" : False,
|
|
323
|
+
}
|
|
324
|
+
case HCPHandler.ListObjectsOutputMode.SIMPLE:
|
|
325
|
+
yield {
|
|
326
|
+
"Key" : folder_object["Prefix"],
|
|
327
|
+
"LastModified" : folder_object_metadata["LastModified"],
|
|
328
|
+
"IsFile" : False,
|
|
329
|
+
}
|
|
330
|
+
case HCPHandler.ListObjectsOutputMode.NAME_ONLY:
|
|
331
|
+
yield {"Key" : folder_object["Prefix"]}
|
|
332
|
+
|
|
333
|
+
# Handle file objects
|
|
334
|
+
for file_object in page.get("Contents", []):
|
|
335
|
+
file_object : dict
|
|
336
|
+
if file_object["Key"] != path_key:
|
|
337
|
+
file_object["IsFile"] = True
|
|
338
|
+
match output_mode:
|
|
339
|
+
case HCPHandler.ListObjectsOutputMode.EXTENDED:
|
|
325
340
|
yield file_object
|
|
326
|
-
|
|
341
|
+
case HCPHandler.ListObjectsOutputMode.SIMPLE:
|
|
342
|
+
yield {
|
|
343
|
+
"Key" : file_object["Key"],
|
|
344
|
+
"LastModified" : file_object["LastModified"],
|
|
345
|
+
"Size" : file_object["Size"],
|
|
346
|
+
"IsFile" : file_object["IsFile"]
|
|
347
|
+
}
|
|
348
|
+
case HCPHandler.ListObjectsOutputMode.NAME_ONLY:
|
|
349
|
+
yield {"Key" : file_object["Key"]}
|
|
327
350
|
|
|
328
351
|
@check_mounted
|
|
329
352
|
def get_object(self, key : str) -> dict:
|
|
@@ -406,7 +429,6 @@ class HCPHandler:
|
|
|
406
429
|
Config = self.transfer_config,
|
|
407
430
|
)
|
|
408
431
|
except ClientError as e0:
|
|
409
|
-
print(str(e0))
|
|
410
432
|
raise e0
|
|
411
433
|
except Exception as e: # pragma: no cover
|
|
412
434
|
raise Exception(e)
|
|
@@ -490,10 +512,11 @@ class HCPHandler:
|
|
|
490
512
|
:type show_progress_bar: bool, optional
|
|
491
513
|
|
|
492
514
|
:param upload_mode:
|
|
493
|
-
The upload mode of the transfer is any of the following
|
|
494
|
-
HCPHandler.UploadMode.STANDARD
|
|
495
|
-
HCPHandler.UploadMode.SIMPLE
|
|
515
|
+
The upload mode of the transfer is any of the following:\n
|
|
516
|
+
HCPHandler.UploadMode.STANDARD,\n
|
|
517
|
+
HCPHandler.UploadMode.SIMPLE,\n
|
|
496
518
|
HCPHandler.UploadMode.EQUAL_PARTS\n
|
|
519
|
+
Default is STANDARD
|
|
497
520
|
:type upload_mode: UploadMode, optional
|
|
498
521
|
|
|
499
522
|
:param equal_parts: The number of equal parts that each file should be divided into when using the HCPHandler.UploadMode.EQUAL_PARTS mode. Default is 5
|
|
@@ -587,14 +610,15 @@ class HCPHandler:
|
|
|
587
610
|
)
|
|
588
611
|
|
|
589
612
|
@check_mounted
|
|
590
|
-
def delete_objects(self, keys : list[str]
|
|
591
|
-
"""
|
|
613
|
+
def delete_objects(self, keys : list[str]) -> str:
|
|
614
|
+
"""
|
|
615
|
+
Delete a list of objects on the mounted bucket
|
|
592
616
|
|
|
593
617
|
:param keys: List of object names to be deleted
|
|
594
618
|
:type keys: list[str]
|
|
595
619
|
|
|
596
|
-
:
|
|
597
|
-
:
|
|
620
|
+
:return: The result of the deletion
|
|
621
|
+
:rtype: str
|
|
598
622
|
"""
|
|
599
623
|
object_list = []
|
|
600
624
|
does_not_exist = []
|
|
@@ -604,45 +628,58 @@ class HCPHandler:
|
|
|
604
628
|
else:
|
|
605
629
|
does_not_exist.append(key)
|
|
606
630
|
|
|
631
|
+
result = ""
|
|
607
632
|
if object_list:
|
|
608
633
|
deletion_dict = {"Objects": object_list}
|
|
609
634
|
response : dict = self.s3_client.delete_objects(
|
|
610
635
|
Bucket = self.bucket_name,
|
|
611
636
|
Delete = deletion_dict
|
|
612
637
|
)
|
|
613
|
-
if verbose:
|
|
614
|
-
print(dumps(response, indent=4))
|
|
615
638
|
|
|
616
|
-
|
|
617
|
-
|
|
639
|
+
deleted_files = list(d["Key"] for d in response["Deleted"])
|
|
640
|
+
result += "The following was successfully deleted: \n" + "\n".join(deleted_files)
|
|
641
|
+
|
|
642
|
+
if does_not_exist:
|
|
643
|
+
result += "The following could not be deleted because they didn't exist: \n" + "\n".join(does_not_exist)
|
|
644
|
+
|
|
645
|
+
return result
|
|
618
646
|
|
|
619
647
|
@check_mounted
|
|
620
|
-
def delete_object(self, key : str
|
|
648
|
+
def delete_object(self, key : str) -> str:
|
|
621
649
|
"""
|
|
622
650
|
Delete a single object in the mounted bucket
|
|
623
651
|
|
|
624
652
|
:param key: The object to be deleted
|
|
625
653
|
:type key: str
|
|
626
|
-
|
|
627
|
-
:
|
|
654
|
+
|
|
655
|
+
:return: The result of the deletion
|
|
656
|
+
:rtype: str
|
|
628
657
|
"""
|
|
629
|
-
self.delete_objects([key]
|
|
658
|
+
return self.delete_objects([key])
|
|
630
659
|
|
|
631
660
|
@check_mounted
|
|
632
|
-
def delete_folder(self, key : str
|
|
661
|
+
def delete_folder(self, key : str) -> str:
|
|
633
662
|
"""
|
|
634
663
|
Delete a folder of objects in the mounted bucket. If there are subfolders, a RuntimeError is raised
|
|
635
664
|
|
|
636
665
|
:param key: The folder of objects to be deleted
|
|
637
666
|
:type key: str
|
|
638
|
-
|
|
639
|
-
:type verbose: bool, optional
|
|
667
|
+
|
|
640
668
|
:raises RuntimeError: If there are subfolders, a RuntimeError is raised
|
|
669
|
+
|
|
670
|
+
:return: The result of the deletion
|
|
671
|
+
:rtype: str
|
|
641
672
|
"""
|
|
642
673
|
if key[-1] != "/":
|
|
643
674
|
key += "/"
|
|
644
675
|
|
|
645
|
-
objects : list[str] = list(
|
|
676
|
+
objects : list[str] = list(
|
|
677
|
+
obj["Key"] for obj in
|
|
678
|
+
self.list_objects(
|
|
679
|
+
key,
|
|
680
|
+
output_mode = HCPHandler.ListObjectsOutputMode.NAME_ONLY
|
|
681
|
+
)
|
|
682
|
+
)
|
|
646
683
|
objects.append(key) # Include the object "folder" path to be deleted
|
|
647
684
|
|
|
648
685
|
if not objects:
|
|
@@ -651,13 +688,28 @@ class HCPHandler:
|
|
|
651
688
|
for object_path in objects:
|
|
652
689
|
if (object_path[-1] == "/") and (not object_path == key): # `objects` might contain key, in which case everything is fine
|
|
653
690
|
raise RuntimeError("There are subfolders in this folder. Please remove these first, before deleting this one")
|
|
654
|
-
|
|
691
|
+
|
|
692
|
+
return self.delete_objects(objects)
|
|
693
|
+
|
|
694
|
+
def delete_bucket(self, bucket : str) -> str:
|
|
695
|
+
"""
|
|
696
|
+
Delete a specified bucket
|
|
697
|
+
|
|
698
|
+
:param bucket: The bucket to be deleted
|
|
699
|
+
:type bucket: str
|
|
700
|
+
:return: The result of the deletion
|
|
701
|
+
:rtype: str
|
|
702
|
+
"""
|
|
703
|
+
self.s3_client.delete_bucket(
|
|
704
|
+
Bucket = bucket
|
|
705
|
+
)
|
|
706
|
+
# If the deletion was not successful, `self.s3_client.delete_bucket` would have thrown an error
|
|
707
|
+
return bucket + " was successfully deleted"
|
|
655
708
|
|
|
656
709
|
@check_mounted
|
|
657
710
|
def search_in_bucket(
|
|
658
711
|
self,
|
|
659
712
|
search_string : str,
|
|
660
|
-
name_only : bool = True,
|
|
661
713
|
case_sensitive : bool = False
|
|
662
714
|
) -> Generator:
|
|
663
715
|
"""
|
|
@@ -667,22 +719,18 @@ class HCPHandler:
|
|
|
667
719
|
:param search_string: Substring to be used in the search
|
|
668
720
|
:type search_string: str
|
|
669
721
|
|
|
670
|
-
:param name_only: If True, yield only a the object names. If False, yield the full metadata about each object. Defaults to False.
|
|
671
|
-
:type name_only: bool, optional
|
|
672
|
-
|
|
673
722
|
:param case_sensitive: Case sensitivity. Defaults to False
|
|
674
723
|
:type case_sensitive: bool, optional
|
|
675
724
|
|
|
676
725
|
:return: A generator of objects based on the search string
|
|
677
726
|
:rtype: Generator
|
|
678
727
|
"""
|
|
679
|
-
return self.fuzzy_search_in_bucket(search_string,
|
|
728
|
+
return self.fuzzy_search_in_bucket(search_string, case_sensitive, 100)
|
|
680
729
|
|
|
681
730
|
@check_mounted
|
|
682
731
|
def fuzzy_search_in_bucket(
|
|
683
732
|
self,
|
|
684
733
|
search_string : str,
|
|
685
|
-
name_only : bool = True,
|
|
686
734
|
case_sensitive : bool = False,
|
|
687
735
|
threshold : int = 80
|
|
688
736
|
) -> Generator:
|
|
@@ -710,20 +758,24 @@ class HCPHandler:
|
|
|
710
758
|
else:
|
|
711
759
|
processor = utils.default_process
|
|
712
760
|
|
|
713
|
-
|
|
714
|
-
full_list = list(self.list_objects(list_all_bucket_objects = True))
|
|
761
|
+
full_list = peekable(self.list_objects(list_all_bucket_objects = True))
|
|
715
762
|
|
|
716
|
-
|
|
763
|
+
full_list_names_only = peekable(
|
|
764
|
+
obj["Key"] for obj in
|
|
765
|
+
self.list_objects(
|
|
766
|
+
output_mode = HCPHandler.ListObjectsOutputMode.NAME_ONLY,
|
|
767
|
+
list_all_bucket_objects = True
|
|
768
|
+
)
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
for _, score, index in process.extract_iter(
|
|
717
772
|
search_string,
|
|
718
|
-
|
|
773
|
+
full_list_names_only,
|
|
719
774
|
scorer = fuzz.partial_ratio,
|
|
720
775
|
processor = processor
|
|
721
776
|
):
|
|
722
777
|
if score >= threshold:
|
|
723
|
-
|
|
724
|
-
yield item
|
|
725
|
-
else:
|
|
726
|
-
yield full_list[index] # type: ignore
|
|
778
|
+
yield full_list[index]
|
|
727
779
|
|
|
728
780
|
@check_mounted
|
|
729
781
|
def get_object_acl(self, key : str) -> dict:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: NGPIris
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.5.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
|
|
@@ -12,7 +12,8 @@ Requires-Dist: RapidFuzz>=3.10.1
|
|
|
12
12
|
Requires-Dist: tqdm>=4.66.2
|
|
13
13
|
Requires-Dist: click>=8.1.7
|
|
14
14
|
Requires-Dist: bitmath==1.3.3.1
|
|
15
|
-
Requires-Dist:
|
|
15
|
+
Requires-Dist: lazy_table==0.2.1
|
|
16
|
+
Requires-Dist: more-itertools==10.7.0
|
|
16
17
|
Dynamic: license-file
|
|
17
18
|
|
|
18
19
|
# NGP IRIS 👀
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: NGPIris
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.5.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
|
|
@@ -12,7 +12,8 @@ Requires-Dist: RapidFuzz>=3.10.1
|
|
|
12
12
|
Requires-Dist: tqdm>=4.66.2
|
|
13
13
|
Requires-Dist: click>=8.1.7
|
|
14
14
|
Requires-Dist: bitmath==1.3.3.1
|
|
15
|
-
Requires-Dist:
|
|
15
|
+
Requires-Dist: lazy_table==0.2.1
|
|
16
|
+
Requires-Dist: more-itertools==10.7.0
|
|
16
17
|
Dynamic: license-file
|
|
17
18
|
|
|
18
19
|
# NGP IRIS 👀
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "NGPIris"
|
|
3
|
-
version = "5.
|
|
3
|
+
version = "5.5.0"
|
|
4
4
|
readme = "README.md"
|
|
5
5
|
dependencies = [
|
|
6
6
|
"requests >= 2.31.0",
|
|
@@ -11,7 +11,8 @@ dependencies = [
|
|
|
11
11
|
"tqdm >= 4.66.2",
|
|
12
12
|
"click >= 8.1.7",
|
|
13
13
|
"bitmath == 1.3.3.1",
|
|
14
|
-
"
|
|
14
|
+
"lazy_table == 0.2.1",
|
|
15
|
+
"more-itertools == 10.7.0"
|
|
15
16
|
]
|
|
16
17
|
authors = [
|
|
17
18
|
{name = "Erik Brink", email = "erik.brink@gu.se"},
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
from conftest import CustomConfig
|
|
3
|
+
from random import randint
|
|
4
|
+
|
|
5
|
+
def test_list_index_names_type(custom_config : CustomConfig) -> None:
|
|
6
|
+
custom_config.hci_h.request_token()
|
|
7
|
+
list_of_indexes = custom_config.hci_h.list_index_names()
|
|
8
|
+
assert type(list_of_indexes) is list
|
|
9
|
+
|
|
10
|
+
def test_look_up_all_indexes(custom_config : CustomConfig) -> None:
|
|
11
|
+
custom_config.hci_h.request_token()
|
|
12
|
+
list_of_indexes = custom_config.hci_h.list_index_names()
|
|
13
|
+
for index in list_of_indexes:
|
|
14
|
+
assert custom_config.hci_h.look_up_index(index)
|
|
15
|
+
|
|
16
|
+
def test_fail_index_look_up(custom_config : CustomConfig) -> None:
|
|
17
|
+
custom_config.hci_h.request_token()
|
|
18
|
+
assert not custom_config.hci_h.look_up_index("anIndexThatDoesNotExist")
|
|
19
|
+
|
|
20
|
+
def test_make_simple_raw_query(custom_config : CustomConfig) -> None:
|
|
21
|
+
custom_config.hci_h.request_token()
|
|
22
|
+
list_of_indexes = custom_config.hci_h.list_index_names()
|
|
23
|
+
arbitrary_index = list_of_indexes[randint(0, len(list_of_indexes) - 1)]
|
|
24
|
+
result = custom_config.hci_h.raw_query(
|
|
25
|
+
{
|
|
26
|
+
"indexName" : arbitrary_index
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
assert result["indexName"] == arbitrary_index
|
|
30
|
+
|
|
31
|
+
def test_fail_raw_query(custom_config : CustomConfig) -> None:
|
|
32
|
+
custom_config.hci_h.request_token()
|
|
33
|
+
query = {}
|
|
34
|
+
try:
|
|
35
|
+
custom_config.hci_h.raw_query(query)
|
|
36
|
+
except:
|
|
37
|
+
assert True
|
|
38
|
+
else: # pragma: no cover
|
|
39
|
+
assert False
|
|
40
|
+
|
|
41
|
+
def test_make_query(custom_config : CustomConfig) -> None:
|
|
42
|
+
custom_config.hci_h.request_token()
|
|
43
|
+
custom_config.hci_h.query(
|
|
44
|
+
custom_config.test_index
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def test_make_query_with_query_string(custom_config : CustomConfig) -> None:
|
|
48
|
+
custom_config.hci_h.request_token()
|
|
49
|
+
custom_config.hci_h.query(
|
|
50
|
+
custom_config.test_index,
|
|
51
|
+
custom_config.query_string
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def test_make_query_with_query_string_and_facet(custom_config : CustomConfig) -> None:
|
|
55
|
+
custom_config.hci_h.request_token()
|
|
56
|
+
if custom_config.test_facet:
|
|
57
|
+
custom_config.hci_h.query(
|
|
58
|
+
custom_config.test_index,
|
|
59
|
+
custom_config.query_string,
|
|
60
|
+
[custom_config.test_facet]
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
custom_config.hci_h.query(
|
|
64
|
+
custom_config.test_index,
|
|
65
|
+
custom_config.query_string,
|
|
66
|
+
[]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def test_make_query_with_facet(custom_config : CustomConfig) -> None:
|
|
70
|
+
custom_config.hci_h.request_token()
|
|
71
|
+
if custom_config.test_facet:
|
|
72
|
+
custom_config.hci_h.query(
|
|
73
|
+
custom_config.test_index,
|
|
74
|
+
facets = [custom_config.test_facet]
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
custom_config.hci_h.query(
|
|
78
|
+
custom_config.test_index,
|
|
79
|
+
facets = []
|
|
80
|
+
)
|
ngpiris-5.4.4/tests/test_hci.py
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
from configparser import ConfigParser
|
|
3
|
-
from NGPIris.hci import HCIHandler
|
|
4
|
-
from random import randint
|
|
5
|
-
from json import dump
|
|
6
|
-
from os import remove
|
|
7
|
-
|
|
8
|
-
ini_config = ConfigParser()
|
|
9
|
-
ini_config.read("tests/test_conf.ini")
|
|
10
|
-
|
|
11
|
-
hci_h = HCIHandler(ini_config.get("General", "credentials_path"))
|
|
12
|
-
hci_h.request_token()
|
|
13
|
-
|
|
14
|
-
def test_list_index_names_type() -> None:
|
|
15
|
-
list_of_indexes = hci_h.list_index_names()
|
|
16
|
-
assert type(list_of_indexes) is list
|
|
17
|
-
|
|
18
|
-
def test_look_up_all_indexes() -> None:
|
|
19
|
-
list_of_indexes = hci_h.list_index_names()
|
|
20
|
-
for index in list_of_indexes:
|
|
21
|
-
assert hci_h.look_up_index(index)
|
|
22
|
-
|
|
23
|
-
def test_fail_index_look_up() -> None:
|
|
24
|
-
assert not hci_h.look_up_index("anIndexThatDoesNotExist")
|
|
25
|
-
|
|
26
|
-
def test_make_simple_raw_query() -> None:
|
|
27
|
-
list_of_indexes = hci_h.list_index_names()
|
|
28
|
-
arbitrary_index = list_of_indexes[randint(0, len(list_of_indexes) - 1)]
|
|
29
|
-
result = hci_h.raw_query(
|
|
30
|
-
{
|
|
31
|
-
"indexName" : arbitrary_index
|
|
32
|
-
}
|
|
33
|
-
)
|
|
34
|
-
assert result["indexName"] == arbitrary_index
|
|
35
|
-
|
|
36
|
-
def test_raw_query_with_results() -> None:
|
|
37
|
-
query = {
|
|
38
|
-
"indexName" : "Parsed_VCF_Index",
|
|
39
|
-
"facetRequests" : [
|
|
40
|
-
{
|
|
41
|
-
"fieldName" : "ref"
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"fieldName" : "alt"
|
|
45
|
-
}
|
|
46
|
-
]
|
|
47
|
-
}
|
|
48
|
-
parsed_VCF_index_result : list = hci_h.raw_query(query)["facets"]
|
|
49
|
-
for i in range(len(parsed_VCF_index_result)):
|
|
50
|
-
assert parsed_VCF_index_result[i]["termCounts"] # Assert if the result is not empty
|
|
51
|
-
|
|
52
|
-
def test_fail_raw_query() -> None:
|
|
53
|
-
query = {}
|
|
54
|
-
try:
|
|
55
|
-
hci_h.raw_query(query)
|
|
56
|
-
except:
|
|
57
|
-
assert True
|
|
58
|
-
else: # pragma: no cover
|
|
59
|
-
assert False
|
|
60
|
-
|
|
61
|
-
def test_make_simple_raw_query_from_JSON() -> None:
|
|
62
|
-
list_of_indexes = hci_h.list_index_names()
|
|
63
|
-
arbitrary_index = list_of_indexes[randint(0, len(list_of_indexes) - 1)]
|
|
64
|
-
path = "tests/data/json_test_query.json"
|
|
65
|
-
with open(path, "w") as f:
|
|
66
|
-
query = {
|
|
67
|
-
"indexName": arbitrary_index
|
|
68
|
-
}
|
|
69
|
-
dump(query, f, indent = 4)
|
|
70
|
-
result = hci_h.raw_query_from_JSON(path)
|
|
71
|
-
assert result["indexName"] == arbitrary_index
|
|
72
|
-
remove(path)
|
|
73
|
-
|
|
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
|