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
43 changes: 34 additions & 9 deletions R/rxp_io.R
Original file line number Diff line number Diff line change
Expand Up @@ -644,12 +644,31 @@ rxp_jl_file <- function(...) rxp_file("Jl", ...)
#' @return A list with elements: `name`, `snippet`, `type`, `additional_files`,
#' `nix_env`.
#' @noRd
rxp_common_setup <- function(out_name, expr_str, nix_env, direction) {
rxp_common_setup <- function(
out_name,
expr_str,
nix_env,
direction,
env_var = NULL
) {
expr_str <- gsub("\"", "'", expr_str)
base <- sanitize_nix_env(nix_env)

# Always set RETICULATE_AUTOCONFIGURE=0 to prevent reticulate from
# modifying PYTHONPATH in Nix's hermetic build environment
default_env <- c(RETICULATE_AUTOCONFIGURE = "0")

# Merge with user-provided env_var if present (user values take precedence)
if (!is.null(env_var) && length(env_var) > 0) {
env_var <- c(default_env[!names(default_env) %in% names(env_var)], env_var)
} else {
env_var <- default_env
}

env_exports <- build_env_exports(env_var)

r_command <- build_transfer_command(out_name, expr_str, direction)
build_phase <- build_reticulate_phase(r_command)
build_phase <- build_reticulate_phase(r_command, env_exports)

snippet <- make_derivation_snippet(
out_name = out_name,
Expand All @@ -665,7 +684,8 @@ rxp_common_setup <- function(out_name, expr_str, nix_env, direction) {
snippet = snippet,
type = paste0("rxp_", direction),
additional_files = "",
nix_env = nix_env
nix_env = nix_env,
env_var = env_var # Store for potential inspection/debugging
),
class = "rxp_derivation"
)
Expand Down Expand Up @@ -704,9 +724,10 @@ build_transfer_command <- function(out_name, expr_str, direction) {
#' @param r_command Character R command
#' @return Character build phase
#' @noRd
build_reticulate_phase <- function(r_command) {
build_reticulate_phase <- function(r_command, env_exports = "") {
sprintf(
"export RETICULATE_PYTHON=${defaultPkgs.python3}/bin/python\n Rscript -e \"\n source('libraries.R')\n%s\"",
"# Clear any inherited PYTHONPATH\n unset PYTHONPATH || true\n # Dynamically determine Python version and set PYTHONPATH\n PYTHON_VERSION=$(${defaultPkgs.python3}/bin/python3 -c 'import sys; print(f\"{sys.version_info.major}.{sys.version_info.minor}\")')\n export PYTHONPATH=${defaultPkgs.python3}/lib/python$PYTHON_VERSION/site-packages\n export RETICULATE_PYTHON=${defaultPkgs.python3}/bin/python\n export RETICULATE_AUTOCONFIGURE=0\n %sRscript -e \"\n source('libraries.R')\n%s\"",
env_exports,
r_command
)
}
Expand All @@ -718,14 +739,16 @@ build_reticulate_phase <- function(r_command) {
#' @param expr Symbol, Python object to be loaded into R.
#' @param nix_env Character, path to the Nix environment file, default is
#' "default.nix".
#' @param env_var Named list of environment variables to set. RETICULATE_AUTOCONFIGURE=0
#' is set by default to prevent reticulate from modifying PYTHONPATH in Nix builds.
#' @details `rxp_py2r(my_obj, my_python_object)` loads a serialized Python
#' object and saves it as an RDS file using `reticulate::py_load_object()`.
#' @return An object of class `rxp_derivation`.
#' @export
rxp_py2r <- function(name, expr, nix_env = "default.nix") {
rxp_py2r <- function(name, expr, nix_env = "default.nix", env_var = NULL) {
out_name <- deparse1(substitute(name))
expr_str <- deparse1(substitute(expr))
rxp_common_setup(out_name, expr_str, nix_env, "py2r")
rxp_common_setup(out_name, expr_str, nix_env, "py2r", env_var)
}

#' Transfer R Object into a Python Session
Expand All @@ -735,12 +758,14 @@ rxp_py2r <- function(name, expr, nix_env = "default.nix") {
#' @param expr Symbol, R object to be saved into a Python pickle.
#' @param nix_env Character, path to the Nix environment file, default is
#' "default.nix".
#' @param env_var Named list of environment variables to set. RETICULATE_AUTOCONFIGURE=0
#' is set by default to prevent reticulate from modifying PYTHONPATH in Nix builds.
#' @details `rxp_r2py(my_obj, my_r_object)` saves an R object to a Python pickle
#' using `reticulate::py_save_object()`.
#' @return An object of class `rxp_derivation`.
#' @export
rxp_r2py <- function(name, expr, nix_env = "default.nix") {
rxp_r2py <- function(name, expr, nix_env = "default.nix", env_var = NULL) {
out_name <- deparse1(substitute(name))
expr_str <- deparse1(substitute(expr))
rxp_common_setup(out_name, expr_str, nix_env, "r2py")
rxp_common_setup(out_name, expr_str, nix_env, "r2py", env_var)
}
2 changes: 1 addition & 1 deletion R/rxp_make.R
Original file line number Diff line number Diff line change
Expand Up @@ -506,4 +506,4 @@ rxp_import_artifacts <- function(
message("Importing store paths from ", archive_file)
system2("nix-store", args = "--import", stdin = archive_file)
message("Import completed")
}
}
5 changes: 4 additions & 1 deletion man/rxp_py2r.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion man/rxp_r2py.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 67 additions & 0 deletions tests/testthat/test-reticulate-config.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
test_that("rxp_py2r sets RETICULATE_AUTOCONFIGURE=0 by default", {
# Mock user input
nix_env <- "default.nix"

# Default behavior
res <- rxp_py2r(
name = my_obj,
expr = py_obj,
nix_env = nix_env
)

expect_true(!is.null(res$env_var))
expect_equal(res$env_var[["RETICULATE_AUTOCONFIGURE"]], "0")

# Verify it appears in the snippet (indirectly validating build_reticulate_phase logic if it was simple string matching)
# But better to check the structured 'env_var' component we added.
})

test_that("rxp_r2py sets RETICULATE_AUTOCONFIGURE=0 by default", {
nix_env <- "default.nix"

res <- rxp_r2py(
name = py_obj,
expr = r_obj,
nix_env = nix_env
)

expect_true(!is.null(res$env_var))
expect_equal(res$env_var[["RETICULATE_AUTOCONFIGURE"]], "0")
})

test_that("User can override RETICULATE_AUTOCONFIGURE", {
nix_env <- "default.nix"

# Override to "1"
res_override <- rxp_py2r(
name = my_obj,
expr = py_obj,
nix_env = nix_env,
env_var = c(RETICULATE_AUTOCONFIGURE = "1")
)

expect_equal(res_override$env_var[["RETICULATE_AUTOCONFIGURE"]], "1")

# Override to empty string/something else
res_override2 <- rxp_r2py(
name = py_obj,
expr = r_obj,
nix_env = nix_env,
env_var = c(RETICULATE_AUTOCONFIGURE = "custom")
)
expect_equal(res_override2$env_var[["RETICULATE_AUTOCONFIGURE"]], "custom")
})

test_that("env_var merging works correctly with other variables", {
nix_env <- "default.nix"

res <- rxp_py2r(
name = my_obj,
expr = py_obj,
nix_env = nix_env,
env_var = c(OTHER_VAR = "foo")
)

expect_equal(res$env_var[["RETICULATE_AUTOCONFIGURE"]], "0")
expect_equal(res$env_var[["OTHER_VAR"]], "foo")
})