Skip to content

Commit 9ffba5a

Browse files
committed
fixing issue with lint and debug ui
1 parent 4920e33 commit 9ffba5a

File tree

3 files changed

+144
-129
lines changed

3 files changed

+144
-129
lines changed

dash/dash.py

Lines changed: 14 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,17 @@
44
import collections
55
import importlib
66
import warnings
7-
from contextvars import copy_context
87
from importlib.machinery import ModuleSpec
98
from importlib.util import find_spec
109
from importlib import metadata
1110
import pkgutil
1211
import threading
1312
import re
1413
import logging
15-
import time
1614
import mimetypes
1715
import hashlib
1816
import base64
1917
import traceback
20-
import inspect
2118
from urllib.parse import urlparse
2219
from typing import Any, Callable, Dict, Optional, Union, Sequence, Literal, List
2320

@@ -30,7 +27,7 @@
3027
from dash import html
3128
from dash import dash_table
3229

33-
from .fingerprint import build_fingerprint, check_fingerprint
30+
from .fingerprint import build_fingerprint
3431
from .resources import Scripts, Css
3532
from .dependencies import (
3633
Input,
@@ -39,8 +36,6 @@
3936
)
4037
from .development.base_component import ComponentRegistry
4138
from .exceptions import (
42-
PreventUpdate,
43-
InvalidResourceError,
4439
ProxyError,
4540
DuplicateCallback,
4641
)
@@ -72,7 +67,7 @@
7267
from .server_factories.flask_factory import FlaskServerFactory
7368
from .server_factories.base_factory import BaseServerFactory
7469

75-
from ._get_app import with_app_context, with_app_context_async, with_app_context_factory
70+
from ._get_app import with_app_context, with_app_context_factory
7671
from ._grouping import map_grouping, grouping_len, update_args_group
7772
from ._obsolete import ObsoleteChecker
7873

@@ -712,8 +707,9 @@ def init_app(self, app: Optional[Any] = None, **kwargs) -> None:
712707
)
713708
if config.compress:
714709
try:
715-
from flask_compress import Compress
710+
import flask_compress # pylint: disable=import-outside-toplevel
716711

712+
Compress = flask_compress.Compress
717713
Compress(self.server)
718714
_flask_compress_version = parse_version(
719715
_get_distribution_version("flask_compress")
@@ -754,7 +750,10 @@ def _setup_routes(self):
754750
["POST"],
755751
)
756752
self._add_url("_reload-hash", self.serve_reload_hash)
757-
self._add_url("_favicon.ico", self.server_factory._serve_default_favicon)
753+
self._add_url(
754+
"_favicon.ico",
755+
self.server_factory._serve_default_favicon, # pylint: disable=protected-access
756+
)
758757
self.server_factory.setup_index(self.server, self)
759758
self.server_factory.setup_catchall(self.server, self)
760759

@@ -1145,7 +1144,7 @@ def _generate_meta(self):
11451144

11461145
return meta_tags + self.config.meta_tags
11471146

1148-
def render_index(self, *args, **kwargs):
1147+
def render_index(self, *_args, **_kwargs):
11491148
scripts = self._generate_scripts_html()
11501149
css = self._generate_css_dist_html()
11511150
config = self._generate_config_html()
@@ -1845,6 +1844,7 @@ def enable_dev_tools(
18451844
dev_tools_silence_routes_logging: Optional[bool] = None,
18461845
dev_tools_disable_version_check: Optional[bool] = None,
18471846
dev_tools_prune_errors: Optional[bool] = None,
1847+
first_run: bool = True,
18481848
) -> bool:
18491849
"""Activate the dev tools, called by `run`. If your application
18501850
is served by wsgi and you want to activate the dev tools, you can call
@@ -2009,53 +2009,12 @@ def enable_dev_tools(
20092009
)
20102010
elif dev_tools.prune_errors:
20112011
secret = gen_salt(20)
2012-
2013-
if hasattr(self.server, "errorhandler"):
2014-
# Flask
2015-
@self.server.errorhandler(Exception)
2016-
def _wrap_errors(error):
2017-
tb = _get_traceback(secret, error)
2018-
return tb, 500
2019-
2020-
elif hasattr(self.server, "exception_handler"):
2021-
# FastAPI
2022-
@self.server.exception_handler(Exception)
2023-
async def _wrap_errors(request, error):
2024-
tb = _get_traceback(secret, error)
2025-
from fastapi.responses import PlainTextResponse
2026-
2027-
return PlainTextResponse(tb, status_code=500)
2012+
self.server_factory.register_prune_error_handler(
2013+
self.server, secret, _get_traceback
2014+
)
20282015

20292016
if debug and dev_tools.ui:
2030-
2031-
def _before_request():
2032-
flask.g.timing_information = { # pylint: disable=assigning-non-slot
2033-
"__dash_server": {"dur": time.time(), "desc": None}
2034-
}
2035-
2036-
def _after_request(response):
2037-
timing_information = flask.g.get("timing_information", None)
2038-
if timing_information is None:
2039-
return response
2040-
2041-
dash_total = timing_information.get("__dash_server", None)
2042-
if dash_total is not None:
2043-
dash_total["dur"] = round((time.time() - dash_total["dur"]) * 1000)
2044-
2045-
for name, info in timing_information.items():
2046-
value = name
2047-
if info.get("desc") is not None:
2048-
value += f';desc="{info["desc"]}"'
2049-
2050-
if info.get("dur") is not None:
2051-
value += f";dur={info['dur']}"
2052-
2053-
response.headers.add("Server-Timing", value)
2054-
2055-
return response
2056-
2057-
self.server_factory.before_request(self.server, _before_request)
2058-
self.server_factory.after_request(self.server, _after_request)
2017+
self.server_factory.register_timing_hooks(self.server, first_run)
20592018

20602019
if (
20612020
debug

dash/server_factories/fastapi_factory.py

Lines changed: 82 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
1-
import traceback
2-
3-
from fastapi import FastAPI, Request, Response, APIRouter
4-
from fastapi.responses import JSONResponse
5-
from dash.exceptions import PreventUpdate, InvalidResourceError
6-
from dash.server_factories import set_request_adapter, get_request_adapter
7-
from .base_factory import BaseServerFactory
1+
import sys
2+
import mimetypes
3+
import hashlib
84
import inspect
95
import pkgutil
106
from contextvars import copy_context
117
import importlib.util
8+
import time
9+
import uvicorn
10+
from fastapi import FastAPI, Request, Response
11+
from fastapi.responses import JSONResponse, PlainTextResponse
12+
from fastapi.staticfiles import StaticFiles
13+
from starlette.responses import Response as StarletteResponse
14+
from starlette.datastructures import MutableHeaders
15+
from dash.fingerprint import check_fingerprint
16+
from dash import _validate
17+
from dash.exceptions import PreventUpdate, InvalidResourceError
18+
from dash.server_factories import set_request_adapter
19+
from .base_factory import BaseServerFactory
1220

1321

1422
class FastAPIServerFactory(BaseServerFactory):
@@ -32,8 +40,6 @@ def create_app(self, name="__main__", config=None):
3240
def register_assets_blueprint(
3341
self, app, blueprint_name, assets_url_path, assets_folder
3442
):
35-
from fastapi.staticfiles import StaticFiles
36-
3743
try:
3844
app.mount(
3945
assets_url_path,
@@ -46,17 +52,21 @@ def register_assets_blueprint(
4652

4753
def register_error_handlers(self, app):
4854
@app.exception_handler(PreventUpdate)
49-
async def _handle_error(request: Request, exc: PreventUpdate):
55+
async def _handle_error(_request, _exc):
5056
return Response(status_code=204)
5157

5258
@app.exception_handler(InvalidResourceError)
53-
async def _invalid_resources_handler(
54-
request: Request, exc: InvalidResourceError
55-
):
59+
async def _invalid_resources_handler(_request, exc):
5660
return Response(content=exc.args[0], status_code=404)
5761

62+
def register_prune_error_handler(self, app, secret, get_traceback_func):
63+
@app.exception_handler(Exception)
64+
async def _wrap_errors(_error_request, error):
65+
tb = get_traceback_func(secret, error)
66+
return PlainTextResponse(tb, status_code=500)
67+
5868
def _html_response_wrapper(self, view_func):
59-
async def wrapped(*args, **kwargs):
69+
async def wrapped(*_args, **_kwargs):
6070
# If view_func is a function, call it; if it's a string, use it directly
6171
html = view_func() if callable(view_func) else view_func
6272
return Response(content=html, media_type="text/html")
@@ -75,10 +85,11 @@ async def index(request: Request):
7585
def setup_catchall(self, app, dash_app):
7686
@dash_app.server.on_event("startup")
7787
def _setup_catchall():
78-
dash_app.enable_dev_tools(**self.config) # do this to make sure dev tools are enabled
79-
from fastapi import Request, Response
88+
dash_app.enable_dev_tools(
89+
**self.config, first_run=False
90+
) # do this to make sure dev tools are enabled
8091

81-
async def catchall(path: str, request: Request):
92+
async def catchall(_path: str, request: Request):
8293
adapter = FastAPIRequestAdapter()
8394
set_request_adapter(adapter)
8495
adapter.set_request(request)
@@ -88,8 +99,6 @@ async def catchall(path: str, request: Request):
8899
app, "/{path:path}", catchall, endpoint="catchall", methods=["GET"]
89100
)
90101

91-
pass # catchall needs to be last to not override other routes
92-
93102
def add_url_rule(self, app, rule, view_func, endpoint=None, methods=None):
94103
if rule == "":
95104
rule = "/"
@@ -114,11 +123,7 @@ def after_request(self, app, func):
114123

115124
def run(self, app, host, port, debug, **kwargs):
116125
frame = inspect.stack()[2]
117-
import uvicorn
118-
119-
self.config = dict({'debug': debug} if debug else {}, **kwargs)
120-
121-
126+
self.config = dict({"debug": debug} if debug else {}, **kwargs)
122127
reload = debug
123128
if reload:
124129
# Dynamically determine the module name from the file path
@@ -149,8 +154,6 @@ def get_request_adapter(self):
149154
return FastAPIRequestAdapter
150155

151156
def _make_before_middleware(self, func):
152-
pass
153-
154157
async def middleware(request, call_next):
155158
if func is not None:
156159
if inspect.iscoroutinefunction(func):
@@ -163,24 +166,20 @@ async def middleware(request, call_next):
163166
return middleware
164167

165168
def _make_after_middleware(self, func):
166-
pass
167-
168169
async def middleware(request, call_next):
169170
response = await call_next(request)
170-
await func()
171+
if func is not None:
172+
if inspect.iscoroutinefunction(func):
173+
await func()
174+
else:
175+
func()
171176
return response
172177

173178
return middleware
174179

175180
def serve_component_suites(
176181
self, dash_app, package_name, fingerprinted_path, request
177182
):
178-
import sys
179-
import mimetypes
180-
import pkgutil
181-
from dash.fingerprint import check_fingerprint
182-
from dash import _validate
183-
184183
path_in_pkg, has_fingerprint = check_fingerprint(fingerprinted_path)
185184
_validate.validate_js_path(dash_app.registered_paths, package_name, path_in_pkg)
186185
extension = "." + path_in_pkg.split(".")[-1]
@@ -194,24 +193,17 @@ def serve_component_suites(
194193
package.__path__,
195194
)
196195
data = pkgutil.get_data(package_name, path_in_pkg)
197-
from starlette.responses import Response as StarletteResponse
198-
199196
headers = {}
200197
if has_fingerprint:
201198
headers["Cache-Control"] = "public, max-age=31536000"
202199
return StarletteResponse(content=data, media_type=mimetype, headers=headers)
203-
else:
204-
import hashlib
205-
206-
etag = hashlib.md5(data).hexdigest() if data else ""
207-
headers["ETag"] = etag
208-
if request.headers.get("if-none-match") == etag:
209-
return StarletteResponse(status_code=304)
210-
return StarletteResponse(content=data, media_type=mimetype, headers=headers)
200+
etag = hashlib.md5(data).hexdigest() if data else ""
201+
headers["ETag"] = etag
202+
if request.headers.get("if-none-match") == etag:
203+
return StarletteResponse(status_code=304)
204+
return StarletteResponse(content=data, media_type=mimetype, headers=headers)
211205

212206
def setup_component_suites(self, app, dash_app):
213-
from fastapi import Request
214-
215207
async def serve(request: Request, package_name: str, fingerprinted_path: str):
216208
return self.serve_component_suites(
217209
dash_app, package_name, fingerprinted_path, request
@@ -223,17 +215,26 @@ async def serve(request: Request, package_name: str, fingerprinted_path: str):
223215
serve,
224216
)
225217

226-
def dispatch(self, app, dash_app, use_async):
218+
def dispatch(self, _app, dash_app, _use_async):
227219
async def _dispatch(request: Request):
228220
adapter = FastAPIRequestAdapter()
229221
set_request_adapter(adapter)
230222
adapter.set_request(request)
223+
# pylint: disable=protected-access
231224
body = await request.json()
232-
g = dash_app._initialize_context(body, adapter)
233-
func = dash_app._prepare_callback(g, body)
234-
args = dash_app._inputs_to_vals(g.inputs_list + g.states_list)
225+
g = dash_app._initialize_context(
226+
body, adapter
227+
) # pylint: disable=protected-access
228+
func = dash_app._prepare_callback(
229+
g, body
230+
) # pylint: disable=protected-access
231+
args = dash_app._inputs_to_vals(
232+
g.inputs_list + g.states_list
233+
) # pylint: disable=protected-access
235234
ctx = copy_context()
236-
partial_func = dash_app._execute_callback(func, args, g.outputs_list, g)
235+
partial_func = dash_app._execute_callback(
236+
func, args, g.outputs_list, g
237+
) # pylint: disable=protected-access
237238
response_data = ctx.run(partial_func)
238239
if inspect.iscoroutine(response_data):
239240
response_data = await response_data
@@ -247,6 +248,33 @@ def _serve_default_favicon(self):
247248
content=pkgutil.get_data("dash", "favicon.ico"), media_type="image/x-icon"
248249
)
249250

251+
def register_timing_hooks(self, app, first_run):
252+
if not first_run:
253+
return
254+
255+
@app.middleware("http")
256+
async def timing_middleware(request, call_next):
257+
# Before request
258+
request.state.timing_information = {
259+
"__dash_server": {"dur": time.time(), "desc": None}
260+
}
261+
response = await call_next(request)
262+
# After request
263+
timing_information = getattr(request.state, "timing_information", None)
264+
if timing_information is not None:
265+
dash_total = timing_information.get("__dash_server", None)
266+
if dash_total is not None:
267+
dash_total["dur"] = round((time.time() - dash_total["dur"]) * 1000)
268+
headers = MutableHeaders(response.headers)
269+
for name, info in timing_information.items():
270+
value = name
271+
if info.get("desc") is not None:
272+
value += f';desc="{info["desc"]}"'
273+
if info.get("dur") is not None:
274+
value += f";dur={info['dur']}"
275+
headers.append("Server-Timing", value)
276+
return response
277+
250278

251279
class FastAPIRequestAdapter:
252280
def __init__(self):
@@ -266,7 +294,7 @@ def is_json(self):
266294
"application/json"
267295
)
268296

269-
def get_cookies(self, request=None):
297+
def get_cookies(self, _request=None):
270298
return self._request.cookies
271299

272300
def get_headers(self):
@@ -282,4 +310,4 @@ def get_origin(self):
282310
return self._request.headers.get("origin")
283311

284312
def get_path(self):
285-
return self._request.url.path # <-- Add this method
313+
return self._request.url.path

0 commit comments

Comments
 (0)