7

There is a functionality in my package that should be used with caution.

The user should be aware of this but if he/she thinks that the situation is OK then it would be bothering to show the warning each time the function is called.

I often see warnings that are displayed only once. They are quite painful to debug so I couldn't find a reproducible example (I'll add one if I get any) but they show a specific warning message, followed by the rlang info:

This warning is displayed once per session

There are a lot of help wanted to debug those messages (for instance here, here, or here, just google "r This warning is displayed once per session")

I think that the package lifecyle often uses those for soft-deprecation, but I wasn't able to discover the trick in lifecycle:::lifecycle_build_message.

How can I throw such a warning in my package?

EDIT:

Here is a reproducible example. You have to restart your R session for it to show again. As you can see, options(warn=2) had no impact.

options(warn=2)
xx=c("Sepal.Width")
tidyselect::vars_select(names(iris), xx)
Dan Chaltiel
  • 7,811
  • 5
  • 47
  • 92
  • 1
    It's better to post answers below rather than editing answers into the question itself. This allows other people to properly upvote potential solutions. Free free to change your accepted answer to the newer, preferred solution. – MrFlick May 07 '21 at 06:06

2 Answers2

7

In the case of tidyselect::vars_select, the trick is in tidyselect:::inform_once.

  if (env_has(inform_env, id)) {
    return(invisible(NULL))
  }
  inform_env[[id]] <- TRUE

  # ....

  inform(paste_line(
    msg, silver("This message is displayed once per session.")
  ))

An environment inform_env is maintained that records whether a given message has been displayed already.


In the case of lifecycle, it works similarly with environment deprecation_env being used in deprecate_warn

deprecate_warn <- function(....) {
  msg <- lifecycle_build_message(when, what, with, details, "deprecate_warn")

  # ....

  if (verbosity == "quiet") {
    return(invisible(NULL))
  }

  if (verbosity == "default" && !needs_warning(id) && ....) {
    return(invisible(NULL))
  }

  # ....

    if (verbosity == "default") {
      # Prevent warning from being displayed again
      env_poke(deprecation_env, id, Sys.time());

      msg <- paste_line(
        msg,
        silver("This warning is displayed once every 8 hours."),
        silver("Call `lifecycle::last_warnings()` to see where this warning was generated.")
      )
    }

    # ....
}

needs_warning <- function(id) {
  last <- deprecation_env[[id]]
  if (is_null(last)) {
    return(TRUE)
  }

  # ....

  # Warn every 8 hours
  (Sys.time() - last) > (8 * 60 * 60)
}
Aurèle
  • 12,545
  • 1
  • 31
  • 49
4

UPDATE mid-2021:

There is now a built-in option in {rlang}. See the help here.

rlang::warn("This message is displayed once per session.",   .frequency = "once")

ORIGINAL ANSWER:

While Aurèle's answer clearly wins the game, tidyselect's function was not exactly fit for my needs, as it required some unexported functions.

For folks who want a simple function to use in their package, here is mine:

#' @importFrom rlang env env_has inform
#' @importFrom crayon silver has_color
#' @author tidyselect (https://github.com/r-lib/tidyselect/blob/2fab83639982d37fd94914210f771ab9cbd36b4b/R/utils.R#L281)
warning_once = function(msg, id=msg) {
    stopifnot(is_string(id))
    
    if (env_has(warning_env, id)) {
        return(invisible(NULL))
    }
    inform_env[[id]] = TRUE
    
    x = "This message is displayed once per session."
    if(is_installed("crayon") && crayon::has_color())
        x=crayon::silver(x)
    warn(paste(msg, x, sep = "\n"))
}
warning_env = rlang::env()
Dan Chaltiel
  • 7,811
  • 5
  • 47
  • 92