Skip to content

Commit

Permalink
Use dev_tools_prune_errors instead of pruned_errors (#113)
Browse files Browse the repository at this point in the history
* rename pruned_errors to prune_errors

* rename pruned to prune

* provide support for no_update in Dash for R (#111)
  • Loading branch information
rpkyle authored Aug 23, 2019
1 parent fcfedcb commit 5c83f5c
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 9 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

S3method(print,dash_component)
export(Dash)
export(dashNoUpdate)
export(input)
export(output)
export(state)
Expand Down
18 changes: 12 additions & 6 deletions R/dash.R
Original file line number Diff line number Diff line change
Expand Up @@ -286,12 +286,18 @@ Dash <- R6::R6Class(

output_value <- getStackTrace(do.call(callback, callback_args),
debug = private$debug,
pruned_errors = private$pruned_errors)
prune_errors = private$prune_errors)

# reset callback context
private$callback_context_ <- NULL

if (is.null(private$stack_message)) {
# inspect the output_value to determine whether any outputs have no_update
# objects within them; these should not be updated
if (length(output_value) == 1 && class(output_value) == "no_update") {
response$body <- character(1) # return empty string
response$status <- 204L
}
else if (is.null(private$stack_message)) {
# pass on output_value to encode_plotly in case there are dccGraph
# components which include Plotly.js figures for which we'll need to
# run plotly_build from the plotly package
Expand Down Expand Up @@ -525,7 +531,7 @@ Dash <- R6::R6Class(
port = Sys.getenv('DASH_PORT', 8050),
block = TRUE,
showcase = FALSE,
pruned_errors = TRUE,
dev_tools_prune_errors = TRUE,
debug = FALSE,
dev_tools_ui = NULL,
dev_tools_props_check = NULL,
Expand All @@ -545,7 +551,7 @@ Dash <- R6::R6Class(
self$config$props_check <- FALSE
}

private$pruned_errors <- pruned_errors
private$prune_errors <- dev_tools_prune_errors
private$debug <- debug

self$server$ignite(block = block, showcase = showcase, ...)
Expand All @@ -569,7 +575,7 @@ Dash <- R6::R6Class(

# initialize flags for debug mode and stack pruning,
debug = NULL,
pruned_errors = NULL,
prune_errors = NULL,
stack_message = NULL,

# callback context
Expand Down
12 changes: 11 additions & 1 deletion R/dependencies.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
#' Use in conjunction with the `callback()` method from the [dash::Dash] class
#' to define the update logic in your application.
#'
#' The `dashNoUpdate()` function permits application developers to prevent a
#' single output from updating the layout. It has no formal arguments.
#'
#' @name dependencies
#' @param id a component id
#' @param property the component property to use


#' @rdname dependencies
#' @export
output <- function(id, property) {
Expand Down Expand Up @@ -44,3 +46,11 @@ dependency <- function(id = NULL, property = NULL) {
property = property
)
}

#' @rdname dependencies
#' @export
dashNoUpdate <- function() {
x <- list(NULL)
class(x) <- "no_update"
return(x)
}
4 changes: 2 additions & 2 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ stackTraceToHTML <- function(call_stack,
# and capture the call stack. By default, the call
# stack will be "pruned" of error handling functions
# for greater readability.
getStackTrace <- function(expr, debug = FALSE, pruned_errors = TRUE) {
getStackTrace <- function(expr, debug = FALSE, prune_errors = TRUE) {
if(debug) {
tryCatch(withCallingHandlers(
expr,
Expand All @@ -711,7 +711,7 @@ getStackTrace <- function(expr, debug = FALSE, pruned_errors = TRUE) {

reverseStack <- rev(calls)

if (pruned_errors) {
if (prune_errors) {
# this line should match the last occurrence of the function
# which raised the error within the call stack; prune here
indexFromLast <- match(TRUE, lapply(reverseStack, function(currentCall) {
Expand Down
7 changes: 7 additions & 0 deletions man/dependencies.Rd

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

111 changes: 111 additions & 0 deletions tests/integration/callbacks/test_no_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from selenium.webdriver.support.select import Select
import time

app = """
library(dash)
library(dashHtmlComponents)
library(dashCoreComponents)
app <- Dash$new()
app$layout(
htmlDiv(list(
dccDropdown(options = list(
list(label = "Red", value = "#FF0000"),
list(label = "Green", value = "#00FF00"),
list(label = "Blue", value = "#0000FF"),
list(label = "Do nothing", value = "nothing")
),
id = "color-selector"),
htmlButton(children = "Select all the colors!",
id = "multi-selector"
),
htmlDiv(id='message-box',
children='Please select a color choice from the dropdown menu.'),
htmlDiv(id='message-box2',
children=' ')
)
)
)
app$callback(output=list(id='message-box2', property='children'),
params=list(
input(id='multi-selector', property='n_clicks')),
function(n_clicks)
{
# if button has been clicked, n_clicks is numeric()
# on first launch of callback at layout initialization,
# value of n_clicks will be list(NULL), which is not
# comparable using >, < or =; hence the is.numeric()
# check
if (is.numeric(n_clicks) && n_clicks >= 1)
{
# return a vector to ensure that the check for
# class(x) == "no_update" isn't made for objects
# where length(x) > 1
return(c("Multiple color values: ",
"#FF0000, ",
"#00FF00, ",
"#0000FF ",
"returned!")
)
}
}
)
app$callback(output=list(id='message-box', property='children'),
params=list(
input(id='color-selector', property='value')),
function(color)
{
if (color %in% c("#FF0000", "#00FF00", "#0000FF")) {
msg <- sprintf("The hexadecimal representation of your last chosen color is %s",
color)
return(msg)
} else {
return(dashNoUpdate())
}
}
)
app$run_server()
"""


def test_rsnu001_no_update(dashr):
dashr.start_server(app)
dashr.find_element("#color-selector").click()
dashr.find_elements("div.VirtualizedSelectOption")[0].click()
dashr.wait_for_text_to_equal(
"#message-box",
"The hexadecimal representation of your last chosen color is #FF0000"
)
dashr.find_element("#color-selector").click()
dashr.find_elements("div.VirtualizedSelectOption")[3].click()
time.sleep(1)
assert dashr.find_element("#message-box").text == "The hexadecimal representation of your last chosen color is #FF0000"
dashr.find_element("#color-selector").click()
dashr.find_elements("div.VirtualizedSelectOption")[1].click()
dashr.wait_for_text_to_equal(
"#message-box",
"The hexadecimal representation of your last chosen color is #00FF00"
)
dashr.find_element("#color-selector").click()
dashr.find_elements("div.VirtualizedSelectOption")[3].click()
time.sleep(1)
assert dashr.find_element("#message-box").text == "The hexadecimal representation of your last chosen color is #00FF00"
dashr.find_element("#color-selector").click()
dashr.find_elements("div.VirtualizedSelectOption")[2].click()
dashr.wait_for_text_to_equal(
"#message-box",
"The hexadecimal representation of your last chosen color is #0000FF"
)
dashr.find_element("#color-selector").click()
dashr.find_elements("div.VirtualizedSelectOption")[3].click()
time.sleep(1)
assert dashr.find_element("#message-box").text == "The hexadecimal representation of your last chosen color is #0000FF"
dashr.find_element("#multi-selector").click()
dashr.wait_for_text_to_equal(
"#message-box2",
"Multiple color values: #FF0000, #00FF00, #0000FF returned!"
)

0 comments on commit 5c83f5c

Please sign in to comment.