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.
Files changed (31) hide show
  1. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/.gitignore +7 -0
  2. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/PKG-INFO +3 -3
  3. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/pyproject.toml +2 -2
  4. divbase_cli-0.1.0a9/src/divbase_cli/__init__.py +1 -0
  5. divbase_cli-0.1.0a9/src/divbase_cli/cli_commands/auth_cli.py +184 -0
  6. divbase_cli-0.1.0a9/src/divbase_cli/cli_commands/file_cli.py +1210 -0
  7. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/query_cli.py +124 -7
  8. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/shared_args_options.py +10 -0
  9. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_config.py +9 -6
  10. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_exceptions.py +6 -49
  11. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/config_resolver.py +4 -4
  12. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/divbase_cli.py +10 -5
  13. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/retries.py +11 -1
  14. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/services/pre_signed_urls.py +65 -32
  15. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/services/s3_files.py +179 -71
  16. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/user_auth.py +176 -31
  17. divbase_cli-0.1.0a3/src/divbase_cli/__init__.py +0 -1
  18. divbase_cli-0.1.0a3/src/divbase_cli/cli_commands/auth_cli.py +0 -97
  19. divbase_cli-0.1.0a3/src/divbase_cli/cli_commands/file_cli.py +0 -632
  20. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/README.md +0 -0
  21. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/__init__.py +0 -0
  22. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/dimensions_cli.py +0 -0
  23. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/task_history_cli.py +0 -0
  24. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/user_config_cli.py +0 -0
  25. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/cli_commands/version_cli.py +0 -0
  26. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/display_task_history.py +0 -0
  27. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/services/__init__.py +0 -0
  28. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/services/announcements.py +0 -0
  29. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/services/project_versions.py +0 -0
  30. {divbase_cli-0.1.0a3 → divbase_cli-0.1.0a9}/src/divbase_cli/user_config.py +0 -0
  31. {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.0a3
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.0a3
21
+ Requires-Dist: divbase-lib==0.1.0a9
22
22
  Requires-Dist: keyring>=25.7.0
23
- Requires-Dist: typer==0.24.1
23
+ Requires-Dist: typer==0.26.7
24
24
  Description-Content-Type: text/markdown
25
25
 
26
26
  # divbase-cli
@@ -8,9 +8,9 @@ authors = [
8
8
  ]
9
9
  requires-python = ">=3.12"
10
10
  dependencies = [
11
- "typer==0.24.1",
11
+ "typer==0.26.7",
12
12
  "keyring>=25.7.0",
13
- "divbase-lib==0.1.0a3",
13
+ "divbase-lib==0.1.0a9",
14
14
  ]
15
15
  dynamic = ["version"]
16
16
 
@@ -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']})")