Skip to content

Commit dfe0ac7

Browse files
committed
fixing for lint
1 parent 4cf4686 commit dfe0ac7

File tree

7 files changed

+229
-303
lines changed

7 files changed

+229
-303
lines changed

dash/backends/__init__.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
from .base_server import BaseDashServer, RequestAdapter
2-
31
import importlib
2+
from .base_server import BaseDashServer
43

54

6-
request_adapter: RequestAdapter
75
backend: BaseDashServer
86

97

108
_backend_imports = {
11-
"flask": ("dash.backends._flask", "FlaskDashServer", "FlaskRequestAdapter"),
12-
"fastapi": ("dash.backends._fastapi", "FastAPIDashServer", "FastAPIRequestAdapter"),
13-
"quart": ("dash.backends._quart", "QuartDashServer", "QuartRequestAdapter"),
9+
"flask": ("dash.backends._flask", "FlaskDashServer"),
10+
"fastapi": ("dash.backends._fastapi", "FastAPIDashServer"),
11+
"quart": ("dash.backends._quart", "QuartDashServer"),
1412
}
1513

1614

17-
def get_backend(name: str) -> tuple[BaseDashServer, RequestAdapter]:
18-
module_name, server_class, request_class = _backend_imports[name.lower()]
15+
def get_backend(name: str) -> BaseDashServer:
16+
module_name, server_class = _backend_imports[name.lower()]
1917
try:
2018
module = importlib.import_module(module_name)
2119
server = getattr(module, server_class)
@@ -74,7 +72,6 @@ def get_server_type(server):
7472

7573
__all__ = [
7674
"get_backend",
77-
"request_adapter",
7875
"backend",
7976
"get_server_type",
8077
]

dash/backends/_fastapi.py

Lines changed: 73 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import asyncio
34
from contextvars import copy_context, ContextVar
45
from typing import TYPE_CHECKING, Any, Callable, Dict
56
import sys
@@ -8,27 +9,41 @@
89
import inspect
910
import pkgutil
1011
import time
11-
import traceback
1212
from importlib.util import spec_from_file_location
1313
import json
1414
import os
15-
import re
15+
16+
try:
17+
from fastapi import FastAPI, Request, Response, Body
18+
from fastapi.responses import JSONResponse
19+
from fastapi.staticfiles import StaticFiles
20+
from starlette.responses import Response as StarletteResponse
21+
from starlette.datastructures import MutableHeaders
22+
from starlette.types import ASGIApp, Scope, Receive, Send
23+
import uvicorn
24+
except ImportError:
25+
FastAPI = None
26+
Request = None
27+
Response = None
28+
Body = None
29+
JSONResponse = None
30+
StaticFiles = None
31+
StarletteResponse = None
32+
MutableHeaders = None
33+
ASGIApp = None
34+
Scope = None
35+
Receive = None
36+
Send = None
37+
uvicorn = None
1638

1739
from dash.fingerprint import check_fingerprint
1840
from dash import _validate
1941
from dash.exceptions import PreventUpdate
2042
from .base_server import BaseDashServer, RequestAdapter
21-
22-
from fastapi import FastAPI, Request, Response, Body
23-
from fastapi.responses import JSONResponse
24-
from fastapi.staticfiles import StaticFiles
25-
from starlette.responses import Response as StarletteResponse
26-
from starlette.datastructures import MutableHeaders
27-
from starlette.types import ASGIApp, Scope, Receive, Send
28-
import uvicorn
43+
from ._utils import format_traceback_html
2944

3045
if TYPE_CHECKING: # pragma: no cover - typing only
31-
from dash.dash import Dash
46+
from dash import Dash
3247

3348

3449
_current_request_var = ContextVar("dash_current_request", default=None)
@@ -49,7 +64,7 @@ def get_current_request() -> Request:
4964
return req
5065

5166

52-
class CurrentRequestMiddleware:
67+
class CurrentRequestMiddleware: # pylint: disable=too-few-public-methods
5368
def __init__(self, app: ASGIApp) -> None: # type: ignore[name-defined]
5469
self.app = app
5570

@@ -66,23 +81,27 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: #
6681
finally:
6782
reset_current_request(token)
6883

84+
6985
# Internal config helpers (local to this file)
7086
_CONFIG_PATH = os.path.join(os.path.dirname(__file__), "dash_config.json")
7187

88+
7289
def _save_config(config):
73-
with open(_CONFIG_PATH, "w") as f:
90+
with open(_CONFIG_PATH, "w", encoding="utf-8") as f:
7491
json.dump(config, f)
7592

93+
7694
def _load_config():
7795
resp = {"debug": False}
7896
try:
7997
if os.path.exists(_CONFIG_PATH):
80-
with open(_CONFIG_PATH, "r") as f:
98+
with open(_CONFIG_PATH, "r", encoding="utf-8") as f:
8199
resp = json.load(f)
82-
except Exception:
100+
except (json.JSONDecodeError, OSError):
83101
pass # ignore errors
84102
return resp
85103

104+
86105
def _remove_config():
87106
try:
88107
os.remove(_CONFIG_PATH)
@@ -130,113 +149,9 @@ def register_error_handlers(self):
130149
self.error_handling_mode = "ignore"
131150

132151
def _get_traceback(self, _secret, error: Exception):
133-
tb = error.__traceback__
134-
errors = traceback.format_exception(type(error), error, tb)
135-
pass_errs = []
136-
callback_handled = False
137-
for err in errors:
138-
if self.error_handling_mode == "prune":
139-
if not callback_handled:
140-
if "callback invoked" in str(err) and "_callback.py" in str(err):
141-
callback_handled = True
142-
continue
143-
pass_errs.append(err)
144-
formatted_tb = "".join(pass_errs)
145-
error_type = type(error).__name__
146-
error_msg = str(error)
147-
148-
# Parse traceback lines to group by file
149-
file_cards = []
150-
pattern = re.compile(r' File "(.+)", line (\d+), in (\w+)')
151-
lines = formatted_tb.split("\n")
152-
current_file = None
153-
card_lines = []
154-
155-
for line in lines[:-1]: # Skip the last line (error message)
156-
match = pattern.match(line)
157-
if match:
158-
if current_file and card_lines:
159-
file_cards.append((current_file, card_lines))
160-
current_file = (
161-
f"{match.group(1)} (line {match.group(2)}, in {match.group(3)})"
162-
)
163-
card_lines = [line]
164-
elif current_file:
165-
card_lines.append(line)
166-
if current_file and card_lines:
167-
file_cards.append((current_file, card_lines))
168-
169-
cards_html = ""
170-
for filename, card in file_cards:
171-
cards_html += (
172-
f"""
173-
<div class="error-card">
174-
<div class="error-card-header">{filename}</div>
175-
<pre class="error-card-traceback">"""
176-
+ "\n".join(card)
177-
+ """</pre>
178-
</div>
179-
"""
180-
)
181-
182-
html = f"""
183-
<!doctype html>
184-
<html lang="en">
185-
<head>
186-
<title>{error_type}: {error_msg} // FastAPI Debugger</title>
187-
<style>
188-
body {{ font-family: monospace; background: #fff; color: #333; }}
189-
.debugger {{ margin: 2em; max-width: 700px; }}
190-
.error-card {{
191-
border: 1px solid #ccc;
192-
border-radius: 6px;
193-
margin-bottom: 1em;
194-
padding: 1em;
195-
background: #f9f9f9;
196-
box-shadow: 0 2px 4px rgba(0,0,0,0.03);
197-
overflow: auto;
198-
}}
199-
.error-card-header {{
200-
font-weight: bold;
201-
margin-bottom: 0.5em;
202-
color: #0074d9;
203-
}}
204-
.error-card-traceback {{
205-
max-height: 150px;
206-
overflow: auto;
207-
margin: 0;
208-
white-space: pre-wrap;
209-
}}
210-
.plain textarea {{ width: 100%; height: 10em; resize: vertical; overflow: auto; }}
211-
h1 {{ color: #c00; }}
212-
</style>
213-
</head>
214-
<body style="padding-bottom:10px">
215-
<div class="debugger">
216-
<h1>{error_type}</h1>
217-
<div class="detail">
218-
<p class="errormsg">{error_type}: {error_msg}</p>
219-
</div>
220-
<h2 class="traceback">Traceback <em>(most recent call last)</em></h2>
221-
{cards_html}
222-
<blockquote>{error_type}: {error_msg}</blockquote>
223-
<div class="plain">
224-
<p>This is the Copy/Paste friendly version of the traceback.</p>
225-
<textarea readonly>{formatted_tb}</textarea>
226-
</div>
227-
<div class="explanation">
228-
The debugger caught an exception in your ASGI application. You can now
229-
look at the traceback which led to the error.
230-
</div>
231-
<div class="footer">
232-
Brought to you by <strong class="arthur">DON'T PANIC</strong>, your
233-
friendly FastAPI powered traceback interpreter.
234-
</div>
235-
</div>
236-
</body>
237-
</html>
238-
"""
239-
return html
152+
return format_traceback_html(
153+
error, self.error_handling_mode, "FastAPI Debugger", "FastAPI"
154+
)
240155

241156
def register_prune_error_handler(self, _secret, prune_errors):
242157
if prune_errors:
@@ -253,7 +168,7 @@ async def wrapped(*_args, **_kwargs):
253168
return wrapped
254169

255170
def setup_index(self, dash_app: Dash):
256-
async def index(request: Request):
171+
async def index(_request: Request):
257172
return Response(content=dash_app.index(), media_type="text/html")
258173

259174
# pylint: disable=protected-access
@@ -270,7 +185,7 @@ def _setup_catchall():
270185
**_load_config(), first_run=False
271186
) # do this to make sure dev tools are enabled
272187

273-
async def catchall(request: Request):
188+
async def catchall(_request: Request):
274189
return Response(content=dash_app.index(), media_type="text/html")
275190

276191
# pylint: disable=protected-access
@@ -308,11 +223,10 @@ def after_request(self, func: Callable[[], Any] | None):
308223

309224
def run(self, dash_app: Dash, host, port, debug, **kwargs):
310225
frame = inspect.stack()[2]
226+
dev_tools = dash_app._dev_tools # pylint: disable=protected-access
311227
config = dict(
312228
{"debug": debug} if debug else {"debug": False},
313-
**{
314-
f"dev_tools_{k}": v for k, v in dash_app._dev_tools.items()
315-
}, # pylint: disable=protected-access
229+
**{f"dev_tools_{k}": v for k, v in dev_tools.items()},
316230
)
317231
_save_config(config)
318232
if debug:
@@ -348,22 +262,26 @@ def make_response(
348262
def jsonify(self, obj: Any):
349263
return JSONResponse(content=obj)
350264

351-
def _make_before_middleware(self, func: Callable[[], Any] | None):
265+
def _make_before_middleware(self, _func: Callable[[], Any] | None):
352266
async def middleware(request, call_next):
353267
try:
354268
response = await call_next(request)
355269
return response
356270
except PreventUpdate:
357271
# No content, nothing to update
358272
return Response(status_code=204)
359-
except Exception as e:
273+
except (Exception) as e: # pylint: disable=broad-except
274+
# Handle exceptions based on error_handling_mode
360275
if self.error_handling_mode in ["raise", "prune"]:
361276
# Prune the traceback to remove internal Dash calls
362277
tb = self._get_traceback(None, e)
363278
return Response(content=tb, media_type="text/html", status_code=500)
364279
return JSONResponse(
365280
status_code=500,
366-
content={"error": "InternalServerError", "message": "An internal server error occurred."},
281+
content={
282+
"error": "InternalServerError",
283+
"message": "An internal server error occurred.",
284+
},
367285
)
368286

369287
return middleware
@@ -417,27 +335,25 @@ async def serve(request: Request, package_name: str, fingerprinted_path: str):
417335
dash_app, package_name, fingerprinted_path, request
418336
)
419337

420-
# pylint: disable=protected-access
421-
dash_app._add_url(
422-
"_dash-component-suites/{package_name}/{fingerprinted_path:path}",
423-
serve,
424-
)
338+
name = "_dash-component-suites/{package_name}/{fingerprinted_path:path}"
339+
dash_app._add_url(name, serve) # pylint: disable=protected-access
425340

426-
# pylint: disable=unused-argument
427341
def dispatch(self, dash_app: Dash):
428342
async def _dispatch(request: Request):
429343
# pylint: disable=protected-access
430344
body = await request.json()
431-
g = dash_app._initialize_context(body) # pylint: disable=protected-access
345+
cb_ctx = dash_app._initialize_context(
346+
body
347+
) # pylint: disable=protected-access
432348
func = dash_app._prepare_callback(
433-
g, body
349+
cb_ctx, body
434350
) # pylint: disable=protected-access
435351
args = dash_app._inputs_to_vals(
436-
g.inputs_list + g.states_list
352+
cb_ctx.inputs_list + cb_ctx.states_list
437353
) # pylint: disable=protected-access
438354
ctx = copy_context()
439355
partial_func = dash_app._execute_callback(
440-
func, args, g.outputs_list, g
356+
func, args, cb_ctx.outputs_list, cb_ctx
441357
) # pylint: disable=protected-access
442358
response_data = ctx.run(partial_func)
443359
if inspect.iscoroutine(response_data):
@@ -494,20 +410,24 @@ def register_callback_api_routes(
494410
sig = inspect.signature(handler)
495411
param_names = list(sig.parameters.keys())
496412

497-
async def view_func(request: Request, body: dict = Body(...)):
498-
# Only pass expected params; ignore extras
499-
kwargs = {
500-
k: v for k, v in body.items() if k in param_names and v is not None
501-
}
502-
if inspect.iscoroutinefunction(handler):
503-
result = await handler(**kwargs)
504-
else:
505-
result = handler(**kwargs)
506-
return JSONResponse(content=result)
413+
def make_view_func(handler, param_names):
414+
async def view_func(_request: Request, body: dict = Body(...)):
415+
kwargs = {
416+
k: v
417+
for k, v in body.items()
418+
if k in param_names and v is not None
419+
}
420+
if inspect.iscoroutinefunction(handler):
421+
result = await handler(**kwargs)
422+
else:
423+
result = handler(**kwargs)
424+
return JSONResponse(content=result)
425+
426+
return view_func
507427

508428
self.server.add_api_route(
509429
route,
510-
view_func,
430+
make_view_func(handler, param_names),
511431
methods=methods,
512432
name=endpoint,
513433
include_in_schema=True,
@@ -566,5 +486,5 @@ def origin(self):
566486
def path(self):
567487
return self._request.url.path
568488

569-
async def get_json(self): # async method retained
570-
return await self._request.json()
489+
def get_json(self):
490+
return asyncio.run(self._request.json())

0 commit comments

Comments
 (0)