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/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
- class Config:
29
- """Configuration management for Lyceum CLI"""
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
- # Use Supabase's refresh_session method with the service role key
96
- supabase_url = "https://tqcebgbexyszvqhnwnhh.supabase.co"
97
- # NOTE: This service key is scoped for refresh token operations only
98
- supabase_service_key = (
99
- "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
100
- "eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InRxY2ViZ2JleHlzenZxaG53bmhoIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIs"
101
- "ImlhdCI6MTc0NzE1NDQ3MSwiZXhwIjoyMDYyNzMwNDcxfQ."
102
- "RpxhmMxFKJSJERobr28bmaZOG9Fxe-qthTYlg8iyFdc"
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 = 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 status_emoji(status: str) -> str:
145
+ def status_icon(status: str) -> str:
165
146
  """
166
- Get emoji for status display.
147
+ Get text icon for status display.
167
148
 
168
149
  Args:
169
150
  status: Status string
170
151
 
171
152
  Returns:
172
- Emoji character
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 '[--]'
@@ -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, status: StatusLine = None) -> bool:
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
- if status:
73
- status.update("Connecting to stream...")
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
- if status:
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
- if status:
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
- if status:
155
- status.stop()
156
- status_val = data.get("status", "unknown")
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]Failed: {status_val}[/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
- if status:
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
- if status:
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]Failed: {status}[/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.capitalize()}[/yellow]")
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lyceum-cli
3
- Version: 1.0.24
3
+ Version: 1.0.26
4
4
  Summary: Command-line interface for Lyceum Cloud Execution API
5
5
  Home-page: https://lyceum.technology
6
6
  Author: Lyceum Team
@@ -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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,3 +1,2 @@
1
1
  lyceum
2
2
  lyceum_cloud_execution_api_client
3
- tests
@@ -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."""
@@ -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."""