Skip to content

Commit 265016a

Browse files
author
Dylan Huang
committed
Update FastAPI dependency to version 0.116.1 and refactor LogsServer to use async context manager for file watching lifecycle. Enhance WebSocket connection handling in the frontend with exponential backoff for reconnection attempts.
1 parent 742f522 commit 265016a

5 files changed

Lines changed: 478 additions & 33 deletions

File tree

eval_protocol/utils/logs_server.py

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
from contextlib import asynccontextmanager
23
import json
34
import logging
45
import os
@@ -8,7 +9,7 @@
89
from watchdog.observers import Observer
910

1011
import uvicorn
11-
from fastapi import WebSocket, WebSocketDisconnect
12+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
1213

1314
from eval_protocol.dataset_logger import default_logger
1415
from eval_protocol.utils.vite_server import ViteServer
@@ -85,6 +86,7 @@ def broadcast_file_update(self, update_type: str, file_path: str):
8586
"""Broadcast file update to all connected clients."""
8687
if not file_path.startswith(default_logger.datasets_dir):
8788
return
89+
logger.info(f"Broadcasting file update: {update_type} {file_path}")
8890

8991
message = {"type": update_type, "path": file_path, "timestamp": time.time()}
9092
# Include file contents for created and modified events
@@ -126,19 +128,29 @@ def __init__(
126128
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "vite-app", "dist")
127129
),
128130
host: str = "localhost",
129-
port: int = 4789,
131+
port: Optional[int] = None,
130132
index_file: str = "index.html",
131133
watch_paths: Optional[List[str]] = None,
132134
):
133-
super().__init__(build_dir, host, port, index_file)
134-
135135
# Initialize WebSocket manager
136136
self.websocket_manager = WebSocketManager()
137137

138138
# Set up file watching
139139
self.watch_paths = watch_paths or [os.getcwd()]
140140
self.observer = Observer()
141141
self.file_watcher = FileWatcher(self.websocket_manager)
142+
self._file_watching_started = False
143+
144+
@asynccontextmanager
145+
async def lifespan(app: FastAPI):
146+
print("before")
147+
self.start_file_watching()
148+
self.websocket_manager._loop = asyncio.get_running_loop()
149+
yield
150+
print("after")
151+
self.stop_file_watching()
152+
153+
super().__init__(build_dir, host, port, index_file, lifespan=lifespan)
142154

143155
# Add WebSocket endpoint
144156
self._setup_websocket_routes()
@@ -174,6 +186,11 @@ async def status():
174186

175187
def start_file_watching(self):
176188
"""Start watching file system for changes."""
189+
# Check if file watching has already been started
190+
if self._file_watching_started:
191+
logger.info("File watching already started, skipping")
192+
return
193+
177194
for path in self.watch_paths:
178195
if os.path.exists(path):
179196
self.observer.schedule(self.file_watcher, path, recursive=True)
@@ -182,15 +199,18 @@ def start_file_watching(self):
182199
logger.warning(f"Watch path does not exist: {path}")
183200

184201
self.observer.start()
202+
self._file_watching_started = True
185203
logger.info("File watching started")
186204

187205
def stop_file_watching(self):
188206
"""Stop watching file system."""
189-
self.observer.stop()
190-
self.observer.join()
191-
logger.info("File watching stopped")
207+
if self._file_watching_started:
208+
self.observer.stop()
209+
self.observer.join()
210+
self._file_watching_started = False
211+
logger.info("File watching stopped")
192212

193-
async def run_async(self, reload: bool = False):
213+
async def run_async(self):
194214
"""
195215
Run the logs server asynchronously with file watching.
196216
@@ -212,10 +232,9 @@ async def run_async(self, reload: bool = False):
212232
self.app,
213233
host=self.host,
214234
port=self.port,
215-
reload=reload,
216-
reload_dirs=self.watch_paths,
217235
log_level="info",
218236
)
237+
219238
server = uvicorn.Server(config)
220239
await server.serve()
221240

@@ -224,22 +243,21 @@ async def run_async(self, reload: bool = False):
224243
finally:
225244
self.stop_file_watching()
226245

227-
def run(self, reload: bool = False):
246+
def run(self):
228247
"""
229248
Run the logs server with file watching.
230249
231250
Args:
232251
reload: Whether to enable auto-reload (default: False)
233252
"""
234-
asyncio.run(self.run_async(reload))
253+
asyncio.run(self.run_async())
254+
255+
256+
server = LogsServer()
257+
app = server.app
235258

236259

237-
def serve_logs(
238-
host: str = "localhost",
239-
port: int = 4789,
240-
watch_paths: Optional[List[str]] = None,
241-
reload: bool = False,
242-
):
260+
def serve_logs():
243261
"""
244262
Convenience function to create and run a LogsServer.
245263
@@ -251,9 +269,8 @@ def serve_logs(
251269
watch_paths: List of paths to watch for file changes
252270
reload: Whether to enable auto-reload
253271
"""
254-
server = LogsServer(host=host, port=port, watch_paths=watch_paths)
255-
server.run(reload=reload)
272+
server.run()
256273

257274

258275
if __name__ == "__main__":
259-
serve_logs(reload=True)
276+
serve_logs()

eval_protocol/utils/vite_server.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
import logging
33
from pathlib import Path
4-
from typing import Optional
4+
from typing import AsyncGenerator, Callable, Optional
55

66
import uvicorn
77
from fastapi import FastAPI, HTTPException
@@ -27,13 +27,18 @@ class ViteServer:
2727
"""
2828

2929
def __init__(
30-
self, build_dir: str = "dist", host: str = "localhost", port: int = 8000, index_file: str = "index.html"
30+
self,
31+
build_dir: str = "dist",
32+
host: str = "localhost",
33+
port: int = 8000,
34+
index_file: str = "index.html",
35+
lifespan: Optional[Callable[[FastAPI], AsyncGenerator[None, None]]] = None,
3136
):
3237
self.build_dir = Path(build_dir)
3338
self.host = host
3439
self.port = port
3540
self.index_file = index_file
36-
self.app = FastAPI(title="Vite SPA Server")
41+
self.app = FastAPI(title="Vite SPA Server", lifespan=lifespan)
3742

3843
# Validate build directory exists
3944
if not self.build_dir.exists():

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ dependencies = [
2020
"requests>=2.25.0",
2121
"pydantic>=2.0.0",
2222
"dataclasses-json>=0.5.7",
23-
"fastapi>=0.68.0",
2423
"uvicorn>=0.15.0",
2524
"python-dotenv>=0.19.0",
2625
"openai==1.78.1",
@@ -50,6 +49,7 @@ dependencies = [
5049
"watchdog>=2.1.0",
5150
"websockets>=15.0.1",
5251
"fireworks-ai>=0.19.12",
52+
"fastapi>=0.116.1",
5353
]
5454

5555
[project.urls]
@@ -143,6 +143,7 @@ tau2 = { git = "https://github.com/sierra-research/tau2-bench.git" }
143143

144144
[dependency-groups]
145145
dev = [
146+
"fastapi[standard]>=0.116.1",
146147
"fastmcp>=2.10.6",
147148
"haikus==0.3.8",
148149
"pytest>=8.4.1",

0 commit comments

Comments
 (0)