qbraid-cli 0.8.0.dev6__py3-none-any.whl → 0.8.1__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/admin/__init__.py +2 -0
- qbraid_cli/admin/app.py +3 -4
- qbraid_cli/admin/buildlogs.py +114 -0
- qbraid_cli/admin/headers.py +41 -41
- qbraid_cli/configure/__init__.py +2 -0
- qbraid_cli/configure/app.py +12 -4
- qbraid_cli/credits/__init__.py +2 -0
- qbraid_cli/credits/app.py +5 -2
- qbraid_cli/devices/__init__.py +2 -0
- qbraid_cli/envs/__init__.py +2 -0
- qbraid_cli/envs/app.py +26 -36
- qbraid_cli/envs/create.py +8 -75
- qbraid_cli/envs/data_handling.py +12 -106
- qbraid_cli/handlers.py +15 -2
- qbraid_cli/jobs/__init__.py +2 -0
- qbraid_cli/kernels/__init__.py +2 -0
- qbraid_cli/kernels/app.py +3 -1
- qbraid_cli/main.py +5 -2
- qbraid_cli/pip/__init__.py +11 -0
- qbraid_cli/pip/app.py +49 -0
- qbraid_cli/pip/hooks.py +73 -0
- qbraid_cli/py.typed +0 -0
- qbraid_cli-0.8.1.dist-info/LICENSE +41 -0
- {qbraid_cli-0.8.0.dev6.dist-info → qbraid_cli-0.8.1.dist-info}/METADATA +55 -21
- qbraid_cli-0.8.1.dist-info/RECORD +39 -0
- qbraid_cli-0.8.0.dev6.dist-info/RECORD +0 -33
- {qbraid_cli-0.8.0.dev6.dist-info → qbraid_cli-0.8.1.dist-info}/WHEEL +0 -0
- {qbraid_cli-0.8.0.dev6.dist-info → qbraid_cli-0.8.1.dist-info}/entry_points.txt +0 -0
- {qbraid_cli-0.8.0.dev6.dist-info → qbraid_cli-0.8.1.dist-info}/top_level.txt +0 -0
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.
|
|
16
|
-
__version_tuple__ = version_tuple = (0, 8,
|
|
15
|
+
__version__ = version = '0.8.1'
|
|
16
|
+
__version_tuple__ = version_tuple = (0, 8, 1)
|
qbraid_cli/admin/__init__.py
CHANGED
qbraid_cli/admin/app.py
CHANGED
|
@@ -3,21 +3,20 @@
|
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
5
|
Module defining commands in the 'qbraid admin' namespace.
|
|
6
|
-
|
|
7
6
|
"""
|
|
8
7
|
|
|
9
8
|
from typing import List
|
|
10
9
|
|
|
11
10
|
import typer
|
|
12
11
|
|
|
12
|
+
from qbraid_cli.admin.buildlogs import buildlogs_app
|
|
13
13
|
from qbraid_cli.admin.headers import check_and_fix_headers
|
|
14
14
|
from qbraid_cli.admin.validation import validate_header_type, validate_paths_exist
|
|
15
15
|
|
|
16
|
-
# disable pretty_exceptions_show_locals to avoid printing sensative information in the traceback
|
|
17
16
|
admin_app = typer.Typer(
|
|
18
|
-
help="CI/CD commands for qBraid maintainers.",
|
|
19
|
-
pretty_exceptions_show_locals=False,
|
|
17
|
+
help="CI/CD commands for qBraid maintainers.", pretty_exceptions_show_locals=False
|
|
20
18
|
)
|
|
19
|
+
admin_app.add_typer(buildlogs_app, name="buildlogs")
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
@admin_app.command(name="headers")
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module defining commands in the 'qbraid admin buildlogs' namespace.
|
|
6
|
+
|
|
7
|
+
This module uses the Typer library to create CLI commands for managing Docker builds and logs
|
|
8
|
+
in an administrative context.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
from qbraid_core.exceptions import RequestsApiError
|
|
15
|
+
from qbraid_core.services.admin.client import AdminClient
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
|
|
18
|
+
from qbraid_cli.handlers import handle_error
|
|
19
|
+
|
|
20
|
+
buildlogs_app = typer.Typer(
|
|
21
|
+
help="Manage qBraid containerized services logs.", pretty_exceptions_show_locals=False
|
|
22
|
+
)
|
|
23
|
+
console = Console()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@buildlogs_app.command(name="get")
|
|
27
|
+
def get_docker_build_logs(
|
|
28
|
+
build_id: str = typer.Option(None, "--build_id", "-b", help="Name of the build ID")
|
|
29
|
+
) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Fetches and displays Docker build logs for a specified build ID.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
build_id (str, optional): The unique identifier for the Docker build.
|
|
35
|
+
|
|
36
|
+
This function queries the administrative backend to retrieve and display build logs.
|
|
37
|
+
If a build ID is provided, it will retrieve and display logs specific to that build ID.
|
|
38
|
+
If build ID not provided, fetches all logs.
|
|
39
|
+
"""
|
|
40
|
+
client = AdminClient()
|
|
41
|
+
|
|
42
|
+
build_log = client.get_docker_build_logs(build_id)
|
|
43
|
+
if build_id and "buildLogs" in build_log and build_log["buildLogs"]:
|
|
44
|
+
log_entry = build_log["buildLogs"][0]
|
|
45
|
+
console.print(log_entry)
|
|
46
|
+
else:
|
|
47
|
+
console.print(build_log)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@buildlogs_app.command(name="post")
|
|
51
|
+
def post_docker_build_log(
|
|
52
|
+
data: str = typer.Option(..., "--data", "-d", help="Data to post to Docker logs")
|
|
53
|
+
) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Posts a new Docker build log entry.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
data (str): JSON string containing the data to be logged.
|
|
59
|
+
|
|
60
|
+
This command converts a JSON string into a dictionary and sends it to the backend service
|
|
61
|
+
to create a new Docker build log.
|
|
62
|
+
"""
|
|
63
|
+
client = AdminClient()
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
data_dict = json.loads(data)
|
|
67
|
+
console.print(client.post_docker_build_logs(data_dict))
|
|
68
|
+
except RequestsApiError:
|
|
69
|
+
handle_error(message="Couldn't post a build_log.")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@buildlogs_app.command(name="put")
|
|
73
|
+
def put_docker_build_log(
|
|
74
|
+
build_id: str = typer.Option(..., "--build_id", "-b", help="Name of the build ID"),
|
|
75
|
+
data: str = typer.Option(..., "--data", "-d", help="Data to post to Docker logs"),
|
|
76
|
+
) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Updates an existing Docker build log entry by a given build ID.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
build_id (str): The unique identifier of the Docker build to update.
|
|
82
|
+
data (str): JSON string containing the updated data for the log.
|
|
83
|
+
|
|
84
|
+
This command updates a Docker build log entry, identified by the provided build ID,
|
|
85
|
+
with the new data provided in JSON format.
|
|
86
|
+
"""
|
|
87
|
+
client = AdminClient()
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
data_dict = json.loads(data)
|
|
91
|
+
console.print(client.put_docker_build_logs(build_id, data_dict))
|
|
92
|
+
except RequestsApiError:
|
|
93
|
+
handle_error(message="Couldn't post a build_log.")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@buildlogs_app.command(name="delete")
|
|
97
|
+
def delete_docker_build_log(
|
|
98
|
+
build_id: str = typer.Option(..., "--build_id", "-b", help="ID of the build log to delete")
|
|
99
|
+
) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Deletes a Docker build log entry by a specified build ID.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
build_id (str): The unique identifier of the Docker build log to delete.
|
|
105
|
+
|
|
106
|
+
This command sends a request to delete a Docker build log identified by the provided build ID.
|
|
107
|
+
"""
|
|
108
|
+
client = AdminClient()
|
|
109
|
+
|
|
110
|
+
console.print(client.delete_docker_build_logs(build_id))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
buildlogs_app()
|
qbraid_cli/admin/headers.py
CHANGED
|
@@ -6,11 +6,13 @@ Script to verify qBraid copyright file headers
|
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
8
|
import os
|
|
9
|
-
import sys
|
|
10
9
|
from typing import List, Optional
|
|
11
10
|
|
|
11
|
+
import typer
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
|
|
14
|
+
from qbraid_cli.handlers import handle_error
|
|
15
|
+
|
|
14
16
|
# pylint: disable=too-many-branches,too-many-statements
|
|
15
17
|
|
|
16
18
|
DEFAULT_HEADER = """# Copyright (c) 2024, qBraid Development Team
|
|
@@ -43,19 +45,17 @@ def check_and_fix_headers(
|
|
|
43
45
|
"""Script to add or verify qBraid copyright file headers"""
|
|
44
46
|
try:
|
|
45
47
|
header = HEADER_TYPES[header_type]
|
|
46
|
-
except KeyError
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
except KeyError:
|
|
49
|
+
handle_error(
|
|
50
|
+
error_type="ValueError",
|
|
51
|
+
message=(
|
|
52
|
+
f"Invalid header type: {HEADER_TYPES}. Expected one of {list(HEADER_TYPES.keys())}"
|
|
53
|
+
),
|
|
54
|
+
)
|
|
50
55
|
|
|
51
56
|
for path in src_paths:
|
|
52
57
|
if not os.path.exists(path):
|
|
53
|
-
|
|
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)
|
|
58
|
+
handle_error(error_type="FileNotFoundError", message=f"Path '{path}' does not exist.")
|
|
59
59
|
|
|
60
60
|
header_2023 = header.replace("2024", "2023")
|
|
61
61
|
|
|
@@ -139,8 +139,11 @@ def check_and_fix_headers(
|
|
|
139
139
|
replace_or_add_header(item, fix)
|
|
140
140
|
checked += 1
|
|
141
141
|
else:
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
handle_error(error_type="FileNotFoundError", message=f"Path '{item}' does not exist.")
|
|
143
|
+
|
|
144
|
+
if checked == 0:
|
|
145
|
+
console.print("[bold]No Python files present. Nothing to do[/bold] 😴")
|
|
146
|
+
raise typer.Exit(0)
|
|
144
147
|
|
|
145
148
|
if not fix:
|
|
146
149
|
if failed_headers:
|
|
@@ -160,34 +163,31 @@ def check_and_fix_headers(
|
|
|
160
163
|
|
|
161
164
|
failed_msg = f"[bold][blue]{num_failed}[/blue] file{s1} need{s2} updating{punc}[/bold]"
|
|
162
165
|
console.print(f"{failed_msg}{passed_msg}")
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
else
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
raise typer.Exit(1)
|
|
167
|
+
|
|
168
|
+
s_checked = "" if checked == 1 else "s"
|
|
169
|
+
console.print("[bold]All done![/bold] ✨ 🚀 ✨")
|
|
170
|
+
console.print(f"[blue]{checked}[/blue] file{s_checked} would be left unchanged.")
|
|
171
|
+
raise typer.Exit(0)
|
|
172
|
+
|
|
173
|
+
for file in fixed_headers:
|
|
174
|
+
console.print(f"[bold]fixed {file}[/bold]")
|
|
175
|
+
num_fixed = len(fixed_headers)
|
|
176
|
+
num_ok = checked - num_fixed
|
|
177
|
+
s_fixed = "" if num_fixed == 1 else "s"
|
|
178
|
+
s_ok = "" if num_ok == 1 else "s"
|
|
179
|
+
console.print("\n[bold]All done![/bold] ✨ 🚀 ✨")
|
|
180
|
+
if num_ok > 0:
|
|
181
|
+
punc = ", "
|
|
182
|
+
unchanged_msg = f"[blue]{num_ok}[/blue] file{s_ok} left unchanged."
|
|
170
183
|
else:
|
|
171
|
-
|
|
172
|
-
|
|
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
|
+
punc = "."
|
|
185
|
+
unchanged_msg = ""
|
|
184
186
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
if num_fixed > 0:
|
|
188
|
+
fixed_msg = f"[bold][blue]{num_fixed}[/blue] file{s_fixed} fixed{punc}[/bold]"
|
|
189
|
+
else:
|
|
190
|
+
fixed_msg = ""
|
|
189
191
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
else:
|
|
193
|
-
console.print("[bold]No Python files present. Nothing to do[/bold] 😴")
|
|
192
|
+
console.print(f"{fixed_msg}{unchanged_msg}")
|
|
193
|
+
raise typer.Exit(0)
|
qbraid_cli/configure/__init__.py
CHANGED
qbraid_cli/configure/app.py
CHANGED
|
@@ -7,6 +7,7 @@ Module defining commands in the 'qbraid configure' namespace.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
|
+
from rich.console import Console
|
|
10
11
|
|
|
11
12
|
from qbraid_cli.configure.actions import default_action
|
|
12
13
|
|
|
@@ -54,15 +55,22 @@ def configure_set(
|
|
|
54
55
|
|
|
55
56
|
@configure_app.command(name="magic")
|
|
56
57
|
def configure_magic():
|
|
57
|
-
"""
|
|
58
|
+
"""Enable qBraid IPython magic commands."""
|
|
58
59
|
# pylint: disable-next=import-outside-toplevel
|
|
59
60
|
from qbraid_core.services.environments import add_magic_config
|
|
60
61
|
|
|
61
62
|
add_magic_config()
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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")
|
|
66
74
|
|
|
67
75
|
|
|
68
76
|
if __name__ == "__main__":
|
qbraid_cli/credits/__init__.py
CHANGED
qbraid_cli/credits/app.py
CHANGED
|
@@ -24,8 +24,11 @@ def credits_value():
|
|
|
24
24
|
return client.user_credits_value()
|
|
25
25
|
|
|
26
26
|
qbraid_credits: float = run_progress_task(get_credits)
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
typer.secho(
|
|
28
|
+
f"\n{typer.style('qBraid credits remaining:')} "
|
|
29
|
+
f"{typer.style(f'{qbraid_credits:.4f}', fg=typer.colors.MAGENTA, bold=True)}",
|
|
30
|
+
nl=True, # Ensure a newline after output (default is True)
|
|
31
|
+
)
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
if __name__ == "__main__":
|
qbraid_cli/devices/__init__.py
CHANGED
qbraid_cli/envs/__init__.py
CHANGED
qbraid_cli/envs/app.py
CHANGED
|
@@ -10,14 +10,19 @@ import shutil
|
|
|
10
10
|
import subprocess
|
|
11
11
|
import sys
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import
|
|
13
|
+
from typing import TYPE_CHECKING, Optional, Tuple
|
|
14
14
|
|
|
15
15
|
import typer
|
|
16
16
|
from rich.console import Console
|
|
17
17
|
|
|
18
|
-
from qbraid_cli.envs.
|
|
18
|
+
from qbraid_cli.envs.create import create_qbraid_env_assets, create_venv
|
|
19
|
+
from qbraid_cli.envs.data_handling import get_envs_data as installed_envs_data
|
|
20
|
+
from qbraid_cli.envs.data_handling import validate_env_name
|
|
19
21
|
from qbraid_cli.handlers import QbraidException, run_progress_task
|
|
20
22
|
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient as EMC
|
|
25
|
+
|
|
21
26
|
envs_app = typer.Typer(help="Manage qBraid environments.")
|
|
22
27
|
|
|
23
28
|
|
|
@@ -34,25 +39,14 @@ def envs_create( # pylint: disable=too-many-statements
|
|
|
34
39
|
),
|
|
35
40
|
) -> None:
|
|
36
41
|
"""Create a new qBraid environment."""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def request_new_env(req_body: Dict[str, str]) -> Dict[str, Any]:
|
|
40
|
-
"""Send request to create new environment and return the slug."""
|
|
41
|
-
from qbraid_core import QbraidSession, RequestsApiError
|
|
42
|
-
|
|
43
|
-
session = QbraidSession()
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
env_data = session.post("/environments/create", json=req_body).json()
|
|
47
|
-
except RequestsApiError as err:
|
|
48
|
-
raise QbraidException("Create environment request failed") from err
|
|
42
|
+
env_description = description or ""
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
44
|
+
def create_environment(*args, **kwargs) -> "Tuple[dict, EMC]":
|
|
45
|
+
"""Create a qBraid environment."""
|
|
46
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
54
47
|
|
|
55
|
-
|
|
48
|
+
client = EnvironmentManagerClient()
|
|
49
|
+
return client.create_environment(*args, **kwargs), client
|
|
56
50
|
|
|
57
51
|
def gather_local_data() -> Tuple[Path, str]:
|
|
58
52
|
"""Gather environment data and return the slug."""
|
|
@@ -71,20 +65,10 @@ def envs_create( # pylint: disable=too-many-statements
|
|
|
71
65
|
|
|
72
66
|
return env_path, python_version
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"code": "", # newline separated list of packages
|
|
79
|
-
"visibility": "private",
|
|
80
|
-
"kernelName": "",
|
|
81
|
-
"prompt": "",
|
|
82
|
-
"origin": "CLI",
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
environment = run_progress_task(
|
|
86
|
-
request_new_env,
|
|
87
|
-
req_body,
|
|
68
|
+
environment, emc = run_progress_task(
|
|
69
|
+
create_environment,
|
|
70
|
+
name,
|
|
71
|
+
env_description,
|
|
88
72
|
description="Validating request...",
|
|
89
73
|
error_message="Failed to create qBraid environment",
|
|
90
74
|
)
|
|
@@ -94,7 +78,6 @@ def envs_create( # pylint: disable=too-many-statements
|
|
|
94
78
|
description="Solving environment...",
|
|
95
79
|
error_message="Failed to create qBraid environment",
|
|
96
80
|
)
|
|
97
|
-
|
|
98
81
|
slug = environment.get("slug")
|
|
99
82
|
display_name = environment.get("displayName")
|
|
100
83
|
prompt = environment.get("prompt")
|
|
@@ -120,7 +103,7 @@ def envs_create( # pylint: disable=too-many-statements
|
|
|
120
103
|
user_confirmation = auto_confirm or typer.confirm("Proceed", default=True)
|
|
121
104
|
typer.echo("")
|
|
122
105
|
if not user_confirmation:
|
|
123
|
-
|
|
106
|
+
emc.delete_environment(slug)
|
|
124
107
|
typer.echo("qBraidSystemExit: Exiting.")
|
|
125
108
|
raise typer.Exit()
|
|
126
109
|
|
|
@@ -165,6 +148,13 @@ def envs_remove(
|
|
|
165
148
|
) -> None:
|
|
166
149
|
"""Delete a qBraid environment."""
|
|
167
150
|
|
|
151
|
+
def delete_environment(slug: str) -> None:
|
|
152
|
+
"""Delete a qBraid environment."""
|
|
153
|
+
from qbraid_core.services.environments.client import EnvironmentManagerClient
|
|
154
|
+
|
|
155
|
+
emc = EnvironmentManagerClient()
|
|
156
|
+
emc.delete_environment(slug)
|
|
157
|
+
|
|
168
158
|
def gather_local_data(env_name: str) -> Tuple[Path, str]:
|
|
169
159
|
"""Get environment path and slug from name (alias)."""
|
|
170
160
|
installed, aliases = installed_envs_data()
|
|
@@ -190,7 +180,7 @@ def envs_remove(
|
|
|
190
180
|
if auto_confirm or typer.confirm(confirmation_message, abort=True):
|
|
191
181
|
typer.echo("")
|
|
192
182
|
run_progress_task(
|
|
193
|
-
|
|
183
|
+
delete_environment,
|
|
194
184
|
slug,
|
|
195
185
|
description="Deleting remote environment data...",
|
|
196
186
|
error_message="Failed to delete qBraid environment",
|
qbraid_cli/envs/create.py
CHANGED
|
@@ -5,93 +5,29 @@
|
|
|
5
5
|
Module supporting 'qbraid envs create' command.
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
|
-
|
|
9
8
|
import json
|
|
10
9
|
import os
|
|
11
10
|
import shutil
|
|
12
|
-
import subprocess
|
|
13
11
|
import sys
|
|
14
|
-
from typing import Optional
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def replace_str(target: str, replacement: str, file_path: str) -> None:
|
|
18
|
-
"""Replace all instances of string in file"""
|
|
19
|
-
with open(file_path, "r", encoding="utf-8") as file:
|
|
20
|
-
content = file.read()
|
|
21
|
-
|
|
22
|
-
content = content.replace(target, replacement)
|
|
23
|
-
|
|
24
|
-
with open(file_path, "w", encoding="utf-8") as file:
|
|
25
|
-
file.write(content)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def update_state_json(
|
|
29
|
-
slug_path: str,
|
|
30
|
-
install_complete: int,
|
|
31
|
-
install_success: int,
|
|
32
|
-
message: Optional[str] = None,
|
|
33
|
-
env_name: Optional[str] = None,
|
|
34
|
-
) -> None:
|
|
35
|
-
"""Update environment's install status values in a JSON file.
|
|
36
|
-
Truth table values: 0 = False, 1 = True, -1 = Unknown
|
|
37
|
-
"""
|
|
38
|
-
# Set default message if none provided
|
|
39
|
-
message = "" if message is None else message.replace("\n", " ")
|
|
40
|
-
|
|
41
|
-
# File path for state.json
|
|
42
|
-
state_json_path = os.path.join(slug_path, "state.json")
|
|
43
12
|
|
|
44
|
-
# Read existing data or use default structure
|
|
45
|
-
if os.path.exists(state_json_path):
|
|
46
|
-
with open(state_json_path, "r", encoding="utf-8") as f:
|
|
47
|
-
data = json.load(f)
|
|
48
|
-
else:
|
|
49
|
-
data = {"install": {}}
|
|
50
|
-
|
|
51
|
-
# Update the data
|
|
52
|
-
data["install"]["complete"] = install_complete
|
|
53
|
-
data["install"]["success"] = install_success
|
|
54
|
-
data["install"]["message"] = message
|
|
55
|
-
|
|
56
|
-
if env_name is not None:
|
|
57
|
-
data["name"] = env_name
|
|
58
|
-
|
|
59
|
-
# Write updated data back to state.json
|
|
60
|
-
with open(state_json_path, "w", encoding="utf-8") as f:
|
|
61
|
-
json.dump(data, f, indent=4)
|
|
62
13
|
|
|
14
|
+
def create_venv(*args, **kwargs) -> None:
|
|
15
|
+
"""Create a python virtual environment for the qBraid environment."""
|
|
16
|
+
from qbraid_core.services.environments import create_local_venv
|
|
63
17
|
|
|
64
|
-
|
|
65
|
-
"""Create virtual environment and swap PS1 display name."""
|
|
66
|
-
venv_path = os.path.join(slug_path, "pyenv")
|
|
67
|
-
subprocess.run([sys.executable, "-m", "venv", venv_path], check=True)
|
|
18
|
+
return create_local_venv(*args, **kwargs)
|
|
68
19
|
|
|
69
|
-
# Determine the correct directory for activation scripts based on the operating system
|
|
70
|
-
if sys.platform == "win32":
|
|
71
|
-
scripts_path = os.path.join(venv_path, "Scripts")
|
|
72
|
-
activate_files = ["activate.bat", "Activate.ps1"]
|
|
73
|
-
else:
|
|
74
|
-
scripts_path = os.path.join(venv_path, "bin")
|
|
75
|
-
activate_files = ["activate", "activate.csh", "activate.fish"]
|
|
76
20
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
replace_str("(pyenv)", f"({prompt})", file_path)
|
|
21
|
+
def update_state_json(*ags, **kwargs) -> None:
|
|
22
|
+
"""Update the state.json file for the qBraid environment."""
|
|
23
|
+
from qbraid_core.services.environments.state import update_install_status
|
|
81
24
|
|
|
82
|
-
|
|
83
|
-
"include-system-site-packages = false",
|
|
84
|
-
"include-system-site-packages = true",
|
|
85
|
-
os.path.join(venv_path, "pyvenv.cfg"),
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
update_state_json(slug_path, 1, 1)
|
|
25
|
+
return update_install_status(*ags, **kwargs)
|
|
89
26
|
|
|
90
27
|
|
|
91
28
|
def create_qbraid_env_assets(slug: str, alias: str, kernel_name: str, slug_path: str) -> None:
|
|
92
29
|
"""Create a qBraid environment including python venv, PS1 configs,
|
|
93
30
|
kernel resource files, and qBraid state.json."""
|
|
94
|
-
# pylint: disable-next=import-outside-toplevel
|
|
95
31
|
from jupyter_client.kernelspec import KernelSpecManager
|
|
96
32
|
|
|
97
33
|
local_resource_dir = os.path.join(slug_path, "kernels", f"python3_{slug}")
|
|
@@ -123,6 +59,3 @@ def create_qbraid_env_assets(slug: str, alias: str, kernel_name: str, slug_path:
|
|
|
123
59
|
loc_path = os.path.join(local_resource_dir, file)
|
|
124
60
|
if os.path.isfile(sys_path):
|
|
125
61
|
shutil.copy(sys_path, loc_path)
|
|
126
|
-
|
|
127
|
-
# create python venv
|
|
128
|
-
create_venv(slug_path, alias)
|
qbraid_cli/envs/data_handling.py
CHANGED
|
@@ -6,85 +6,23 @@ Module for handling data related to qBraid environments.
|
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import json
|
|
10
|
-
import keyword
|
|
11
|
-
import re
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import Dict, List, Tuple
|
|
14
|
-
|
|
15
9
|
import typer
|
|
16
10
|
|
|
17
11
|
from qbraid_cli.handlers import QbraidException
|
|
18
12
|
|
|
19
13
|
|
|
20
|
-
def
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
14
|
+
def get_envs_data(*args, **kwargs) -> dict:
|
|
15
|
+
"""Get data for installed environments."""
|
|
16
|
+
from qbraid_core.services.environments.paths import installed_envs_data
|
|
17
|
+
|
|
18
|
+
return installed_envs_data(*args, **kwargs)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_valid_env_name(value: str) -> bool:
|
|
22
|
+
"""Check if a given string is a valid Python environment name."""
|
|
23
|
+
from qbraid_core.services.environments.validate import is_valid_env_name as is_valid
|
|
24
|
+
|
|
25
|
+
return is_valid(value)
|
|
88
26
|
|
|
89
27
|
|
|
90
28
|
def validate_env_name(value: str) -> str:
|
|
@@ -96,38 +34,6 @@ def validate_env_name(value: str) -> str:
|
|
|
96
34
|
return value
|
|
97
35
|
|
|
98
36
|
|
|
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
37
|
def request_delete_env(slug: str) -> str:
|
|
132
38
|
"""Send request to delete environment given slug."""
|
|
133
39
|
from qbraid_core import QbraidSession, RequestsApiError
|
qbraid_cli/handlers.py
CHANGED
|
@@ -7,6 +7,7 @@ and executing operations with progress tracking within the qBraid CLI.
|
|
|
7
7
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import os
|
|
10
11
|
import traceback
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from typing import Any, Callable, List, Optional, Union
|
|
@@ -18,6 +19,11 @@ from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
|
18
19
|
from .exceptions import DEFAULT_ERROR_MESSAGE, QbraidException
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
def _should_display_progress():
|
|
23
|
+
"""Whether to display rich progress UI."""
|
|
24
|
+
return os.getenv("QBRAID_CLI_SHOW_PROGRESS", "true").lower() in ["true", "1", "t", "y", "yes"]
|
|
25
|
+
|
|
26
|
+
|
|
21
27
|
def handle_error(
|
|
22
28
|
error_type: Optional[str] = None, message: Optional[str] = None, include_traceback: bool = True
|
|
23
29
|
) -> None:
|
|
@@ -104,6 +110,13 @@ def run_progress_task(
|
|
|
104
110
|
Raises:
|
|
105
111
|
typer.Exit: If the operation fails, after displaying the error message using typer.secho.
|
|
106
112
|
"""
|
|
113
|
+
if not _should_display_progress():
|
|
114
|
+
try:
|
|
115
|
+
return operation(*args, **kwargs)
|
|
116
|
+
except Exception as err: # pylint: disable=broad-exception-caught
|
|
117
|
+
custom_message = error_message if error_message else str(err)
|
|
118
|
+
return handle_error(message=custom_message)
|
|
119
|
+
|
|
107
120
|
console = Console()
|
|
108
121
|
with Progress(
|
|
109
122
|
"[progress.description]{task.description}",
|
|
@@ -117,9 +130,9 @@ def run_progress_task(
|
|
|
117
130
|
result = operation(*args, **kwargs)
|
|
118
131
|
progress.update(task, completed=100, status="Done")
|
|
119
132
|
return result
|
|
120
|
-
except Exception as
|
|
133
|
+
except Exception as err: # pylint: disable=broad-exception-caught
|
|
121
134
|
progress.update(task, completed=100, status="Failed")
|
|
122
|
-
custom_message = error_message if error_message else str(
|
|
135
|
+
custom_message = error_message if error_message else str(err)
|
|
123
136
|
return handle_error(message=custom_message)
|
|
124
137
|
|
|
125
138
|
|
qbraid_cli/jobs/__init__.py
CHANGED
qbraid_cli/kernels/__init__.py
CHANGED
qbraid_cli/kernels/app.py
CHANGED
|
@@ -12,7 +12,6 @@ import typer
|
|
|
12
12
|
from jupyter_client.kernelspec import KernelSpecManager
|
|
13
13
|
from rich.console import Console
|
|
14
14
|
|
|
15
|
-
from qbraid_cli.envs.data_handling import installed_envs_data
|
|
16
15
|
from qbraid_cli.handlers import handle_error
|
|
17
16
|
|
|
18
17
|
kernels_app = typer.Typer(help="Manage qBraid kernels.")
|
|
@@ -20,6 +19,9 @@ kernels_app = typer.Typer(help="Manage qBraid kernels.")
|
|
|
20
19
|
|
|
21
20
|
def _get_kernels_path(environment: str) -> Path:
|
|
22
21
|
"""Get the path to the kernels directory for the given environment."""
|
|
22
|
+
# pylint: disable-next=import-outside-toplevel
|
|
23
|
+
from qbraid_core.services.environments.paths import installed_envs_data
|
|
24
|
+
|
|
23
25
|
slug_to_path, name_to_slug = installed_envs_data()
|
|
24
26
|
|
|
25
27
|
if environment in name_to_slug:
|
qbraid_cli/main.py
CHANGED
|
@@ -15,6 +15,7 @@ from qbraid_cli.devices.app import devices_app
|
|
|
15
15
|
from qbraid_cli.envs.app import envs_app
|
|
16
16
|
from qbraid_cli.jobs.app import jobs_app
|
|
17
17
|
from qbraid_cli.kernels.app import kernels_app
|
|
18
|
+
from qbraid_cli.pip.app import pip_app
|
|
18
19
|
|
|
19
20
|
app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]})
|
|
20
21
|
|
|
@@ -25,15 +26,17 @@ app.add_typer(devices_app, name="devices")
|
|
|
25
26
|
app.add_typer(envs_app, name="envs")
|
|
26
27
|
app.add_typer(jobs_app, name="jobs")
|
|
27
28
|
app.add_typer(kernels_app, name="kernels")
|
|
29
|
+
app.add_typer(pip_app, name="pip")
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
def version_callback(value: bool):
|
|
31
33
|
"""Show the version and exit."""
|
|
32
34
|
if value:
|
|
33
|
-
|
|
35
|
+
# pylint: disable-next=import-error
|
|
36
|
+
from ._version import __version__ # type: ignore
|
|
34
37
|
|
|
35
38
|
typer.echo(f"qbraid-cli/{__version__}")
|
|
36
|
-
raise typer.Exit()
|
|
39
|
+
raise typer.Exit(0)
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
def show_banner():
|
qbraid_cli/pip/app.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module defining commands in the 'qbraid pip' namespace.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from qbraid_core.system import QbraidSystemError, get_active_python_path
|
|
13
|
+
|
|
14
|
+
from qbraid_cli.handlers import handle_error
|
|
15
|
+
from qbraid_cli.pip.hooks import get_env_cfg_path, safe_set_include_sys_packages
|
|
16
|
+
|
|
17
|
+
# disable pretty_exceptions_show_locals to avoid printing sensative information in the traceback
|
|
18
|
+
pip_app = typer.Typer(help="Run pip command in active qBraid environment.")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pip_app.command(
|
|
22
|
+
"install", context_settings={"allow_extra_args": True, "ignore_unknown_options": True}
|
|
23
|
+
)
|
|
24
|
+
def pip_install(ctx: typer.Context):
|
|
25
|
+
"""
|
|
26
|
+
Perform pip install action with open-ended arguments and options.
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
python_exe = get_active_python_path()
|
|
31
|
+
cfg_path = get_env_cfg_path(python_exe)
|
|
32
|
+
except QbraidSystemError:
|
|
33
|
+
python_exe = sys.executable
|
|
34
|
+
cfg_path = None
|
|
35
|
+
|
|
36
|
+
safe_set_include_sys_packages(False, cfg_path)
|
|
37
|
+
|
|
38
|
+
command = [str(python_exe), "-m", "pip", "install"] + ctx.args
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
subprocess.check_call(command)
|
|
42
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
43
|
+
handle_error(message="Failed carry-out pip command.")
|
|
44
|
+
finally:
|
|
45
|
+
safe_set_include_sys_packages(True, cfg_path)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
pip_app()
|
qbraid_cli/pip/hooks.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module defining pre- and post-command hooks for the 'qbraid pip' namespace.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, Union
|
|
11
|
+
|
|
12
|
+
from qbraid_core.services.environments import get_default_envs_paths
|
|
13
|
+
from qbraid_core.system.executables import python_paths_equivalent
|
|
14
|
+
from qbraid_core.system.packages import (
|
|
15
|
+
extract_include_sys_site_pkgs_value,
|
|
16
|
+
set_include_sys_site_pkgs_value,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def safe_set_include_sys_packages(value: bool, file_path: Optional[Union[str, Path]]) -> None:
|
|
21
|
+
"""Set include-system-site-packages value safely"""
|
|
22
|
+
if not file_path:
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
set_include_sys_site_pkgs_value(value, file_path)
|
|
27
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def find_matching_prefix(python_executable: Path, path_list: list[Path]) -> Optional[Path]:
|
|
34
|
+
"""
|
|
35
|
+
Finds and returns the first path in the list that is a prefix of the Python executable path.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
python_executable (Path): The path to the Python executable.
|
|
39
|
+
path_list (List[Path]): A list of paths to check against the Python executable path.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Optional[Path]: The first matching path that is a prefix of the Python executable path,
|
|
43
|
+
or None if no match is found.
|
|
44
|
+
"""
|
|
45
|
+
python_executable_str = str(python_executable.resolve())
|
|
46
|
+
for path in path_list:
|
|
47
|
+
if python_executable_str.startswith(str(path.resolve())):
|
|
48
|
+
return path
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_env_cfg_path(python_exe: Path) -> Optional[Path]:
|
|
53
|
+
"""Get the path to the pyvenv.cfg file."""
|
|
54
|
+
is_sys_python = python_paths_equivalent(python_exe, sys.executable)
|
|
55
|
+
|
|
56
|
+
if is_sys_python:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
all_envs_paths = get_default_envs_paths()
|
|
60
|
+
|
|
61
|
+
env_path = find_matching_prefix(python_exe, all_envs_paths)
|
|
62
|
+
|
|
63
|
+
if not env_path:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
cfg_path = env_path / "pyvenv.cfg"
|
|
67
|
+
|
|
68
|
+
should_flip = extract_include_sys_site_pkgs_value(cfg_path)
|
|
69
|
+
|
|
70
|
+
if should_flip:
|
|
71
|
+
return cfg_path
|
|
72
|
+
|
|
73
|
+
return None
|
qbraid_cli/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
qBraid Closed-Source Software License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, qBraid Development Team
|
|
4
|
+
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
This license agreement ("License") is between the qBraid Development Team ("Author") and you, the Licensee. By using or distributing the qBraid ("Software"), you agree to the following terms:
|
|
8
|
+
|
|
9
|
+
1. Grant of License.
|
|
10
|
+
|
|
11
|
+
Subject to the terms of this License, the Author grants you a non-exclusive, non-transferable license to use the Software for personal, academic, and educational purposes. Use of the Software for commercial purposes is strictly prohibited unless express permission is granted by the Author.
|
|
12
|
+
|
|
13
|
+
2. Distribution.
|
|
14
|
+
|
|
15
|
+
You may distribute the Software to others, provided that any such distribution is made under the same terms of this License. Modifications to the Software may not be distributed.
|
|
16
|
+
|
|
17
|
+
3. Modification.
|
|
18
|
+
|
|
19
|
+
You are permitted to modify the Software for personal use. You may contribute improvements or bug fixes back to the Author or the community, but you may not distribute the modifications.
|
|
20
|
+
|
|
21
|
+
4. Commercial Use.
|
|
22
|
+
|
|
23
|
+
The Software may not be used for commercial purposes without explicit, prior written permission from the Author. Permissions for commercial use will be considered on a case-by-case basis.
|
|
24
|
+
|
|
25
|
+
5. Attribution
|
|
26
|
+
|
|
27
|
+
If the Software is used in a non-private setting, including but not limited to academic work, commercial settings, or published literature, attribution must be given to the "qBraid Jupyter Environment Manager authored by the qBraid Development Team."
|
|
28
|
+
|
|
29
|
+
6. Disclaimer of Warranty.
|
|
30
|
+
|
|
31
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
32
|
+
|
|
33
|
+
7. Limitation of Liability.
|
|
34
|
+
|
|
35
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
36
|
+
|
|
37
|
+
8. Termination.
|
|
38
|
+
|
|
39
|
+
This License is effective until terminated. Your rights under this License will terminate automatically without notice from the Author if you fail to comply with any term(s) of this License.
|
|
40
|
+
|
|
41
|
+
By using the Software, you agree to be bound by the terms of this License. Redistribution of the Software or use of the Software other than as specifically authorized under this License is prohibited and may result in severe civil and criminal penalties.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qbraid-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Command Line Interface for interacting with all parts of the qBraid platform.
|
|
5
5
|
Author-email: qBraid Development Team <contact@qbraid.com>
|
|
6
6
|
License: Proprietary
|
|
@@ -26,24 +26,17 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
26
26
|
Classifier: Programming Language :: Python :: 3.12
|
|
27
27
|
Requires-Python: >=3.9
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
29
30
|
Requires-Dist: typer >=0.12.1
|
|
30
31
|
Requires-Dist: rich >=10.11.0
|
|
31
32
|
Requires-Dist: jupyter-client <9.0.0,>=7.0.0
|
|
32
33
|
Requires-Dist: ipykernel
|
|
33
|
-
Requires-Dist: qbraid-core >=0.1.
|
|
34
|
+
Requires-Dist: qbraid-core >=0.1.10
|
|
34
35
|
Provides-Extra: dev
|
|
35
|
-
Requires-Dist:
|
|
36
|
+
Requires-Dist: ruff ; extra == 'dev'
|
|
36
37
|
Requires-Dist: isort ; extra == 'dev'
|
|
37
|
-
Requires-Dist:
|
|
38
|
+
Requires-Dist: black ; extra == 'dev'
|
|
38
39
|
Requires-Dist: pytest ; extra == 'dev'
|
|
39
|
-
Provides-Extra: docs
|
|
40
|
-
Requires-Dist: sphinx ~=7.2.6 ; extra == 'docs'
|
|
41
|
-
Requires-Dist: sphinx-rtd-theme <2.1,>=1.3 ; extra == 'docs'
|
|
42
|
-
Requires-Dist: docutils <0.22 ; extra == 'docs'
|
|
43
|
-
Requires-Dist: toml ; extra == 'docs'
|
|
44
|
-
Requires-Dist: build ; extra == 'docs'
|
|
45
|
-
Requires-Dist: m2r ; extra == 'docs'
|
|
46
|
-
Requires-Dist: typer ; extra == 'docs'
|
|
47
40
|
Provides-Extra: jobs
|
|
48
41
|
Requires-Dist: amazon-braket-sdk >=1.48.1 ; extra == 'jobs'
|
|
49
42
|
|
|
@@ -54,24 +47,43 @@ Requires-Dist: amazon-braket-sdk >=1.48.1 ; extra == 'jobs'
|
|
|
54
47
|
[](https://pypi.org/project/qbraid-cli/)
|
|
55
48
|
[](https://pepy.tech/project/qbraid-cli)
|
|
56
49
|
[](https://github.com/qBraid/qBraid-Lab/issues)
|
|
57
|
-
[](https://discord.gg/KugF6Cnncm)
|
|
58
50
|
|
|
59
51
|
Command Line Interface for interacting with all parts of the qBraid platform.
|
|
60
52
|
|
|
61
|
-
The **qBraid CLI** is a
|
|
53
|
+
The **qBraid CLI** is a versatile command-line interface tool designed for seamless interaction with qBraid cloud services and quantum software management tools. Initially exclusive to the [qBraid Lab](https://docs.qbraid.com/projects/lab/en/latest/lab/overview.html) platform, the CLI now supports local installations as well. This enhancement broadens access to features like [qBraid Quantum Jobs](https://docs.qbraid.com/projects/lab/en/latest/lab/quantum_jobs.html), enabling direct acess to QPU devices from leading providers like IonQ, Oxford Quantum Circuits, QuEra, and Rigetti, as well as on-demand simulators from AWS, all using qBraid credits, with no additional access keys required.
|
|
62
54
|
|
|
63
55
|
## Getting Started
|
|
64
56
|
|
|
65
|
-
|
|
57
|
+
The qBraid-CLI comes pre-installed and pre-configured in qBraid Lab:
|
|
66
58
|
|
|
67
59
|
- [Launch qBraid Lab →](https://lab.qbraid.com/)
|
|
68
60
|
- [Make an account →](https://account.qbraid.com/)
|
|
69
61
|
|
|
70
62
|
For help, see qBraid Lab User Guide: [Getting Started](https://docs.qbraid.com/projects/lab/en/latest/lab/getting_started.html).
|
|
71
63
|
|
|
64
|
+
You can also install the qBraid-CLI from PyPI with:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install qbraid-cli
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Local configuration
|
|
71
|
+
|
|
72
|
+
After installation, you must configure your account credentials to use the CLI locally:
|
|
73
|
+
|
|
74
|
+
1. Create a qBraid account or log in to your existing account by visiting
|
|
75
|
+
[account.qbraid.com](https://account.qbraid.com/)
|
|
76
|
+
2. Copy your API Key token from the left side of
|
|
77
|
+
your [account page](https://account.qbraid.com/):
|
|
78
|
+
3. Save your API key from step 2 in local configuration file `~/.qbraid/qbraidrc` using:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
$ qbraid configure
|
|
82
|
+
```
|
|
83
|
+
|
|
72
84
|
## Basic Commands
|
|
73
85
|
|
|
74
|
-
```
|
|
86
|
+
```bash
|
|
75
87
|
$ qbraid
|
|
76
88
|
----------------------------------
|
|
77
89
|
* Welcome to the qBraid CLI! *
|
|
@@ -94,19 +106,19 @@ Reference Docs: https://docs.qbraid.com/projects/cli/en/stable/guide/overview.ht
|
|
|
94
106
|
|
|
95
107
|
A qBraid CLI command has the following structure:
|
|
96
108
|
|
|
97
|
-
```
|
|
109
|
+
```bash
|
|
98
110
|
$ qbraid <command> <subcommand> [options and parameters]
|
|
99
111
|
```
|
|
100
112
|
|
|
101
113
|
For example, to list installed environments, the command would be:
|
|
102
114
|
|
|
103
|
-
```
|
|
115
|
+
```bash
|
|
104
116
|
$ qbraid envs list
|
|
105
117
|
```
|
|
106
118
|
|
|
107
119
|
To view help documentation, use one of the following:
|
|
108
120
|
|
|
109
|
-
```
|
|
121
|
+
```bash
|
|
110
122
|
$ qbraid --help
|
|
111
123
|
$ qbraid <command> --help
|
|
112
124
|
$ qbraid <command> <subcommand> --help
|
|
@@ -114,7 +126,7 @@ $ qbraid <command> <subcommand> --help
|
|
|
114
126
|
|
|
115
127
|
For example:
|
|
116
128
|
|
|
117
|
-
```
|
|
129
|
+
```bash
|
|
118
130
|
$ qbraid --help
|
|
119
131
|
|
|
120
132
|
Usage: qbraid [OPTIONS] COMMAND [ARGS]...
|
|
@@ -138,6 +150,28 @@ Commands
|
|
|
138
150
|
|
|
139
151
|
To get the version of the qBraid CLI:
|
|
140
152
|
|
|
141
|
-
```
|
|
153
|
+
```bash
|
|
142
154
|
$ qbraid --version
|
|
143
155
|
```
|
|
156
|
+
|
|
157
|
+
## Magic Commands
|
|
158
|
+
|
|
159
|
+
You can also access the CLI directly from within [Notebooks](https://docs.qbraid.com/projects/lab/en/latest/lab/notebooks.html) using IPython [magic commands](https://ipython.readthedocs.io/en/stable/interactive/magics.html). First, configure the qBraid magic commands extension using:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
$ qbraid configure magic
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The above command can also be executed from within a Jupyter notebook using the ``!`` operator. Then, from within a notebook cell, load the qBraid magic IPython extension using:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
In [1]: %load_ext qbraid_magic
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Now you can continue to use the qBraid-CLI as normal from within your Jupyter notebook using the magic ``%`` operator, e.g.
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
In [2]: %qbraid jobs state
|
|
175
|
+
|
|
176
|
+
In [3]: %qbraid jobs enable braket -y
|
|
177
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
qbraid_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
qbraid_cli/_version.py,sha256=Z8fNqmfsoA07nbVEewR90wZCgaN6eyDwPwgtgc2KIak,411
|
|
3
|
+
qbraid_cli/exceptions.py,sha256=KjlhYJhSHMVazaNiBjD_Ur06w4sekP8zRsFzBdyIpno,672
|
|
4
|
+
qbraid_cli/handlers.py,sha256=glxTEwxax3zKgYl9qsZ2evZXgrWQrseJS_OGyHTMFeA,7040
|
|
5
|
+
qbraid_cli/main.py,sha256=eRgBEYj9WPxHqTbSQvlV39yaDje-6yvZdrTnw5UQbQY,2638
|
|
6
|
+
qbraid_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
qbraid_cli/admin/__init__.py,sha256=qcWD5mQEUCtr49mrUpZmk7eGDe0L_Gtc8RwZmzIXSwo,175
|
|
8
|
+
qbraid_cli/admin/app.py,sha256=ZyTBkEvlAR-xK3fbC32yntsE3rywzL4WHjJzRTFTlzs,1454
|
|
9
|
+
qbraid_cli/admin/buildlogs.py,sha256=1i7CRpkIHIcKgGvHesPr9duCkyV7M-MESaHI5Z3ZdD0,3578
|
|
10
|
+
qbraid_cli/admin/headers.py,sha256=E6mE3odZL57VfHHZxYtRUkqMKmrmT4zuUoINzI7eJF8,6435
|
|
11
|
+
qbraid_cli/admin/validation.py,sha256=LkAVXlHtM0MhCa34MIWrfX59wGXMVlZmdVB4-AQ8fBk,1003
|
|
12
|
+
qbraid_cli/configure/__init__.py,sha256=YaJ74Ztz2vl3eYp8_jVBucWkXscxz7EZEIzr70OfuOM,187
|
|
13
|
+
qbraid_cli/configure/actions.py,sha256=3rrWHaCAsogyx0Ll-lcjbSzldD4kPuz1z6VQiWebSWw,3203
|
|
14
|
+
qbraid_cli/configure/app.py,sha256=1uRe2lkUA4TtYb5b4mbD4LH-cKCbsZGT3Wfk7fpNzX0,2414
|
|
15
|
+
qbraid_cli/credits/__init__.py,sha256=MJhgWgpFT1_886sB-_POlRs1y3SRy23HIrKVBkp0X-Y,181
|
|
16
|
+
qbraid_cli/credits/app.py,sha256=AY3qtveO50KeQ2XREiEVqUcTrESgRuoqt9pt2Z8t4Y0,866
|
|
17
|
+
qbraid_cli/devices/__init__.py,sha256=hiScO-px6jCL5cJj5Hbty55EUfNejTO4bmqUZuS3aqc,181
|
|
18
|
+
qbraid_cli/devices/app.py,sha256=zxSxrEQn7irkJoME4S_CBnRqWeB8cqPaBsIMfpdYFk0,2530
|
|
19
|
+
qbraid_cli/devices/validation.py,sha256=YhShyUufgrKnx2XjXOXF-PqFJYklJT9CgeqIwKcNam4,809
|
|
20
|
+
qbraid_cli/envs/__init__.py,sha256=1-cMvrATsddYxcetPJWxq6bEOqJWMktGdhoZ4qm8euA,172
|
|
21
|
+
qbraid_cli/envs/activate.py,sha256=VpvVYSfQDlcmlNWJOgkLIQ2p8YXPPLG8Jbl5t8GHUDw,2140
|
|
22
|
+
qbraid_cli/envs/app.py,sha256=PD7U-zdQeoWPXPRpmKC3nwDVOfa69dk5GCIsE3mpla8,8411
|
|
23
|
+
qbraid_cli/envs/create.py,sha256=HxNciItDoGC5jqiruHm0oUfNSqtuDfzE-ro4ztGUh5Q,2185
|
|
24
|
+
qbraid_cli/envs/data_handling.py,sha256=Ibnp2yJoUDpivb_sNqi0suYgJZNat_LmM6Ya0Ovez5s,1288
|
|
25
|
+
qbraid_cli/jobs/__init__.py,sha256=qVLRHYIzP4XHpx_QWP_vCzd3LsCscCORaEx-Vcbx29U,172
|
|
26
|
+
qbraid_cli/jobs/app.py,sha256=kmg9mYla3Nd7EdjQlFu7IOvm7sejLNfPPA6Qeet-IfE,4898
|
|
27
|
+
qbraid_cli/jobs/toggle_braket.py,sha256=d5C_Di80jWMFlh-77eH8YY9pjMKWXK5abenUDtPlE_I,6662
|
|
28
|
+
qbraid_cli/jobs/validation.py,sha256=xNbjUggMhUs4wzkuRm4PuFPi_wrElYicUgYXLznHz3U,2983
|
|
29
|
+
qbraid_cli/kernels/__init__.py,sha256=jORS9vV17s5laQyq8gSVB18EPBImgEIbMZ1wKC094DA,181
|
|
30
|
+
qbraid_cli/kernels/app.py,sha256=eELxcuYV_d0wNR5t3IYznEcjqGmRM1j7GeHqVgcvDqs,3439
|
|
31
|
+
qbraid_cli/pip/__init__.py,sha256=tJtU0rxn-ODogNh5Y4pp_BgDQXMN-3JY1QGj0OZHwjQ,169
|
|
32
|
+
qbraid_cli/pip/app.py,sha256=yd55KzE1dApxPV5fyBa8PfdxaeDovUZyrPm_UZy_Ie0,1386
|
|
33
|
+
qbraid_cli/pip/hooks.py,sha256=KuDHmntPXVK8tSb4MLk9VANhL-eINswhLd8_g_25WMY,2123
|
|
34
|
+
qbraid_cli-0.8.1.dist-info/LICENSE,sha256=P1gi-ofB8lmkRt_mxDoJpcgQq9Ckq9WhRAS1oYk-G1s,2506
|
|
35
|
+
qbraid_cli-0.8.1.dist-info/METADATA,sha256=Hab46g_Tequpsr6ehVMyS8QoAS051pJTr6lvz57RKXc,6732
|
|
36
|
+
qbraid_cli-0.8.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
37
|
+
qbraid_cli-0.8.1.dist-info/entry_points.txt,sha256=c5ZJ7NjbxhDqMpou9q5F03_b_KG34HzFDijIDmEIwgQ,47
|
|
38
|
+
qbraid_cli-0.8.1.dist-info/top_level.txt,sha256=LTYJgeYSCHo9Il8vZu0yIPuGdGyNaIw6iRy6BeoZo8o,11
|
|
39
|
+
qbraid_cli-0.8.1.dist-info/RECORD,,
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
qbraid_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
qbraid_cli/_version.py,sha256=9V-2xXPHq42ZtrAPqEiMfWvgn4E8aKBKuNNPjD1ffOk,424
|
|
3
|
-
qbraid_cli/exceptions.py,sha256=KjlhYJhSHMVazaNiBjD_Ur06w4sekP8zRsFzBdyIpno,672
|
|
4
|
-
qbraid_cli/handlers.py,sha256=i3vdRtdy4bZKg3j6fwfVMz1ddhMgzlc2hhmj-vewxpI,6542
|
|
5
|
-
qbraid_cli/main.py,sha256=IRKazFqtFpoiq_xOtqb-IXDKAVs4lacFIRCQCoiGNF4,2503
|
|
6
|
-
qbraid_cli/admin/__init__.py,sha256=Suo_L1_yBodCvLM_fpw8gRIhD4mVVOXKObtxeoMaBVo,150
|
|
7
|
-
qbraid_cli/admin/app.py,sha256=__6lo-iFsbfz-ayD1-AS8X1z_gYhCad1NK17hnrL7HY,1451
|
|
8
|
-
qbraid_cli/admin/headers.py,sha256=oE2Ry9221ZV4tgYqFJNHAPilDw72xR3LqG_gBw7BFxM,6667
|
|
9
|
-
qbraid_cli/admin/validation.py,sha256=LkAVXlHtM0MhCa34MIWrfX59wGXMVlZmdVB4-AQ8fBk,1003
|
|
10
|
-
qbraid_cli/configure/__init__.py,sha256=6GU7vR6JYRGcMsmdrpFbwLO5VSUmnLgwSbtmGWMQND4,158
|
|
11
|
-
qbraid_cli/configure/actions.py,sha256=3rrWHaCAsogyx0Ll-lcjbSzldD4kPuz1z6VQiWebSWw,3203
|
|
12
|
-
qbraid_cli/configure/app.py,sha256=Y-XQYQ5lwQJkHW1vUNK44RpVmmA86mZR0xf_Q-yT7yE,2160
|
|
13
|
-
qbraid_cli/credits/__init__.py,sha256=t-3XAJFAXiu_jI4sgjaIOuNne_AoSYaSEsi-SSRkvPw,154
|
|
14
|
-
qbraid_cli/credits/app.py,sha256=iHikmjx8pylMFNzHckuauOg-Nb9pS7xQq_H75ibVJig,774
|
|
15
|
-
qbraid_cli/devices/__init__.py,sha256=_PU3eMQRV4DkPw-oCmfCPh8EbVmgG76ieEKuNsY9Xqc,154
|
|
16
|
-
qbraid_cli/devices/app.py,sha256=zxSxrEQn7irkJoME4S_CBnRqWeB8cqPaBsIMfpdYFk0,2530
|
|
17
|
-
qbraid_cli/devices/validation.py,sha256=YhShyUufgrKnx2XjXOXF-PqFJYklJT9CgeqIwKcNam4,809
|
|
18
|
-
qbraid_cli/envs/__init__.py,sha256=YgIoMWxfGqzmwfypO5JHYuCOu6BfFwb9NHgQel1IJM8,148
|
|
19
|
-
qbraid_cli/envs/activate.py,sha256=VpvVYSfQDlcmlNWJOgkLIQ2p8YXPPLG8Jbl5t8GHUDw,2140
|
|
20
|
-
qbraid_cli/envs/app.py,sha256=t6bRwJGy-M3PAu870ZsttsM8tpSB0OFasgCJiV9nTSA,8620
|
|
21
|
-
qbraid_cli/envs/create.py,sha256=uCRex_TcFYw26jUOU06Ta5I8Mq5pRqLVaOE6MxrrExs,4337
|
|
22
|
-
qbraid_cli/envs/data_handling.py,sha256=mTVzsj6KleeeYDKGhgD-IesF9KQQMSszKFSEo8Wrv9w,4001
|
|
23
|
-
qbraid_cli/jobs/__init__.py,sha256=bj9XmZ4JL8OtMMZbHIu-DPhpOMXGLSB-W1b0wO7wKro,148
|
|
24
|
-
qbraid_cli/jobs/app.py,sha256=kmg9mYla3Nd7EdjQlFu7IOvm7sejLNfPPA6Qeet-IfE,4898
|
|
25
|
-
qbraid_cli/jobs/toggle_braket.py,sha256=d5C_Di80jWMFlh-77eH8YY9pjMKWXK5abenUDtPlE_I,6662
|
|
26
|
-
qbraid_cli/jobs/validation.py,sha256=xNbjUggMhUs4wzkuRm4PuFPi_wrElYicUgYXLznHz3U,2983
|
|
27
|
-
qbraid_cli/kernels/__init__.py,sha256=VhpBota_v7OoiGxrPCqJU4XBVcolf81mbCYGSxXzVhc,154
|
|
28
|
-
qbraid_cli/kernels/app.py,sha256=ZJWVdKzCDfzGnA1pqp01vDbE7fh8p84jC-y6DDgWlxc,3373
|
|
29
|
-
qbraid_cli-0.8.0.dev6.dist-info/METADATA,sha256=2B553-9K_zSo24YJE_PL1t31R9ZQRcvv9TV0CcT-yzA,5919
|
|
30
|
-
qbraid_cli-0.8.0.dev6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
31
|
-
qbraid_cli-0.8.0.dev6.dist-info/entry_points.txt,sha256=c5ZJ7NjbxhDqMpou9q5F03_b_KG34HzFDijIDmEIwgQ,47
|
|
32
|
-
qbraid_cli-0.8.0.dev6.dist-info/top_level.txt,sha256=LTYJgeYSCHo9Il8vZu0yIPuGdGyNaIw6iRy6BeoZo8o,11
|
|
33
|
-
qbraid_cli-0.8.0.dev6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|