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/_version.py CHANGED
@@ -1,7 +1,16 @@
1
- """
2
- Module containing version information
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
3
9
 
4
- Version number (major.minor.patch[-label])
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
5
14
 
6
- """
7
- __version__ = "0.7.0"
15
+ __version__ = version = '0.8.0'
16
+ __version_tuple__ = version_tuple = (0, 8, 0)
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining the qbraid admin namespace
6
+
7
+ """
8
+
9
+ from .app import admin_app
@@ -0,0 +1,50 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining commands in the 'qbraid admin' namespace.
6
+
7
+ """
8
+
9
+ from typing import List
10
+
11
+ import typer
12
+
13
+ from qbraid_cli.admin.headers import check_and_fix_headers
14
+ from qbraid_cli.admin.validation import validate_header_type, validate_paths_exist
15
+
16
+ # disable pretty_exceptions_show_locals to avoid printing sensative information in the traceback
17
+ admin_app = typer.Typer(
18
+ help="CI/CD commands for qBraid maintainers.",
19
+ pretty_exceptions_show_locals=False,
20
+ )
21
+
22
+
23
+ @admin_app.command(name="headers")
24
+ def admin_headers(
25
+ src_paths: List[str] = typer.Argument(
26
+ ..., help="Source file or directory paths to verify.", callback=validate_paths_exist
27
+ ),
28
+ header_type: str = typer.Option(
29
+ "default",
30
+ "--type",
31
+ "-t",
32
+ help="Type of header to use ('default' or 'gpl').",
33
+ callback=validate_header_type,
34
+ ),
35
+ skip_files: List[str] = typer.Option(
36
+ [], "--skip", "-s", help="Files to skip during verification.", callback=validate_paths_exist
37
+ ),
38
+ fix: bool = typer.Option(
39
+ False, "--fix", "-f", help="Whether to fix the headers instead of just verifying."
40
+ ),
41
+ ):
42
+ """
43
+ Verifies and optionally fixes qBraid headers in specified files and directories.
44
+
45
+ """
46
+ check_and_fix_headers(src_paths, header_type=header_type, skip_files=skip_files, fix=fix)
47
+
48
+
49
+ if __name__ == "__main__":
50
+ admin_app()
@@ -0,0 +1,193 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Script to verify qBraid copyright file headers
6
+
7
+ """
8
+ import os
9
+ import sys
10
+ from typing import List, Optional
11
+
12
+ from rich.console import Console
13
+
14
+ # pylint: disable=too-many-branches,too-many-statements
15
+
16
+ DEFAULT_HEADER = """# Copyright (c) 2024, qBraid Development Team
17
+ # All rights reserved.
18
+ """
19
+
20
+ DEFAULT_HEADER_GPL = """# Copyright (C) 2024 qBraid
21
+ #
22
+ # This file is part of the qBraid-SDK
23
+ #
24
+ # The qBraid-SDK is free software released under the GNU General Public License v3
25
+ # or later. You can redistribute and/or modify it under the terms of the GPL v3.
26
+ # See the LICENSE file in the project root or <https://www.gnu.org/licenses/gpl-3.0.html>.
27
+ #
28
+ # THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3.
29
+ """
30
+
31
+ HEADER_TYPES = {
32
+ "default": DEFAULT_HEADER,
33
+ "gpl": DEFAULT_HEADER_GPL,
34
+ }
35
+
36
+
37
+ def check_and_fix_headers(
38
+ src_paths: List[str],
39
+ header_type: str = "default",
40
+ skip_files: Optional[List[str]] = None,
41
+ fix: bool = False,
42
+ ) -> None:
43
+ """Script to add or verify qBraid copyright file headers"""
44
+ try:
45
+ header = HEADER_TYPES[header_type]
46
+ except KeyError as err:
47
+ raise ValueError(
48
+ f"Invalid header type: {HEADER_TYPES}. Expected one of {list(HEADER_TYPES.keys())}"
49
+ ) from err
50
+
51
+ for path in src_paths:
52
+ if not os.path.exists(path):
53
+ sys.stderr.write(
54
+ f"Usage: qbraid verify_headers [OPTIONS] SRC ...\n"
55
+ f"Try 'qbraid verify_headers --help' for help.\n\n"
56
+ f"Error: Invalid value for 'SRC ...': Path '{path}' does not exist.\n"
57
+ )
58
+ sys.exit(1)
59
+
60
+ header_2023 = header.replace("2024", "2023")
61
+
62
+ skip_files = skip_files or []
63
+
64
+ failed_headers = []
65
+ fixed_headers = []
66
+
67
+ console = Console()
68
+
69
+ def should_skip(file_path: str, content: str) -> bool:
70
+ if file_path in skip_files:
71
+ return True
72
+
73
+ if os.path.basename(file_path) == "__init__.py":
74
+ return not content.strip()
75
+
76
+ skip_header_tag = "# qbraid: skip-header"
77
+ line_number = 0
78
+
79
+ for line in content.splitlines():
80
+ line_number += 1
81
+ if 5 <= line_number <= 30 and skip_header_tag in line:
82
+ return True
83
+ if line_number > 30:
84
+ break
85
+
86
+ return False
87
+
88
+ def replace_or_add_header(file_path: str, fix: bool = False) -> None:
89
+ with open(file_path, "r", encoding="ISO-8859-1") as f:
90
+ content = f.read()
91
+
92
+ # This finds the start of the actual content after skipping initial whitespace and comments.
93
+ lines = content.splitlines()
94
+ first_non_comment_line_index = next(
95
+ (i for i, line in enumerate(lines) if not line.strip().startswith("#")), None
96
+ )
97
+
98
+ # Prepare the content by stripping leading and trailing whitespace and separating into lines
99
+ actual_content = (
100
+ "\n".join(lines[first_non_comment_line_index:]).strip()
101
+ if first_non_comment_line_index is not None
102
+ else ""
103
+ )
104
+
105
+ # Check if the content already starts with the header or if the file should be skipped
106
+ if (
107
+ content.lstrip().startswith(header)
108
+ or content.lstrip().startswith(header_2023)
109
+ or should_skip(file_path, content)
110
+ ):
111
+ return
112
+
113
+ if not fix:
114
+ failed_headers.append(file_path)
115
+ else:
116
+ # Form the new content by combining the header, one blank line, and the actual content
117
+ new_content = header.strip() + "\n\n" + actual_content
118
+ with open(file_path, "w", encoding="ISO-8859-1") as f:
119
+ f.write(new_content)
120
+ fixed_headers.append(file_path)
121
+
122
+ def process_files_in_directory(directory: str, fix: bool = False) -> int:
123
+ count = 0
124
+ if not os.path.isdir(directory):
125
+ return count
126
+ for root, _, files in os.walk(directory):
127
+ for file in files:
128
+ if file.endswith(".py"):
129
+ file_path = os.path.join(root, file)
130
+ replace_or_add_header(file_path, fix)
131
+ count += 1
132
+ return count
133
+
134
+ checked = 0
135
+ for item in src_paths:
136
+ if os.path.isdir(item):
137
+ checked += process_files_in_directory(item, fix)
138
+ elif os.path.isfile(item) and item.endswith(".py"):
139
+ replace_or_add_header(item, fix)
140
+ checked += 1
141
+ else:
142
+ failed_headers.append(item)
143
+ print(f"File or directory not found: {item}")
144
+
145
+ if not fix:
146
+ if failed_headers:
147
+ for file in failed_headers:
148
+ console.print(f"[bold]would fix {file}[/bold]")
149
+ num_failed = len(failed_headers)
150
+ num_passed = checked - num_failed
151
+ s1, s2 = ("", "s") if num_failed == 1 else ("s", "")
152
+ s_passed = "" if num_passed == 1 else "s"
153
+ console.print("[bold]\nOh no![/bold] 💥 💔 💥")
154
+ if num_passed > 0:
155
+ punc = ", "
156
+ passed_msg = f"[blue]{num_passed}[/blue] file{s_passed} would be left unchanged."
157
+ else:
158
+ punc = "."
159
+ passed_msg = ""
160
+
161
+ failed_msg = f"[bold][blue]{num_failed}[/blue] file{s1} need{s2} updating{punc}[/bold]"
162
+ console.print(f"{failed_msg}{passed_msg}")
163
+ elif checked == 0:
164
+ console.print("[bold]No Python files present. Nothing to do[/bold] 😴")
165
+ else:
166
+ s_checked = "" if checked == 1 else "s"
167
+ console.print("[bold]All done![/bold] ✨ 🚀 ✨")
168
+ console.print(f"[blue]{checked}[/blue] file{s_checked} would be left unchanged.")
169
+
170
+ else:
171
+ for file in fixed_headers:
172
+ console.print(f"[bold]fixed {file}[/bold]")
173
+ num_fixed = len(fixed_headers)
174
+ num_ok = checked - num_fixed
175
+ s_fixed = "" if num_fixed == 1 else "s"
176
+ s_ok = "" if num_ok == 1 else "s"
177
+ console.print("\n[bold]All done![/bold] ✨ 🚀 ✨")
178
+ if num_ok > 0:
179
+ punc = ", "
180
+ unchanged_msg = f"[blue]{num_ok}[/blue] file{s_ok} left unchanged."
181
+ else:
182
+ punc = "."
183
+ unchanged_msg = ""
184
+
185
+ if num_fixed > 0:
186
+ fixed_msg = f"[bold][blue]{num_fixed}[/blue] file{s_fixed} fixed{punc}[/bold]"
187
+ else:
188
+ fixed_msg = ""
189
+
190
+ if fixed_msg or unchanged_msg:
191
+ console.print(f"{fixed_msg}{unchanged_msg}")
192
+ else:
193
+ console.print("[bold]No Python files present. Nothing to do[/bold] 😴")
@@ -0,0 +1,33 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module for validating command arguments for qBraid admin commands.
6
+
7
+ """
8
+
9
+ import os
10
+ from typing import List
11
+
12
+ import typer
13
+
14
+ from qbraid_cli.admin.headers import HEADER_TYPES
15
+ from qbraid_cli.handlers import _format_list_items, validate_item
16
+
17
+
18
+ def validate_header_type(value: str) -> str:
19
+ """Validate header type."""
20
+ header_types = list(HEADER_TYPES.keys())
21
+ return validate_item(value, header_types, "Header type")
22
+
23
+
24
+ def validate_paths_exist(paths: List[str]) -> List[str]:
25
+ """Verifies that each path in the provided list exists."""
26
+ non_existent_paths = [path for path in paths if not os.path.exists(path)]
27
+ if non_existent_paths:
28
+ if len(non_existent_paths) == 1:
29
+ raise typer.BadParameter(f"Path '{non_existent_paths[0]}' does not exist")
30
+ raise typer.BadParameter(
31
+ f"The following paths do not exist: {_format_list_items(non_existent_paths)}"
32
+ )
33
+ return paths
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining the qbraid configure namespace
6
+
7
+ """
8
+
9
+ from .app import configure_app
@@ -0,0 +1,111 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining actions invoked by 'qbraid configure' command(s).
6
+
7
+ """
8
+
9
+ import configparser
10
+ import re
11
+ from copy import deepcopy
12
+ from typing import Dict, Optional
13
+
14
+ import typer
15
+ from qbraid_core.config import (
16
+ DEFAULT_CONFIG_SECTION,
17
+ DEFAULT_ENDPOINT_URL,
18
+ USER_CONFIG_PATH,
19
+ load_config,
20
+ save_config,
21
+ )
22
+ from rich.console import Console
23
+
24
+ from qbraid_cli.handlers import handle_filesystem_operation
25
+
26
+
27
+ def validate_input(key: str, value: str) -> str:
28
+ """Validate the user input based on the key.
29
+
30
+ Args:
31
+ key (str): The configuration key
32
+ value (str): The user input value
33
+
34
+ Returns:
35
+ str: The validated value
36
+
37
+ Raises:
38
+ typer.BadParameter: If the value is invalid
39
+ """
40
+ if key == "url":
41
+ if not re.match(r"^https?://\S+$", value):
42
+ raise typer.BadParameter("Invalid URL format.")
43
+ elif key == "email":
44
+ if not re.match(r"^\S+@\S+\.\S+$", value):
45
+ raise typer.BadParameter("Invalid email format.")
46
+ elif key == "api-key":
47
+ if not re.match(r"^[a-zA-Z0-9]{11}$", value):
48
+ raise typer.BadParameter("Invalid API key format.")
49
+ return value
50
+
51
+
52
+ def prompt_for_config(
53
+ config: configparser.ConfigParser,
54
+ section: str,
55
+ key: str,
56
+ default_values: Optional[Dict[str, str]] = None,
57
+ ) -> str:
58
+ """Prompt the user for a configuration setting, showing the current value as default."""
59
+ default_values = default_values or {}
60
+ current_value = config.get(section, key, fallback=default_values.get(key, ""))
61
+ display_value = "None" if not current_value else current_value
62
+ hide_input = False
63
+ show_default = True
64
+
65
+ if section == DEFAULT_CONFIG_SECTION:
66
+ if key == "url":
67
+ return current_value
68
+
69
+ if key == "api-key":
70
+ if current_value:
71
+ display_value = "*" * len(current_value[:-4]) + current_value[-4:]
72
+ hide_input = True
73
+ show_default = False
74
+
75
+ new_value = typer.prompt(
76
+ f"Enter {key}", default=display_value, show_default=show_default, hide_input=hide_input
77
+ ).strip()
78
+
79
+ if new_value == display_value:
80
+ return current_value
81
+
82
+ return validate_input(key, new_value)
83
+
84
+
85
+ def default_action(section: str = DEFAULT_CONFIG_SECTION):
86
+ """Configure qBraid CLI options."""
87
+ config = load_config()
88
+ original_config = deepcopy(config)
89
+
90
+ if section not in config:
91
+ config[section] = {}
92
+
93
+ default_values = {"url": DEFAULT_ENDPOINT_URL}
94
+
95
+ config[section]["url"] = prompt_for_config(config, section, "url", default_values)
96
+ config[section]["api-key"] = prompt_for_config(config, section, "api-key", default_values)
97
+
98
+ for key in list(config[section]):
99
+ if not config[section][key]:
100
+ del config[section][key]
101
+
102
+ console = Console()
103
+ if config == original_config:
104
+ console.print("\n[bold grey70]Configuration saved, unchanged.")
105
+ else:
106
+
107
+ def _save_config():
108
+ save_config(config)
109
+
110
+ handle_filesystem_operation(_save_config, USER_CONFIG_PATH)
111
+ console.print("\n[bold green]Configuration updated successfully.")
@@ -0,0 +1,77 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining commands in the 'qbraid configure' namespace.
6
+
7
+ """
8
+
9
+ import typer
10
+ from rich.console import Console
11
+
12
+ from qbraid_cli.configure.actions import default_action
13
+
14
+ # disable pretty_exceptions_show_locals to avoid printing sensative information in the traceback
15
+ configure_app = typer.Typer(
16
+ help="Configure qBraid CLI options.", pretty_exceptions_show_locals=False
17
+ )
18
+
19
+
20
+ @configure_app.callback(invoke_without_command=True)
21
+ def configure(ctx: typer.Context):
22
+ """
23
+ Prompts user for configuration values such as your qBraid API Key.
24
+ If your config file does not exist (the default location is ~/.qbraid/qbraidrc),
25
+ the qBraid CLI will create it for you. To keep an existing value, hit enter
26
+ when prompted for the value. When you are prompted for information, the current
27
+ value will be displayed in [brackets]. If the config item has no value, it be
28
+ displayed as [None].
29
+
30
+ """
31
+ if ctx.invoked_subcommand is None:
32
+ default_action()
33
+
34
+
35
+ @configure_app.command(name="set")
36
+ def configure_set(
37
+ name: str = typer.Argument(..., help="Config name"),
38
+ value: str = typer.Argument(..., help="Config value"),
39
+ profile: str = typer.Option("default", help="Profile name"),
40
+ ):
41
+ """Set configuration value in qbraidrc file."""
42
+ # pylint: disable-next=import-outside-toplevel
43
+ from qbraid_core.config import load_config, save_config
44
+
45
+ config = load_config()
46
+
47
+ if profile not in config:
48
+ config[profile] = {}
49
+
50
+ config[profile][name] = value
51
+
52
+ save_config(config)
53
+ typer.echo("Configuration updated successfully.")
54
+
55
+
56
+ @configure_app.command(name="magic")
57
+ def configure_magic():
58
+ """Enable qBraid IPython magic commands."""
59
+ # pylint: disable-next=import-outside-toplevel
60
+ from qbraid_core.services.environments import add_magic_config
61
+
62
+ add_magic_config()
63
+
64
+ console = Console()
65
+
66
+ in_1 = (
67
+ "[green]In [[/green][yellow]1[/yellow][green]]:[/green] [blue]%[/blue]load_ext qbraid_magic"
68
+ )
69
+ in_2 = "[green]In [[/green][yellow]2[/yellow][green]]:[/green] [blue]%[/blue]qbraid"
70
+
71
+ console.print("\nSuccessfully configured qBraid IPython magic commands.\n")
72
+ console.print("You can now use the qBraid-CLI from inside a Jupyter notebook as follows:")
73
+ console.print(f"\n\t{in_1}\n\n\t{in_2}\n")
74
+
75
+
76
+ if __name__ == "__main__":
77
+ configure_app()
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining the qbraid credits namespace
6
+
7
+ """
8
+
9
+ from .app import credits_app
@@ -0,0 +1,32 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining commands in the 'qbraid credits' namespace.
6
+
7
+ """
8
+
9
+ import typer
10
+
11
+ from qbraid_cli.handlers import run_progress_task
12
+
13
+ credits_app = typer.Typer(help="Manage qBraid credits.")
14
+
15
+
16
+ @credits_app.command(name="value")
17
+ def credits_value():
18
+ """Get number of qBraid credits remaining."""
19
+
20
+ def get_credits() -> float:
21
+ from qbraid_core import QbraidClient
22
+
23
+ client = QbraidClient()
24
+ return client.user_credits_value()
25
+
26
+ qbraid_credits: float = run_progress_task(get_credits)
27
+ credits_text = typer.style(f"{qbraid_credits:.2f}", fg=typer.colors.GREEN, bold=True)
28
+ typer.secho(f"\nqBraid credits: {credits_text}")
29
+
30
+
31
+ if __name__ == "__main__":
32
+ credits_app()
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining the qbraid devices namespace
6
+
7
+ """
8
+
9
+ from .app import devices_app
@@ -0,0 +1,80 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining commands in the 'qbraid devices' namespace.
6
+
7
+ """
8
+
9
+ from typing import Any, Callable, Optional, Tuple
10
+
11
+ import typer
12
+ from rich.console import Console
13
+
14
+ from qbraid_cli.devices.validation import validate_provider, validate_status, validate_type
15
+ from qbraid_cli.handlers import run_progress_task
16
+
17
+ devices_app = typer.Typer(help="Manage qBraid quantum devices.")
18
+
19
+
20
+ @devices_app.command(name="list")
21
+ def devices_list( # pylint: disable=too-many-branches
22
+ status: Optional[str] = typer.Option(
23
+ None, "--status", "-s", help="'ONLINE'|'OFFLINE'|'RETIRED'", callback=validate_status
24
+ ),
25
+ device_type: Optional[str] = typer.Option(
26
+ None, "--type", "-t", help="'QPU'|'SIMULATOR'", callback=validate_type
27
+ ),
28
+ provider: Optional[str] = typer.Option(
29
+ None,
30
+ "--provider",
31
+ "-p",
32
+ help="'AWS'|'IBM'|'IonQ'|'Rigetti'|'OQC'|'QuEra'",
33
+ callback=validate_provider,
34
+ ),
35
+ ) -> None:
36
+ """List qBraid quantum devices."""
37
+ filters = {}
38
+ if status:
39
+ filters["status"] = status
40
+ if device_type:
41
+ filters["type"] = "Simulator" if device_type == "SIMULATOR" else device_type
42
+ if provider:
43
+ filters["provider"] = provider
44
+
45
+ def import_devices() -> Tuple[Any, Callable]:
46
+ from qbraid_core.services.quantum import QuantumClient, process_device_data
47
+
48
+ client = QuantumClient()
49
+
50
+ return client, process_device_data
51
+
52
+ result: Tuple[Callable, Callable] = run_progress_task(import_devices)
53
+ client, process_device_data = result
54
+ raw_data = client.search_devices(filters)
55
+ device_data, msg = process_device_data(raw_data)
56
+
57
+ console = Console()
58
+ header_1 = "Provider"
59
+ header_2 = "Device Name"
60
+ header_3 = "ID"
61
+ header_4 = "Status"
62
+ console.print(
63
+ f"\n[bold]{header_1.ljust(12)}{header_2.ljust(35)}{header_3.ljust(41)}{header_4}[/bold]"
64
+ )
65
+ for device_provider, device_name, device_id, device_status in device_data:
66
+ if device_status == "ONLINE":
67
+ status_color = "green"
68
+ elif device_status == "OFFLINE":
69
+ status_color = "red"
70
+ else:
71
+ status_color = "grey"
72
+ console.print(
73
+ f"{device_provider.ljust(12)}{device_name.ljust(35)}{device_id.ljust(40)}",
74
+ f"[{status_color}]{device_status}[/{status_color}]",
75
+ )
76
+ console.print(f"\n{msg}", style="italic", justify="left")
77
+
78
+
79
+ if __name__ == "__main__":
80
+ devices_app()
@@ -0,0 +1,26 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module for validating command arguments for qBraid devices commands.
6
+
7
+ """
8
+
9
+ from typing import Optional, Union
10
+
11
+ from qbraid_cli.handlers import validate_item
12
+
13
+
14
+ def validate_status(value: Optional[str]) -> Union[str, None]:
15
+ """Validate device status query parameter."""
16
+ return validate_item(value, ["ONLINE", "OFFLINE", "RETIRED"], "Status")
17
+
18
+
19
+ def validate_type(value: Optional[str]) -> Union[str, None]:
20
+ """Validate device type query parameter."""
21
+ return validate_item(value, ["QPU", "SIMULATOR"], "Type")
22
+
23
+
24
+ def validate_provider(value: Optional[str]) -> Union[str, None]:
25
+ """Validate device provider query parameter."""
26
+ return validate_item(value, ["AWS", "IBM", "IonQ", "Rigetti", "OQC", "QuEra"], "Provider")
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
4
+ """
5
+ Module defining the qbraid envs namespace
6
+
7
+ """
8
+
9
+ from .app import envs_app