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

Un-deprecate Plumber$run(swaggerCallback=, debug=) and add Plumber$run(docs=,quiet=) #765

Merged
merged 21 commits into from
Mar 1, 2021
Merged
Show file tree
Hide file tree
Changes from 18 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
6 changes: 4 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ Imports:
magrittr,
mime,
lifecycle (>= 0.2.0),
ellipsis (>= 0.3.0)
ellipsis (>= 0.3.0),
rlang
LazyData: TRUE
ByteCompile: TRUE
Suggests:
Expand All @@ -46,7 +47,8 @@ Suggests:
yaml,
feather,
future,
rstudioapi
rstudioapi,
mockery (>= 0.4.2)
RoxygenNote: 7.1.1
Collate:
'async.R'
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ importFrom(jsonlite,parse_json)
importFrom(jsonlite,toJSON)
importFrom(lifecycle,deprecated)
importFrom(magrittr,"%>%")
importFrom(rlang,"%||%")
importFrom(rlang,missing_arg)
importFrom(stats,runif)
importFrom(stats,setNames)
importFrom(utils,file.edit)
Expand Down
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ plumber 1.0.0.9999 Development version

* OpenAPI Specification can be set using a file path. (@meztez #696)

* Un-deprecated `Plumber$run(debug=, swaggerCallback=)` and added the parameters for `Plumber$run(docs=, quiet=)` and `pr_run(debug=, docs=, swaggerCallback=, quiet=)`. Now, all four parameters will not produce lingering effects on the `Plumber` router. (@jcheng5 #765)
* Setting `quiet = TRUE` will suppress routine startup messages.
* Setting `debug = TRUE`, will display information when an error occurs. See `pr_set_debug()`.
* Setting `docs` will update the visual documentation. See `pr_set_docs()`.
* Setting `swaggerCallback` to a function which will be called with a url to the documentation. See `pr_set_docs_callback()`.
jcheng5 marked this conversation as resolved.
Show resolved Hide resolved

* To update a `PlumberEndpoint` path after initialization, call the new `PlumberEndpoint$setPath(path)`. This will update internal path matching meta data. (Active bindings were not used to avoid breaking changes.) (@blairj09 #770)

### Bug fixes
Expand Down
174 changes: 119 additions & 55 deletions R/plumber.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#' @import R6
#' @import stringi
#' @importFrom rlang %||%
NULL

# used to identify annotation flags.
Expand Down Expand Up @@ -86,16 +87,15 @@ Plumber <- R6Class(
}

# Initialize
private$maxSize <- getOption('plumber.maxRequestSize', 0) #0 Unlimited
self$setSerializer(serializer_json())
# Default parsers to maintain legacy features
self$setParsers(c("json", "form", "text", "octet", "multi"))
self$setErrorHandler(defaultErrorHandler())
self$set404Handler(default404Handler)
self$setDocs(TRUE)
private$docs_info$has_not_been_set <- TRUE # set to know if `$setDocs()` has been called before `$run()`
self$setDocsCallback(getOption('plumber.docs.callback', getOption('plumber.swagger.url')))
self$setDebug(interactive())
private$docs_callback <- rlang::missing_arg()
private$debug <- NULL
self$setApiSpec(NULL)

# Add in the initial filters
Expand Down Expand Up @@ -143,71 +143,128 @@ Plumber <- R6Class(
#' Mac OS X, port numbers smaller than 1025 require root privileges.
#'
#' This value does not need to be explicitly assigned. To explicitly set it, see [options_plumber()].
#' @param debug Deprecated. See `$setDebug()`
#' @param swagger Deprecated. See `$setDocs(docs)` or `$setApiSpec()`
#' @param swaggerCallback Deprecated. See `$setDocsCallback()`
#' @param debug If `TRUE`, it will provide more insight into your API errors. Using this value will only last for the duration of the run. If a `$setDebug()` has not been called, `debug` will default to `interactive()` at `$run()` time. See `$setDebug()` for more details.
#' @param swagger Deprecated. Please use `docs` instead. See `$setDocs(docs)` or `$setApiSpec()` for more customization.
#' @param swaggerCallback An optional single-argument function that is
#' called back with the URL to an OpenAPI user interface when one becomes
#' ready. If missing, defaults to information previously set with `$setDocsCallback()`.
#' This value will only be used while running the router.
#' @param docs Visual documentation value to use while running the API.
#' This value will only be used while running the router.
#' If missing, defaults to information previously set with `setDocs()`.
#' For more customization, see `$setDocs()` or [pr_set_docs()] for examples.
#' @param quiet If `TRUE`, don't print routine startup messages.
schloerke marked this conversation as resolved.
Show resolved Hide resolved
#' @param ... Should be empty.
#' @importFrom lifecycle deprecated
#' @importFrom rlang missing_arg
run = function(
host = '127.0.0.1',
port = getOption('plumber.port', NULL),
swagger = deprecated(),
debug = deprecated(),
swaggerCallback = deprecated()
debug = missing_arg(),
swaggerCallback = missing_arg(),
...,
# any new args should go below `...`
docs = missing_arg(),
quiet = FALSE
) {

if (isTRUE(private$disable_run)) {
schloerke marked this conversation as resolved.
Show resolved Hide resolved
stop("Plumber router `$run()` method should not be called while `plumb()`ing a file")
}

ellipsis::check_dots_empty()

# Legacy support for RStudio pro products.
# Checks must be kept for >= 2 yrs after plumber v1.0.0 release date
if (lifecycle::is_present(debug)) {
lifecycle::deprecate_warn("1.0.0", "run(debug = )", "setDebug(debug = )")
self$setDebug(debug)
}
if (lifecycle::is_present(swagger)) {
if (is.function(swagger)) {
# between v0.4.6 and v1.0.0
lifecycle::deprecate_warn("1.0.0", "run(swagger = )", "setApiSpec(api = )")
self$setApiSpec(swagger)
# spec is now enabled by default. Do not alter
if (!rlang::is_missing(docs)) {
lifecycle::deprecate_warn("1.0.0", "Plumber$run(swagger = )", "Plumber$run(docs = )", details = "`docs` will take preference (ignoring `swagger`)")
# (`docs` is resolved after `swagger` checks)
} else {
if (isTRUE(private$docs_info$has_not_been_set)) {
# <= v0.4.6
lifecycle::deprecate_warn("1.0.0", "run(swagger = )", "setDocs(docs = )")
self$setDocs(swagger)
if (is.function(swagger)) {
# between v0.4.6 and v1.0.0
lifecycle::deprecate_warn("1.0.0", "Plumber$run(swagger = )", "Plumber$setApiSpec(api = )")
# set the new api function and force turn on the docs
old_api_spec_handler <- private$api_spec_handler
self$setApiSpec(swagger)
on.exit({
private$api_spec_handler <- old_api_spec_handler
}, add = TRUE)
docs <- TRUE
} else {
# $setDocs() has been called (other than during initialization).
# Believe that it is the correct behavior
# Warn about updating the run method
lifecycle::deprecate_warn("1.0.0", "run(swagger = )", details = "The Plumber docs have already been set. Ignoring `swagger` parameter.")
if (isTRUE(private$docs_info$has_not_been_set)) {
# <= v0.4.6
lifecycle::deprecate_warn("1.0.0", "Plumber$run(swagger = )", "Plumber$run(docs = )")
docs <- swagger
} else {
# $setDocs() has been called (other than during initialization).
# `docs` is not provided
# Believe that prior `$setDocs()` behavior is the correct behavior
# Warn about updating the run method
lifecycle::deprecate_warn("1.0.0", "Plumber$run(swagger = )", "Plumber$run(docs = )", details = "The Plumber docs have already been set. Ignoring `swagger` parameter.")
}
}
}
}
if (lifecycle::is_present(swaggerCallback)) {
lifecycle::deprecate_warn("1.0.0", "run(swaggerCallback = )", "setDocsCallback(callback = )")
self$setDocsCallback(swaggerCallback)
}

port <- findPort(port)

message("Running plumber API at ", urlHost(host = host, port = port, changeHostLocation = FALSE))
# Delay setting max size option. It could be set in `plumber.R`, which is after initialization
private$maxSize <- getOption('plumber.maxRequestSize', 0) #0 Unlimited

# Delay the setting of swaggerCallback as long as possible.
# An option could be set in `plumber.R`, which is after initialization
# Order: Run method parameter, internally set value, option, fallback option, NULL
swaggerCallback <-
rlang::maybe_missing(swaggerCallback,
rlang::maybe_missing(private$docs_callback,
getOption('plumber.docs.callback', getOption('plumber.swagger.url', NULL))
)
)

# Delay the setting of debug as long as possible.
# The router could be made in an interactive setting and used in background process.
# Do not determine if interactive until run time
prev_debug <- private$debug
on.exit({
private$debug <- prev_debug
}, add = TRUE)
# Fix the debug value while running.
self$setDebug(
# Order: Run method param, internally set value, is interactive()
# `$getDebug()` is dynamic given `setDebug()` has never been called.
rlang::maybe_missing(debug, self$getDebug())
)

docs_info <-
if (!rlang::is_missing(docs)) {
# Manually provided. Need to upgrade the parameter
upgrade_docs_parameter(docs)
} else {
private$docs_info
}

if (!isTRUE(quiet)) {
message("Running plumber API at ", urlHost(host = host, port = port, changeHostLocation = FALSE))
}

# Set and restore the wd to make it appear that the proc is running local to the file's definition.
if (!is.null(private$filename)) {
old_wd <- setwd(dirname(private$filename))
on.exit({setwd(old_wd)}, add = TRUE)
}

if (isTRUE(private$docs_info$enabled)) {
if (isTRUE(docs_info$enabled)) {
mount_docs(
pr = self,
host = host,
port = port,
docs_info = private$docs_info,
callback = private$docs_callback
docs_info = docs_info,
callback = swaggerCallback,
schloerke marked this conversation as resolved.
Show resolved Hide resolved
quiet = quiet
)
on.exit(unmount_docs(self, private$docs_info), add = TRUE)
on.exit(unmount_docs(self, docs_info), add = TRUE)
}

on.exit(private$runHooks("exit"), add = TRUE)
Expand Down Expand Up @@ -879,23 +936,7 @@ Plumber <- R6Class(
docs = getOption("plumber.docs", TRUE),
...
) {
stopifnot(length(docs) == 1)
stopifnot(is.logical(docs) || is.character(docs))
if (isTRUE(docs)) {
docs <- "swagger"
}
if (is.character(docs) && is_docs_available(docs)) {
enabled <- TRUE
} else {
enabled <- FALSE
docs <- "__not_enabled__"
}
private$docs_info <- list(
enabled = enabled,
docs = docs,
args = list(...),
has_not_been_set = FALSE
)
private$docs_info <- upgrade_docs_parameter(docs, ...)
},
#' @description Set a callback to notify where the API's visual documentation is located.
#'
Expand All @@ -918,19 +959,19 @@ Plumber <- R6Class(
}
private$docs_callback <- callback
},
#' @description Set debug value to include error messages
#' @description Set debug value to include error messages.
#'
#' See also: `$getDebug()` and [pr_set_debug()]
#' @param debug `TRUE` provides more insight into your API errors.
setDebug = function(debug = interactive()) {
stopifnot(length(debug) == 1)
private$debug <- isTRUE(debug)
},
#' @description Retrieve the `debug` value.
#' @description Retrieve the `debug` value. If it has never been set, the result of `interactive()` will be used.
#'
#' See also: `$getDebug()` and [pr_set_debug()]
getDebug = function() {
private$debug
private$debug %||% default_debug()
},
#' @description Add a filter to plumber router
#'
Expand Down Expand Up @@ -1176,7 +1217,7 @@ Plumber <- R6Class(

errorHandler = NULL,
notFoundHandler = NULL,
maxSize = NULL, # Max request size in bytes
maxSize = 0, # Max request size in bytes. (0 is a no-op)

api_spec_handler = NULL,
docs_info = NULL,
Expand Down Expand Up @@ -1271,9 +1312,32 @@ Plumber <- R6Class(



upgrade_docs_parameter <- function(docs, ...) {
stopifnot(length(docs) == 1)
stopifnot(is.logical(docs) || is.character(docs))
if (isTRUE(docs)) {
docs <- "swagger"
}
if (is.character(docs) && is_docs_available(docs)) {
enabled <- TRUE
} else {
enabled <- FALSE
docs <- "__not_enabled__"
}

list(
enabled = enabled,
docs = docs,
args = list(...),
has_not_been_set = FALSE
)
}



default_debug <- function() {
interactive()
}


urlHost <- function(scheme = "http", host, port, path = "", changeHostLocation = FALSE) {
Expand Down
36 changes: 33 additions & 3 deletions R/pr.R
Original file line number Diff line number Diff line change
Expand Up @@ -475,24 +475,54 @@ pr_filter <- function(pr,
#' @param port A number or integer that indicates the server port that should
#' be listened on. Note that on most Unix-like systems including Linux and
#' Mac OS X, port numbers smaller than 1025 require root privileges.
#' @param ... Should be empty.
#' @param debug If `TRUE`, it will provide more insight into your API errors.
#' Using this value will only last for the duration of the run.
#' If [pr_set_debug()] has not been called, `debug` will default to `interactive()` at [pr_run()] time
#' @param docs Visual documentation value to use while running the API.
#' This value will only be used while running the router.
#' If missing, defaults to information previously set with [pr_set_docs()].
#' For more customization, see [pr_set_docs()] for examples.
#' @param swaggerCallback An optional single-argument function that is called
#' back with the URL to an OpenAPI user interface when one becomes ready. If
#' missing, defaults to information set with [pr_set_docs_callback()].
#' This value will only be used while running the router.
#' @param quiet If `TRUE`, don't print routine startup messages.
schloerke marked this conversation as resolved.
Show resolved Hide resolved
#'
#' @examples
#' \dontrun{
#' pr() %>%
#' pr_run()
#'
#' pr() %>%
#' pr_run(port = 5762)
#' pr_run(
#' # manually set port
#' port = 5762,
#' # turn of visual documentation
jcheng5 marked this conversation as resolved.
Show resolved Hide resolved
#' docs = FALSE,
#' # do not display startup messages
#' quiet = TRUE
#' )
#' }
#'
#' @export
pr_run <- function(pr,
host = '127.0.0.1',
port = getOption('plumber.port', NULL)
port = getOption('plumber.port', NULL),
...,
debug = missing_arg(),
docs = missing_arg(),
swaggerCallback = missing_arg(),
quiet = FALSE
) {
validate_pr(pr)
schloerke marked this conversation as resolved.
Show resolved Hide resolved
ellipsis::check_dots_empty()
pr$run(host = host,
port = port)
port = port,
debug = debug,
docs = docs,
swaggerCallback = swaggerCallback,
schloerke marked this conversation as resolved.
Show resolved Hide resolved
quiet = quiet)
}


Expand Down
Loading