Skip to content

Commit b247baa

Browse files
authored
Independent of dependency version, serve it if requested (#442)
* If new dependencies are requested, serve it regardless of version; tests * Comment white space * Manually set `App(server=None)`
1 parent a0339bf commit b247baa

File tree

2 files changed

+67
-6
lines changed

2 files changed

+67
-6
lines changed

shiny/_app.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,20 @@ def _ensure_web_dependencies(self, deps: list[HTMLDependency]) -> None:
328328
self._register_web_dependency(dep)
329329

330330
def _register_web_dependency(self, dep: HTMLDependency) -> None:
331-
if (
332-
dep.name in self._registered_dependencies
333-
and dep.version >= self._registered_dependencies[dep.name].version
334-
):
331+
# If the dependency has been seen before, quit early.
332+
333+
# Even if the htmldependency version is higher or lower, the HTML being sent to
334+
# the user is requesting THIS dependency. Therefore, it should be available to
335+
# the user independent of any previous versions of the dependency being served.
336+
337+
# Note: htmltools does de-duplicate dependencies and finds the highest version
338+
# to return. However, dynamic UI and callable UI do not run through the same
339+
# filter over time. When using callable UI functions, UI dependencies are reset
340+
# on refresh. So if a dependency makes it here, it is not necessarily the
341+
# highest version served over time but is the highest version for this
342+
# particular UI. Therefore, serve it must be served.
343+
dep_name = html_dep_name(dep)
344+
if dep_name in self._registered_dependencies:
335345
return
336346

337347
# For HTMLDependencies that have sources on disk, mount the source dir.
@@ -344,11 +354,11 @@ def _register_web_dependency(self, dep: HTMLDependency) -> None:
344354
starlette.routing.Mount(
345355
"/" + paths["href"],
346356
StaticFiles(directory=paths["source"]),
347-
name=dep.name + "-" + str(dep.version),
357+
name=dep_name,
348358
),
349359
)
350360

351-
self._registered_dependencies[dep.name] = dep
361+
self._registered_dependencies[dep_name] = dep
352362

353363
def _render_page(self, ui: Tag | TagList, lib_prefix: str) -> RenderedHTML:
354364
ui_res = copy.copy(ui)
@@ -365,3 +375,7 @@ def is_uifunc(x: Tag | TagList | Callable[[Request], Tag | TagList]):
365375
return False
366376
else:
367377
return True
378+
379+
380+
def html_dep_name(dep: HTMLDependency) -> str:
381+
return dep.name + "-" + str(dep.version)

tests/test_ui_dependencies.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import htmltools
2+
3+
from shiny import App, ui
4+
from shiny._app import html_dep_name
5+
6+
7+
def test_duplicate_deps():
8+
some_dep1 = htmltools.HTMLDependency(
9+
"test_dep",
10+
"1",
11+
source={
12+
"subdir": ".",
13+
},
14+
all_files=True,
15+
)
16+
some_dep2 = htmltools.HTMLDependency(
17+
"test_dep",
18+
"2",
19+
source={
20+
"subdir": ".",
21+
},
22+
all_files=True,
23+
)
24+
some_dep3 = htmltools.HTMLDependency(
25+
"test_dep",
26+
"3",
27+
source={
28+
"subdir": ".",
29+
},
30+
all_files=True,
31+
)
32+
33+
# Put v1 at the end to make sure lower versions can be added
34+
# Put v3 in the middle to make sure a higher version can be added
35+
deps = [some_dep2, some_dep3, some_dep1]
36+
37+
app = App(ui.div("ui goes here"), server=None)
38+
39+
# During a page refresh, the same session is kept alive. This means the mapping of `app._registered_dependencies` is kept. However, the dependencies requested by the user can be different when using a UI function.
40+
41+
# Simulate adding the _conflicting_ dependencies from three different dynamic UI
42+
# functions
43+
for dep in deps:
44+
app._register_web_dependency(dep)
45+
# All three dependencies should be registered as they are unique and will be requested by the browser
46+
for dep in deps:
47+
assert html_dep_name(dep) in app._registered_dependencies

0 commit comments

Comments
 (0)