dashR icon indicating copy to clipboard operation
dashR copied to clipboard

Revise assert_valid_callbacks to permit passing unnamed lists as callback output

Open z267xu opened this issue 6 years ago • 1 comments

In Python, we may not need to give names to a list. In R, we would better do so, or the extraction may return NULL. The output is a list with first argument id and second property. Hence, if the users fail to give names, the following line would throw an error

https://github.com/plotly/dashR/blob/6bc44b5c4203b03bd5ef04ab5dd24b23bf9b987b/R/utils.R#L360-L363

So, we can add an outputCheck function bellow.

outputCheck <- function(output) {
  
  if(length(output) != 2) {
    
    stop(sprintf("Both 'id' and 'property' should be provided"), call. = FALSE)
    
  } else {
    
    namesOutput <- names(output)
    
    if(is.null(namesOutput)) {
      
      warning(sprintf("output names are missing. The first element in output would be set as 'id' and the second would be 'property'"), call. = FALSE)
      
      names(output) <- c("id", "property")
      
    } else {
      
      if(!all(namesOutput %in% c("id", "property"))) {
        
        warning(sprintf("One of output names is missing."), call. = FALSE)
        
        names(output) <- if(all(namesOutput == c("", "property")) | all(namesOutput == c("id", ""))) {

          c("id", "property") 
          
        } else if(all(namesOutput == c("property", "")) | all(namesOutput == c("", "id"))) {
          
          c("property", "id")
          
        } else stop(sprintf("Only 'id' and 'property' can be provided"))
        
      } else if (all(namesOutput %in% "id")) {
        
        warning(sprintf("Both names are 'id', the second one would be set as 'property'"))
        
        names(output) <- c("id", "property")
        
      } else if (all(namesOutput %in% "property")) {
        
        warning(sprintf("Both names are 'property', the first one would be set as 'id'"))
        
        names(output) <- c("id", "property")
        
      } else NULL
    }
  }
  
  output
}

and draw this line

output <- outputCheck(output)

in https://github.com/plotly/dashR/blob/6bc44b5c4203b03bd5ef04ab5dd24b23bf9b987b/R/dash.R#L438-L444

before assert_valid_callbacks(output, params, func)

It has been tested and should handle all kinds of missing given names @rpkyle

z267xu avatar May 14 '19 15:05 z267xu

Thanks @z267xu -- nice find and great first issue!

This is definitely what I would call a bug, since we want to permit app developers to use similar shorthand in DashR as in Dash for Python. For example, either of the following is acceptable callback syntax:

@app.callback(
    Output(component_id='my-div', component_property='children'),
@app.callback(
    Output('my-div', 'children'),

Given the way assert_valid_callbacks is currently written, the following line fails in DashR:

app$callback(output = list("my-div", "children"), 

with the error

Error in if (!(is.character(output$id) & !grepl("^\\s*$", output$id) &  : 
  argument is of length zero

The reason for this is that the corresponding test in assert_valid_callbacks expects a named list for output, which is actually not idiomatic for Dash:

https://github.com/plotly/dashR/blob/6bc44b5c4203b03bd5ef04ab5dd24b23bf9b987b/R/utils.R#L360-L363

I believe this test should actually (and in this order)

:one: assert that the argument passed to output is a list :two: assert that this list has two, and only two, elements :three: treat output[[1]] as id, and output[[2]] as its property, them perform same check as before

I'm not opposed to writing additional tests to inspect the output, but I'd rather keep it simple whenever possible.

@alexcjohnson

rpkyle avatar May 14 '19 17:05 rpkyle