Skip to content

Commit 476d1dd

Browse files
committed
adjustments for fastapi to use a controllable subprocess vs running with uvicorn, this allows for unloading the config file properly
1 parent ee1e611 commit 476d1dd

File tree

1 file changed

+41
-25
lines changed

1 file changed

+41
-25
lines changed

dash/backends/_fastapi.py

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from importlib.util import spec_from_file_location
1313
import json
1414
import os
15+
import subprocess
1516

1617
try:
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+
7476
def _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

Comments
 (0)