qbraid-cli 0.8.0.dev1__py3-none-any.whl → 0.8.0.dev4__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 CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.8.0.dev1'
16
- __version_tuple__ = version_tuple = (0, 8, 0, 'dev1')
15
+ __version__ = version = '0.8.0.dev4'
16
+ __version_tuple__ = version_tuple = (0, 8, 0, 'dev4')
@@ -1,6 +1,9 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
1
4
  """
2
5
  Module defining the qbraid configure namespace
3
6
 
4
7
  """
5
8
 
6
- from .app import app
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.")
@@ -1,120 +1,23 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
1
4
  """
2
5
  Module defining commands in the 'qbraid configure' namespace.
3
6
 
4
7
  """
5
8
 
6
- import configparser
7
- import re
8
- from copy import deepcopy
9
- from pathlib import Path
10
- from typing import Dict, Optional
11
-
12
9
  import typer
10
+ from qbraid_core.config import load_config, save_config
13
11
 
14
- from qbraid_cli.exceptions import QbraidException
15
- from qbraid_cli.handlers import handle_filesystem_operation
12
+ from qbraid_cli.configure.actions import default_action
16
13
 
17
14
  # disable pretty_exceptions_show_locals to avoid printing sensative information in the traceback
18
- app = typer.Typer(help="Configure qBraid CLI options.", pretty_exceptions_show_locals=False)
19
-
20
-
21
- def load_config() -> configparser.ConfigParser:
22
- """Load the configuration from the file."""
23
- config_path = Path.home() / ".qbraid" / "qbraidrc"
24
- config = configparser.ConfigParser()
25
- try:
26
- config.read(config_path)
27
- except (FileNotFoundError, PermissionError, configparser.Error) as err:
28
- raise QbraidException(f"Failed to load configuration from {config_path}.") from err
29
-
30
- return config
31
-
32
-
33
- def save_config(config: configparser.ConfigParser) -> None:
34
- """Save configuration to qbraidrc file."""
35
- config_path = Path.home() / ".qbraid"
36
-
37
- def save_operation():
38
- config_path.mkdir(parents=True, exist_ok=True)
39
- with (config_path / "qbraidrc").open("w") as configfile:
40
- config.write(configfile)
41
-
42
- handle_filesystem_operation(save_operation, config_path)
43
-
44
-
45
- def validate_input(key: str, value: str) -> str:
46
- """Validate the user input based on the key.
47
-
48
- Args:
49
- key (str): The configuration key
50
- value (str): The user input value
51
-
52
- Returns:
53
- str: The validated value
54
-
55
- Raises:
56
- typer.BadParameter: If the value is invalid
57
- """
58
- if key == "url":
59
- if not re.match(r"^https?://\S+$", value):
60
- raise typer.BadParameter("Invalid URL format.")
61
- elif key == "email":
62
- if not re.match(r"^\S+@\S+\.\S+$", value):
63
- raise typer.BadParameter("Invalid email format.")
64
- elif key == "api-key":
65
- if not re.match(r"^[a-zA-Z0-9]{11}$", value):
66
- raise typer.BadParameter("Invalid API key format.")
67
- return value
68
-
69
-
70
- def prompt_for_config(
71
- config: configparser.ConfigParser,
72
- section: str,
73
- key: str,
74
- default_values: Optional[Dict[str, str]] = None,
75
- ) -> str:
76
- """Prompt the user for a configuration setting, showing the current value as default."""
77
- default_values = default_values or {}
78
- current_value = config.get(section, key, fallback=default_values.get(key, ""))
79
- display_value = "None" if not current_value else current_value
80
-
81
- if key == "api-key" and current_value:
82
- display_value = "*" * len(current_value[:-4]) + current_value[-4:]
83
-
84
- new_value = typer.prompt(f"Enter {key}", default=display_value, show_default=True).strip()
85
-
86
- if new_value == display_value:
87
- return current_value
88
-
89
- return validate_input(key, new_value)
90
-
91
-
92
- def default_action(section: str = "default"):
93
- """Configure qBraid CLI options."""
94
- config = load_config()
95
- original_config = deepcopy(config)
96
-
97
- if section not in config:
98
- config[section] = {}
99
-
100
- default_values = {"url": "https://api.qbraid.com/api"}
101
-
102
- config[section]["url"] = prompt_for_config(config, section, "url", default_values)
103
- config[section]["email"] = prompt_for_config(config, section, "email", default_values)
104
- config[section]["api-key"] = prompt_for_config(config, section, "api-key", default_values)
105
-
106
- for key in list(config[section]):
107
- if not config[section][key]:
108
- del config[section][key]
109
-
110
- if config == original_config:
111
- typer.echo("\nConfiguration saved, unchanged.")
112
- else:
113
- save_config(config)
114
- typer.echo("\nConfiguration updated successfully.")
15
+ configure_app = typer.Typer(
16
+ help="Configure qBraid CLI options.", pretty_exceptions_show_locals=False
17
+ )
115
18
 
116
19
 
117
- @app.callback(invoke_without_command=True)
20
+ @configure_app.callback(invoke_without_command=True)
118
21
  def configure(ctx: typer.Context):
119
22
  """
120
23
  Prompts user for configuration values such as your qBraid API Key.
@@ -129,7 +32,7 @@ def configure(ctx: typer.Context):
129
32
  default_action()
130
33
 
131
34
 
132
- @app.command(name="set")
35
+ @configure_app.command(name="set")
133
36
  def configure_set(
134
37
  name: str = typer.Argument(..., help="Config name"),
135
38
  value: str = typer.Argument(..., help="Config value"),
@@ -148,4 +51,4 @@ def configure_set(
148
51
 
149
52
 
150
53
  if __name__ == "__main__":
151
- app()
54
+ configure_app()
@@ -1,6 +1,9 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
1
4
  """
2
5
  Module defining the qbraid credits namespace
3
6
 
4
7
  """
5
8
 
6
- from .app import app
9
+ from .app import credits_app
qbraid_cli/credits/app.py CHANGED
@@ -1,3 +1,6 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
1
4
  """
2
5
  Module defining commands in the 'qbraid credits' namespace.
3
6
 
@@ -7,24 +10,23 @@ import typer
7
10
 
8
11
  from qbraid_cli.handlers import run_progress_task
9
12
 
10
- app = typer.Typer(help="Manage qBraid credits.")
13
+ credits_app = typer.Typer(help="Manage qBraid credits.")
11
14
 
12
15
 
13
- @app.command(name="value")
16
+ @credits_app.command(name="value")
14
17
  def credits_value():
15
18
  """Get number of qBraid credits remaining."""
16
19
 
17
- def get_credits() -> int:
18
- from qbraid.api import QbraidSession
20
+ def get_credits() -> float:
21
+ from qbraid_core import QbraidClient
19
22
 
20
- session = QbraidSession()
21
- res = session.get("/billing/credits/get-user-credits").json()
22
- return res["qbraidCredits"]
23
+ client = QbraidClient()
24
+ return client.user_credits_value()
23
25
 
24
- qbraid_credits: int = run_progress_task(get_credits)
25
- credits_text = typer.style(str(qbraid_credits), fg=typer.colors.GREEN, bold=True)
26
+ qbraid_credits: float = run_progress_task(get_credits)
27
+ credits_text = typer.style(f"{qbraid_credits:.2f}", fg=typer.colors.GREEN, bold=True)
26
28
  typer.secho(f"\nqBraid credits: {credits_text}")
27
29
 
28
30
 
29
31
  if __name__ == "__main__":
30
- app()
32
+ credits_app()
@@ -1,6 +1,9 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
1
4
  """
2
5
  Module defining the qbraid devices namespace
3
6
 
4
7
  """
5
8
 
6
- from .app import app
9
+ from .app import devices_app
qbraid_cli/devices/app.py CHANGED
@@ -1,34 +1,24 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
1
4
  """
2
5
  Module defining commands in the 'qbraid devices' namespace.
3
6
 
4
7
  """
5
8
 
6
- from typing import Callable, Optional, Tuple, Union
9
+ from typing import Any, Callable, Optional, Tuple
7
10
 
8
11
  import typer
12
+ from rich.console import Console
9
13
 
10
- from qbraid_cli.handlers import handle_error, run_progress_task, validate_item
11
-
12
- app = typer.Typer(help="Manage qBraid quantum devices.")
13
-
14
-
15
- def validate_status(value: Optional[str]) -> Union[str, None]:
16
- """Validate device status query parameter."""
17
- return validate_item(value, ["ONLINE", "OFFLINE", "RETIRED"], "Status")
18
-
19
-
20
- def validate_type(value: Optional[str]) -> Union[str, None]:
21
- """Validate device type query parameter."""
22
- return validate_item(value, ["QPU", "SIMULATOR"], "Type")
14
+ from qbraid_cli.devices.validation import validate_provider, validate_status, validate_type
15
+ from qbraid_cli.handlers import run_progress_task
23
16
 
17
+ devices_app = typer.Typer(help="Manage qBraid quantum devices.")
24
18
 
25
- def validate_provider(value: Optional[str]) -> Union[str, None]:
26
- """Validate device provider query parameter."""
27
- return validate_item(value, ["AWS", "IBM", "IonQ", "Rigetti", "OQC", "QuEra"], "Provider")
28
19
 
29
-
30
- @app.command(name="list")
31
- def devices_list(
20
+ @devices_app.command(name="list")
21
+ def devices_list( # pylint: disable=too-many-branches
32
22
  status: Optional[str] = typer.Option(
33
23
  None, "--status", "-s", help="'ONLINE'|'OFFLINE'|'RETIRED'", callback=validate_status
34
24
  ),
@@ -44,16 +34,6 @@ def devices_list(
44
34
  ),
45
35
  ) -> None:
46
36
  """List qBraid quantum devices."""
47
-
48
- def import_devices() -> Tuple[Callable, Exception]:
49
- from qbraid import get_devices
50
- from qbraid.exceptions import QbraidError
51
-
52
- return get_devices, QbraidError
53
-
54
- result: Tuple[Callable, Exception] = run_progress_task(import_devices)
55
- get_devices, QbraidError = result
56
-
57
37
  filters = {}
58
38
  if status:
59
39
  filters["status"] = status
@@ -62,11 +42,39 @@ def devices_list(
62
42
  if provider:
63
43
  filters["provider"] = provider
64
44
 
65
- try:
66
- get_devices(filters=filters)
67
- except QbraidError:
68
- handle_error(message="Failed to fetch quantum devices.")
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")
69
77
 
70
78
 
71
79
  if __name__ == "__main__":
72
- app()
80
+ devices_app()
@@ -0,0 +1,27 @@
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
+
10
+ from typing import Optional, Union
11
+
12
+ from qbraid_cli.handlers import validate_item
13
+
14
+
15
+ def validate_status(value: Optional[str]) -> Union[str, None]:
16
+ """Validate device status query parameter."""
17
+ return validate_item(value, ["ONLINE", "OFFLINE", "RETIRED"], "Status")
18
+
19
+
20
+ def validate_type(value: Optional[str]) -> Union[str, None]:
21
+ """Validate device type query parameter."""
22
+ return validate_item(value, ["QPU", "SIMULATOR"], "Type")
23
+
24
+
25
+ def validate_provider(value: Optional[str]) -> Union[str, None]:
26
+ """Validate device provider query parameter."""
27
+ return validate_item(value, ["AWS", "IBM", "IonQ", "Rigetti", "OQC", "QuEra"], "Provider")
@@ -1,6 +1,9 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
1
4
  """
2
5
  Module defining the qbraid envs namespace
3
6
 
4
7
  """
5
8
 
6
- from .app import app
9
+ from .app import envs_app
@@ -1,3 +1,6 @@
1
+ # Copyright (c) 2024, qBraid Development Team
2
+ # All rights reserved.
3
+
1
4
  """
2
5
  Module supporting 'qbraid envs activate' command.
3
6
 
@@ -31,13 +34,13 @@ def print_activate_command(venv_path: Path) -> None:
31
34
  # Windows operating system
32
35
  activate_script = venv_path / "Scripts" / "activate"
33
36
  activate_script_ps = venv_path / "Scripts" / "Activate.ps1"
34
- typer.echo(" " + str(activate_script))
37
+ typer.echo("\t$ " + str(activate_script))
35
38
  typer.echo("\nOr for PowerShell, use:\n")
36
- typer.echo(" " + f"& {activate_script_ps}")
39
+ typer.echo("\t$ " + f"& {activate_script_ps}")
37
40
  else:
38
41
  # Unix-like operating systems (Linux/macOS)
39
42
  activate_script = venv_path / "bin" / "activate"
40
- typer.echo(" " + f"source {activate_script}")
43
+ typer.echo("\t$ " + f"source {activate_script}")
41
44
  typer.echo("")
42
45
  raise typer.Exit()
43
46
 
@@ -48,12 +51,12 @@ def activate_pyvenv(venv_path: Path):
48
51
 
49
52
  if shell_path is None:
50
53
  print_activate_command(venv_path)
51
-
54
+ return # Return early since we can't proceed without a shell
52
55
  try:
53
56
  shell_rc = find_shell_rc(shell_path)
54
57
  except (FileNotFoundError, ValueError):
55
58
  print_activate_command(venv_path)
56
-
59
+ return # Return early since no suitable shell rc file was found
57
60
  bin_path = str(venv_path / "bin")
58
61
 
59
62
  os.system(