lyceum-cli 1.0.24__py3-none-any.whl → 1.0.26__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_compose.py +263 -0
- lyceum/external/compute/execution/notebook.py +240 -0
- lyceum/external/compute/inference/batch.py +8 -10
- lyceum/external/vms/__init__.py +0 -0
- lyceum/external/vms/instances.py +301 -0
- lyceum/external/vms/management.py +383 -0
- lyceum/main.py +5 -0
- lyceum/shared/config.py +19 -24
- lyceum/shared/display.py +12 -31
- lyceum/shared/streaming.py +17 -45
- {lyceum_cli-1.0.24.dist-info → lyceum_cli-1.0.26.dist-info}/METADATA +1 -1
- lyceum_cli-1.0.26.dist-info/RECORD +34 -0
- {lyceum_cli-1.0.24.dist-info → lyceum_cli-1.0.26.dist-info}/WHEEL +1 -1
- {lyceum_cli-1.0.24.dist-info → lyceum_cli-1.0.26.dist-info}/top_level.txt +0 -1
- lyceum_cli-1.0.24.dist-info/RECORD +0 -42
- 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_cli-1.0.24.dist-info → lyceum_cli-1.0.26.dist-info}/entry_points.txt +0 -0
lyceum/main.py
CHANGED
|
@@ -11,7 +11,10 @@ from rich.console import Console
|
|
|
11
11
|
from .external.auth.login import auth_app
|
|
12
12
|
from .external.compute.execution.python import python_app
|
|
13
13
|
from .external.compute.execution.docker import docker_app
|
|
14
|
+
from .external.compute.execution.docker_compose import compose_app
|
|
14
15
|
from .external.compute.execution.workloads import workloads_app
|
|
16
|
+
from .external.compute.execution.notebook import notebook_app
|
|
17
|
+
from .external.vms.management import vms_app
|
|
15
18
|
|
|
16
19
|
app = typer.Typer(
|
|
17
20
|
name="lyceum",
|
|
@@ -25,7 +28,9 @@ console = Console()
|
|
|
25
28
|
app.add_typer(auth_app, name="auth")
|
|
26
29
|
app.add_typer(python_app, name="python")
|
|
27
30
|
app.add_typer(docker_app, name="docker")
|
|
31
|
+
app.add_typer(compose_app, name="compose")
|
|
28
32
|
app.add_typer(workloads_app, name="workloads")
|
|
33
|
+
app.add_typer(notebook_app, name="notebook")
|
|
29
34
|
|
|
30
35
|
|
|
31
36
|
|
lyceum/shared/config.py
CHANGED
|
@@ -1,32 +1,28 @@
|
|
|
1
1
|
"""Configuration management for Lyceum CLI"""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
|
|
5
|
-
# Add the generated client to the path
|
|
6
|
-
import sys
|
|
4
|
+
import os
|
|
7
5
|
import time
|
|
8
6
|
from pathlib import Path
|
|
9
7
|
|
|
10
8
|
import jwt
|
|
11
9
|
import typer
|
|
12
10
|
from rich.console import Console
|
|
13
|
-
|
|
14
11
|
from supabase import create_client
|
|
15
12
|
|
|
16
|
-
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "lyceum-cloud-execution-api-client"))
|
|
17
|
-
|
|
18
|
-
# Commented out - using httpx directly instead
|
|
19
|
-
# from lyceum_cloud_execution_api_client.client import AuthenticatedClient
|
|
20
|
-
|
|
21
13
|
console = Console()
|
|
22
14
|
|
|
23
15
|
# Configuration
|
|
24
16
|
CONFIG_DIR = Path.home() / ".lyceum"
|
|
25
17
|
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
26
18
|
|
|
19
|
+
# Supabase configuration from environment variables (required)
|
|
20
|
+
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
|
21
|
+
SUPABASE_ANON_KEY = os.getenv("SUPABASE_ANON_KEY")
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
|
|
24
|
+
class _Config:
|
|
25
|
+
"""Configuration management for Lyceum CLI (private - use the global 'config' instance)"""
|
|
30
26
|
|
|
31
27
|
def __init__(self):
|
|
32
28
|
"""Initialize configuration with default values"""
|
|
@@ -92,17 +88,16 @@ class Config:
|
|
|
92
88
|
return False
|
|
93
89
|
|
|
94
90
|
try:
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
supabase = create_client(supabase_url, supabase_service_key)
|
|
91
|
+
# Check if Supabase credentials are configured
|
|
92
|
+
if not SUPABASE_URL or not SUPABASE_ANON_KEY:
|
|
93
|
+
console.print(
|
|
94
|
+
"[red]Error: SUPABASE_URL and SUPABASE_ANON_KEY environment variables must be set[/red]"
|
|
95
|
+
)
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
# Use Supabase's refresh_session method with the public anon key
|
|
99
|
+
# This is safe for client-side use and properly scoped for user token refresh
|
|
100
|
+
supabase = create_client(SUPABASE_URL, SUPABASE_ANON_KEY)
|
|
106
101
|
|
|
107
102
|
# Use refresh_session method
|
|
108
103
|
response = supabase.auth.refresh_session(refresh_token=self.refresh_token)
|
|
@@ -131,7 +126,7 @@ class Config:
|
|
|
131
126
|
def get_client(self):
|
|
132
127
|
"""Get authenticated API client with automatic token refresh"""
|
|
133
128
|
if not self.api_key:
|
|
134
|
-
console.print("[red]Error: Not authenticated. Run 'lyceum login' first.[/red]")
|
|
129
|
+
console.print("[red]Error: Not authenticated. Run 'lyceum auth login' first.[/red]")
|
|
135
130
|
raise typer.Exit(1)
|
|
136
131
|
|
|
137
132
|
# Check if token is expired and try to refresh
|
|
@@ -146,4 +141,4 @@ class Config:
|
|
|
146
141
|
|
|
147
142
|
|
|
148
143
|
# Global config instance
|
|
149
|
-
config =
|
|
144
|
+
config = _Config()
|
lyceum/shared/display.py
CHANGED
|
@@ -2,25 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
|
|
5
|
-
from rich.table import Table
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def create_table(title: str, columns: list[tuple[str, str]]) -> Table:
|
|
9
|
-
"""
|
|
10
|
-
Create a Rich table with the given title and columns.
|
|
11
|
-
|
|
12
|
-
Args:
|
|
13
|
-
title: Table title
|
|
14
|
-
columns: List of (column_name, style) tuples
|
|
15
|
-
|
|
16
|
-
Returns:
|
|
17
|
-
Rich Table instance
|
|
18
|
-
"""
|
|
19
|
-
table = Table(title=title)
|
|
20
|
-
for col_name, style in columns:
|
|
21
|
-
table.add_column(col_name, style=style)
|
|
22
|
-
return table
|
|
23
|
-
|
|
24
5
|
|
|
25
6
|
def format_timestamp(timestamp: str | int | datetime, relative: bool = False) -> str:
|
|
26
7
|
"""
|
|
@@ -161,33 +142,33 @@ def status_color(status: str) -> str:
|
|
|
161
142
|
return 'white'
|
|
162
143
|
|
|
163
144
|
|
|
164
|
-
def
|
|
145
|
+
def status_icon(status: str) -> str:
|
|
165
146
|
"""
|
|
166
|
-
Get
|
|
147
|
+
Get text icon for status display.
|
|
167
148
|
|
|
168
149
|
Args:
|
|
169
150
|
status: Status string
|
|
170
151
|
|
|
171
152
|
Returns:
|
|
172
|
-
|
|
153
|
+
Text-based status indicator
|
|
173
154
|
"""
|
|
174
155
|
status_lower = status.lower()
|
|
175
156
|
|
|
176
157
|
if status_lower in ['completed', 'success']:
|
|
177
|
-
return '
|
|
158
|
+
return '[OK]'
|
|
178
159
|
elif status_lower in ['failed', 'error', 'failed_user', 'failed_system']:
|
|
179
|
-
return '
|
|
160
|
+
return '[FAIL]'
|
|
180
161
|
elif status_lower in ['running', 'in_progress']:
|
|
181
|
-
return '
|
|
162
|
+
return '[RUN]'
|
|
182
163
|
elif status_lower in ['pending', 'queued']:
|
|
183
|
-
return '
|
|
164
|
+
return '[WAIT]'
|
|
184
165
|
elif status_lower in ['validating']:
|
|
185
|
-
return '
|
|
166
|
+
return '[VAL]'
|
|
186
167
|
elif status_lower in ['finalizing']:
|
|
187
|
-
return '
|
|
168
|
+
return '[FIN]'
|
|
188
169
|
elif status_lower in ['cancelled']:
|
|
189
|
-
return '
|
|
170
|
+
return '[STOP]'
|
|
190
171
|
elif status_lower in ['expired', 'timeout']:
|
|
191
|
-
return '
|
|
172
|
+
return '[TIME]'
|
|
192
173
|
else:
|
|
193
|
-
return '
|
|
174
|
+
return '[--]'
|
lyceum/shared/streaming.py
CHANGED
|
@@ -60,7 +60,7 @@ def normalize_newlines(text: str) -> str:
|
|
|
60
60
|
return re.sub(r'\n{2,}', '\n', text)
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
def stream_execution_output(execution_id: str, streaming_url: str = None
|
|
63
|
+
def stream_execution_output(execution_id: str, streaming_url: str = None) -> bool:
|
|
64
64
|
"""Stream execution output in real-time. Returns True if successful, False if failed."""
|
|
65
65
|
if not streaming_url:
|
|
66
66
|
# Fallback to stream endpoint if no streaming URL provided
|
|
@@ -69,25 +69,15 @@ def stream_execution_output(execution_id: str, streaming_url: str = None, status
|
|
|
69
69
|
stream_url = streaming_url
|
|
70
70
|
|
|
71
71
|
try:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
headers =
|
|
76
|
-
"Accept": "text/event-stream",
|
|
77
|
-
"Cache-Control": "no-cache",
|
|
78
|
-
}
|
|
79
|
-
with httpx.stream("POST", stream_url, headers=headers, timeout=600.0) as response:
|
|
72
|
+
console.print("[dim]Connecting to execution stream...[/dim]")
|
|
73
|
+
|
|
74
|
+
headers = {"Authorization": f"Bearer {config.api_key}"}
|
|
75
|
+
with httpx.stream("GET", stream_url, headers=headers, timeout=600.0) as response:
|
|
80
76
|
if response.status_code != 200:
|
|
81
|
-
|
|
82
|
-
status.stop()
|
|
83
|
-
if response.status_code == 404:
|
|
84
|
-
console.print("[yellow]Stream not found - execution may have already completed[/yellow]")
|
|
85
|
-
else:
|
|
86
|
-
console.print(f"[red]Stream failed: HTTP {response.status_code}[/red]")
|
|
77
|
+
console.print(f"[red]Stream failed: HTTP {response.status_code}[/red]")
|
|
87
78
|
return False
|
|
88
79
|
|
|
89
|
-
|
|
90
|
-
status.update("Waiting for output...")
|
|
80
|
+
console.print("[dim]Streaming output...[/dim]")
|
|
91
81
|
|
|
92
82
|
first_output = True
|
|
93
83
|
for line in response.iter_lines():
|
|
@@ -103,10 +93,6 @@ def stream_execution_output(execution_id: str, streaming_url: str = None, status
|
|
|
103
93
|
output_data = data["output"]
|
|
104
94
|
content = output_data.get("content", "")
|
|
105
95
|
if content:
|
|
106
|
-
# Stop status spinner on first output
|
|
107
|
-
if first_output and status:
|
|
108
|
-
status.stop()
|
|
109
|
-
first_output = False
|
|
110
96
|
clean_output = strip_ansi_codes(content)
|
|
111
97
|
# Normalize newlines to avoid excessive blank lines
|
|
112
98
|
clean_output = normalize_newlines(clean_output)
|
|
@@ -114,8 +100,6 @@ def stream_execution_output(execution_id: str, streaming_url: str = None, status
|
|
|
114
100
|
|
|
115
101
|
# Handle job finished event
|
|
116
102
|
elif "jobFinished" in data:
|
|
117
|
-
if status:
|
|
118
|
-
status.stop()
|
|
119
103
|
job_data = data["jobFinished"]
|
|
120
104
|
job = job_data.get("job", {})
|
|
121
105
|
result = job.get("result", {})
|
|
@@ -126,7 +110,6 @@ def stream_execution_output(execution_id: str, streaming_url: str = None, status
|
|
|
126
110
|
if not first_output:
|
|
127
111
|
print()
|
|
128
112
|
|
|
129
|
-
# Check for system failure (error field present)
|
|
130
113
|
if error:
|
|
131
114
|
console.print(f"[red]{error}[/red]")
|
|
132
115
|
return False
|
|
@@ -143,46 +126,34 @@ def stream_execution_output(execution_id: str, streaming_url: str = None, status
|
|
|
143
126
|
if event_type == "output":
|
|
144
127
|
output = data.get("content", "")
|
|
145
128
|
if output:
|
|
146
|
-
if first_output and status:
|
|
147
|
-
status.stop()
|
|
148
|
-
first_output = False
|
|
149
129
|
clean_output = strip_ansi_codes(output)
|
|
150
130
|
clean_output = normalize_newlines(clean_output)
|
|
151
131
|
print(clean_output, end="", flush=True)
|
|
152
132
|
|
|
153
133
|
elif event_type == "completed":
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if not first_output:
|
|
158
|
-
print()
|
|
159
|
-
if status_val == "completed":
|
|
134
|
+
status = data.get("status", "unknown")
|
|
135
|
+
if status == "completed":
|
|
136
|
+
console.print("\n[green]Execution completed successfully[/green]")
|
|
160
137
|
return True
|
|
161
138
|
else:
|
|
162
|
-
console.print(f"[red]
|
|
139
|
+
console.print(f"\n[red]Execution failed: {status}[/red]")
|
|
163
140
|
return False
|
|
164
141
|
|
|
165
142
|
elif event_type == "error":
|
|
166
|
-
if status:
|
|
167
|
-
status.stop()
|
|
168
143
|
error_msg = data.get("message", "Unknown error")
|
|
169
|
-
console.print(f"[red]Error: {error_msg}[/red]")
|
|
144
|
+
console.print(f"\n[red]Error: {error_msg}[/red]")
|
|
170
145
|
return False
|
|
171
146
|
|
|
172
147
|
except json.JSONDecodeError:
|
|
173
148
|
# Skip malformed JSON
|
|
174
149
|
continue
|
|
175
150
|
|
|
176
|
-
|
|
177
|
-
status.stop()
|
|
178
|
-
console.print("[yellow]Stream ended without completion signal[/yellow]")
|
|
151
|
+
console.print("\n[yellow]Stream ended without completion signal[/yellow]")
|
|
179
152
|
# Fallback: poll execution status
|
|
180
153
|
return check_execution_status(execution_id)
|
|
181
154
|
|
|
182
155
|
except Exception as e:
|
|
183
|
-
|
|
184
|
-
status.stop()
|
|
185
|
-
console.print(f"[red]Streaming error: {e}[/red]")
|
|
156
|
+
console.print(f"\n[red]Streaming error: {e}[/red]")
|
|
186
157
|
# Fallback: poll execution status
|
|
187
158
|
return check_execution_status(execution_id)
|
|
188
159
|
|
|
@@ -206,15 +177,16 @@ def check_execution_status(execution_id: str) -> bool:
|
|
|
206
177
|
status = data.get('status', 'unknown')
|
|
207
178
|
|
|
208
179
|
if status == 'completed':
|
|
180
|
+
console.print("[green]Execution completed successfully[/green]")
|
|
209
181
|
return True
|
|
210
182
|
elif status in ['failed_user', 'failed_system', 'failed']:
|
|
211
|
-
console.print(f"[red]
|
|
183
|
+
console.print(f"[red]Execution failed: {status}[/red]")
|
|
212
184
|
errors = data.get('errors')
|
|
213
185
|
if errors:
|
|
214
186
|
console.print(f"[red]Error: {errors}[/red]")
|
|
215
187
|
return False
|
|
216
188
|
elif status in ['timeout', 'cancelled']:
|
|
217
|
-
console.print(f"[yellow]{status
|
|
189
|
+
console.print(f"[yellow]Execution {status}[/yellow]")
|
|
218
190
|
return False
|
|
219
191
|
elif status in ['running', 'pending', 'queued']:
|
|
220
192
|
# Still running, continue polling
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
lyceum/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
|
+
lyceum/main.py,sha256=s4ZvNPGMeGoV9AIq68hEwfo9ioFfu2qIoRXb2upjNDk,1076
|
|
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=-yJ0aEV8_vDXiT6BXzjpqZ2uDdnTnkop4qhagw2dSZA,23447
|
|
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_compose.py,sha256=YsWPnw5nB1ZpqjU9X8o_klT78I5m46PapVwVEeWra_8,9189
|
|
11
|
+
lyceum/external/compute/execution/notebook.py,sha256=Gw9UhJ-UjYhpjdIYQ4IMYhVjhSkAFpOQ9aFYj1qOeww,7542
|
|
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=mgEndr02UM1j00o-iRLUpDqS5KFvyg0Htc0Gg0s3hTU,11394
|
|
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/vms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
lyceum/external/vms/instances.py,sha256=8DKpI8PbyZFzk5RT-IPgoMDjkf_-HC-2pJKuSFs-5BA,11007
|
|
21
|
+
lyceum/external/vms/management.py,sha256=dYEkN5Qiur-SG4G5CLOk2Rbr0HW3rK1BROSp0K6KxC8,15405
|
|
22
|
+
lyceum/shared/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
23
|
+
lyceum/shared/config.py,sha256=gz2AjjdOsi6WbJApIh_GYRs_GYjsORQ6h3YJiHEIOxI,5384
|
|
24
|
+
lyceum/shared/display.py,sha256=-VSAfoa0yivTvxRrN2RYr2Sq1x_msZqENjnkSedmbhQ,4444
|
|
25
|
+
lyceum/shared/imports.py,sha256=wEG4wfVTIqJ6MBWDRAN96iGmVCb9ST2aOqSjkbvajug,11768
|
|
26
|
+
lyceum/shared/streaming.py,sha256=x3zA8Pn9ia06t8nKJfP6hztxOVKPUC3Nk3qmAiIsl9M,8194
|
|
27
|
+
lyceum_cloud_execution_api_client/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
28
|
+
lyceum_cloud_execution_api_client/api/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
29
|
+
lyceum_cloud_execution_api_client/models/__init__.py,sha256=AMlb9R9O9aNC9hvKz_8TFpEfOolYC3VtFS5JX17kYks,4888
|
|
30
|
+
lyceum_cli-1.0.26.dist-info/METADATA,sha256=dqSFAXN9qNAOm6OnfGegeH8hBmszMkL-85nSDfAOIHA,1482
|
|
31
|
+
lyceum_cli-1.0.26.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
32
|
+
lyceum_cli-1.0.26.dist-info/entry_points.txt,sha256=Oq-9wDkxVd6MHgNiUTYwXI9SGhvR3VkD7Mvk0xhiUZo,43
|
|
33
|
+
lyceum_cli-1.0.26.dist-info/top_level.txt,sha256=CR7FEMloAXgLsHUR6ti3mWNcpgje27HRHSfq8doIils,41
|
|
34
|
+
lyceum_cli-1.0.26.dist-info/RECORD,,
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
lyceum/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
|
-
lyceum/main.py,sha256=YsGAjWCrRcEofRnMyHj55FyoOQG6L1-OgMiD5Jn7YzU,814
|
|
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/python.py,sha256=1ekNuF8_j-YG0-oNFP3C6Jjyw7IUlIngvL06C1usyu8,12864
|
|
11
|
-
lyceum/external/compute/execution/workloads.py,sha256=4fsRWbYGmsQMGPPIN1jUG8cG5NPG9yV26ANJ-DtaXqc,5844
|
|
12
|
-
lyceum/external/compute/inference/__init__.py,sha256=4YLoUKDEzitexynJv_Q5O0w1lty8CJ6uyRxuc1LiaBw,89
|
|
13
|
-
lyceum/external/compute/inference/batch.py,sha256=HWwS6XHDZOG7QftNTNGvzao8Y6DzkEOdpOAZAEZyav4,11438
|
|
14
|
-
lyceum/external/compute/inference/chat.py,sha256=hITj_UGLaxCJQskU-YbeaEerM5Xt_eJpEsYrTJoUpk4,8485
|
|
15
|
-
lyceum/external/compute/inference/models.py,sha256=BkCEdvyliezGOUulj557e-Eoif0_HKR3CxqpEhdAZaA,10339
|
|
16
|
-
lyceum/external/general/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
17
|
-
lyceum/shared/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
18
|
-
lyceum/shared/config.py,sha256=mZXbdwKxbkRA6RCnTPruaNWtLOaheYO4MnCxXTFNLu8,5548
|
|
19
|
-
lyceum/shared/display.py,sha256=n9QQy-JVvoAgzss9D9cGfdw7GUY6t9qTYWr81v9q1fk,4855
|
|
20
|
-
lyceum/shared/imports.py,sha256=wEG4wfVTIqJ6MBWDRAN96iGmVCb9ST2aOqSjkbvajug,11768
|
|
21
|
-
lyceum/shared/streaming.py,sha256=wFb7w7fra63y8WWaIA8_E1Z6Sx_6G-0J53Zh010eZgk,9355
|
|
22
|
-
lyceum_cloud_execution_api_client/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
23
|
-
lyceum_cloud_execution_api_client/api/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
24
|
-
lyceum_cloud_execution_api_client/models/__init__.py,sha256=AMlb9R9O9aNC9hvKz_8TFpEfOolYC3VtFS5JX17kYks,4888
|
|
25
|
-
tests/__init__.py,sha256=yuZY5OKj5n7_jxuEuXtUjl77XCIopOxfiz7s_leAYPc,24
|
|
26
|
-
tests/conftest.py,sha256=TwnnolWULDaAyCygQLj595TKreS1p2y-ezeN0b2WWX8,5052
|
|
27
|
-
tests/unit/__init__.py,sha256=_w8WnsodwDTzGEx1Xtj1xjihh_VCUevi4LA4tOdW_Uw,33
|
|
28
|
-
tests/unit/external/__init__.py,sha256=7lGFpul2lNMPJVPV1Jd27wzpsgb9EZwwa6q8fixUFLY,39
|
|
29
|
-
tests/unit/external/compute/__init__.py,sha256=hf16EmvKpT4P0iw292Z0xCRswKqcU8PteT4Zr1sgRLo,38
|
|
30
|
-
tests/unit/external/compute/execution/__init__.py,sha256=eUpROWmXCsK9ZaImYMw9iIDiQUIxj5HCrd06SyjE0Vg,40
|
|
31
|
-
tests/unit/external/compute/execution/test_data.py,sha256=49p19xquOlmm7jKwCr6NQLooG9MpK8GkGQ3PwHrVuj0,856
|
|
32
|
-
tests/unit/external/compute/execution/test_dependency_resolver.py,sha256=2X0Lan8C4av7QDeHpjZxk_RlTB1C6GzU1Gv5G0WYnSg,9479
|
|
33
|
-
tests/unit/external/compute/execution/test_python_helpers.py,sha256=KyeTX0ps7L0x7xBSslwiQHiGa3oriX_vcqgkcJvbcm4,13421
|
|
34
|
-
tests/unit/external/compute/execution/test_python_run.py,sha256=fKuWyTVshC3iGvoWfQmeNMi0N-46iCC_Zm2-lq-n-Ro,10353
|
|
35
|
-
tests/unit/shared/__init__.py,sha256=ouZueovCsCdgfuKoMP1Uh69E3fmn0U8ltllHWhXhpgg,39
|
|
36
|
-
tests/unit/shared/test_config.py,sha256=rokbpv_S9J-aUtg41eddn0ZuVWyTYZNXO9VtCZdXstU,11315
|
|
37
|
-
tests/unit/shared/test_streaming.py,sha256=sFjkla5SvaWWPi42lQ6vHSTor_htLMMbBttnCyGsB98,8706
|
|
38
|
-
lyceum_cli-1.0.24.dist-info/METADATA,sha256=4EBOllg5v6S83iHiUya5ZpPibYvu8SmU6KNDQUxUWEo,1482
|
|
39
|
-
lyceum_cli-1.0.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
40
|
-
lyceum_cli-1.0.24.dist-info/entry_points.txt,sha256=Oq-9wDkxVd6MHgNiUTYwXI9SGhvR3VkD7Mvk0xhiUZo,43
|
|
41
|
-
lyceum_cli-1.0.24.dist-info/top_level.txt,sha256=-546wowhLvi8w6Gef9vwO0T1i0ME2PanTrgC0YX29y4,47
|
|
42
|
-
lyceum_cli-1.0.24.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."""
|