divbase-cli 0.1.0a3__tar.gz → 0.1.0a9__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.
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/.gitignore +7 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/PKG-INFO +3 -3
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/pyproject.toml +2 -2
- divbase_cli-0.1.0a9/src/divbase_cli/__init__.py +1 -0
- divbase_cli-0.1.0a9/src/divbase_cli/cli_commands/auth_cli.py +184 -0
- divbase_cli-0.1.0a9/src/divbase_cli/cli_commands/file_cli.py +1210 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/query_cli.py +124 -7
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/shared_args_options.py +10 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_config.py +9 -6
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_exceptions.py +6 -49
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/config_resolver.py +4 -4
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/divbase_cli.py +10 -5
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/retries.py +11 -1
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/services/pre_signed_urls.py +65 -32
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/services/s3_files.py +179 -71
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/user_auth.py +176 -31
- divbase_cli-0.1.0a3/src/divbase_cli/__init__.py +0 -1
- divbase_cli-0.1.0a3/src/divbase_cli/cli_commands/auth_cli.py +0 -97
- divbase_cli-0.1.0a3/src/divbase_cli/cli_commands/file_cli.py +0 -632
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/README.md +0 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/__init__.py +0 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/dimensions_cli.py +0 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/task_history_cli.py +0 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/user_config_cli.py +0 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/version_cli.py +0 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/display_task_history.py +0 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/services/__init__.py +0 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/services/announcements.py +0 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/services/project_versions.py +0 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/user_config.py +0 -0
- {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/utils.py +0 -0
|
@@ -22,6 +22,9 @@ __pycache__/
|
|
|
22
22
|
tests/fixtures/temp*
|
|
23
23
|
tests/fixtures/merged*
|
|
24
24
|
divbase_metadata_template*.tsv
|
|
25
|
+
# query results files
|
|
26
|
+
result_of_job_*.vcf.gz
|
|
27
|
+
result_of_job_*.log
|
|
25
28
|
|
|
26
29
|
# query job config files
|
|
27
30
|
bcftools_divbase_job_config.json
|
|
@@ -40,6 +43,10 @@ scripts/benchmarking/results
|
|
|
40
43
|
htmlcov/
|
|
41
44
|
docker/coverage-data/
|
|
42
45
|
|
|
46
|
+
# log files written by docker compose, if env var set to do so (by default no)
|
|
47
|
+
docker/logs/*
|
|
48
|
+
!docker/logs/.gitkeep
|
|
49
|
+
|
|
43
50
|
#MacOS artifacts
|
|
44
51
|
.DS_Store
|
|
45
52
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: divbase-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a9
|
|
4
4
|
Summary: Command Line Interface for Divbase
|
|
5
5
|
Project-URL: Homepage, https://divbase.scilifelab-2-prod.sys.kth.se
|
|
6
6
|
Project-URL: Documentation, https://scilifelabdatacentre.github.io/divbase
|
|
@@ -18,9 +18,9 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
20
20
|
Requires-Python: >=3.12
|
|
21
|
-
Requires-Dist: divbase-lib==0.1.
|
|
21
|
+
Requires-Dist: divbase-lib==0.1.0a9
|
|
22
22
|
Requires-Dist: keyring>=25.7.0
|
|
23
|
-
Requires-Dist: typer==0.
|
|
23
|
+
Requires-Dist: typer==0.26.7
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
|
|
26
26
|
# divbase-cli
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0a9"
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI subcommand for managing user auth with DivBase server.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from pydantic import SecretStr
|
|
11
|
+
from rich import print
|
|
12
|
+
|
|
13
|
+
from divbase_cli.cli_config import cli_settings
|
|
14
|
+
from divbase_cli.cli_exceptions import DivBaseAPIConnectionError, DivBaseAPIError
|
|
15
|
+
from divbase_cli.config_resolver import resolve_url_for_non_project_specific_commands
|
|
16
|
+
from divbase_cli.services.announcements import get_and_display_announcements
|
|
17
|
+
from divbase_cli.user_auth import (
|
|
18
|
+
PERSONAL_ACCESS_TOKEN_EXPIRED_MESSAGE,
|
|
19
|
+
PATData,
|
|
20
|
+
check_existing_session,
|
|
21
|
+
delete_stored_pat,
|
|
22
|
+
load_stored_user_pat,
|
|
23
|
+
login_to_divbase,
|
|
24
|
+
logout_of_divbase,
|
|
25
|
+
make_authenticated_request,
|
|
26
|
+
)
|
|
27
|
+
from divbase_cli.user_config import load_user_config
|
|
28
|
+
from divbase_lib.divbase_constants import PAT_TOKEN_PREFIX
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
divbase_home_url = cli_settings.DIVBASE_API_URL.replace("/api", "")
|
|
33
|
+
|
|
34
|
+
auth_app = typer.Typer(
|
|
35
|
+
no_args_is_help=True,
|
|
36
|
+
help=f"Login/logout of DivBase server. To register, visit {divbase_home_url}.",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@auth_app.command("login")
|
|
41
|
+
def login(
|
|
42
|
+
email: str,
|
|
43
|
+
divbase_url: str = typer.Option(cli_settings.DIVBASE_API_URL, help="DivBase server URL to connect to."),
|
|
44
|
+
force: bool = typer.Option(False, "--force", "-f", help="Force login again even if already logged in"),
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
Log in to the DivBase server.
|
|
48
|
+
|
|
49
|
+
You'll be prompted for your password after running the command.
|
|
50
|
+
"""
|
|
51
|
+
password: str = typer.prompt("please enter your password", hide_input=True, confirmation_prompt=False)
|
|
52
|
+
secret_password = SecretStr(password)
|
|
53
|
+
del password # avoid user passwords showing up in error messages etc...
|
|
54
|
+
|
|
55
|
+
config = load_user_config()
|
|
56
|
+
|
|
57
|
+
if not force:
|
|
58
|
+
session_expires_at = check_existing_session(divbase_url=divbase_url, config=config)
|
|
59
|
+
if session_expires_at:
|
|
60
|
+
print(f"Already logged in to {divbase_url} with email: {config.logged_in_email}.")
|
|
61
|
+
print(f"Session expires: {datetime.fromtimestamp(session_expires_at)}")
|
|
62
|
+
|
|
63
|
+
if not typer.confirm("Do you want to login again? This will replace your current session."):
|
|
64
|
+
print("Login cancelled.")
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
login_to_divbase(email=email, password=secret_password, divbase_url=divbase_url)
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
get_and_display_announcements(divbase_base_url=divbase_url)
|
|
71
|
+
except (DivBaseAPIError, DivBaseAPIConnectionError):
|
|
72
|
+
# lets not fail the login process if announcements are not working.
|
|
73
|
+
logger.info("Failed to get announcements after login. Error was: ", exc_info=True)
|
|
74
|
+
|
|
75
|
+
print(f"Logged in successfully as: {email}")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@auth_app.command("logout")
|
|
79
|
+
def logout():
|
|
80
|
+
"""
|
|
81
|
+
Log out of the DivBase server.
|
|
82
|
+
|
|
83
|
+
Removes your locally stored credentials and invalidates the server-side refresh token.
|
|
84
|
+
"""
|
|
85
|
+
logout_of_divbase()
|
|
86
|
+
print("Logged out successfully.")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@auth_app.command("add-pat")
|
|
90
|
+
def add_pat(
|
|
91
|
+
name: str = typer.Argument(..., help="Name of the personal access token (PAT). E.g. 'work-laptop-pat'"),
|
|
92
|
+
expires_unix_timestamp: int | None = typer.Option(
|
|
93
|
+
None,
|
|
94
|
+
"--expires",
|
|
95
|
+
"-e",
|
|
96
|
+
help="When the personal access token (PAT) expires (if it does), as a unix timestamp.",
|
|
97
|
+
min=1,
|
|
98
|
+
),
|
|
99
|
+
overwrite_existing: bool = typer.Option(
|
|
100
|
+
False, "--overwrite-existing", "-o", help="Overwrite the existing stored PAT if one already exists."
|
|
101
|
+
),
|
|
102
|
+
):
|
|
103
|
+
"""
|
|
104
|
+
Add a personal access token (PAT) to your device for authentication with DivBase.
|
|
105
|
+
|
|
106
|
+
PATs are used for authenticating with the DivBase API instead of using your password, and are recommended for use in scripts and pipelines.
|
|
107
|
+
See https://scilifelabdatacentre.github.io/divbase/user-guides/using-divbase-programmatically/ for more details on how to create a personal access token.
|
|
108
|
+
You can only store one personal access token at a time.
|
|
109
|
+
"""
|
|
110
|
+
existing = load_stored_user_pat()
|
|
111
|
+
if existing and not overwrite_existing:
|
|
112
|
+
print(
|
|
113
|
+
f"A personal access token named '{existing.name}' is already stored on your device \n"
|
|
114
|
+
f"It is due to expire on: {existing.pat_expiry_formatted()} \n"
|
|
115
|
+
"Append the flag --overwrite-existing (-o) to this command if you want to replace it. \n"
|
|
116
|
+
"You can only store one PAT at a time."
|
|
117
|
+
)
|
|
118
|
+
raise typer.Exit(code=1)
|
|
119
|
+
|
|
120
|
+
if expires_unix_timestamp and expires_unix_timestamp < time.time():
|
|
121
|
+
print("The expiry time you entered is in the past. Please enter a valid expiry time for the PAT.")
|
|
122
|
+
raise typer.Exit(code=1)
|
|
123
|
+
|
|
124
|
+
pat = SecretStr(
|
|
125
|
+
typer.prompt("please paste your personal access token now", hide_input=True, confirmation_prompt=False)
|
|
126
|
+
)
|
|
127
|
+
if not pat.get_secret_value().startswith(PAT_TOKEN_PREFIX):
|
|
128
|
+
print(
|
|
129
|
+
"It looks like the token you entered is not a valid personal access token. "
|
|
130
|
+
f"Please make sure you copied the entire token, including the '{PAT_TOKEN_PREFIX}' prefix."
|
|
131
|
+
)
|
|
132
|
+
raise typer.Exit(code=1)
|
|
133
|
+
|
|
134
|
+
pat_data = PATData(name=name, pat=pat, pat_expires_at=expires_unix_timestamp)
|
|
135
|
+
pat_data.dump_pat_data()
|
|
136
|
+
print(f"Personal access token '{name}' stored successfully.")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@auth_app.command("rm-pat")
|
|
140
|
+
def rm_pat():
|
|
141
|
+
"""
|
|
142
|
+
Remove the stored personal access token from your device.
|
|
143
|
+
|
|
144
|
+
If no such token exists the command will still succeed.
|
|
145
|
+
"""
|
|
146
|
+
delete_stored_pat()
|
|
147
|
+
print("If a personal access token was stored, it has now been removed.")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@auth_app.command("pat-info")
|
|
151
|
+
def pat_info():
|
|
152
|
+
"""
|
|
153
|
+
Display information about the personal access token stored on this device, if any.
|
|
154
|
+
|
|
155
|
+
The token will not be displayed for security reasons, but the name and expiry time will be shown.
|
|
156
|
+
"""
|
|
157
|
+
pat_data = load_stored_user_pat()
|
|
158
|
+
if not pat_data:
|
|
159
|
+
print("No personal access token stored on this device.")
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
print(f"Name: {pat_data.name}")
|
|
163
|
+
print(f"Expires: {pat_data.pat_expiry_formatted()}")
|
|
164
|
+
|
|
165
|
+
if pat_data.is_pat_expired():
|
|
166
|
+
print("[red bold]Warning [/red bold]")
|
|
167
|
+
print(PERSONAL_ACCESS_TOKEN_EXPIRED_MESSAGE)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@auth_app.command("whoami")
|
|
171
|
+
def whoami():
|
|
172
|
+
"""
|
|
173
|
+
Return information about the currently logged-in user.
|
|
174
|
+
"""
|
|
175
|
+
divbase_url = resolve_url_for_non_project_specific_commands()
|
|
176
|
+
|
|
177
|
+
request = make_authenticated_request(
|
|
178
|
+
method="GET",
|
|
179
|
+
divbase_base_url=divbase_url,
|
|
180
|
+
api_route="v1/auth/whoami",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
current_user = request.json()
|
|
184
|
+
print(f"Currently logged in as: {current_user['email']} (Name: {current_user['name']})")
|