Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* `include_js()` and `include_css()` now correctly handle file permissions in multi-user settings. (#2061)

* Temporary directories/files created for HTML dependencies will be cleaned up on App object deletion (#2079)

### Deprecations

* `ui.update_navs()` has been deprecated in favor of `ui.update_navset()`. (#2047)
Expand Down
31 changes: 31 additions & 0 deletions shiny/_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import copy
import os
import secrets
import shutil
import tempfile
from contextlib import AsyncExitStack, asynccontextmanager
from inspect import signature
from pathlib import Path
Expand Down Expand Up @@ -214,6 +216,35 @@ def __init__(
cast("Tag | TagList", ui), lib_prefix=self.lib_prefix
)

def __del__(self) -> None:
deps = self._registered_dependencies.values()
self._cleanup_temp_source_dirs(list(deps))

@staticmethod
def _cleanup_temp_source_dirs(deps: list[HTMLDependency]) -> None:
# include_css()/include_js() create temporary directories to hold files that
# persist across user sessions, but also need to be cleaned up at some point,
# and Python (unlike R) does not cleanup tempdirs on process exit. So, our next
# best option is to clean them up when the App object is deleted. It's not
# perfect (the App object might be deleted while the process is still running,
# and there might be multiple App objects using the same UI). However, it still
# seems worth doing since that is such a hypothetical edge case. More generally,
# if _any_ HTMLDependency with a source directory that is a _subdirectory_ of
# the (system-wide) temp directory, we should remove it.
current_temp_dir = os.path.realpath(tempfile.gettempdir())
for dep in deps:
src = dep.source.get("subdir") if dep.source else None
if not src:
continue
src = os.path.realpath(src)
if not os.path.exists(src):
continue
if src == current_temp_dir:
continue
common = os.path.commonprefix([src, current_temp_dir])
if common == current_temp_dir:
shutil.rmtree(src)

def init_starlette_app(self) -> starlette.applications.Starlette:
routes: list[starlette.routing.BaseRoute] = [
starlette.routing.WebSocketRoute("/websocket/", self._on_connect_cb),
Expand Down
Loading