I’m sure there’s already a way to do this, but here goes. OR maybe this is an anti-pattern. Either way, this is me, asking the stupid question.
I ran into this a few hours ago:
Sys.unsetenv("ENTREZ_KEY")
library(brranching)
mynames <- c("Poa annua", "Salix goodingii", "Helianthus annuus")
phylomatic_names(taxa = mynames, format='rsubmit')
No ENTREZ API key provided
Get one via taxize::use_entrez()
See https://ncbiinsights.ncbi.nlm.nih.gov/2017/11/02/new-api-keys-for-the-e-utilities/
No ENTREZ API key provided
Get one via taxize::use_entrez()
See https://ncbiinsights.ncbi.nlm.nih.gov/2017/11/02/new-api-keys-for-the-e-utilities/
No ENTREZ API key provided
Get one via taxize::use_entrez()
See https://ncbiinsights.ncbi.nlm.nih.gov/2017/11/02/new-api-keys-for-the-e-utilities/
[1] "poaceae%2Fpoa%2Fpoa_annua" "salicaceae%2Fsalix%2Fsalix_goodingii" "asteraceae%2Fhelianthus%2Fhelianthus_annuus"
The brranching package uses the taxize package internally, calling it’s function
taxize::tax_name()
. The taxize::tax_name()
function throws useful messages to the user
if their NCBI Entrez API key is not found, and gives them instructions on how to find it.
However, the user does not have to get an API key. If they don’t they then get subjected to lots of repeats of the same message.
I wondered if there’s anything that could be done about this. That is, if the same message is going to be thrown that was already thrown within a function call, just skip additional messages that are the same.
There is of course suppressMessages()
for messages, but in package development if you
do want a user to see a message, you don’t want to suppress messages. suppressMessages
is too blunt of an instrument for this use case.
the code
with_mssgs()
captures values and messages, suppressing the message
with_mssgs <- function(expr) {
my_mssgs <- NULL
w_handler <- function(w) {
my_mssgs <<- c(my_mssgs, list(w))
invokeRestart("muffleMessage")
}
val <- withCallingHandlers(expr, message = w_handler)
list(value = val, messages = my_mssgs)
}
MessageKeeper
is a little R6 class to handle messages, matching, and
simple checks to see if messages have been used or not.
library(R6)
MessageKeeper <- R6::R6Class("MessageKeeper",
public = list(
bucket = NULL,
print = function(x, ...) {
cat('MessageKeeper', sep = "\n")
cat(paste0(' messages: ', length(self$bucket)))
if (length(self$bucket) > 0) {
cat("\n")
for (i in self$bucket) {
cat(paste0(" ", substring(i, 1, 50)))
}
}
},
add = function(x) {
self$bucket <- c(self$bucket, list(x))
invisible(self)
},
remove = function() {
if (self$length() == 0) return(NULL)
head <- self$bucket[[1]]
self$bucket <- self$bucket[-1]
head
},
purge = function() {
self$bucket <- NULL
},
thrown_already = function(x) {
x %in% self$bucket
},
not_thrown_yet = function(x) {
!self$thrown_already(x)
}
)
)
MessageKeeper examples
mssger <- MessageKeeper$new()
mssger
#> MessageKeeper
#> messages: 0
mssger$add("one")
mssger$add("two")
mssger
#> MessageKeeper
#> messages: 2
#> one two
mssger$thrown_already("one")
#> [1] TRUE
mssger$thrown_already("bears")
#> [1] FALSE
mssger$not_thrown_yet("bears")
#> [1] TRUE
mssger$purge()
handle_mssgs()
is a function you wrap your target function in to
handle the messages
handle_mssgs <- function(expr) {
res <- with_mssgs(expr)
if (!is.null(res$messages)) {
# if not thrown yet, add to bucket and throw it
if (my_mssger$not_thrown_yet(res$messages[[1]]$message)) {
my_mssger$add(res$messages[[1]]$message)
message(res$messages[[1]]$message)
}
}
return(res$value)
}
Set up the message keeper
my_mssger <- MessageKeeper$new()
squared()
squares a numeric value and returns it, throwing a message if
it’s greater than 20
squared <- function(x) {
stopifnot(is.numeric(x))
y <- x^2
if (y > 20) message("woops, > than 20! check your numbers")
return(y)
}
foo()
runs any vector of numbers through squared()
using vapply()
foo <- function(x) {
vapply(x, function(z) squared(z), numeric(1))
}
bar()
does the same, but uses our MessageKeeper thingy
bar <- function(x) {
# tear down on exit
on.exit(my_mssger$purge())
vapply(x, function(z) handle_mssgs(squared(z)), numeric(1))
}
foo()
annoyingly throws a message for every instance possible
foo(1:20)
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> woops, > than 20! check your numbers
#> [1] 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289
#> [18] 324 361 400
while bar()
only throws the message once
bar(1:20)
#> woops, > than 20! check your numbers
#> [1] 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289
#> [18] 324 361 400