qbraid-cli 0.7.0__py3-none-any.whl → 0.8.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.
Potentially problematic release.
This version of qbraid-cli might be problematic. Click here for more details.
- qbraid_cli/_version.py +14 -5
- qbraid_cli/admin/__init__.py +9 -0
- qbraid_cli/admin/app.py +50 -0
- qbraid_cli/admin/headers.py +193 -0
- qbraid_cli/admin/validation.py +33 -0
- qbraid_cli/configure/__init__.py +9 -0
- qbraid_cli/configure/actions.py +111 -0
- qbraid_cli/configure/app.py +77 -0
- qbraid_cli/credits/__init__.py +9 -0
- qbraid_cli/credits/app.py +32 -0
- qbraid_cli/devices/__init__.py +9 -0
- qbraid_cli/devices/app.py +80 -0
- qbraid_cli/devices/validation.py +26 -0
- qbraid_cli/envs/__init__.py +9 -0
- qbraid_cli/envs/activate.py +65 -0
- qbraid_cli/envs/app.py +270 -0
- qbraid_cli/envs/create.py +128 -0
- qbraid_cli/envs/data_handling.py +140 -0
- qbraid_cli/exceptions.py +24 -0
- qbraid_cli/handlers.py +168 -0
- qbraid_cli/jobs/__init__.py +9 -0
- qbraid_cli/jobs/app.py +149 -0
- qbraid_cli/jobs/toggle_braket.py +185 -0
- qbraid_cli/jobs/validation.py +93 -0
- qbraid_cli/kernels/__init__.py +9 -0
- qbraid_cli/kernels/app.py +111 -0
- qbraid_cli/main.py +80 -0
- qbraid_cli-0.8.0.dist-info/METADATA +143 -0
- qbraid_cli-0.8.0.dist-info/RECORD +33 -0
- {qbraid_cli-0.7.0.dist-info → qbraid_cli-0.8.0.dist-info}/WHEEL +1 -1
- qbraid_cli-0.8.0.dist-info/entry_points.txt +2 -0
- qbraid_cli/bin/qbraid.sh +0 -1317
- qbraid_cli/configure.py +0 -87
- qbraid_cli/wrapper.py +0 -91
- qbraid_cli-0.7.0.data/scripts/qbraid.sh +0 -1317
- qbraid_cli-0.7.0.dist-info/METADATA +0 -118
- qbraid_cli-0.7.0.dist-info/RECORD +0 -11
- qbraid_cli-0.7.0.dist-info/entry_points.txt +0 -2
- {qbraid_cli-0.7.0.dist-info → qbraid_cli-0.8.0.dist-info}/top_level.txt +0 -0
qbraid_cli/handlers.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module providing application support utilities, including abstractions for error handling
|
|
6
|
+
and executing operations with progress tracking within the qBraid CLI.
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import traceback
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Callable, List, Optional, Union
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
17
|
+
|
|
18
|
+
from .exceptions import DEFAULT_ERROR_MESSAGE, QbraidException
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def handle_error(
|
|
22
|
+
error_type: Optional[str] = None, message: Optional[str] = None, include_traceback: bool = True
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Generic CLI error handling helper function.
|
|
25
|
+
|
|
26
|
+
This function handles errors by printing a styled error message to stderr and optionally
|
|
27
|
+
including a traceback. It then exits the application with a non-zero status code, indicating
|
|
28
|
+
an error.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
error_type (Optional[str]): The type of the error to be displayed. Defaults to "Error" if
|
|
32
|
+
not specified.
|
|
33
|
+
message (Optional[str]): The error message to be displayed. If not specified, a default
|
|
34
|
+
error message is used.
|
|
35
|
+
include_traceback (bool): If True, include the traceback of the exception in the output.
|
|
36
|
+
Defaults to True.
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
typer.Exit: Exits the application with a status code of 1 to indicate an error.
|
|
40
|
+
"""
|
|
41
|
+
error_type = error_type or "Error"
|
|
42
|
+
message = message or DEFAULT_ERROR_MESSAGE
|
|
43
|
+
error_prefix = typer.style(f"{error_type}:", fg=typer.colors.RED, bold=True)
|
|
44
|
+
full_message = f"\n{error_prefix} {message}\n"
|
|
45
|
+
if include_traceback:
|
|
46
|
+
tb_string = traceback.format_exc()
|
|
47
|
+
# TODO: find out reason for weird traceback emitted from
|
|
48
|
+
# qbraid jobs enable/disable when library not installed.
|
|
49
|
+
# For now, if matches, just don't print it.
|
|
50
|
+
if tb_string.strip() != "NoneType: None":
|
|
51
|
+
full_message += f"\n{tb_string}"
|
|
52
|
+
typer.echo(full_message, err=True)
|
|
53
|
+
raise typer.Exit(code=1)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def handle_filesystem_operation(operation: Callable[[], None], path: Path) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Executes a filesystem operation with error handling.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
operation (Callable[[], None]): The operation to be executed. This should be a callable that
|
|
62
|
+
performs the desired filesystem operation, such as creating
|
|
63
|
+
directories or writing files.
|
|
64
|
+
path (Path): The path involved in the operation, used for error messaging.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
QbraidException: If a PermissionError or OSError occurs during the operation.
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
operation()
|
|
71
|
+
except PermissionError as err:
|
|
72
|
+
raise QbraidException(f"Permission denied: Unable to write to {path}.") from err
|
|
73
|
+
except OSError as err:
|
|
74
|
+
raise QbraidException(f"Failed to save configuration to {path}: {err.strerror}") from err
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def run_progress_task(
|
|
78
|
+
operation: Callable[..., Any],
|
|
79
|
+
*args,
|
|
80
|
+
description: Optional[str] = None,
|
|
81
|
+
error_message: Optional[str] = None,
|
|
82
|
+
**kwargs,
|
|
83
|
+
) -> Any:
|
|
84
|
+
"""
|
|
85
|
+
Executes a given operation while displaying its progress.
|
|
86
|
+
|
|
87
|
+
This function abstracts the setup and update of progress tasks, allowing for a
|
|
88
|
+
uniform interface for task execution with progress tracking. It supports custom
|
|
89
|
+
error messages and utilizes the rich library for console output.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
operation (Callable[..., Any]): The operation to be executed. This can be any callable
|
|
93
|
+
that performs the task's work.
|
|
94
|
+
*args: Variable length argument list for the operation.
|
|
95
|
+
description (optional, str): The description of the task to display in the progress bar.
|
|
96
|
+
error_message (optional, str): Custom error message to display if the operation.
|
|
97
|
+
fails. Defaults to None, in which case the
|
|
98
|
+
exception's message is used.
|
|
99
|
+
**kwargs: Arbitrary keyword arguments for the operation.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Any: The result of the operation, if successful.
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
typer.Exit: If the operation fails, after displaying the error message using typer.secho.
|
|
106
|
+
"""
|
|
107
|
+
console = Console()
|
|
108
|
+
with Progress(
|
|
109
|
+
"[progress.description]{task.description}",
|
|
110
|
+
SpinnerColumn(),
|
|
111
|
+
TextColumn("{task.fields[status]}"),
|
|
112
|
+
console=console,
|
|
113
|
+
) as progress:
|
|
114
|
+
description = description if description else "Fetching..."
|
|
115
|
+
task = progress.add_task(description, status="In Progress", total=None)
|
|
116
|
+
try:
|
|
117
|
+
result = operation(*args, **kwargs)
|
|
118
|
+
progress.update(task, completed=100, status="Done")
|
|
119
|
+
return result
|
|
120
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
121
|
+
progress.update(task, completed=100, status="Failed")
|
|
122
|
+
custom_message = error_message if error_message else str(e)
|
|
123
|
+
return handle_error(message=custom_message)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _format_list_items(items: List[str]) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Formats a list of items as a string with values comma-separated and
|
|
129
|
+
each item surrounded by single quotes
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
items (List[str]): The list of items to format.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
str: The formatted string.
|
|
136
|
+
"""
|
|
137
|
+
return ", ".join(f"'{item}'" for item in items)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def validate_item(
|
|
141
|
+
value: Optional[str], allowed_items: List[str], item_type: str
|
|
142
|
+
) -> Union[str, None]:
|
|
143
|
+
"""
|
|
144
|
+
Generic item validation function.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
value (optional, str): The value to validate.
|
|
148
|
+
allowed_items (List[str]): A list of allowed items.
|
|
149
|
+
item_type (str): A description of the item type (e.g., 'provider', 'status', 'type') for
|
|
150
|
+
error messages.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Union[str, None]: The validated value, in its original case as specified in the
|
|
154
|
+
allowed_items list, or None if the value is None.
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
typer.BadParameter: If the value is not in the allowed_items list.
|
|
158
|
+
"""
|
|
159
|
+
if value is None:
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
items_lower_to_original = {item.lower(): item for item in allowed_items}
|
|
163
|
+
|
|
164
|
+
value_lower = value.lower()
|
|
165
|
+
if value_lower not in items_lower_to_original:
|
|
166
|
+
items_formatted = _format_list_items(allowed_items)
|
|
167
|
+
raise typer.BadParameter(f"{item_type.capitalize()} must be one of {items_formatted}.")
|
|
168
|
+
return items_lower_to_original[value_lower]
|
qbraid_cli/jobs/app.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module defining commands in the 'qbraid jobs' namespace.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from typing import Any, Callable, Dict, Tuple
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
from qbraid_cli.handlers import handle_error, run_progress_task
|
|
14
|
+
from qbraid_cli.jobs.toggle_braket import disable_braket, enable_braket
|
|
15
|
+
from qbraid_cli.jobs.validation import handle_jobs_state, run_progress_get_state, validate_library
|
|
16
|
+
|
|
17
|
+
jobs_app = typer.Typer(help="Manage qBraid quantum jobs.")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@jobs_app.command(name="enable")
|
|
21
|
+
def jobs_enable(
|
|
22
|
+
library: str = typer.Argument(
|
|
23
|
+
..., help="Software library with quantum jobs support.", callback=validate_library
|
|
24
|
+
),
|
|
25
|
+
auto_confirm: bool = typer.Option(
|
|
26
|
+
False, "--yes", "-y", help="Automatically answer 'yes' to all prompts"
|
|
27
|
+
),
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Enable qBraid Quantum Jobs."""
|
|
30
|
+
|
|
31
|
+
def enable_action():
|
|
32
|
+
if library == "braket":
|
|
33
|
+
enable_braket(auto_confirm=auto_confirm)
|
|
34
|
+
else:
|
|
35
|
+
raise RuntimeError(f"Unsupported device library: '{library}'.")
|
|
36
|
+
|
|
37
|
+
handle_jobs_state(library, "enable", enable_action)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@jobs_app.command(name="disable")
|
|
41
|
+
def jobs_disable(
|
|
42
|
+
library: str = typer.Argument(
|
|
43
|
+
..., help="Software library with quantum jobs support.", callback=validate_library
|
|
44
|
+
),
|
|
45
|
+
auto_confirm: bool = typer.Option(
|
|
46
|
+
False, "--yes", "-y", help="Automatically answer 'yes' to all prompts"
|
|
47
|
+
),
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Disable qBraid Quantum Jobs."""
|
|
50
|
+
|
|
51
|
+
def disable_action():
|
|
52
|
+
if library == "braket":
|
|
53
|
+
disable_braket(auto_confirm=auto_confirm)
|
|
54
|
+
else:
|
|
55
|
+
raise RuntimeError(f"Unsupported device library: '{library}'.")
|
|
56
|
+
|
|
57
|
+
handle_jobs_state(library, "disable", disable_action)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@jobs_app.command(name="state")
|
|
61
|
+
def jobs_state(
|
|
62
|
+
library: str = typer.Argument(
|
|
63
|
+
default=None,
|
|
64
|
+
help="Optional: Specify a software library with quantum jobs support to check its status.",
|
|
65
|
+
callback=validate_library,
|
|
66
|
+
)
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Display the state of qBraid Quantum Jobs for the current environment."""
|
|
69
|
+
result: Tuple[str, Dict[str, Tuple[bool, bool]]] = run_progress_get_state(library)
|
|
70
|
+
python_exe, state_values = result
|
|
71
|
+
state_values = dict(sorted(state_values.items()))
|
|
72
|
+
|
|
73
|
+
console = Console()
|
|
74
|
+
header_1, header_2 = "Library", "State"
|
|
75
|
+
max_lib_length = max((len(lib) for lib in state_values.keys()), default=len(header_1))
|
|
76
|
+
padding = max_lib_length + 9
|
|
77
|
+
|
|
78
|
+
console.print(f"Executable: {python_exe}")
|
|
79
|
+
console.print(f"\n{header_1:<{padding}}{header_2}", style="bold")
|
|
80
|
+
|
|
81
|
+
for lib, (installed, enabled) in state_values.items():
|
|
82
|
+
state_str = (
|
|
83
|
+
"[green]enabled"
|
|
84
|
+
if enabled and installed
|
|
85
|
+
else "[red]disabled" if installed else "[grey70]unavailable"
|
|
86
|
+
)
|
|
87
|
+
console.print(f"{lib:<{padding-1}}", state_str, end="\n")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@jobs_app.command(name="list")
|
|
91
|
+
def jobs_list(
|
|
92
|
+
limit: int = typer.Option(
|
|
93
|
+
10, "--limit", "-l", help="Limit the maximum number of results returned"
|
|
94
|
+
)
|
|
95
|
+
) -> None:
|
|
96
|
+
"""List qBraid Quantum Jobs."""
|
|
97
|
+
|
|
98
|
+
def import_jobs() -> Tuple[Any, Callable]:
|
|
99
|
+
from qbraid_core.services.quantum import QuantumClient, process_job_data
|
|
100
|
+
|
|
101
|
+
client = QuantumClient()
|
|
102
|
+
|
|
103
|
+
return client, process_job_data
|
|
104
|
+
|
|
105
|
+
result: Tuple[Any, Callable] = run_progress_task(import_jobs)
|
|
106
|
+
client, process_job_data = result
|
|
107
|
+
# https://github.com/qBraid/api/issues/644
|
|
108
|
+
# raw_data = client.search_jobs(query={"numResults": limit})
|
|
109
|
+
raw_data = client.search_jobs(query={})
|
|
110
|
+
job_data, msg = process_job_data(raw_data)
|
|
111
|
+
job_data = job_data[:limit]
|
|
112
|
+
|
|
113
|
+
longest_job_id = max(len(item[0]) for item in job_data)
|
|
114
|
+
spacing = longest_job_id + 5
|
|
115
|
+
try:
|
|
116
|
+
console = Console()
|
|
117
|
+
header_1 = "Job ID"
|
|
118
|
+
header_2 = "Submitted"
|
|
119
|
+
header_3 = "Status"
|
|
120
|
+
console.print(f"\n[bold]{header_1.ljust(spacing)}{header_2.ljust(36)}{header_3}[/bold]")
|
|
121
|
+
for job_id, submitted, status in job_data:
|
|
122
|
+
if status == "COMPLETED":
|
|
123
|
+
status_color = "green"
|
|
124
|
+
elif status in ["FAILED", "CANCELLED"]:
|
|
125
|
+
status_color = "red"
|
|
126
|
+
elif status in [
|
|
127
|
+
"INITIALIZING",
|
|
128
|
+
"INITIALIZED",
|
|
129
|
+
"CREATED",
|
|
130
|
+
"QUEUED",
|
|
131
|
+
"VALIDATING",
|
|
132
|
+
"RUNNING",
|
|
133
|
+
]:
|
|
134
|
+
status_color = "blue"
|
|
135
|
+
else:
|
|
136
|
+
status_color = "grey"
|
|
137
|
+
console.print(
|
|
138
|
+
f"{job_id.ljust(spacing)}{submitted.ljust(35)}",
|
|
139
|
+
f"[{status_color}]{status}[/{status_color}]",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
console.print(f"\n{msg}", style="italic", justify="left")
|
|
143
|
+
|
|
144
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
145
|
+
handle_error(message="Failed to fetch quantum jobs.")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
jobs_app()
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module supporting 'qbraid jobs enable/disable braket' and commands.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, Tuple
|
|
14
|
+
|
|
15
|
+
import typer
|
|
16
|
+
|
|
17
|
+
from qbraid_cli.exceptions import QbraidException
|
|
18
|
+
from qbraid_cli.handlers import handle_error, handle_filesystem_operation, run_progress_task
|
|
19
|
+
|
|
20
|
+
logging.basicConfig(level=logging.INFO)
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_package_data(package: str) -> Tuple[str, str, str, str]:
|
|
25
|
+
"""Retrieve package version and location data.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
package (str): The name of the package to retrieve data for.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Tuple[str, str, str, str]: The installed and latest versions of the package, and the
|
|
32
|
+
local site-packages path where it is / would be installed.
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
QbraidException: If package version or location data cannot be retrieved.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
# pylint: disable=import-outside-toplevel
|
|
39
|
+
from qbraid_core.system import (
|
|
40
|
+
QbraidSystemError,
|
|
41
|
+
get_active_python_path,
|
|
42
|
+
get_active_site_packages_path,
|
|
43
|
+
get_latest_package_version,
|
|
44
|
+
get_local_package_version,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
python_pathlib = get_active_python_path()
|
|
49
|
+
site_packages_path = get_active_site_packages_path(python_path=python_pathlib)
|
|
50
|
+
installed_version = get_local_package_version(package, python_path=python_pathlib)
|
|
51
|
+
latest_version = get_latest_package_version(package)
|
|
52
|
+
except QbraidSystemError as err:
|
|
53
|
+
raise QbraidException("Failed to retrieve required system and/or package metadata") from err
|
|
54
|
+
|
|
55
|
+
return installed_version, latest_version, str(site_packages_path), str(python_pathlib)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def confirm_updates(
|
|
59
|
+
mode: str,
|
|
60
|
+
site_packages_path: str,
|
|
61
|
+
installed_version: Optional[str] = None,
|
|
62
|
+
latest_version: Optional[str] = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Prompts the user to proceed with enabling or disabling qBraid Quantum Jobs.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
mode (str): The mode of operation, either "enable" or "disable".
|
|
69
|
+
site_packages_path (str): The location of the site-packages directory where
|
|
70
|
+
target package(s) will be updated.
|
|
71
|
+
installed_version (optional, str): The installed version of the target package.
|
|
72
|
+
latest_version (optional, str): The latest version of the target package available on PyPI.
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
ValueError: If an invalid mode is provided.
|
|
76
|
+
typer.Exit: If the user declines to proceed with enabling or disabling qBraid Quantum Jobs.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
core_package = "botocore"
|
|
80
|
+
versioned_package = "boto3"
|
|
81
|
+
if mode == "enable":
|
|
82
|
+
provider = "qBraid"
|
|
83
|
+
update_msg = f"update {versioned_package} and install"
|
|
84
|
+
elif mode == "disable":
|
|
85
|
+
provider = "boto"
|
|
86
|
+
update_msg = "re-install"
|
|
87
|
+
else:
|
|
88
|
+
raise ValueError(f"Invalid mode: {mode}. Expected 'enable' or 'disable'.")
|
|
89
|
+
|
|
90
|
+
typer.echo(f"\n==> WARNING: {provider}/{core_package} package required <==")
|
|
91
|
+
if (
|
|
92
|
+
installed_version is not None
|
|
93
|
+
and latest_version is not None
|
|
94
|
+
and installed_version != latest_version
|
|
95
|
+
):
|
|
96
|
+
typer.echo(f"==> WARNING: A newer version of {versioned_package} exists. <==")
|
|
97
|
+
typer.echo(f" current version: {installed_version}")
|
|
98
|
+
typer.echo(f" latest version: {latest_version}")
|
|
99
|
+
|
|
100
|
+
gerund = mode[:-2].capitalize() + "ing"
|
|
101
|
+
|
|
102
|
+
typer.echo(
|
|
103
|
+
f"\n{gerund} quantum jobs will automatically {update_msg} {provider}/{core_package}, "
|
|
104
|
+
"which may cause incompatibilities with the amazon-braket-sdk and/or awscli.\n"
|
|
105
|
+
)
|
|
106
|
+
typer.echo("## Package Plan ##")
|
|
107
|
+
if mode == "enable":
|
|
108
|
+
typer.echo(
|
|
109
|
+
f" {versioned_package} location: {os.path.join(site_packages_path, versioned_package)}"
|
|
110
|
+
)
|
|
111
|
+
typer.echo(f" {core_package} location: {os.path.join(site_packages_path, core_package)}\n")
|
|
112
|
+
|
|
113
|
+
user_confirmation = typer.confirm("Proceed", default=True)
|
|
114
|
+
if not user_confirmation:
|
|
115
|
+
typer.echo("\nqBraidSystemExit: Exiting.")
|
|
116
|
+
raise typer.Exit()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def aws_configure_dummy() -> None:
|
|
120
|
+
"""
|
|
121
|
+
Initializes AWS configuration and credentials files with placeholder values.
|
|
122
|
+
|
|
123
|
+
"""
|
|
124
|
+
from qbraid_core.services.quantum.proxy_braket import aws_configure
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
handle_filesystem_operation(aws_configure, Path.home() / ".aws")
|
|
128
|
+
except QbraidException:
|
|
129
|
+
handle_error(message="Failed to configure qBraid quantum jobs.")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def enable_braket(auto_confirm: bool = False):
|
|
133
|
+
"""Enable qBraid quantum jobs for Amazon Braket."""
|
|
134
|
+
installed, latest, path, python_exe = run_progress_task(
|
|
135
|
+
get_package_data, "boto3", description="Solving environment..."
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if not auto_confirm:
|
|
139
|
+
confirm_updates("enable", path, installed_version=installed, latest_version=latest)
|
|
140
|
+
typer.echo("")
|
|
141
|
+
|
|
142
|
+
aws_configure_dummy() # TODO: possibly add another confirmation for writing aws config files
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
subprocess.check_call([python_exe, "-m", "pip", "install", "--upgrade", "boto3"])
|
|
146
|
+
subprocess.check_call([python_exe, "-m", "pip", "uninstall", "botocore", "-y", "--quiet"])
|
|
147
|
+
subprocess.check_call(
|
|
148
|
+
[python_exe, "-m", "pip", "install", "git+https://github.com/qBraid/botocore.git"]
|
|
149
|
+
)
|
|
150
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
151
|
+
handle_error(message="Failed to enable qBraid quantum jobs.")
|
|
152
|
+
|
|
153
|
+
typer.secho("\nSuccessfully enabled qBraid quantum jobs.", fg=typer.colors.GREEN, bold=True)
|
|
154
|
+
typer.secho("\nTo disable, run: \n\n\t$ qbraid jobs disable braket\n")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def disable_braket(auto_confirm: bool = False):
|
|
158
|
+
"""Disable qBraid quantum jobs for Amazon Braket."""
|
|
159
|
+
package = "botocore"
|
|
160
|
+
installed, latest, path, python_exe = run_progress_task(
|
|
161
|
+
get_package_data, package, description="Solving environment..."
|
|
162
|
+
)
|
|
163
|
+
package = f"{package}~={installed}" if installed < latest else package
|
|
164
|
+
|
|
165
|
+
if not auto_confirm:
|
|
166
|
+
confirm_updates("disable", path)
|
|
167
|
+
typer.echo("")
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
subprocess.check_call(
|
|
171
|
+
[
|
|
172
|
+
python_exe,
|
|
173
|
+
"-m",
|
|
174
|
+
"pip",
|
|
175
|
+
"install",
|
|
176
|
+
package,
|
|
177
|
+
"--force-reinstall",
|
|
178
|
+
],
|
|
179
|
+
stderr=subprocess.DEVNULL,
|
|
180
|
+
)
|
|
181
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
182
|
+
handle_error(message="Failed to disable qBraid quantum jobs.")
|
|
183
|
+
|
|
184
|
+
typer.secho("\nSuccessfully disabled qBraid quantum jobs.", fg=typer.colors.GREEN, bold=True)
|
|
185
|
+
typer.secho("\nTo enable, run: \n\n\t$ qbraid jobs enable braket\n")
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module for validating command arguments for qBraid Quantum Jobs.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Any, Callable, Dict, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
|
|
14
|
+
from qbraid_cli.handlers import handle_error, run_progress_task, validate_item
|
|
15
|
+
|
|
16
|
+
LEGACY_ARGS: Dict[str, str] = {
|
|
17
|
+
"amazon_braket": "braket",
|
|
18
|
+
"aws_braket": "braket",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def validate_library(value: str) -> str:
|
|
23
|
+
"""Validate quantum jobs library."""
|
|
24
|
+
# pylint:disable-next=import-outside-toplevel
|
|
25
|
+
from qbraid_core.services.quantum.proxy import SUPPORTED_QJOB_LIBS
|
|
26
|
+
|
|
27
|
+
qjobs_libs = list(SUPPORTED_QJOB_LIBS.keys())
|
|
28
|
+
|
|
29
|
+
if value in LEGACY_ARGS:
|
|
30
|
+
old_value = value
|
|
31
|
+
value = LEGACY_ARGS[value]
|
|
32
|
+
|
|
33
|
+
console = Console()
|
|
34
|
+
console.print(
|
|
35
|
+
f"[red]DeprecationWarning:[/red] Argument '{old_value}' "
|
|
36
|
+
f"is deprecated. Use '{value}' instead.\n"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return validate_item(value, qjobs_libs, "Library")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_state(library: Optional[str] = None) -> Tuple[str, Dict[str, Tuple[bool, bool]]]:
|
|
43
|
+
"""Get the state of qBraid Quantum Jobs for the specified library."""
|
|
44
|
+
from qbraid_core.services.quantum import QuantumClient
|
|
45
|
+
|
|
46
|
+
jobs_state = QuantumClient.qbraid_jobs_state(device_lib=library)
|
|
47
|
+
|
|
48
|
+
python_exe: str = jobs_state.get("exe", sys.executable)
|
|
49
|
+
libs_state: Dict[str, Any] = jobs_state.get("libs", {})
|
|
50
|
+
|
|
51
|
+
state_values = {
|
|
52
|
+
lib: (state["supported"], state["enabled"]) for lib, state in libs_state.items()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return python_exe, state_values
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def run_progress_get_state(
|
|
59
|
+
library: Optional[str] = None,
|
|
60
|
+
) -> Tuple[str, Dict[str, Tuple[bool, bool]]]:
|
|
61
|
+
"""Run get state function with rich progress UI."""
|
|
62
|
+
return run_progress_task(
|
|
63
|
+
get_state,
|
|
64
|
+
library,
|
|
65
|
+
description="Collecting package metadata...",
|
|
66
|
+
error_message=f"Failed to collect {library} package metadata.",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def handle_jobs_state(
|
|
71
|
+
library: str,
|
|
72
|
+
action: str, # 'enable' or 'disable'
|
|
73
|
+
action_callback: Callable[[], None],
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Handle the common logic for enabling or disabling qBraid Quantum Jobs."""
|
|
76
|
+
_, state_values = run_progress_get_state(library)
|
|
77
|
+
installed, enabled = state_values[library]
|
|
78
|
+
|
|
79
|
+
if not installed:
|
|
80
|
+
handle_error(
|
|
81
|
+
message=f"{library} not installed."
|
|
82
|
+
) # TODO: Provide command to install library?
|
|
83
|
+
if (enabled and action == "enable") or (not enabled and action == "disable"):
|
|
84
|
+
action_color = "green" if enabled else "red"
|
|
85
|
+
console = Console()
|
|
86
|
+
console.print(
|
|
87
|
+
f"\nqBraid quantum jobs already [bold {action_color}]{action}d[/bold {action_color}] "
|
|
88
|
+
f"for [magenta]{library}[/magenta].\n\nCheck the state of all quantum jobs "
|
|
89
|
+
"libraries in this environment with: \n\n\t$ qbraid jobs state\n"
|
|
90
|
+
)
|
|
91
|
+
raise typer.Exit()
|
|
92
|
+
|
|
93
|
+
action_callback() # Perform the specific enable/disable action
|