How to display a warning only once per session?

Viewed 621

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)
2 Answers

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)
}

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()
Related