From 9037a18c91eacbda6a3d50a4fe9a5d01818978ac Mon Sep 17 00:00:00 2001 From: Richard Preen Date: Wed, 14 Jan 2026 23:45:29 +0000 Subject: [PATCH 1/4] feat: add support for conda environments --- R/acro_init.R | 75 ++++++++++++++++++++++++- R/create_virtualenv.R | 55 ------------------ man/acro_init.Rd | 8 ++- man/create_virtualenv.Rd | 17 ------ man/get_python.Rd | 14 +++++ man/install_acro.Rd | 21 ------- man/install_conda.Rd | 14 +++++ man/install_venv.Rd | 14 +++++ tests/testthat/test-create_virtualenv.R | 18 ------ tests/testthat/test-install_acro.R | 2 +- 10 files changed, 121 insertions(+), 117 deletions(-) delete mode 100644 R/create_virtualenv.R delete mode 100644 man/create_virtualenv.Rd create mode 100644 man/get_python.Rd delete mode 100644 man/install_acro.Rd create mode 100644 man/install_conda.Rd create mode 100644 man/install_venv.Rd delete mode 100644 tests/testthat/test-create_virtualenv.R diff --git a/R/acro_init.R b/R/acro_init.R index 76e4e3b..3ed9267 100644 --- a/R/acro_init.R +++ b/R/acro_init.R @@ -1,12 +1,81 @@ +# Globals ----------------------------------------------------------------- +acro_venv <- "r-acro" +acro_pkg <- "acro==0.4.11" +ch <- "conda-forge" + + +#' Resolve Python executable +#' +#' @return String path to a Python executable. +## nocov start +get_python <- function() { + python <- Sys.which("python3") + if (python == "") { + python <- Sys.which("python") + if (python == "") { + stop("Python not found in PATH. Please ensure Python is installed.") + } + } + return(python) +} +## nocov end + + +#' Install ACRO in a conda environment +#' +#' @param envname Name of the conda environment +## nocov start +install_conda <- function(envname) { # nocov + if (!reticulate::condaenv_exists(envname = envname, conda = "auto")) { + reticulate::conda_create(envname = envname, python_version = "3.12", channel = ch) + reticulate::conda_install(envname = envname, packages = acro_pkg, channel = ch) + } +} +## nocov end + + +#' Initialise ACRO in a Python virtual environment +#' +#' @param envname Name of the virtual environment +install_venv <- function(envname = acro_venv) { + if (!reticulate::virtualenv_exists(envname)) { + python <- get_python() + + reticulate::virtualenv_create( + envname = envname, + python = python, + force = TRUE, + packages = NULL + ) + + reticulate::py_install(acro_pkg, envname = envname) + } +} + + #' Initialise an ACRO object #' #' @param suppress Whether to automatically apply suppression. +#' @param envname Name of the Python environment to use. +#' @param use_conda Whether to use a Conda environment (`TRUE`) or venv (`FALSE`). #' -#' @return No return value, called for side effects +#' @return Invisibly returns the ACRO object, which is used internally. #' @export +acro_init <- function(suppress = FALSE, envname = acro_venv, use_conda = FALSE) { + # initialise the environment + if (!reticulate::py_available(initialize = FALSE)) { + if (use_conda) { # nocov + install_conda(envname) # nocov + reticulate::use_condaenv(envname, required = TRUE) # nocov + } else { + install_venv(envname) + reticulate::use_virtualenv(envname, required = TRUE) + } + } -acro_init <- function(suppress = FALSE) { - create_virtualenv() + # import the acro package and instantiate an object acro <- reticulate::import("acro", delay_load = TRUE) acroEnv$ac <- acro$ACRO(suppress = suppress) + + invisible(acroEnv$ac) } diff --git a/R/create_virtualenv.R b/R/create_virtualenv.R deleted file mode 100644 index 55fdd5a..0000000 --- a/R/create_virtualenv.R +++ /dev/null @@ -1,55 +0,0 @@ -acro_venv <- "r-acro" -acro_package <- "acro==0.4.11" -python_version <- ">=3.10" - -#' Install acro -#' -#' @param envname the name of the Python virtual environment -#' @param python the path to Python executable -#' @param ... Any other parameters. -#' -#' @return No return value, called for side effects -install_acro <- function(envname = "r-acro", python = NULL, ...) { - # Get Python executable if not provided - if (is.null(python)) { - python <- Sys.which("python3") - if (python == "") { - python <- Sys.which("python") # nocov - if (python == "") { # nocov - stop("Python not found in PATH. Please ensure Python is installed and accessible.") # nocov - } # nocov - } - } - - # create Python virtual environment - reticulate::virtualenv_create( - envname = envname, - python = python, - force = TRUE, - packages = NULL - ) - - # install Python acro - reticulate::py_install(acro_package, envname = envname) -} - -#' Create a python virtual environment -#' -#' @param ... Any other parameters. -#' -#' @return No return value, called for side effects -create_virtualenv <- function(...) { - # Get Python executable path - python_path <- Sys.which("python3") - if (python_path == "") { - python_path <- Sys.which("python") # nocov - } - - # ensure a virtual environment exists - if (!reticulate::virtualenv_exists(acro_venv)) { - install_acro(envname = acro_venv, python = python_path) - } - - # activate virtual environment - reticulate::use_virtualenv(acro_venv, required = TRUE) -} diff --git a/man/acro_init.Rd b/man/acro_init.Rd index 819ec44..19b4068 100644 --- a/man/acro_init.Rd +++ b/man/acro_init.Rd @@ -4,13 +4,17 @@ \alias{acro_init} \title{Initialise an ACRO object} \usage{ -acro_init(suppress = FALSE) +acro_init(suppress = FALSE, envname = acro_venv, use_conda = FALSE) } \arguments{ \item{suppress}{Whether to automatically apply suppression.} + +\item{envname}{Name of the Python environment to use.} + +\item{use_conda}{Whether to use a Conda environment (\code{TRUE}) or venv (\code{FALSE}).} } \value{ -No return value, called for side effects +Invisibly returns the ACRO object, which is used internally. } \description{ Initialise an ACRO object diff --git a/man/create_virtualenv.Rd b/man/create_virtualenv.Rd deleted file mode 100644 index bd1bea5..0000000 --- a/man/create_virtualenv.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/create_virtualenv.R -\name{create_virtualenv} -\alias{create_virtualenv} -\title{Create a python virtual environment} -\usage{ -create_virtualenv(...) -} -\arguments{ -\item{...}{Any other parameters.} -} -\value{ -No return value, called for side effects -} -\description{ -Create a python virtual environment -} diff --git a/man/get_python.Rd b/man/get_python.Rd new file mode 100644 index 0000000..7f45c7d --- /dev/null +++ b/man/get_python.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/acro_init.R +\name{get_python} +\alias{get_python} +\title{Resolve Python executable} +\usage{ +get_python() +} +\value{ +String path to a Python executable. +} +\description{ +Resolve Python executable +} diff --git a/man/install_acro.Rd b/man/install_acro.Rd deleted file mode 100644 index f9ea752..0000000 --- a/man/install_acro.Rd +++ /dev/null @@ -1,21 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/create_virtualenv.R -\name{install_acro} -\alias{install_acro} -\title{Install acro} -\usage{ -install_acro(envname = "r-acro", python = NULL, ...) -} -\arguments{ -\item{envname}{the name of the Python virtual environment} - -\item{python}{the path to Python executable} - -\item{...}{Any other parameters.} -} -\value{ -No return value, called for side effects -} -\description{ -Install acro -} diff --git a/man/install_conda.Rd b/man/install_conda.Rd new file mode 100644 index 0000000..b6c6e60 --- /dev/null +++ b/man/install_conda.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/acro_init.R +\name{install_conda} +\alias{install_conda} +\title{Install ACRO in a conda environment} +\usage{ +install_conda(envname) +} +\arguments{ +\item{envname}{Name of the conda environment} +} +\description{ +Install ACRO in a conda environment +} diff --git a/man/install_venv.Rd b/man/install_venv.Rd new file mode 100644 index 0000000..773ccd5 --- /dev/null +++ b/man/install_venv.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/acro_init.R +\name{install_venv} +\alias{install_venv} +\title{Initialise ACRO in a Python virtual environment} +\usage{ +install_venv(envname = acro_venv) +} +\arguments{ +\item{envname}{Name of the virtual environment} +} +\description{ +Initialise ACRO in a Python virtual environment +} diff --git a/tests/testthat/test-create_virtualenv.R b/tests/testthat/test-create_virtualenv.R deleted file mode 100644 index c4ac98c..0000000 --- a/tests/testthat/test-create_virtualenv.R +++ /dev/null @@ -1,18 +0,0 @@ -test_that("find_python returns a valid path", { - testthat::skip_on_cran() - python_path <- Sys.which("python3") - if (python_path == "") { - python_path <- Sys.which("python") - } - expect_true(python_path != "") -}) - -test_that("install_acro function exists and is callable", { - testthat::skip_on_cran() - expect_true(is.function(install_acro)) -}) - -test_that("create_virtualenv function exists and is callable", { - testthat::skip_on_cran() - expect_true(is.function(create_virtualenv)) -}) diff --git a/tests/testthat/test-install_acro.R b/tests/testthat/test-install_acro.R index 5ea92f4..51a4d72 100644 --- a/tests/testthat/test-install_acro.R +++ b/tests/testthat/test-install_acro.R @@ -4,7 +4,7 @@ test_that("install_acro installs 'acro' in the specified environment", { test_envname <- "test-acro-env" # Run the install_acro function - install_acro(envname = test_envname) + install_venv(envname = test_envname) # Check if 'acro' is installed in the test environment is_installed <- reticulate::py_module_available("acro") From 738d143da591f1232ccb4a0dbc680de8691f553a Mon Sep 17 00:00:00 2001 From: Richard Preen Date: Thu, 15 Jan 2026 11:01:39 +0000 Subject: [PATCH 2/4] add conda to ci; clean up public API --- .github/workflows/R-CMD-check.yaml | 38 ++++++++++++++++++++++++------ R/acro_init.R | 36 +++++++++++++++++++--------- man/acro_init.Rd | 6 +++-- man/get_python.Rd | 14 ----------- man/install_conda.Rd | 14 ----------- man/install_venv.Rd | 14 ----------- 6 files changed, 60 insertions(+), 62 deletions(-) delete mode 100644 man/get_python.Rd delete mode 100644 man/install_conda.Rd delete mode 100644 man/install_venv.Rd diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index cd48a0e..7abe708 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -3,43 +3,67 @@ on: branches: [main, master] pull_request: branches: [main, master] + name: R-CMD-check + jobs: R-CMD-check: runs-on: ${{ matrix.os }} - name: ${{ matrix.os }} (${{ matrix.r }}) + name: ${{ matrix.os }} (${{ matrix.r }}) - ${{ matrix.backend }} + strategy: fail-fast: false matrix: include: - - { os: "ubuntu-latest", python: "3.13", r: "release" } - - { os: "windows-latest", python: "3.13", r: "release" } - - { os: "macOS-latest", python: "3.13", r: "release" } - - { os: "ubuntu-latest", python: "3.10", r: "oldrel-1" } - - { os: "ubuntu-latest", python: "3.10", r: "oldrel-2" } - - { os: "ubuntu-latest", python: "3.10", r: "oldrel-3" } + - { os: "ubuntu-latest", python: "3.13", r: "release", backend: "venv" } + - { os: "ubuntu-latest", python: "3.13", r: "release", backend: "conda" } + + - { os: "windows-latest", python: "3.13", r: "release", backend: "venv" } + - { os: "windows-latest", python: "3.13", r: "release", backend: "conda" } + + - { os: "macOS-latest", python: "3.13", r: "release", backend: "venv" } + - { os: "macOS-latest", python: "3.13", r: "release", backend: "conda" } + + - { os: "ubuntu-latest", python: "3.10", r: "oldrel-1", backend: "venv" } + - { os: "ubuntu-latest", python: "3.10", r: "oldrel-2", backend: "venv" } + - { os: "ubuntu-latest", python: "3.10", r: "oldrel-3", backend: "venv" } + env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} R_KEEP_PKG_SOURCE: yes MPLBACKEND: Agg + ACRO_USE_CONDA: ${{ matrix.backend == 'conda' && 'true' || 'false' }} + steps: - name: Checkout ACRO uses: actions/checkout@v4 + + - name: Setup Miniconda + if: matrix.backend == 'conda' + uses: conda-incubator/setup-miniconda@v3 + with: + python-version: ${{ matrix.python }} + auto-activate-base: false + - name: Setup Python uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} + - name: Setup pandoc uses: r-lib/actions/setup-pandoc@v2 + - name: Setup R uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.r }} + - name: Install R dependencies uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: any::rcmdcheck needs: check + - name: Check R Package uses: r-lib/actions/check-r-package@v2 with: diff --git a/R/acro_init.R b/R/acro_init.R index 3ed9267..08c5467 100644 --- a/R/acro_init.R +++ b/R/acro_init.R @@ -4,9 +4,7 @@ acro_pkg <- "acro==0.4.11" ch <- "conda-forge" -#' Resolve Python executable -#' -#' @return String path to a Python executable. +# Internal helper: resolve Python executable ## nocov start get_python <- function() { python <- Sys.which("python3") @@ -21,9 +19,7 @@ get_python <- function() { ## nocov end -#' Install ACRO in a conda environment -#' -#' @param envname Name of the conda environment +# Internal helper: install ACRO in a Conda environment ## nocov start install_conda <- function(envname) { # nocov if (!reticulate::condaenv_exists(envname = envname, conda = "auto")) { @@ -34,9 +30,7 @@ install_conda <- function(envname) { # nocov ## nocov end -#' Initialise ACRO in a Python virtual environment -#' -#' @param envname Name of the virtual environment +# Internal helper: install ACRO in a Python virtual environment install_venv <- function(envname = acro_venv) { if (!reticulate::virtualenv_exists(envname)) { python <- get_python() @@ -53,15 +47,35 @@ install_venv <- function(envname = acro_venv) { } +# Internal helper: resolve whether Conda should be used +get_use_conda <- function(use_conda = NULL) { + if (is.null(use_conda)) { + use_conda <- tolower(Sys.getenv("ACRO_USE_CONDA")) %in% c("1", "true", "yes") + } + use_conda <- isTRUE(use_conda) # default FALSE + + if (use_conda && is.null(reticulate::conda_binary())) { # nocov + stop("Conda requested but no Miniconda installation found", call. = FALSE) # nocov + } + + return(use_conda) +} + + #' Initialise an ACRO object #' #' @param suppress Whether to automatically apply suppression. #' @param envname Name of the Python environment to use. -#' @param use_conda Whether to use a Conda environment (`TRUE`) or venv (`FALSE`). +#' @param use_conda Whether to use a Conda environment. +#' If `NULL`, looks for environment variable `ACRO_USE_CONDA`, +#' defaults to `FALSE` if unset. #' #' @return Invisibly returns the ACRO object, which is used internally. #' @export -acro_init <- function(suppress = FALSE, envname = acro_venv, use_conda = FALSE) { +acro_init <- function(suppress = FALSE, envname = acro_venv, use_conda = NULL) { + # define the environment + use_conda <- get_use_conda(use_conda) + # initialise the environment if (!reticulate::py_available(initialize = FALSE)) { if (use_conda) { # nocov diff --git a/man/acro_init.Rd b/man/acro_init.Rd index 19b4068..7f681e3 100644 --- a/man/acro_init.Rd +++ b/man/acro_init.Rd @@ -4,14 +4,16 @@ \alias{acro_init} \title{Initialise an ACRO object} \usage{ -acro_init(suppress = FALSE, envname = acro_venv, use_conda = FALSE) +acro_init(suppress = FALSE, envname = acro_venv, use_conda = NULL) } \arguments{ \item{suppress}{Whether to automatically apply suppression.} \item{envname}{Name of the Python environment to use.} -\item{use_conda}{Whether to use a Conda environment (\code{TRUE}) or venv (\code{FALSE}).} +\item{use_conda}{Whether to use a Conda environment. +If \code{NULL}, looks for environment variable \code{ACRO_USE_CONDA}, +defaults to \code{FALSE} if unset.} } \value{ Invisibly returns the ACRO object, which is used internally. diff --git a/man/get_python.Rd b/man/get_python.Rd deleted file mode 100644 index 7f45c7d..0000000 --- a/man/get_python.Rd +++ /dev/null @@ -1,14 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/acro_init.R -\name{get_python} -\alias{get_python} -\title{Resolve Python executable} -\usage{ -get_python() -} -\value{ -String path to a Python executable. -} -\description{ -Resolve Python executable -} diff --git a/man/install_conda.Rd b/man/install_conda.Rd deleted file mode 100644 index b6c6e60..0000000 --- a/man/install_conda.Rd +++ /dev/null @@ -1,14 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/acro_init.R -\name{install_conda} -\alias{install_conda} -\title{Install ACRO in a conda environment} -\usage{ -install_conda(envname) -} -\arguments{ -\item{envname}{Name of the conda environment} -} -\description{ -Install ACRO in a conda environment -} diff --git a/man/install_venv.Rd b/man/install_venv.Rd deleted file mode 100644 index 773ccd5..0000000 --- a/man/install_venv.Rd +++ /dev/null @@ -1,14 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/acro_init.R -\name{install_venv} -\alias{install_venv} -\title{Initialise ACRO in a Python virtual environment} -\usage{ -install_venv(envname = acro_venv) -} -\arguments{ -\item{envname}{Name of the virtual environment} -} -\description{ -Initialise ACRO in a Python virtual environment -} From 25e854193d82ceffcc74896de383761038872cc0 Mon Sep 17 00:00:00 2001 From: Richard Preen Date: Thu, 15 Jan 2026 15:14:52 +0000 Subject: [PATCH 3/4] feat: add yaml config param to acro init --- R/acro_init.R | 5 +++-- man/acro_init.Rd | 9 ++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/R/acro_init.R b/R/acro_init.R index 08c5467..09ecd2b 100644 --- a/R/acro_init.R +++ b/R/acro_init.R @@ -64,6 +64,7 @@ get_use_conda <- function(use_conda = NULL) { #' Initialise an ACRO object #' +#' @param config Name of a yaml configuration file with safe parameters. #' @param suppress Whether to automatically apply suppression. #' @param envname Name of the Python environment to use. #' @param use_conda Whether to use a Conda environment. @@ -72,7 +73,7 @@ get_use_conda <- function(use_conda = NULL) { #' #' @return Invisibly returns the ACRO object, which is used internally. #' @export -acro_init <- function(suppress = FALSE, envname = acro_venv, use_conda = NULL) { +acro_init <- function(config = "default", suppress = FALSE, envname = acro_venv, use_conda = NULL) { # define the environment use_conda <- get_use_conda(use_conda) @@ -89,7 +90,7 @@ acro_init <- function(suppress = FALSE, envname = acro_venv, use_conda = NULL) { # import the acro package and instantiate an object acro <- reticulate::import("acro", delay_load = TRUE) - acroEnv$ac <- acro$ACRO(suppress = suppress) + acroEnv$ac <- acro$ACRO(config = config, suppress = suppress) invisible(acroEnv$ac) } diff --git a/man/acro_init.Rd b/man/acro_init.Rd index 7f681e3..1e4cb99 100644 --- a/man/acro_init.Rd +++ b/man/acro_init.Rd @@ -4,9 +4,16 @@ \alias{acro_init} \title{Initialise an ACRO object} \usage{ -acro_init(suppress = FALSE, envname = acro_venv, use_conda = NULL) +acro_init( + config = "default", + suppress = FALSE, + envname = acro_venv, + use_conda = NULL +) } \arguments{ +\item{config}{Name of a yaml configuration file with safe parameters.} + \item{suppress}{Whether to automatically apply suppression.} \item{envname}{Name of the Python environment to use.} From 8824f60f0168b445d0cf57266fb0f73fb50b07a4 Mon Sep 17 00:00:00 2001 From: Richard Preen Date: Thu, 15 Jan 2026 17:09:55 +0000 Subject: [PATCH 4/4] chore: clean up conda warning message --- R/acro_init.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/acro_init.R b/R/acro_init.R index 09ecd2b..327255e 100644 --- a/R/acro_init.R +++ b/R/acro_init.R @@ -55,7 +55,7 @@ get_use_conda <- function(use_conda = NULL) { use_conda <- isTRUE(use_conda) # default FALSE if (use_conda && is.null(reticulate::conda_binary())) { # nocov - stop("Conda requested but no Miniconda installation found", call. = FALSE) # nocov + stop("Conda requested but no conda installation found", call. = FALSE) # nocov } return(use_conda)