qubecli 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qubecli/__init__.py +0 -0
- qubecli/main.py +186 -0
- qubecli-1.0.0.dist-info/METADATA +135 -0
- qubecli-1.0.0.dist-info/RECORD +7 -0
- qubecli-1.0.0.dist-info/WHEEL +4 -0
- qubecli-1.0.0.dist-info/entry_points.txt +2 -0
- qubecli-1.0.0.dist-info/licenses/LICENSE +21 -0
qubecli/__init__.py
ADDED
|
File without changes
|
qubecli/main.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated, Generator, Optional
|
|
6
|
+
|
|
7
|
+
import grpc
|
|
8
|
+
import typer
|
|
9
|
+
from qubecore_client import QubeClient
|
|
10
|
+
|
|
11
|
+
_CONFIG_PATH = Path.home() / ".qubecore" / "config.json"
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(help="QubeCore CLI — Quantum Computing Operating System")
|
|
14
|
+
config_app = typer.Typer(help="서버 설정 관리")
|
|
15
|
+
app.add_typer(config_app, name="config")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _State:
|
|
19
|
+
address: str = "localhost:50051"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_state = _State()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _load_config() -> dict:
|
|
26
|
+
if _CONFIG_PATH.exists():
|
|
27
|
+
return json.loads(_CONFIG_PATH.read_text())
|
|
28
|
+
return {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _save_config(data: dict) -> None:
|
|
32
|
+
_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
_CONFIG_PATH.write_text(json.dumps(data, indent=2))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _resolve_address(host: Optional[str]) -> str:
|
|
37
|
+
if host is not None:
|
|
38
|
+
return host
|
|
39
|
+
return _load_config().get("host", "localhost:50051")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@contextmanager
|
|
43
|
+
def _get_client(address: str) -> Generator[QubeClient, None, None]:
|
|
44
|
+
client = QubeClient(address=address)
|
|
45
|
+
try:
|
|
46
|
+
yield client
|
|
47
|
+
except grpc.RpcError as e:
|
|
48
|
+
if e.code() == grpc.StatusCode.UNAVAILABLE:
|
|
49
|
+
typer.echo(f"서버에 연결할 수 없습니다: {address}", err=True)
|
|
50
|
+
typer.echo(" 'qubecore config set host <host:port>' 로 서버 주소를 확인하세요.", err=True)
|
|
51
|
+
else:
|
|
52
|
+
typer.echo(f"gRPC 오류: {e.details()}", err=True)
|
|
53
|
+
raise typer.Exit(1)
|
|
54
|
+
finally:
|
|
55
|
+
client.close()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _priority_label(priority: int) -> str:
|
|
59
|
+
return {1: "CRITICAL", 2: "HIGH", 3: "NORMAL", 4: "LOW"}.get(priority, str(priority))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@app.callback()
|
|
63
|
+
def _callback(
|
|
64
|
+
host: Annotated[Optional[str], typer.Option(help="QubeCore 서버 주소 (host:port)")] = None,
|
|
65
|
+
ver: Annotated[bool, typer.Option("-v", "--version", help="버전 출력", is_eager=True)] = False,
|
|
66
|
+
) -> None:
|
|
67
|
+
if ver:
|
|
68
|
+
typer.echo(f"v{version('qubecli')}")
|
|
69
|
+
raise typer.Exit()
|
|
70
|
+
_state.address = _resolve_address(host)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@config_app.command("set")
|
|
74
|
+
def config_set(
|
|
75
|
+
key: Annotated[str, typer.Argument(help="설정 키")],
|
|
76
|
+
value: Annotated[str, typer.Argument(help="설정 값")],
|
|
77
|
+
) -> None:
|
|
78
|
+
"""설정값 저장 (예: qubecore config set host 192.168.1.100:50051)"""
|
|
79
|
+
if key not in ("host",):
|
|
80
|
+
typer.echo(f"알 수 없는 키: {key}. 사용 가능: host", err=True)
|
|
81
|
+
raise typer.Exit(1)
|
|
82
|
+
cfg = _load_config()
|
|
83
|
+
cfg[key] = value
|
|
84
|
+
_save_config(cfg)
|
|
85
|
+
typer.echo(f"✓ {key} = {value} 저장 완료 ({_CONFIG_PATH})")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@config_app.command("get")
|
|
89
|
+
def config_get(
|
|
90
|
+
key: Annotated[Optional[str], typer.Argument(help="설정 키 (생략 시 전체 출력)")] = None,
|
|
91
|
+
) -> None:
|
|
92
|
+
"""설정값 조회"""
|
|
93
|
+
cfg = _load_config()
|
|
94
|
+
if not cfg:
|
|
95
|
+
typer.echo("저장된 설정 없음")
|
|
96
|
+
return
|
|
97
|
+
if key:
|
|
98
|
+
typer.echo(f" {key} : {cfg.get(key, '(없음)')}")
|
|
99
|
+
else:
|
|
100
|
+
for k, v in cfg.items():
|
|
101
|
+
typer.echo(f" {k} : {v}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@config_app.command("unset")
|
|
105
|
+
def config_unset(
|
|
106
|
+
key: Annotated[str, typer.Argument(help="삭제할 설정 키")],
|
|
107
|
+
) -> None:
|
|
108
|
+
"""설정값 삭제"""
|
|
109
|
+
cfg = _load_config()
|
|
110
|
+
if key not in cfg:
|
|
111
|
+
typer.echo(f" {key} : 설정된 값 없음")
|
|
112
|
+
return
|
|
113
|
+
del cfg[key]
|
|
114
|
+
_save_config(cfg)
|
|
115
|
+
typer.echo(f"✓ {key} 삭제 완료")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@app.command()
|
|
119
|
+
def submit(
|
|
120
|
+
backend: Annotated[str, typer.Option(help="백엔드 이름 (backend1, kreo.sc-20 등)")],
|
|
121
|
+
circuit: Annotated[str, typer.Option(help="회로 문자열 (QASM2/QASM3/JSON)")],
|
|
122
|
+
shots: Annotated[int, typer.Option(help="실행 횟수")],
|
|
123
|
+
priority: Annotated[Optional[int], typer.Option(help="우선순위 1~4 (기본값 3=NORMAL)")] = None,
|
|
124
|
+
optimization_level: Annotated[Optional[int], typer.Option(help="최적화 레벨 0~3")] = None,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""게이트 회로 실행 작업 제출"""
|
|
127
|
+
with _get_client(_state.address) as client:
|
|
128
|
+
job_id = client.submit_gate_job(
|
|
129
|
+
backend_name=backend,
|
|
130
|
+
gate_circuits=[circuit.encode("raw_unicode_escape").decode("unicode_escape")],
|
|
131
|
+
shots=shots,
|
|
132
|
+
priority=priority,
|
|
133
|
+
optimization_level=optimization_level,
|
|
134
|
+
)
|
|
135
|
+
typer.echo("✓ 작업 제출 완료")
|
|
136
|
+
typer.echo(f" job_id : {job_id}")
|
|
137
|
+
typer.echo(f" priority : {_priority_label(priority or 3)}")
|
|
138
|
+
typer.echo(f" status : PENDING")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@app.command()
|
|
142
|
+
def status(
|
|
143
|
+
job_id: Annotated[str, typer.Option(help="작업 ID")],
|
|
144
|
+
) -> None:
|
|
145
|
+
"""작업 상태 조회"""
|
|
146
|
+
with _get_client(_state.address) as client:
|
|
147
|
+
job = client.get_job_status(job_id)
|
|
148
|
+
typer.echo(f" job_id : {job['job_id']}")
|
|
149
|
+
typer.echo(f" status : {job['status']}")
|
|
150
|
+
typer.echo(f" priority : {_priority_label(job['priority'])}")
|
|
151
|
+
typer.echo(f" submitted : {job['submitted_at']}")
|
|
152
|
+
typer.echo(f" started : {job['started_at'] or '(미시작)'}")
|
|
153
|
+
typer.echo(f" finished : {job['finished_at'] or '(미완료)'}")
|
|
154
|
+
if job["error"]:
|
|
155
|
+
typer.echo(f" error : {job['error']}")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@app.command()
|
|
159
|
+
def result(
|
|
160
|
+
job_id: Annotated[str, typer.Option(help="작업 ID")],
|
|
161
|
+
) -> None:
|
|
162
|
+
"""작업 결과 조회"""
|
|
163
|
+
with _get_client(_state.address) as client:
|
|
164
|
+
job = client.get_job_result(job_id)
|
|
165
|
+
typer.echo(f" job_id : {job['job_id']}")
|
|
166
|
+
typer.echo(f" status : {job['status']}")
|
|
167
|
+
for i, r in enumerate(job["results"]):
|
|
168
|
+
typer.echo(f" [회로 {i}] counts : {r['counts_json']}")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@app.command()
|
|
172
|
+
def cancel(
|
|
173
|
+
job_id: Annotated[str, typer.Option(help="작업 ID")],
|
|
174
|
+
) -> None:
|
|
175
|
+
"""작업 취소 (PENDING 상태일 때만 가능)"""
|
|
176
|
+
with _get_client(_state.address) as client:
|
|
177
|
+
message = client.cancel_job(job_id)
|
|
178
|
+
typer.echo(f"✓ {message}")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def main() -> None:
|
|
182
|
+
app()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
main()
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qubecli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: QubeCore CLI — Command Line Interface for QubeCore
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 QubeCore Project
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Python: >=3.11
|
|
28
|
+
Requires-Dist: qubecore-client>=1.0.10
|
|
29
|
+
Requires-Dist: typer>=0.12.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# QubeCli
|
|
36
|
+
|
|
37
|
+
Command Line Interface for QubeCore — Quantum Computing Operating System.
|
|
38
|
+
|
|
39
|
+
## Overview
|
|
40
|
+
|
|
41
|
+
QubeCli provides a terminal-based interface for submitting and managing
|
|
42
|
+
quantum jobs on QubeCore. Built on [qubecore-client](https://github.com/sdt-quantum/qubecore-client).
|
|
43
|
+
|
|
44
|
+
## Requirements
|
|
45
|
+
|
|
46
|
+
- Python 3.11+
|
|
47
|
+
- QubeCore Server running and accessible
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install qubecli
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Set server address
|
|
59
|
+
qubecore config set host 192.168.1.100:50051
|
|
60
|
+
|
|
61
|
+
# Submit a job (use single quotes to avoid shell interpretation of \n)
|
|
62
|
+
qubecore submit --backend backend1 --circuit 'OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[2];\ncreg c[2];\nh q[0];\ncx q[0],q[1];\nmeasure q -> c;' --shots 100
|
|
63
|
+
|
|
64
|
+
# Check status
|
|
65
|
+
qubecore status --job-id <job_id>
|
|
66
|
+
|
|
67
|
+
# Fetch result
|
|
68
|
+
qubecore result --job-id <job_id>
|
|
69
|
+
|
|
70
|
+
# Cancel job
|
|
71
|
+
qubecore cancel --job-id <job_id>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Commands
|
|
75
|
+
|
|
76
|
+
### Version
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
qubecore -v
|
|
80
|
+
qubecore --version
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Server Configuration
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
qubecore config set host 192.168.1.100:50051
|
|
87
|
+
qubecore config get
|
|
88
|
+
qubecore config get host
|
|
89
|
+
qubecore config unset host
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Job Commands
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Submit
|
|
96
|
+
qubecore submit --backend <backend> --circuit <circuit_string> --shots <n>
|
|
97
|
+
qubecore submit --backend backend1 --circuit '...' --shots 100 --priority 2 --optimization-level 1
|
|
98
|
+
|
|
99
|
+
# Status
|
|
100
|
+
qubecore status --job-id <job_id>
|
|
101
|
+
|
|
102
|
+
# Result
|
|
103
|
+
qubecore result --job-id <job_id>
|
|
104
|
+
|
|
105
|
+
# Cancel (PENDING 상태일 때만 가능)
|
|
106
|
+
qubecore cancel --job-id <job_id>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
One-time server override:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
qubecore --host 10.0.0.1:50051 submit --backend backend1 --circuit "..." --shots 100
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Options
|
|
116
|
+
|
|
117
|
+
### `submit`
|
|
118
|
+
|
|
119
|
+
| Option | Required | Description |
|
|
120
|
+
|--------|----------|-------------|
|
|
121
|
+
| `--backend` | Yes | 백엔드 이름 (`backend1`, `kreo.sc-20` 등) |
|
|
122
|
+
| `--circuit` | Yes | 회로 문자열 (QASM2/QASM3/JSON) |
|
|
123
|
+
| `--shots` | Yes | 실행 횟수 |
|
|
124
|
+
| `--priority` | No | 우선순위 1~4 (1=CRITICAL, 2=HIGH, 3=NORMAL, 4=LOW) |
|
|
125
|
+
| `--optimization-level` | No | 최적화 레벨 0~3 |
|
|
126
|
+
|
|
127
|
+
## Compatibility
|
|
128
|
+
|
|
129
|
+
| qubecli | qubecore-client | qubecore |
|
|
130
|
+
|---------|----------------|----------|
|
|
131
|
+
| 1.x | 1.x | 1.x |
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
qubecli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
qubecli/main.py,sha256=MDjlRNGqEdq3Z6KciSsRGEnk_KgON3-1cv9RQELxYyM,5890
|
|
3
|
+
qubecli-1.0.0.dist-info/METADATA,sha256=7d1epRXAgkmPYFeiadQiUp8do33LTdcPxd4wFCF_8qo,3760
|
|
4
|
+
qubecli-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
5
|
+
qubecli-1.0.0.dist-info/entry_points.txt,sha256=wqFAyzHQSyGZAH-55bOWlBJxc0mxbPWRw6FxbkBcv1Q,47
|
|
6
|
+
qubecli-1.0.0.dist-info/licenses/LICENSE,sha256=MtfyWLHSlemM7mXY9Cx2xx97V_rzxlQhqXun0dXX2X8,1072
|
|
7
|
+
qubecli-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 QubeCore Project
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|