fraclab-sdk 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.
- README.md +1601 -0
- fraclab_sdk/__init__.py +34 -0
- fraclab_sdk/algorithm/__init__.py +13 -0
- fraclab_sdk/algorithm/export.py +1 -0
- fraclab_sdk/algorithm/library.py +378 -0
- fraclab_sdk/cli.py +381 -0
- fraclab_sdk/config.py +54 -0
- fraclab_sdk/devkit/__init__.py +25 -0
- fraclab_sdk/devkit/compile.py +342 -0
- fraclab_sdk/devkit/export.py +354 -0
- fraclab_sdk/devkit/validate.py +1043 -0
- fraclab_sdk/errors.py +124 -0
- fraclab_sdk/materialize/__init__.py +8 -0
- fraclab_sdk/materialize/fsops.py +125 -0
- fraclab_sdk/materialize/hash.py +28 -0
- fraclab_sdk/materialize/materializer.py +241 -0
- fraclab_sdk/models/__init__.py +52 -0
- fraclab_sdk/models/bundle_manifest.py +51 -0
- fraclab_sdk/models/dataspec.py +65 -0
- fraclab_sdk/models/drs.py +47 -0
- fraclab_sdk/models/output_contract.py +111 -0
- fraclab_sdk/models/run_output_manifest.py +119 -0
- fraclab_sdk/results/__init__.py +25 -0
- fraclab_sdk/results/preview.py +150 -0
- fraclab_sdk/results/reader.py +329 -0
- fraclab_sdk/run/__init__.py +10 -0
- fraclab_sdk/run/logs.py +42 -0
- fraclab_sdk/run/manager.py +403 -0
- fraclab_sdk/run/subprocess_runner.py +153 -0
- fraclab_sdk/runtime/__init__.py +11 -0
- fraclab_sdk/runtime/artifacts.py +303 -0
- fraclab_sdk/runtime/data_client.py +123 -0
- fraclab_sdk/runtime/runner_main.py +286 -0
- fraclab_sdk/runtime/snapshot_provider.py +1 -0
- fraclab_sdk/selection/__init__.py +11 -0
- fraclab_sdk/selection/model.py +247 -0
- fraclab_sdk/selection/validate.py +54 -0
- fraclab_sdk/snapshot/__init__.py +12 -0
- fraclab_sdk/snapshot/index.py +94 -0
- fraclab_sdk/snapshot/library.py +205 -0
- fraclab_sdk/snapshot/loader.py +217 -0
- fraclab_sdk/specs/manifest.py +89 -0
- fraclab_sdk/utils/io.py +32 -0
- fraclab_sdk-0.1.0.dist-info/METADATA +1622 -0
- fraclab_sdk-0.1.0.dist-info/RECORD +47 -0
- fraclab_sdk-0.1.0.dist-info/WHEEL +4 -0
- fraclab_sdk-0.1.0.dist-info/entry_points.txt +4 -0
fraclab_sdk/cli.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""Fraclab SDK CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
import traceback
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from functools import wraps
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TypeVar
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
|
|
15
|
+
from fraclab_sdk.algorithm import AlgorithmLibrary
|
|
16
|
+
from fraclab_sdk.config import SDKConfig
|
|
17
|
+
from fraclab_sdk.errors import ExitCode, FraclabError
|
|
18
|
+
from fraclab_sdk.results import ResultReader
|
|
19
|
+
from fraclab_sdk.run import RunManager
|
|
20
|
+
from fraclab_sdk.run.logs import tail_stderr, tail_stdout
|
|
21
|
+
from fraclab_sdk.run.manager import RunStatus
|
|
22
|
+
from fraclab_sdk.selection.model import SelectionModel
|
|
23
|
+
from fraclab_sdk.snapshot import SnapshotLibrary
|
|
24
|
+
|
|
25
|
+
app = typer.Typer(help="Fraclab SDK CLI")
|
|
26
|
+
snapshot_app = typer.Typer()
|
|
27
|
+
algo_app = typer.Typer()
|
|
28
|
+
run_app = typer.Typer()
|
|
29
|
+
results_app = typer.Typer()
|
|
30
|
+
validate_app = typer.Typer()
|
|
31
|
+
|
|
32
|
+
app.add_typer(snapshot_app, name="snapshot")
|
|
33
|
+
app.add_typer(algo_app, name="algo")
|
|
34
|
+
app.add_typer(run_app, name="run")
|
|
35
|
+
app.add_typer(results_app, name="results")
|
|
36
|
+
app.add_typer(validate_app, name="validate")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _error(msg: str) -> None:
|
|
40
|
+
"""Print error message to stderr."""
|
|
41
|
+
typer.echo(f"Error: {msg}", err=True)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _get_libs() -> tuple[SDKConfig, SnapshotLibrary, AlgorithmLibrary, RunManager]:
|
|
45
|
+
cfg = SDKConfig()
|
|
46
|
+
return cfg, SnapshotLibrary(cfg), AlgorithmLibrary(cfg), RunManager(cfg)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
F = TypeVar("F", bound=Callable)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def handle_errors(func: F) -> F:
|
|
53
|
+
"""Decorator to handle FraclabError and other exceptions uniformly."""
|
|
54
|
+
|
|
55
|
+
@wraps(func)
|
|
56
|
+
def wrapper(*args, **kwargs):
|
|
57
|
+
try:
|
|
58
|
+
return func(*args, **kwargs)
|
|
59
|
+
except typer.Exit:
|
|
60
|
+
# Re-raise typer.Exit unchanged (it's used for normal exits)
|
|
61
|
+
raise
|
|
62
|
+
except FraclabError as e:
|
|
63
|
+
_error(str(e))
|
|
64
|
+
raise typer.Exit(e.exit_code) from None
|
|
65
|
+
except FileNotFoundError as e:
|
|
66
|
+
_error(f"File not found: {e.filename or e}")
|
|
67
|
+
raise typer.Exit(ExitCode.INPUT_ERROR) from None
|
|
68
|
+
except json.JSONDecodeError as e:
|
|
69
|
+
_error(f"Invalid JSON: {e.msg} at line {e.lineno}")
|
|
70
|
+
raise typer.Exit(ExitCode.INPUT_ERROR) from None
|
|
71
|
+
except Exception as e:
|
|
72
|
+
_error(f"Internal error: {e}")
|
|
73
|
+
if "--debug" in sys.argv:
|
|
74
|
+
traceback.print_exc(file=sys.stderr)
|
|
75
|
+
raise typer.Exit(ExitCode.INTERNAL_ERROR) from None
|
|
76
|
+
|
|
77
|
+
return wrapper # type: ignore
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@snapshot_app.command("import")
|
|
81
|
+
@handle_errors
|
|
82
|
+
def snapshot_import(path: Path):
|
|
83
|
+
"""Import a snapshot (dir or zip)."""
|
|
84
|
+
_, snap_lib, _, _ = _get_libs()
|
|
85
|
+
snap_id = snap_lib.import_snapshot(path)
|
|
86
|
+
typer.echo(f"Imported snapshot: {snap_id}")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@snapshot_app.command("list")
|
|
90
|
+
@handle_errors
|
|
91
|
+
def snapshot_list():
|
|
92
|
+
"""List snapshots."""
|
|
93
|
+
_, snap_lib, _, _ = _get_libs()
|
|
94
|
+
snaps = snap_lib.list_snapshots()
|
|
95
|
+
if not snaps:
|
|
96
|
+
typer.echo("No snapshots")
|
|
97
|
+
raise typer.Exit(0)
|
|
98
|
+
for s in snaps:
|
|
99
|
+
typer.echo(f"{s.snapshot_id}\t{ s.bundle_id }\t{ s.created_at }")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@algo_app.command("import")
|
|
103
|
+
@handle_errors
|
|
104
|
+
def algo_import(path: Path):
|
|
105
|
+
"""Import an algorithm (dir or zip)."""
|
|
106
|
+
_, _, algo_lib, _ = _get_libs()
|
|
107
|
+
algo_id, ver = algo_lib.import_algorithm(path)
|
|
108
|
+
typer.echo(f"Imported algorithm: {algo_id}:{ver}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@algo_app.command("list")
|
|
112
|
+
@handle_errors
|
|
113
|
+
def algo_list():
|
|
114
|
+
"""List algorithms."""
|
|
115
|
+
_, _, algo_lib, _ = _get_libs()
|
|
116
|
+
algos = algo_lib.list_algorithms()
|
|
117
|
+
if not algos:
|
|
118
|
+
typer.echo("No algorithms")
|
|
119
|
+
raise typer.Exit(0)
|
|
120
|
+
for a in algos:
|
|
121
|
+
typer.echo(f"{a.algorithm_id}\t{a.version}\t{a.imported_at}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@algo_app.command("compile")
|
|
125
|
+
@handle_errors
|
|
126
|
+
def algo_compile(
|
|
127
|
+
workspace: Path = typer.Argument(..., help="Path to algorithm workspace"),
|
|
128
|
+
bundle: Path | None = typer.Option(None, "--bundle", "-b", help="Bundle path for drs.json"),
|
|
129
|
+
skip_inputspec: bool = typer.Option(False, "--skip-inputspec", help="Skip InputSpec compilation (schema.inputspec:INPUT_SPEC)"),
|
|
130
|
+
skip_output_contract: bool = typer.Option(
|
|
131
|
+
False, "--skip-output-contract", help="Skip OutputContract compilation"
|
|
132
|
+
),
|
|
133
|
+
):
|
|
134
|
+
"""Compile algorithm workspace to generate dist/ artifacts.
|
|
135
|
+
|
|
136
|
+
Generates:
|
|
137
|
+
- dist/params.schema.json (from schema.inputspec:INPUT_SPEC)
|
|
138
|
+
- dist/output_contract.json (from schema.output_contract:OUTPUT_CONTRACT)
|
|
139
|
+
- dist/drs.json (from bundle)
|
|
140
|
+
"""
|
|
141
|
+
from fraclab_sdk.devkit.compile import compile_algorithm
|
|
142
|
+
|
|
143
|
+
result = compile_algorithm(
|
|
144
|
+
workspace=workspace,
|
|
145
|
+
bundle_path=bundle,
|
|
146
|
+
skip_inputspec=skip_inputspec,
|
|
147
|
+
skip_output_contract=skip_output_contract,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
typer.echo(f"Compiled algorithm workspace: {workspace}")
|
|
151
|
+
typer.echo(f" params.schema.json: {result.params_schema_path}")
|
|
152
|
+
typer.echo(f" output_contract.json: {result.output_contract_path}")
|
|
153
|
+
typer.echo(f" drs.json: {result.drs_path}")
|
|
154
|
+
if result.bound_bundle:
|
|
155
|
+
typer.echo(f" Bound bundle hashes: {result.bound_bundle}")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@algo_app.command("export")
|
|
159
|
+
@handle_errors
|
|
160
|
+
def algo_export(
|
|
161
|
+
workspace: Path = typer.Argument(..., help="Path to algorithm workspace"),
|
|
162
|
+
output: Path = typer.Argument(..., help="Output path (.zip or directory)"),
|
|
163
|
+
auto_compile: bool = typer.Option(False, "--auto-compile", "-c", help="Auto-compile if needed"),
|
|
164
|
+
bundle: Path | None = typer.Option(None, "--bundle", "-b", help="Bundle path for auto-compile"),
|
|
165
|
+
):
|
|
166
|
+
"""Export algorithm workspace as distributable package.
|
|
167
|
+
|
|
168
|
+
Creates a package containing main.py, manifest.json, and dist/ artifacts.
|
|
169
|
+
"""
|
|
170
|
+
from fraclab_sdk.devkit.export import export_algorithm_package
|
|
171
|
+
|
|
172
|
+
result = export_algorithm_package(
|
|
173
|
+
workspace=workspace,
|
|
174
|
+
output=output,
|
|
175
|
+
auto_compile=auto_compile,
|
|
176
|
+
bundle_path=bundle,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
typer.echo(f"Exported algorithm to: {result.output_path}")
|
|
180
|
+
typer.echo(f" Files included: {len(result.files_included)}")
|
|
181
|
+
if result.files_rejected:
|
|
182
|
+
typer.echo(f" Files rejected: {len(result.files_rejected)}", err=True)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@run_app.command("create")
|
|
186
|
+
@handle_errors
|
|
187
|
+
def run_create(
|
|
188
|
+
snapshot_id: str = typer.Argument(...),
|
|
189
|
+
algorithm_id: str = typer.Argument(...),
|
|
190
|
+
algorithm_version: str = typer.Argument(...),
|
|
191
|
+
params_path: Path | None = typer.Option(None, "--params", "-p", help="JSON file with params"),
|
|
192
|
+
):
|
|
193
|
+
"""Create a run selecting all items."""
|
|
194
|
+
_, snap_lib, algo_lib, run_mgr = _get_libs()
|
|
195
|
+
snapshot = snap_lib.get_snapshot(snapshot_id)
|
|
196
|
+
algorithm = algo_lib.get_algorithm(algorithm_id, algorithm_version)
|
|
197
|
+
|
|
198
|
+
selection = SelectionModel.from_snapshot_and_drs(snapshot, algorithm.drs)
|
|
199
|
+
# select all items for each dataset
|
|
200
|
+
for ds in selection.get_selectable_datasets():
|
|
201
|
+
selection.set_selected(ds.dataset_key, list(range(ds.total_items)))
|
|
202
|
+
|
|
203
|
+
params: dict = {}
|
|
204
|
+
if params_path:
|
|
205
|
+
params = json.loads(params_path.read_text())
|
|
206
|
+
|
|
207
|
+
run_id = run_mgr.create_run(
|
|
208
|
+
snapshot_id=snapshot_id,
|
|
209
|
+
algorithm_id=algorithm_id,
|
|
210
|
+
algorithm_version=algorithm_version,
|
|
211
|
+
selection=selection,
|
|
212
|
+
params=params,
|
|
213
|
+
)
|
|
214
|
+
typer.echo(run_id)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@run_app.command("exec")
|
|
218
|
+
@handle_errors
|
|
219
|
+
def run_exec(run_id: str, timeout: int | None = typer.Option(None, "--timeout", "-t")):
|
|
220
|
+
"""Execute a run."""
|
|
221
|
+
_, _, _, run_mgr = _get_libs()
|
|
222
|
+
result = run_mgr.execute(run_id, timeout_s=timeout)
|
|
223
|
+
typer.echo(f"{result.status.value} (exit_code={result.exit_code})")
|
|
224
|
+
if result.status == RunStatus.TIMEOUT:
|
|
225
|
+
_error(result.error or f"Timeout after {timeout}s")
|
|
226
|
+
raise typer.Exit(ExitCode.TIMEOUT)
|
|
227
|
+
if result.status == RunStatus.FAILED:
|
|
228
|
+
_error(result.error or "Run failed")
|
|
229
|
+
raise typer.Exit(ExitCode.RUN_FAILED)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@run_app.command("tail")
|
|
233
|
+
@handle_errors
|
|
234
|
+
def run_tail(run_id: str, stderr: bool = typer.Option(False, "--stderr")):
|
|
235
|
+
"""Tail stdout/stderr."""
|
|
236
|
+
_, _, _, run_mgr = _get_libs()
|
|
237
|
+
run_dir = run_mgr.get_run_dir(run_id)
|
|
238
|
+
if not run_dir.exists():
|
|
239
|
+
_error(f"Run directory not found: {run_id}")
|
|
240
|
+
raise typer.Exit(ExitCode.INPUT_ERROR)
|
|
241
|
+
if stderr:
|
|
242
|
+
typer.echo(tail_stderr(run_dir))
|
|
243
|
+
else:
|
|
244
|
+
typer.echo(tail_stdout(run_dir))
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@results_app.command("list")
|
|
248
|
+
@handle_errors
|
|
249
|
+
def results_list(run_id: str):
|
|
250
|
+
"""List artifacts for a run."""
|
|
251
|
+
_, _, _, run_mgr = _get_libs()
|
|
252
|
+
run_dir = run_mgr.get_run_dir(run_id)
|
|
253
|
+
if not run_dir.exists():
|
|
254
|
+
_error(f"Run directory not found: {run_id}")
|
|
255
|
+
raise typer.Exit(ExitCode.INPUT_ERROR)
|
|
256
|
+
reader = ResultReader(run_dir)
|
|
257
|
+
if not reader.has_manifest():
|
|
258
|
+
_error("Manifest not found (run may not have completed)")
|
|
259
|
+
raise typer.Exit(ExitCode.INPUT_ERROR)
|
|
260
|
+
manifest = reader.read_manifest()
|
|
261
|
+
typer.echo(f"Status: {manifest.status}")
|
|
262
|
+
for art in manifest.list_all_artifacts():
|
|
263
|
+
typer.echo(f"{art.artifactKey}\t{art.type}\t{art.uri or ''}")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@validate_app.command("inputspec")
|
|
267
|
+
@handle_errors
|
|
268
|
+
def validate_inputspec_cmd(
|
|
269
|
+
workspace: Path = typer.Argument(..., help="Path to algorithm workspace"),
|
|
270
|
+
):
|
|
271
|
+
"""Validate InputSpec (schema.inputspec:INPUT_SPEC).
|
|
272
|
+
|
|
273
|
+
Checks json_schema_extra fields, show_when conditions, and enum_labels.
|
|
274
|
+
"""
|
|
275
|
+
from fraclab_sdk.devkit.validate import ValidationSeverity, validate_inputspec
|
|
276
|
+
|
|
277
|
+
result = validate_inputspec(workspace)
|
|
278
|
+
|
|
279
|
+
if result.valid:
|
|
280
|
+
typer.echo("InputSpec validation passed")
|
|
281
|
+
else:
|
|
282
|
+
typer.echo("InputSpec validation failed", err=True)
|
|
283
|
+
|
|
284
|
+
for issue in result.issues:
|
|
285
|
+
prefix = "ERROR" if issue.severity == ValidationSeverity.ERROR else "WARN"
|
|
286
|
+
path_str = f" at {issue.path}" if issue.path else ""
|
|
287
|
+
typer.echo(f" [{prefix}] {issue.code}{path_str}: {issue.message}", err=True)
|
|
288
|
+
|
|
289
|
+
if not result.valid:
|
|
290
|
+
raise typer.Exit(ExitCode.INPUT_ERROR)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@validate_app.command("output-contract")
|
|
294
|
+
@handle_errors
|
|
295
|
+
def validate_output_contract_cmd(
|
|
296
|
+
workspace_or_path: Path = typer.Argument(..., help="Workspace or output_contract.json path"),
|
|
297
|
+
):
|
|
298
|
+
"""Validate OutputContract structure.
|
|
299
|
+
|
|
300
|
+
Checks key uniqueness, kind/schema consistency, and dimensions.
|
|
301
|
+
"""
|
|
302
|
+
from fraclab_sdk.devkit.validate import ValidationSeverity, validate_output_contract
|
|
303
|
+
|
|
304
|
+
result = validate_output_contract(workspace_or_path)
|
|
305
|
+
|
|
306
|
+
if result.valid:
|
|
307
|
+
typer.echo("OutputContract validation passed")
|
|
308
|
+
else:
|
|
309
|
+
typer.echo("OutputContract validation failed", err=True)
|
|
310
|
+
|
|
311
|
+
for issue in result.issues:
|
|
312
|
+
prefix = "ERROR" if issue.severity == ValidationSeverity.ERROR else "WARN"
|
|
313
|
+
path_str = f" at {issue.path}" if issue.path else ""
|
|
314
|
+
typer.echo(f" [{prefix}] {issue.code}{path_str}: {issue.message}", err=True)
|
|
315
|
+
|
|
316
|
+
if not result.valid:
|
|
317
|
+
raise typer.Exit(ExitCode.INPUT_ERROR)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@validate_app.command("bundle")
|
|
321
|
+
@handle_errors
|
|
322
|
+
def validate_bundle_cmd(
|
|
323
|
+
bundle_path: Path = typer.Argument(..., help="Path to bundle directory"),
|
|
324
|
+
):
|
|
325
|
+
"""Validate bundle hash integrity.
|
|
326
|
+
|
|
327
|
+
Checks ds.json and drs.json hashes against manifest.
|
|
328
|
+
"""
|
|
329
|
+
from fraclab_sdk.devkit.validate import ValidationSeverity, validate_bundle
|
|
330
|
+
|
|
331
|
+
result = validate_bundle(bundle_path)
|
|
332
|
+
|
|
333
|
+
if result.valid:
|
|
334
|
+
typer.echo("Bundle validation passed")
|
|
335
|
+
else:
|
|
336
|
+
typer.echo("Bundle validation failed", err=True)
|
|
337
|
+
|
|
338
|
+
for issue in result.issues:
|
|
339
|
+
prefix = "ERROR" if issue.severity == ValidationSeverity.ERROR else "WARN"
|
|
340
|
+
typer.echo(f" [{prefix}] {issue.code}: {issue.message}", err=True)
|
|
341
|
+
|
|
342
|
+
if not result.valid:
|
|
343
|
+
raise typer.Exit(ExitCode.INPUT_ERROR)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@validate_app.command("run-manifest")
|
|
347
|
+
@handle_errors
|
|
348
|
+
def validate_run_manifest_cmd(
|
|
349
|
+
manifest_path: Path = typer.Argument(..., help="Path to run output manifest.json"),
|
|
350
|
+
contract_path: Path | None = typer.Option(
|
|
351
|
+
None, "--contract", "-c", help="Path to output_contract.json for alignment check"
|
|
352
|
+
),
|
|
353
|
+
):
|
|
354
|
+
"""Validate run output manifest against OutputContract.
|
|
355
|
+
|
|
356
|
+
If contract is provided, checks that all required datasets/items/artifacts are present.
|
|
357
|
+
"""
|
|
358
|
+
from fraclab_sdk.devkit.validate import ValidationSeverity, validate_run_manifest
|
|
359
|
+
|
|
360
|
+
result = validate_run_manifest(manifest_path, contract_path)
|
|
361
|
+
|
|
362
|
+
if result.valid:
|
|
363
|
+
typer.echo("Run manifest validation passed")
|
|
364
|
+
else:
|
|
365
|
+
typer.echo("Run manifest validation failed", err=True)
|
|
366
|
+
|
|
367
|
+
for issue in result.issues:
|
|
368
|
+
prefix = "ERROR" if issue.severity == ValidationSeverity.ERROR else "WARN"
|
|
369
|
+
path_str = f" at {issue.path}" if issue.path else ""
|
|
370
|
+
typer.echo(f" [{prefix}] {issue.code}{path_str}: {issue.message}", err=True)
|
|
371
|
+
|
|
372
|
+
if not result.valid:
|
|
373
|
+
raise typer.Exit(ExitCode.INPUT_ERROR)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def main():
|
|
377
|
+
app()
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
if __name__ == "__main__":
|
|
381
|
+
main()
|
fraclab_sdk/config.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""SDK configuration and path resolution."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SDKConfig:
|
|
8
|
+
"""Configuration for Fraclab SDK paths.
|
|
9
|
+
|
|
10
|
+
Resolves SDK home directory from environment variable FRACLAB_SDK_HOME
|
|
11
|
+
or falls back to ~/.fraclab.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, sdk_home: Path | None = None) -> None:
|
|
15
|
+
"""Initialize SDK configuration.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
sdk_home: Optional explicit SDK home path. If None, resolves from
|
|
19
|
+
FRACLAB_SDK_HOME env var or defaults to ~/.fraclab.
|
|
20
|
+
"""
|
|
21
|
+
if sdk_home is not None:
|
|
22
|
+
self._sdk_home = Path(sdk_home).expanduser().resolve()
|
|
23
|
+
else:
|
|
24
|
+
env_home = os.environ.get("FRACLAB_SDK_HOME")
|
|
25
|
+
if env_home:
|
|
26
|
+
self._sdk_home = Path(env_home).expanduser().resolve()
|
|
27
|
+
else:
|
|
28
|
+
self._sdk_home = (Path.home() / ".fraclab").expanduser().resolve()
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def sdk_home(self) -> Path:
|
|
32
|
+
"""Root SDK home directory."""
|
|
33
|
+
return self._sdk_home
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def snapshots_dir(self) -> Path:
|
|
37
|
+
"""Directory for snapshot storage."""
|
|
38
|
+
return self._sdk_home / "snapshots"
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def algorithms_dir(self) -> Path:
|
|
42
|
+
"""Directory for algorithm storage."""
|
|
43
|
+
return self._sdk_home / "algorithms"
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def runs_dir(self) -> Path:
|
|
47
|
+
"""Directory for run storage."""
|
|
48
|
+
return self._sdk_home / "runs"
|
|
49
|
+
|
|
50
|
+
def ensure_dirs(self) -> None:
|
|
51
|
+
"""Create all SDK directories if they don't exist."""
|
|
52
|
+
self.snapshots_dir.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
self.algorithms_dir.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
self.runs_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Development toolkit for algorithm compilation and validation.
|
|
2
|
+
|
|
3
|
+
This module provides tools for:
|
|
4
|
+
- Compiling algorithm workspaces (generating dist/ artifacts)
|
|
5
|
+
- Exporting algorithms as distributable packages
|
|
6
|
+
- Validating InputSpec, OutputContract, and run manifests
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from fraclab_sdk.devkit.compile import compile_algorithm
|
|
10
|
+
from fraclab_sdk.devkit.export import export_algorithm_package
|
|
11
|
+
from fraclab_sdk.devkit.validate import (
|
|
12
|
+
validate_bundle,
|
|
13
|
+
validate_inputspec,
|
|
14
|
+
validate_output_contract,
|
|
15
|
+
validate_run_manifest,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"compile_algorithm",
|
|
20
|
+
"export_algorithm_package",
|
|
21
|
+
"validate_bundle",
|
|
22
|
+
"validate_inputspec",
|
|
23
|
+
"validate_output_contract",
|
|
24
|
+
"validate_run_manifest",
|
|
25
|
+
]
|