@@ -132,7 +132,6 @@ def ensure_assets():
132132@app .get ("/api/config" )
133133def get_config ():
134134 """Return key configuration paths and settings for the frontend."""
135- ensure_assets ()
136135 # In the runner, we assume assets are in the current directory or nearby
137136 tutorial_db = Path ("assets/tutorial_database.sqlite" )
138137 return {
@@ -144,6 +143,18 @@ def get_config():
144143 }
145144
146145
146+ @app .post ("/api/download_tutorial" )
147+ def download_tutorial ():
148+ """Explicitly download tutorial assets."""
149+ ensure_assets ()
150+ tutorial_db = Path ("assets/tutorial_database.sqlite" )
151+ if tutorial_db .exists ():
152+ # Ensure it's served
153+ start_datasette (str (tutorial_db .absolute ()))
154+ return {"path" : str (tutorial_db .absolute ())}
155+ raise HTTPException (status_code = 500 , detail = "Failed to download tutorial assets" )
156+
157+
147158@app .get ("/api/files" )
148159def list_files (path : str = "." ):
149160 """Helper to browse files for input database selection."""
@@ -324,6 +335,16 @@ def flush(self):
324335 f"Output Database target: { run_toml ['output_database' ]} "
325336 )
326337
338+ # Ensure this output database is being served by Datasette
339+ # If it's outside our known list, restart datasette to include it.
340+ # We do this before starting the run so the user can see it appear or update.
341+ try :
342+ target_db = run_toml ["output_database" ]
343+ if target_db :
344+ start_datasette (str (Path (target_db ).absolute ()))
345+ except Exception as e :
346+ print (f"Failed to refresh datasette: { e } " )
347+
327348 config_path = output_dir / "run_config.toml"
328349 with open (config_path , "w" , encoding = "utf-8" ) as f :
329350 f .write (tomlkit .dumps (run_toml ))
@@ -378,43 +399,78 @@ async def websocket_endpoint(websocket: WebSocket):
378399 manager .disconnect (websocket )
379400
380401
381- if __name__ == "__main__" :
382- import uvicorn
402+ # --- Datasette Management ---
403+ DATASETTE_PROCESS = None
404+ SERVED_DATABASES = set ()
405+
406+
407+ def start_datasette (new_db : Optional [str ] = None ):
408+ """
409+ Start or restart Datasette serving the tutorial DB + output DBs.
410+ If new_db is provided and not already served, restart the process to include it.
411+ """
412+ global DATASETTE_PROCESS , SERVED_DATABASES
383413 import subprocess
384414 import os
415+ import sys
385416
386- ensure_assets ()
417+ # If new_db is already served, no need to restart
418+ if new_db :
419+ abs_new = str (Path (new_db ).resolve ())
420+ if abs_new in SERVED_DATABASES :
421+ return
422+ SERVED_DATABASES .add (abs_new )
423+
424+ # Kill existing process if running
425+ if DATASETTE_PROCESS :
426+ try :
427+ DATASETTE_PROCESS .terminate ()
428+ DATASETTE_PROCESS .wait (timeout = 5 )
429+ except Exception :
430+ try :
431+ DATASETTE_PROCESS .kill ()
432+ except Exception :
433+ pass
434+
435+ print ("Starting/Restarting Datasette on port 8001..." , flush = True )
387436
388- # Start Datasette in a subprocess
389- print ("Starting Datasette on port 8001..." , flush = True )
390437 try :
391438 log_file = open ("datasette.log" , "a" )
392-
393- # Create output directory if missing
394439 output_base = Path ("output" )
395440 output_base .mkdir (exist_ok = True )
396441
442+ # Baseline: Tutorial DB + All found in output directory
397443 tutorial_db = Path ("assets/tutorial_database.sqlite" )
444+ current_serve_list = []
398445
399- # Discover all sqlite files in the output directory recursively
400- to_serve = [str (p .absolute ()) for p in output_base .rglob ("*.sqlite" )]
446+ # 1. Add recursive output/ files
447+ for p in output_base .rglob ("*.sqlite" ):
448+ current_serve_list .append (str (p .absolute ()))
449+
450+ # 2. Add tutorial DB
401451 if tutorial_db .exists ():
402- to_serve .append (str (tutorial_db .absolute ()))
452+ current_serve_list .append (str (tutorial_db .absolute ()))
453+
454+ # 3. Add any globally tracked external databases (like the new input/output one)
455+ # Merge them in to ensure we serve what we've seen so far.
456+ for db_path in SERVED_DATABASES :
457+ if db_path not in current_serve_list :
458+ current_serve_list .append (db_path )
459+
460+ # Deduplicate just in case
461+ final_serve_list = list (set (current_serve_list ))
403462
404- # Isolate Datasette from host configs (like ~/.datasette/config.json)
405- # by pointing HOME to a known clean location.
463+ # Isolate Datasette environment
406464 env = os .environ .copy ()
407465 env ["HOME" ] = str (Path ("." ).absolute ())
408466
409- # Use sys.executable -m datasette to ensure we run in the same environment.
410- # We serve ONLY the explicit database files found.
411- subprocess .Popen (
467+ DATASETTE_PROCESS = subprocess .Popen (
412468 [
413469 sys .executable ,
414470 "-m" ,
415471 "datasette" ,
416472 "serve" ,
417- * to_serve ,
473+ * final_serve_list ,
418474 "--port" ,
419475 "8001" ,
420476 "--host" ,
@@ -430,7 +486,14 @@ async def websocket_endpoint(websocket: WebSocket):
430486 print (
431487 "Datasette process launched. (Logs available in datasette.log)" , flush = True
432488 )
489+
433490 except Exception as e :
434491 print (f"Warning: Could not start Datasette: { e } " , flush = True )
435492
493+
494+ if __name__ == "__main__" :
495+ import uvicorn
496+
497+ start_datasette () # Initial start
498+
436499 uvicorn .run (app , host = "0.0.0.0" , port = 8000 )
0 commit comments