lyceum-cli 1.0.25__py3-none-any.whl → 1.0.27__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.
- lyceum/external/auth/login.py +18 -18
- lyceum/external/compute/execution/docker.py +4 -2
- lyceum/external/compute/execution/docker_compose.py +263 -0
- lyceum/external/compute/execution/notebook.py +0 -2
- lyceum/external/compute/execution/python.py +2 -1
- lyceum/external/compute/inference/batch.py +8 -10
- lyceum/external/vms/instances.py +301 -0
- lyceum/external/vms/management.py +383 -0
- lyceum/main.py +3 -0
- lyceum/shared/config.py +19 -24
- lyceum/shared/display.py +12 -31
- lyceum/shared/streaming.py +17 -45
- {lyceum_cli-1.0.25.dist-info → lyceum_cli-1.0.27.dist-info}/METADATA +1 -1
- lyceum_cli-1.0.27.dist-info/RECORD +34 -0
- {lyceum_cli-1.0.25.dist-info → lyceum_cli-1.0.27.dist-info}/WHEEL +1 -1
- {lyceum_cli-1.0.25.dist-info → lyceum_cli-1.0.27.dist-info}/top_level.txt +0 -1
- lyceum/external/compute/execution/docker_config.py +0 -123
- lyceum/external/storage/files.py +0 -273
- lyceum_cli-1.0.25.dist-info/RECORD +0 -46
- tests/__init__.py +0 -1
- tests/conftest.py +0 -200
- tests/unit/__init__.py +0 -1
- tests/unit/external/__init__.py +0 -1
- tests/unit/external/compute/__init__.py +0 -1
- tests/unit/external/compute/execution/__init__.py +0 -1
- tests/unit/external/compute/execution/test_data.py +0 -33
- tests/unit/external/compute/execution/test_dependency_resolver.py +0 -257
- tests/unit/external/compute/execution/test_python_helpers.py +0 -406
- tests/unit/external/compute/execution/test_python_run.py +0 -289
- tests/unit/shared/__init__.py +0 -1
- tests/unit/shared/test_config.py +0 -341
- tests/unit/shared/test_streaming.py +0 -259
- /lyceum/external/{storage → vms}/__init__.py +0 -0
- {lyceum_cli-1.0.25.dist-info → lyceum_cli-1.0.27.dist-info}/entry_points.txt +0 -0
lyceum/external/storage/files.py
DELETED
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
"""Storage file management commands"""
|
|
2
|
-
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
import httpx
|
|
7
|
-
import typer
|
|
8
|
-
from rich.console import Console
|
|
9
|
-
from rich.table import Table
|
|
10
|
-
|
|
11
|
-
from ...shared.config import config
|
|
12
|
-
|
|
13
|
-
console = Console()
|
|
14
|
-
|
|
15
|
-
storage_app = typer.Typer(name="storage", help="File storage commands")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def format_size(size_bytes: int) -> str:
|
|
19
|
-
"""Format bytes into human-readable size."""
|
|
20
|
-
for unit in ["B", "KB", "MB", "GB"]:
|
|
21
|
-
if size_bytes < 1024:
|
|
22
|
-
return f"{size_bytes:.1f} {unit}"
|
|
23
|
-
size_bytes /= 1024
|
|
24
|
-
return f"{size_bytes:.1f} TB"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@storage_app.command("list")
|
|
28
|
-
def list_files(
|
|
29
|
-
prefix: str = typer.Option("", "--prefix", "-p", help="Filter by prefix/folder"),
|
|
30
|
-
limit: int = typer.Option(100, "--limit", "-n", help="Maximum files to list"),
|
|
31
|
-
):
|
|
32
|
-
"""List files in your storage bucket."""
|
|
33
|
-
try:
|
|
34
|
-
config.get_client()
|
|
35
|
-
|
|
36
|
-
params = {"prefix": prefix, "max_files": limit}
|
|
37
|
-
|
|
38
|
-
response = httpx.get(
|
|
39
|
-
f"{config.base_url}/api/v2/external/storage/list-files",
|
|
40
|
-
headers={"Authorization": f"Bearer {config.api_key}"},
|
|
41
|
-
params=params,
|
|
42
|
-
timeout=30.0,
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
if response.status_code != 200:
|
|
46
|
-
console.print(f"[red]Error: HTTP {response.status_code}[/red]")
|
|
47
|
-
if response.status_code == 401:
|
|
48
|
-
console.print("[yellow]Run 'lyceum auth login' to re-authenticate.[/yellow]")
|
|
49
|
-
else:
|
|
50
|
-
console.print(f"[red]{response.content.decode()}[/red]")
|
|
51
|
-
raise typer.Exit(1)
|
|
52
|
-
|
|
53
|
-
files = response.json()
|
|
54
|
-
|
|
55
|
-
if not files:
|
|
56
|
-
console.print("[dim]No files found.[/dim]")
|
|
57
|
-
if prefix:
|
|
58
|
-
console.print(f"[dim]Prefix filter: {prefix}[/dim]")
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
table = Table(title="Storage Files")
|
|
62
|
-
table.add_column("Key", style="cyan")
|
|
63
|
-
table.add_column("Size", style="green", justify="right")
|
|
64
|
-
table.add_column("Last Modified", style="dim")
|
|
65
|
-
|
|
66
|
-
for f in files:
|
|
67
|
-
last_mod = f.get("last_modified", "")
|
|
68
|
-
if last_mod:
|
|
69
|
-
try:
|
|
70
|
-
dt = datetime.fromisoformat(last_mod.replace("Z", "+00:00"))
|
|
71
|
-
last_mod = dt.strftime("%Y-%m-%d %H:%M")
|
|
72
|
-
except Exception:
|
|
73
|
-
pass
|
|
74
|
-
|
|
75
|
-
table.add_row(
|
|
76
|
-
f.get("key", ""),
|
|
77
|
-
format_size(f.get("size", 0)),
|
|
78
|
-
last_mod,
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
console.print(table)
|
|
82
|
-
console.print(f"\n[dim]Total: {len(files)} file(s)[/dim]")
|
|
83
|
-
|
|
84
|
-
except typer.Exit:
|
|
85
|
-
raise
|
|
86
|
-
except Exception as e:
|
|
87
|
-
console.print(f"[red]Error: {e}[/red]")
|
|
88
|
-
raise typer.Exit(1)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@storage_app.command("upload")
|
|
92
|
-
def upload_file(
|
|
93
|
-
file_path: Path = typer.Argument(..., help="Local file to upload"),
|
|
94
|
-
dest: str = typer.Option(None, "--dest", "-d", help="Destination path in storage (defaults to filename)"),
|
|
95
|
-
):
|
|
96
|
-
"""Upload a file to your storage bucket."""
|
|
97
|
-
try:
|
|
98
|
-
config.get_client()
|
|
99
|
-
|
|
100
|
-
if not file_path.exists():
|
|
101
|
-
console.print(f"[red]Error: File not found: {file_path}[/red]")
|
|
102
|
-
raise typer.Exit(1)
|
|
103
|
-
|
|
104
|
-
if not file_path.is_file():
|
|
105
|
-
console.print(f"[red]Error: Not a file: {file_path}[/red]")
|
|
106
|
-
raise typer.Exit(1)
|
|
107
|
-
|
|
108
|
-
remote_path = dest or file_path.name
|
|
109
|
-
file_size = file_path.stat().st_size
|
|
110
|
-
|
|
111
|
-
console.print(f"[dim]Uploading {file_path.name} ({format_size(file_size)})...[/dim]")
|
|
112
|
-
|
|
113
|
-
with open(file_path, "rb") as f:
|
|
114
|
-
files = {"file": (file_path.name, f)}
|
|
115
|
-
data = {"key": remote_path} if dest else {}
|
|
116
|
-
|
|
117
|
-
response = httpx.post(
|
|
118
|
-
f"{config.base_url}/api/v2/external/storage/upload",
|
|
119
|
-
headers={"Authorization": f"Bearer {config.api_key}"},
|
|
120
|
-
files=files,
|
|
121
|
-
data=data,
|
|
122
|
-
timeout=300.0, # 5 min timeout for large files
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
if response.status_code != 200:
|
|
126
|
-
console.print(f"[red]Error: HTTP {response.status_code}[/red]")
|
|
127
|
-
if response.status_code == 401:
|
|
128
|
-
console.print("[yellow]Run 'lyceum auth login' to re-authenticate.[/yellow]")
|
|
129
|
-
else:
|
|
130
|
-
console.print(f"[red]{response.content.decode()}[/red]")
|
|
131
|
-
raise typer.Exit(1)
|
|
132
|
-
|
|
133
|
-
result = response.json()
|
|
134
|
-
console.print(f"[green]Uploaded successfully![/green]")
|
|
135
|
-
console.print(f"[dim]Key: {result.get('key')}[/dim]")
|
|
136
|
-
console.print(f"[dim]Size: {format_size(result.get('size', 0))}[/dim]")
|
|
137
|
-
|
|
138
|
-
except typer.Exit:
|
|
139
|
-
raise
|
|
140
|
-
except Exception as e:
|
|
141
|
-
console.print(f"[red]Error: {e}[/red]")
|
|
142
|
-
raise typer.Exit(1)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
@storage_app.command("download")
|
|
146
|
-
def download_file(
|
|
147
|
-
path: str = typer.Argument(..., help="File path in storage to download"),
|
|
148
|
-
output: Path = typer.Option(None, "--output", "-o", help="Local output path (defaults to filename)"),
|
|
149
|
-
):
|
|
150
|
-
"""Download a file from your storage bucket."""
|
|
151
|
-
try:
|
|
152
|
-
config.get_client()
|
|
153
|
-
|
|
154
|
-
# Default output to the filename part of the path
|
|
155
|
-
output_path = output or Path(path.split("/")[-1])
|
|
156
|
-
|
|
157
|
-
console.print(f"[dim]Downloading {path}...[/dim]")
|
|
158
|
-
|
|
159
|
-
response = httpx.get(
|
|
160
|
-
f"{config.base_url}/api/v2/external/storage/download/{path}",
|
|
161
|
-
headers={"Authorization": f"Bearer {config.api_key}"},
|
|
162
|
-
timeout=300.0,
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
if response.status_code == 404:
|
|
166
|
-
console.print(f"[red]Error: File not found: {path}[/red]")
|
|
167
|
-
raise typer.Exit(1)
|
|
168
|
-
|
|
169
|
-
if response.status_code != 200:
|
|
170
|
-
console.print(f"[red]Error: HTTP {response.status_code}[/red]")
|
|
171
|
-
if response.status_code == 401:
|
|
172
|
-
console.print("[yellow]Run 'lyceum auth login' to re-authenticate.[/yellow]")
|
|
173
|
-
else:
|
|
174
|
-
console.print(f"[red]{response.content.decode()}[/red]")
|
|
175
|
-
raise typer.Exit(1)
|
|
176
|
-
|
|
177
|
-
with open(output_path, "wb") as f:
|
|
178
|
-
f.write(response.content)
|
|
179
|
-
|
|
180
|
-
console.print(f"[green]Downloaded successfully![/green]")
|
|
181
|
-
console.print(f"[dim]Saved to: {output_path}[/dim]")
|
|
182
|
-
console.print(f"[dim]Size: {format_size(len(response.content))}[/dim]")
|
|
183
|
-
|
|
184
|
-
except typer.Exit:
|
|
185
|
-
raise
|
|
186
|
-
except Exception as e:
|
|
187
|
-
console.print(f"[red]Error: {e}[/red]")
|
|
188
|
-
raise typer.Exit(1)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
@storage_app.command("delete")
|
|
192
|
-
def delete_file(
|
|
193
|
-
path: str = typer.Argument(..., help="File path in storage to delete"),
|
|
194
|
-
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
195
|
-
):
|
|
196
|
-
"""Delete a file from your storage bucket."""
|
|
197
|
-
try:
|
|
198
|
-
config.get_client()
|
|
199
|
-
|
|
200
|
-
if not force:
|
|
201
|
-
confirm = typer.confirm(f"Delete '{path}'?")
|
|
202
|
-
if not confirm:
|
|
203
|
-
console.print("[dim]Cancelled.[/dim]")
|
|
204
|
-
raise typer.Exit(0)
|
|
205
|
-
|
|
206
|
-
response = httpx.delete(
|
|
207
|
-
f"{config.base_url}/api/v2/external/storage/delete/{path}",
|
|
208
|
-
headers={"Authorization": f"Bearer {config.api_key}"},
|
|
209
|
-
timeout=30.0,
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
if response.status_code == 404:
|
|
213
|
-
console.print(f"[red]Error: File not found: {path}[/red]")
|
|
214
|
-
raise typer.Exit(1)
|
|
215
|
-
|
|
216
|
-
if response.status_code != 200:
|
|
217
|
-
console.print(f"[red]Error: HTTP {response.status_code}[/red]")
|
|
218
|
-
if response.status_code == 401:
|
|
219
|
-
console.print("[yellow]Run 'lyceum auth login' to re-authenticate.[/yellow]")
|
|
220
|
-
else:
|
|
221
|
-
console.print(f"[red]{response.content.decode()}[/red]")
|
|
222
|
-
raise typer.Exit(1)
|
|
223
|
-
|
|
224
|
-
console.print(f"[green]Deleted: {path}[/green]")
|
|
225
|
-
|
|
226
|
-
except typer.Exit:
|
|
227
|
-
raise
|
|
228
|
-
except Exception as e:
|
|
229
|
-
console.print(f"[red]Error: {e}[/red]")
|
|
230
|
-
raise typer.Exit(1)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
@storage_app.command("delete-folder")
|
|
234
|
-
def delete_folder(
|
|
235
|
-
prefix: str = typer.Argument(..., help="Folder prefix to delete"),
|
|
236
|
-
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
237
|
-
):
|
|
238
|
-
"""Delete all files in a folder (by prefix)."""
|
|
239
|
-
try:
|
|
240
|
-
config.get_client()
|
|
241
|
-
|
|
242
|
-
if not force:
|
|
243
|
-
confirm = typer.confirm(f"Delete all files in '{prefix}/'?")
|
|
244
|
-
if not confirm:
|
|
245
|
-
console.print("[dim]Cancelled.[/dim]")
|
|
246
|
-
raise typer.Exit(0)
|
|
247
|
-
|
|
248
|
-
response = httpx.delete(
|
|
249
|
-
f"{config.base_url}/api/v2/external/storage/delete-folder/{prefix}",
|
|
250
|
-
headers={"Authorization": f"Bearer {config.api_key}"},
|
|
251
|
-
timeout=60.0,
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
if response.status_code == 404:
|
|
255
|
-
console.print(f"[yellow]No files found in folder: {prefix}/[/yellow]")
|
|
256
|
-
raise typer.Exit(0)
|
|
257
|
-
|
|
258
|
-
if response.status_code != 200:
|
|
259
|
-
console.print(f"[red]Error: HTTP {response.status_code}[/red]")
|
|
260
|
-
if response.status_code == 401:
|
|
261
|
-
console.print("[yellow]Run 'lyceum auth login' to re-authenticate.[/yellow]")
|
|
262
|
-
else:
|
|
263
|
-
console.print(f"[red]{response.content.decode()}[/red]")
|
|
264
|
-
raise typer.Exit(1)
|
|
265
|
-
|
|
266
|
-
result = response.json()
|
|
267
|
-
console.print(f"[green]{result.get('message')}[/green]")
|
|
268
|
-
|
|
269
|
-
except typer.Exit:
|
|
270
|
-
raise
|
|
271
|
-
except Exception as e:
|
|
272
|
-
console.print(f"[red]Error: {e}[/red]")
|
|
273
|
-
raise typer.Exit(1)
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
lyceum/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
|
-
lyceum/main.py,sha256=jE4P4SvHTeguLKL9wRBfOBxGHzDKxH_PcM46ouY-xnY,921
|
|
3
|
-
lyceum/external/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
4
|
-
lyceum/external/auth/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
5
|
-
lyceum/external/auth/login.py,sha256=T-V6pzi3s0ZynpUUeLnN2y-cxJI4msj5z26S3_wJ1AE,23526
|
|
6
|
-
lyceum/external/compute/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
|
-
lyceum/external/compute/execution/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
8
|
-
lyceum/external/compute/execution/config.py,sha256=6JJgLJnDPTwevEaNdB1nEICih_qbBmws5u5_S9gj7k0,8866
|
|
9
|
-
lyceum/external/compute/execution/docker.py,sha256=lATtJzsmsu9iCUH-ynXZsfrIg-_aCkYE5r2l3DolisQ,9603
|
|
10
|
-
lyceum/external/compute/execution/docker_config.py,sha256=tIiEVlp3yYZxLqhRsBqri7wV9_K0fPm6bvTRhRnKEd4,4071
|
|
11
|
-
lyceum/external/compute/execution/notebook.py,sha256=fbN546-j1Cw88j7jbDKkup8J1xaws0eBYZJgt1NTJVg,7544
|
|
12
|
-
lyceum/external/compute/execution/python.py,sha256=1ekNuF8_j-YG0-oNFP3C6Jjyw7IUlIngvL06C1usyu8,12864
|
|
13
|
-
lyceum/external/compute/execution/workloads.py,sha256=4fsRWbYGmsQMGPPIN1jUG8cG5NPG9yV26ANJ-DtaXqc,5844
|
|
14
|
-
lyceum/external/compute/inference/__init__.py,sha256=4YLoUKDEzitexynJv_Q5O0w1lty8CJ6uyRxuc1LiaBw,89
|
|
15
|
-
lyceum/external/compute/inference/batch.py,sha256=HWwS6XHDZOG7QftNTNGvzao8Y6DzkEOdpOAZAEZyav4,11438
|
|
16
|
-
lyceum/external/compute/inference/chat.py,sha256=hITj_UGLaxCJQskU-YbeaEerM5Xt_eJpEsYrTJoUpk4,8485
|
|
17
|
-
lyceum/external/compute/inference/models.py,sha256=BkCEdvyliezGOUulj557e-Eoif0_HKR3CxqpEhdAZaA,10339
|
|
18
|
-
lyceum/external/general/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
19
|
-
lyceum/external/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
lyceum/external/storage/files.py,sha256=WdSp6t90jLzChOkg2uI4rZ5pq6bmHDCSo7CDUDVXWyk,9311
|
|
21
|
-
lyceum/shared/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
22
|
-
lyceum/shared/config.py,sha256=mZXbdwKxbkRA6RCnTPruaNWtLOaheYO4MnCxXTFNLu8,5548
|
|
23
|
-
lyceum/shared/display.py,sha256=n9QQy-JVvoAgzss9D9cGfdw7GUY6t9qTYWr81v9q1fk,4855
|
|
24
|
-
lyceum/shared/imports.py,sha256=wEG4wfVTIqJ6MBWDRAN96iGmVCb9ST2aOqSjkbvajug,11768
|
|
25
|
-
lyceum/shared/streaming.py,sha256=wFb7w7fra63y8WWaIA8_E1Z6Sx_6G-0J53Zh010eZgk,9355
|
|
26
|
-
lyceum_cloud_execution_api_client/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
27
|
-
lyceum_cloud_execution_api_client/api/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
28
|
-
lyceum_cloud_execution_api_client/models/__init__.py,sha256=AMlb9R9O9aNC9hvKz_8TFpEfOolYC3VtFS5JX17kYks,4888
|
|
29
|
-
tests/__init__.py,sha256=yuZY5OKj5n7_jxuEuXtUjl77XCIopOxfiz7s_leAYPc,24
|
|
30
|
-
tests/conftest.py,sha256=TwnnolWULDaAyCygQLj595TKreS1p2y-ezeN0b2WWX8,5052
|
|
31
|
-
tests/unit/__init__.py,sha256=_w8WnsodwDTzGEx1Xtj1xjihh_VCUevi4LA4tOdW_Uw,33
|
|
32
|
-
tests/unit/external/__init__.py,sha256=7lGFpul2lNMPJVPV1Jd27wzpsgb9EZwwa6q8fixUFLY,39
|
|
33
|
-
tests/unit/external/compute/__init__.py,sha256=hf16EmvKpT4P0iw292Z0xCRswKqcU8PteT4Zr1sgRLo,38
|
|
34
|
-
tests/unit/external/compute/execution/__init__.py,sha256=eUpROWmXCsK9ZaImYMw9iIDiQUIxj5HCrd06SyjE0Vg,40
|
|
35
|
-
tests/unit/external/compute/execution/test_data.py,sha256=49p19xquOlmm7jKwCr6NQLooG9MpK8GkGQ3PwHrVuj0,856
|
|
36
|
-
tests/unit/external/compute/execution/test_dependency_resolver.py,sha256=2X0Lan8C4av7QDeHpjZxk_RlTB1C6GzU1Gv5G0WYnSg,9479
|
|
37
|
-
tests/unit/external/compute/execution/test_python_helpers.py,sha256=KyeTX0ps7L0x7xBSslwiQHiGa3oriX_vcqgkcJvbcm4,13421
|
|
38
|
-
tests/unit/external/compute/execution/test_python_run.py,sha256=fKuWyTVshC3iGvoWfQmeNMi0N-46iCC_Zm2-lq-n-Ro,10353
|
|
39
|
-
tests/unit/shared/__init__.py,sha256=ouZueovCsCdgfuKoMP1Uh69E3fmn0U8ltllHWhXhpgg,39
|
|
40
|
-
tests/unit/shared/test_config.py,sha256=rokbpv_S9J-aUtg41eddn0ZuVWyTYZNXO9VtCZdXstU,11315
|
|
41
|
-
tests/unit/shared/test_streaming.py,sha256=sFjkla5SvaWWPi42lQ6vHSTor_htLMMbBttnCyGsB98,8706
|
|
42
|
-
lyceum_cli-1.0.25.dist-info/METADATA,sha256=mC4h00m-5ihgHqd9gdNoIpET5Ok0EQv5cAqLKaVLc2c,1482
|
|
43
|
-
lyceum_cli-1.0.25.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
44
|
-
lyceum_cli-1.0.25.dist-info/entry_points.txt,sha256=Oq-9wDkxVd6MHgNiUTYwXI9SGhvR3VkD7Mvk0xhiUZo,43
|
|
45
|
-
lyceum_cli-1.0.25.dist-info/top_level.txt,sha256=-546wowhLvi8w6Gef9vwO0T1i0ME2PanTrgC0YX29y4,47
|
|
46
|
-
lyceum_cli-1.0.25.dist-info/RECORD,,
|
tests/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Lyceum CLI tests."""
|
tests/conftest.py
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
"""Shared pytest fixtures for CLI tests."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
|
7
|
-
|
|
8
|
-
import pytest
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# --- Basic Fixtures ---
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@pytest.fixture
|
|
15
|
-
def mock_execution_id() -> str:
|
|
16
|
-
"""Consistent execution ID for testing."""
|
|
17
|
-
return "550e8400-e29b-41d4-a716-446655440000"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@pytest.fixture
|
|
21
|
-
def mock_streaming_url(mock_execution_id) -> str:
|
|
22
|
-
"""Mock streaming URL."""
|
|
23
|
-
return f"https://api.lyceum.dev/stream/{mock_execution_id}"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# --- Config Fixtures ---
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@pytest.fixture
|
|
30
|
-
def mock_config_data() -> dict:
|
|
31
|
-
"""Mock config file data."""
|
|
32
|
-
return {
|
|
33
|
-
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjk5OTk5OTk5OTl9.test",
|
|
34
|
-
"refresh_token": "test-refresh-token",
|
|
35
|
-
"base_url": "https://api.lyceum.dev",
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@pytest.fixture
|
|
40
|
-
def setup_config_file(tmp_path, mock_config_data):
|
|
41
|
-
"""Setup mock config file in tmp directory."""
|
|
42
|
-
|
|
43
|
-
def _setup(config_data: dict = None):
|
|
44
|
-
config_dir = tmp_path / ".lyceum"
|
|
45
|
-
config_dir.mkdir(parents=True, exist_ok=True)
|
|
46
|
-
config_file = config_dir / "config.json"
|
|
47
|
-
config_file.write_text(json.dumps(config_data or mock_config_data))
|
|
48
|
-
return config_file
|
|
49
|
-
|
|
50
|
-
return _setup
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# --- HTTP Response Fixtures (following PR #502 pattern) ---
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@pytest.fixture
|
|
57
|
-
def setup_httpx_response():
|
|
58
|
-
"""Setup mock for httpx response."""
|
|
59
|
-
|
|
60
|
-
def _setup(status_code: int = 200, json_data: dict = None, text: str = "OK"):
|
|
61
|
-
mock_response = MagicMock()
|
|
62
|
-
mock_response.status_code = status_code
|
|
63
|
-
mock_response.json.return_value = json_data or {}
|
|
64
|
-
mock_response.text = text
|
|
65
|
-
mock_response.content = text.encode()
|
|
66
|
-
return mock_response
|
|
67
|
-
|
|
68
|
-
return _setup
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@pytest.fixture
|
|
72
|
-
def setup_httpx_post(setup_httpx_response):
|
|
73
|
-
"""Setup mock for httpx.post calls."""
|
|
74
|
-
|
|
75
|
-
def _setup(status_code: int = 200, execution_id: str = "test-exec-id"):
|
|
76
|
-
response = setup_httpx_response(
|
|
77
|
-
status_code=status_code,
|
|
78
|
-
json_data={
|
|
79
|
-
"execution_id": execution_id,
|
|
80
|
-
"streaming_url": f"https://api.lyceum.dev/stream/{execution_id}",
|
|
81
|
-
"status": "queued",
|
|
82
|
-
},
|
|
83
|
-
)
|
|
84
|
-
return response
|
|
85
|
-
|
|
86
|
-
return _setup
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@pytest.fixture
|
|
90
|
-
def setup_httpx_stream():
|
|
91
|
-
"""Setup mock for httpx.stream SSE responses."""
|
|
92
|
-
|
|
93
|
-
def _setup(events: list[dict], status_code: int = 200):
|
|
94
|
-
mock_response = MagicMock()
|
|
95
|
-
mock_response.status_code = status_code
|
|
96
|
-
mock_response.iter_lines.return_value = [
|
|
97
|
-
f"data: {json.dumps(event)}" for event in events
|
|
98
|
-
]
|
|
99
|
-
|
|
100
|
-
mock_context = MagicMock()
|
|
101
|
-
mock_context.__enter__.return_value = mock_response
|
|
102
|
-
mock_context.__exit__.return_value = None
|
|
103
|
-
|
|
104
|
-
return mock_context
|
|
105
|
-
|
|
106
|
-
return _setup
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
# --- Machine/Quota Fixtures ---
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
@pytest.fixture
|
|
113
|
-
def setup_available_machines():
|
|
114
|
-
"""Setup mock for available machines API response."""
|
|
115
|
-
|
|
116
|
-
def _setup(machines: list[str] = None):
|
|
117
|
-
machines = machines or ["cpu", "a100", "h100"]
|
|
118
|
-
mock_response = MagicMock()
|
|
119
|
-
mock_response.status_code = 200
|
|
120
|
-
mock_response.json.return_value = {
|
|
121
|
-
"hardware_profiles": [{"hardware_profile": m} for m in machines]
|
|
122
|
-
}
|
|
123
|
-
return mock_response
|
|
124
|
-
|
|
125
|
-
return _setup
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
# --- File System Fixtures ---
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@pytest.fixture
|
|
132
|
-
def sample_workspace(tmp_path):
|
|
133
|
-
"""Create a sample workspace with Python files and packages."""
|
|
134
|
-
workspace = tmp_path / "workspace"
|
|
135
|
-
workspace.mkdir()
|
|
136
|
-
|
|
137
|
-
# Main file
|
|
138
|
-
main_py = workspace / "main.py"
|
|
139
|
-
main_py.write_text(
|
|
140
|
-
'''"""Main script"""
|
|
141
|
-
import numpy as np
|
|
142
|
-
from mypackage import helper_function
|
|
143
|
-
from standalone import standalone_func
|
|
144
|
-
|
|
145
|
-
def main():
|
|
146
|
-
print(helper_function())
|
|
147
|
-
print(standalone_func())
|
|
148
|
-
|
|
149
|
-
if __name__ == "__main__":
|
|
150
|
-
main()
|
|
151
|
-
'''
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
# Package
|
|
155
|
-
pkg = workspace / "mypackage"
|
|
156
|
-
pkg.mkdir()
|
|
157
|
-
(pkg / "__init__.py").write_text(
|
|
158
|
-
'''"""My package"""
|
|
159
|
-
from .utils import helper_function
|
|
160
|
-
'''
|
|
161
|
-
)
|
|
162
|
-
(pkg / "utils.py").write_text(
|
|
163
|
-
'''"""Utils module"""
|
|
164
|
-
def helper_function():
|
|
165
|
-
return "Hello from utils"
|
|
166
|
-
'''
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
# Standalone module
|
|
170
|
-
(workspace / "standalone.py").write_text(
|
|
171
|
-
'''"""Standalone module"""
|
|
172
|
-
def standalone_func():
|
|
173
|
-
return "Hello from standalone"
|
|
174
|
-
'''
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
# Requirements file
|
|
178
|
-
(workspace / "requirements.txt").write_text("numpy==1.24.0\n")
|
|
179
|
-
|
|
180
|
-
return workspace
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
@pytest.fixture
|
|
184
|
-
def sample_workspace_config(sample_workspace):
|
|
185
|
-
"""Create .lyceum/config.json in sample workspace."""
|
|
186
|
-
|
|
187
|
-
def _setup(config_data: dict = None):
|
|
188
|
-
lyceum_dir = sample_workspace / ".lyceum"
|
|
189
|
-
lyceum_dir.mkdir()
|
|
190
|
-
config_file = lyceum_dir / "config.json"
|
|
191
|
-
|
|
192
|
-
default_config = {
|
|
193
|
-
"workspace": str(sample_workspace),
|
|
194
|
-
"dependencies": {"merged": ["numpy==1.24.0"]},
|
|
195
|
-
"local_packages": ["mypackage"],
|
|
196
|
-
}
|
|
197
|
-
config_file.write_text(json.dumps(config_data or default_config))
|
|
198
|
-
return config_file
|
|
199
|
-
|
|
200
|
-
return _setup
|
tests/unit/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Unit tests for Lyceum CLI."""
|
tests/unit/external/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Unit tests for external modules."""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Unit tests for compute modules."""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Unit tests for execution modules."""
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"""Shared test data for CLI tests."""
|
|
2
|
-
|
|
3
|
-
VALID_EXECUTION_PAYLOAD = {
|
|
4
|
-
"code": "print('hello')",
|
|
5
|
-
"execution_type": "cpu",
|
|
6
|
-
"timeout": 60,
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
VALID_EXECUTION_WITH_REQUIREMENTS = {
|
|
10
|
-
**VALID_EXECUTION_PAYLOAD,
|
|
11
|
-
"requirements_content": "numpy==1.24.0",
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
VALID_EXECUTION_WITH_IMPORTS = {
|
|
15
|
-
**VALID_EXECUTION_PAYLOAD,
|
|
16
|
-
"import_files": '{"mypackage/__init__.py": "# init", "mypackage/utils.py": "def foo(): pass"}',
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
SAMPLE_SSE_OUTPUT_EVENT = {"output": {"content": "Hello, World!\n"}}
|
|
20
|
-
|
|
21
|
-
SAMPLE_SSE_JOB_FINISHED = {
|
|
22
|
-
"jobFinished": {"job": {"result": {"returnCode": "0"}}}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
SAMPLE_SSE_JOB_FAILED = {
|
|
26
|
-
"jobFinished": {"job": {"result": {"returnCode": "1"}}}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
SAMPLE_WORKSPACE_CONFIG = {
|
|
30
|
-
"workspace": "/path/to/workspace",
|
|
31
|
-
"dependencies": {"merged": ["numpy==1.24.0", "pandas>=2.0.0"]},
|
|
32
|
-
"local_packages": ["mypackage"],
|
|
33
|
-
}
|