|
| 1 | +#' Add Python as a lesson dependency |
| 2 | +#' |
| 3 | +#' Associate a version of Python with your lesson. This is essentially a wrapper |
| 4 | +#' around [renv::use_python()]. |
| 5 | +#' |
| 6 | +#' @param path path to the current project |
| 7 | +#' @inheritParams renv::use_python |
| 8 | +#' @param ... Further arguments to be passed on to [renv::use_python()] |
| 9 | +#' |
| 10 | +#' @details |
| 11 | +#' This helper function adds Python as a dependency to the \pkg{renv} lockfile |
| 12 | +#' and installs a Python environment of the specified `type`. This ensures any |
| 13 | +#' Python packages used for this lesson are installed separately from the user's |
| 14 | +#' main library, much like the R packages (see [manage_deps()]). |
| 15 | +#' |
| 16 | +#' Note that \pkg{renv} is not (yet) able to automatically detect Python package |
| 17 | +#' dependencies (e.g. from `import` statements). So any required Python packages |
| 18 | +#' still need to be installed manually. To facilitate this, the [py_install()] |
| 19 | +#' helper is provided. This will install Python packages in the correct |
| 20 | +#' environment and record them in a `requirements.txt` file, which will be |
| 21 | +#' tracked by \pkg{renv}. Subsequent calls of [manage_deps()] will then |
| 22 | +#' correctly restore the required Python packages if needed. |
| 23 | +#' |
| 24 | +#' @export |
| 25 | +#' @rdname use_python |
| 26 | +#' @seealso [renv::use_python()], [py_install()] |
| 27 | +#' @return The path to the Python executable. Note that this function is mainly |
| 28 | +#' called for its side effects. |
| 29 | +#' @examples |
| 30 | +#' \dontrun{ |
| 31 | +#' tmp <- tempfile() |
| 32 | +#' on.exit(unlink(tmp)) |
| 33 | +#' |
| 34 | +#' ## Create lesson with Python support |
| 35 | +#' lsn <- create_lesson(tmp, name = "This Lesson", open = FALSE, add_python = TRUE) |
| 36 | +#' lsn |
| 37 | +#' |
| 38 | +#' ## Add Python as a dependency to an existing lesson |
| 39 | +#' setwd(lsn) |
| 40 | +#' use_python() |
| 41 | +#' |
| 42 | +#' ## Install Python packages and record them as dependencies |
| 43 | +#' py_install("numpy") |
| 44 | +#' } |
| 45 | +use_python <- function(path = ".", python = NULL, |
| 46 | + type = c("auto", "virtualenv", "conda", "system"), ...) { |
| 47 | + |
| 48 | + wd <- getwd() |
| 49 | + |
| 50 | + ## Load the renv profile, unloading it upon exit |
| 51 | + on.exit({ |
| 52 | + invisible(utils::capture.output(renv::deactivate(project = path), type = "message")) |
| 53 | + setwd(wd) |
| 54 | + }, add = TRUE, after = FALSE) |
| 55 | + |
| 56 | + ## Set up working directory, avoids some renv side effects |
| 57 | + setwd(path) |
| 58 | + renv::load(project = path) |
| 59 | + prof <- Sys.getenv("RENV_PROFILE") |
| 60 | + |
| 61 | + install_reticulate(path = path) |
| 62 | + renv::use_python(python = python, type = type, ...) |
| 63 | + |
| 64 | + ## NOTE: use_python() deactivates the default profile, see https://github.com/rstudio/renv/issues/1217 |
| 65 | + ## Workaround: re-activate the profile |
| 66 | + renv::activate(project = path, profile = prof) |
| 67 | + invisible(path) |
| 68 | +} |
| 69 | + |
| 70 | + |
| 71 | +#' Install Python packages and add them to the cache |
| 72 | +#' |
| 73 | +#' To add Python packages, `py_install()` is provided, which installs Python |
| 74 | +#' packages with [reticulate::py_install()] and then records them in the renv |
| 75 | +#' environment. This ensures [manage_deps()] keeps track of the Python packages |
| 76 | +#' as well. |
| 77 | +#' |
| 78 | +#' @param packages Python packages to be installed as a character vecto. |
| 79 | +#' @param path path to your lesson. Defaults to the current working directory. |
| 80 | +#' @param ... Further arguments to be passed to [reticulate::py_install()] |
| 81 | +#' |
| 82 | +#' @export |
| 83 | +#' @rdname use_python |
| 84 | +py_install <- function(packages, path = ".", ...) { |
| 85 | + |
| 86 | + ## Load the renv profile, unloading it upon exit |
| 87 | + renv::load(project = path) |
| 88 | + |
| 89 | + on.exit({ |
| 90 | + invisible(utils::capture.output(renv::deactivate(project = path), type = "message")) |
| 91 | + }, add = TRUE, after = FALSE) |
| 92 | + |
| 93 | + install_reticulate(path = path) |
| 94 | + reticulate::py_install(packages = packages, ...) |
| 95 | + |
| 96 | + cli::cli_alert("Updating the package cache") |
| 97 | + renv::snapshot(lockfile = renv::paths$lockfile(project = path), prompt = FALSE) |
| 98 | +} |
| 99 | + |
| 100 | +install_reticulate <- function(path) { |
| 101 | + renv_lib <- renv::paths$library(project = path) |
| 102 | + has_reticulate <- requireNamespace("reticulate", lib.loc = renv_lib, quietly = TRUE) |
| 103 | + if (!has_reticulate) { |
| 104 | + cli::cli_alert("Adding `reticulate` as a dependency") |
| 105 | + ## Force reticulate to be recorded by renv |
| 106 | + dep_file <- fs::path(path, "dependencies.R") |
| 107 | + write("library(reticulate)", file = dep_file, append = TRUE) |
| 108 | + renv::install("reticulate", library = renv_lib) |
| 109 | + } |
| 110 | + invisible(NULL) |
| 111 | +} |
0 commit comments