Skip to content

Commit b90666a

Browse files
committed
fix: harden kaleido context cleanup
1 parent cfdf5a7 commit b90666a

2 files changed

Lines changed: 61 additions & 14 deletions

File tree

R/kaleido.R

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,10 @@ legacyKaleidoScope <- function(kaleido) {
217217
}
218218

219219
.py_run_string_with_context <- function(code, context = list(), convert = TRUE) {
220-
py <- reticulate::py
221220
context_names <- names(context)
222221
old_values <- vector("list", length(context))
223222
had_value <- logical(length(context))
223+
was_set <- logical(length(context))
224224

225225
if (length(context) > 0) {
226226
if (is.null(context_names) || any(context_names == "")) {
@@ -229,26 +229,28 @@ legacyKaleidoScope <- function(kaleido) {
229229
if (any(!grepl("^[A-Za-z_][A-Za-z0-9_]*$", context_names))) {
230230
rlang::abort("`context` names must be valid Python identifiers.")
231231
}
232-
233-
for (i in seq_along(context)) {
234-
name <- context_names[[i]]
235-
had_value[[i]] <- reticulate::py_has_attr(py, name)
236-
if (had_value[[i]]) {
237-
old_values[[i]] <- py[[name]]
238-
}
239-
py[[name]] <- context[[i]]
240-
}
241-
232+
233+
py <- reticulate::py
242234
on.exit({
243-
for (i in rev(seq_along(context))) {
235+
for (i in rev(which(was_set))) {
244236
name <- context_names[[i]]
245237
if (had_value[[i]]) {
246-
py[[name]] <- old_values[[i]]
238+
reticulate::py_set_attr(py, name, old_values[[i]])
247239
} else {
248-
reticulate::py_run_string(paste("del", name))
240+
reticulate::py_del_attr(py, name)
249241
}
250242
}
251243
}, add = TRUE)
244+
245+
for (i in seq_along(context)) {
246+
name <- context_names[[i]]
247+
had_value[[i]] <- reticulate::py_has_attr(py, name)
248+
if (had_value[[i]]) {
249+
old_values[[i]] <- reticulate::py_get_attr(py, name)
250+
}
251+
reticulate::py_set_attr(py, name, context[[i]])
252+
was_set[[i]] <- TRUE
253+
}
252254
}
253255

254256
reticulate::py_run_string(code, convert = convert)

tests/testthat/test-kaleido.R

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
test_that("newKaleidoScope does not inline Windows temp paths into Python code", {
22
skip_if_not_installed("reticulate")
33
skip_if_not(suppressWarnings(reticulate::py_available(TRUE)))
4+
withr::defer({
5+
py <- reticulate::py
6+
for (name in c("fig", "tmp_json_path")) {
7+
if (reticulate::py_has_attr(py, name)) {
8+
reticulate::py_del_attr(py, name)
9+
}
10+
}
11+
})
412

513
win_path <- "C:\\users\\name\\AppData\\Local\\Temp\\Rtmp\\file.json"
614
py_calls <- character()
@@ -52,6 +60,8 @@ test_that("newKaleidoScope does not inline Windows temp paths into Python code",
5260
})
5361

5462
test_that("py_run_string_with_context rejects invalid Python identifiers", {
63+
skip_if_not_installed("reticulate")
64+
5565
expect_error(
5666
plotly:::.py_run_string_with_context(
5767
"value = 1",
@@ -60,3 +70,38 @@ test_that("py_run_string_with_context rejects invalid Python identifiers", {
6070
"`context` names must be valid Python identifiers\\."
6171
)
6272
})
73+
74+
test_that("py_run_string_with_context cleans up partial assignments", {
75+
skip_if_not_installed("reticulate")
76+
skip_if_not(suppressWarnings(reticulate::py_available(TRUE)))
77+
78+
deleted <- character()
79+
80+
testthat::local_mocked_bindings(
81+
py_has_attr = function(x, name) FALSE,
82+
py_get_attr = function(x, name, silent = FALSE) stop("unexpected py_get_attr call"),
83+
py_set_attr = function(x, name, value) {
84+
if (identical(name, "second")) {
85+
stop("boom")
86+
}
87+
invisible(NULL)
88+
},
89+
py_del_attr = function(x, name) {
90+
deleted <<- c(deleted, name)
91+
invisible(NULL)
92+
},
93+
py_run_string = function(code, local = FALSE, convert = TRUE) {
94+
stop("unexpected py_run_string call")
95+
},
96+
.package = "reticulate"
97+
)
98+
99+
expect_error(
100+
plotly:::.py_run_string_with_context(
101+
"value = 1",
102+
context = list(first = 1, second = 2)
103+
),
104+
"boom"
105+
)
106+
expect_identical(deleted, "first")
107+
})

0 commit comments

Comments
 (0)