Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for full and partial call stacks, improved error handling in DashR #87

Merged
merged 21 commits into from
Jun 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ Imports:
assertthat,
digest,
base64enc,
mime
mime,
crayon
Suggests:
dashHtmlComponents (>= 0.13.5),
dashCoreComponents (>= 0.42.1),
Expand Down
143 changes: 110 additions & 33 deletions R/dash.R
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ Dash <- R6::R6Class(
private$assets_url_path <- sub("/$", "", assets_url_path)
private$assets_ignore <- assets_ignore
private$suppress_callback_exceptions <- suppress_callback_exceptions

# config options
self$config$routes_pathname_prefix <- resolve_prefix(routes_pathname_prefix, "DASH_ROUTES_PATHNAME_PREFIX")
self$config$requests_pathname_prefix <- resolve_prefix(requests_pathname_prefix, "DASH_REQUESTS_PATHNAME_PREFIX")
Expand Down Expand Up @@ -193,8 +194,8 @@ Dash <- R6::R6Class(

payload <- Map(function(callback_signature) {
list(
output=callback_signature$output,
inputs=callback_signature$inputs,
output=paste0(callback_signature$output, collapse="."),
state=callback_signature$state
)
}, private$callback_map)
Expand All @@ -217,8 +218,7 @@ Dash <- R6::R6Class(
}

# get the callback associated with this particular output
thisOutput <- with(request$body$output, paste(id, property, sep = "."))
callback <- private$callback_map[[thisOutput]][['func']]
callback <- private$callback_map[[request$body$output]][['func']]
if (!length(callback)) stop_report("Couldn't find output component.")
if (!is.function(callback)) {
stop(sprintf("Couldn't find a callback function associated with '%s'", thisOutput))
Expand Down Expand Up @@ -254,24 +254,45 @@ Dash <- R6::R6Class(
}
}

output_value <- do.call(callback, callback_args)

# 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
output_value <- encode_plotly(output_value)

# have to format the response body like this
# https://github.com/plotly/dash/blob/064c811d/dash/dash.py#L562-L584
resp <- list(
response = list(
props = setNames(list(output_value), request$body$output$property)
# set the callback context associated with this invocation of the callback
private$callback_context_ <- setCallbackContext(request$body)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great. I think all you need to do to finish off the callback context part of this PR is set it back to NULL after the do.call below, and have callback_context throw an error if it finds NULL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 6fc1cac

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry - what I meant was, down here when you access callback_context_

https://github.com/plotly/dashR/pull/87/files#diff-1881af3720f8f49fd3a44e51c9fb5a0dR489

At that point, if it's NULL, that means you're not in a callback so you shouldn't be able to access it, so throw an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 96e4672


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

# reset callback context
private$callback_context_ <- NULL

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
output_value <- encode_plotly(output_value)

# have to format the response body like this
# https://github.com/plotly/dash/blob/064c811d/dash/dash.py#L562-L584
resp <- list(
response = list(
props = setNames(list(output_value), gsub( "(^.+)(\\.)", "", request$body$output))
)
)
)

response$body <- to_JSON(resp)
response$status <- 200L
response$type <- 'json'

response$body <- to_JSON(resp)
response$status <- 200L
response$type <- 'json'
} else if (private$debug==TRUE) {
# if there is an error, send it back to dash-renderer
response$body <- private$stack_message
response$status <- 500L
response$type <- 'html'
private$stack_message <- NULL
} else {
# if not in debug mode, do not return stack
response$body <- NULL
response$status <- 500L
private$stack_message <- NULL
}
TRUE
})

Expand All @@ -280,7 +301,9 @@ Dash <- R6::R6Class(
# https://github.com/plotly/dash/blob/1249ffbd051bfb5fdbe439612cbec7fa8fff5ab5/dash/dash.py#L488
# https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data
dash_suite <- paste0(self$config$routes_pathname_prefix, "_dash-component-suites/:package_name/:filename")

route$add_handler("get", dash_suite, function(request, response, keys, ...) {

filename <- basename(file.path(keys$filename))

dep_list <- c(private$dependencies_internal,
Expand Down Expand Up @@ -453,26 +476,56 @@ Dash <- R6::R6Class(

# register the callback_map
private$callback_map[[paste(output$id, output$property, sep='.')]] <- list(
output=output,
inputs=inputs,
output=output,
state=state,
func=func
)
},

# ------------------------------------------------------------------------
# request and return callback context
# ------------------------------------------------------------------------
callback_context = function() {
if (is.null(private$callback_context_)) {
warning("callback_context is undefined; callback_context may only be accessed within a callback.")
}
private$callback_context_
},

# ------------------------------------------------------------------------
# convenient fiery wrappers
# ------------------------------------------------------------------------
run_server = function(host = Sys.getenv('DASH_HOST', "127.0.0.1"),
port = Sys.getenv('DASH_PORT', 8050),
block = TRUE,
showcase = FALSE,
pruned_errors = TRUE,
debug = FALSE,
dev_tools_ui = NULL,
dev_tools_props_check = NULL,
...) {
self$server$host <- host
self$server$port <- as.numeric(port)

if (debug & !(isFALSE(dev_tools_ui)) | isTRUE(dev_tools_ui)) {
self$config$ui <- TRUE
} else {
self$config$ui <- FALSE
}

if (debug & !(isFALSE(dev_tools_props_check)) | isTRUE(dev_tools_props_check)) {
self$config$props_check <- TRUE
} else {
self$config$props_check <- FALSE
}

private$pruned_errors <- pruned_errors
private$debug <- debug

self$server$ignite(block = block, showcase = showcase, ...)
}
),
}
),

private = list(
# private fields defined on initiation
Expand All @@ -488,7 +541,15 @@ Dash <- R6::R6Class(
css = NULL,
scripts = NULL,
other = NULL,


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

# callback context
callback_context_ = NULL,

# fields for tracking HTML dependencies
dependencies = list(),
dependencies_user = list(),
Expand Down Expand Up @@ -575,9 +636,6 @@ Dash <- R6::R6Class(
# add on HTML dependencies we've identified by crawling the layout
private$dependencies <- c(private$dependencies, deps_layout)

# DashR's own dependencies
private$dependencies_internal <- dashR:::.dashR_js_metadata()

# return the computed layout
oldClass(layout_) <- c("dash_layout", oldClass(layout_))
layout_
Expand Down Expand Up @@ -685,7 +743,7 @@ Dash <- R6::R6Class(

# akin to https://github.com/plotly/dash-renderer/blob/master/dash_renderer/__init__.py
react_version_enabled= function() {
version <- private$dependencies_internal$react$version
version <- private$dependencies_internal$`react-prod`$version
return(version)
},
react_deps = function() {
Expand All @@ -701,14 +759,27 @@ Dash <- R6::R6Class(
.index = NULL,

collect_resources = function() {
# DashR's own dependencies
# serve the dev version of dash-renderer when in debug mode
dependencies_all_internal <- dashR:::.dashR_js_metadata()
if (private$debug) {
depsSubset <- dependencies_all_internal[names(dependencies_all_internal) != c("dash-renderer-prod",
"dash-renderer-map-prod")]
} else {
depsSubset <- dependencies_all_internal[names(dependencies_all_internal) != c("dash-renderer-dev",
"dash-renderer-map-dev")]
}

private$dependencies_internal <- depsSubset

# collect and resolve package dependencies
depsAll <- compact(c(
private$react_deps()[private$react_versions() %in% private$react_version_enabled()],
private$dependencies,
private$dependencies_user,
private$dependencies_internal[names(private$dependencies_internal) %in% 'dash-renderer']
private$dependencies_internal[grepl(pattern = "dash-renderer", x = private$dependencies_internal)]
))

# normalizes local paths and keeps newer versions of duplicates
depsAll <- htmltools::resolveDependencies(depsAll, FALSE)

Expand Down Expand Up @@ -774,7 +845,13 @@ Dash <- R6::R6Class(
} else {
favicon <- ""
}


# set script tag to invoke a new dash_renderer
scripts_invoke_renderer <- sprintf("<script id=\"%s\" type=\"%s\">%s</script>",
"_dash-renderer",
"application/javascript",
"var renderer = new DashRenderer();")

# serving order of CSS and JS tags: package -> external -> assets
css_tags <- paste(c(css_deps,
css_external,
Expand All @@ -783,7 +860,8 @@ Dash <- R6::R6Class(

scripts_tags <- paste(c(scripts_deps,
scripts_external,
scripts_assets),
scripts_assets,
scripts_invoke_renderer),
collapse = "\n")

return(list(css_tags = css_tags,
Expand Down Expand Up @@ -831,7 +909,6 @@ Dash <- R6::R6Class(
to_JSON(self$config),
scripts_tags
)

}
)
)
Expand Down
83 changes: 42 additions & 41 deletions R/internal.R
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
.dashR_js_metadata <- function() {
deps_metadata <- list(`react` = structure(list(name = "react",
version = "15.4.2",
src = list(href = "https://unpkg.com/react@15.4.2",
file = "lib/react@15.4.2"),
meta = NULL,
script = "dist/react.min.js",
stylesheet = NULL,
head = NULL,
attachment = NULL,
package = "dashR",
all_files = FALSE),
class = "html_dependency"),
`react-prod` = structure(list(name = "react",
version = "16.2.0",
src = list(href = "https://unpkg.com/react@16.2.0",
file = "lib/react@16.2.0"),
deps_metadata <- list(`react-prod` = structure(list(name = "react",
version = "16.8.6",
src = list(href = "https://unpkg.com/react@16.8.6",
file = "lib/react@16.8.6"),
meta = NULL,
script = "umd/react.production.min.js",
stylesheet = NULL,
Expand All @@ -23,34 +11,46 @@
package = "dashR",
all_files = FALSE),
class = "html_dependency"),
`react-dom` = structure(list(name = "react-dom",
version = "15.4.2",
src = list(href = "https://unpkg.com/react-dom@15.4.2",
file = "lib/react-dom@15.4.2"),
meta = NULL,
script = "dist/react-dom.min.js",
stylesheet = NULL,
head = NULL,
attachment = NULL,
package = "dashR",
all_files = FALSE),
class = "html_dependency"),
`react-dom-prod` = structure(list(name = "react-dom",
version = "16.2.0",
src = list(href = "https://unpkg.com/react-dom@16.2.0",
file = "lib/react-dom@16.2.0"),
version = "16.8.6",
src = list(href = "https://unpkg.com/react-dom@16.8.6",
file = "lib/react-dom@16.8.6"),
meta = NULL,
script = "umd/react-dom.production.min.js",
script = "dist/react-dom.production.min.js",
stylesheet = NULL,
head = NULL,
attachment = NULL,
package = "dashR",
all_files = FALSE),
class = "html_dependency"),
`dash-renderer` = structure(list(name = "dash-renderer",
version = "0.18.0",
src = list(href = "https://unpkg.com/dash-renderer@0.18.0",
file = "lib/dash-renderer@0.18.0"),
`dash-renderer-dev` = structure(list(name = "dash-renderer",
version = "0.23.0",
src = list(href = "https://unpkg.com/dash-renderer@0.23.0",
file = "lib/dash-renderer@0.23.0"),
meta = NULL,
script = "dash-renderer/dash_renderer.dev.js",
stylesheet = NULL,
head = NULL,
attachment = NULL,
package = "dashR",
all_files = FALSE),
class = "html_dependency"),
`dash-renderer-map-dev` = structure(list(name = "dash-renderer",
version = "0.23.0",
src = list(href = "https://unpkg.com/dash-renderer@0.23.0",
file = "lib/dash-renderer@0.23.0"),
meta = NULL,
script = "dash-renderer/dash_renderer.dev.js.map",
stylesheet = NULL,
head = NULL,
attachment = NULL,
package = "dashR",
all_files = FALSE),
class = "html_dependency"),
`dash-renderer-prod` = structure(list(name = "dash-renderer",
version = "0.23.0",
src = list(href = "https://unpkg.com/dash-renderer@0.23.0",
file = "lib/dash-renderer@0.23.0"),
meta = NULL,
script = "dash-renderer/dash_renderer.min.js",
stylesheet = NULL,
Expand All @@ -59,17 +59,18 @@
package = "dashR",
all_files = FALSE),
class = "html_dependency"),
`dash-renderer` = structure(list(name = "dash-renderer",
version = "0.18.0",
src = list(href = "https://unpkg.com/dash-renderer@0.18.0",
file = "lib/dash-renderer@0.18.0"),
`dash-renderer-map-prod` = structure(list(name = "dash-renderer",
version = "0.23.0",
src = list(href = "https://unpkg.com/dash-renderer@0.23.0",
file = "lib/dash-renderer@0.23.0"),
meta = NULL,
script = "dash-renderer/dash_renderer.min.js.map",
stylesheet = NULL,
head = NULL,
attachment = NULL,
package = "dashR",
all_files = FALSE),
class = "html_dependency"))
class = "html_dependency")
)
return(deps_metadata)
}
Loading