dash-devtools-plus 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,632 @@
1
+ """Dash hooks registration for Dash Devtools Plus."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ import re
7
+ import shutil
8
+ import sys
9
+ from functools import lru_cache, wraps
10
+ from pathlib import Path, PurePosixPath, PureWindowsPath
11
+ from threading import Lock
12
+ from typing import Any, Optional, Union
13
+ from urllib.parse import quote
14
+ from weakref import WeakKeyDictionary, ref
15
+
16
+ from dash import get_app, hooks
17
+
18
+ from .hook_inventory import build_hook_inventory, capture_app_hook_snapshot
19
+ from .server_metrics import collect_server_metrics
20
+
21
+
22
+ _LOCK = Lock()
23
+ _REGISTERED = False
24
+ _DEBUG_STATE_LOCK = Lock()
25
+ _APP_DEBUG_STATE: WeakKeyDictionary[Any, bool] = WeakKeyDictionary()
26
+ _CLIENTSIDE_SOURCE_LOCK = Lock()
27
+ _APP_CLIENTSIDE_SOURCES: WeakKeyDictionary[Any, dict[str, dict[str, Any]]] = (
28
+ WeakKeyDictionary()
29
+ )
30
+ _CONFIG: dict[str, Any] = {
31
+ "defaultLocale": "en",
32
+ "accentColor": "#119DFF",
33
+ }
34
+ _CALLBACKS_ROUTE = "_dash-devtools-plus/callbacks"
35
+ _COMPONENT_LIBRARIES_ROUTE = "_dash-devtools-plus/component-libraries"
36
+ _HOOK_LIBRARIES_ROUTE = "_dash-devtools-plus/hook-libraries"
37
+ _SERVER_METRICS_ROUTE = "_dash-devtools-plus/server-metrics"
38
+ _COMPONENT_LIBRARY_SIGNATURE = frozenset({"_component", "_dash", "_js_dist"})
39
+ _PROJECT_MARKERS = ("pyproject.toml", "setup.py", "setup.cfg", ".git")
40
+ _EDITOR_ORDER = ("vscode", "cursor", "pycharm")
41
+ _EDITOR_LABELS = {
42
+ "vscode": "VS Code",
43
+ "cursor": "Cursor",
44
+ "pycharm": "PyCharm",
45
+ }
46
+ _EDITOR_COMMANDS = {
47
+ "vscode": ("code",),
48
+ "cursor": ("cursor",),
49
+ "pycharm": ("pycharm", "pycharm-professional", "pycharm-community"),
50
+ }
51
+ _SUPPORTED_EDITORS = frozenset(_EDITOR_LABELS)
52
+ _EDITOR: Optional[str] = "vscode"
53
+ _PROJECT_ROOT: Optional[Path] = None
54
+ _EDITOR_PROJECT_ROOT: Optional[str] = None
55
+
56
+
57
+ def configure_devtools_plus(
58
+ *,
59
+ default_locale: str = "en",
60
+ accent_color: str = "#119DFF",
61
+ editor: Optional[str] = "vscode",
62
+ project_root: Optional[Union[str, Path]] = None,
63
+ editor_project_root: Optional[Union[str, Path]] = None,
64
+ ) -> None:
65
+ """Configure Devtools Plus before constructing the :class:`dash.Dash` app.
66
+
67
+ Parameters are deliberately small and serialisable because Dash passes them
68
+ directly to the custom React component.
69
+ """
70
+
71
+ global _EDITOR, _PROJECT_ROOT, _EDITOR_PROJECT_ROOT
72
+
73
+ if default_locale not in {"en", "zh-CN"}:
74
+ raise ValueError("default_locale must be 'en' or 'zh-CN'")
75
+ if editor not in {*_SUPPORTED_EDITORS, None}:
76
+ supported = ", ".join(repr(name) for name in _EDITOR_ORDER)
77
+ raise ValueError(f"editor must be one of {supported}, or None")
78
+
79
+ resolved_project_root = None
80
+ if project_root is not None:
81
+ resolved_project_root = Path(project_root).expanduser().resolve()
82
+ if not resolved_project_root.is_dir():
83
+ raise ValueError("project_root must point to an existing directory")
84
+
85
+ client_root = None
86
+ if editor_project_root is not None:
87
+ client_root = str(editor_project_root).strip()
88
+ if not client_root:
89
+ raise ValueError("editor_project_root must not be empty")
90
+
91
+ _EDITOR = editor
92
+ _PROJECT_ROOT = resolved_project_root
93
+ _EDITOR_PROJECT_ROOT = client_root
94
+ _CONFIG.update(
95
+ defaultLocale=default_locale,
96
+ accentColor=accent_color,
97
+ )
98
+ register()
99
+
100
+
101
+ def _component_props() -> dict[str, Any]:
102
+ try:
103
+ app = get_app()
104
+ except Exception: # Dash can call props outside an active app context.
105
+ app = None
106
+
107
+ return _component_props_for(app)
108
+
109
+
110
+ def _component_props_for(app: Any | None) -> dict[str, Any]:
111
+ """Build props for one app so multi-app processes cannot share debug state."""
112
+
113
+ return {
114
+ **_CONFIG,
115
+ "enabled": bool(app is not None and _devtools_enabled(app)),
116
+ "callbacksEndpoint": _CALLBACKS_ROUTE,
117
+ "componentLibrariesEndpoint": _COMPONENT_LIBRARIES_ROUTE,
118
+ "hookLibrariesEndpoint": _HOOK_LIBRARIES_ROUTE,
119
+ "serverMetricsEndpoint": _SERVER_METRICS_ROUTE,
120
+ }
121
+
122
+
123
+ def _set_debug_state(app: Any, enabled: bool) -> None:
124
+ """Remember the explicit ``debug`` value resolved by Dash for one app."""
125
+
126
+ try:
127
+ with _DEBUG_STATE_LOCK:
128
+ _APP_DEBUG_STATE[app] = bool(enabled)
129
+ except TypeError:
130
+ # Keep compatibility if a future Dash app becomes non-weakrefable.
131
+ return
132
+
133
+
134
+ def _tracked_debug_state(app: Any) -> bool:
135
+ try:
136
+ with _DEBUG_STATE_LOCK:
137
+ return bool(_APP_DEBUG_STATE.get(app, False))
138
+ except TypeError:
139
+ return False
140
+
141
+
142
+ def _backend_debug_state(app: Any) -> bool:
143
+ """Read the live backend debug flag without assuming Flask internals."""
144
+
145
+ backend_server = getattr(getattr(app, "backend", None), "server", None)
146
+ if backend_server is None:
147
+ backend_server = getattr(app, "server", None)
148
+ return bool(getattr(backend_server, "debug", False))
149
+
150
+
151
+ def _devtools_enabled(app: Any) -> bool:
152
+ """Require both debug mode and the native Dash Dev Tools UI."""
153
+
154
+ dev_tools = getattr(app, "_dev_tools", {}) # pylint: disable=protected-access
155
+ ui_enabled = bool(getattr(dev_tools, "get", lambda *_: False)("ui"))
156
+ debug_enabled = _tracked_debug_state(app) or _backend_debug_state(app)
157
+ return debug_enabled and ui_enabled
158
+
159
+
160
+ def _install_debug_tracker(app: Any) -> None:
161
+ """Track Dash's resolved debug value without retaining application objects."""
162
+
163
+ original = app.enable_dev_tools
164
+ if getattr(original, "_dash_devtools_plus_debug_tracker", False):
165
+ return
166
+
167
+ @wraps(original)
168
+ def tracked_enable_dev_tools(*args: Any, **kwargs: Any) -> bool:
169
+ debug_enabled = original(*args, **kwargs)
170
+ _set_debug_state(app, bool(debug_enabled))
171
+ return debug_enabled
172
+
173
+ tracked_enable_dev_tools._dash_devtools_plus_debug_tracker = True # type: ignore[attr-defined]
174
+ app.enable_dev_tools = tracked_enable_dev_tools
175
+ _set_debug_state(app, _backend_debug_state(app))
176
+
177
+
178
+ def _bind_component_props(app: Any) -> None:
179
+ """Bind the cloned Devtools Plus registration to its owning Dash app."""
180
+
181
+ app_reference = ref(app)
182
+
183
+ def app_component_props() -> dict[str, Any]:
184
+ return _component_props_for(app_reference())
185
+
186
+ app_component_props.__module__ = __name__
187
+ app_component_props.__name__ = "app_component_props"
188
+ for devtools_hook in app._hooks.get_hooks("dev_tools"): # pylint: disable=protected-access
189
+ if devtools_hook.get("namespace") == "DashDevtoolsPlus":
190
+ devtools_hook["props"] = app_component_props
191
+
192
+
193
+ def _setup_app(app: Any) -> None:
194
+ """Install the security boundary before capturing hook registrations."""
195
+
196
+ _install_debug_tracker(app)
197
+ _bind_component_props(app)
198
+ _install_clientside_source_tracker(app)
199
+ capture_app_hook_snapshot(app)
200
+
201
+
202
+ def _harden_response(response: Any) -> Any:
203
+ """Prevent sensitive development metadata from being cached or sniffed."""
204
+
205
+ headers = getattr(response, "headers", None)
206
+ if headers is not None:
207
+ headers["Cache-Control"] = "no-store, max-age=0"
208
+ headers["Pragma"] = "no-cache"
209
+ headers["X-Content-Type-Options"] = "nosniff"
210
+ return response
211
+
212
+
213
+ def _debug_only_response(app: Any, payload_factory: Any) -> Any:
214
+ """Serve a payload only inside an explicitly debug-enabled Dash app."""
215
+
216
+ if not _devtools_enabled(app):
217
+ return _harden_response(app.backend.make_response("Not Found", status=404))
218
+ return _harden_response(app.backend.jsonify(payload_factory()))
219
+
220
+
221
+ def _is_relative_to(path: Path, parent: Path) -> bool:
222
+ try:
223
+ path.relative_to(parent)
224
+ except ValueError:
225
+ return False
226
+ return True
227
+
228
+
229
+ def _source_root(source_file: Path) -> Optional[Path]:
230
+ """Find a project boundary without exposing paths outside the project."""
231
+
232
+ if _PROJECT_ROOT is not None:
233
+ return _PROJECT_ROOT if _is_relative_to(source_file, _PROJECT_ROOT) else None
234
+
235
+ candidates = (Path.cwd().resolve(), *source_file.parents)
236
+ for candidate in candidates:
237
+ if not _is_relative_to(source_file, candidate):
238
+ continue
239
+ if any((candidate / marker).exists() for marker in _PROJECT_MARKERS):
240
+ return candidate
241
+ return source_file.parent
242
+
243
+
244
+ def _editor_uri(
245
+ editor: str, source_file: Path, source_root: Path, line: int
246
+ ) -> Optional[str]:
247
+ """Build an IDE URI for a verified file contained by the project root."""
248
+
249
+ if editor not in _SUPPORTED_EDITORS or not _is_relative_to(source_file, source_root):
250
+ return None
251
+
252
+ relative_path = source_file.relative_to(source_root)
253
+ editor_root = _EDITOR_PROJECT_ROOT or str(source_root)
254
+ is_windows_path = (
255
+ bool(re.match(r"^[A-Za-z]:[\\/]", editor_root)) or "\\" in editor_root
256
+ )
257
+ if is_windows_path:
258
+ target_path = PureWindowsPath(editor_root, *relative_path.parts).as_posix()
259
+ else:
260
+ target_path = PurePosixPath(editor_root, *relative_path.parts).as_posix()
261
+
262
+ encoded_path = quote(target_path, safe="/:")
263
+ if editor == "pycharm":
264
+ return f"pycharm://open?file={encoded_path}&line={line}&column=1"
265
+
266
+ uri_path = encoded_path if encoded_path.startswith("/") else f"/{encoded_path}"
267
+ return f"{editor}://file{uri_path}:{line}:1"
268
+
269
+
270
+ def _windows_url_protocol_registered(scheme: str) -> bool:
271
+ """Check the merged Windows URL protocol registry without launching an app."""
272
+
273
+ try:
274
+ import winreg # pylint: disable=import-outside-toplevel
275
+
276
+ with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, scheme) as protocol_key:
277
+ try:
278
+ winreg.QueryValueEx(protocol_key, "URL Protocol")
279
+ except OSError:
280
+ return False
281
+
282
+ with winreg.OpenKey(
283
+ winreg.HKEY_CLASSES_ROOT, rf"{scheme}\shell\open\command"
284
+ ) as command_key:
285
+ command, _ = winreg.QueryValueEx(command_key, None)
286
+ return bool(str(command).strip())
287
+ except (ImportError, OSError):
288
+ return False
289
+
290
+
291
+ @lru_cache(maxsize=len(_EDITOR_ORDER))
292
+ def _editor_supported(editor: str) -> bool:
293
+ """Best-effort detection of IDE URL handlers on the Dash server host."""
294
+
295
+ if editor not in _SUPPORTED_EDITORS:
296
+ return False
297
+ if sys.platform == "win32":
298
+ return _windows_url_protocol_registered(editor)
299
+
300
+ if any(shutil.which(command) for command in _EDITOR_COMMANDS[editor]):
301
+ return True
302
+ if sys.platform != "darwin":
303
+ return False
304
+
305
+ application_names = {
306
+ "vscode": ("Visual Studio Code.app",),
307
+ "cursor": ("Cursor.app",),
308
+ "pycharm": ("PyCharm.app", "PyCharm Professional.app", "PyCharm CE.app"),
309
+ }
310
+ application_roots = (Path("/Applications"), Path.home() / "Applications")
311
+ return any(
312
+ (root / application_name).is_dir()
313
+ for root in application_roots
314
+ for application_name in application_names[editor]
315
+ )
316
+
317
+
318
+ def _editor_targets(
319
+ source_file: Path, source_root: Path, line: int
320
+ ) -> list[dict[str, Any]]:
321
+ """Return trusted IDE targets, with the configured preference listed first."""
322
+
323
+ if _EDITOR is None:
324
+ return []
325
+
326
+ editor_order = (_EDITOR, *(_editor for _editor in _EDITOR_ORDER if _editor != _EDITOR))
327
+ targets = []
328
+ for editor in editor_order:
329
+ uri = _editor_uri(editor, source_file, source_root, line)
330
+ if uri is not None:
331
+ targets.append(
332
+ {
333
+ "id": editor,
334
+ "label": _EDITOR_LABELS[editor],
335
+ "uri": uri,
336
+ "supported": _editor_supported(editor),
337
+ }
338
+ )
339
+ return targets
340
+
341
+
342
+ def _source_location(
343
+ source_path_value: str,
344
+ line: int,
345
+ *,
346
+ kind: str,
347
+ function: Optional[str],
348
+ docstring: Optional[str] = None,
349
+ ) -> dict[str, Any]:
350
+ """Build safe, project-relative source metadata for the debug endpoint."""
351
+
352
+ source_path = Path(source_path_value).resolve()
353
+ root = _source_root(source_path)
354
+ if root is None:
355
+ raise ValueError("callback source is outside the configured project root")
356
+ relative_path = source_path.relative_to(root).as_posix()
357
+ editor_targets = _editor_targets(source_path, root, line)
358
+ return {
359
+ "kind": kind,
360
+ "function": function,
361
+ "path": relative_path,
362
+ "line": line,
363
+ "editorUri": editor_targets[0]["uri"] if editor_targets else None,
364
+ "editorUris": editor_targets,
365
+ "docstring": docstring,
366
+ }
367
+
368
+
369
+ def _callback_docstring(callback: Any) -> Optional[str]:
370
+ """Return a display-ready Docstring while preserving relative indentation."""
371
+
372
+ try:
373
+ original = inspect.unwrap(callback)
374
+ value = getattr(original, "__doc__", None)
375
+ if not isinstance(value, str):
376
+ return None
377
+ cleaned = inspect.cleandoc(value)
378
+ return cleaned or None
379
+ except (AttributeError, TypeError, ValueError):
380
+ return None
381
+
382
+
383
+ def _callback_source(callback: Any) -> dict[str, Any]:
384
+ if callback is None:
385
+ return {
386
+ "kind": "clientside",
387
+ "function": None,
388
+ "path": None,
389
+ "line": None,
390
+ "editorUri": None,
391
+ "editorUris": [],
392
+ "docstring": None,
393
+ }
394
+
395
+ docstring = _callback_docstring(callback)
396
+ try:
397
+ original = inspect.unwrap(callback)
398
+ source_path_value = inspect.getsourcefile(original) or inspect.getfile(original)
399
+ line = inspect.getsourcelines(original)[1]
400
+ return _source_location(
401
+ source_path_value,
402
+ line,
403
+ kind="python",
404
+ function=getattr(original, "__name__", type(original).__name__),
405
+ docstring=docstring,
406
+ )
407
+ except (OSError, TypeError, ValueError):
408
+ return {
409
+ "kind": "unavailable",
410
+ "function": getattr(callback, "__name__", None),
411
+ "path": None,
412
+ "line": None,
413
+ "editorUri": None,
414
+ "editorUris": [],
415
+ "docstring": docstring,
416
+ }
417
+
418
+
419
+ def _clientside_registration_source(caller_frame: Any) -> dict[str, Any]:
420
+ """Describe the Python statement that registered a clientside callback."""
421
+
422
+ if caller_frame is None:
423
+ return _callback_source(None)
424
+ try:
425
+ return _source_location(
426
+ caller_frame.f_code.co_filename,
427
+ caller_frame.f_lineno,
428
+ kind="clientside-registration",
429
+ function="app.clientside_callback",
430
+ )
431
+ except (AttributeError, OSError, TypeError, ValueError):
432
+ return _callback_source(None)
433
+
434
+
435
+ def _install_clientside_source_tracker(app: Any) -> None:
436
+ """Capture each ``app.clientside_callback`` call site for this Dash app."""
437
+
438
+ original = app.clientside_callback
439
+ if getattr(original, "_dash_devtools_plus_source_tracker", False):
440
+ return
441
+
442
+ @wraps(original)
443
+ def tracked_clientside_callback(*args: Any, **kwargs: Any) -> Any:
444
+ current_frame = inspect.currentframe()
445
+ try:
446
+ source = _clientside_registration_source(
447
+ current_frame.f_back if current_frame is not None else None
448
+ )
449
+ finally:
450
+ del current_frame
451
+
452
+ with _CLIENTSIDE_SOURCE_LOCK:
453
+ dependency_count = len(app._callback_list) # pylint: disable=protected-access
454
+ result = original(*args, **kwargs)
455
+ new_dependencies = app._callback_list[dependency_count:] # pylint: disable=protected-access
456
+ app_sources = _APP_CLIENTSIDE_SOURCES.setdefault(app, {})
457
+ for dependency in new_dependencies:
458
+ callback_id = dependency.get("output")
459
+ if callback_id:
460
+ app_sources[callback_id] = source
461
+ return result
462
+
463
+ tracked_clientside_callback._dash_devtools_plus_source_tracker = True # type: ignore[attr-defined]
464
+ app.clientside_callback = tracked_clientside_callback
465
+
466
+
467
+ def _tracked_clientside_source(app: Any, callback_id: Any) -> dict[str, Any]:
468
+ try:
469
+ with _CLIENTSIDE_SOURCE_LOCK:
470
+ source = _APP_CLIENTSIDE_SOURCES.get(app, {}).get(callback_id)
471
+ except TypeError:
472
+ source = None
473
+ return dict(source) if source is not None else _callback_source(None)
474
+
475
+
476
+ def _callback_metadata(app: Any) -> list[dict[str, Any]]:
477
+ callbacks = []
478
+ for dependency in app._callback_list: # pylint: disable=protected-access
479
+ item = dict(dependency)
480
+ callback_id = item.get("output")
481
+ callback_entry = app.callback_map.get(callback_id, {})
482
+ if item.get("no_output"):
483
+ # Dash uses an internal hash as the registry key for callbacks with
484
+ # no declared Output. It is an implementation detail, not a role.
485
+ item["output"] = None
486
+ item["mcp_enabled"] = callback_entry.get("mcp_enabled")
487
+ if item.get("clientside_function"):
488
+ item["source"] = _tracked_clientside_source(app, callback_id)
489
+ else:
490
+ item["source"] = _callback_source(callback_entry.get("callback"))
491
+ callbacks.append(item)
492
+ return callbacks
493
+
494
+
495
+ def _serve_callback_metadata():
496
+ app = get_app()
497
+ return _debug_only_response(app, lambda: _callback_metadata(app))
498
+
499
+
500
+ def _component_library_metadata() -> list[dict[str, Any]]:
501
+ """Return loaded Dash component libraries identified by their signature."""
502
+
503
+ libraries: list[tuple[int, dict[str, Any]]] = []
504
+ seen_modules: set[int] = set()
505
+ for module_name, module in tuple(sys.modules.items()):
506
+ if module is None or id(module) in seen_modules:
507
+ continue
508
+ try:
509
+ if not _COMPONENT_LIBRARY_SIGNATURE.issubset(dir(module)):
510
+ continue
511
+
512
+ seen_modules.add(id(module))
513
+ package = getattr(module, "package", None)
514
+ package_metadata = package if isinstance(package, dict) else {}
515
+ display_name = (
516
+ package_metadata.get("name")
517
+ or getattr(module, "package_name", None)
518
+ or module_name
519
+ )
520
+ version = getattr(module, "__version__", None) or package_metadata.get(
521
+ "version"
522
+ )
523
+ exports = getattr(module, "__all__", ())
524
+ js_dist = getattr(module, "_js_dist", ())
525
+
526
+ libraries.append(
527
+ (
528
+ id(module),
529
+ {
530
+ "module": module_name,
531
+ "name": str(display_name),
532
+ "version": str(version) if version is not None else None,
533
+ "exports": len(exports) if hasattr(exports, "__len__") else None,
534
+ "jsAssets": len(js_dist) if hasattr(js_dist, "__len__") else None,
535
+ "scope": "core" if module_name.startswith("dash.") else "third-party",
536
+ },
537
+ )
538
+ )
539
+ except (AttributeError, RuntimeError, TypeError, ValueError):
540
+ continue
541
+
542
+ aliases: dict[int, set[str]] = {module_id: set() for module_id, _ in libraries}
543
+ project_root = Path.cwd().resolve()
544
+ for owner_module in tuple(sys.modules.values()):
545
+ source_file = getattr(owner_module, "__file__", None)
546
+ if not source_file:
547
+ continue
548
+ try:
549
+ if not _is_relative_to(Path(source_file).resolve(), project_root):
550
+ continue
551
+ namespace = tuple(vars(owner_module).items())
552
+ except (OSError, RuntimeError, TypeError, ValueError):
553
+ continue
554
+
555
+ for alias, value in namespace:
556
+ module_aliases = aliases.get(id(value))
557
+ if (
558
+ module_aliases is not None
559
+ and alias.isidentifier()
560
+ and not alias.startswith("_")
561
+ ):
562
+ module_aliases.add(alias)
563
+
564
+ records = []
565
+ for module_id, library in libraries:
566
+ library["aliases"] = sorted(aliases[module_id], key=str.casefold)
567
+ records.append(library)
568
+
569
+ return sorted(
570
+ records,
571
+ key=lambda item: (item["scope"] != "core", item["name"].casefold()),
572
+ )
573
+
574
+
575
+ def _serve_component_library_metadata():
576
+ app = get_app()
577
+ return _debug_only_response(app, _component_library_metadata)
578
+
579
+
580
+ def _serve_hook_library_metadata():
581
+ app = get_app()
582
+ return _debug_only_response(app, lambda: build_hook_inventory(app))
583
+
584
+
585
+ def _serve_server_metrics():
586
+ app = get_app()
587
+ return _debug_only_response(app, collect_server_metrics)
588
+
589
+
590
+ def register() -> None:
591
+ """Register assets and the toolbar component exactly once."""
592
+
593
+ global _REGISTERED
594
+ with _LOCK:
595
+ if _REGISTERED:
596
+ return
597
+
598
+ hooks.script(
599
+ [
600
+ {
601
+ "dev_package_path": "assets/dash_devtools_plus.js",
602
+ "namespace": "dash_devtools_plus",
603
+ "dev_only": True,
604
+ }
605
+ ]
606
+ )
607
+ hooks.stylesheet(
608
+ [
609
+ {
610
+ "dev_package_path": "assets/dash_devtools_plus.css",
611
+ "namespace": "dash_devtools_plus",
612
+ "dev_only": True,
613
+ }
614
+ ]
615
+ )
616
+
617
+ hooks.route(name=_CALLBACKS_ROUTE)(_serve_callback_metadata)
618
+ hooks.route(name=_COMPONENT_LIBRARIES_ROUTE)(
619
+ _serve_component_library_metadata
620
+ )
621
+ hooks.route(name=_HOOK_LIBRARIES_ROUTE)(_serve_hook_library_metadata)
622
+ hooks.route(name=_SERVER_METRICS_ROUTE)(_serve_server_metrics)
623
+ hooks.setup(priority=sys.maxsize)(_setup_app)
624
+
625
+ hooks.devtool(
626
+ namespace="DashDevtoolsPlus",
627
+ component_type="DevtoolsPlus",
628
+ props=_component_props,
629
+ position="left",
630
+ )
631
+
632
+ _REGISTERED = True