Skip to content

Commit fe6b30a

Browse files
committed
allowing callbacks to be exposed as api's by providing a endpoint.
1 parent dd74965 commit fe6b30a

File tree

2 files changed

+54
-1
lines changed

2 files changed

+54
-1
lines changed

dash/_callback.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def is_no_update(obj):
7272
GLOBAL_CALLBACK_LIST = []
7373
GLOBAL_CALLBACK_MAP = {}
7474
GLOBAL_INLINE_SCRIPTS = []
75+
GLOBAL_API_PATHS = {}
7576

7677

7778
# pylint: disable=too-many-locals
@@ -87,6 +88,7 @@ def callback(
8788
cache_args_to_ignore: Optional[list] = None,
8889
cache_ignore_triggered=True,
8990
on_error: Optional[Callable[[Exception], Any]] = None,
91+
api_path: Optional[str] = None,
9092
**_kwargs,
9193
) -> Callable[..., Any]:
9294
"""
@@ -178,6 +180,7 @@ def callback(
178180
)
179181
callback_map = _kwargs.pop("callback_map", GLOBAL_CALLBACK_MAP)
180182
callback_list = _kwargs.pop("callback_list", GLOBAL_CALLBACK_LIST)
183+
callback_api_paths = _kwargs.pop("callback_api_paths", GLOBAL_API_PATHS)
181184

182185
if background:
183186
background_spec: Any = {
@@ -217,12 +220,14 @@ def callback(
217220
callback_list,
218221
callback_map,
219222
config_prevent_initial_callbacks,
223+
callback_api_paths,
220224
*_args,
221225
**_kwargs,
222226
background=background_spec,
223227
manager=manager,
224228
running=running,
225229
on_error=on_error,
230+
api_path=api_path,
226231
)
227232

228233

@@ -585,7 +590,12 @@ def _prepare_response(
585590

586591
# pylint: disable=too-many-branches,too-many-statements
587592
def register_callback(
588-
callback_list, callback_map, config_prevent_initial_callbacks, *_args, **_kwargs
593+
callback_list,
594+
callback_map,
595+
config_prevent_initial_callbacks,
596+
callback_api_paths,
597+
*_args,
598+
**_kwargs,
589599
):
590600
(
591601
output,
@@ -638,6 +648,10 @@ def register_callback(
638648

639649
# pylint: disable=too-many-locals
640650
def wrap_func(func):
651+
if _kwargs.get("api_path"):
652+
api_path = _kwargs.get("api_path")
653+
callback_api_paths[api_path] = func
654+
641655
if background is None:
642656
background_key = None
643657
else:

dash/dash.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ def __init__( # pylint: disable=too-many-statements
568568
self.callback_map = {}
569569
# same deps as a list to catch duplicate outputs, and to send to the front end
570570
self._callback_list = []
571+
self.callback_api_paths = {}
571572

572573
# list of inline scripts
573574
self._inline_scripts = []
@@ -778,6 +779,42 @@ def _setup_routes(self):
778779
# catch-all for front-end routes, used by dcc.Location
779780
self._add_url("<path:path>", self.index)
780781

782+
def setup_apis(self):
783+
# Copy over global callback data structures assigned with `dash.callback`
784+
for k in list(_callback.GLOBAL_API_PATHS):
785+
if k in self.callback_api_paths:
786+
raise DuplicateCallback(
787+
f"The callback `{k}` provided with `dash.callback` was already "
788+
"assigned with `app.callback`."
789+
)
790+
self.callback_api_paths[k] = _callback.GLOBAL_API_PATHS.pop(k)
791+
792+
def make_parse_body(func):
793+
def _parse_body():
794+
if flask.request.is_json:
795+
data = flask.request.get_json()
796+
return flask.jsonify(func(**data))
797+
return flask.jsonify({})
798+
799+
return _parse_body
800+
801+
def make_parse_body_async(func):
802+
async def _parse_body_async():
803+
if flask.request.is_json:
804+
data = flask.request.get_json()
805+
result = await func(**data)
806+
return flask.jsonify(result)
807+
return flask.jsonify({})
808+
809+
return _parse_body_async
810+
811+
for path, func in self.callback_api_paths.items():
812+
print(path)
813+
if asyncio.iscoroutinefunction(func):
814+
self._add_url(path, make_parse_body_async(func), ["POST"])
815+
else:
816+
self._add_url(path, make_parse_body(func), ["POST"])
817+
781818
def _setup_plotlyjs(self):
782819
# pylint: disable=import-outside-toplevel
783820
from plotly.offline import get_plotlyjs_version
@@ -1346,6 +1383,7 @@ def callback(self, *_args, **_kwargs) -> Callable[..., Any]:
13461383
config_prevent_initial_callbacks=self.config.prevent_initial_callbacks,
13471384
callback_list=self._callback_list,
13481385
callback_map=self.callback_map,
1386+
callback_api_paths=self.callback_api_paths,
13491387
**_kwargs,
13501388
)
13511389

@@ -1496,6 +1534,7 @@ def dispatch(self):
14961534
def _setup_server(self):
14971535
if self._got_first_request["setup_server"]:
14981536
return
1537+
14991538
self._got_first_request["setup_server"] = True
15001539

15011540
# Apply _force_eager_loading overrides from modules

0 commit comments

Comments
 (0)