qall 0.1.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.
- qall/__init__.py +35 -0
- qall/cli/__init__.py +13 -0
- qall/cli/artifact.py +53 -0
- qall/cli/checkpoint.py +22 -0
- qall/cli/config.py +44 -0
- qall/cli/daemon.py +217 -0
- qall/cli/log.py +22 -0
- qall/cli/main.py +159 -0
- qall/cli/provider.py +60 -0
- qall/cli/registry.py +145 -0
- qall/cli/resource.py +28 -0
- qall/cli/task.py +113 -0
- qall/cli/utils.py +45 -0
- qall/cli/worker.py +42 -0
- qall/cli/workflow.py +79 -0
- qall/data/worker_catalog.yml +30 -0
- qall/visualization/__init__.py +13 -0
- qall-0.1.0.data/purelib/worker_catalog.yml +30 -0
- qall-0.1.0.dist-info/METADATA +121 -0
- qall-0.1.0.dist-info/RECORD +22 -0
- qall-0.1.0.dist-info/WHEEL +4 -0
- qall-0.1.0.dist-info/entry_points.txt +3 -0
qall/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
formatter = logging.Formatter(
|
|
18
|
+
fmt="[%(asctime)s] %(levelname)s | %(name)s | %(message)s",
|
|
19
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
console_handler = logging.StreamHandler()
|
|
23
|
+
console_handler.setFormatter(formatter)
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
logger.setLevel(logging.INFO)
|
|
27
|
+
logger.addHandler(console_handler)
|
|
28
|
+
|
|
29
|
+
# Optional: Prevent logs from propagating to the global root logger
|
|
30
|
+
# to avoid duplicate logs if the end-user also configures logging.
|
|
31
|
+
logger.propagate = False
|
|
32
|
+
|
|
33
|
+
from qorus.sdk import task, workflow, run
|
|
34
|
+
|
|
35
|
+
__all__ = ["task", "workflow", "run"]
|
qall/cli/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
qall/cli/artifact.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
from qorus.core import artifact
|
|
17
|
+
|
|
18
|
+
artifact_cli = typer.Typer(
|
|
19
|
+
name="artifact",
|
|
20
|
+
help="Inspect and download generated execution artifacts.",
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@artifact_cli.command("ls")
|
|
26
|
+
def list_artifacts(
|
|
27
|
+
workflow_run_id: str = typer.Argument(
|
|
28
|
+
..., help="The ID of the workflow run to list artifacts for."
|
|
29
|
+
)
|
|
30
|
+
):
|
|
31
|
+
typer.echo("Available artifacts:")
|
|
32
|
+
|
|
33
|
+
artifacts = artifact.list_artifacts(workflow_run_id)
|
|
34
|
+
|
|
35
|
+
if not artifacts:
|
|
36
|
+
typer.echo(" No artifacts found.")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
for name in artifacts:
|
|
40
|
+
typer.echo(f"- {typer.style(name, bold=True)}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@artifact_cli.command("info")
|
|
44
|
+
def artifact_info(
|
|
45
|
+
artifact_id: str = typer.Argument(
|
|
46
|
+
..., help="The unique identifier tracking hash of the generated asset."
|
|
47
|
+
)
|
|
48
|
+
):
|
|
49
|
+
"""Display download links, sizing, and origin execution contexts for a specific task artifact."""
|
|
50
|
+
art = artifact.get_artifact(artifact_id=artifact_id)
|
|
51
|
+
typer.echo(
|
|
52
|
+
f"Artifact Name: {art.name} | Task Run Origin: {art.task_run_id} | URL: {art.url}"
|
|
53
|
+
)
|
qall/cli/checkpoint.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
import qorus.core as core
|
|
17
|
+
|
|
18
|
+
checkpoint_cli = typer.Typer(
|
|
19
|
+
name="checkpoint",
|
|
20
|
+
help="Inspect generated execution task checkpoints.",
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
)
|
qall/cli/config.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
import qorus.core as core
|
|
19
|
+
|
|
20
|
+
config_cli = typer.Typer(
|
|
21
|
+
name="config",
|
|
22
|
+
help="Manage local configuration profile configurations.",
|
|
23
|
+
no_args_is_help=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@config_cli.command("init")
|
|
28
|
+
def config_init(
|
|
29
|
+
provider: Optional[str] = typer.Option(
|
|
30
|
+
"scaleway", "--provider", help="Default target cloud provider name."
|
|
31
|
+
),
|
|
32
|
+
erase: bool = typer.Option(
|
|
33
|
+
False, "--erase", help="Overwrite existing configuration files if present."
|
|
34
|
+
),
|
|
35
|
+
):
|
|
36
|
+
"""Initialize a default .qorus.yml project layout file inside the current directory boundary."""
|
|
37
|
+
res = core.init_config(provider_name=provider, erase_exists=erase)
|
|
38
|
+
|
|
39
|
+
if res is None:
|
|
40
|
+
typer.echo("Configuration file already exists. Use --erase to force overwrite.")
|
|
41
|
+
else:
|
|
42
|
+
typer.echo(
|
|
43
|
+
"Project workspace configuration initialized successfully (.qorus.yml)."
|
|
44
|
+
)
|
qall/cli/daemon.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import typer
|
|
16
|
+
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
from qorus.core.daemon import (
|
|
20
|
+
start_daemon,
|
|
21
|
+
run_task,
|
|
22
|
+
get_daemon_status,
|
|
23
|
+
stop_daemon,
|
|
24
|
+
restart_daemon,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
daemon_cli = typer.Typer(
|
|
28
|
+
name="daemon",
|
|
29
|
+
help="Manage daemon",
|
|
30
|
+
no_args_is_help=True,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@daemon_cli.command("start")
|
|
35
|
+
def daemon_start(
|
|
36
|
+
port: int = typer.Option(50053, "--port", "-p", help="gRPC port for the daemon"),
|
|
37
|
+
image: str = typer.Option(
|
|
38
|
+
None,
|
|
39
|
+
"--image",
|
|
40
|
+
"-i",
|
|
41
|
+
help="Daemon Docker image to use",
|
|
42
|
+
),
|
|
43
|
+
block_registry: str = typer.Option(
|
|
44
|
+
None,
|
|
45
|
+
"--block-dir",
|
|
46
|
+
"-b",
|
|
47
|
+
help="Path to block registry directory",
|
|
48
|
+
),
|
|
49
|
+
artifact_registry: str = typer.Option(
|
|
50
|
+
None,
|
|
51
|
+
"--artifact-dir",
|
|
52
|
+
"-a",
|
|
53
|
+
help="Path to artifact registry directory",
|
|
54
|
+
),
|
|
55
|
+
worker_provider: str = typer.Option(
|
|
56
|
+
None,
|
|
57
|
+
"--worker-provider",
|
|
58
|
+
"-w",
|
|
59
|
+
help="Name of the worker provider",
|
|
60
|
+
),
|
|
61
|
+
):
|
|
62
|
+
"""
|
|
63
|
+
Start a local Qorus daemon in a Docker container.
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
start_daemon(
|
|
67
|
+
port=port,
|
|
68
|
+
block_registry_path=block_registry,
|
|
69
|
+
artifact_registry_path=artifact_registry,
|
|
70
|
+
image=image,
|
|
71
|
+
worker_provider=worker_provider,
|
|
72
|
+
)
|
|
73
|
+
except KeyboardInterrupt:
|
|
74
|
+
typer.echo("Daemon stopped.")
|
|
75
|
+
except Exception as e:
|
|
76
|
+
typer.echo(f"Error: {e}", err=True)
|
|
77
|
+
raise typer.Exit(code=1)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@daemon_cli.command("stop")
|
|
81
|
+
def daemon_stop(
|
|
82
|
+
port: int = typer.Option(50053, "--port", "-p", help="gRPC port for the daemon"),
|
|
83
|
+
):
|
|
84
|
+
try:
|
|
85
|
+
stop_daemon(port)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
typer.echo(f"Error: {e}", err=True)
|
|
88
|
+
raise typer.Exit(code=1)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@daemon_cli.command("restart")
|
|
92
|
+
def daemon_restart(
|
|
93
|
+
port: int = typer.Option(50053, "--port", "-p", help="gRPC port for the daemon"),
|
|
94
|
+
):
|
|
95
|
+
try:
|
|
96
|
+
restart_daemon(port)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
typer.echo(f"Error: {e}", err=True)
|
|
99
|
+
raise typer.Exit(code=1)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@daemon_cli.command("run")
|
|
103
|
+
def daemon_run(
|
|
104
|
+
task_hash: str = typer.Argument(
|
|
105
|
+
..., help="Hash of the task stored in registry to execute."
|
|
106
|
+
),
|
|
107
|
+
artifact_hash: str = typer.Option(
|
|
108
|
+
None,
|
|
109
|
+
"--artifact-hash",
|
|
110
|
+
"-a",
|
|
111
|
+
help="Hash of the input artifact stored in registry for the task execution.",
|
|
112
|
+
),
|
|
113
|
+
daemon_image: str = typer.Option(
|
|
114
|
+
None,
|
|
115
|
+
"--daemon-image",
|
|
116
|
+
"-i",
|
|
117
|
+
help="Daemon Docker image to use",
|
|
118
|
+
),
|
|
119
|
+
daemon_address: Optional[str] = typer.Option(
|
|
120
|
+
None,
|
|
121
|
+
"--daemon-address",
|
|
122
|
+
"-d",
|
|
123
|
+
help="Daemon gRPC address (default: QORUS_DAEMON_ADDRESS env var or localhost:50053)",
|
|
124
|
+
),
|
|
125
|
+
block_registry: str = typer.Option(
|
|
126
|
+
None,
|
|
127
|
+
"--block-dir",
|
|
128
|
+
"-b",
|
|
129
|
+
help="Path to block registry directory",
|
|
130
|
+
),
|
|
131
|
+
artifact_registry: str = typer.Option(
|
|
132
|
+
None,
|
|
133
|
+
"--artifact-dir",
|
|
134
|
+
"-a",
|
|
135
|
+
help="Path to artifact registry directory",
|
|
136
|
+
),
|
|
137
|
+
worker_provider: str = typer.Option(
|
|
138
|
+
None,
|
|
139
|
+
"--worker-provider",
|
|
140
|
+
"-w",
|
|
141
|
+
help="Name of the worker provider",
|
|
142
|
+
),
|
|
143
|
+
timeout: int = typer.Option(
|
|
144
|
+
300, "--timeout", "-t", help="Timeout for the task execution."
|
|
145
|
+
),
|
|
146
|
+
):
|
|
147
|
+
"""
|
|
148
|
+
Orders a local Daemon instance to start and execute a given task.
|
|
149
|
+
"""
|
|
150
|
+
try:
|
|
151
|
+
artifacts = run_task(
|
|
152
|
+
task_hash=task_hash,
|
|
153
|
+
input_artifact_hash=artifact_hash,
|
|
154
|
+
daemon_address=daemon_address,
|
|
155
|
+
block_registry=block_registry,
|
|
156
|
+
artifact_registry=artifact_registry,
|
|
157
|
+
daemon_image=daemon_image,
|
|
158
|
+
timeout=timeout,
|
|
159
|
+
worker_provider=worker_provider,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if artifacts:
|
|
163
|
+
typer.echo(f"Task completed successfully!")
|
|
164
|
+
typer.echo(f"Output artifacts: {len(artifacts)}")
|
|
165
|
+
|
|
166
|
+
for artifact in artifacts:
|
|
167
|
+
typer.echo(f" - {artifact.hash}")
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
typer.echo(f"Error: {e}", err=True)
|
|
171
|
+
raise typer.Exit(code=1)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@daemon_cli.command("status")
|
|
175
|
+
def daemon_status(
|
|
176
|
+
daemon_image: str = typer.Option(
|
|
177
|
+
None,
|
|
178
|
+
"--daemon-image",
|
|
179
|
+
"-i",
|
|
180
|
+
help="Daemon Docker image to use",
|
|
181
|
+
),
|
|
182
|
+
daemon_address: Optional[str] = typer.Option(
|
|
183
|
+
None,
|
|
184
|
+
"--daemon-address",
|
|
185
|
+
"-d",
|
|
186
|
+
help="Daemon gRPC address (default: QORUS_DAEMON_ADDRESS env var or localhost:50053)",
|
|
187
|
+
),
|
|
188
|
+
block_registry: str = typer.Option(
|
|
189
|
+
None,
|
|
190
|
+
"--block-dir",
|
|
191
|
+
"-b",
|
|
192
|
+
help="Path to block registry directory",
|
|
193
|
+
),
|
|
194
|
+
artifact_registry: str = typer.Option(
|
|
195
|
+
None,
|
|
196
|
+
"--artifact-dir",
|
|
197
|
+
"-a",
|
|
198
|
+
help="Path to artifact registry directory",
|
|
199
|
+
),
|
|
200
|
+
):
|
|
201
|
+
"""
|
|
202
|
+
Check the status of the local daemon container.
|
|
203
|
+
"""
|
|
204
|
+
try:
|
|
205
|
+
status = get_daemon_status(
|
|
206
|
+
daemon_image=daemon_image,
|
|
207
|
+
daemon_address=daemon_address,
|
|
208
|
+
block_registry=block_registry,
|
|
209
|
+
artifact_registry=artifact_registry,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if status:
|
|
213
|
+
typer.echo(f"status: {status}")
|
|
214
|
+
|
|
215
|
+
except Exception as e:
|
|
216
|
+
typer.echo(f"Error: {e}", err=True)
|
|
217
|
+
raise typer.Exit(code=1)
|
qall/cli/log.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
import qorus.core as core
|
|
17
|
+
|
|
18
|
+
log_cli = typer.Typer(
|
|
19
|
+
name="logs",
|
|
20
|
+
help="Manage workflow and task logs generation.",
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
)
|
qall/cli/main.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import typer
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
import qorus.core as core
|
|
21
|
+
|
|
22
|
+
from .artifact import artifact_cli
|
|
23
|
+
from .provider import provider_cli
|
|
24
|
+
from .registry import registry_cli
|
|
25
|
+
from .log import log_cli
|
|
26
|
+
from .workflow import workflow_cli
|
|
27
|
+
from .resource import resource_cli
|
|
28
|
+
from .checkpoint import checkpoint_cli
|
|
29
|
+
from .config import config_cli
|
|
30
|
+
from .daemon import daemon_cli
|
|
31
|
+
from .task import task_cli
|
|
32
|
+
from .worker import worker_cli
|
|
33
|
+
from .utils import parse_extra_args
|
|
34
|
+
|
|
35
|
+
_DEFAULT_LOGGING_LEVEL = logging.INFO
|
|
36
|
+
|
|
37
|
+
cli = typer.Typer(
|
|
38
|
+
name="qorus",
|
|
39
|
+
help="A python tool for modern hybrid and quantum workflows.",
|
|
40
|
+
no_args_is_help=True,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
cli.add_typer(workflow_cli)
|
|
44
|
+
cli.add_typer(registry_cli)
|
|
45
|
+
cli.add_typer(artifact_cli)
|
|
46
|
+
cli.add_typer(provider_cli)
|
|
47
|
+
cli.add_typer(config_cli)
|
|
48
|
+
cli.add_typer(resource_cli)
|
|
49
|
+
cli.add_typer(log_cli)
|
|
50
|
+
cli.add_typer(daemon_cli)
|
|
51
|
+
cli.add_typer(task_cli)
|
|
52
|
+
cli.add_typer(worker_cli)
|
|
53
|
+
cli.add_typer(checkpoint_cli)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _setup_logging():
|
|
57
|
+
logging_level_str = os.getenv("QORUS_LOGGING_LEVEL")
|
|
58
|
+
|
|
59
|
+
if not logging_level_str and not _DEFAULT_LOGGING_LEVEL:
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
if logging_level_str:
|
|
63
|
+
mapping = logging.getLevelNamesMapping()
|
|
64
|
+
logging_level = mapping.get(logging_level_str.upper())
|
|
65
|
+
else:
|
|
66
|
+
logging_level = _DEFAULT_LOGGING_LEVEL
|
|
67
|
+
|
|
68
|
+
logging.basicConfig(level=logging_level)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
_setup_logging()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@cli.command("run")
|
|
75
|
+
def run(
|
|
76
|
+
entrypoint: str = typer.Argument(
|
|
77
|
+
..., help="Path to the Python script containing tasks."
|
|
78
|
+
),
|
|
79
|
+
local: bool = typer.Option(
|
|
80
|
+
False, "--local", help="Wether to run locally or remotely"
|
|
81
|
+
),
|
|
82
|
+
input_artifact_hash: Optional[str] = typer.Option(
|
|
83
|
+
None,
|
|
84
|
+
"--input-artifact-hash",
|
|
85
|
+
help="The artifact's hash the entrypoint task may require to execute.",
|
|
86
|
+
),
|
|
87
|
+
name: Optional[str] = typer.Option(
|
|
88
|
+
None,
|
|
89
|
+
"--name",
|
|
90
|
+
help="Name of the workflow to declare. Script file name if not provided",
|
|
91
|
+
),
|
|
92
|
+
version: Optional[str] = typer.Option(
|
|
93
|
+
"latest", "--version", "-v", help="Version tag for the target push."
|
|
94
|
+
),
|
|
95
|
+
profile: Optional[str] = typer.Option(
|
|
96
|
+
None, "--profile", "-p", help="Target configuration profile name."
|
|
97
|
+
),
|
|
98
|
+
):
|
|
99
|
+
"""Compile, delta-push, provision hardware and run an arbitrary hybrid workflow script instantly."""
|
|
100
|
+
typer.echo(f"Initiating compilation run for: {entrypoint}")
|
|
101
|
+
run_obj = core.create_workflow_run(
|
|
102
|
+
entrypoint=entrypoint,
|
|
103
|
+
workflow_name=name,
|
|
104
|
+
workflow_version=version,
|
|
105
|
+
profile_name=profile,
|
|
106
|
+
local_only=local,
|
|
107
|
+
input_artifact_hash=input_artifact_hash,
|
|
108
|
+
)
|
|
109
|
+
typer.echo(f"Workflow run ID: {run_obj.id}")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@cli.command("fix")
|
|
113
|
+
def fix(
|
|
114
|
+
entrypoint: str = typer.Argument(
|
|
115
|
+
..., help="Path to the Python script containing tasks."
|
|
116
|
+
),
|
|
117
|
+
):
|
|
118
|
+
run_obj = core.fix_workflow_run(entrypoint=entrypoint)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@cli.command(
|
|
122
|
+
"login", context_settings={"allow_extra_args": True, "ignore_unknown_options": True}
|
|
123
|
+
)
|
|
124
|
+
def login(
|
|
125
|
+
ctx: typer.Context, # Typer Context is mandatory to capture extra unkown arguments
|
|
126
|
+
provider_name: str = typer.Argument(
|
|
127
|
+
..., help="Name of the cloud provider (e.g., scaleway)."
|
|
128
|
+
),
|
|
129
|
+
registry_domain: str = typer.Argument(
|
|
130
|
+
..., help="Domain URI of the content-addressed registry server."
|
|
131
|
+
),
|
|
132
|
+
):
|
|
133
|
+
creds = parse_extra_args(ctx.args)
|
|
134
|
+
|
|
135
|
+
if not creds:
|
|
136
|
+
typer.echo(
|
|
137
|
+
"Warning: No dynamic credentials flags passed (e.g. --api-key=XYZ).",
|
|
138
|
+
err=True,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
core.login_provider(provider_name=provider_name, credentials=creds)
|
|
142
|
+
core.login_registry(registry_domain=registry_domain, credentials=creds)
|
|
143
|
+
typer.echo("Successfully authenticated against both endpoints.")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@cli.command("logout")
|
|
147
|
+
def logout():
|
|
148
|
+
old_provider = core.logout_provider()
|
|
149
|
+
|
|
150
|
+
if old_provider:
|
|
151
|
+
typer.echo(f"Dropped provider credentials successfully from {old_provider}")
|
|
152
|
+
|
|
153
|
+
old_registry = core.logout_registry()
|
|
154
|
+
|
|
155
|
+
if old_registry:
|
|
156
|
+
typer.echo(f"Dropped provider credentials successfully from {old_registry}")
|
|
157
|
+
|
|
158
|
+
if not old_provider and not old_registry:
|
|
159
|
+
typer.echo(f"Already logout")
|
qall/cli/provider.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
import qorus.core as core
|
|
17
|
+
|
|
18
|
+
from .utils import parse_extra_args
|
|
19
|
+
|
|
20
|
+
provider_cli = typer.Typer(
|
|
21
|
+
name="provider",
|
|
22
|
+
help="Configure compute provider environments.",
|
|
23
|
+
no_args_is_help=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@provider_cli.command(
|
|
28
|
+
"login", context_settings={"allow_extra_args": True, "ignore_unknown_options": True}
|
|
29
|
+
)
|
|
30
|
+
def provider_login(
|
|
31
|
+
ctx: typer.Context,
|
|
32
|
+
provider_name: str = typer.Argument(
|
|
33
|
+
..., help="Compute provider identity token identifier (e.g., scaleway)."
|
|
34
|
+
),
|
|
35
|
+
):
|
|
36
|
+
"""Authenticate the local instance against a provider control plane."""
|
|
37
|
+
creds = parse_extra_args(ctx.args)
|
|
38
|
+
|
|
39
|
+
if not creds:
|
|
40
|
+
typer.echo(
|
|
41
|
+
"Warning: No dynamic credentials flags passed (e.g. --api-key=XYZ).",
|
|
42
|
+
err=True,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
core.login_provider(
|
|
46
|
+
provider_name=provider_name,
|
|
47
|
+
credentials=creds,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
typer.echo(f"Provider session initialized for: {provider_name}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@provider_cli.command("logout")
|
|
54
|
+
def provider_logout():
|
|
55
|
+
old_provider = core.logout_provider()
|
|
56
|
+
|
|
57
|
+
if old_provider:
|
|
58
|
+
typer.echo(f"Dropped provider credentials successfully from {old_provider}")
|
|
59
|
+
else:
|
|
60
|
+
typer.echo(f"Already logout")
|
qall/cli/registry.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import typer
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
import qorus.core as core
|
|
18
|
+
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from qorus.specification import WorkflowSpec, WorkerSpec
|
|
22
|
+
|
|
23
|
+
from .utils import parse_extra_args
|
|
24
|
+
|
|
25
|
+
registry_cli = typer.Typer(
|
|
26
|
+
name="registry",
|
|
27
|
+
help="Interact with containerless content-addressed task registries.",
|
|
28
|
+
no_args_is_help=True,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@registry_cli.command("push")
|
|
33
|
+
def registry_push(
|
|
34
|
+
file: Path = typer.Argument(
|
|
35
|
+
..., help="Path to the Python entrypoint script file containing your tasks."
|
|
36
|
+
),
|
|
37
|
+
local: bool = typer.Option(
|
|
38
|
+
False,
|
|
39
|
+
"--local",
|
|
40
|
+
help="Wether to store in a local block registry or a remote one.",
|
|
41
|
+
),
|
|
42
|
+
worker: bool = typer.Option(
|
|
43
|
+
False,
|
|
44
|
+
"--worker",
|
|
45
|
+
help="Whether to push a worker spec instead of a workflow spec.",
|
|
46
|
+
),
|
|
47
|
+
name: bool = typer.Option(
|
|
48
|
+
None,
|
|
49
|
+
"--name",
|
|
50
|
+
"--n",
|
|
51
|
+
help="Name of the registry entry.",
|
|
52
|
+
),
|
|
53
|
+
version: bool = typer.Option(
|
|
54
|
+
None,
|
|
55
|
+
"--version",
|
|
56
|
+
"--v",
|
|
57
|
+
help="Version of the registry entry.",
|
|
58
|
+
),
|
|
59
|
+
):
|
|
60
|
+
"""Statically compile code statements, analyze the DAG, and delta-push missing blocks to the registry."""
|
|
61
|
+
|
|
62
|
+
if not os.path.exists(file):
|
|
63
|
+
typer.echo(f"Cannot found file {file}")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
typer.echo(f"Compiling local Merkle DAG elements from script: {file}")
|
|
67
|
+
|
|
68
|
+
name = name or file.stem
|
|
69
|
+
version = version or "latest"
|
|
70
|
+
|
|
71
|
+
if worker:
|
|
72
|
+
worker_spec = WorkerSpec(file)
|
|
73
|
+
tag, dag = core.push_worker_on_registry(
|
|
74
|
+
worker_spec=worker_spec,
|
|
75
|
+
worker_name=name,
|
|
76
|
+
worker_version=version,
|
|
77
|
+
local_only=local,
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
workflow_spec = WorkflowSpec(file)
|
|
81
|
+
tag, dag = core.push_workflow_on_registry(
|
|
82
|
+
workflow_spec=workflow_spec,
|
|
83
|
+
workflow_name=name,
|
|
84
|
+
workflow_version=version,
|
|
85
|
+
local_only=local,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
typer.echo(f"Registry synchronization push complete: {tag}, {dag}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@registry_cli.command("pull")
|
|
92
|
+
def registry_pull(
|
|
93
|
+
uri: str = typer.Argument(
|
|
94
|
+
..., help="The canonical target URI formatted as 'name:version' to download."
|
|
95
|
+
)
|
|
96
|
+
):
|
|
97
|
+
"""Download the full underlying Merkle DAG block graph from a remote registry into the local cache."""
|
|
98
|
+
if ":" not in uri:
|
|
99
|
+
name, version = uri, "latest"
|
|
100
|
+
else:
|
|
101
|
+
name, version = uri.split(":", 1)
|
|
102
|
+
core.pull_from_registry(workflow_name=name, workflow_version=version)
|
|
103
|
+
typer.echo(f"Blocks downloaded and cached locally for URI: {name}:{version}")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@registry_cli.command(
|
|
107
|
+
"login", context_settings={"allow_extra_args": True, "ignore_unknown_options": True}
|
|
108
|
+
)
|
|
109
|
+
def registry_login(
|
|
110
|
+
ctx: typer.Context,
|
|
111
|
+
registry_domain: str = typer.Argument(
|
|
112
|
+
..., help="Target registry hostname URL endpoint."
|
|
113
|
+
),
|
|
114
|
+
):
|
|
115
|
+
"""Authenticate and store login tokens for a specific content-addressed task registry server."""
|
|
116
|
+
creds = parse_extra_args(ctx.args)
|
|
117
|
+
|
|
118
|
+
if not creds:
|
|
119
|
+
typer.echo(
|
|
120
|
+
"Warning: No dynamic credentials flags passed (e.g. --api-key=XYZ).",
|
|
121
|
+
err=True,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
core.login_registry(
|
|
125
|
+
registry_domain=registry_domain,
|
|
126
|
+
credentials=creds,
|
|
127
|
+
)
|
|
128
|
+
typer.echo(
|
|
129
|
+
f"Registry token persistent mapping written for domain: {registry_domain}"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@registry_cli.command("logout")
|
|
134
|
+
def registry_logout():
|
|
135
|
+
old_registry = core.logout_registry()
|
|
136
|
+
|
|
137
|
+
if old_registry:
|
|
138
|
+
typer.echo(f"Dropped registry credentials successfully from {old_registry}")
|
|
139
|
+
else:
|
|
140
|
+
typer.echo(f"Already logout")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@registry_cli.command("clear")
|
|
144
|
+
def registry_clear():
|
|
145
|
+
core.clear_registry()
|
qall/cli/resource.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
import qorus.core as core
|
|
17
|
+
|
|
18
|
+
resource_cli = typer.Typer(
|
|
19
|
+
name="resource",
|
|
20
|
+
help="Inspect hardware topologies and execution modalities.",
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@resource_cli.command("ls")
|
|
26
|
+
def resource_list():
|
|
27
|
+
"""Poll the compute provider to list all accessible CPU, GPU, and physical/emulated QPU hardware."""
|
|
28
|
+
typer.echo("Polling cloud control plane for active available hardware...")
|
qall/cli/task.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import os
|
|
15
|
+
import typer
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
from qorus.core.runtime import execute_task, ExecutionMode
|
|
21
|
+
|
|
22
|
+
task_cli = typer.Typer(
|
|
23
|
+
name="task",
|
|
24
|
+
help="Manage task execution engine",
|
|
25
|
+
no_args_is_help=True,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@task_cli.command("run")
|
|
30
|
+
def run(
|
|
31
|
+
task_hash: str,
|
|
32
|
+
task_run_id: str,
|
|
33
|
+
artifact_hash: str = typer.Option(
|
|
34
|
+
None,
|
|
35
|
+
"--artifact-hash",
|
|
36
|
+
"-a",
|
|
37
|
+
help="Hash of the artifact to use as input for the task",
|
|
38
|
+
),
|
|
39
|
+
block_registry: str = typer.Option(
|
|
40
|
+
None,
|
|
41
|
+
"--block-registry",
|
|
42
|
+
"-l",
|
|
43
|
+
help="Local registry directory",
|
|
44
|
+
),
|
|
45
|
+
daemon_address: str = typer.Option(
|
|
46
|
+
None,
|
|
47
|
+
"--daemon-address",
|
|
48
|
+
"-d",
|
|
49
|
+
help="Daemon gRPC address (default: localhost:50053)",
|
|
50
|
+
),
|
|
51
|
+
mode: Optional[str] = typer.Option(
|
|
52
|
+
"daemon",
|
|
53
|
+
"--mode",
|
|
54
|
+
"-m",
|
|
55
|
+
help="Execution mode: 'local' for daemonless, 'daemon' for daemon-based",
|
|
56
|
+
),
|
|
57
|
+
worker_address: Optional[list[str]] = typer.Option(
|
|
58
|
+
None,
|
|
59
|
+
"--worker-address",
|
|
60
|
+
"-w",
|
|
61
|
+
help="Worker gRPC address",
|
|
62
|
+
),
|
|
63
|
+
):
|
|
64
|
+
"""
|
|
65
|
+
Execute a task. Made for intra-container execution.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
execution_mode = ExecutionMode(mode.lower())
|
|
70
|
+
except ValueError:
|
|
71
|
+
typer.echo(
|
|
72
|
+
f"Error: Invalid execution mode '{mode}'. Must be 'local' or 'daemon'.",
|
|
73
|
+
err=True,
|
|
74
|
+
)
|
|
75
|
+
raise typer.Exit(code=1)
|
|
76
|
+
|
|
77
|
+
typer.echo(f"[Runtime] Executing in {execution_mode} mode")
|
|
78
|
+
|
|
79
|
+
if not daemon_address:
|
|
80
|
+
daemon_address = os.environ.get("QORUS_DAEMON_ADDRESS", "127.0.0.1:50053")
|
|
81
|
+
|
|
82
|
+
task_output = execute_task(
|
|
83
|
+
task_hash=task_hash,
|
|
84
|
+
task_run_id=task_run_id,
|
|
85
|
+
daemon_address=daemon_address,
|
|
86
|
+
block_registry=block_registry,
|
|
87
|
+
artifact_hash=artifact_hash,
|
|
88
|
+
mode=execution_mode,
|
|
89
|
+
worker_addresses=worker_address,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
typer.echo(f"[Runtime] Task done!")
|
|
93
|
+
|
|
94
|
+
if task_output.return_code == 0:
|
|
95
|
+
log = f" Task {task_hash} successful! " + (
|
|
96
|
+
"Output artifact hash: " + task_output.output_artifact_hash
|
|
97
|
+
if task_output.output_artifact_hash
|
|
98
|
+
else "No output (no artifact created)."
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
log = f" Task {task_hash} failed with code {task_output.return_code}."
|
|
102
|
+
|
|
103
|
+
typer.echo("[Runtime]" + log)
|
|
104
|
+
|
|
105
|
+
typer.echo("[Runtime]Task logs:")
|
|
106
|
+
typer.echo("[Runtime]" + "-" * 20)
|
|
107
|
+
typer.echo("[Runtime] stdout:")
|
|
108
|
+
typer.echo(task_output.stdout.replace("\n", "\n[Runtime]\t"))
|
|
109
|
+
typer.echo("[Runtime]" + "-" * 20)
|
|
110
|
+
typer.echo("[Runtime] stderr:")
|
|
111
|
+
typer.echo("[Runtime] " + task_output.stderr.replace("\n", "\n[Runtime]\t"))
|
|
112
|
+
|
|
113
|
+
raise typer.Exit(code=task_output.return_code)
|
qall/cli/utils.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from typing import List
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def parse_extra_args(args: List[str]) -> dict:
|
|
18
|
+
"""
|
|
19
|
+
Parses unkown arbitrary extra CLI options flags into a clean Python dictionary.
|
|
20
|
+
Handles both '--key=value' and '--key value' parameter syntaxes.
|
|
21
|
+
"""
|
|
22
|
+
extra_dict = {}
|
|
23
|
+
i = 0
|
|
24
|
+
while i < len(args):
|
|
25
|
+
arg = args[i]
|
|
26
|
+
if arg.startswith("--"):
|
|
27
|
+
# Case A: Syntax option is '--key=value'
|
|
28
|
+
if "=" in arg:
|
|
29
|
+
key, val = arg.split("=", 1)
|
|
30
|
+
extra_dict[key.lstrip("-")] = val
|
|
31
|
+
i += 1
|
|
32
|
+
# Case B: Syntax option is '--key value'
|
|
33
|
+
else:
|
|
34
|
+
key = arg.lstrip("-")
|
|
35
|
+
if i + 1 < len(args) and not args[i + 1].startswith("-"):
|
|
36
|
+
extra_dict[key] = args[i + 1]
|
|
37
|
+
i += 2
|
|
38
|
+
else:
|
|
39
|
+
# Fallback flag default value if standalone
|
|
40
|
+
extra_dict[key] = "True"
|
|
41
|
+
i += 1
|
|
42
|
+
else:
|
|
43
|
+
# Skip invalid positions or unformatted extra strings
|
|
44
|
+
i += 1
|
|
45
|
+
return extra_dict
|
qall/cli/worker.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
from qorus.core.runtime import execute_worker
|
|
19
|
+
|
|
20
|
+
worker_cli = typer.Typer(
|
|
21
|
+
name="worker",
|
|
22
|
+
help="Manage worker execution engine",
|
|
23
|
+
no_args_is_help=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@worker_cli.command("run")
|
|
28
|
+
def run(
|
|
29
|
+
worker_hash: str,
|
|
30
|
+
port: int = typer.Option(None, "--port", "-p", help="gRPC port for the worker"),
|
|
31
|
+
block_registry: str = typer.Option(
|
|
32
|
+
None,
|
|
33
|
+
"--block-registry",
|
|
34
|
+
"-l",
|
|
35
|
+
help="Local registry directory",
|
|
36
|
+
),
|
|
37
|
+
):
|
|
38
|
+
execute_worker(
|
|
39
|
+
worker_hash=worker_hash,
|
|
40
|
+
port=port,
|
|
41
|
+
block_registry=block_registry,
|
|
42
|
+
)
|
qall/cli/workflow.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
import qorus.core as core
|
|
17
|
+
|
|
18
|
+
workflow_cli = typer.Typer(
|
|
19
|
+
name="workflow",
|
|
20
|
+
help="Manage and control lifecycle workflows.",
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@workflow_cli.command("create")
|
|
26
|
+
def workflow_create(
|
|
27
|
+
uri: str = typer.Argument(
|
|
28
|
+
..., help="Target canonical URI string formatted as 'name:version'."
|
|
29
|
+
),
|
|
30
|
+
profile: str = typer.Argument(
|
|
31
|
+
..., help="The resource profile name to apply to this orchestration model."
|
|
32
|
+
),
|
|
33
|
+
):
|
|
34
|
+
"""Register or update a formal workflow definition inside the cloud provider control plane infrastructure."""
|
|
35
|
+
if ":" not in uri:
|
|
36
|
+
typer.echo(
|
|
37
|
+
"Error: URI must match the 'name:version' format constraint.", err=True
|
|
38
|
+
)
|
|
39
|
+
raise typer.Exit(code=1)
|
|
40
|
+
name, version = uri.split(":", 1)
|
|
41
|
+
|
|
42
|
+
wf = core.create_workflow(
|
|
43
|
+
workflow_name=name, workflow_version=version, profile=profile
|
|
44
|
+
)
|
|
45
|
+
typer.echo(
|
|
46
|
+
f"Workflow reference compiled on provider ecosystem. Endpoint: {wf.endpoint}"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@workflow_cli.command("info")
|
|
51
|
+
def workflow_info(
|
|
52
|
+
workflow_id: str = typer.Argument(
|
|
53
|
+
..., help="The unique tracking identifier of the target workflow."
|
|
54
|
+
)
|
|
55
|
+
):
|
|
56
|
+
"""Retrieve deep structure info and infrastructure metrics for a registered workflow asset."""
|
|
57
|
+
# Assuming core tracking retrieval logic or printing simple data structures
|
|
58
|
+
typer.echo(
|
|
59
|
+
f"Fetching structural information boundaries for workflow ID: {workflow_id}"
|
|
60
|
+
)
|
|
61
|
+
raise NotImplementedError()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@workflow_cli.command("ls")
|
|
65
|
+
def workflow_list():
|
|
66
|
+
"""List all available workflows deployed across the active provider endpoint layout."""
|
|
67
|
+
typer.echo("Listing available architecture workflows...")
|
|
68
|
+
raise NotImplementedError()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@workflow_cli.command("run")
|
|
72
|
+
def workflow_run(
|
|
73
|
+
workflow_id: str = typer.Argument(
|
|
74
|
+
..., help="The structural tracking ID of the pre-compiled workflow."
|
|
75
|
+
)
|
|
76
|
+
):
|
|
77
|
+
"""Execute a pre-compiled workflow asset that has already been registered on the cloud platform."""
|
|
78
|
+
run_obj = core.run_workflow(workflow_id=workflow_id)
|
|
79
|
+
typer.echo(f"Workflow run triggered execution sequence. Run ID: {run_obj.id}")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
qiskit-scaleway-worker:
|
|
2
|
+
provider: scaleway
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
hash: xxxxxxxxxxxxxxxxx
|
|
5
|
+
input_formats:
|
|
6
|
+
- qiskit.QuantumCircuit
|
|
7
|
+
perceval-scaleway-worker:
|
|
8
|
+
provider: scaleway
|
|
9
|
+
version: 0.1.0
|
|
10
|
+
hash: xxxxxxxxxxxxxxxxx
|
|
11
|
+
input_formats:
|
|
12
|
+
- perceval.Circuit
|
|
13
|
+
cirq-scaleway-worker:
|
|
14
|
+
provider: scaleway
|
|
15
|
+
version: 0.1.0
|
|
16
|
+
hash: xxxxxxxxxxxxxxxxx
|
|
17
|
+
input_formats:
|
|
18
|
+
- cirq.Circuit
|
|
19
|
+
cirq_local_qc_worker:
|
|
20
|
+
provider: local
|
|
21
|
+
version: latest
|
|
22
|
+
hash: zdpuAu5wZRLMPj8SMBQG5h85zmG6ypYKN8KD7Gg8UThTSCwwC
|
|
23
|
+
input_formats:
|
|
24
|
+
- cirq.Circuit
|
|
25
|
+
qiskit_local_qc_worker:
|
|
26
|
+
provider: local
|
|
27
|
+
version: latest
|
|
28
|
+
hash: zdpuAkXnJzuDVo3DfVxPZ7GnHpbkZXpn9YP7GTFFrqGrzhDYz
|
|
29
|
+
input_formats:
|
|
30
|
+
- qiskit.QuantumCircuit
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright 2026 Scaleway
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
qiskit-scaleway-worker:
|
|
2
|
+
provider: scaleway
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
hash: xxxxxxxxxxxxxxxxx
|
|
5
|
+
input_formats:
|
|
6
|
+
- qiskit.QuantumCircuit
|
|
7
|
+
perceval-scaleway-worker:
|
|
8
|
+
provider: scaleway
|
|
9
|
+
version: 0.1.0
|
|
10
|
+
hash: xxxxxxxxxxxxxxxxx
|
|
11
|
+
input_formats:
|
|
12
|
+
- perceval.Circuit
|
|
13
|
+
cirq-scaleway-worker:
|
|
14
|
+
provider: scaleway
|
|
15
|
+
version: 0.1.0
|
|
16
|
+
hash: xxxxxxxxxxxxxxxxx
|
|
17
|
+
input_formats:
|
|
18
|
+
- cirq.Circuit
|
|
19
|
+
cirq_local_qc_worker:
|
|
20
|
+
provider: local
|
|
21
|
+
version: latest
|
|
22
|
+
hash: zdpuAu5wZRLMPj8SMBQG5h85zmG6ypYKN8KD7Gg8UThTSCwwC
|
|
23
|
+
input_formats:
|
|
24
|
+
- cirq.Circuit
|
|
25
|
+
qiskit_local_qc_worker:
|
|
26
|
+
provider: local
|
|
27
|
+
version: latest
|
|
28
|
+
hash: zdpuAkXnJzuDVo3DfVxPZ7GnHpbkZXpn9YP7GTFFrqGrzhDYz
|
|
29
|
+
input_formats:
|
|
30
|
+
- qiskit.QuantumCircuit
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: qall
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A python tool for modern hybrid and quantum workflows.
|
|
5
|
+
Requires-Dist: cloudpickle==3.1.2
|
|
6
|
+
Requires-Dist: dataclasses-json>=0.6.7
|
|
7
|
+
Requires-Dist: docker>=7.1.0
|
|
8
|
+
Requires-Dist: grpcio>=1.81.1
|
|
9
|
+
Requires-Dist: keyring>=25.7.0
|
|
10
|
+
Requires-Dist: matplotlib>=3.11.0
|
|
11
|
+
Requires-Dist: pyan3>=2.5.0
|
|
12
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
13
|
+
Requires-Dist: qio>=0.2.2
|
|
14
|
+
Requires-Dist: rustworkx==0.17.1
|
|
15
|
+
Requires-Dist: typer>=0.26.7
|
|
16
|
+
Requires-Dist: lmdb>=2.3.0
|
|
17
|
+
Requires-Dist: retry==0.9.2
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Hybrid computing as a Service
|
|
22
|
+
|
|
23
|
+
## Protobuf
|
|
24
|
+
|
|
25
|
+
To re-generate registry client/server API:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd protobuf/
|
|
29
|
+
|
|
30
|
+
make registry
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
To re-generate daemon client/server API:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd protobuf/
|
|
37
|
+
|
|
38
|
+
make daemon
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Registry server
|
|
42
|
+
|
|
43
|
+
To test registry server:
|
|
44
|
+
```bash
|
|
45
|
+
cd qall-registry-server/
|
|
46
|
+
|
|
47
|
+
make test
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
To build registry server:
|
|
51
|
+
```bash
|
|
52
|
+
cd qall-registry-server/
|
|
53
|
+
|
|
54
|
+
make build
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
To start local instructure
|
|
58
|
+
```bash
|
|
59
|
+
cd infrastructure/local/
|
|
60
|
+
|
|
61
|
+
make start
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Registry client
|
|
65
|
+
|
|
66
|
+
To install registry client:
|
|
67
|
+
```bash
|
|
68
|
+
cd qall-registry-client/
|
|
69
|
+
|
|
70
|
+
pip install .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
To test registry client:
|
|
74
|
+
```bash
|
|
75
|
+
cd qall-registry-client/
|
|
76
|
+
|
|
77
|
+
export qall_REGISTRY_URL=localhost:50051; pytest -vvv tests/
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Daemon server
|
|
81
|
+
|
|
82
|
+
To run daemon server:
|
|
83
|
+
```bash
|
|
84
|
+
make build
|
|
85
|
+
|
|
86
|
+
qall daemon start
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Daemon client
|
|
90
|
+
|
|
91
|
+
To install daemon client:
|
|
92
|
+
```bash
|
|
93
|
+
cd qall-daemon-client/
|
|
94
|
+
|
|
95
|
+
pip install .
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Scaleway HCaaS client
|
|
99
|
+
TODO
|
|
100
|
+
|
|
101
|
+
## qall (SDK)
|
|
102
|
+
|
|
103
|
+
To install qall sdk:
|
|
104
|
+
```bash
|
|
105
|
+
cd qall/
|
|
106
|
+
|
|
107
|
+
pip install .
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
To push a project to the registry:
|
|
111
|
+
```bash
|
|
112
|
+
cd qall/
|
|
113
|
+
|
|
114
|
+
pip install .
|
|
115
|
+
|
|
116
|
+
qall registry push examples/basic_hierarchy/my_worflow.py
|
|
117
|
+
|
|
118
|
+
qall daemon start
|
|
119
|
+
|
|
120
|
+
qall daemon run <root hash>
|
|
121
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
qall/__init__.py,sha256=4zQjELaG-rnReGhj5sZOPw41tIbval0icnDYw54qrs0,1149
|
|
2
|
+
qall/cli/__init__.py,sha256=qY1dcrxE8XuqOxGK8YcTJ0vYOLPv_Bptz53NiqMYQtI,573
|
|
3
|
+
qall/cli/artifact.py,sha256=7dFdeDwPm4AVVXI5p-CeNAgpMlxVeu2tFLBiSSeuUOU,1647
|
|
4
|
+
qall/cli/checkpoint.py,sha256=iLyv9i6DyZmiMES7yo6tcMhyrCLnM-n3xo9EjbpyTJM,753
|
|
5
|
+
qall/cli/config.py,sha256=QhOvMW1MyhvKlujhN_JJtpScFih86I3auM98WqJhHNQ,1487
|
|
6
|
+
qall/cli/daemon.py,sha256=fWXVkzDQadLISz52qNa4HLRG3GW9Ln6QYV5ZAOzwjEM,5828
|
|
7
|
+
qall/cli/log.py,sha256=vyXT2Gx9j1h7DLl3MwR-84GFrjYUtAMKTh14bcO7Qr0,736
|
|
8
|
+
qall/cli/main.py,sha256=dUCXAAZEKjzZ1fFvAC_EFc8SZl3Rvkp8CHQ49sFj93A,4743
|
|
9
|
+
qall/cli/provider.py,sha256=5k6E4sv_Dz0iR4OzI6496td1iq42ZWhRsVlybaaFxRE,1761
|
|
10
|
+
qall/cli/registry.py,sha256=kFXTzfBYTh78b8SeCPNUC85wB4xisJvj5o8fJSuEtC0,4220
|
|
11
|
+
qall/cli/resource.py,sha256=3QcLpKD2qC9D0W76uvBuMC89J2ePHxO8bwb4IqTJrxE,992
|
|
12
|
+
qall/cli/task.py,sha256=pTi3jFzzfjnkOZau1CSgosJYV1Mq-4Ib6e7TAvNVAeM,3310
|
|
13
|
+
qall/cli/utils.py,sha256=f76y3MYeL0TycuTPXTIuz-z8_rZRgePoiejM4QUiWFY,1662
|
|
14
|
+
qall/cli/worker.py,sha256=rguYE8IJw5fJbBfsvlAgqiFSO-wC9ibT3nZ0Qs5-vvY,1188
|
|
15
|
+
qall/cli/workflow.py,sha256=uSWnaD34eEjSUOe3hC0tdXjPk4wwqiqXqz7cLrl9qIw,2727
|
|
16
|
+
qall/data/worker_catalog.yml,sha256=PAuCpQBBFEGzDb1_ESgAzpCyETFfWPFr05bHUBVhues,690
|
|
17
|
+
qall/visualization/__init__.py,sha256=qY1dcrxE8XuqOxGK8YcTJ0vYOLPv_Bptz53NiqMYQtI,573
|
|
18
|
+
qall-0.1.0.data/purelib/worker_catalog.yml,sha256=PAuCpQBBFEGzDb1_ESgAzpCyETFfWPFr05bHUBVhues,690
|
|
19
|
+
qall-0.1.0.dist-info/WHEEL,sha256=a0ta4TK7UKMB0EbDesmi2mn0Djkly7aj_7Tu2R7SseE,81
|
|
20
|
+
qall-0.1.0.dist-info/entry_points.txt,sha256=BqU8hoxdqEfdJJ9hyX20-DUFBHAFa2IEVqkq0Fjk2MM,44
|
|
21
|
+
qall-0.1.0.dist-info/METADATA,sha256=4kZMV7O1iP0OYh1WWpvYu8Fw6iL7HZHH4ZZekD-OyTQ,1718
|
|
22
|
+
qall-0.1.0.dist-info/RECORD,,
|