Skip to content
Merged
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
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

export(check_container_class)
export(check_envvar)
export(check_that)
export(get_auth_token)
export(get_container)
export(list_container_names)
Expand Down
62 changes: 56 additions & 6 deletions R/azkit_helpers.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
#' An alternative to stopifnot/assert_that etc
#'
#' If the predicate function is true of `x` then `x` is returned. Otherwise,
#' an error is thrown with a custom `message`.
#'
#' @param x The object to be checked
#' @param predicate The predicate function used to check `x`
#' @param message A custom error message, as a string. Will be shown to the
#' user if the predicate check does not succeed. Can include `glue`d variables
#' and `{cli}` semantic markup.
#' @param pf Set as [parent.frame()] so variables in the caller environment can
#' be used in the custom error message.
#' @seealso [check_vec]
#' @export
check_that <- function(x, predicate, message, pf = parent.frame()) {
if (predicate(x)) {
x
} else {
cli::cli_abort(message, call = rlang::caller_call(), .envir = pf)
}
}


#' @keywords internal
ct_error_msg <- \(text) paste0("{.fn check_that}: ", text)

#' An alternative to stopifnot/assert_that etc
#'
#' This function makes it easy to use the `{purrr}` functions `every()`,
Expand All @@ -6,10 +32,9 @@
#' Not suitable for checking if `length(x) == 1` as it will check vectors
#' element-wise, so will potentially return `TRUE` even if `length(x) > 1`
#'
#' @param x The object to be checked
#' @param predicate The predicate function used to check elements of `x`
#' @param message A custom error message, as a string. Will be shown to the
#' user if the predicate check does not succeed. Can include `glue` variables
#' user if the predicate check does not succeed. Can include `glue`d variables
#' and `{cli}` semantic markup. Variable values will be searched for in the
#' environment of the caller function (not in the environment of `check_vec()`
#' ). This makes it easier to include informative values in the message.
Expand All @@ -19,8 +44,7 @@
#' predicate. "none" can be used to generate an inverse predicate, or the
#' situation where success means that none of the elements of x satisfies the
#' predicate. "some" is unlikely to be useful often, but it is available.
#' @param pf Set as [parent.frame] so variables in the caller environment can
#' be used in the custom error message.
#' @inheritParams check_that
#' @seealso [check_scalar_type()]
#' @keywords internal
check_vec <- function(
Expand Down Expand Up @@ -51,8 +75,8 @@ cv_error_msg <- \(text) paste0("{.fn check_vec}: ", text)
#' Possible values for the `type` parameter are: "character", "logical", "list",
#' "integer", "double", "string", "bool", "bytes", "raw", "vector", "complex".
# Supplying "string" or "bool" will additionally check that `x` is not missing.
#' @seealso [check_vec()]
#' @inheritParams check_vec
#' @seealso [check_that]
#' @inheritParams check_that
#' @param type A string defining the R object type that `x` is checked to be
#' @keywords internal
check_scalar_type <- function(
Expand Down Expand Up @@ -88,6 +112,32 @@ check_scalar_type <- function(
cst_error_msg <- \(text) paste0("{.fn check_scalar_type}: ", text)


#' Check if a supplied non-NULL value is a string with >0 characters
#'
#' Will error if x is equal to `""`, or if it is otherwise missing or invalid.
#' With the exception that if x is NULL, then NULL will be passed through.
#' @inheritParams check_that
#' @param message A custom error message, as a string. Will be shown to the
#' user if the check does not pass. Can include `glue` variables and `{cli}`
#' semantic markup. Variable values will be searched for in the environment of
#' the caller function (not in the environment of `check_nzchar()`). This
#' makes it easier to include informative values in the message.
#' @keywords internal
check_nzchar <- function(x, message, pf = parent.frame()) {
if (is.null(x)) {
NULL
}
cnz <- "check_nzchar" # nolint
check_scalar_type(x, "string", "{.fn {cnz}}: {.var x} is not a string")
if (nzchar(x)) {
x
} else {
message <- paste0("{.fn {cnz}}: ", message)
cli::cli_abort(message, call = rlang::caller_call(), .envir = pf)
}
}


#' grepl a glued regex
#'
#' Use \{glue\} expressions in grepl (and put the arguments the right way round)
Expand Down
22 changes: 13 additions & 9 deletions R/get_container.R
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
#' Get Azure storage container
#'
#' Use [list_container_names] to see a list of available containers
#' The environment variable "AZ_STORAGE_EP" must be set. This provides the URL
#' for the default Azure storage endpoint.
#' Use [list_container_names] to get a list of available container names.
#'
#' @param container_name Name of the container as a string. `NULL` by default,
#' which means the function will look instead for a container name stored in
#' the environment variable "AZ_CONTAINER"
#' @param ... arguments to be passed through to [get_auth_token()]
#' @param ... arguments to be passed through to [get_auth_token]
#' @returns An Azure blob container (list object of class "blob_container")
#' @export
get_container <- function(container_name = NULL, ...) {
cst_msg <- cst_error_msg("{.var container_name} must be a string")
container_name <- (container_name %||% check_envvar("AZ_CONTAINER")) |>
check_scalar_type("character", cst_msg)
msg <- glue::glue(
"{.var container_name} is empty. ",
"Did you forget to set an environment variable?"
)
cont_nm <- check_nzchar(container_name, msg) %||% check_envvar("AZ_CONTAINER")
token <- get_auth_token(...)
endpoint <- get_default_endpoint(token)
container_names <- list_container_names(token)
not_found_msg <- cv_error_msg("Container {.val {container_name}} not found")
container_name |>
check_vec(\(x) x %in% container_names, not_found_msg) |>
not_found_msg <- ct_error_msg("Container {.val {cont_nm}} not found")
cont_nm |>
check_that(\(x) x %in% container_names, not_found_msg) |>
AzureStor::blob_container(endpoint = endpoint)
}

Expand Down Expand Up @@ -59,6 +63,6 @@ get_default_endpoint <- function(token) {
#' @returns the value of the environment variable named in `x`
#' @export
check_envvar <- function(x) {
cst_msg <- cst_error_msg("{.envvar {x}} is not set")
cst_msg <- cst_error_msg("The environment variable {.envvar {x}} is not set")
check_scalar_type(Sys.getenv(x, NA_character_), "string", cst_msg)
}
4 changes: 2 additions & 2 deletions R/list_files.R
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
list_files <- function(container, path = "", ext = "", recursive = TRUE) {
stopifnot(rlang::is_character(c(path, ext), 2))
stopifnot(rlang::is_bool(recursive))
pnf_msg <- cv_error_msg("Path {.val {path}} not found")
check_vec(path, \(x) AzureStor::blob_dir_exists(container, x), pnf_msg)
pnf_msg <- ct_error_msg("Path {.val {path}} not found")
check_that(path, \(x) AzureStor::blob_dir_exists(container, x), pnf_msg)

tbl <- AzureStor::list_blobs(container, path, recursive = recursive)
if (nrow(tbl) > 0) {
Expand Down
12 changes: 5 additions & 7 deletions R/read_azure_files.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#' being read. Useful for checking the function is doing what is expected, but
#' can be turned off with `FALSE`. Can be set persistently with the option
#' "azkit.info". If `NULL` then it will default to the value of
#' [rlang::is_interactive] (ie `TRUE` for interactive sessions).
#' [rlang::is_interactive] (that is, `TRUE` for interactive sessions).
#' @param ... optional arguments to be passed through to [arrow::read_parquet]
#' @returns A tibble
#' @examples \dontrun{
Expand Down Expand Up @@ -139,12 +139,10 @@ check_blob_exists <- function(container, file, ext, info, path) {
dplyr::filter(dplyr::if_any("name", \(x) x == {{ file_path }})) |>
dplyr::pull("name")

if (length(filepath_out) == 0) {
cli::cli_abort("no matching {ext} file found")
}
if (length(filepath_out) > 1) {
cli::cli_abort("multiple matching {ext} files found")
}
msg1 <- ct_error_msg("no matching {ext} file found")
msg2 <- cst_error_msg("multiple matching {ext} files found")
check_that(filepath_out, \(x) length(x) > 0, msg1) # check length > 0
check_scalar_type(filepath_out, "character", msg2) # check length == 1

info_option <- getOption("azkit.info")
stopifnot(rlang::is_scalar_logical(info) || is.null(info))
Expand Down
2 changes: 1 addition & 1 deletion man/check_blob_exists.Rd

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

25 changes: 25 additions & 0 deletions man/check_nzchar.Rd

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

8 changes: 3 additions & 5 deletions man/check_scalar_type.Rd

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

27 changes: 27 additions & 0 deletions man/check_that.Rd

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

2 changes: 1 addition & 1 deletion man/check_vec.Rd

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

6 changes: 4 additions & 2 deletions man/get_container.Rd

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

4 changes: 2 additions & 2 deletions tests/testthat/test-read_azure_files.R
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ test_that("tdd of check_blob_exists", {
dplyr::pull("name")
stop_msg1 <- glue::glue("no matching {file_ext} file found")
stop_msg2 <- glue::glue("multiple matching {file_ext} files found")
check_vec(filepath, rlang::is_character, stop_msg1) # check length > 0
check_that(filepath, \(x) length(x) > 0, stop_msg1) # check length > 0
check_scalar_type(filepath, "character", stop_msg2) # check length == 1
}
expect_error(check_blob_exists(support_container, "unmatched"), "matching")
Expand All @@ -255,7 +255,7 @@ test_that("tdd of check_blob_exists", {
dplyr::pull("name")
stop_msg1 <- glue::glue("no matching {file_ext} file found")
stop_msg2 <- glue::glue("multiple matching {file_ext} files found")
check_vec(filepath, rlang::is_character, stop_msg1) # check length > 0
check_that(filepath, \(x) length(x) > 0, stop_msg1) # check length > 0
check_scalar_type(filepath, "character", stop_msg2) # check length == 1
filepath
}
Expand Down