qbraid-cli 0.8.0.dev0__py3-none-any.whl → 0.9.5__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 (45) hide show
  1. qbraid_cli/_version.py +2 -2
  2. qbraid_cli/account/__init__.py +11 -0
  3. qbraid_cli/account/app.py +65 -0
  4. qbraid_cli/admin/__init__.py +11 -0
  5. qbraid_cli/admin/app.py +59 -0
  6. qbraid_cli/admin/headers.py +235 -0
  7. qbraid_cli/admin/validation.py +32 -0
  8. qbraid_cli/chat/__init__.py +11 -0
  9. qbraid_cli/chat/app.py +76 -0
  10. qbraid_cli/configure/__init__.py +6 -1
  11. qbraid_cli/configure/actions.py +111 -0
  12. qbraid_cli/configure/app.py +34 -109
  13. qbraid_cli/devices/__init__.py +6 -1
  14. qbraid_cli/devices/app.py +43 -35
  15. qbraid_cli/devices/validation.py +26 -0
  16. qbraid_cli/envs/__init__.py +6 -1
  17. qbraid_cli/envs/activate.py +8 -5
  18. qbraid_cli/envs/app.py +215 -131
  19. qbraid_cli/envs/create.py +14 -120
  20. qbraid_cli/envs/data_handling.py +46 -0
  21. qbraid_cli/exceptions.py +3 -0
  22. qbraid_cli/files/__init__.py +11 -0
  23. qbraid_cli/files/app.py +118 -0
  24. qbraid_cli/handlers.py +47 -13
  25. qbraid_cli/jobs/__init__.py +6 -1
  26. qbraid_cli/jobs/app.py +84 -97
  27. qbraid_cli/jobs/toggle_braket.py +68 -74
  28. qbraid_cli/jobs/validation.py +94 -0
  29. qbraid_cli/kernels/__init__.py +6 -1
  30. qbraid_cli/kernels/app.py +81 -11
  31. qbraid_cli/main.py +66 -14
  32. qbraid_cli/pip/__init__.py +11 -0
  33. qbraid_cli/pip/app.py +50 -0
  34. qbraid_cli/pip/hooks.py +74 -0
  35. qbraid_cli/py.typed +0 -0
  36. qbraid_cli-0.9.5.dist-info/LICENSE +41 -0
  37. qbraid_cli-0.9.5.dist-info/METADATA +179 -0
  38. qbraid_cli-0.9.5.dist-info/RECORD +42 -0
  39. {qbraid_cli-0.8.0.dev0.dist-info → qbraid_cli-0.9.5.dist-info}/WHEEL +1 -1
  40. qbraid_cli/credits/__init__.py +0 -6
  41. qbraid_cli/credits/app.py +0 -30
  42. qbraid_cli-0.8.0.dev0.dist-info/METADATA +0 -124
  43. qbraid_cli-0.8.0.dev0.dist-info/RECORD +0 -25
  44. {qbraid_cli-0.8.0.dev0.dist-info → qbraid_cli-0.9.5.dist-info}/entry_points.txt +0 -0
  45. {qbraid_cli-0.8.0.dev0.dist-info → qbraid_cli-0.9.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,118 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining commands in the 'qbraid files' namespace.
6
+
7
+ """
8
+
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import rich
13
+ import typer
14
+
15
+ from qbraid_cli.handlers import run_progress_task
16
+
17
+ files_app = typer.Typer(help="Manage qBraid cloud storage files.", no_args_is_help=True)
18
+
19
+
20
+ @files_app.command(name="upload")
21
+ def files_upload(
22
+ filepath: Path = typer.Argument(
23
+ ...,
24
+ exists=True,
25
+ dir_okay=False,
26
+ resolve_path=True,
27
+ help="Local path to the file to upload.",
28
+ ),
29
+ namespace: str = typer.Option(
30
+ "user",
31
+ "--namespace",
32
+ "-n",
33
+ help="Target qBraid namespace for the upload.",
34
+ ),
35
+ object_path: str = typer.Option(
36
+ None,
37
+ "--object-path",
38
+ "-p",
39
+ help=("Target object path. " "Defaults to original filename in namespace root."),
40
+ ),
41
+ overwrite: bool = typer.Option(
42
+ False,
43
+ "--overwrite",
44
+ "-o",
45
+ help="Overwrite existing file if it already exists in the target location.",
46
+ ),
47
+ ):
48
+ """Upload a local file to qBraid storage."""
49
+
50
+ def upload_file() -> dict[str, Any]:
51
+ from qbraid_core.services.files import FileManagerClient
52
+
53
+ client = FileManagerClient()
54
+ data = client.upload_file(
55
+ filepath, namespace=namespace, object_path=object_path, overwrite=overwrite
56
+ )
57
+ return data
58
+
59
+ data: dict = run_progress_task(
60
+ upload_file, description="Uploading file...", include_error_traceback=False
61
+ )
62
+
63
+ rich.print("File uploaded successfully!")
64
+ namespace = data.get("namespace")
65
+ object_path = data.get("objectPath")
66
+
67
+ if namespace and object_path:
68
+ rich.print(f"\nNamespace: '{namespace}'")
69
+ rich.print(f"Object path: '{object_path}'")
70
+
71
+
72
+ @files_app.command(name="download")
73
+ def files_download(
74
+ object_path: str = typer.Argument(
75
+ ...,
76
+ help="The folder + filename describing the file to download.",
77
+ ),
78
+ namespace: str = typer.Option(
79
+ "user",
80
+ "--namespace",
81
+ "-n",
82
+ help="Source qBraid namespace for the download.",
83
+ ),
84
+ save_path: Path = typer.Option(
85
+ Path.cwd(),
86
+ "--save-path",
87
+ "-s",
88
+ resolve_path=True,
89
+ help="Local directory to save the downloaded file.",
90
+ ),
91
+ overwrite: bool = typer.Option(
92
+ False,
93
+ "--overwrite",
94
+ "-o",
95
+ help="Overwrite existing file if it already exists in the target location.",
96
+ ),
97
+ ):
98
+ """Download a file from qBraid storage."""
99
+
100
+ def download_file() -> Path:
101
+ from qbraid_core.services.files import FileManagerClient
102
+
103
+ client = FileManagerClient()
104
+ file_path = client.download_file(
105
+ object_path, namespace=namespace, save_path=save_path, overwrite=overwrite
106
+ )
107
+ return file_path
108
+
109
+ file_path: Path = run_progress_task(
110
+ download_file, description="Downloading file...", include_error_traceback=False
111
+ )
112
+
113
+ rich.print("File downloaded successfully!")
114
+ rich.print(f"Saved to: '{str(file_path)}'")
115
+
116
+
117
+ if __name__ == "__main__":
118
+ files_app()
qbraid_cli/handlers.py CHANGED
@@ -1,20 +1,38 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
1
4
  """
2
5
  Module providing application support utilities, including abstractions for error handling
3
6
  and executing operations with progress tracking within the qBraid CLI.
4
7
 
5
8
  """
6
9
 
10
+ import os
7
11
  import traceback
8
12
  from pathlib import Path
9
- from typing import Any, Callable, List, Optional, Union
13
+ from time import sleep
14
+ from typing import Any, Callable, Optional, Union
10
15
 
11
16
  import typer
12
17
  from rich.console import Console
13
- from rich.progress import Progress, SpinnerColumn, TextColumn
18
+ from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn
14
19
 
15
20
  from .exceptions import DEFAULT_ERROR_MESSAGE, QbraidException
16
21
 
17
22
 
23
+ def _should_display_progress():
24
+ """Whether to display rich progress UI."""
25
+ return os.getenv("QBRAID_CLI_SHOW_PROGRESS", "true").lower() in ["true", "1", "t", "y", "yes"]
26
+
27
+
28
+ def _update_completed_task(
29
+ progress: Progress, task_id: TaskID, success: bool = True, sleep_time: float = 0.15
30
+ ):
31
+ status = "Done" if success else "Failed"
32
+ progress.update(task_id, completed=100, status=status)
33
+ sleep(sleep_time)
34
+
35
+
18
36
  def handle_error(
19
37
  error_type: Optional[str] = None, message: Optional[str] = None, include_traceback: bool = True
20
38
  ) -> None:
@@ -38,10 +56,14 @@ def handle_error(
38
56
  error_type = error_type or "Error"
39
57
  message = message or DEFAULT_ERROR_MESSAGE
40
58
  error_prefix = typer.style(f"{error_type}:", fg=typer.colors.RED, bold=True)
41
- full_message = f"{error_prefix} {message}\n"
59
+ full_message = f"\n{error_prefix} {message}\n"
42
60
  if include_traceback:
43
61
  tb_string = traceback.format_exc()
44
- full_message += f"\n{tb_string}"
62
+ # TODO: find out reason for weird traceback emitted from
63
+ # qbraid jobs enable/disable when library not installed.
64
+ # For now, if matches, just don't print it.
65
+ if tb_string.strip() != "NoneType: None":
66
+ full_message += f"\n{tb_string}"
45
67
  typer.echo(full_message, err=True)
46
68
  raise typer.Exit(code=1)
47
69
 
@@ -72,6 +94,7 @@ def run_progress_task(
72
94
  *args,
73
95
  description: Optional[str] = None,
74
96
  error_message: Optional[str] = None,
97
+ include_error_traceback: bool = True,
75
98
  **kwargs,
76
99
  ) -> Any:
77
100
  """
@@ -89,6 +112,8 @@ def run_progress_task(
89
112
  error_message (optional, str): Custom error message to display if the operation.
90
113
  fails. Defaults to None, in which case the
91
114
  exception's message is used.
115
+ include_error_traceback (bool): Whether to include the traceback in the error message.
116
+ Defaults to True.
92
117
  **kwargs: Arbitrary keyword arguments for the operation.
93
118
 
94
119
  Returns:
@@ -97,6 +122,13 @@ def run_progress_task(
97
122
  Raises:
98
123
  typer.Exit: If the operation fails, after displaying the error message using typer.secho.
99
124
  """
125
+ if not _should_display_progress():
126
+ try:
127
+ return operation(*args, **kwargs)
128
+ except Exception as err: # pylint: disable=broad-exception-caught
129
+ custom_message = error_message if error_message else str(err)
130
+ return handle_error(message=custom_message, include_traceback=include_error_traceback)
131
+
100
132
  console = Console()
101
133
  with Progress(
102
134
  "[progress.description]{task.description}",
@@ -108,21 +140,23 @@ def run_progress_task(
108
140
  task = progress.add_task(description, status="In Progress", total=None)
109
141
  try:
110
142
  result = operation(*args, **kwargs)
111
- progress.update(task, completed=100, status="Done")
143
+ _update_completed_task(progress, task, success=True)
112
144
  return result
113
- except Exception as e: # pylint: disable=broad-exception-caught
114
- progress.update(task, completed=100, status="Failed")
115
- custom_message = error_message if error_message else str(e)
116
- return handle_error(message=custom_message)
145
+ except Exception as err: # pylint: disable=broad-exception-caught
146
+ _update_completed_task(progress, task, success=False)
147
+ custom_message = error_message if error_message else str(err)
148
+ return handle_error(message=custom_message, include_traceback=include_error_traceback)
149
+ finally:
150
+ progress.remove_task(task)
117
151
 
118
152
 
119
- def _format_list_items(items: List[str]) -> str:
153
+ def _format_list_items(items: list[str]) -> str:
120
154
  """
121
155
  Formats a list of items as a string with values comma-separated and
122
156
  each item surrounded by single quotes
123
157
 
124
158
  Args:
125
- items (List[str]): The list of items to format.
159
+ items (list[str]): The list of items to format.
126
160
 
127
161
  Returns:
128
162
  str: The formatted string.
@@ -131,14 +165,14 @@ def _format_list_items(items: List[str]) -> str:
131
165
 
132
166
 
133
167
  def validate_item(
134
- value: Optional[str], allowed_items: List[str], item_type: str
168
+ value: Optional[str], allowed_items: list[str], item_type: str
135
169
  ) -> Union[str, None]:
136
170
  """
137
171
  Generic item validation function.
138
172
 
139
173
  Args:
140
174
  value (optional, str): The value to validate.
141
- allowed_items (List[str]): A list of allowed items.
175
+ allowed_items (list[str]): A list of allowed items.
142
176
  item_type (str): A description of the item type (e.g., 'provider', 'status', 'type') for
143
177
  error messages.
144
178
 
@@ -1,6 +1,11 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
1
4
  """
2
5
  Module defining the qbraid jobs namespace
3
6
 
4
7
  """
5
8
 
6
- from .app import app
9
+ from .app import jobs_app
10
+
11
+ __all__ = ["jobs_app"]
qbraid_cli/jobs/app.py CHANGED
@@ -1,165 +1,152 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
1
4
  """
2
5
  Module defining commands in the 'qbraid jobs' namespace.
3
6
 
4
7
  """
5
8
 
6
- from typing import Callable, Dict, Optional, Tuple
9
+ from typing import Any, Callable
7
10
 
8
11
  import typer
9
12
  from rich.console import Console
10
13
 
11
- from qbraid_cli.handlers import handle_error, run_progress_task, validate_item
12
-
13
- app = typer.Typer(help="Manage qBraid quantum jobs.")
14
-
15
- QJOB_LIBS = ["braket"]
16
-
17
-
18
- def validate_library(value: str) -> str:
19
- """Validate quantum jobs library."""
20
- return validate_item(value, QJOB_LIBS, "Library")
21
-
22
-
23
- def get_state(library: Optional[str] = None) -> Dict[str, Tuple[bool, bool]]:
24
- """Get the state of qBraid Quantum Jobs for the specified library."""
25
- from qbraid.api.system import qbraid_jobs_state
26
-
27
- state_values = {}
14
+ from qbraid_cli.handlers import handle_error, run_progress_task
15
+ from qbraid_cli.jobs.toggle_braket import disable_braket, enable_braket
16
+ from qbraid_cli.jobs.validation import handle_jobs_state, run_progress_get_state, validate_library
28
17
 
29
- if library:
30
- libraries_to_check = [library]
31
- else:
32
- libraries_to_check = QJOB_LIBS
18
+ jobs_app = typer.Typer(help="Manage qBraid quantum jobs.", no_args_is_help=True)
33
19
 
34
- for lib in libraries_to_check:
35
- state_values[lib] = qbraid_jobs_state(lib)
36
20
 
37
- return state_values
38
-
39
-
40
- def run_progress_get_state(library: Optional[str] = None) -> Dict[str, Tuple[bool, bool]]:
41
- """Run get state function with rich progress UI."""
42
- return run_progress_task(
43
- get_state,
44
- library,
45
- description="Collecting package metadata...",
46
- error_message=f"Failed to collect {library} package metadata.",
47
- )
48
-
49
-
50
- def handle_jobs_state(
51
- library: str,
52
- action: str, # 'enable' or 'disable'
53
- action_callback: Callable[[], None],
54
- ) -> None:
55
- """Handle the common logic for enabling or disabling qBraid Quantum Jobs."""
56
- state_values: Dict[str, Tuple[bool, bool]] = run_progress_get_state(library)
57
- installed, enabled = state_values[library]
58
-
59
- if not installed:
60
- handle_error(message=f"{library} not installed.")
61
- if (enabled and action == "enable") or (not enabled and action == "disable"):
62
- action_color = "green" if enabled else "red"
63
- console = Console()
64
- console.print(
65
- f"\nqBraid quantum jobs already [bold {action_color}]{action}d[/bold {action_color}] "
66
- f"for [magenta]{library}[/magenta]."
67
- )
68
- console.print(
69
- "To check the state of all quantum jobs libraries in this environment, "
70
- "use: `[bold]qbraid jobs state[/bold]`"
71
- )
72
- raise typer.Exit()
73
-
74
- action_callback() # Perform the specific enable/disable action
75
-
76
-
77
- @app.command(name="enable")
21
+ @jobs_app.command(name="enable")
78
22
  def jobs_enable(
79
23
  library: str = typer.Argument(
80
24
  ..., help="Software library with quantum jobs support.", callback=validate_library
81
- )
25
+ ),
26
+ auto_confirm: bool = typer.Option(
27
+ False, "--yes", "-y", help="Automatically answer 'yes' to all prompts"
28
+ ),
82
29
  ) -> None:
83
30
  """Enable qBraid Quantum Jobs."""
84
31
 
85
32
  def enable_action():
86
33
  if library == "braket":
87
- from .toggle_braket import enable_braket
88
-
89
- enable_braket()
34
+ enable_braket(auto_confirm=auto_confirm)
90
35
  else:
91
36
  raise RuntimeError(f"Unsupported device library: '{library}'.")
92
37
 
93
38
  handle_jobs_state(library, "enable", enable_action)
94
39
 
95
40
 
96
- @app.command(name="disable")
41
+ @jobs_app.command(name="disable")
97
42
  def jobs_disable(
98
43
  library: str = typer.Argument(
99
44
  ..., help="Software library with quantum jobs support.", callback=validate_library
100
- )
45
+ ),
46
+ auto_confirm: bool = typer.Option(
47
+ False, "--yes", "-y", help="Automatically answer 'yes' to all prompts"
48
+ ),
101
49
  ) -> None:
102
50
  """Disable qBraid Quantum Jobs."""
103
51
 
104
52
  def disable_action():
105
53
  if library == "braket":
106
- from .toggle_braket import disable_braket
107
-
108
- disable_braket()
54
+ disable_braket(auto_confirm=auto_confirm)
109
55
  else:
110
56
  raise RuntimeError(f"Unsupported device library: '{library}'.")
111
57
 
112
58
  handle_jobs_state(library, "disable", disable_action)
113
59
 
114
60
 
115
- @app.command(name="state")
61
+ @jobs_app.command(name="state")
116
62
  def jobs_state(
117
63
  library: str = typer.Argument(
118
64
  default=None,
119
65
  help="Optional: Specify a software library with quantum jobs support to check its status.",
120
66
  callback=validate_library,
121
- )
67
+ ),
122
68
  ) -> None:
123
69
  """Display the state of qBraid Quantum Jobs for the current environment."""
124
- state_values: Dict[str, Tuple[bool, bool]] = run_progress_get_state(library)
70
+ result: tuple[str, dict[str, tuple[bool, bool]]] = run_progress_get_state(library)
71
+ python_exe, state_values = result
72
+ state_values = dict(sorted(state_values.items()))
125
73
 
126
74
  console = Console()
127
- console.print("\nLibrary State", style="bold")
75
+ header_1, header_2 = "Library", "State"
76
+ max_lib_length = max((len(lib) for lib in state_values.keys()), default=len(header_1))
77
+ padding = max_lib_length + 9
128
78
 
79
+ output = ""
129
80
  for lib, (installed, enabled) in state_values.items():
130
- if installed:
131
- if enabled:
132
- console.print(f"{lib:<12} ", "[green]enabled", end="")
133
- else:
134
- console.print(f"{lib:<12} ", "[red]disabled", end="")
135
- else:
136
- console.print(f"{lib:<12} n/a", end="")
81
+ state_str = (
82
+ "[green]enabled"
83
+ if enabled and installed
84
+ else "[red]disabled" if installed else "[grey70]unavailable"
85
+ )
86
+ output += f"{lib:<{padding-1}} {state_str}\n"
137
87
 
138
- console.print("\n")
88
+ console.print(f"Executable: {python_exe}")
89
+ console.print(f"\n{header_1:<{padding}}{header_2}", style="bold")
90
+ console.print(output)
139
91
 
140
92
 
141
- @app.command(name="list")
93
+ @jobs_app.command(name="list")
142
94
  def jobs_list(
143
95
  limit: int = typer.Option(
144
96
  10, "--limit", "-l", help="Limit the maximum number of results returned"
145
- )
97
+ ),
146
98
  ) -> None:
147
99
  """List qBraid Quantum Jobs."""
148
100
 
149
- def import_jobs() -> Tuple[Callable, Exception]:
150
- from qbraid import get_jobs
151
- from qbraid.exceptions import QbraidError
101
+ def import_jobs() -> tuple[Any, Callable]:
102
+ from qbraid_core.services.quantum import QuantumClient, process_job_data
152
103
 
153
- return get_jobs, QbraidError
104
+ client = QuantumClient()
154
105
 
155
- result: Tuple[Callable, Exception] = run_progress_task(import_jobs)
156
- get_jobs, QbraidError = result
106
+ return client, process_job_data
157
107
 
108
+ result: tuple[Any, Callable] = run_progress_task(import_jobs)
109
+ client, process_job_data = result
110
+ # https://github.com/qBraid/api/issues/644
111
+ # raw_data = client.search_jobs(query={"numResults": limit})
112
+ raw_data = client.search_jobs(query={})
113
+ job_data, msg = process_job_data(raw_data)
114
+ job_data = job_data[:limit]
115
+
116
+ longest_job_id = max(len(item[0]) for item in job_data)
117
+ spacing = longest_job_id + 5
158
118
  try:
159
- get_jobs(filters={"numResults": limit})
160
- except QbraidError:
119
+ console = Console()
120
+ header_1 = "Job ID"
121
+ header_2 = "Submitted"
122
+ header_3 = "Status"
123
+ console.print(f"[bold]{header_1.ljust(spacing)}{header_2.ljust(36)}{header_3}[/bold]")
124
+ for job_id, submitted, status in job_data:
125
+ if status == "COMPLETED":
126
+ status_color = "green"
127
+ elif status in ["FAILED", "CANCELLED"]:
128
+ status_color = "red"
129
+ elif status in [
130
+ "INITIALIZING",
131
+ "INITIALIZED",
132
+ "CREATED",
133
+ "QUEUED",
134
+ "VALIDATING",
135
+ "RUNNING",
136
+ ]:
137
+ status_color = "blue"
138
+ else:
139
+ status_color = "grey"
140
+ console.print(
141
+ f"{job_id.ljust(spacing)}{submitted.ljust(35)}",
142
+ f"[{status_color}]{status}[/{status_color}]",
143
+ )
144
+
145
+ console.print(f"\n{msg}", style="italic", justify="left")
146
+
147
+ except Exception: # pylint: disable=broad-exception-caught
161
148
  handle_error(message="Failed to fetch quantum jobs.")
162
149
 
163
150
 
164
151
  if __name__ == "__main__":
165
- app()
152
+ jobs_app()