From 9e95e9a08403a7a35e88a1a2112db0a06dfa60e9 Mon Sep 17 00:00:00 2001 From: Alex Dixon Date: Tue, 6 Aug 2024 18:30:03 -0700 Subject: [PATCH 1/4] reload server working except for db watcher --- src/ell/studio/__main__.py | 14 ++++++++++---- src/ell/studio/data_server.py | 9 +++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/ell/studio/__main__.py b/src/ell/studio/__main__.py index d1b53285..8d04042c 100644 --- a/src/ell/studio/__main__.py +++ b/src/ell/studio/__main__.py @@ -17,9 +17,9 @@ def main(): parser.add_argument("--dev", action="store_true", help="Run in development mode") args = parser.parse_args() - app = create_app(args.storage_dir) if not args.dev: + app = create_app() # In production mode, serve the built React app static_dir = os.path.join(os.path.dirname(__file__), "static") app.mount("/", StaticFiles(directory=static_dir, html=True), name="static") @@ -68,9 +68,15 @@ async def db_watcher(db_path, app): # Start the database watcher loop = asyncio.new_event_loop() - config = uvicorn.Config(app=app, port=args.port, loop=loop) - server = uvicorn.Server(config) - loop.create_task(server.serve()) + # config = uvicorn.Config(app=app, port=args.port, loop=loop,reload=True,#if args.dev else False, + # reload_delay=1) + # server = uvicorn.Server(config) + uvicorn.run("ell.studio.data_server:create_app", + reload=True, + reload_delay=5, + host=args.host, + port=args.port) + # loop.create_task(server.serve()) loop.create_task(db_watcher(db_path, app)) loop.run_forever() diff --git a/src/ell/studio/data_server.py b/src/ell/studio/data_server.py index 8fbefce0..55960c0a 100644 --- a/src/ell/studio/data_server.py +++ b/src/ell/studio/data_server.py @@ -8,6 +8,7 @@ import logging import asyncio import json +from argparse import ArgumentParser logger = logging.getLogger(__name__) @@ -28,9 +29,13 @@ async def broadcast(self, message: str): print(f"Broadcasting message to {connection} {message}") await connection.send_text(message) +def create_app(): + parser = ArgumentParser(description="ELL Studio Data Server") + parser.add_argument("--storage-dir", default=os.getcwd(), + help="Directory for filesystem serializer storage (default: current directory)") + args, _ = parser.parse_known_args() -def create_app(storage_dir: Optional[str] = None): - storage_path = storage_dir or os.environ.get("ELL_STORAGE_DIR") or os.getcwd() + storage_path = args.storage_dir or os.environ.get("ELL_STORAGE_DIR") or os.getcwd() assert storage_path, "ELL_STORAGE_DIR must be set" serializer = SQLiteStore(storage_path) From c662768b5ce5eb7f0c56dc69f61feb110818a09c Mon Sep 17 00:00:00 2001 From: Alex Dixon Date: Tue, 6 Aug 2024 19:17:14 -0700 Subject: [PATCH 2/4] i guess just use claude --- src/ell/studio/__main__.py | 73 ++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/src/ell/studio/__main__.py b/src/ell/studio/__main__.py index 8d04042c..6481cf72 100644 --- a/src/ell/studio/__main__.py +++ b/src/ell/studio/__main__.py @@ -5,7 +5,9 @@ from ell.studio.data_server import create_app from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse -from watchfiles import awatch +import watchfiles +import importlib +import sys import time def main(): @@ -31,13 +33,11 @@ async def serve_react_app(full_path: str): db_path = os.path.join(args.storage_dir, "ell.db") async def db_watcher(db_path, app): + print("Starting db watcher") last_stat = None - while True: - await asyncio.sleep(0.1) # Fixed interval of 0.1 seconds try: current_stat = os.stat(db_path) - if last_stat is None: print(f"Database file found: {db_path}") await app.notify_clients("database_updated") @@ -64,21 +64,58 @@ async def db_watcher(db_path, app): except Exception as e: print(f"Error checking database file: {e}") await asyncio.sleep(1) # Wait a bit longer on errors + finally: + await asyncio.sleep(1) # Use a consistent sleep interval + + def get_dependencies(module_name): + module = importlib.import_module(module_name) + return list(set(sys.modules[name].__file__ for name in sys.modules if name.startswith(module_name.split('.')[0]))) + + def reload_app(): + importlib.reload(sys.modules["ell.studio.data_server"]) + return create_app() + + async def run_server(server): + await server.serve() + + async def watch_files(dependencies, server, config, loop): + async for changes in watchfiles.awatch(*dependencies): + print(f"Detected changes in {changes}. Reloading...") + new_app = reload_app() + await server.shutdown() + config.app = new_app + server.force_exit = False + loop.create_task(run_server(server)) + + async def main_async(args): + db_path = os.path.join(args.storage_dir, "ell.db") + dependencies = get_dependencies("ell.studio.data_server") + app = create_app() + + config = uvicorn.Config( + app=app, + host=args.host, + port=args.port, + loop=asyncio.get_event_loop(), + ) + server = uvicorn.Server(config) + + tasks = [ + asyncio.create_task(run_server(server)), + asyncio.create_task(watch_files(dependencies, server, config, asyncio.get_event_loop())), + asyncio.create_task(db_watcher(db_path, app)) + ] + + try: + await asyncio.gather(*tasks) + except asyncio.CancelledError: + pass + finally: + for task in tasks: + task.cancel() + await asyncio.gather(*tasks, return_exceptions=True) - # Start the database watcher - loop = asyncio.new_event_loop() - - # config = uvicorn.Config(app=app, port=args.port, loop=loop,reload=True,#if args.dev else False, - # reload_delay=1) - # server = uvicorn.Server(config) - uvicorn.run("ell.studio.data_server:create_app", - reload=True, - reload_delay=5, - host=args.host, - port=args.port) - # loop.create_task(server.serve()) - loop.create_task(db_watcher(db_path, app)) - loop.run_forever() + asyncio.run(main_async(args)) if __name__ == "__main__": main() \ No newline at end of file From 69ecf314bae946ef4cfc48cc172665675ce8fd6a Mon Sep 17 00:00:00 2001 From: Alex Dixon Date: Fri, 9 Aug 2024 14:44:05 -0700 Subject: [PATCH 3/4] works with prod --- src/ell/studio/__main__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ell/studio/__main__.py b/src/ell/studio/__main__.py index 6481cf72..651f53ff 100644 --- a/src/ell/studio/__main__.py +++ b/src/ell/studio/__main__.py @@ -20,8 +20,9 @@ def main(): args = parser.parse_args() + app = create_app() + if not args.dev: - app = create_app() # In production mode, serve the built React app static_dir = os.path.join(os.path.dirname(__file__), "static") app.mount("/", StaticFiles(directory=static_dir, html=True), name="static") @@ -30,7 +31,6 @@ def main(): async def serve_react_app(full_path: str): return FileResponse(os.path.join(static_dir, "index.html")) - db_path = os.path.join(args.storage_dir, "ell.db") async def db_watcher(db_path, app): print("Starting db watcher") @@ -90,7 +90,6 @@ async def watch_files(dependencies, server, config, loop): async def main_async(args): db_path = os.path.join(args.storage_dir, "ell.db") dependencies = get_dependencies("ell.studio.data_server") - app = create_app() config = uvicorn.Config( app=app, @@ -102,9 +101,13 @@ async def main_async(args): tasks = [ asyncio.create_task(run_server(server)), - asyncio.create_task(watch_files(dependencies, server, config, asyncio.get_event_loop())), - asyncio.create_task(db_watcher(db_path, app)) ] + # todo. figure out equivalent for other backends + # maybe the server should broadcast a message to all clients on write instead of the db watcher approach + if args.storage_dir: + tasks.append(asyncio.create_task(db_watcher(db_path, app))) + if args.dev: + tasks.append(asyncio.create_task(watch_files(dependencies, server, config, asyncio.get_event_loop()))) try: await asyncio.gather(*tasks) From a8385aa7f800985c9f001d5137d81d2f45781cac Mon Sep 17 00:00:00 2001 From: Alex Dixon Date: Fri, 9 Aug 2024 14:54:09 -0700 Subject: [PATCH 4/4] fix server main filepath --- src/ell/studio/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ell/studio/__main__.py b/src/ell/studio/__main__.py index 21df6892..cadb584c 100644 --- a/src/ell/studio/__main__.py +++ b/src/ell/studio/__main__.py @@ -72,7 +72,7 @@ def get_dependencies(module_name): return list(set(sys.modules[name].__file__ for name in sys.modules if name.startswith(module_name.split('.')[0]))) def reload_app(): - importlib.reload(sys.modules["ell.studio.data_server"]) + importlib.reload(sys.modules["ell.studio.server"]) return create_app() async def run_server(server): @@ -89,7 +89,7 @@ async def watch_files(dependencies, server, config, loop): async def main_async(args): db_path = os.path.join(args.storage_dir, "ell.db") - dependencies = get_dependencies("ell.studio.data_server") + dependencies = get_dependencies("ell.studio.server") config = uvicorn.Config( app=app,