forked from carpentries/sandpaper
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from milanmlft/use_python
Add support for setting up Python through renv
- Loading branch information
Showing
10 changed files
with
336 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
#' Add Python as a lesson dependency | ||
#' | ||
#' Associate a version of Python with your lesson. This is essentially a wrapper | ||
#' around [renv::use_python()]. | ||
#' | ||
#' @param path path to the current project | ||
#' @inheritParams renv::use_python | ||
#' @param ... Further arguments to be passed on to [renv::use_python()] | ||
#' | ||
#' @details | ||
#' This helper function adds Python as a dependency to the \pkg{renv} lockfile | ||
#' and installs a Python environment of the specified `type`. This ensures any | ||
#' Python packages used for this lesson are installed separately from the user's | ||
#' main library, much like the R packages (see [manage_deps()]). | ||
#' | ||
#' Note that \pkg{renv} is not (yet) able to automatically detect Python package | ||
#' dependencies (e.g. from `import` statements). So any required Python packages | ||
#' still need to be installed manually. To facilitate this, the [py_install()] | ||
#' helper is provided. This will install Python packages in the correct | ||
#' environment and record them in a `requirements.txt` file, which will be | ||
#' tracked by \pkg{renv}. Subsequent calls of [manage_deps()] will then | ||
#' correctly restore the required Python packages if needed. | ||
#' | ||
#' @export | ||
#' @rdname use_python | ||
#' @seealso [renv::use_python()], [py_install()] | ||
#' @return The path to the Python executable. Note that this function is mainly | ||
#' called for its side effects. | ||
#' @examples | ||
#' \dontrun{ | ||
#' tmp <- tempfile() | ||
#' on.exit(unlink(tmp)) | ||
#' | ||
#' ## Create lesson with Python support | ||
#' lsn <- create_lesson(tmp, name = "This Lesson", open = FALSE, add_python = TRUE) | ||
#' lsn | ||
#' | ||
#' ## Add Python as a dependency to an existing lesson | ||
#' setwd(lsn) | ||
#' use_python() | ||
#' | ||
#' ## Install Python packages and record them as dependencies | ||
#' py_install("numpy") | ||
#' } | ||
use_python <- function(path = ".", python = NULL, | ||
type = c("auto", "virtualenv", "conda", "system"), ...) { | ||
|
||
wd <- getwd() | ||
|
||
## Load the renv profile, unloading it upon exit | ||
on.exit({ | ||
invisible(utils::capture.output(renv::deactivate(project = path), type = "message")) | ||
setwd(wd) | ||
}, add = TRUE, after = FALSE) | ||
|
||
## Set up working directory, avoids some renv side effects | ||
setwd(path) | ||
renv::load(project = path) | ||
prof <- Sys.getenv("RENV_PROFILE") | ||
|
||
install_reticulate(path = path) | ||
renv::use_python(python = python, type = type, ...) | ||
|
||
## NOTE: use_python() deactivates the default profile, see https://github.com/rstudio/renv/issues/1217 | ||
## Workaround: re-activate the profile | ||
renv::activate(project = path, profile = prof) | ||
invisible(path) | ||
} | ||
|
||
|
||
#' Install Python packages and add them to the cache | ||
#' | ||
#' To add Python packages, `py_install()` is provided, which installs Python | ||
#' packages with [reticulate::py_install()] and then records them in the renv | ||
#' environment. This ensures [manage_deps()] keeps track of the Python packages | ||
#' as well. | ||
#' | ||
#' @param packages Python packages to be installed as a character vecto. | ||
#' @param path path to your lesson. Defaults to the current working directory. | ||
#' @param ... Further arguments to be passed to [reticulate::py_install()] | ||
#' | ||
#' @export | ||
#' @rdname use_python | ||
py_install <- function(packages, path = ".", ...) { | ||
|
||
## Load the renv profile, unloading it upon exit | ||
renv::load(project = path) | ||
|
||
on.exit({ | ||
invisible(utils::capture.output(renv::deactivate(project = path), type = "message")) | ||
}, add = TRUE, after = FALSE) | ||
|
||
install_reticulate(path = path) | ||
reticulate::py_install(packages = packages, ...) | ||
|
||
cli::cli_alert("Updating the package cache") | ||
renv::snapshot(lockfile = renv::paths$lockfile(project = path), prompt = FALSE) | ||
} | ||
|
||
install_reticulate <- function(path) { | ||
renv_lib <- renv::paths$library(project = path) | ||
has_reticulate <- requireNamespace("reticulate", lib.loc = renv_lib, quietly = TRUE) | ||
if (!has_reticulate) { | ||
cli::cli_alert("Adding `reticulate` as a dependency") | ||
## Force reticulate to be recorded by renv | ||
dep_file <- fs::path(path, "dependencies.R") | ||
write("library(reticulate)", file = dep_file, append = TRUE) | ||
renv::install("reticulate", library = renv_lib) | ||
} | ||
invisible(NULL) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
## Helpers to temporarily load renv environment | ||
local_load_py_pkg <- function(lsn, package) { | ||
local_renv_load(lsn) | ||
reticulate::import(package) | ||
} | ||
|
||
## These helpers are used in `test-use_python.R`, they are implemented separately to ensure the | ||
## temporary loading of the renv environment doesn't interfere with the testing environment | ||
check_reticulate <- function(lsn) { | ||
local_renv_load(lsn) | ||
lib <- renv::paths$library(project = lsn) | ||
withr::local_libpaths(lib) | ||
rlang::is_installed("reticulate") | ||
} | ||
|
||
check_reticulate_config <- function(lsn) { | ||
local_renv_load(lsn) | ||
reticulate::py_config() | ||
} | ||
|
||
get_renv_env <- function(lsn, which = "RETICULATE_PYTHON") { | ||
local_renv_load(lsn) | ||
Sys.getenv(which) | ||
} | ||
|
||
## Temporarily load a renv profile, unloading it upon exit | ||
local_renv_load <- function(lsn, env = parent.frame()) { | ||
## NOTE: renv:::unload() is currently not exported: https://github.com/rstudio/renv/issues/1285 | ||
withr::defer(renv:::unload(project = lsn), envir = env) | ||
renv::load(lsn) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.