qbraid-cli 0.8.5a1__py3-none-any.whl → 0.12.0a0__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.
- qbraid_cli/_version.py +16 -14
- qbraid_cli/account/__init__.py +11 -0
- qbraid_cli/account/app.py +67 -0
- qbraid_cli/admin/app.py +21 -13
- qbraid_cli/admin/headers.py +132 -23
- qbraid_cli/admin/validation.py +1 -8
- qbraid_cli/chat/__init__.py +11 -0
- qbraid_cli/chat/app.py +76 -0
- qbraid_cli/configure/actions.py +21 -2
- qbraid_cli/configure/app.py +147 -2
- qbraid_cli/configure/claude_config.py +215 -0
- qbraid_cli/devices/app.py +27 -4
- qbraid_cli/envs/activate.py +38 -8
- qbraid_cli/envs/app.py +716 -89
- qbraid_cli/envs/create.py +3 -2
- qbraid_cli/files/__init__.py +11 -0
- qbraid_cli/files/app.py +139 -0
- qbraid_cli/handlers.py +35 -5
- qbraid_cli/jobs/app.py +33 -13
- qbraid_cli/jobs/toggle_braket.py +2 -13
- qbraid_cli/jobs/validation.py +1 -0
- qbraid_cli/kernels/app.py +4 -3
- qbraid_cli/main.py +57 -13
- qbraid_cli/mcp/__init__.py +10 -0
- qbraid_cli/mcp/app.py +126 -0
- qbraid_cli/mcp/serve.py +321 -0
- qbraid_cli/pip/app.py +2 -2
- qbraid_cli/pip/hooks.py +1 -0
- {qbraid_cli-0.8.5a1.dist-info → qbraid_cli-0.12.0a0.dist-info}/METADATA +37 -14
- qbraid_cli-0.12.0a0.dist-info/RECORD +46 -0
- {qbraid_cli-0.8.5a1.dist-info → qbraid_cli-0.12.0a0.dist-info}/WHEEL +1 -1
- {qbraid_cli-0.8.5a1.dist-info → qbraid_cli-0.12.0a0.dist-info/licenses}/LICENSE +6 -4
- qbraid_cli/admin/buildlogs.py +0 -114
- qbraid_cli/credits/__init__.py +0 -11
- qbraid_cli/credits/app.py +0 -35
- qbraid_cli-0.8.5a1.dist-info/RECORD +0 -39
- {qbraid_cli-0.8.5a1.dist-info → qbraid_cli-0.12.0a0.dist-info}/entry_points.txt +0 -0
- {qbraid_cli-0.8.5a1.dist-info → qbraid_cli-0.12.0a0.dist-info}/top_level.txt +0 -0
qbraid_cli/_version.py
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
TYPE_CHECKING = False
|
|
4
|
-
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple, Union
|
|
6
|
-
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
-
else:
|
|
8
|
-
VERSION_TUPLE = object
|
|
1
|
+
# Copyright (c) 2025, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
9
3
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
__version_tuple__: VERSION_TUPLE
|
|
13
|
-
version_tuple: VERSION_TUPLE
|
|
4
|
+
"""
|
|
5
|
+
Module defining package version.
|
|
14
6
|
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import importlib.metadata
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
__version__ = importlib.metadata.version("qbraid-cli")
|
|
13
|
+
except Exception: # pylint: disable=broad-exception-caught # pragma: no cover
|
|
14
|
+
__version__ = "dev"
|
|
15
|
+
|
|
16
|
+
__version_tuple__ = tuple(int(part) if part.isdigit() else part for part in __version__.split("."))
|
|
17
|
+
|
|
18
|
+
__all__ = ["__version__", "__version_tuple__"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module defining commands in the 'qbraid user' namespace.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import rich
|
|
12
|
+
import typer
|
|
13
|
+
|
|
14
|
+
from qbraid_cli.configure.actions import QBRAID_ORG_MODEL_ENABLED
|
|
15
|
+
from qbraid_cli.handlers import run_progress_task
|
|
16
|
+
|
|
17
|
+
account_app = typer.Typer(help="Manage qBraid account.", no_args_is_help=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@account_app.command(name="credits")
|
|
21
|
+
def account_credits():
|
|
22
|
+
"""Get number of qBraid credits remaining."""
|
|
23
|
+
|
|
24
|
+
def get_credits() -> float:
|
|
25
|
+
from qbraid_core import QbraidClient
|
|
26
|
+
|
|
27
|
+
client = QbraidClient()
|
|
28
|
+
return client.user_credits_value()
|
|
29
|
+
|
|
30
|
+
qbraid_credits: float = run_progress_task(get_credits)
|
|
31
|
+
typer.secho(
|
|
32
|
+
f"{typer.style('qBraid credits remaining:')} "
|
|
33
|
+
f"{typer.style(f'{qbraid_credits:.4f}', fg=typer.colors.MAGENTA, bold=True)}",
|
|
34
|
+
nl=True, # Ensure a newline after output (default is True)
|
|
35
|
+
)
|
|
36
|
+
rich.print("\nFor more information, visit: https://docs.qbraid.com/home/pricing#credits")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@account_app.command(name="info")
|
|
40
|
+
def account_info():
|
|
41
|
+
"""Get qBraid account (user) metadata."""
|
|
42
|
+
|
|
43
|
+
def get_user() -> dict[str, Any]:
|
|
44
|
+
from qbraid_core import QbraidSession
|
|
45
|
+
|
|
46
|
+
session = QbraidSession()
|
|
47
|
+
user = session.get_user()
|
|
48
|
+
personal_info: dict = user.get("personalInformation", {})
|
|
49
|
+
metadata = {
|
|
50
|
+
"_id": user.get("_id"),
|
|
51
|
+
"userName": user.get("userName"),
|
|
52
|
+
"email": user.get("email"),
|
|
53
|
+
"joinedDate": user.get("createdAt", "Unknown"),
|
|
54
|
+
"activePlan": user.get("activePlan", "") or "Free",
|
|
55
|
+
"role": personal_info.get("role", "") or "guest",
|
|
56
|
+
}
|
|
57
|
+
if QBRAID_ORG_MODEL_ENABLED:
|
|
58
|
+
metadata["organization"] = personal_info.get("organization", "") or "qbraid"
|
|
59
|
+
|
|
60
|
+
return metadata
|
|
61
|
+
|
|
62
|
+
info = run_progress_task(get_user)
|
|
63
|
+
rich.print(info)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
account_app()
|
qbraid_cli/admin/app.py
CHANGED
|
@@ -5,16 +5,19 @@
|
|
|
5
5
|
Module defining commands in the 'qbraid admin' namespace.
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
|
+
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
8
11
|
import typer
|
|
9
12
|
|
|
10
|
-
from qbraid_cli.admin.
|
|
11
|
-
from qbraid_cli.admin.
|
|
12
|
-
from qbraid_cli.admin.validation import validate_header_type, validate_paths_exist
|
|
13
|
+
from qbraid_cli.admin.headers import HeaderType, check_and_fix_headers
|
|
14
|
+
from qbraid_cli.admin.validation import validate_paths_exist
|
|
13
15
|
|
|
14
16
|
admin_app = typer.Typer(
|
|
15
|
-
help="CI/CD commands for qBraid maintainers.",
|
|
17
|
+
help="CI/CD commands for qBraid maintainers.",
|
|
18
|
+
pretty_exceptions_show_locals=False,
|
|
19
|
+
no_args_is_help=True,
|
|
16
20
|
)
|
|
17
|
-
admin_app.add_typer(buildlogs_app, name="buildlogs")
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
@admin_app.command(name="headers")
|
|
@@ -22,25 +25,30 @@ def admin_headers(
|
|
|
22
25
|
src_paths: list[str] = typer.Argument(
|
|
23
26
|
..., help="Source file or directory paths to verify.", callback=validate_paths_exist
|
|
24
27
|
),
|
|
25
|
-
header_type:
|
|
26
|
-
"default",
|
|
27
|
-
"--type",
|
|
28
|
-
"-t",
|
|
29
|
-
help="Type of header to use ('default' or 'gpl').",
|
|
30
|
-
callback=validate_header_type,
|
|
28
|
+
header_type: HeaderType = typer.Option(
|
|
29
|
+
"default", "--type", "-t", help="Type of header to use."
|
|
31
30
|
),
|
|
32
31
|
skip_files: list[str] = typer.Option(
|
|
33
|
-
[], "--skip", "-s", help="Files to skip during verification."
|
|
32
|
+
[], "--skip", "-s", help="Files to skip during verification."
|
|
34
33
|
),
|
|
35
34
|
fix: bool = typer.Option(
|
|
36
35
|
False, "--fix", "-f", help="Whether to fix the headers instead of just verifying."
|
|
37
36
|
),
|
|
37
|
+
project_name: Optional[str] = typer.Option(
|
|
38
|
+
None, "--project", "-p", help="Name of the project to use in the header."
|
|
39
|
+
),
|
|
38
40
|
):
|
|
39
41
|
"""
|
|
40
42
|
Verifies and optionally fixes qBraid headers in specified files and directories.
|
|
41
43
|
|
|
42
44
|
"""
|
|
43
|
-
check_and_fix_headers(
|
|
45
|
+
check_and_fix_headers(
|
|
46
|
+
src_paths,
|
|
47
|
+
header_type=header_type,
|
|
48
|
+
skip_files=skip_files,
|
|
49
|
+
fix=fix,
|
|
50
|
+
project_name=project_name,
|
|
51
|
+
)
|
|
44
52
|
|
|
45
53
|
|
|
46
54
|
if __name__ == "__main__":
|
qbraid_cli/admin/headers.py
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
# Copyright (c) 2024, qBraid Development Team
|
|
2
2
|
# All rights reserved.
|
|
3
3
|
|
|
4
|
+
# pylint: disable=too-many-branches,too-many-statements
|
|
5
|
+
|
|
4
6
|
"""
|
|
5
7
|
Script to verify qBraid copyright file headers
|
|
6
8
|
|
|
7
9
|
"""
|
|
10
|
+
|
|
11
|
+
import datetime
|
|
8
12
|
import os
|
|
13
|
+
import warnings
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from pathlib import Path
|
|
9
16
|
from typing import Optional
|
|
10
17
|
|
|
11
18
|
import typer
|
|
@@ -13,51 +20,144 @@ from rich.console import Console
|
|
|
13
20
|
|
|
14
21
|
from qbraid_cli.handlers import handle_error
|
|
15
22
|
|
|
16
|
-
|
|
23
|
+
CURR_YEAR = datetime.datetime.now().year
|
|
24
|
+
PREV_YEAR = CURR_YEAR - 1
|
|
25
|
+
|
|
26
|
+
COMMENT_MARKER = {
|
|
27
|
+
".py": "#",
|
|
28
|
+
".js": "//",
|
|
29
|
+
".ts": "//",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
VALID_EXTS = tuple(COMMENT_MARKER.keys())
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class HeaderType(Enum):
|
|
36
|
+
"""Type of header to use."""
|
|
17
37
|
|
|
18
|
-
|
|
38
|
+
default = "default" # pylint: disable=invalid-name
|
|
39
|
+
gpl = "gpl" # pylint: disable=invalid-name
|
|
40
|
+
apache = "apache" # pylint: disable=invalid-name
|
|
41
|
+
mit = "mit" # pylint: disable=invalid-name
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
DEFAULT_HEADER = f"""# Copyright (c) {CURR_YEAR}, qBraid Development Team
|
|
19
45
|
# All rights reserved.
|
|
20
46
|
"""
|
|
21
47
|
|
|
22
|
-
DEFAULT_HEADER_GPL = """# Copyright (C)
|
|
48
|
+
DEFAULT_HEADER_GPL = f"""# Copyright (C) {CURR_YEAR} qBraid
|
|
23
49
|
#
|
|
24
|
-
# This file is part of
|
|
50
|
+
# This file is part of {{project_name}}
|
|
25
51
|
#
|
|
26
|
-
#
|
|
52
|
+
# {{project_name_start}} is free software released under the GNU General Public License v3
|
|
27
53
|
# or later. You can redistribute and/or modify it under the terms of the GPL v3.
|
|
28
54
|
# See the LICENSE file in the project root or <https://www.gnu.org/licenses/gpl-3.0.html>.
|
|
29
55
|
#
|
|
30
|
-
# THERE IS NO WARRANTY for
|
|
56
|
+
# THERE IS NO WARRANTY for {{project_name}}, as per Section 15 of the GPL v3.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
DEFAULT_HEADER_APACHE = f"""# Copyright {CURR_YEAR} qBraid
|
|
60
|
+
#
|
|
61
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
62
|
+
# you may not use this file except in compliance with the License.
|
|
63
|
+
# You may obtain a copy of the License at
|
|
64
|
+
#
|
|
65
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
66
|
+
#
|
|
67
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
68
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
69
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
70
|
+
# See the License for the specific language governing permissions and
|
|
71
|
+
# limitations under the License.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
DEFAULT_HEADER_MIT = f"""# MIT License
|
|
75
|
+
#
|
|
76
|
+
# Copyright (c) {CURR_YEAR} qBraid
|
|
77
|
+
#
|
|
78
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
79
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
80
|
+
# in the Software without restriction, including without limitation the rights
|
|
81
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
82
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
83
|
+
# furnished to do so, subject to the following conditions:
|
|
84
|
+
#
|
|
85
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
86
|
+
# copies or substantial portions of the Software.
|
|
87
|
+
#
|
|
88
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
89
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
90
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
91
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
92
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
93
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
94
|
+
# SOFTWARE.
|
|
31
95
|
"""
|
|
32
96
|
|
|
33
97
|
HEADER_TYPES = {
|
|
34
|
-
|
|
35
|
-
|
|
98
|
+
HeaderType.default: DEFAULT_HEADER,
|
|
99
|
+
HeaderType.gpl: DEFAULT_HEADER_GPL,
|
|
100
|
+
HeaderType.apache: DEFAULT_HEADER_APACHE,
|
|
101
|
+
HeaderType.mit: DEFAULT_HEADER_MIT,
|
|
36
102
|
}
|
|
37
103
|
|
|
38
104
|
|
|
105
|
+
def get_formatted_header(header_type: HeaderType, project_name: Optional[str] = None) -> str:
|
|
106
|
+
"""Get the formatted header based on the header type
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
header_type (str): The type of header to use.
|
|
110
|
+
project_name (str): The name of the project to use in the header.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
str: The formatted header
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
header = HEADER_TYPES[header_type]
|
|
117
|
+
if header_type == HeaderType.gpl:
|
|
118
|
+
if project_name is None:
|
|
119
|
+
handle_error("ValueError", "Project name is required for GPL header")
|
|
120
|
+
|
|
121
|
+
if project_name.split(" ")[0].lower() == "the":
|
|
122
|
+
project_name = project_name[:1].lower() + project_name[1:]
|
|
123
|
+
project_name_start = project_name[:1].upper() + project_name[1:]
|
|
124
|
+
else:
|
|
125
|
+
project_name_start = project_name
|
|
126
|
+
return header.format(project_name=project_name, project_name_start=project_name_start)
|
|
127
|
+
|
|
128
|
+
if project_name is not None:
|
|
129
|
+
warnings.warn(f"\nProject name is not used for header type '{header_type}'.", UserWarning)
|
|
130
|
+
|
|
131
|
+
return header
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _get_comment_marker(file_path: str, default: Optional[str] = None) -> str:
|
|
135
|
+
file_ext = Path(file_path).suffix
|
|
136
|
+
return COMMENT_MARKER.get(file_ext, default)
|
|
137
|
+
|
|
138
|
+
|
|
39
139
|
def check_and_fix_headers(
|
|
40
140
|
src_paths: list[str],
|
|
41
|
-
header_type:
|
|
141
|
+
header_type: HeaderType = HeaderType.default,
|
|
42
142
|
skip_files: Optional[list[str]] = None,
|
|
43
143
|
fix: bool = False,
|
|
144
|
+
project_name: Optional[str] = None,
|
|
44
145
|
) -> None:
|
|
45
146
|
"""Script to add or verify qBraid copyright file headers"""
|
|
46
147
|
try:
|
|
47
|
-
header =
|
|
148
|
+
header = get_formatted_header(header_type, project_name)
|
|
48
149
|
except KeyError:
|
|
150
|
+
members = HeaderType._member_names_ # pylint: disable=no-member,protected-access
|
|
49
151
|
handle_error(
|
|
50
152
|
error_type="ValueError",
|
|
51
|
-
message=(
|
|
52
|
-
f"Invalid header type: {HEADER_TYPES}. Expected one of {list(HEADER_TYPES.keys())}"
|
|
53
|
-
),
|
|
153
|
+
message=(f"Invalid header type: {HEADER_TYPES}. Expected one of {members}"),
|
|
54
154
|
)
|
|
55
155
|
|
|
56
156
|
for path in src_paths:
|
|
57
157
|
if not os.path.exists(path):
|
|
58
158
|
handle_error(error_type="FileNotFoundError", message=f"Path '{path}' does not exist.")
|
|
59
159
|
|
|
60
|
-
|
|
160
|
+
header_prev_year = header.replace(str(CURR_YEAR), str(PREV_YEAR))
|
|
61
161
|
|
|
62
162
|
skip_files = skip_files or []
|
|
63
163
|
|
|
@@ -73,7 +173,8 @@ def check_and_fix_headers(
|
|
|
73
173
|
if os.path.basename(file_path) == "__init__.py":
|
|
74
174
|
return not content.strip()
|
|
75
175
|
|
|
76
|
-
|
|
176
|
+
comment_marker = _get_comment_marker(file_path)
|
|
177
|
+
skip_header_tag = f"{comment_marker} qbraid: skip-header"
|
|
77
178
|
line_number = 0
|
|
78
179
|
|
|
79
180
|
for line in content.splitlines():
|
|
@@ -89,10 +190,12 @@ def check_and_fix_headers(
|
|
|
89
190
|
with open(file_path, "r", encoding="ISO-8859-1") as f:
|
|
90
191
|
content = f.read()
|
|
91
192
|
|
|
193
|
+
comment_marker = _get_comment_marker(file_path)
|
|
194
|
+
|
|
92
195
|
# This finds the start of the actual content after skipping initial whitespace and comments.
|
|
93
196
|
lines = content.splitlines()
|
|
94
197
|
first_non_comment_line_index = next(
|
|
95
|
-
(i for i, line in enumerate(lines) if not line.strip().startswith(
|
|
198
|
+
(i for i, line in enumerate(lines) if not line.strip().startswith(comment_marker)), None
|
|
96
199
|
)
|
|
97
200
|
|
|
98
201
|
# Prepare the content by stripping leading and trailing whitespace and separating into lines
|
|
@@ -102,10 +205,13 @@ def check_and_fix_headers(
|
|
|
102
205
|
else ""
|
|
103
206
|
)
|
|
104
207
|
|
|
208
|
+
updated_header = header.replace("#", comment_marker)
|
|
209
|
+
updated_prev_header = header_prev_year.replace("#", comment_marker)
|
|
210
|
+
|
|
105
211
|
# Check if the content already starts with the header or if the file should be skipped
|
|
106
212
|
if (
|
|
107
|
-
content.lstrip().startswith(
|
|
108
|
-
or content.lstrip().startswith(
|
|
213
|
+
content.lstrip().startswith(updated_header)
|
|
214
|
+
or content.lstrip().startswith(updated_prev_header)
|
|
109
215
|
or should_skip(file_path, content)
|
|
110
216
|
):
|
|
111
217
|
return
|
|
@@ -114,7 +220,7 @@ def check_and_fix_headers(
|
|
|
114
220
|
failed_headers.append(file_path)
|
|
115
221
|
else:
|
|
116
222
|
# Form the new content by combining the header, one blank line, and the actual content
|
|
117
|
-
new_content =
|
|
223
|
+
new_content = updated_header.strip() + "\n\n" + actual_content + "\n"
|
|
118
224
|
with open(file_path, "w", encoding="ISO-8859-1") as f:
|
|
119
225
|
f.write(new_content)
|
|
120
226
|
fixed_headers.append(file_path)
|
|
@@ -125,7 +231,7 @@ def check_and_fix_headers(
|
|
|
125
231
|
return count
|
|
126
232
|
for root, _, files in os.walk(directory):
|
|
127
233
|
for file in files:
|
|
128
|
-
if file.endswith(
|
|
234
|
+
if file.endswith(VALID_EXTS):
|
|
129
235
|
file_path = os.path.join(root, file)
|
|
130
236
|
replace_or_add_header(file_path, fix)
|
|
131
237
|
count += 1
|
|
@@ -135,14 +241,17 @@ def check_and_fix_headers(
|
|
|
135
241
|
for item in src_paths:
|
|
136
242
|
if os.path.isdir(item):
|
|
137
243
|
checked += process_files_in_directory(item, fix)
|
|
138
|
-
elif os.path.isfile(item) and item.endswith(
|
|
244
|
+
elif os.path.isfile(item) and item.endswith(VALID_EXTS):
|
|
139
245
|
replace_or_add_header(item, fix)
|
|
140
246
|
checked += 1
|
|
141
247
|
else:
|
|
142
|
-
|
|
248
|
+
if not os.path.isfile(item):
|
|
249
|
+
handle_error(
|
|
250
|
+
error_type="FileNotFoundError", message=f"Path '{item}' does not exist."
|
|
251
|
+
)
|
|
143
252
|
|
|
144
253
|
if checked == 0:
|
|
145
|
-
console.print("[bold]No
|
|
254
|
+
console.print(f"[bold]No {VALID_EXTS} files present. Nothing to do[/bold] 😴")
|
|
146
255
|
raise typer.Exit(0)
|
|
147
256
|
|
|
148
257
|
if not fix:
|
qbraid_cli/admin/validation.py
CHANGED
|
@@ -10,14 +10,7 @@ import os
|
|
|
10
10
|
|
|
11
11
|
import typer
|
|
12
12
|
|
|
13
|
-
from qbraid_cli.
|
|
14
|
-
from qbraid_cli.handlers import _format_list_items, validate_item
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def validate_header_type(value: str) -> str:
|
|
18
|
-
"""Validate header type."""
|
|
19
|
-
header_types = list(HEADER_TYPES.keys())
|
|
20
|
-
return validate_item(value, header_types, "Header type")
|
|
13
|
+
from qbraid_cli.handlers import _format_list_items
|
|
21
14
|
|
|
22
15
|
|
|
23
16
|
def validate_paths_exist(paths: list[str]) -> list[str]:
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module defining the qbraid chat namespace
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .app import ChatFormat, list_models_callback, prompt_callback
|
|
10
|
+
|
|
11
|
+
__all__ = ["list_models_callback", "prompt_callback", "ChatFormat"]
|
qbraid_cli/chat/app.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Copyright (c) 2024, qBraid Development Team
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module defining commands in the 'qbraid chat' namespace.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
from qbraid_cli.handlers import handle_error, run_progress_task
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ChatFormat(str, Enum):
|
|
17
|
+
"""Format of the response from the chat service."""
|
|
18
|
+
|
|
19
|
+
text = "text" # pylint: disable=invalid-name
|
|
20
|
+
code = "code" # pylint: disable=invalid-name
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def list_models_callback():
|
|
24
|
+
"""List available chat models."""
|
|
25
|
+
# pylint: disable-next=import-outside-toplevel
|
|
26
|
+
from qbraid_core.services.chat import ChatClient
|
|
27
|
+
|
|
28
|
+
client = ChatClient()
|
|
29
|
+
|
|
30
|
+
models = run_progress_task(
|
|
31
|
+
client.get_models,
|
|
32
|
+
description="Connecting to chat service...",
|
|
33
|
+
include_error_traceback=False,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
console = Console()
|
|
37
|
+
table = Table(title="Available Chat Models\n", show_lines=True, title_justify="left")
|
|
38
|
+
|
|
39
|
+
table.add_column("Model", style="cyan", no_wrap=True)
|
|
40
|
+
table.add_column("Pricing [not bold](1k tokens ~750 words)", style="magenta")
|
|
41
|
+
table.add_column("Description", style="green")
|
|
42
|
+
|
|
43
|
+
for model in models:
|
|
44
|
+
table.add_row(
|
|
45
|
+
model["model"],
|
|
46
|
+
f"{model['pricing']['input']} credits / 1M input tokens\n"
|
|
47
|
+
f"{model['pricing']['output']} credits / 1M output tokens",
|
|
48
|
+
model["description"],
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
console.print(table)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def prompt_callback(prompt: str, model: str, response_format: ChatFormat, stream: bool):
|
|
55
|
+
"""Send a chat prompt to the chat service."""
|
|
56
|
+
# pylint: disable-next=import-outside-toplevel
|
|
57
|
+
from qbraid_core.services.chat import ChatClient, ChatServiceRequestError
|
|
58
|
+
|
|
59
|
+
client = ChatClient()
|
|
60
|
+
|
|
61
|
+
if stream:
|
|
62
|
+
try:
|
|
63
|
+
for chunk in client.chat_stream(prompt, model, response_format):
|
|
64
|
+
print(chunk, end="")
|
|
65
|
+
except ChatServiceRequestError as err:
|
|
66
|
+
handle_error(message=str(err), include_traceback=False)
|
|
67
|
+
else:
|
|
68
|
+
content = run_progress_task(
|
|
69
|
+
client.chat,
|
|
70
|
+
prompt,
|
|
71
|
+
model,
|
|
72
|
+
response_format,
|
|
73
|
+
description="Connecting to chat service...",
|
|
74
|
+
include_error_traceback=False,
|
|
75
|
+
)
|
|
76
|
+
print(content)
|
qbraid_cli/configure/actions.py
CHANGED
|
@@ -15,6 +15,8 @@ import typer
|
|
|
15
15
|
from qbraid_core.config import (
|
|
16
16
|
DEFAULT_CONFIG_SECTION,
|
|
17
17
|
DEFAULT_ENDPOINT_URL,
|
|
18
|
+
DEFAULT_ORGANIZATION,
|
|
19
|
+
DEFAULT_WORKSPACE,
|
|
18
20
|
USER_CONFIG_PATH,
|
|
19
21
|
load_config,
|
|
20
22
|
save_config,
|
|
@@ -23,6 +25,8 @@ from rich.console import Console
|
|
|
23
25
|
|
|
24
26
|
from qbraid_cli.handlers import handle_filesystem_operation
|
|
25
27
|
|
|
28
|
+
QBRAID_ORG_MODEL_ENABLED = False # Set to True if organization/workspace model is enabled
|
|
29
|
+
|
|
26
30
|
|
|
27
31
|
def validate_input(key: str, value: str) -> str:
|
|
28
32
|
"""Validate the user input based on the key.
|
|
@@ -37,6 +41,8 @@ def validate_input(key: str, value: str) -> str:
|
|
|
37
41
|
Raises:
|
|
38
42
|
typer.BadParameter: If the value is invalid
|
|
39
43
|
"""
|
|
44
|
+
value = value.strip()
|
|
45
|
+
|
|
40
46
|
if key == "url":
|
|
41
47
|
if not re.match(r"^https?://\S+$", value):
|
|
42
48
|
raise typer.BadParameter("Invalid URL format.")
|
|
@@ -44,7 +50,7 @@ def validate_input(key: str, value: str) -> str:
|
|
|
44
50
|
if not re.match(r"^\S+@\S+\.\S+$", value):
|
|
45
51
|
raise typer.BadParameter("Invalid email format.")
|
|
46
52
|
elif key == "api-key":
|
|
47
|
-
if not re.match(r"^[a-zA-Z0-9]
|
|
53
|
+
if not re.match(r"^[a-zA-Z0-9]+$", value):
|
|
48
54
|
raise typer.BadParameter("Invalid API key format.")
|
|
49
55
|
return value
|
|
50
56
|
|
|
@@ -90,11 +96,24 @@ def default_action(section: str = DEFAULT_CONFIG_SECTION):
|
|
|
90
96
|
if section not in config:
|
|
91
97
|
config[section] = {}
|
|
92
98
|
|
|
93
|
-
default_values = {
|
|
99
|
+
default_values = {
|
|
100
|
+
"url": DEFAULT_ENDPOINT_URL,
|
|
101
|
+
}
|
|
102
|
+
if QBRAID_ORG_MODEL_ENABLED:
|
|
103
|
+
default_values["organization"] = DEFAULT_ORGANIZATION
|
|
104
|
+
default_values["workspace"] = DEFAULT_WORKSPACE
|
|
94
105
|
|
|
95
106
|
config[section]["url"] = prompt_for_config(config, section, "url", default_values)
|
|
96
107
|
config[section]["api-key"] = prompt_for_config(config, section, "api-key", default_values)
|
|
97
108
|
|
|
109
|
+
if QBRAID_ORG_MODEL_ENABLED:
|
|
110
|
+
config[section]["organization"] = prompt_for_config(
|
|
111
|
+
config, section, "organization", default_values
|
|
112
|
+
)
|
|
113
|
+
config[section]["workspace"] = prompt_for_config(
|
|
114
|
+
config, section, "workspace", default_values
|
|
115
|
+
)
|
|
116
|
+
|
|
98
117
|
for key in list(config[section]):
|
|
99
118
|
if not config[section][key]:
|
|
100
119
|
del config[section][key]
|