@@ -118,7 +118,9 @@ def check_titiler_endpoint(titiler_endpoint: Optional[str] = None) -> Any:
118118 Returns:
119119 The titiler endpoint.
120120 """
121- if titiler_endpoint is None :
121+ if titiler_endpoint is not None and titiler_endpoint .lower () == "local" :
122+ titiler_endpoint = run_titiler (show_logs = False )
123+ elif titiler_endpoint is None :
122124 if os .environ .get ("TITILER_ENDPOINT" ) is not None :
123125 titiler_endpoint = os .environ .get ("TITILER_ENDPOINT" )
124126
@@ -2514,3 +2516,91 @@ def get_cog_link_from_stac_item(item_url: str) -> str:
25142516 except Exception as e :
25152517 print (f"Failed to retrieve STAC item: { e } " )
25162518 return None
2519+
2520+
2521+ def run_titiler (
2522+ show_logs : bool = False ,
2523+ start_port : int = 8000 ,
2524+ max_port : int = 8100 ,
2525+ return_titiler_endpoint : bool = False ,
2526+ ):
2527+ """Run TiTiler as a background service on an available port.
2528+
2529+ Args:
2530+ show_logs (bool): If True, stream logs to the notebook output.
2531+ start_port (int): First port to try.
2532+ max_port (int): Last port to try (exclusive).
2533+ return_titiler_endpoint (bool): If True, return the titiler endpoint. Defaults to False.
2534+
2535+ Returns:
2536+ tuple: (endpoint, port, process)
2537+ """
2538+
2539+ import subprocess
2540+ import socket
2541+ import atexit
2542+ import signal
2543+ import time
2544+ import threading
2545+
2546+ def find_free_port (start , end ):
2547+ for port in range (start , end ):
2548+ with socket .socket (socket .AF_INET , socket .SOCK_STREAM ) as s :
2549+ try :
2550+ s .bind (("127.0.0.1" , port ))
2551+ return port
2552+ except OSError :
2553+ continue
2554+ raise RuntimeError (f"No free port found between { start } and { end } " )
2555+
2556+ port = find_free_port (start_port , max_port )
2557+
2558+ cmd = [
2559+ "uvicorn" ,
2560+ "titiler.application.main:app" ,
2561+ "--host" ,
2562+ "127.0.0.1" ,
2563+ "--port" ,
2564+ str (port ),
2565+ "--log-level" ,
2566+ "info" ,
2567+ ]
2568+
2569+ proc = subprocess .Popen (
2570+ cmd ,
2571+ stdout = subprocess .PIPE if show_logs else subprocess .DEVNULL ,
2572+ stderr = subprocess .STDOUT ,
2573+ )
2574+
2575+ # Optionally stream logs in a background thread
2576+ if show_logs :
2577+
2578+ def stream_logs ():
2579+ for line in iter (proc .stdout .readline , b"" ):
2580+ print (line .decode ().rstrip ())
2581+
2582+ threading .Thread (target = stream_logs , daemon = True ).start ()
2583+
2584+ # Wait a bit for startup
2585+ time .sleep (2 )
2586+ endpoint = f"http://127.0.0.1:{ port } "
2587+ print (f"🚀 TiTiler is running at { endpoint } " )
2588+
2589+ # Register cleanup on kernel shutdown
2590+ def stop_titiler ():
2591+ if proc .poll () is None :
2592+ print ("🛑 Stopping TiTiler..." )
2593+ proc .send_signal (signal .SIGINT )
2594+ try :
2595+ proc .wait (timeout = 5 )
2596+ except subprocess .TimeoutExpired :
2597+ proc .kill ()
2598+
2599+ atexit .register (stop_titiler )
2600+
2601+ os .environ ["TITILER_ENDPOINT" ] = endpoint
2602+ os .environ ["TITILER_PORT" ] = str (port )
2603+ os .environ ["TITILER_PROCESS" ] = str (proc )
2604+
2605+ if return_titiler_endpoint :
2606+ return endpoint , port , proc
0 commit comments