qbraid-cli 0.8.0.dev1__py3-none-any.whl → 0.8.0.dev3__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 +2 -2
- qbraid_cli/configure/__init__.py +4 -1
- qbraid_cli/configure/actions.py +111 -0
- qbraid_cli/configure/app.py +11 -108
- qbraid_cli/credits/__init__.py +4 -1
- qbraid_cli/credits/app.py +12 -10
- qbraid_cli/devices/__init__.py +4 -1
- qbraid_cli/devices/app.py +43 -35
- qbraid_cli/devices/validation.py +27 -0
- qbraid_cli/envs/__init__.py +4 -1
- qbraid_cli/envs/activate.py +8 -5
- qbraid_cli/envs/app.py +38 -155
- qbraid_cli/envs/create.py +3 -0
- qbraid_cli/envs/data_handling.py +140 -0
- qbraid_cli/exceptions.py +3 -0
- qbraid_cli/handlers.py +3 -0
- qbraid_cli/jobs/__init__.py +4 -1
- qbraid_cli/jobs/app.py +66 -85
- qbraid_cli/jobs/toggle_braket.py +25 -23
- qbraid_cli/jobs/validation.py +74 -0
- qbraid_cli/kernels/__init__.py +4 -1
- qbraid_cli/kernels/app.py +81 -12
- qbraid_cli/main.py +15 -8
- {qbraid_cli-0.8.0.dev1.dist-info → qbraid_cli-0.8.0.dev3.dist-info}/METADATA +13 -9
- qbraid_cli-0.8.0.dev3.dist-info/RECORD +29 -0
- {qbraid_cli-0.8.0.dev1.dist-info → qbraid_cli-0.8.0.dev3.dist-info}/WHEEL +1 -1
- qbraid_cli-0.8.0.dev1.dist-info/RECORD +0 -25
- {qbraid_cli-0.8.0.dev1.dist-info → qbraid_cli-0.8.0.dev3.dist-info}/entry_points.txt +0 -0
- {qbraid_cli-0.8.0.dev1.dist-info → qbraid_cli-0.8.0.dev3.dist-info}/top_level.txt +0 -0
qbraid_cli/envs/app.py
CHANGED
|
@@ -1,150 +1,27 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
1
4
|
"""
|
|
2
5
|
Module defining commands in the 'qbraid envs' namespace.
|
|
3
6
|
|
|
4
7
|
"""
|
|
5
8
|
|
|
6
|
-
import json
|
|
7
|
-
import keyword
|
|
8
|
-
import re
|
|
9
9
|
import shutil
|
|
10
10
|
import subprocess
|
|
11
11
|
import sys
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import Any, Dict,
|
|
13
|
+
from typing import Any, Dict, Optional, Tuple
|
|
14
14
|
|
|
15
15
|
import typer
|
|
16
16
|
from rich.console import Console
|
|
17
17
|
|
|
18
|
+
from qbraid_cli.envs.data_handling import installed_envs_data, request_delete_env, validate_env_name
|
|
18
19
|
from qbraid_cli.handlers import QbraidException, run_progress_task
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def is_valid_env_name(env_name: str) -> bool: # pylint: disable=too-many-return-statements
|
|
24
|
-
"""
|
|
25
|
-
Validates a Python virtual environment name against best practices.
|
|
26
|
-
|
|
27
|
-
This function checks if the given environment name is valid based on certain
|
|
28
|
-
criteria, including length, use of special characters, reserved names, and
|
|
29
|
-
operating system-specific restrictions.
|
|
30
|
-
|
|
31
|
-
Args:
|
|
32
|
-
env_name (str): The name of the Python virtual environment to validate.
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
bool: True if the name is valid, False otherwise.
|
|
36
|
-
|
|
37
|
-
Raises:
|
|
38
|
-
ValueError: If the environment name is not a string or is empty.
|
|
39
|
-
"""
|
|
40
|
-
# Basic checks for empty names or purely whitespace names
|
|
41
|
-
if not env_name or env_name.isspace():
|
|
42
|
-
return False
|
|
43
|
-
|
|
44
|
-
# Check for invalid characters, including shell metacharacters and spaces
|
|
45
|
-
if re.search(r'[<>:"/\\|?*\s&;()$[\]#~!{}]', env_name):
|
|
46
|
-
return False
|
|
47
|
-
|
|
48
|
-
if env_name.startswith("tmp"):
|
|
49
|
-
return False
|
|
50
|
-
|
|
51
|
-
# Reserved names for Windows (example list, can be expanded)
|
|
52
|
-
reserved_names = [
|
|
53
|
-
"CON",
|
|
54
|
-
"PRN",
|
|
55
|
-
"AUX",
|
|
56
|
-
"NUL",
|
|
57
|
-
"COM1",
|
|
58
|
-
"COM2",
|
|
59
|
-
"COM3",
|
|
60
|
-
"COM4",
|
|
61
|
-
"COM5",
|
|
62
|
-
"COM6",
|
|
63
|
-
"COM7",
|
|
64
|
-
"COM8",
|
|
65
|
-
"COM9",
|
|
66
|
-
"LPT1",
|
|
67
|
-
"LPT2",
|
|
68
|
-
"LPT3",
|
|
69
|
-
"LPT4",
|
|
70
|
-
"LPT5",
|
|
71
|
-
"LPT6",
|
|
72
|
-
"LPT7",
|
|
73
|
-
"LPT8",
|
|
74
|
-
"LPT9",
|
|
75
|
-
]
|
|
76
|
-
if env_name.upper() in reserved_names:
|
|
77
|
-
return False
|
|
78
|
-
|
|
79
|
-
if len(env_name) > 20:
|
|
80
|
-
return False
|
|
81
|
-
|
|
82
|
-
# Check against Python reserved words
|
|
83
|
-
if keyword.iskeyword(env_name):
|
|
84
|
-
return False
|
|
85
|
-
|
|
86
|
-
# Check if it starts with a number, which is not a good practice
|
|
87
|
-
if env_name[0].isdigit():
|
|
88
|
-
return False
|
|
89
|
-
|
|
90
|
-
return True
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def validate_env_name(value: str) -> str:
|
|
94
|
-
"""Validate environment name."""
|
|
95
|
-
if not is_valid_env_name(value):
|
|
96
|
-
raise typer.BadParameter(
|
|
97
|
-
f"Invalid environment name '{value}'. " "Please use a valid Python environment name."
|
|
98
|
-
)
|
|
99
|
-
return value
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def installed_envs_data() -> Tuple[Dict[str, Path], Dict[str, str]]:
|
|
103
|
-
"""Gather paths and aliases for all installed qBraid environments."""
|
|
104
|
-
from qbraid.api.system import get_qbraid_envs_paths, is_valid_slug
|
|
105
|
-
|
|
106
|
-
installed = {}
|
|
107
|
-
aliases = {}
|
|
108
|
-
|
|
109
|
-
qbraid_env_paths: List[Path] = get_qbraid_envs_paths()
|
|
110
|
-
|
|
111
|
-
for env_path in qbraid_env_paths:
|
|
112
|
-
for entry in env_path.iterdir():
|
|
113
|
-
if entry.is_dir() and is_valid_slug(entry.name):
|
|
114
|
-
installed[entry.name] = entry
|
|
21
|
+
envs_app = typer.Typer(help="Manage qBraid environments.")
|
|
115
22
|
|
|
116
|
-
if entry.name == "qbraid_000000":
|
|
117
|
-
aliases["default"] = entry.name
|
|
118
|
-
continue
|
|
119
23
|
|
|
120
|
-
|
|
121
|
-
if state_json_path.exists():
|
|
122
|
-
try:
|
|
123
|
-
with open(state_json_path, "r", encoding="utf-8") as f:
|
|
124
|
-
data = json.load(f)
|
|
125
|
-
aliases[data.get("name", entry.name[:-7])] = entry.name
|
|
126
|
-
# pylint: disable-next=broad-exception-caught
|
|
127
|
-
except (json.JSONDecodeError, Exception):
|
|
128
|
-
aliases[entry.name[:-7]] = entry.name
|
|
129
|
-
else:
|
|
130
|
-
aliases[entry.name[:-7]] = entry.name
|
|
131
|
-
|
|
132
|
-
return installed, aliases
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def request_delete_env(slug: str) -> str:
|
|
136
|
-
"""Send request to delete environment given slug."""
|
|
137
|
-
from qbraid.api import QbraidSession, RequestsApiError
|
|
138
|
-
|
|
139
|
-
session = QbraidSession()
|
|
140
|
-
|
|
141
|
-
try:
|
|
142
|
-
session.delete(f"/environments/{slug}")
|
|
143
|
-
except RequestsApiError as err:
|
|
144
|
-
raise QbraidException("Delete environment request failed") from err
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
@app.command(name="create")
|
|
24
|
+
@envs_app.command(name="create")
|
|
148
25
|
def envs_create( # pylint: disable=too-many-statements
|
|
149
26
|
name: str = typer.Option(
|
|
150
27
|
..., "--name", "-n", help="Name of the environment to create", callback=validate_env_name
|
|
@@ -152,13 +29,16 @@ def envs_create( # pylint: disable=too-many-statements
|
|
|
152
29
|
description: Optional[str] = typer.Option(
|
|
153
30
|
None, "--description", "-d", help="Short description of the environment"
|
|
154
31
|
),
|
|
32
|
+
auto_confirm: bool = typer.Option(
|
|
33
|
+
False, "--yes", "-y", help="Automatically answer 'yes' to all prompts"
|
|
34
|
+
),
|
|
155
35
|
) -> None:
|
|
156
36
|
"""Create a new qBraid environment."""
|
|
157
37
|
from .create import create_qbraid_env_assets, create_venv
|
|
158
38
|
|
|
159
39
|
def request_new_env(req_body: Dict[str, str]) -> Dict[str, Any]:
|
|
160
40
|
"""Send request to create new environment and return the slug."""
|
|
161
|
-
from
|
|
41
|
+
from qbraid_core import QbraidSession, RequestsApiError
|
|
162
42
|
|
|
163
43
|
session = QbraidSession()
|
|
164
44
|
|
|
@@ -176,9 +56,9 @@ def envs_create( # pylint: disable=too-many-statements
|
|
|
176
56
|
|
|
177
57
|
def gather_local_data() -> Tuple[Path, str]:
|
|
178
58
|
"""Gather environment data and return the slug."""
|
|
179
|
-
from
|
|
59
|
+
from qbraid_core.services.environments import get_default_envs_paths
|
|
180
60
|
|
|
181
|
-
env_path =
|
|
61
|
+
env_path = get_default_envs_paths()[0]
|
|
182
62
|
|
|
183
63
|
result = subprocess.run(
|
|
184
64
|
[sys.executable, "--version"],
|
|
@@ -237,7 +117,7 @@ def envs_create( # pylint: disable=too-many-statements
|
|
|
237
117
|
typer.echo(f" location: {slug_path}")
|
|
238
118
|
typer.echo(f" version: {python_version}\n")
|
|
239
119
|
|
|
240
|
-
user_confirmation = typer.confirm("Proceed", default=True)
|
|
120
|
+
user_confirmation = auto_confirm or typer.confirm("Proceed", default=True)
|
|
241
121
|
typer.echo("")
|
|
242
122
|
if not user_confirmation:
|
|
243
123
|
request_delete_env(slug)
|
|
@@ -276,9 +156,12 @@ def envs_create( # pylint: disable=too-many-statements
|
|
|
276
156
|
typer.echo("# $ deactivate")
|
|
277
157
|
|
|
278
158
|
|
|
279
|
-
@
|
|
159
|
+
@envs_app.command(name="remove")
|
|
280
160
|
def envs_remove(
|
|
281
|
-
name: str = typer.Option(..., "-n", "--name", help="Name of the environment to remove")
|
|
161
|
+
name: str = typer.Option(..., "-n", "--name", help="Name of the environment to remove"),
|
|
162
|
+
auto_confirm: bool = typer.Option(
|
|
163
|
+
False, "--yes", "-y", help="Automatically answer 'yes' to all prompts"
|
|
164
|
+
),
|
|
282
165
|
) -> None:
|
|
283
166
|
"""Delete a qBraid environment."""
|
|
284
167
|
|
|
@@ -304,8 +187,7 @@ def envs_remove(
|
|
|
304
187
|
"Are you sure you want to continue?"
|
|
305
188
|
)
|
|
306
189
|
|
|
307
|
-
|
|
308
|
-
if typer.confirm(confirmation_message, abort=True):
|
|
190
|
+
if auto_confirm or typer.confirm(confirmation_message, abort=True):
|
|
309
191
|
typer.echo("")
|
|
310
192
|
run_progress_task(
|
|
311
193
|
request_delete_env,
|
|
@@ -321,25 +203,24 @@ def envs_remove(
|
|
|
321
203
|
error_message="Failed to delete qBraid environment",
|
|
322
204
|
)
|
|
323
205
|
typer.echo(f"\nEnvironment '{name}' successfully removed.")
|
|
324
|
-
# console = Console()
|
|
325
|
-
# console.print(
|
|
326
|
-
# f"\n[bold green]Successfully deleted qBraid environment: "
|
|
327
|
-
# f"[/bold green][bold magenta]{name}[/bold magenta]\n"
|
|
328
|
-
# )
|
|
329
206
|
|
|
330
207
|
|
|
331
|
-
@
|
|
208
|
+
@envs_app.command(name="list")
|
|
332
209
|
def envs_list():
|
|
333
210
|
"""List installed qBraid environments."""
|
|
334
211
|
installed, aliases = installed_envs_data()
|
|
335
212
|
|
|
213
|
+
console = Console()
|
|
214
|
+
|
|
336
215
|
if len(installed) == 0:
|
|
337
|
-
print(
|
|
338
|
-
|
|
216
|
+
console.print(
|
|
217
|
+
"No qBraid environments installed.\n\n"
|
|
218
|
+
+ "Use 'qbraid envs create' to create a new environment.",
|
|
219
|
+
style="yellow",
|
|
220
|
+
)
|
|
339
221
|
return
|
|
340
222
|
|
|
341
223
|
alias_path_pairs = [(alias, installed[slug_name]) for alias, slug_name in aliases.items()]
|
|
342
|
-
|
|
343
224
|
sorted_alias_path_pairs = sorted(
|
|
344
225
|
alias_path_pairs,
|
|
345
226
|
key=lambda x: (x[0] != "default", str(x[1]).startswith(str(Path.home())), x[0]),
|
|
@@ -348,21 +229,23 @@ def envs_list():
|
|
|
348
229
|
current_env_path = Path(sys.executable).parent.parent.parent
|
|
349
230
|
|
|
350
231
|
max_alias_length = (
|
|
351
|
-
max(len(alias) for alias,
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
max(len(str(path)) for _, path in sorted_alias_path_pairs) if sorted_alias_path_pairs else 0
|
|
232
|
+
max(len(str(alias)) for alias, envpath in sorted_alias_path_pairs)
|
|
233
|
+
if sorted_alias_path_pairs
|
|
234
|
+
else 0
|
|
355
235
|
)
|
|
356
236
|
|
|
357
237
|
print("# qbraid environments:")
|
|
358
238
|
print("#")
|
|
359
239
|
print("")
|
|
240
|
+
|
|
360
241
|
for alias, path in sorted_alias_path_pairs:
|
|
361
|
-
mark = "*
|
|
362
|
-
|
|
242
|
+
mark = "*" if path == current_env_path else " "
|
|
243
|
+
# Format each line with spacing based on the longest alias for alignment
|
|
244
|
+
line = f"{alias.ljust(max_alias_length+3)}{mark} {path}" # fix the most optimal spacing
|
|
245
|
+
console.print(line)
|
|
363
246
|
|
|
364
247
|
|
|
365
|
-
@
|
|
248
|
+
@envs_app.command(name="activate")
|
|
366
249
|
def envs_activate(
|
|
367
250
|
name: str = typer.Argument(..., help="Name of the environment. Values from 'qbraid envs list'.")
|
|
368
251
|
):
|
|
@@ -384,4 +267,4 @@ def envs_activate(
|
|
|
384
267
|
|
|
385
268
|
|
|
386
269
|
if __name__ == "__main__":
|
|
387
|
-
|
|
270
|
+
envs_app()
|
qbraid_cli/envs/create.py
CHANGED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module for handling data related to qBraid environments.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import keyword
|
|
11
|
+
import re
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, List, Tuple
|
|
14
|
+
|
|
15
|
+
import typer
|
|
16
|
+
|
|
17
|
+
from qbraid_cli.handlers import QbraidException
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_valid_env_name(env_name: str) -> bool: # pylint: disable=too-many-return-statements
|
|
21
|
+
"""
|
|
22
|
+
Validates a Python virtual environment name against best practices.
|
|
23
|
+
|
|
24
|
+
This function checks if the given environment name is valid based on certain
|
|
25
|
+
criteria, including length, use of special characters, reserved names, and
|
|
26
|
+
operating system-specific restrictions.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
env_name (str): The name of the Python virtual environment to validate.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
bool: True if the name is valid, False otherwise.
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
ValueError: If the environment name is not a string or is empty.
|
|
36
|
+
"""
|
|
37
|
+
# Basic checks for empty names or purely whitespace names
|
|
38
|
+
if not env_name or env_name.isspace():
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
# Check for invalid characters, including shell metacharacters and spaces
|
|
42
|
+
if re.search(r'[<>:"/\\|?*\s&;()$[\]#~!{}]', env_name):
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
if env_name.startswith("tmp"):
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
# Reserved names for Windows (example list, can be expanded)
|
|
49
|
+
reserved_names = [
|
|
50
|
+
"CON",
|
|
51
|
+
"PRN",
|
|
52
|
+
"AUX",
|
|
53
|
+
"NUL",
|
|
54
|
+
"COM1",
|
|
55
|
+
"COM2",
|
|
56
|
+
"COM3",
|
|
57
|
+
"COM4",
|
|
58
|
+
"COM5",
|
|
59
|
+
"COM6",
|
|
60
|
+
"COM7",
|
|
61
|
+
"COM8",
|
|
62
|
+
"COM9",
|
|
63
|
+
"LPT1",
|
|
64
|
+
"LPT2",
|
|
65
|
+
"LPT3",
|
|
66
|
+
"LPT4",
|
|
67
|
+
"LPT5",
|
|
68
|
+
"LPT6",
|
|
69
|
+
"LPT7",
|
|
70
|
+
"LPT8",
|
|
71
|
+
"LPT9",
|
|
72
|
+
]
|
|
73
|
+
if env_name.upper() in reserved_names:
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
if len(env_name) > 20:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
# Check against Python reserved words
|
|
80
|
+
if keyword.iskeyword(env_name):
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
# Check if it starts with a number, which is not a good practice
|
|
84
|
+
if env_name[0].isdigit():
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def validate_env_name(value: str) -> str:
|
|
91
|
+
"""Validate environment name."""
|
|
92
|
+
if not is_valid_env_name(value):
|
|
93
|
+
raise typer.BadParameter(
|
|
94
|
+
f"Invalid environment name '{value}'. " "Please use a valid Python environment name."
|
|
95
|
+
)
|
|
96
|
+
return value
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def installed_envs_data() -> Tuple[Dict[str, Path], Dict[str, str]]:
|
|
100
|
+
"""Gather paths and aliases for all installed qBraid environments."""
|
|
101
|
+
from qbraid_core.services.environments.paths import get_default_envs_paths, is_valid_slug
|
|
102
|
+
|
|
103
|
+
installed = {}
|
|
104
|
+
aliases = {}
|
|
105
|
+
|
|
106
|
+
qbraid_env_paths: List[Path] = get_default_envs_paths()
|
|
107
|
+
|
|
108
|
+
for env_path in qbraid_env_paths:
|
|
109
|
+
for entry in env_path.iterdir():
|
|
110
|
+
if entry.is_dir() and is_valid_slug(entry.name):
|
|
111
|
+
installed[entry.name] = entry
|
|
112
|
+
|
|
113
|
+
if entry.name == "qbraid_000000":
|
|
114
|
+
aliases["default"] = entry.name
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
state_json_path = entry / "state.json"
|
|
118
|
+
if state_json_path.exists():
|
|
119
|
+
try:
|
|
120
|
+
with open(state_json_path, "r", encoding="utf-8") as f:
|
|
121
|
+
data = json.load(f)
|
|
122
|
+
aliases[data.get("name", entry.name[:-7])] = entry.name
|
|
123
|
+
# pylint: disable-next=broad-exception-caught
|
|
124
|
+
except (json.JSONDecodeError, Exception):
|
|
125
|
+
aliases[entry.name[:-7]] = entry.name
|
|
126
|
+
else:
|
|
127
|
+
aliases[entry.name[:-7]] = entry.name
|
|
128
|
+
return installed, aliases
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def request_delete_env(slug: str) -> str:
|
|
132
|
+
"""Send request to delete environment given slug."""
|
|
133
|
+
from qbraid_core import QbraidSession, RequestsApiError
|
|
134
|
+
|
|
135
|
+
session = QbraidSession()
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
session.delete(f"/environments/{slug}")
|
|
139
|
+
except RequestsApiError as err:
|
|
140
|
+
raise QbraidException("Delete environment request failed") from err
|
qbraid_cli/exceptions.py
CHANGED
qbraid_cli/handlers.py
CHANGED
qbraid_cli/jobs/__init__.py
CHANGED
qbraid_cli/jobs/app.py
CHANGED
|
@@ -1,119 +1,65 @@
|
|
|
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
9
|
import sys
|
|
7
|
-
from typing import Callable, Dict,
|
|
10
|
+
from typing import Any, Callable, Dict, Tuple
|
|
8
11
|
|
|
9
12
|
import typer
|
|
10
13
|
from rich.console import Console
|
|
11
14
|
|
|
12
|
-
from qbraid_cli.handlers import handle_error, run_progress_task
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
state_values = {}
|
|
29
|
-
|
|
30
|
-
if library:
|
|
31
|
-
libraries_to_check = [library]
|
|
32
|
-
else:
|
|
33
|
-
libraries_to_check = QJOB_LIBS
|
|
34
|
-
|
|
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()
|
|
15
|
+
from qbraid_cli.handlers import handle_error, run_progress_task
|
|
16
|
+
from qbraid_cli.jobs.toggle_braket import disable_braket, enable_braket
|
|
17
|
+
from qbraid_cli.jobs.validation import handle_jobs_state, run_progress_get_state, validate_library
|
|
74
18
|
|
|
75
|
-
|
|
19
|
+
jobs_app = typer.Typer(help="Manage qBraid quantum jobs.")
|
|
76
20
|
|
|
77
21
|
|
|
78
|
-
@
|
|
22
|
+
@jobs_app.command(name="enable")
|
|
79
23
|
def jobs_enable(
|
|
80
24
|
library: str = typer.Argument(
|
|
81
25
|
..., help="Software library with quantum jobs support.", callback=validate_library
|
|
82
|
-
)
|
|
26
|
+
),
|
|
27
|
+
auto_confirm: bool = typer.Option(
|
|
28
|
+
False, "--yes", "-y", help="Automatically answer 'yes' to all prompts"
|
|
29
|
+
),
|
|
83
30
|
) -> None:
|
|
84
31
|
"""Enable qBraid Quantum Jobs."""
|
|
85
32
|
|
|
86
33
|
def enable_action():
|
|
87
34
|
if library == "braket":
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
enable_braket()
|
|
35
|
+
enable_braket(auto_confirm=auto_confirm)
|
|
91
36
|
else:
|
|
92
37
|
raise RuntimeError(f"Unsupported device library: '{library}'.")
|
|
93
38
|
|
|
94
39
|
handle_jobs_state(library, "enable", enable_action)
|
|
95
40
|
|
|
96
41
|
|
|
97
|
-
@
|
|
42
|
+
@jobs_app.command(name="disable")
|
|
98
43
|
def jobs_disable(
|
|
99
44
|
library: str = typer.Argument(
|
|
100
45
|
..., help="Software library with quantum jobs support.", callback=validate_library
|
|
101
|
-
)
|
|
46
|
+
),
|
|
47
|
+
auto_confirm: bool = typer.Option(
|
|
48
|
+
False, "--yes", "-y", help="Automatically answer 'yes' to all prompts"
|
|
49
|
+
),
|
|
102
50
|
) -> None:
|
|
103
51
|
"""Disable qBraid Quantum Jobs."""
|
|
104
52
|
|
|
105
53
|
def disable_action():
|
|
106
54
|
if library == "braket":
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
disable_braket()
|
|
55
|
+
disable_braket(auto_confirm=auto_confirm)
|
|
110
56
|
else:
|
|
111
57
|
raise RuntimeError(f"Unsupported device library: '{library}'.")
|
|
112
58
|
|
|
113
59
|
handle_jobs_state(library, "disable", disable_action)
|
|
114
60
|
|
|
115
61
|
|
|
116
|
-
@
|
|
62
|
+
@jobs_app.command(name="state")
|
|
117
63
|
def jobs_state(
|
|
118
64
|
library: str = typer.Argument(
|
|
119
65
|
default=None,
|
|
@@ -142,7 +88,7 @@ def jobs_state(
|
|
|
142
88
|
console.print(f"{lib:<{padding-1}}", state_str, end="\n")
|
|
143
89
|
|
|
144
90
|
|
|
145
|
-
@
|
|
91
|
+
@jobs_app.command(name="list")
|
|
146
92
|
def jobs_list(
|
|
147
93
|
limit: int = typer.Option(
|
|
148
94
|
10, "--limit", "-l", help="Limit the maximum number of results returned"
|
|
@@ -150,20 +96,55 @@ def jobs_list(
|
|
|
150
96
|
) -> None:
|
|
151
97
|
"""List qBraid Quantum Jobs."""
|
|
152
98
|
|
|
153
|
-
def import_jobs() -> Tuple[
|
|
154
|
-
from
|
|
155
|
-
|
|
99
|
+
def import_jobs() -> Tuple[Any, Callable]:
|
|
100
|
+
from qbraid_core.services.quantum import QuantumClient, process_job_data
|
|
101
|
+
|
|
102
|
+
client = QuantumClient()
|
|
156
103
|
|
|
157
|
-
return
|
|
104
|
+
return client, process_job_data
|
|
158
105
|
|
|
159
|
-
result: Tuple[
|
|
160
|
-
|
|
106
|
+
result: Tuple[Any, Callable] = run_progress_task(import_jobs)
|
|
107
|
+
client, process_job_data = result
|
|
108
|
+
# https://github.com/qBraid/api/issues/644
|
|
109
|
+
# raw_data = client.search_jobs(query={"numResults": limit})
|
|
110
|
+
raw_data = client.search_jobs(query={})
|
|
111
|
+
job_data, msg = process_job_data(raw_data)
|
|
112
|
+
job_data = job_data[:limit]
|
|
161
113
|
|
|
114
|
+
longest_job_id = max(len(item[0]) for item in job_data)
|
|
115
|
+
spacing = longest_job_id + 5
|
|
162
116
|
try:
|
|
163
|
-
|
|
164
|
-
|
|
117
|
+
console = Console()
|
|
118
|
+
header_1 = "Job ID"
|
|
119
|
+
header_2 = "Submitted"
|
|
120
|
+
header_3 = "Status"
|
|
121
|
+
console.print(f"\n[bold]{header_1.ljust(spacing)}{header_2.ljust(36)}{header_3}[/bold]")
|
|
122
|
+
for job_id, submitted, status in job_data:
|
|
123
|
+
if status == "COMPLETED":
|
|
124
|
+
status_color = "green"
|
|
125
|
+
elif status in ["FAILED", "CANCELLED"]:
|
|
126
|
+
status_color = "red"
|
|
127
|
+
elif status in [
|
|
128
|
+
"INITIALIZING",
|
|
129
|
+
"INITIALIZED",
|
|
130
|
+
"CREATED",
|
|
131
|
+
"QUEUED",
|
|
132
|
+
"VALIDATING",
|
|
133
|
+
"RUNNING",
|
|
134
|
+
]:
|
|
135
|
+
status_color = "blue"
|
|
136
|
+
else:
|
|
137
|
+
status_color = "grey"
|
|
138
|
+
console.print(
|
|
139
|
+
f"{job_id.ljust(spacing)}{submitted.ljust(35)}",
|
|
140
|
+
f"[{status_color}]{status}[/{status_color}]",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
console.print(f"\n{msg}", style="italic", justify="left")
|
|
144
|
+
|
|
145
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
165
146
|
handle_error(message="Failed to fetch quantum jobs.")
|
|
166
147
|
|
|
167
148
|
|
|
168
149
|
if __name__ == "__main__":
|
|
169
|
-
|
|
150
|
+
jobs_app()
|