q8s 0.8.0__tar.gz → 0.8.0.dev2__tar.gz
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.
- {q8s-0.8.0 → q8s-0.8.0.dev2}/PKG-INFO +1 -1
- {q8s-0.8.0 → q8s-0.8.0.dev2}/pyproject.toml +1 -1
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/cli.py +0 -16
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/plugins/cpu_job.py +2 -3
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/plugins/cuda_job.py +2 -3
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/project.py +6 -46
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/workload.py +15 -15
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s.egg-info/PKG-INFO +1 -1
- {q8s-0.8.0 → q8s-0.8.0.dev2}/LICENSE +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/README.md +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/__init__.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/__main__.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/constants.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/deps/__init__.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/deps/code_analyzer.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/deps/parser.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/enums.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/execution.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/install.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/kernel.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/matplotlib/backend.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/multifiles.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/plugins/__init__.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/plugins/job_template_spec.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s/utils.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s.egg-info/SOURCES.txt +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s.egg-info/dependency_links.txt +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s.egg-info/entry_points.txt +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s.egg-info/requires.txt +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/q8s.egg-info/top_level.txt +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/setup.cfg +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/tests/__init__.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/tests/fixtures/cache/app.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/tests/fixtures/simple/qiskit.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/tests/test_backend.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/tests/test_deps.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/tests/test_job_template_spec.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/tests/test_kernel.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/tests/test_multifiles.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/tests/test_project.py +0 -0
- {q8s-0.8.0 → q8s-0.8.0.dev2}/tests/test_utils.py +0 -0
|
@@ -16,22 +16,6 @@ from q8s.workload import Workload
|
|
|
16
16
|
app = typer.Typer()
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
@app.command()
|
|
20
|
-
def init(
|
|
21
|
-
images: Annotated[
|
|
22
|
-
bool, typer.Option(help="Initialize images cache if build in a CI pipeline")
|
|
23
|
-
] = False,
|
|
24
|
-
):
|
|
25
|
-
project = Project()
|
|
26
|
-
project.init_cache()
|
|
27
|
-
|
|
28
|
-
if images:
|
|
29
|
-
project.images_from_ci()
|
|
30
|
-
project.update_images_cache()
|
|
31
|
-
|
|
32
|
-
print(f"Project {project.name} initialized")
|
|
33
|
-
|
|
34
|
-
|
|
35
19
|
@app.command()
|
|
36
20
|
def build(
|
|
37
21
|
init: Annotated[bool, typer.Option(help="Initialize project")] = False,
|
|
@@ -25,14 +25,13 @@ class CPUJobTemplatePlugin:
|
|
|
25
25
|
|
|
26
26
|
volume_name = f"app-volume-{name}"
|
|
27
27
|
|
|
28
|
-
env_var = list(env)
|
|
29
28
|
if workload.is_src_project:
|
|
30
|
-
|
|
29
|
+
env.append(client.V1EnvVar(name="PYTHONPATH", value=f"{WORKSPACE}/src"))
|
|
31
30
|
|
|
32
31
|
container = client.V1Container(
|
|
33
32
|
name="quantum-routine",
|
|
34
33
|
image=container_image,
|
|
35
|
-
env=
|
|
34
|
+
env=env,
|
|
36
35
|
command=["python"],
|
|
37
36
|
args=(
|
|
38
37
|
["-m", workload.entry_module] + workload.args
|
|
@@ -31,14 +31,13 @@ class CUDAJobTemplatePlugin:
|
|
|
31
31
|
|
|
32
32
|
volume_name = f"app-volume-{name}"
|
|
33
33
|
|
|
34
|
-
env_var = list(env)
|
|
35
34
|
if workload.is_src_project:
|
|
36
|
-
|
|
35
|
+
env.append(client.V1EnvVar(name="PYTHONPATH", value=f"{WORKSPACE}/src"))
|
|
37
36
|
|
|
38
37
|
container = client.V1Container(
|
|
39
38
|
name="quantum-routine",
|
|
40
39
|
image=container_image,
|
|
41
|
-
env=
|
|
40
|
+
env=env,
|
|
42
41
|
command=["python"],
|
|
43
42
|
args=(
|
|
44
43
|
["-m", workload.entry_module] + workload.args
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import configparser
|
|
2
1
|
from dataclasses import dataclass
|
|
3
2
|
from datetime import datetime
|
|
4
3
|
from pathlib import Path
|
|
@@ -44,7 +43,7 @@ def _read_stream_lines(stream, progress, silent, is_error: bool = False):
|
|
|
44
43
|
"""
|
|
45
44
|
while True:
|
|
46
45
|
line = stream.readline()
|
|
47
|
-
if line ==
|
|
46
|
+
if line == '':
|
|
48
47
|
break
|
|
49
48
|
if not silent:
|
|
50
49
|
if is_error:
|
|
@@ -97,7 +96,6 @@ class Q8SDocker:
|
|
|
97
96
|
username: str
|
|
98
97
|
registry: Optional[str]
|
|
99
98
|
|
|
100
|
-
|
|
101
99
|
@dataclass
|
|
102
100
|
class Q8SProject:
|
|
103
101
|
name: str
|
|
@@ -114,11 +112,9 @@ class CacheNotBuiltException(Exception):
|
|
|
114
112
|
class ProjectNotFoundException(Exception):
|
|
115
113
|
pass
|
|
116
114
|
|
|
117
|
-
|
|
118
115
|
class ProjectInvalidConfigurationException(Exception):
|
|
119
116
|
pass
|
|
120
117
|
|
|
121
|
-
|
|
122
118
|
class Project:
|
|
123
119
|
name: str
|
|
124
120
|
__path: str
|
|
@@ -219,15 +215,14 @@ class Project:
|
|
|
219
215
|
stderr=STDOUT,
|
|
220
216
|
bufsize=1,
|
|
221
217
|
universal_newlines=True,
|
|
222
|
-
encoding="utf-8",
|
|
223
|
-
errors="replace",
|
|
218
|
+
encoding="utf-8", # force UTF-8 decoding
|
|
219
|
+
errors="replace", # avoid crashing on bad bytes
|
|
224
220
|
)
|
|
225
221
|
|
|
226
222
|
if sys.platform == "win32":
|
|
227
223
|
# Windows-safe: read line by line instead of using selectors
|
|
228
224
|
_handle_subprocess_output(build_process, progress, silent)
|
|
229
225
|
else:
|
|
230
|
-
|
|
231
226
|
def handle_output(stream, mask):
|
|
232
227
|
# Because the process' output is line buffered, there's only ever one
|
|
233
228
|
# line to read when this function is called
|
|
@@ -275,8 +270,8 @@ class Project:
|
|
|
275
270
|
stderr=STDOUT,
|
|
276
271
|
bufsize=1,
|
|
277
272
|
universal_newlines=True,
|
|
278
|
-
encoding="utf-8",
|
|
279
|
-
errors="replace",
|
|
273
|
+
encoding="utf-8", # force UTF-8 decoding
|
|
274
|
+
errors="replace", # avoid crashing on bad bytes
|
|
280
275
|
)
|
|
281
276
|
|
|
282
277
|
if sys.platform == "win32":
|
|
@@ -284,7 +279,6 @@ class Project:
|
|
|
284
279
|
_handle_subprocess_output(push_process, progress, silent)
|
|
285
280
|
|
|
286
281
|
else:
|
|
287
|
-
|
|
288
282
|
def handle_output(stream, mask):
|
|
289
283
|
# Because the process' output is line buffered, there's only ever one
|
|
290
284
|
# line to read when this function is called
|
|
@@ -312,19 +306,11 @@ class Project:
|
|
|
312
306
|
else:
|
|
313
307
|
progress.advance(task)
|
|
314
308
|
|
|
315
|
-
def images_from_ci(self):
|
|
316
|
-
"""
|
|
317
|
-
Images are build in a CI pipeline.
|
|
318
|
-
"""
|
|
319
|
-
for target in self.configuration.targets.keys():
|
|
320
|
-
self.__images[target] = self.__image_name(target)
|
|
321
|
-
|
|
322
309
|
def update_images_cache(self):
|
|
323
310
|
"""
|
|
324
311
|
Update the images cache
|
|
325
312
|
"""
|
|
326
313
|
with open(join(self.__path, ".q8s_cache", "images"), "w") as f:
|
|
327
|
-
print(f"# This file is autogenerated by q8sctl\n", file=f)
|
|
328
314
|
yaml.dump(self.__images, f)
|
|
329
315
|
|
|
330
316
|
def clear_cache(self):
|
|
@@ -339,7 +325,7 @@ class Project:
|
|
|
339
325
|
|
|
340
326
|
def __image_name(self, target: str):
|
|
341
327
|
registry = self.configuration.docker.registry
|
|
342
|
-
username = self.__docker_login()
|
|
328
|
+
username = self.__docker_login()
|
|
343
329
|
|
|
344
330
|
# Build image name based on available information
|
|
345
331
|
if registry and username:
|
|
@@ -402,15 +388,6 @@ class Project:
|
|
|
402
388
|
file=f,
|
|
403
389
|
)
|
|
404
390
|
print(f"LABEL org.opencontainers.image.title={self.name}", file=f)
|
|
405
|
-
|
|
406
|
-
url = self.__get_project_url()
|
|
407
|
-
|
|
408
|
-
if url is not None:
|
|
409
|
-
print(
|
|
410
|
-
f"LABEL org.opencontainers.image.source={url.strip()}",
|
|
411
|
-
file=f,
|
|
412
|
-
)
|
|
413
|
-
|
|
414
391
|
print("", file=f)
|
|
415
392
|
|
|
416
393
|
print(f"WORKDIR {WORKSPACE}", file=f)
|
|
@@ -422,20 +399,3 @@ class Project:
|
|
|
422
399
|
raise Exception(f"Target {target} not found")
|
|
423
400
|
|
|
424
401
|
return getattr(self.configuration.targets, target)
|
|
425
|
-
|
|
426
|
-
def __get_project_url(self) -> Optional[str]:
|
|
427
|
-
"""
|
|
428
|
-
Get the project URL from setup.cfg if available
|
|
429
|
-
"""
|
|
430
|
-
cfg_path = Path(self.__path) / "setup.cfg"
|
|
431
|
-
if not cfg_path.exists():
|
|
432
|
-
return None
|
|
433
|
-
|
|
434
|
-
config = configparser.ConfigParser()
|
|
435
|
-
config.read(cfg_path)
|
|
436
|
-
|
|
437
|
-
url = config.get("metadata", "url", fallback=None)
|
|
438
|
-
if url is not None:
|
|
439
|
-
return url.strip()
|
|
440
|
-
|
|
441
|
-
return None
|
|
@@ -7,34 +7,34 @@ from .multifiles import collect_imported_files
|
|
|
7
7
|
|
|
8
8
|
class Workload:
|
|
9
9
|
"""
|
|
10
|
-
Represents a
|
|
10
|
+
Represents a mutifile workload consisting of an entry script and its imported files
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
self.__args: list[str] = []
|
|
13
|
+
__pyproject_root: Path | None = None
|
|
14
|
+
__args: list[str] = []
|
|
16
15
|
|
|
16
|
+
def __init__(self, entry_script: Path | str = None, code: str = None):
|
|
17
17
|
if entry_script is not None:
|
|
18
18
|
|
|
19
|
-
self.
|
|
20
|
-
|
|
21
|
-
if self.__detect_and_set_src_layout():
|
|
19
|
+
if self.__is_src_layout():
|
|
20
|
+
self.__entry_script = Path(os.path.abspath(entry_script))
|
|
22
21
|
self.__base_path = self.__pyproject_root
|
|
23
22
|
|
|
24
23
|
self.__files = collect_imported_files(
|
|
25
24
|
self.__entry_script, is_project=True
|
|
26
25
|
)
|
|
26
|
+
|
|
27
27
|
else:
|
|
28
|
+
self.__entry_script = Path(os.path.abspath(entry_script))
|
|
28
29
|
self.__base_path = self.__entry_script.parent
|
|
29
30
|
|
|
30
31
|
self.__files = collect_imported_files(
|
|
31
32
|
self.__entry_script, is_project=False
|
|
32
33
|
)
|
|
33
34
|
|
|
34
|
-
self.__data = {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
self.__data[self.__path_mapping(f)] = file.read()
|
|
35
|
+
self.__data = {
|
|
36
|
+
self.__path_mapping(f): open(f, "r").read() for f in self.__files
|
|
37
|
+
}
|
|
38
38
|
|
|
39
39
|
elif code is not None:
|
|
40
40
|
self.__entry_script = Path(os.path.abspath("main.py"))
|
|
@@ -86,7 +86,7 @@ class Workload:
|
|
|
86
86
|
"""
|
|
87
87
|
Entry script module name
|
|
88
88
|
"""
|
|
89
|
-
return self.entry_script.replace(
|
|
89
|
+
return self.entry_script.replace("/", ".")[:-3] # strip .py
|
|
90
90
|
|
|
91
91
|
@property
|
|
92
92
|
def data(self) -> dict[str, str]:
|
|
@@ -127,7 +127,7 @@ class Workload:
|
|
|
127
127
|
"""
|
|
128
128
|
return self.__relative_path(file).replace("/", "__")
|
|
129
129
|
|
|
130
|
-
def
|
|
130
|
+
def __pyproject_root(self, start: Path | None = None) -> Path | None:
|
|
131
131
|
"""
|
|
132
132
|
Find the project root by looking for setup.cfg or pyproject.toml
|
|
133
133
|
"""
|
|
@@ -140,11 +140,11 @@ class Workload:
|
|
|
140
140
|
|
|
141
141
|
return None
|
|
142
142
|
|
|
143
|
-
def
|
|
143
|
+
def __is_src_layout(self) -> bool:
|
|
144
144
|
"""
|
|
145
145
|
Determine if the project uses a src/ layout
|
|
146
146
|
"""
|
|
147
|
-
root = self.
|
|
147
|
+
root = self.__pyproject_root()
|
|
148
148
|
|
|
149
149
|
if root is None:
|
|
150
150
|
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|