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.

Files changed (39) hide show
  1. qbraid_cli/_version.py +14 -5
  2. qbraid_cli/admin/__init__.py +9 -0
  3. qbraid_cli/admin/app.py +50 -0
  4. qbraid_cli/admin/headers.py +193 -0
  5. qbraid_cli/admin/validation.py +33 -0
  6. qbraid_cli/configure/__init__.py +9 -0
  7. qbraid_cli/configure/actions.py +111 -0
  8. qbraid_cli/configure/app.py +77 -0
  9. qbraid_cli/credits/__init__.py +9 -0
  10. qbraid_cli/credits/app.py +32 -0
  11. qbraid_cli/devices/__init__.py +9 -0
  12. qbraid_cli/devices/app.py +80 -0
  13. qbraid_cli/devices/validation.py +26 -0
  14. qbraid_cli/envs/__init__.py +9 -0
  15. qbraid_cli/envs/activate.py +65 -0
  16. qbraid_cli/envs/app.py +270 -0
  17. qbraid_cli/envs/create.py +128 -0
  18. qbraid_cli/envs/data_handling.py +140 -0
  19. qbraid_cli/exceptions.py +24 -0
  20. qbraid_cli/handlers.py +168 -0
  21. qbraid_cli/jobs/__init__.py +9 -0
  22. qbraid_cli/jobs/app.py +149 -0
  23. qbraid_cli/jobs/toggle_braket.py +185 -0
  24. qbraid_cli/jobs/validation.py +93 -0
  25. qbraid_cli/kernels/__init__.py +9 -0
  26. qbraid_cli/kernels/app.py +111 -0
  27. qbraid_cli/main.py +80 -0
  28. qbraid_cli-0.8.0.dist-info/METADATA +143 -0
  29. qbraid_cli-0.8.0.dist-info/RECORD +33 -0
  30. {qbraid_cli-0.7.0.dist-info → qbraid_cli-0.8.0.dist-info}/WHEEL +1 -1
  31. qbraid_cli-0.8.0.dist-info/entry_points.txt +2 -0
  32. qbraid_cli/bin/qbraid.sh +0 -1317
  33. qbraid_cli/configure.py +0 -87
  34. qbraid_cli/wrapper.py +0 -91
  35. qbraid_cli-0.7.0.data/scripts/qbraid.sh +0 -1317
  36. qbraid_cli-0.7.0.dist-info/METADATA +0 -118
  37. qbraid_cli-0.7.0.dist-info/RECORD +0 -11
  38. qbraid_cli-0.7.0.dist-info/entry_points.txt +0 -2
  39. {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]
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining the qbraid jobs namespace
6
+
7
+ """
8
+
9
+ from .app import jobs_app
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
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining the qbraid kernels namespace
6
+
7
+ """
8
+
9
+ from .app import kernels_app