adalab-cli 1.4.0__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.
- adalab_cli/__init__.py +11 -0
- adalab_cli/cli/__init__.py +5 -0
- adalab_cli/cli/app.py +191 -0
- adalab_cli/cli/authentication_flows.py +147 -0
- adalab_cli/cli/completion_tree.py +67 -0
- adalab_cli/cli/databases_commands.py +221 -0
- adalab_cli/cli/factory.py +100 -0
- adalab_cli/cli/gallery_commands.py +166 -0
- adalab_cli/cli/interactive.py +65 -0
- adalab_cli/cli/kernel_commands.py +48 -0
- adalab_cli/cli/logger.py +125 -0
- adalab_cli/cli/login_commands.py +267 -0
- adalab_cli/cli/oci_commands.py +77 -0
- adalab_cli/cli/schedule_commands.py +188 -0
- adalab_cli/cli/user_commands.py +75 -0
- adalab_cli/cli/utils.py +163 -0
- adalab_cli-1.4.0.dist-info/METADATA +58 -0
- adalab_cli-1.4.0.dist-info/RECORD +20 -0
- adalab_cli-1.4.0.dist-info/WHEEL +4 -0
- adalab_cli-1.4.0.dist-info/entry_points.txt +3 -0
adalab_cli/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""The adalab-cli base module exposes the CLI application.
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
import importlib.metadata
|
|
5
|
+
|
|
6
|
+
_DISTRIBUTION_METADATA = importlib.metadata.metadata("adalab-cli")
|
|
7
|
+
__project__ = _DISTRIBUTION_METADATA["name"]
|
|
8
|
+
__version__ = _DISTRIBUTION_METADATA["version"]
|
|
9
|
+
__description__ = _DISTRIBUTION_METADATA["description"]
|
|
10
|
+
|
|
11
|
+
__all__ = ["cli"]
|
adalab_cli/cli/app.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Main module for the adalib CLI.
|
|
2
|
+
|
|
3
|
+
This module initializes the Typer application and integrates various
|
|
4
|
+
submodules that handle different command groups for the adalib CLI.
|
|
5
|
+
|
|
6
|
+
Submodules:
|
|
7
|
+
- oci_commands: Handles commands related to Open Container Initiative (OCI).
|
|
8
|
+
- gallery_commands: Manages commands for gallery operations.
|
|
9
|
+
- kernel_commands: Contains commands for kernel-related operations.
|
|
10
|
+
- user_commands: Deals with user-related commands.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import importlib.metadata
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
import requests
|
|
17
|
+
import typer
|
|
18
|
+
from loguru import logger
|
|
19
|
+
from prompt_toolkit.completion import NestedCompleter
|
|
20
|
+
from rich import print as rich_print
|
|
21
|
+
from rich.console import Console
|
|
22
|
+
from rich.table import Table
|
|
23
|
+
|
|
24
|
+
from . import authentication_flows as auth
|
|
25
|
+
from .completion_tree import completion_tree
|
|
26
|
+
from .databases_commands import db_app
|
|
27
|
+
from .gallery_commands import gallery_app
|
|
28
|
+
from .interactive import interactive_mode
|
|
29
|
+
from .logger import Logger
|
|
30
|
+
from .login_commands import load_config
|
|
31
|
+
from .schedule_commands import schedules_app
|
|
32
|
+
from .user_commands import user_app
|
|
33
|
+
|
|
34
|
+
app = typer.Typer(no_args_is_help=False)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.callback("adalab", invoke_without_command=True, no_args_is_help=True)
|
|
38
|
+
def main(
|
|
39
|
+
token: str = typer.Option(None, help="User token for authentication"),
|
|
40
|
+
adalab_url: str = typer.Option(None, help="URL of the AdaLab server"),
|
|
41
|
+
adalab_secret: str = typer.Option(None, help="Client secret for AdaLab"),
|
|
42
|
+
interactive: bool = typer.Option(
|
|
43
|
+
False, "-i", "--interactive", help="Activate interactive mode"
|
|
44
|
+
),
|
|
45
|
+
verbose: int = typer.Option(
|
|
46
|
+
0,
|
|
47
|
+
"-v",
|
|
48
|
+
count=True,
|
|
49
|
+
help="Verbosity level of the logs: -v (default): INFO | -vv: DEBUG | -vvv: TRACE",
|
|
50
|
+
min=0,
|
|
51
|
+
max=3,
|
|
52
|
+
),
|
|
53
|
+
no_output: bool = typer.Option(False, "--no-output", help="Disable output from the CLI."),
|
|
54
|
+
):
|
|
55
|
+
if no_output:
|
|
56
|
+
logger.remove()
|
|
57
|
+
else:
|
|
58
|
+
Logger(verbosity=verbose)
|
|
59
|
+
logger.info("Starting adalab CLI.")
|
|
60
|
+
logger.debug("Debugging mode activated.")
|
|
61
|
+
logger.trace("Tracing mode activated.")
|
|
62
|
+
|
|
63
|
+
# Attempt to configure adalib with user token.
|
|
64
|
+
# This works when token is either provided or stored in environment.
|
|
65
|
+
try:
|
|
66
|
+
auth.in_line_authentication(adalab_url, adalab_secret, token)
|
|
67
|
+
except AssertionError as e:
|
|
68
|
+
logger.info(f"{str(e)}")
|
|
69
|
+
|
|
70
|
+
# Start the interactive mode
|
|
71
|
+
if interactive:
|
|
72
|
+
rich_print("🏡 Welcome to AdaLab CLI interactive mode.")
|
|
73
|
+
rich_print("👋 Type 'exit' to quit.")
|
|
74
|
+
rich_print("ℹ️ Type 'help' for help.")
|
|
75
|
+
interactive_mode(
|
|
76
|
+
this_app=app,
|
|
77
|
+
title="adalab",
|
|
78
|
+
nested=True,
|
|
79
|
+
color="seagreen",
|
|
80
|
+
completion=NestedCompleter.from_nested_dict(completion_tree),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.command("login")
|
|
85
|
+
def login_commands(
|
|
86
|
+
auth_flow: str = typer.Option(
|
|
87
|
+
default="browser",
|
|
88
|
+
help="Authentication flow. Options: browser, device-code, user-token",
|
|
89
|
+
),
|
|
90
|
+
token: str = typer.Option(None, help="Token for authentication"),
|
|
91
|
+
):
|
|
92
|
+
"""
|
|
93
|
+
Login to adalab
|
|
94
|
+
"""
|
|
95
|
+
stored_configuration = load_config()
|
|
96
|
+
adalab_server_url = stored_configuration.get(
|
|
97
|
+
"adalab_url"
|
|
98
|
+
) # Look for the URL in the config file
|
|
99
|
+
if adalab_server_url is None:
|
|
100
|
+
adalab_server_url = os.getenv("ADALAB_URL") # Look for the URL in env vars
|
|
101
|
+
if adalab_server_url is None:
|
|
102
|
+
adalab_server_url = typer.prompt("What is the AdaLab url?") # Ask for the URL
|
|
103
|
+
logger.info(f"AdaLab server URL: {adalab_server_url}")
|
|
104
|
+
|
|
105
|
+
adalab_client_secret = stored_configuration.get(
|
|
106
|
+
"adalab_secret"
|
|
107
|
+
) # Look for the client secret in the config file
|
|
108
|
+
if adalab_client_secret is None:
|
|
109
|
+
adalab_client_secret = os.getenv("ADALAB_CLIENT_SECRET")
|
|
110
|
+
if "ADALAB_CLIENT_SECRET" not in os.environ:
|
|
111
|
+
adalab_client_secret = typer.prompt(
|
|
112
|
+
"What is the AdaLab client secret?", hide_input=True
|
|
113
|
+
)
|
|
114
|
+
logger.info("AdaLab client secret: ********")
|
|
115
|
+
# Check validity of URL
|
|
116
|
+
response = requests.get(adalab_server_url + "/adaboard/api", timeout=10)
|
|
117
|
+
if response.status_code != 200:
|
|
118
|
+
typer.echo("Invalid URL. Please try again.")
|
|
119
|
+
raise typer.Exit()
|
|
120
|
+
|
|
121
|
+
# Check which of the three kinds of authentication flows was set
|
|
122
|
+
if auth_flow == "browser":
|
|
123
|
+
auth.start_browser_auth(
|
|
124
|
+
adalab_server_url=adalab_server_url, adalab_client_secret=adalab_client_secret
|
|
125
|
+
)
|
|
126
|
+
elif auth_flow == "user-token":
|
|
127
|
+
auth.start_user_token_auth(
|
|
128
|
+
adalab_server_url=adalab_server_url,
|
|
129
|
+
token=token,
|
|
130
|
+
adalab_client_secret=adalab_client_secret,
|
|
131
|
+
)
|
|
132
|
+
elif auth_flow == "device-code":
|
|
133
|
+
auth.start_device_code_auth(
|
|
134
|
+
adalab_server_url=adalab_server_url, adalab_client_secret=adalab_client_secret
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
else:
|
|
138
|
+
typer.echo(
|
|
139
|
+
"Invalid authentication flow. Please try again."
|
|
140
|
+
"Options: browser, device-code, user-token"
|
|
141
|
+
)
|
|
142
|
+
raise typer.Exit()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@app.command("version")
|
|
146
|
+
def get_version():
|
|
147
|
+
"""
|
|
148
|
+
Prints the version of adalib.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
version_string_adalib = importlib.metadata.version("adalib")
|
|
152
|
+
version_string_adalib_auth = importlib.metadata.version("adalib-auth")
|
|
153
|
+
version_string_adalab_cli = importlib.metadata.version("adalab-cli")
|
|
154
|
+
|
|
155
|
+
table = Table("Package", "Version")
|
|
156
|
+
table.add_row("adalib", version_string_adalib)
|
|
157
|
+
table.add_row("adalib-auth", version_string_adalib_auth)
|
|
158
|
+
table.add_row("adalab-cli", version_string_adalab_cli)
|
|
159
|
+
|
|
160
|
+
console = Console()
|
|
161
|
+
|
|
162
|
+
console.print(table)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
app.add_typer(
|
|
166
|
+
db_app,
|
|
167
|
+
name="databases",
|
|
168
|
+
help="Commands for interacting with databases.",
|
|
169
|
+
no_args_is_help=True,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
app.add_typer(
|
|
173
|
+
gallery_app,
|
|
174
|
+
name="cards",
|
|
175
|
+
help="Commands for interacting with the gallery.",
|
|
176
|
+
no_args_is_help=True,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
app.add_typer(
|
|
180
|
+
user_app,
|
|
181
|
+
name="user",
|
|
182
|
+
help="Commands for interacting with user information.",
|
|
183
|
+
no_args_is_help=True,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
app.add_typer(
|
|
187
|
+
schedules_app,
|
|
188
|
+
name="schedules",
|
|
189
|
+
help="Commands for interacting with schedules.",
|
|
190
|
+
no_args_is_help=True,
|
|
191
|
+
)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from time import sleep
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
import typer
|
|
6
|
+
from adalib_auth.config import get_config
|
|
7
|
+
from keycloak import exceptions
|
|
8
|
+
from loguru import logger
|
|
9
|
+
from rich import print as rich_print
|
|
10
|
+
|
|
11
|
+
from .login_commands import CLIENT_ID, KeycloakAuth, save_credentials
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def start_browser_auth(adalab_server_url: str = None, adalab_client_secret: str = None):
|
|
15
|
+
"""Starts the browser-based authentication flow.
|
|
16
|
+
|
|
17
|
+
:param adalab_server_url: The URL of the Adalab server, defaults to None
|
|
18
|
+
:type adalab_server_url: str, optional
|
|
19
|
+
"""
|
|
20
|
+
auth = KeycloakAuth(
|
|
21
|
+
adalab_server_url=adalab_server_url, adalab_client_secret=adalab_client_secret
|
|
22
|
+
)
|
|
23
|
+
auth.login()
|
|
24
|
+
logger.success("Authentication successful. Credentials saved.")
|
|
25
|
+
rich_print(
|
|
26
|
+
"🎉 [bold green]Authentication successful[/bold green]. You can now enjoy the AdaLab CLI."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def start_user_token_auth(
|
|
31
|
+
token: str = None,
|
|
32
|
+
adalab_server_url: str = None,
|
|
33
|
+
adalab_client_secret: str = None,
|
|
34
|
+
store_credentials: bool = True,
|
|
35
|
+
):
|
|
36
|
+
"""Starts the user token authentication flow.
|
|
37
|
+
|
|
38
|
+
This function exchanges the user token for an access and refresh token. The token can be given
|
|
39
|
+
directly in the prompt or as an environment variable.
|
|
40
|
+
The access token and refresh token are saved to a file for future use.
|
|
41
|
+
|
|
42
|
+
:param token: The user token, it will be retrieve from ADALAB_USER_TOKEN environment variable
|
|
43
|
+
or prompted from the user.
|
|
44
|
+
:type token: str, optional
|
|
45
|
+
:param adalab_server_url: The URL of the ADALab server. If not provided, it will be retrieved
|
|
46
|
+
from the adalab_server_url argument.
|
|
47
|
+
:type adalab_server_url: str, optional
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
if token is None:
|
|
51
|
+
token = os.getenv("ADALAB_USER_TOKEN") or typer.prompt(
|
|
52
|
+
"What is your user token", hide_input=True
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
resp = requests.get(
|
|
56
|
+
adalab_server_url + "/adaboard/api/adalib/token",
|
|
57
|
+
headers={"Authorization": f"Token {token}"},
|
|
58
|
+
)
|
|
59
|
+
if resp.status_code != 200:
|
|
60
|
+
raise AssertionError(f"Failed to authenticate with user token. {resp.text}")
|
|
61
|
+
|
|
62
|
+
jh_token = resp.json()["access_token"]
|
|
63
|
+
new_token = KeycloakAuth(
|
|
64
|
+
adalab_server_url=adalab_server_url, adalab_client_secret=adalab_client_secret
|
|
65
|
+
).client.exchange_token(token=jh_token, audience=CLIENT_ID)
|
|
66
|
+
if store_credentials:
|
|
67
|
+
save_credentials(
|
|
68
|
+
my_access_token=new_token["access_token"],
|
|
69
|
+
my_refresh_token=new_token["refresh_token"],
|
|
70
|
+
)
|
|
71
|
+
logger.success("Authentication successful. Credentials saved.")
|
|
72
|
+
rich_print(
|
|
73
|
+
"🎉 [bold green]Authentication successful[/bold green]. You can now enjoy the AdaLab CLI."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def start_device_code_auth(adalab_server_url: str = None, adalab_client_secret: str = None):
|
|
78
|
+
"""
|
|
79
|
+
Starts the device code authentication flow.
|
|
80
|
+
|
|
81
|
+
This function initiates the device code authentication flow using the AdaLab server URL.
|
|
82
|
+
It retrieves the device code information from the Keycloak authentication client,
|
|
83
|
+
prompts the user to visit the verification URI and enter the user code,
|
|
84
|
+
and waits for the user authentication to complete.
|
|
85
|
+
Once the authentication is successful, the access token and refresh token are saved.
|
|
86
|
+
|
|
87
|
+
:param adalab_server_url: The AdaLab server URL, defaults to None
|
|
88
|
+
:type adalab_server_url: str, optional
|
|
89
|
+
"""
|
|
90
|
+
auth = KeycloakAuth(
|
|
91
|
+
adalab_server_url=adalab_server_url, adalab_client_secret=adalab_client_secret
|
|
92
|
+
)
|
|
93
|
+
device_code_info = auth.client.device()
|
|
94
|
+
device_code = device_code_info["device_code"]
|
|
95
|
+
user_code = device_code_info["user_code"]
|
|
96
|
+
verification_uri = device_code_info["verification_uri"]
|
|
97
|
+
rich_print("Please visit %s and enter the code: %s", verification_uri, user_code)
|
|
98
|
+
rich_print("This code expires in %s seconds", device_code_info["expires_in"])
|
|
99
|
+
token = None
|
|
100
|
+
while token is None:
|
|
101
|
+
try:
|
|
102
|
+
token = auth.client.token(
|
|
103
|
+
grant_type="urn:ietf:params:oauth:grant-type:device_code",
|
|
104
|
+
device_code=device_code,
|
|
105
|
+
)
|
|
106
|
+
except exceptions.KeycloakPostError as e:
|
|
107
|
+
if "authorization_pending" in str(e):
|
|
108
|
+
logger.info("Waiting for user authentication...")
|
|
109
|
+
sleep(device_code_info["interval"])
|
|
110
|
+
else:
|
|
111
|
+
logger.error("Error during device polling: %s", str(e))
|
|
112
|
+
exit(1)
|
|
113
|
+
save_credentials(
|
|
114
|
+
my_access_token=token["access_token"],
|
|
115
|
+
my_refresh_token=token["refresh_token"],
|
|
116
|
+
)
|
|
117
|
+
logger.success("Authentication successful. Credentials saved.")
|
|
118
|
+
rich_print(
|
|
119
|
+
"🎉 [bold green]Authentication successful[/bold green]. You can now enjoy the AdaLab CLI."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def in_line_authentication(
|
|
124
|
+
adalab_server_url: str = None, adalab_secret: str = None, user_token: str = None
|
|
125
|
+
) -> None:
|
|
126
|
+
"""Auxiliary function for in-line authentication. This will not store credentials in the
|
|
127
|
+
configuration file. Instead it will be used for one-time authentication. Only valid for the
|
|
128
|
+
user token authentication flow.
|
|
129
|
+
|
|
130
|
+
:param adalab_server_url: The AdaLab server URL, defaults to None
|
|
131
|
+
:type adalab_server_url: str, optional
|
|
132
|
+
:param adalab_secret: The AdaLab JupyterHub client secret, defaults to None
|
|
133
|
+
:type adalab_secret: str, optional
|
|
134
|
+
:param user_token: The user token retrieved from AdaLab, defaults to None
|
|
135
|
+
:type user_token: str, optional
|
|
136
|
+
"""
|
|
137
|
+
if adalab_server_url is None:
|
|
138
|
+
adalab_server_url = os.getenv("ADALAB_URL") or None
|
|
139
|
+
if adalab_secret is None:
|
|
140
|
+
adalab_secret = os.getenv("ADALAB_CLIENT_SECRET") or None
|
|
141
|
+
if user_token is None:
|
|
142
|
+
user_token = os.getenv("ADALAB_USER_TOKEN") or None
|
|
143
|
+
|
|
144
|
+
if None in (adalab_server_url, adalab_secret, user_token):
|
|
145
|
+
raise AssertionError("Missing required variables for in-line authentication.")
|
|
146
|
+
|
|
147
|
+
get_config(adaboard_api_url=adalab_server_url + "/adaboard/api", token=user_token)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Map of the CLI commands and subcommands for the CLI completion tree."""
|
|
2
|
+
|
|
3
|
+
completion_tree = {
|
|
4
|
+
"cards": {
|
|
5
|
+
"approve-card": {"--help": None},
|
|
6
|
+
"expose-card": {"--help": None},
|
|
7
|
+
"get-card": {"--help": None},
|
|
8
|
+
"hide-card": {"--help": None},
|
|
9
|
+
"list-cards": {
|
|
10
|
+
"all": None,
|
|
11
|
+
"notebook": None,
|
|
12
|
+
"voila": None,
|
|
13
|
+
"url": None,
|
|
14
|
+
"group": None,
|
|
15
|
+
"--help": None,
|
|
16
|
+
},
|
|
17
|
+
"help": None,
|
|
18
|
+
"exit": None,
|
|
19
|
+
},
|
|
20
|
+
"databases": {
|
|
21
|
+
"list-databases": {"--no-pretty": None, "--help": None},
|
|
22
|
+
"list-datasets": {"--no-pretty": None, "--return-sql": None, "--help": None},
|
|
23
|
+
"list-tables": {"--no-pretty": None, "--help": None},
|
|
24
|
+
"view-dataset": {"--head": None, "--no-pretty": None, "--help": None},
|
|
25
|
+
"help": None,
|
|
26
|
+
"exit": None,
|
|
27
|
+
},
|
|
28
|
+
"schedules": {
|
|
29
|
+
"create-schedule": {
|
|
30
|
+
"--schedule": None,
|
|
31
|
+
"--pool": None,
|
|
32
|
+
"--active": None,
|
|
33
|
+
"--concurrent": None,
|
|
34
|
+
"--cleanup": None,
|
|
35
|
+
"--timeout": None,
|
|
36
|
+
"--help": None,
|
|
37
|
+
},
|
|
38
|
+
"delete-run": {"--help": None},
|
|
39
|
+
"delete-schedule": {"--help": None},
|
|
40
|
+
"start-run": {
|
|
41
|
+
"--cleanup": None,
|
|
42
|
+
"--pool": None,
|
|
43
|
+
"--timeout": None,
|
|
44
|
+
"--help": None,
|
|
45
|
+
},
|
|
46
|
+
"stop-run": {"--help": None},
|
|
47
|
+
"update-schedule": {
|
|
48
|
+
"--schedule": None,
|
|
49
|
+
"--pool": None,
|
|
50
|
+
"--active": None,
|
|
51
|
+
"--concurrent": None,
|
|
52
|
+
"--cleanup": None,
|
|
53
|
+
"--timeout": None,
|
|
54
|
+
"--help": None,
|
|
55
|
+
},
|
|
56
|
+
"help": None,
|
|
57
|
+
"exit": None,
|
|
58
|
+
},
|
|
59
|
+
"user": {
|
|
60
|
+
"whoami": {"--full-information": None, "--with-notifications": None, "--help": None},
|
|
61
|
+
"help": None,
|
|
62
|
+
"exit": None,
|
|
63
|
+
},
|
|
64
|
+
"version": None,
|
|
65
|
+
"help": None,
|
|
66
|
+
"exit": None,
|
|
67
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
""" docstrings"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from loguru import logger
|
|
7
|
+
from prompt_toolkit.completion import NestedCompleter
|
|
8
|
+
from rich import print as rich_print
|
|
9
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
10
|
+
|
|
11
|
+
# from adalib.superset import databases, datasets
|
|
12
|
+
from typing_extensions import Annotated
|
|
13
|
+
|
|
14
|
+
from .completion_tree import completion_tree
|
|
15
|
+
from .factory import check_authentication
|
|
16
|
+
|
|
17
|
+
# from . import utils
|
|
18
|
+
from .interactive import interactive_mode
|
|
19
|
+
|
|
20
|
+
db_app = typer.Typer()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@db_app.command("list-databases")
|
|
24
|
+
def database_list_databases(
|
|
25
|
+
pretty: Annotated[
|
|
26
|
+
bool,
|
|
27
|
+
typer.Option(help="Set this flag to get the output in a nice tabular view."),
|
|
28
|
+
] = True
|
|
29
|
+
):
|
|
30
|
+
"""Prints all databases available."""
|
|
31
|
+
from adalib.superset import databases # noqa: E402
|
|
32
|
+
|
|
33
|
+
from . import utils
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
with Progress(
|
|
37
|
+
SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True
|
|
38
|
+
) as progress:
|
|
39
|
+
progress.add_task(description="Fetching data...", total=None)
|
|
40
|
+
my_databases = databases.all().as_df()
|
|
41
|
+
except Exception as e:
|
|
42
|
+
logger.error(f"Error: {str(e)}")
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
# my_databases = databases.all().as_df()
|
|
45
|
+
utils.print_dataframe(dataframe=my_databases, pretty=pretty, title="Databases")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@db_app.command("list-datasets")
|
|
49
|
+
def database_list_datasets(
|
|
50
|
+
database_id: Annotated[
|
|
51
|
+
int,
|
|
52
|
+
typer.Argument(help="Specific database from which datasets are listed."),
|
|
53
|
+
] = None,
|
|
54
|
+
return_sql: Annotated[
|
|
55
|
+
bool,
|
|
56
|
+
typer.Option(help="Set this flag to get the SQL query in the output."),
|
|
57
|
+
] = False,
|
|
58
|
+
pretty: Annotated[
|
|
59
|
+
bool,
|
|
60
|
+
typer.Option(help="Set this flag to get the output in a nice tabular view."),
|
|
61
|
+
] = True,
|
|
62
|
+
):
|
|
63
|
+
"""
|
|
64
|
+
Prints all datasets available in the databases DATABASE_ID.
|
|
65
|
+
If DATABASE_ID is not specified then datasets in all available databases are returned.
|
|
66
|
+
|
|
67
|
+
Example usage:
|
|
68
|
+
|
|
69
|
+
adalab databases list-datasets --return-sql --pretty 1
|
|
70
|
+
|
|
71
|
+
will print the available datasets in the database identified with DATABASE_ID=1, including
|
|
72
|
+
the SQL query (if available) in a nice tabular view.
|
|
73
|
+
"""
|
|
74
|
+
from . import utils
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
with Progress(
|
|
78
|
+
SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True
|
|
79
|
+
) as progress:
|
|
80
|
+
progress.add_task(description="Fetching data...", total=None)
|
|
81
|
+
my_datasets = utils.get_datasets()
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.error(f"Error: {str(e)}")
|
|
85
|
+
sys.exit(1)
|
|
86
|
+
|
|
87
|
+
filtered_datasets = utils.filter_datasets_by_database_id(
|
|
88
|
+
dataset_list=my_datasets, database_id=database_id
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if filtered_datasets is False:
|
|
92
|
+
rich_print(f"The database with index {database_id} is not available.")
|
|
93
|
+
|
|
94
|
+
else:
|
|
95
|
+
format_datasets = utils.check_sql(dataset_list=filtered_datasets, include_sql=return_sql)
|
|
96
|
+
utils.print_dataframe(
|
|
97
|
+
dataframe=format_datasets,
|
|
98
|
+
pretty=pretty,
|
|
99
|
+
title=(
|
|
100
|
+
f"Datasets in database {database_id}"
|
|
101
|
+
if (database_id is not None)
|
|
102
|
+
else "All Datasets"
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@db_app.command("view-dataset", no_args_is_help=True)
|
|
108
|
+
def database_view_dataset_by_index(
|
|
109
|
+
ds_index: Annotated[int, typer.Argument(help="Index of the dataset to print.")] = None,
|
|
110
|
+
head: Annotated[
|
|
111
|
+
int,
|
|
112
|
+
typer.Option(help="Set --head N for printing the first N elements in the dataframe."),
|
|
113
|
+
] = 10,
|
|
114
|
+
pretty: Annotated[
|
|
115
|
+
bool,
|
|
116
|
+
typer.Option(help="Set this flag to get the output in a nice tabular view."),
|
|
117
|
+
] = True,
|
|
118
|
+
):
|
|
119
|
+
"""
|
|
120
|
+
Prints the first elements of the dataset indexed by DS_INDEX, by
|
|
121
|
+
default it prints the first 10 elements.
|
|
122
|
+
|
|
123
|
+
Example usage:
|
|
124
|
+
|
|
125
|
+
adalab databases view-dataset 15 --head 11 --pretty
|
|
126
|
+
|
|
127
|
+
will print the first 11 elements of the dataset with index 15
|
|
128
|
+
in a nice tabular view.
|
|
129
|
+
"""
|
|
130
|
+
from adalib.superset import datasets
|
|
131
|
+
|
|
132
|
+
from . import utils
|
|
133
|
+
|
|
134
|
+
if ds_index not in datasets.all().as_df().index:
|
|
135
|
+
return rich_print(
|
|
136
|
+
f"Dataset with index {ds_index} [bold red]not available[/bold red].\n"
|
|
137
|
+
+ "Run [bold blue]list-datasets[/bold blue] for available options"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
with Progress(
|
|
142
|
+
SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True
|
|
143
|
+
) as progress:
|
|
144
|
+
progress.add_task(description="Fetching data...", total=None)
|
|
145
|
+
my_dataset = datasets.get(id=ds_index)
|
|
146
|
+
|
|
147
|
+
if my_dataset.kind == "virtual":
|
|
148
|
+
my_df = utils.retrieve_virtual_dataset(my_dataset=my_dataset, head=head)
|
|
149
|
+
elif my_dataset.kind == "physical":
|
|
150
|
+
my_df = utils.retrieve_physical_dataset(my_dataset=my_dataset, head=head)
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logger.error(f"Error: {str(e)}")
|
|
154
|
+
sys.exit(1)
|
|
155
|
+
|
|
156
|
+
utils.print_dataframe(dataframe=my_df, pretty=pretty, title=f"Dataset {ds_index}")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@db_app.command("list-tables", no_args_is_help=True)
|
|
160
|
+
def list_tables(
|
|
161
|
+
db_index: Annotated[int, typer.Argument(help="Database index.")] = None,
|
|
162
|
+
pretty: Annotated[
|
|
163
|
+
bool,
|
|
164
|
+
typer.Option(help="Set this flag to get the output in a nice tabular view."),
|
|
165
|
+
] = True,
|
|
166
|
+
):
|
|
167
|
+
"""
|
|
168
|
+
List tables in the database with the specified index.
|
|
169
|
+
|
|
170
|
+
Example usage:
|
|
171
|
+
|
|
172
|
+
adalab databases list-tables 1 --pretty
|
|
173
|
+
|
|
174
|
+
will print the tables in the databes with database index 1 in a nice tabular view.
|
|
175
|
+
"""
|
|
176
|
+
from adalib.superset import databases
|
|
177
|
+
|
|
178
|
+
from . import utils
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
available_index = databases.all().as_df().index
|
|
182
|
+
assert db_index in available_index, (
|
|
183
|
+
f"Database with index {db_index} not available."
|
|
184
|
+
+ " Run list-databases for available options"
|
|
185
|
+
)
|
|
186
|
+
except AssertionError as ae:
|
|
187
|
+
logger.error(f"Error: {str(ae)}")
|
|
188
|
+
sys.exit(1)
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
with Progress(
|
|
192
|
+
SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True
|
|
193
|
+
) as progress:
|
|
194
|
+
progress.add_task(description="Fetching data...", total=None)
|
|
195
|
+
db = databases.get(id=db_index)
|
|
196
|
+
tables = db.run(
|
|
197
|
+
query="SELECT table_name "
|
|
198
|
+
"FROM information_schema.tables "
|
|
199
|
+
"WHERE table_schema = 'public';"
|
|
200
|
+
).as_df()
|
|
201
|
+
except Exception as e:
|
|
202
|
+
logger.error(f"Error: {str(e)}")
|
|
203
|
+
sys.exit(1)
|
|
204
|
+
utils.print_dataframe(dataframe=tables, pretty=pretty, title=f"Tables in database {db_index}")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@db_app.callback("callback", invoke_without_command=True)
|
|
208
|
+
def main(
|
|
209
|
+
interactive: bool = typer.Option(
|
|
210
|
+
False, "-i", "--interactive", help="Activate interactive mode"
|
|
211
|
+
)
|
|
212
|
+
):
|
|
213
|
+
if interactive:
|
|
214
|
+
interactive_mode(
|
|
215
|
+
this_app=db_app,
|
|
216
|
+
title="databases",
|
|
217
|
+
color="maroon",
|
|
218
|
+
completion=NestedCompleter.from_nested_dict(completion_tree["databases"]),
|
|
219
|
+
)
|
|
220
|
+
else:
|
|
221
|
+
check_authentication()
|