Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Azure storage option #120

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
8 changes: 7 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ Authors@R:
person(given = "Mark",
family = "Edmondson",
role = "ctb",
email = "[email protected]"))
email = "[email protected]"),
person(given = "Hong",
family = "Ooi",
role = "ctb",
email = "[email protected]"))
Description: Cache the results of a function so that when you
call it again with the same arguments it returns the pre-computed
value.
Expand All @@ -41,6 +45,8 @@ Suggests:
covr,
googleAuthR,
googleCloudStorageR,
AzureStor (>= 3.0.0),
AzureAuth,
httr,
testthat
Remotes:
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated by roxygen2: do not edit by hand

S3method(print,memoised)
export(cache_azure)
export(cache_filesystem)
export(cache_gcs)
export(cache_memory)
Expand Down
104 changes: 104 additions & 0 deletions R/cache_azure.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#' Azure Storage Cache
#'
#' Azure Storage backed cache, for remote caching. File, blob and ADLSgen2 storage are all supported.
#'
#' @examples
#'
#' \dontrun{
#' library(AzureStor)
#'
#' # use Azure blob storage for the cache
#' stor <- storage_endpoint("https://blob.accountname.core.windows.net", key="storage-key")
#' azcache <- cache_azure("cache_container", stor)
#' mem_runif <- memoise(runif, cache = azcache)
#'
#'
#' # you can also pass the endpoint URL and key to cache_azure:
#' azcache <- cache_azure("cache_container", "https://blob.accountname.core.windows.net",
#' key = "storage-key")
#'
#' # a better alternative to a master key: OAuth 2.0 authentication via AAD
#' token <- AzureAuth::get_azure_token(
#' "https://storage.azure.com",
#' tenant = "mytenant",
#' app = "app_id"
#' )
#' azcache <- cache_azure("cache_container", "https://blob.accountname.core.windows.net",
#' token = token)
#' }
#'
#' @param cache_name Name of the storage container for storing cache files.
#' @param endpoint The storage account endpoint for the cache. This should be an object of class \code{AzureStor::storage_endpoint}, or inheriting from it. Alternatively, you can provide the endpoint URL as a string, and pass any authentication arguments in `...`.
#' @param compress Argument passed to \code{saveRDS}. One of FALSE, "gzip",
#' "bzip2" or "xz". Default: FALSE.
#' @param ... Further arguments that will be passed to \code{AzureStor::storage_endpoint}, if \code{endpoint} is a URL.
#' @export
cache_azure <- function(cache_name, endpoint, compress = FALSE, ...) {
if(!requireNamespace("AzureStor")) { stop("Package `AzureStor` must be installed for `cache_azure()`.") } # nocov

if(is.character(endpoint)) {
endpoint <- AzureStor::storage_endpoint(endpoint, ...)
} else if(!inherits(endpoint, "storage_endpoint")) {
stop("Must provide either the URL of a storage account endpoint, or a `storage_endpoint` object")
}

path <- tempfile("memoise-")
dir.create(path)

# create container if it doesn't exist
try(AzureStor::create_storage_container(endpoint, cache_name), silent = TRUE)
cache <- AzureStor::storage_container(endpoint, cache_name)

cache_reset <- function() {
keys <- cache_keys()
lapply(keys, function(key) {
AzureStor::delete_storage_file(cache, key, confirm = FALSE)
})
}

cache_set <- function(key, value) {
rds <- file.path(path, key)
saveRDS(value, rds, compress = compress)
opts <- options(azure_storage_progress_bar = FALSE)
on.exit({
unlink(rds)
options(opts)
})
AzureStor::storage_upload(cache, rds, key)
}

cache_get <- function(key) {
rds <- file.path(path, key)
opts <- options(azure_storage_progress_bar = FALSE)
on.exit({
unlink(rds)
options(opts)
})
res <- try(AzureStor::storage_download(cache, key, rds, overwrite = TRUE), silent = TRUE)
if(inherits(res, "try-error")) {
return(cachem::key_missing())
}
readRDS(rds)
}

cache_has_key <- function(key) {
key %in% cache_keys()
}

cache_drop_key <- function(key) {
AzureStor::delete_storage_file(cache, key, confirm = FALSE)
}

cache_keys <- function() {
AzureStor::list_storage_files(cache, info = "name")
}

list(
reset = cache_reset,
set = cache_set,
get = cache_get,
exists = cache_has_key,
remove = cache_drop_key,
keys = cache_keys
)
}
6 changes: 6 additions & 0 deletions R/cache_filesystem.R
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
#'
#' mem_runif <- memoise(runif, cache = gd)
#'
#' # Use with OneDrive (on Windows)
#'
#' od <- cache_filesystem("~/../OneDrive")
#'
#' mem_runif <- memoise(runif, cache = od)
#'
#' }
#'
#' @export
Expand Down
47 changes: 47 additions & 0 deletions man/cache_azure.Rd

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

6 changes: 6 additions & 0 deletions man/cache_filesystem.Rd

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

11 changes: 11 additions & 0 deletions tests/testthat/helper.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,14 @@ skip_on_travis_pr <- function() {

invisible(TRUE)
}

skip_without_azure_credentials <- function() {
storage_url <- Sys.getenv("AZ_TEST_STORAGE_MEMOISE_URL")
storage_key <- Sys.getenv("AZ_TEST_STORAGE_MEMOISE_KEY")

if (storage_url == "" || storage_key == "") {
testthat::skip("No Azure storage credentials")
} else {
invisible(TRUE)
}
}
41 changes: 41 additions & 0 deletions tests/testthat/test-azure.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
context("azure storage")

skip_on_cran()
skip_on_travis_pr()
skip_without_azure_credentials()

storage_url <- Sys.getenv("AZ_TEST_STORAGE_MEMOISE_URL")
storage_key <- Sys.getenv("AZ_TEST_STORAGE_MEMOISE_KEY")

bl_endp <- AzureStor::storage_endpoint(storage_url, key=storage_key)
cache_name <- paste0(sample(letters, 20, TRUE), collapse = "")

test_that("using an Azure storage cache works", {

azcache <- cache_azure(cache_name, bl_endp)
i <- 0
fn <- function() { i <<- i + 1; i }
fnm <- memoise(fn, cache = azcache)
on.exit(forget(fnm))

expect_equal(fn(), 1)
expect_equal(fn(), 2)
expect_equal(fnm(), 3)
expect_equal(fnm(), 3)
expect_equal(fn(), 4)
expect_equal(fnm(), 3)

expect_false(forget(fn))
expect_true(forget(fnm))
expect_equal(fnm(), 5)

expect_true(drop_cache(fnm)())
expect_equal(fnm(), 6)

expect_true(is.memoised(fnm))
expect_false(is.memoised(fn))
})

teardown({
AzureStor::delete_blob_container(bl_endp, cache_name, confirm = FALSE)
})