qbraid-cli 0.7.1__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 (41) hide show
  1. qbraid_cli/_version.py +14 -6
  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 +17 -5
  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.7.1.dist-info → qbraid_cli-0.8.0.dist-info}/METADATA +68 -46
  29. qbraid_cli-0.8.0.dist-info/RECORD +33 -0
  30. {qbraid_cli-0.7.1.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/_display.py +0 -44
  33. qbraid_cli/bin/qbraid.sh +0 -1346
  34. qbraid_cli/configure.py +0 -113
  35. qbraid_cli/envs.py +0 -195
  36. qbraid_cli/jobs.py +0 -226
  37. qbraid_cli/wrapper.py +0 -103
  38. qbraid_cli-0.7.1.data/scripts/qbraid.sh +0 -1346
  39. qbraid_cli-0.7.1.dist-info/RECORD +0 -15
  40. qbraid_cli-0.7.1.dist-info/entry_points.txt +0 -2
  41. {qbraid_cli-0.7.1.dist-info → qbraid_cli-0.8.0.dist-info}/top_level.txt +0 -0
qbraid_cli/configure.py DELETED
@@ -1,113 +0,0 @@
1
- """
2
- Update qbraidrc configuration file
3
-
4
- """
5
-
6
- import configparser
7
- import re
8
- from pathlib import Path
9
- from typing import Optional
10
-
11
-
12
- def load_config():
13
- """Load the configuration from the file."""
14
- config_path = Path.home() / ".qbraid" / "qbraidrc"
15
- config = configparser.ConfigParser()
16
- config.read(config_path)
17
- return config
18
-
19
-
20
- def save_config(config):
21
- """Save the configuration to the file."""
22
- config_path = Path.home() / ".qbraid"
23
-
24
- # Create the .qbraid directory if it doesn't exist
25
- config_path.mkdir(parents=True, exist_ok=True)
26
-
27
- # Write the configuration to the qbraidrc file
28
- with (config_path / "qbraidrc").open("w") as configfile:
29
- config.write(configfile)
30
-
31
-
32
- def validate_input(key, value):
33
- """Validate the user input based on the key."""
34
- if key == "url":
35
- if not re.match(r"^https?://\S+$", value):
36
- raise ValueError("Invalid URL format.")
37
- elif key == "email":
38
- if not re.match(r"^\S+@\S+\.\S+$", value):
39
- raise ValueError("Invalid email format.")
40
- elif key == "api-key":
41
- if not re.match(r"^[a-zA-Z0-9]{11}$", value):
42
- raise ValueError("Invalid API key format.")
43
- return value
44
-
45
-
46
- def prompt_for_config(config, section, key, default_values=None):
47
- """Prompt the user for a configuration setting, showing the current value as default."""
48
- default_values = default_values or {}
49
- current_value = config.get(section, key, fallback=default_values.get(key, ""))
50
- display_value = "None" if not current_value else current_value
51
-
52
- while True:
53
- try:
54
- new_value = input(f"Enter {key} [{display_value}]: ").strip()
55
- new_value = new_value or current_value
56
- return validate_input(key, new_value)
57
- except ValueError as e:
58
- print(f"Error: {e}")
59
-
60
-
61
- def configure():
62
- """Prompt the user to configure each setting."""
63
- try:
64
- config = load_config()
65
- except Exception as e: # pylint: disable=broad-exception-caught
66
- print(f"Error loading configuration: {e}")
67
- return
68
-
69
- section = "default"
70
-
71
- if section not in config:
72
- config[section] = {}
73
-
74
- default_values = {"url": "https://api.qbraid.com/api"}
75
-
76
- try:
77
- config[section]["url"] = prompt_for_config(
78
- config, section, "url", default_values
79
- )
80
- config[section]["email"] = prompt_for_config(
81
- config, section, "email", default_values
82
- )
83
- config[section]["api-key"] = prompt_for_config(
84
- config, section, "api-key", default_values
85
- )
86
-
87
- for key in list(config[section]):
88
- if not config[section][key]:
89
- del config[section][key]
90
-
91
- save_config(config)
92
- print("Configuration updated successfully.")
93
- except Exception as e: # pylint: disable=broad-exception-caught
94
- print(f"Error updating configuration: {e}")
95
-
96
-
97
- def configure_set(key: str, value: str, profile: Optional[str] = None):
98
- """Set a configuration setting."""
99
- try:
100
- config = load_config()
101
- except Exception as e: # pylint: disable=broad-exception-caught
102
- print(f"Error loading configuration: {e}")
103
- return
104
-
105
- section = profile or "default"
106
-
107
- if section not in config:
108
- config[section] = {}
109
-
110
- config[section][key] = value
111
-
112
- save_config(config)
113
- print("Configuration updated successfully.")
qbraid_cli/envs.py DELETED
@@ -1,195 +0,0 @@
1
- """
2
- Module for interfacing with qBraid environments.
3
-
4
- """
5
-
6
- import json
7
- import os
8
- import shutil
9
- import subprocess
10
- import sys
11
- import threading
12
- from typing import List, Optional
13
-
14
- from jupyter_client.kernelspec import KernelSpecManager
15
- from qbraid.api import QbraidSession
16
-
17
- from ._display import print_progress_cycle
18
-
19
- DEFAULT_VISIBILITY = "private"
20
-
21
- DEFAULT_ENVS_PATH = os.path.join(os.path.expanduser("~"), ".qbraid", "environments")
22
- QBRAID_ENVS_PATH = os.getenv("QBRAID_USR_ENVS", DEFAULT_ENVS_PATH)
23
-
24
-
25
- def create_alias(slug: str) -> str:
26
- """Create an alias from a slug."""
27
- return slug[:-7].replace("_", "-").strip("-")
28
-
29
-
30
- def replace_str(target: str, replacement: str, file_path: str) -> None:
31
- """Replace all instances of string in file"""
32
- with open(file_path, "r", encoding="utf-8") as file:
33
- content = file.read()
34
-
35
- content = content.replace(target, replacement)
36
-
37
- with open(file_path, "w", encoding="utf-8") as file:
38
- file.write(content)
39
-
40
-
41
- def update_install_status(
42
- slug_path: str, complete: int, success: int, message: Optional[str] = None
43
- ) -> None:
44
- """Update environment's install status values in a JSON file.
45
- Truth table values: 0 = False, 1 = True, -1 = Unknown
46
- """
47
- # Set default message if none provided
48
- message = "" if message is None else message.replace("\n", " ")
49
-
50
- # File path for state.json
51
- state_json_path = os.path.join(slug_path, "state.json")
52
-
53
- # Read existing data or use default structure
54
- if os.path.exists(state_json_path):
55
- with open(state_json_path, "r", encoding="utf-8") as f:
56
- data = json.load(f)
57
- else:
58
- data = {"install": {}}
59
-
60
- # Update the data
61
- data["install"]["complete"] = complete
62
- data["install"]["success"] = success
63
- data["install"]["message"] = message
64
-
65
- # Write updated data back to state.json
66
- with open(state_json_path, "w", encoding="utf-8") as f:
67
- json.dump(data, f, indent=4)
68
-
69
-
70
- def create_venv(slug_path: str, prompt: str) -> None:
71
- """Create virtual environment and swap PS1 display name."""
72
- venv_path = os.path.join(slug_path, "pyenv")
73
- subprocess.run([sys.executable, "-m", "venv", venv_path], check=True)
74
-
75
- bin_path = os.path.join(venv_path, "bin")
76
- activate_files = ["activate", "activate.csh", "activate.fish"]
77
-
78
- for file in activate_files:
79
- file_path = os.path.join(bin_path, file)
80
- replace_str("(pyenv)", f"({prompt})", file_path)
81
-
82
- replace_str(
83
- "include-system-site-packages = false",
84
- "include-system-site-packages = true",
85
- os.path.join(venv_path, "pyvenv.cfg"),
86
- )
87
-
88
- update_install_status(slug_path, 1, 1)
89
-
90
-
91
- def create_qbraid_env(slug: str, prompt: str, display_name: str) -> None:
92
- """Create a qBraid environment including python venv, PS1 configs,
93
- kernel resource files, and qBraid state.json."""
94
- slug_path = os.path.join(QBRAID_ENVS_PATH, slug)
95
- local_resource_dir = os.path.join(slug_path, "kernels", f"python3_{slug}")
96
- os.makedirs(local_resource_dir, exist_ok=True)
97
-
98
- # create state.json
99
- update_install_status(slug_path, 0, 0)
100
-
101
- # create kernel.json
102
- kernel_json_path = os.path.join(local_resource_dir, "kernel.json")
103
- kernel_spec_manager = KernelSpecManager()
104
- kernelspec_dict = kernel_spec_manager.get_all_specs()
105
- kernel_data = kernelspec_dict["python3"]["spec"]
106
- kernel_data["argv"][0] = os.path.join(slug_path, "pyenv", "bin", "python")
107
- kernel_data["display_name"] = display_name
108
- with open(kernel_json_path, "w", encoding="utf-8") as file:
109
- json.dump(kernel_data, file, indent=4)
110
-
111
- # copy logo files
112
- sys_resource_dir = kernelspec_dict["python3"]["resource_dir"]
113
- logo_files = ["logo-32x32.png", "logo-64x64.png", "logo-svg.svg"]
114
-
115
- for file in logo_files:
116
- sys_path = os.path.join(sys_resource_dir, file)
117
- loc_path = os.path.join(local_resource_dir, file)
118
- if os.path.isfile(sys_path):
119
- shutil.copy(sys_path, loc_path)
120
-
121
- # create python venv
122
- create_venv(slug_path, prompt)
123
-
124
-
125
- # pylint: disable-next=too-many-arguments,too-many-locals
126
- def create(
127
- name: str,
128
- description: Optional[str] = None,
129
- tags: Optional[List[str]] = None,
130
- code: Optional[str] = None,
131
- visibility: Optional[str] = None,
132
- kernel_name: Optional[str] = None,
133
- prompt: Optional[str] = None,
134
- ) -> None:
135
- """
136
- Create a new qBraid environment.
137
-
138
- Args:
139
- name (str): Name of the environment.
140
- description (Optional[str]): Description of the environment.
141
- tags (Optional[List[str]]): List of tags associated with the environment.
142
- code (Optional[str]): package list in requirements.txt format.
143
- visibility (Optional[str]): Visibility status (e.g., 'private').
144
- kernel_name (Optional[str]): Name of the kernel.
145
- prompt (Optional[str]): Prompt for the environment.
146
-
147
- Returns:
148
- None
149
- """
150
- req_body = {
151
- "name": name,
152
- "description": description or "",
153
- "tags": tags or "", # comma separated list of tags
154
- "code": code or "", # newline separated list of packages
155
- "visibility": visibility or DEFAULT_VISIBILITY,
156
- "kernelName": kernel_name or "",
157
- "prompt": prompt or "",
158
- }
159
-
160
- session = QbraidSession()
161
-
162
- try:
163
- resp = session.post("/environments/create", json=req_body).json()
164
- except Exception as e: # pylint: disable=broad-exception-caught
165
- print(f"Failed to create environment: {e}")
166
- return
167
-
168
- slug = resp.get("slug")
169
- if slug is None:
170
- print("Failed to create environment: no slug returned")
171
- return
172
-
173
- alias = create_alias(slug)
174
- kernel_name = kernel_name if kernel_name else f"Python 3 [{alias}]"
175
- prompt = prompt if prompt else alias
176
-
177
- stop_event = threading.Event()
178
- message = "Creating qBraid environment"
179
- interval = 0.5
180
-
181
- # Start the progress bar in a separate thread
182
- progress_thread = threading.Thread(
183
- target=print_progress_cycle,
184
- args=(
185
- stop_event,
186
- message,
187
- interval,
188
- ),
189
- )
190
- progress_thread.start()
191
- create_qbraid_env(slug, prompt, kernel_name)
192
- stop_event.set()
193
- progress_thread.join()
194
-
195
- print(f"\nSuccessfully created qBraid environment: {slug}\n")
qbraid_cli/jobs.py DELETED
@@ -1,226 +0,0 @@
1
- """
2
- Module containing functions to enable/disable quantum jobs.
3
-
4
- """
5
-
6
- import os
7
- import site
8
- import subprocess
9
- import sys
10
- import threading
11
- from importlib.metadata import PackageNotFoundError, version
12
-
13
- import requests
14
-
15
- from ._display import print_progress_linear
16
- from .exceptions import QuantumJobsException
17
-
18
-
19
- def get_latest_boto3_version() -> str:
20
- """Retrieves the latest version of botocore from PyPI."""
21
- url = "https://pypi.org/pypi/boto3/json"
22
- try:
23
- response = requests.get(url, timeout=5)
24
- response.raise_for_status()
25
- data = response.json()
26
- return data["info"]["version"]
27
- except requests.RequestException as err:
28
- raise QuantumJobsException("Failed to retrieve latest boto3 version.") from err
29
-
30
-
31
- def get_local_boto3_version() -> str:
32
- """Retrieves the local version of boto3."""
33
- try:
34
- return version("boto3")
35
- except PackageNotFoundError as err:
36
- raise QuantumJobsException(
37
- "boto3 is not installed in the current environment."
38
- ) from err
39
-
40
-
41
- def get_target_site_packages_path() -> str:
42
- """Retrieves the site-packages path of the current Python environment."""
43
-
44
- # List of all site-packages directories
45
- site_packages_paths = site.getsitepackages()
46
-
47
- if len(site_packages_paths) == 1:
48
- return site_packages_paths[0]
49
-
50
- # Path to the currently running Python interpreter
51
- python_executable_path = sys.executable
52
-
53
- # Base path of the Python environment
54
- env_base_path = os.path.dirname(os.path.dirname(python_executable_path))
55
-
56
- # Find the site-packages path that is within the same environment
57
- for path in site_packages_paths:
58
- if env_base_path in path:
59
- return path
60
-
61
- raise QuantumJobsException("Failed to find site-packages path.")
62
-
63
-
64
- def get_local_boto3_path() -> str:
65
- """Retrieves the local path of boto3."""
66
- try:
67
- site_packages_path = get_target_site_packages_path()
68
- return os.path.join(site_packages_path, "boto3")
69
- except PackageNotFoundError as err:
70
- raise QuantumJobsException(
71
- "boto3 is not installed in the current environment."
72
- ) from err
73
-
74
-
75
- def verify_boto3_compatible() -> None:
76
- """Verifies that the local version of boto3 is compatible with qBraid Quantum Jobs."""
77
- stop_event = threading.Event()
78
- message = "Collecting package metadata..."
79
- interval = 0.1
80
-
81
- # Start the progress bar in a separate thread
82
- progress_thread = threading.Thread(
83
- target=print_progress_linear,
84
- args=(
85
- stop_event,
86
- message,
87
- interval,
88
- ),
89
- )
90
- progress_thread.start()
91
-
92
- installed_version = get_local_boto3_version()
93
- latest_version = get_latest_boto3_version()
94
-
95
- installed_major, intalled_minor, _ = installed_version.split(".")
96
- latest_major, latest_minor, _ = latest_version.split(".")
97
-
98
- # Signal the progress thread to stop
99
- stop_event.set()
100
- progress_thread.join()
101
-
102
- if installed_major == latest_major and intalled_minor == latest_minor:
103
- return None
104
-
105
- boto3_location = get_local_boto3_path()
106
-
107
- print("==> WARNING: A newer version of boto3 exists. <==")
108
- print(f" current version: {installed_version}")
109
- print(f" latest version: {latest_version}\n\n")
110
- print(
111
- "Enabling quantum jobs will automatically update boto3, "
112
- "which may cause incompatibilities with the amazon-braket-sdk and/or awscli.\n\n"
113
- )
114
- print("## Package Plan ##\n")
115
- print(f" boto3 location: {boto3_location}\n\n")
116
- user_input = input("Proceed ([y]/n)? ")
117
-
118
- if user_input.lower() in ["n", "no"]:
119
- print("\nqBraidSystemExit: Exiting.")
120
- sys.exit()
121
-
122
- print("")
123
- subprocess.check_call(
124
- [sys.executable, "-m", "pip", "install", "--upgrade", "boto3"]
125
- )
126
- print("")
127
-
128
- return None
129
-
130
-
131
- def manage_package(action, package, exception_message) -> None:
132
- """Generic function to install or uninstall packages.
133
-
134
- Args:
135
- action (str): 'install' or 'uninstall'
136
- package (str): The package to install or uninstall
137
- exception_message (str): Error message to raise in case of exception
138
-
139
- Returns:
140
- None
141
-
142
- Raises:
143
- QuantumJobsException: If the action fails.
144
- """
145
- try:
146
- if action == "uninstall":
147
- subprocess.check_call(
148
- [sys.executable, "-m", "pip", "uninstall", package, "-y", "--quiet"]
149
- )
150
- elif action == "install":
151
- subprocess.check_call(
152
- [sys.executable, "-m", "pip", "install", package, "--quiet"]
153
- )
154
- except subprocess.CalledProcessError as err:
155
- raise QuantumJobsException(exception_message) from err
156
-
157
-
158
- def enable_quantum_jobs() -> None:
159
- """Enables qBraid quantum jobs."""
160
- try:
161
- verify_boto3_compatible()
162
- stop_event = threading.Event()
163
- message = "Installing qBraid botocore..."
164
- interval = 1.5
165
-
166
- # Start the progress bar in a separate thread
167
- progress_thread = threading.Thread(
168
- target=print_progress_linear,
169
- args=(
170
- stop_event,
171
- message,
172
- interval,
173
- ),
174
- )
175
- progress_thread.start()
176
- manage_package(
177
- "uninstall",
178
- "botocore",
179
- "Failed to install qBraid botocore.",
180
- )
181
- manage_package(
182
- "install",
183
- "git+https://github.com/qBraid/botocore.git",
184
- "Failed to install qBraid botocore.",
185
- )
186
- stop_event.set()
187
- progress_thread.join()
188
- except Exception as err:
189
- stop_event.set()
190
- progress_thread.join()
191
- raise QuantumJobsException("Failed to enable qBraid quantum jobs.") from err
192
-
193
- print("\nSuccessfully enabled qBraid quantum jobs.\n")
194
-
195
-
196
- def disable_quantum_jobs() -> None:
197
- """Disables qBraid quantum jobs."""
198
- try:
199
- stop_event = threading.Event()
200
- message = "Uninstalling qBraid botocore..."
201
- interval = 0.5
202
-
203
- # Start the progress bar in a separate thread
204
- progress_thread = threading.Thread(
205
- target=print_progress_linear,
206
- args=(
207
- stop_event,
208
- message,
209
- interval,
210
- ),
211
- )
212
- progress_thread.start()
213
- manage_package(
214
- "uninstall",
215
- "botocore",
216
- "Failed to uninstall qBraid botocore.",
217
- )
218
- manage_package("install", "botocore", "Failed to reinstall original boto3.")
219
- stop_event.set()
220
- progress_thread.join()
221
- except Exception as err:
222
- stop_event.set()
223
- progress_thread.join()
224
- raise QuantumJobsException("Failed to disable qBraid quantum jobs.") from err
225
-
226
- print("\nSuccessfully disabled qBraid quantum jobs.\n")
qbraid_cli/wrapper.py DELETED
@@ -1,103 +0,0 @@
1
- """
2
- Module to run the qbraid command line interface.
3
-
4
- Lazy loading is used to avoid loading the qbraid package until it is needed.
5
-
6
- """
7
-
8
- import os
9
- import subprocess
10
- import sys
11
-
12
- PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
13
-
14
- # pylint: disable=import-outside-toplevel
15
-
16
-
17
- def flag_help_command():
18
- """Check if the help command should be triggered."""
19
- if len(sys.argv) > 0:
20
- last_arg = sys.argv[-1]
21
- if last_arg in ["-h", "--help", "help"]:
22
- return True
23
- return False
24
-
25
-
26
- def is_help_command(arg_index):
27
- """Check if the help command should be triggered for the given argument index"""
28
- if flag_help_command() and arg_index == len(sys.argv) - 1:
29
- return True
30
- return False
31
-
32
-
33
- def get_credits():
34
- """Get the number of credits available to the user."""
35
- from qbraid.api import QbraidSession
36
-
37
- session = QbraidSession()
38
- res = session.get("/billing/credits/get-user-credits").json()
39
- qbraid_credits = res["qbraidCredits"]
40
- print(qbraid_credits)
41
-
42
-
43
- def main():
44
- """The subprocess.run function is used to run the script and pass arguments."""
45
- if len(sys.argv) == 2 and sys.argv[1] == "configure":
46
- from .configure import configure
47
-
48
- configure()
49
- if len(sys.argv) == 5 and sys.argv[1:3] == ["configure", "set"]:
50
- from .configure import configure_set
51
-
52
- configure_set(sys.argv[3], sys.argv[4])
53
- elif len(sys.argv) == 2 and sys.argv[1] == "credits":
54
- get_credits()
55
- elif sys.argv[1:] == ["--version"] or sys.argv[1:] == ["-V"]:
56
- from ._version import __version__
57
-
58
- print(f"qbraid-cli/{__version__}")
59
- elif len(sys.argv) == 3 and sys.argv[1:] == ["jobs", "list"]:
60
- from qbraid import get_jobs
61
-
62
- get_jobs()
63
- elif len(sys.argv) == 3 and sys.argv[1:] == ["devices", "list"]:
64
- from qbraid import get_devices
65
-
66
- get_devices()
67
- elif (
68
- len(sys.argv) == 5
69
- and sys.argv[1:3] == ["envs", "create"]
70
- and sys.argv[3] in ["-n", "--name"]
71
- ):
72
- from .envs import create
73
-
74
- create(sys.argv[4])
75
- else:
76
- result = subprocess.run(
77
- [os.path.join(PROJECT_ROOT, "bin", "qbraid.sh")] + sys.argv[1:],
78
- text=True,
79
- capture_output=True,
80
- check=False,
81
- )
82
-
83
- if result.stdout:
84
- if len(sys.argv) == 4 and sys.argv[2] == "activate":
85
- line_lst = result.stdout.split("\n")
86
- line_lst = line_lst[:-1] # remove trailing blank line
87
- bin_path = line_lst.pop() # last line contains bin_path
88
- std_out = "\n".join(line_lst) # all other lines are regular stdout
89
- print(std_out)
90
- # activate python environment using bin_path
91
- os.system(
92
- f"cat ~/.bashrc {bin_path}/activate > {bin_path}/activate2 && "
93
- rf"sed -i 's/echo -e/\# echo -e/' {bin_path}/activate2 && "
94
- f"/bin/bash --rcfile {bin_path}/activate2"
95
- )
96
- else:
97
- print(result.stdout)
98
- if result.stderr:
99
- print(result.stderr)
100
-
101
-
102
- if __name__ == "__main__":
103
- main()