Skip to content

Commit c345ce2

Browse files
committed
dill caching engine for knitr, with tests
1 parent b52ecaa commit c345ce2

File tree

3 files changed

+91
-31
lines changed

3 files changed

+91
-31
lines changed

R/knitr-engine.R

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -189,16 +189,14 @@ eng_python_initialize <- function(options, context, envir) {
189189
use_python(options$engine.path[[1]])
190190

191191
if (options$cache > 0) {
192-
load_module <- tryCatch(
193-
import("dill"),
194-
error = function(c) {
195-
py_error <- py_last_error()
196-
if(py_error$type == "ImportError" && py_error$value == "No module named dill") {
197-
warning("The Python module dill was not found. This module is needed for full cache functionality.")
198-
"No dill"
199-
}})
200-
if (load_module != "No dill")
201-
py_run_string("import dill")
192+
module <- tryCatch(import("dill"), error = identity)
193+
if (inherits(module, "error")) {
194+
if (module$message == "ImportError: No module named dill") {
195+
warning("The Python module dill was not found. This module is needed for full cache functionality.")
196+
} else {
197+
stop(module$message)
198+
}
199+
}
202200
}
203201
eng_python_initialize_matplotlib(options, context, envir)
204202
}
@@ -292,18 +290,20 @@ eng_python_wrap <- function(outputs, options) {
292290
}
293291

294292
save_python_session <- function(cache_path) {
295-
dill <- tryCatch(
296-
import("dill"),
297-
error = function(c) {
298-
py_error <- py_last_error()
299-
if(py_error$type == "ImportError" && py_error$value == "No module named dill") {
300-
"No dill"
301-
}})
302-
if (dill == "No dill") return()
303-
304-
py_run_string("globals().pop('r')")
305-
py_run_string("globals().pop('R')")
306-
dill$dump_session(filename = paste0(cache_path, ".pkl"), byref = TRUE)
293+
module <- tryCatch(import("dill"), error = identity)
294+
if (inherits(module, "error")) {
295+
if (module$message == "ImportError: No module named dill") return()
296+
signalCondition(module$message)
297+
}
298+
299+
r_objs_exist <- "all(r_obj in globals() for r_obj in ('r', 'R'))"
300+
r_is_R <- "isinstance(r, R)"
301+
if (py_eval(r_objs_exist) && py_eval(r_is_R)) {
302+
py_run_string("globals().pop('r')")
303+
py_run_string("globals().pop('R')")
304+
}
305+
306+
module$dump_session(filename = paste0(cache_path, ".pkl"), byref = TRUE)
307307
}
308308

309309
#' A reticulate cache engine for Knitr
@@ -327,15 +327,13 @@ save_python_session <- function(cache_path) {
327327
#'
328328
#' @export
329329
cache_eng_python <- function(cache_path) {
330-
dill <- tryCatch(
331-
import("dill"),
332-
error = function(c) {
333-
py_error <- py_last_error()
334-
if(py_error$type == "ImportError" && py_error$value == "No module named dill") {
335-
"No dill"
336-
}})
337-
if (dill == "No dill") return()
338-
dill$load_session(filename = paste0(cache_path, ".pkl"))
330+
module <- tryCatch(import("dill"), error = identity)
331+
if (inherits(module, "error")) {
332+
if (module$message == "ImportError: No module named dill") return()
333+
stop(module$message)
334+
}
335+
336+
module$load_session(filename = paste0(cache_path, ".pkl"))
339337
}
340338

341339

R/python.R

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,66 @@ summary.python.builtin.object <- function(object, ...) {
198198
str(object)
199199
}
200200

201+
202+
#' Convert between Python and R objects
203+
#'
204+
#' @inheritParams import
205+
#' @param x Object to convert
206+
#'
207+
#' @return Converted object
208+
#'
209+
#' @name r-py-conversion
210+
#' @export
211+
py_to_r <- function(x) {
212+
213+
ensure_python_initialized()
214+
215+
if (!inherits(x, "python.builtin.object"))
216+
stop("Object to convert is not a Python object")
217+
218+
# get the default wrapper
219+
x <- py_ref_to_r(x)
220+
221+
# allow customization of the wrapper
222+
wrapper <- py_to_r_wrapper(x)
223+
attributes(wrapper) <- attributes(x)
224+
225+
# return the wrapper
226+
wrapper
227+
}
228+
229+
#' R wrapper for Python objects
230+
#'
231+
#' S3 method to create a custom R wrapper for a Python object.
232+
#' The default wrapper is either an R environment or an R function
233+
#' (for callable python objects).
234+
#'
235+
#' @param x Python object
236+
#'
237+
#' @export
238+
py_to_r_wrapper <- function(x) {
239+
UseMethod("py_to_r_wrapper")
240+
}
241+
242+
#' @export
243+
py_to_r_wrapper.default <- function(x) {
244+
x
245+
}
246+
247+
248+
249+
250+
251+
#' @rdname r-py-conversion
252+
#' @export
253+
r_to_py <- function(x, convert = FALSE) {
254+
255+
ensure_python_initialized()
256+
257+
r_to_py_impl(x, convert = convert)
258+
}
259+
260+
201261
#' @export
202262
`$.python.builtin.module` <- function(x, name) {
203263

tests/testthat/test-python-cache-engine.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ test_that("An R Markdown document can be rendered with cache using reticulate",
66
skip_if_not_installed("rmarkdown")
77
skip_if_not_installed("callr")
88

9+
unlink("resources/eng-reticulate-cache-test_cache/", recursive = TRUE)
10+
911
path <- callr::r(
1012
function() {
1113
rmarkdown::render("resources/eng-reticulate-cache-test.Rmd", quiet = TRUE, envir = new.env())

0 commit comments

Comments
 (0)