qbraid-cli 0.8.0.dev1__py3-none-any.whl → 0.9.6__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 +93 -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 +116 -197
  19. qbraid_cli/envs/create.py +13 -109
  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 +46 -12
  25. qbraid_cli/jobs/__init__.py +6 -1
  26. qbraid_cli/jobs/app.py +76 -93
  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 +74 -17
  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.6.dist-info/LICENSE +41 -0
  37. qbraid_cli-0.9.6.dist-info/METADATA +179 -0
  38. qbraid_cli-0.9.6.dist-info/RECORD +42 -0
  39. {qbraid_cli-0.8.0.dev1.dist-info → qbraid_cli-0.9.6.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.dev1.dist-info/METADATA +0 -136
  43. qbraid_cli-0.8.0.dev1.dist-info/RECORD +0 -25
  44. {qbraid_cli-0.8.0.dev1.dist-info → qbraid_cli-0.9.6.dist-info}/entry_points.txt +0 -0
  45. {qbraid_cli-0.8.0.dev1.dist-info → qbraid_cli-0.9.6.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:
@@ -41,7 +59,11 @@ def handle_error(
41
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,128 +1,74 @@
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
- import sys
7
- from typing import Callable, Dict, Optional, Tuple
9
+ from typing import Any, Callable
8
10
 
9
11
  import typer
10
12
  from rich.console import Console
11
13
 
12
- from qbraid_cli.handlers import handle_error, run_progress_task, validate_item
13
-
14
- app = typer.Typer(help="Manage qBraid quantum jobs.")
15
-
16
- QJOB_LIBS = ["braket"]
17
-
18
-
19
- def validate_library(value: str) -> str:
20
- """Validate quantum jobs library."""
21
- return validate_item(value, QJOB_LIBS, "Library")
22
-
23
-
24
- def get_state(library: Optional[str] = None) -> Dict[str, Tuple[bool, bool]]:
25
- """Get the state of qBraid Quantum Jobs for the specified library."""
26
- from qbraid.api.system import qbraid_jobs_state
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
27
17
 
28
- state_values = {}
18
+ jobs_app = typer.Typer(help="Manage qBraid quantum jobs.", no_args_is_help=True)
29
19
 
30
- if library:
31
- libraries_to_check = [library]
32
- else:
33
- libraries_to_check = QJOB_LIBS
34
20
 
35
- for lib in libraries_to_check:
36
- state_values[lib] = qbraid_jobs_state(lib)
37
-
38
- return state_values
39
-
40
-
41
- def run_progress_get_state(library: Optional[str] = None) -> Dict[str, Tuple[bool, bool]]:
42
- """Run get state function with rich progress UI."""
43
- return run_progress_task(
44
- get_state,
45
- library,
46
- description="Collecting package metadata...",
47
- error_message=f"Failed to collect {library} package metadata.",
48
- )
49
-
50
-
51
- def handle_jobs_state(
52
- library: str,
53
- action: str, # 'enable' or 'disable'
54
- action_callback: Callable[[], None],
55
- ) -> None:
56
- """Handle the common logic for enabling or disabling qBraid Quantum Jobs."""
57
- state_values: Dict[str, Tuple[bool, bool]] = run_progress_get_state(library)
58
- installed, enabled = state_values[library]
59
-
60
- if not installed:
61
- handle_error(message=f"{library} not installed.")
62
- if (enabled and action == "enable") or (not enabled and action == "disable"):
63
- action_color = "green" if enabled else "red"
64
- console = Console()
65
- console.print(
66
- f"\nqBraid quantum jobs already [bold {action_color}]{action}d[/bold {action_color}] "
67
- f"for [magenta]{library}[/magenta]."
68
- )
69
- console.print(
70
- "To check the state of all quantum jobs libraries in this environment, "
71
- "use: `[bold]qbraid jobs state[/bold]`"
72
- )
73
- raise typer.Exit()
74
-
75
- action_callback() # Perform the specific enable/disable action
76
-
77
-
78
- @app.command(name="enable")
21
+ @jobs_app.command(name="enable")
79
22
  def jobs_enable(
80
23
  library: str = typer.Argument(
81
24
  ..., help="Software library with quantum jobs support.", callback=validate_library
82
- )
25
+ ),
26
+ auto_confirm: bool = typer.Option(
27
+ False, "--yes", "-y", help="Automatically answer 'yes' to all prompts"
28
+ ),
83
29
  ) -> None:
84
30
  """Enable qBraid Quantum Jobs."""
85
31
 
86
32
  def enable_action():
87
33
  if library == "braket":
88
- from .toggle_braket import enable_braket
89
-
90
- enable_braket()
34
+ enable_braket(auto_confirm=auto_confirm)
91
35
  else:
92
36
  raise RuntimeError(f"Unsupported device library: '{library}'.")
93
37
 
94
38
  handle_jobs_state(library, "enable", enable_action)
95
39
 
96
40
 
97
- @app.command(name="disable")
41
+ @jobs_app.command(name="disable")
98
42
  def jobs_disable(
99
43
  library: str = typer.Argument(
100
44
  ..., help="Software library with quantum jobs support.", callback=validate_library
101
- )
45
+ ),
46
+ auto_confirm: bool = typer.Option(
47
+ False, "--yes", "-y", help="Automatically answer 'yes' to all prompts"
48
+ ),
102
49
  ) -> None:
103
50
  """Disable qBraid Quantum Jobs."""
104
51
 
105
52
  def disable_action():
106
53
  if library == "braket":
107
- from .toggle_braket import disable_braket
108
-
109
- disable_braket()
54
+ disable_braket(auto_confirm=auto_confirm)
110
55
  else:
111
56
  raise RuntimeError(f"Unsupported device library: '{library}'.")
112
57
 
113
58
  handle_jobs_state(library, "disable", disable_action)
114
59
 
115
60
 
116
- @app.command(name="state")
61
+ @jobs_app.command(name="state")
117
62
  def jobs_state(
118
63
  library: str = typer.Argument(
119
64
  default=None,
120
65
  help="Optional: Specify a software library with quantum jobs support to check its status.",
121
66
  callback=validate_library,
122
- )
67
+ ),
123
68
  ) -> None:
124
69
  """Display the state of qBraid Quantum Jobs for the current environment."""
125
- 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
126
72
  state_values = dict(sorted(state_values.items()))
127
73
 
128
74
  console = Console()
@@ -130,40 +76,77 @@ def jobs_state(
130
76
  max_lib_length = max((len(lib) for lib in state_values.keys()), default=len(header_1))
131
77
  padding = max_lib_length + 9
132
78
 
133
- console.print(f"Executable: {sys.executable}")
134
- console.print(f"\n{header_1:<{padding}}{header_2}", style="bold")
135
-
79
+ output = ""
136
80
  for lib, (installed, enabled) in state_values.items():
137
81
  state_str = (
138
82
  "[green]enabled"
139
83
  if enabled and installed
140
84
  else "[red]disabled" if installed else "[grey70]unavailable"
141
85
  )
142
- console.print(f"{lib:<{padding-1}}", state_str, end="\n")
86
+ output += f"{lib:<{padding-1}} {state_str}\n"
87
+
88
+ console.print(f"Executable: {python_exe}")
89
+ console.print(f"\n{header_1:<{padding}}{header_2}", style="bold")
90
+ console.print(output)
143
91
 
144
92
 
145
- @app.command(name="list")
93
+ @jobs_app.command(name="list")
146
94
  def jobs_list(
147
95
  limit: int = typer.Option(
148
96
  10, "--limit", "-l", help="Limit the maximum number of results returned"
149
- )
97
+ ),
150
98
  ) -> None:
151
99
  """List qBraid Quantum Jobs."""
152
100
 
153
- def import_jobs() -> Tuple[Callable, Exception]:
154
- from qbraid import get_jobs
155
- from qbraid.exceptions import QbraidError
101
+ def import_jobs() -> tuple[Any, Callable]:
102
+ from qbraid_core.services.quantum import QuantumClient, process_job_data
103
+
104
+ client = QuantumClient()
156
105
 
157
- return get_jobs, QbraidError
106
+ return client, process_job_data
158
107
 
159
- result: Tuple[Callable, Exception] = run_progress_task(import_jobs)
160
- get_jobs, QbraidError = result
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]
161
115
 
116
+ longest_job_id = max(len(item[0]) for item in job_data)
117
+ spacing = longest_job_id + 5
162
118
  try:
163
- get_jobs(filters={"numResults": limit})
164
- 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
165
148
  handle_error(message="Failed to fetch quantum jobs.")
166
149
 
167
150
 
168
151
  if __name__ == "__main__":
169
- app()
152
+ jobs_app()