1212from importlib .util import spec_from_file_location
1313import json
1414import os
15+ import subprocess
1516
1617try :
1718 from fastapi import FastAPI , Request , Response , Body
@@ -71,6 +72,7 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: #
7172 finally :
7273 reset_current_request (token )
7374
75+
7476def _save_config (config_path , config ):
7577 with open (config_path , "w" , encoding = "utf-8" ) as f :
7678 json .dump (config , f )
@@ -104,11 +106,16 @@ def __init__(self, server: FastAPI):
104106
105107 fname = inspect .stack ()[2 ]
106108 file_path = fname .filename
107- rel_path = os .path .relpath (file_path , os .getcwd ())
108- module_name = os .path .splitext (rel_path )[0 ].replace (os .sep , "." )
109- # Internal config helpers (local to this file)
110- # This is used to persist dev tools config between reloads, since uvicorn runs a new process
111- self ._CONFIG_PATH = os .path .join (os .path .dirname (file_path ), f"_{ module_name } _dash_config.json" )
109+
110+ # Manually build the config directory path
111+ home_dir = os .path .expanduser ("~" )
112+ config_dir = os .path .join (home_dir , ".local" , "share" , "plotly-dash-configs" )
113+ os .makedirs (config_dir , exist_ok = True )
114+
115+ # Hash the file path for a unique config filename
116+ hash_digest = hashlib .sha256 (file_path .encode ("utf-8" )).hexdigest ()
117+ config_filename = f"{ hash_digest } .json"
118+ self ._CONFIG_PATH = os .path .join (config_dir , config_filename )
112119 super ().__init__ ()
113120
114121 def __call__ (self , * args : Any , ** kwargs : Any ):
@@ -117,6 +124,9 @@ def __call__(self, *args: Any, **kwargs: Any):
117124 return self .server (* args , ** kwargs )
118125 raise TypeError ("FastAPI app must be called with (scope, receive, send)" )
119126
127+ def _cleanup_config (self ):
128+ _remove_config (self ._CONFIG_PATH )
129+
120130 @staticmethod
121131 def create_app (name : str = "__main__" , config : Dict [str , Any ] | None = None ):
122132 app = FastAPI ()
@@ -169,10 +179,6 @@ async def index(_request: Request):
169179 dash_app ._add_url ("" , index , methods = ["GET" ])
170180
171181 def setup_catchall (self , dash_app : Dash ):
172- @self .server .on_event ("shutdown" )
173- def cleanup_config ():
174- _remove_config (self ._CONFIG_PATH )
175-
176182 @self .server .on_event ("startup" )
177183 def _setup_catchall ():
178184 dash_app .enable_dev_tools (
@@ -228,28 +234,38 @@ def has_request_context(self) -> bool:
228234
229235 def run (self , dash_app : Dash , host , port , debug , ** kwargs ):
230236 frame = inspect .stack ()[2 ]
231- dev_tools = dash_app ._dev_tools # pylint: disable=protected-access
237+ dev_tools = dash_app ._dev_tools
232238 config = dict (
233239 {"debug" : debug } if debug else {"debug" : False },
234240 ** {f"dev_tools_{ k } " : v for k , v in dev_tools .items ()},
235241 )
236242 _save_config (self ._CONFIG_PATH , config )
237- if debug :
238- if kwargs .get ("reload" ) is None :
239- kwargs ["reload" ] = True
243+ if debug and kwargs .get ("reload" ) is None :
244+ kwargs ["reload" ] = True
245+
246+ file_path = frame .filename
247+ rel_path = os .path .relpath (file_path , os .getcwd ())
248+ module_name = os .path .splitext (rel_path )[0 ].replace (os .sep , "." )
249+ uvicorn_args = [
250+ sys .executable ,
251+ "-m" ,
252+ "uvicorn" ,
253+ f"{ module_name } :server" ,
254+ "--host" ,
255+ str (host ),
256+ "--port" ,
257+ str (port ),
258+ ]
240259 if kwargs .get ("reload" ):
241- # Dynamically determine the module name from the file path
242- file_path = frame .filename
243- rel_path = os .path .relpath (file_path , os .getcwd ())
244- module_name = os .path .splitext (rel_path )[0 ].replace (os .sep , "." )
245- uvicorn .run (
246- f"{ module_name } :server" ,
247- host = host ,
248- port = port ,
249- ** kwargs ,
250- )
251- else :
252- uvicorn .run (self .server , host = host , port = port , ** kwargs )
260+ uvicorn_args .append ("--reload" )
261+
262+ # Add any other kwargs as CLI args if needed
263+
264+ try :
265+ proc = subprocess .Popen (uvicorn_args , env = os .environ .copy ())
266+ proc .wait ()
267+ finally :
268+ self ._cleanup_config ()
253269
254270 def make_response (
255271 self ,
0 commit comments