divbase-cli 0.1.0.dev2__py3-none-any.whl → 0.1.0.dev4__py3-none-any.whl
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.
- divbase_cli/__init__.py +1 -1
- divbase_cli/cli_commands/auth_cli.py +16 -10
- divbase_cli/cli_commands/dimensions_cli.py +322 -26
- divbase_cli/cli_commands/file_cli.py +488 -101
- divbase_cli/cli_commands/query_cli.py +14 -9
- divbase_cli/cli_commands/shared_args_options.py +23 -0
- divbase_cli/cli_commands/task_history_cli.py +3 -8
- divbase_cli/cli_commands/user_config_cli.py +14 -44
- divbase_cli/cli_commands/version_cli.py +98 -37
- divbase_cli/cli_config.py +27 -9
- divbase_cli/cli_exceptions.py +37 -22
- divbase_cli/config_resolver.py +10 -10
- divbase_cli/divbase_cli.py +1 -1
- divbase_cli/retries.py +34 -0
- divbase_cli/services/__init__.py +0 -0
- divbase_cli/services/announcements.py +46 -0
- divbase_cli/services/pre_signed_urls.py +456 -0
- divbase_cli/services/project_versions.py +103 -0
- divbase_cli/services/s3_files.py +442 -0
- divbase_cli/user_auth.py +104 -46
- divbase_cli/user_config.py +20 -9
- divbase_cli/utils.py +28 -0
- {divbase_cli-0.1.0.dev2.dist-info → divbase_cli-0.1.0.dev4.dist-info}/METADATA +3 -4
- divbase_cli-0.1.0.dev4.dist-info/RECORD +28 -0
- {divbase_cli-0.1.0.dev2.dist-info → divbase_cli-0.1.0.dev4.dist-info}/WHEEL +1 -1
- divbase_cli/pre_signed_urls.py +0 -169
- divbase_cli/services.py +0 -219
- divbase_cli-0.1.0.dev2.dist-info/RECORD +0 -22
- {divbase_cli-0.1.0.dev2.dist-info → divbase_cli-0.1.0.dev4.dist-info}/entry_points.txt +0 -0
|
@@ -17,13 +17,11 @@ TODO:
|
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
import logging
|
|
20
|
-
from pathlib import Path
|
|
21
20
|
|
|
22
21
|
import typer
|
|
23
22
|
from rich import print
|
|
24
23
|
|
|
25
|
-
from divbase_cli.cli_commands.
|
|
26
|
-
from divbase_cli.cli_commands.version_cli import PROJECT_NAME_OPTION
|
|
24
|
+
from divbase_cli.cli_commands.shared_args_options import PROJECT_NAME_OPTION
|
|
27
25
|
from divbase_cli.cli_config import cli_settings
|
|
28
26
|
from divbase_cli.config_resolver import resolve_project
|
|
29
27
|
from divbase_cli.user_auth import make_authenticated_request
|
|
@@ -75,7 +73,6 @@ def sample_metadata_query(
|
|
|
75
73
|
),
|
|
76
74
|
metadata_tsv_name: str = METADATA_TSV_ARGUMENT,
|
|
77
75
|
project: str | None = PROJECT_NAME_OPTION,
|
|
78
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
79
76
|
) -> None:
|
|
80
77
|
"""
|
|
81
78
|
Query the tsv sidecar metadata file for the VCF files in the project's data store on DivBase.
|
|
@@ -86,7 +83,7 @@ def sample_metadata_query(
|
|
|
86
83
|
TODO: handle when the name of the sample column is something other than Sample_ID
|
|
87
84
|
"""
|
|
88
85
|
|
|
89
|
-
project_config = resolve_project(project_name=project
|
|
86
|
+
project_config = resolve_project(project_name=project)
|
|
90
87
|
|
|
91
88
|
request_data = SampleMetadataQueryRequest(tsv_filter=filter, metadata_tsv_name=metadata_tsv_name)
|
|
92
89
|
|
|
@@ -100,14 +97,23 @@ def sample_metadata_query(
|
|
|
100
97
|
|
|
101
98
|
results = SampleMetadataQueryTaskResult(**response.json())
|
|
102
99
|
|
|
100
|
+
if results.warnings:
|
|
101
|
+
print("[yellow]Warnings:[/yellow]")
|
|
102
|
+
for warning in results.warnings:
|
|
103
|
+
print(f" • {warning}")
|
|
104
|
+
print()
|
|
105
|
+
|
|
103
106
|
if show_sample_results:
|
|
104
107
|
print("[bright_blue]Name and file for each sample in query results:[/bright_blue]")
|
|
105
108
|
for sample in results.sample_and_filename_subset:
|
|
106
109
|
print(f"Sample ID: '{sample['Sample_ID']}', Filename: '{sample['Filename']}'")
|
|
107
110
|
|
|
108
111
|
print(f"The results for the query ([bright_blue]{results.query_message}[/bright_blue]):")
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
|
|
113
|
+
unique_sample_ids = results.unique_sample_ids or []
|
|
114
|
+
unique_filenames = results.unique_filenames or []
|
|
115
|
+
print(f"Unique Sample IDs: {unique_sample_ids}")
|
|
116
|
+
print(f"Unique filenames: {unique_filenames}\n")
|
|
111
117
|
|
|
112
118
|
|
|
113
119
|
@query_app.command("bcftools-pipe")
|
|
@@ -116,7 +122,6 @@ def pipe_query(
|
|
|
116
122
|
command: str = BCFTOOLS_ARGUMENT,
|
|
117
123
|
metadata_tsv_name: str = METADATA_TSV_ARGUMENT,
|
|
118
124
|
project: str | None = PROJECT_NAME_OPTION,
|
|
119
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
120
125
|
) -> None:
|
|
121
126
|
"""
|
|
122
127
|
Submit a query to run on the DivBase API. A single, merged VCF file will be added to the project on success.
|
|
@@ -129,7 +134,7 @@ def pipe_query(
|
|
|
129
134
|
TODO consider handling the bcftools command whitelist checks also on the CLI level since the error messages are nicer looking?
|
|
130
135
|
TODO consider moving downloading of missing files elsewhere, since this is now done before the celery task
|
|
131
136
|
"""
|
|
132
|
-
project_config = resolve_project(project_name=project
|
|
137
|
+
project_config = resolve_project(project_name=project)
|
|
133
138
|
|
|
134
139
|
request_data = BcftoolsQueryRequest(tsv_filter=tsv_filter, command=command, metadata_tsv_name=metadata_tsv_name)
|
|
135
140
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
To avoid potential problems with circular imports, we can put shared typer args + options (etc...) here
|
|
3
|
+
|
|
4
|
+
If you have something that is only used in one of the cli subcommands, don't move it here.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
PROJECT_NAME_OPTION = typer.Option(
|
|
10
|
+
None,
|
|
11
|
+
"--project",
|
|
12
|
+
"-p",
|
|
13
|
+
help="Name of the DivBase project, if not provided uses the default in your DivBase config file",
|
|
14
|
+
show_default=False,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
FORMAT_AS_TSV_OPTION = typer.Option(
|
|
19
|
+
False,
|
|
20
|
+
"--tsv",
|
|
21
|
+
"-t",
|
|
22
|
+
help="If set, will print the output in .TSV format for easier programmatic parsing.",
|
|
23
|
+
)
|
|
@@ -6,11 +6,9 @@ Submits a query for fetching the Celery task history for the user to the DivBase
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
-
from pathlib import Path
|
|
10
9
|
|
|
11
10
|
import typer
|
|
12
11
|
|
|
13
|
-
from divbase_cli.cli_commands.user_config_cli import CONFIG_FILE_OPTION
|
|
14
12
|
from divbase_cli.cli_exceptions import AuthenticationError
|
|
15
13
|
from divbase_cli.display_task_history import TaskHistoryDisplayManager
|
|
16
14
|
from divbase_cli.user_auth import make_authenticated_request
|
|
@@ -28,7 +26,6 @@ task_history_app = typer.Typer(
|
|
|
28
26
|
|
|
29
27
|
@task_history_app.command("user")
|
|
30
28
|
def list_task_history_for_user(
|
|
31
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
32
29
|
limit: int = typer.Option(10, help="Maximum number of tasks to display in the terminal. Sorted by recency."),
|
|
33
30
|
project: str | None = typer.Option(
|
|
34
31
|
None, help="Optional project name to filter the user's task history by project."
|
|
@@ -40,7 +37,7 @@ def list_task_history_for_user(
|
|
|
40
37
|
|
|
41
38
|
# TODO add option to sort ASC/DESC by task timestamp
|
|
42
39
|
|
|
43
|
-
config = load_user_config(
|
|
40
|
+
config = load_user_config()
|
|
44
41
|
logged_in_url = config.logged_in_url
|
|
45
42
|
|
|
46
43
|
if not logged_in_url:
|
|
@@ -73,13 +70,12 @@ def list_task_history_for_user(
|
|
|
73
70
|
@task_history_app.command("id")
|
|
74
71
|
def task_history_by_id(
|
|
75
72
|
task_id: int | None = typer.Argument(..., help="Task ID to check the status of a specific query job."),
|
|
76
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
77
73
|
):
|
|
78
74
|
"""
|
|
79
75
|
Check status of a specific task submitted by the user by its task ID.
|
|
80
76
|
"""
|
|
81
77
|
|
|
82
|
-
config = load_user_config(
|
|
78
|
+
config = load_user_config()
|
|
83
79
|
logged_in_url = config.logged_in_url
|
|
84
80
|
|
|
85
81
|
if not logged_in_url:
|
|
@@ -104,7 +100,6 @@ def task_history_by_id(
|
|
|
104
100
|
|
|
105
101
|
@task_history_app.command("project")
|
|
106
102
|
def list_task_history_for_project(
|
|
107
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
108
103
|
limit: int = typer.Option(10, help="Maximum number of tasks to display in the terminal. Sorted by recency."),
|
|
109
104
|
project: str = typer.Argument(..., help="Project name to check the task history for."),
|
|
110
105
|
):
|
|
@@ -115,7 +110,7 @@ def list_task_history_for_project(
|
|
|
115
110
|
# TODO add option to sort ASC/DESC by task timestamp
|
|
116
111
|
# TODO use default project from config if not --project specified
|
|
117
112
|
|
|
118
|
-
config = load_user_config(
|
|
113
|
+
config = load_user_config()
|
|
119
114
|
logged_in_url = config.logged_in_url
|
|
120
115
|
|
|
121
116
|
if not logged_in_url:
|
|
@@ -4,8 +4,6 @@ config subcommand for the divbase-cli package.
|
|
|
4
4
|
Controls the user config file stored at "~/.config/.divbase_tools.yaml" (unless specified otherwise).
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
7
|
import typer
|
|
10
8
|
from rich import print
|
|
11
9
|
from rich.console import Console
|
|
@@ -13,34 +11,12 @@ from rich.table import Table
|
|
|
13
11
|
|
|
14
12
|
from divbase_cli.cli_config import cli_settings
|
|
15
13
|
from divbase_cli.user_config import (
|
|
16
|
-
create_user_config,
|
|
17
14
|
load_user_config,
|
|
18
15
|
)
|
|
19
16
|
|
|
20
|
-
CONFIG_FILE_OPTION = typer.Option(
|
|
21
|
-
cli_settings.CONFIG_PATH,
|
|
22
|
-
"--config",
|
|
23
|
-
"-c",
|
|
24
|
-
help="Path to your user configuration file. If you didn't specify a custom path when you created it, you don't need to set this.",
|
|
25
|
-
)
|
|
26
|
-
|
|
27
17
|
config_app = typer.Typer(help="Manage your user configuration file for the DivBase CLI.", no_args_is_help=True)
|
|
28
18
|
|
|
29
19
|
|
|
30
|
-
@config_app.command("create")
|
|
31
|
-
def create_user_config_command(
|
|
32
|
-
config_file: Path = typer.Option(
|
|
33
|
-
cli_settings.CONFIG_PATH,
|
|
34
|
-
"--config",
|
|
35
|
-
"-c",
|
|
36
|
-
help="Where to store your config file locally on your pc.",
|
|
37
|
-
),
|
|
38
|
-
):
|
|
39
|
-
"""Create a user configuration file for divbase-cli."""
|
|
40
|
-
create_user_config(config_path=config_file)
|
|
41
|
-
print(f"User configuration file created at {config_file.resolve()}.")
|
|
42
|
-
|
|
43
|
-
|
|
44
20
|
@config_app.command("add")
|
|
45
21
|
def add_project_command(
|
|
46
22
|
name: str = typer.Argument(..., help="Name of the project to add to your config file."),
|
|
@@ -56,17 +32,16 @@ def add_project_command(
|
|
|
56
32
|
"-d",
|
|
57
33
|
help="Set this project as the default project in your config file.",
|
|
58
34
|
),
|
|
59
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
60
35
|
):
|
|
61
36
|
"""Add a new project to your user configuration file."""
|
|
62
|
-
config = load_user_config(
|
|
37
|
+
config = load_user_config()
|
|
63
38
|
project = config.add_project(
|
|
64
39
|
name=name,
|
|
65
40
|
divbase_url=divbase_url,
|
|
66
41
|
is_default=make_default,
|
|
67
42
|
)
|
|
68
43
|
|
|
69
|
-
print(f"Project: '{project.name}' added to your config
|
|
44
|
+
print(f"Project: '{project.name}' added to your config.")
|
|
70
45
|
print(f"The URL: {project.divbase_url} was set as the DivBase API URL for this project.")
|
|
71
46
|
|
|
72
47
|
if make_default:
|
|
@@ -75,17 +50,16 @@ def add_project_command(
|
|
|
75
50
|
print(f"To make '{project.name}' your default project you can run: 'divbase config set-default {project.name}'")
|
|
76
51
|
|
|
77
52
|
|
|
78
|
-
@config_app.command("
|
|
53
|
+
@config_app.command("rm")
|
|
79
54
|
def remove_project_command(
|
|
80
55
|
name: str = typer.Argument(..., help="Name of the project to remove from your user configuration file."),
|
|
81
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
82
56
|
):
|
|
83
57
|
"""Remove a project from your user configuration file."""
|
|
84
|
-
config = load_user_config(
|
|
58
|
+
config = load_user_config()
|
|
85
59
|
removed_project = config.remove_project(name)
|
|
86
60
|
|
|
87
61
|
if not removed_project:
|
|
88
|
-
print(f"
|
|
62
|
+
print(f"Nothing to do, the project '{name}' was not found in your user config")
|
|
89
63
|
else:
|
|
90
64
|
print(f"The project '{removed_project}' was removed from your config.")
|
|
91
65
|
|
|
@@ -93,20 +67,17 @@ def remove_project_command(
|
|
|
93
67
|
@config_app.command("set-default")
|
|
94
68
|
def set_default_project_command(
|
|
95
69
|
name: str = typer.Argument(..., help="Name of the project to add to the user configuration file."),
|
|
96
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
97
70
|
):
|
|
98
71
|
"""Set your default project to use in all divbase-cli commands."""
|
|
99
|
-
config = load_user_config(
|
|
72
|
+
config = load_user_config()
|
|
100
73
|
default_project_name = config.set_default_project(name=name)
|
|
101
74
|
print(f"Default project is now set to '{default_project_name}'.")
|
|
102
75
|
|
|
103
76
|
|
|
104
77
|
@config_app.command("show-default")
|
|
105
|
-
def show_default_project_command(
|
|
106
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
107
|
-
) -> None:
|
|
78
|
+
def show_default_project_command() -> None:
|
|
108
79
|
"""Print the currently set default project to the console."""
|
|
109
|
-
config = load_user_config(
|
|
80
|
+
config = load_user_config()
|
|
110
81
|
|
|
111
82
|
if config.default_project:
|
|
112
83
|
print(config.default_project)
|
|
@@ -123,10 +94,9 @@ def set_default_dload_dir_command(
|
|
|
123
94
|
You can specify an absolute path.
|
|
124
95
|
You can use '.' to refer to the directory you run the command from.""",
|
|
125
96
|
),
|
|
126
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
127
97
|
):
|
|
128
98
|
"""Set the default download dir"""
|
|
129
|
-
config = load_user_config(
|
|
99
|
+
config = load_user_config()
|
|
130
100
|
dload_dir = config.set_default_download_dir(download_dir=download_dir)
|
|
131
101
|
if dload_dir == ".":
|
|
132
102
|
print("The default download directory will be whereever you run the command from.")
|
|
@@ -135,14 +105,14 @@ def set_default_dload_dir_command(
|
|
|
135
105
|
|
|
136
106
|
|
|
137
107
|
@config_app.command("show")
|
|
138
|
-
def show_user_config(
|
|
139
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
140
|
-
):
|
|
108
|
+
def show_user_config():
|
|
141
109
|
"""Pretty print the contents of your current config file."""
|
|
142
|
-
config = load_user_config(
|
|
110
|
+
config = load_user_config()
|
|
143
111
|
console = Console()
|
|
144
112
|
|
|
145
|
-
console.print(
|
|
113
|
+
console.print(
|
|
114
|
+
f"[bold]Your DivBase user configuration file's contents located at:[/bold] '{cli_settings.CONFIG_PATH.resolve()}'\n"
|
|
115
|
+
)
|
|
146
116
|
|
|
147
117
|
if not config.default_download_dir:
|
|
148
118
|
dload_dir_info = "Not specified, meaning the working directory of wherever you run the download command from."
|
|
@@ -1,28 +1,23 @@
|
|
|
1
1
|
"""CLI commands for managing project versions in DivBase."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from pathlib import Path
|
|
5
4
|
from zoneinfo import ZoneInfo
|
|
6
5
|
|
|
7
6
|
import typer
|
|
8
7
|
from rich import print
|
|
9
|
-
from rich.console import Console
|
|
10
8
|
from rich.table import Table
|
|
11
9
|
|
|
12
|
-
from divbase_cli.cli_commands.
|
|
10
|
+
from divbase_cli.cli_commands.shared_args_options import FORMAT_AS_TSV_OPTION, PROJECT_NAME_OPTION
|
|
13
11
|
from divbase_cli.config_resolver import ensure_logged_in, resolve_project
|
|
14
|
-
from divbase_cli.services import (
|
|
12
|
+
from divbase_cli.services.project_versions import (
|
|
15
13
|
add_version_command,
|
|
16
14
|
delete_version_command,
|
|
17
15
|
get_version_details_command,
|
|
18
16
|
list_versions_command,
|
|
17
|
+
update_version_command,
|
|
19
18
|
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
None,
|
|
23
|
-
help="Name of the DivBase project, if not provided uses the default in your DivBase config file",
|
|
24
|
-
show_default=False,
|
|
25
|
-
)
|
|
19
|
+
from divbase_cli.utils import print_rich_table_as_tsv
|
|
20
|
+
from divbase_lib.utils import format_file_size
|
|
26
21
|
|
|
27
22
|
version_app = typer.Typer(
|
|
28
23
|
no_args_is_help=True,
|
|
@@ -42,11 +37,10 @@ def add_version(
|
|
|
42
37
|
name: str = typer.Argument(help="Name of the version (e.g., semantic version).", show_default=False),
|
|
43
38
|
description: str = typer.Option("", help="Optional description of the version."),
|
|
44
39
|
project: str | None = PROJECT_NAME_OPTION,
|
|
45
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
46
40
|
):
|
|
47
41
|
"""Add a new project version entry which specifies the current state of all files in the project at the current timestamp."""
|
|
48
|
-
project_config = resolve_project(project_name=project
|
|
49
|
-
logged_in_url = ensure_logged_in(
|
|
42
|
+
project_config = resolve_project(project_name=project)
|
|
43
|
+
logged_in_url = ensure_logged_in(desired_url=project_config.divbase_url)
|
|
50
44
|
|
|
51
45
|
add_version_response = add_version_command(
|
|
52
46
|
name=name,
|
|
@@ -57,11 +51,58 @@ def add_version(
|
|
|
57
51
|
print(f"New version: '{add_version_response.name}' added to the project: '{project_config.name}'")
|
|
58
52
|
|
|
59
53
|
|
|
60
|
-
@version_app.command("
|
|
54
|
+
@version_app.command("update")
|
|
55
|
+
def update_version(
|
|
56
|
+
version_name: str = typer.Argument(help="Name of the existing version you want to modify", show_default=False),
|
|
57
|
+
new_name: str | None = typer.Option(
|
|
58
|
+
None, "--new-name", "-n", help="New name for the version. If not specified, the name will remain unchanged."
|
|
59
|
+
),
|
|
60
|
+
new_description: str | None = typer.Option(
|
|
61
|
+
None,
|
|
62
|
+
"--new-description",
|
|
63
|
+
"-d",
|
|
64
|
+
help="Optional new description of the version. If not specified, the description will remain unchanged.",
|
|
65
|
+
),
|
|
66
|
+
project: str | None = PROJECT_NAME_OPTION,
|
|
67
|
+
):
|
|
68
|
+
"""
|
|
69
|
+
Update an existing project version entry's name and/or description.
|
|
70
|
+
|
|
71
|
+
The files and timestamp associated with a version entry are immutable and cannot be changed.
|
|
72
|
+
This is by design to ensure version entries are representations of the project's state at the timepoint they were created at.
|
|
73
|
+
You can create a new version entry to capture the current state of the project instead.
|
|
74
|
+
|
|
75
|
+
To see your current version entries run: `divbase-cli version ls`
|
|
76
|
+
"""
|
|
77
|
+
if not new_name and not new_description:
|
|
78
|
+
print(
|
|
79
|
+
"No updates specified. Please provide a new name (--new-name) and/or description (--new-description) to update for this entry."
|
|
80
|
+
)
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
project_config = resolve_project(project_name=project)
|
|
84
|
+
logged_in_url = ensure_logged_in(desired_url=project_config.divbase_url)
|
|
85
|
+
|
|
86
|
+
updated_version = update_version_command(
|
|
87
|
+
project_name=project_config.name,
|
|
88
|
+
divbase_base_url=logged_in_url,
|
|
89
|
+
version_name=version_name,
|
|
90
|
+
new_name=new_name,
|
|
91
|
+
new_description=new_description,
|
|
92
|
+
)
|
|
93
|
+
if new_name and new_name != version_name:
|
|
94
|
+
print(f"Version '{version_name}' renamed to '{updated_version.name}' in project: '{project_config.name}'")
|
|
95
|
+
else:
|
|
96
|
+
print(f"Version '{updated_version.name}' updated in project: '{project_config.name}'")
|
|
97
|
+
if new_description:
|
|
98
|
+
print(f"New description: '{updated_version.description}'")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@version_app.command("ls")
|
|
61
102
|
def list_versions(
|
|
62
103
|
project: str | None = PROJECT_NAME_OPTION,
|
|
63
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
64
104
|
include_deleted: bool = typer.Option(False, help="Include soft-deleted versions in the listing."),
|
|
105
|
+
format_output_as_tsv: bool = FORMAT_AS_TSV_OPTION,
|
|
65
106
|
):
|
|
66
107
|
"""
|
|
67
108
|
List all entries in the project versioning file.
|
|
@@ -70,8 +111,8 @@ def list_versions(
|
|
|
70
111
|
If you specify --include-deleted, soft-deleted versions will also be shown.
|
|
71
112
|
Soft-deleted versions can be restored by a DivBase admin within 30 days of deletion.
|
|
72
113
|
"""
|
|
73
|
-
project_config = resolve_project(project_name=project
|
|
74
|
-
logged_in_url = ensure_logged_in(
|
|
114
|
+
project_config = resolve_project(project_name=project)
|
|
115
|
+
logged_in_url = ensure_logged_in(desired_url=project_config.divbase_url)
|
|
75
116
|
|
|
76
117
|
versions_info = list_versions_command(
|
|
77
118
|
project_name=project_config.name, include_deleted=include_deleted, divbase_base_url=logged_in_url
|
|
@@ -81,7 +122,6 @@ def list_versions(
|
|
|
81
122
|
print(f"No versions found for project: {project_config.name}.")
|
|
82
123
|
return
|
|
83
124
|
|
|
84
|
-
console = Console()
|
|
85
125
|
table = Table(title=f"Versions for {project_config.name}")
|
|
86
126
|
table.add_column("Version", style="cyan", no_wrap=True)
|
|
87
127
|
table.add_column("Created ", style="magenta")
|
|
@@ -99,49 +139,70 @@ def list_versions(
|
|
|
99
139
|
else:
|
|
100
140
|
table.add_row(name, created_at, desc)
|
|
101
141
|
|
|
102
|
-
|
|
142
|
+
if not format_output_as_tsv:
|
|
143
|
+
print(table)
|
|
144
|
+
else:
|
|
145
|
+
print_rich_table_as_tsv(table=table)
|
|
103
146
|
|
|
104
147
|
|
|
105
148
|
@version_app.command("info")
|
|
106
149
|
def get_version_info(
|
|
107
150
|
version: str = typer.Argument(help="Specific version to retrieve information for"),
|
|
108
151
|
project: str | None = PROJECT_NAME_OPTION,
|
|
109
|
-
|
|
152
|
+
format_output_as_tsv: bool = FORMAT_AS_TSV_OPTION,
|
|
110
153
|
):
|
|
111
|
-
"""
|
|
112
|
-
|
|
113
|
-
|
|
154
|
+
"""
|
|
155
|
+
Provide detailed information about a user specified project version, including all files present and their unique hashes.
|
|
156
|
+
"""
|
|
157
|
+
project_config = resolve_project(project_name=project)
|
|
158
|
+
logged_in_url = ensure_logged_in(desired_url=project_config.divbase_url)
|
|
114
159
|
|
|
115
160
|
version_details = get_version_details_command(
|
|
116
161
|
project_name=project_config.name, divbase_base_url=logged_in_url, version_name=version
|
|
117
162
|
)
|
|
118
163
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
164
|
+
table = Table(title=f"Project version files for {project_config.name}")
|
|
165
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
|
166
|
+
table.add_column("Version ID", style="magenta")
|
|
167
|
+
table.add_column("MD5 Checksum", style="green")
|
|
168
|
+
table.add_column("Size", style="yellow")
|
|
169
|
+
for object_name, file_details in version_details.files.items():
|
|
170
|
+
file_size = format_file_size(file_details["size"])
|
|
171
|
+
|
|
172
|
+
table.add_row(
|
|
173
|
+
object_name,
|
|
174
|
+
file_details["version_id"],
|
|
175
|
+
file_details["etag"],
|
|
176
|
+
file_size,
|
|
126
177
|
)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
print(f"
|
|
178
|
+
|
|
179
|
+
if not format_output_as_tsv:
|
|
180
|
+
print(f"Project version entry for project: '{project_config.name}' with name: '{version_details.name}'")
|
|
181
|
+
print(f"Entry created at: {format_timestamp(version_details.created_at)}")
|
|
182
|
+
if version_details.description:
|
|
183
|
+
print(f"Description: {version_details.description}")
|
|
184
|
+
if version_details.is_deleted:
|
|
185
|
+
print(
|
|
186
|
+
"[red]WARNING: This version has been soft-deleted and will soon be permanently deleted unless restored - Contact a DivBase admin to prevent this.[/red]"
|
|
187
|
+
)
|
|
188
|
+
print(table)
|
|
189
|
+
else:
|
|
190
|
+
print_rich_table_as_tsv(table=table)
|
|
130
191
|
|
|
131
192
|
|
|
132
|
-
@version_app.command("
|
|
193
|
+
@version_app.command("rm")
|
|
133
194
|
def delete_version(
|
|
134
195
|
name: str = typer.Argument(help="Name of the version (e.g., semantic version).", show_default=False),
|
|
135
196
|
project: str | None = PROJECT_NAME_OPTION,
|
|
136
|
-
config_file: Path = CONFIG_FILE_OPTION,
|
|
137
197
|
):
|
|
138
198
|
"""
|
|
139
199
|
Delete a version entry in the project versioning table. This does not delete the files themselves.
|
|
200
|
+
|
|
140
201
|
Deleted version entries older than 30 days will be permanently deleted.
|
|
141
202
|
You can ask a DivBase admin to restore a deleted version within that time period.
|
|
142
203
|
"""
|
|
143
|
-
project_config = resolve_project(project_name=project
|
|
144
|
-
logged_in_url = ensure_logged_in(
|
|
204
|
+
project_config = resolve_project(project_name=project)
|
|
205
|
+
logged_in_url = ensure_logged_in(desired_url=project_config.divbase_url)
|
|
145
206
|
|
|
146
207
|
deleted_version = delete_version_command(
|
|
147
208
|
project_name=project_config.name, divbase_base_url=logged_in_url, version_name=name
|
divbase_cli/cli_config.py
CHANGED
|
@@ -2,16 +2,31 @@
|
|
|
2
2
|
Settings for DivBase CLI.
|
|
3
3
|
|
|
4
4
|
This class creates a single 'settings' object at module load time that can be imported and used throughout the entire package.
|
|
5
|
+
|
|
6
|
+
The user config and tokens are stored in the users local app dir:
|
|
7
|
+
https://typer.tiangolo.com/tutorial/app-dir/
|
|
5
8
|
"""
|
|
6
9
|
|
|
7
10
|
import os
|
|
8
11
|
from dataclasses import dataclass
|
|
9
12
|
from pathlib import Path
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
APP_NAME = "divbase-cli"
|
|
17
|
+
APP_DIR = Path(typer.get_app_dir(APP_NAME))
|
|
13
18
|
DEFAULT_METADATA_TSV_NAME = "sample_metadata.tsv"
|
|
14
|
-
|
|
19
|
+
DEFAULT_LOG_LEVEL = "INFO"
|
|
20
|
+
DEV_MODE = os.getenv("DIVBASE_DEV", "0") == "1"
|
|
21
|
+
CONFIG_PATH = APP_DIR / "config.yaml"
|
|
22
|
+
TOKENS_PATH = APP_DIR / ".secrets"
|
|
23
|
+
|
|
24
|
+
if DEV_MODE:
|
|
25
|
+
DEFAULT_DIVBASE_API_URL = "http://localhost:8000/api"
|
|
26
|
+
DEFAULT_LOGGING_ON = "1"
|
|
27
|
+
else:
|
|
28
|
+
DEFAULT_DIVBASE_API_URL = "https://divbase-dev.scilifelab-2-dev.sys.kth.se/api" # TODO - prod url when time
|
|
29
|
+
DEFAULT_LOGGING_ON = "0"
|
|
15
30
|
|
|
16
31
|
|
|
17
32
|
@dataclass
|
|
@@ -19,21 +34,24 @@ class DivBaseCLISettings:
|
|
|
19
34
|
"""
|
|
20
35
|
Settings for DivBase CLI.
|
|
21
36
|
|
|
22
|
-
|
|
23
|
-
import the '
|
|
37
|
+
You do not need to create an instance of this class yourself,
|
|
38
|
+
instead, import the 'cli_settings' instance created at this module's load time.
|
|
24
39
|
"""
|
|
25
40
|
|
|
26
|
-
CONFIG_PATH: Path = Path(os.getenv("
|
|
27
|
-
TOKENS_PATH: Path = Path(os.getenv("
|
|
41
|
+
CONFIG_PATH: Path = Path(os.getenv("DIVBASE_CLI_CONFIG_PATH", CONFIG_PATH))
|
|
42
|
+
TOKENS_PATH: Path = Path(os.getenv("DIVBASE_CLI_TOKENS_PATH", TOKENS_PATH))
|
|
28
43
|
DIVBASE_API_URL: str = os.getenv("DIVBASE_API_URL", DEFAULT_DIVBASE_API_URL)
|
|
29
44
|
METADATA_TSV_NAME: str = os.getenv("DIVBASE_METADATA_TSV_NAME", DEFAULT_METADATA_TSV_NAME)
|
|
30
|
-
LOGGING_ON: bool =
|
|
31
|
-
LOG_LEVEL: str = os.getenv("DIVBASE_LOG_LEVEL",
|
|
45
|
+
LOGGING_ON: bool = os.getenv("DIVBASE_LOGGING_ON", DEFAULT_LOGGING_ON) == "1"
|
|
46
|
+
LOG_LEVEL: str = os.getenv("DIVBASE_LOG_LEVEL", DEFAULT_LOG_LEVEL).upper()
|
|
32
47
|
|
|
33
48
|
def __post_init__(self):
|
|
34
49
|
valid_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET"]
|
|
35
50
|
if self.LOG_LEVEL not in valid_levels:
|
|
36
51
|
raise ValueError(f"Invalid LOG_LEVEL: {self.LOG_LEVEL}. Must be one of {valid_levels}.")
|
|
37
52
|
|
|
53
|
+
if self.DIVBASE_API_URL.endswith("/"):
|
|
54
|
+
self.DIVBASE_API_URL = self.DIVBASE_API_URL[:-1]
|
|
55
|
+
|
|
38
56
|
|
|
39
57
|
cli_settings = DivBaseCLISettings()
|