Skip to content

added retry logic to HTTP requests #1656

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

Merged
merged 4 commits into from
May 19, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
added retry logic to HTTP requests
  • Loading branch information
jameslamb committed May 19, 2020
commit 858a7e29f0b797bbdc2ea487aa8aaaca2cce6647
6 changes: 3 additions & 3 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -241,14 +241,14 @@ importFrom(htmlwidgets,saveWidget)
importFrom(htmlwidgets,shinyRenderWidget)
importFrom(htmlwidgets,shinyWidgetOutput)
importFrom(htmlwidgets,sizingPolicy)
importFrom(httr,GET)
importFrom(httr,PATCH)
importFrom(httr,POST)
importFrom(httr,RETRY)
importFrom(httr,add_headers)
importFrom(httr,authenticate)
importFrom(httr,config)
importFrom(httr,content)
importFrom(httr,stop_for_status)
importFrom(httr,warn_for_status)
importFrom(httr,write_disk)
importFrom(jsonlite,parse_json)
importFrom(jsonlite,read_json)
importFrom(jsonlite,toJSON)
Expand Down
108 changes: 58 additions & 50 deletions R/api_exports.R
Original file line number Diff line number Diff line change
@@ -1,68 +1,68 @@
#' Tools for working with plotly's REST API (v2)
#'
#'
#' Convenience functions for working with version 2 of plotly's REST API.
#' Upload R objects to a plotly account via `api_create()` and download
#' plotly objects via `api_download_plot()`/`api_download_grid()`.
#' For anything else, use `api()`.
#'
#' @param id a filename id.
#'
#' @param id a filename id.
#' @param username a plotly username.
#'
#' @param x An R object to hosted on plotly's web platform.
#'
#' @param x An R object to hosted on plotly's web platform.
#' Can be a plotly/ggplot2 object or a \link{data.frame}.
#' @param filename character vector naming file(s). If `x` is a plot,
#' can be a vector of length 2 naming both the plot AND the underlying grid.
#' @param fileopt character string describing whether to "overwrite" existing
#' @param fileopt character string describing whether to "overwrite" existing
#' files or ensure "new" file(s) are always created.
#' @param sharing If 'public', anyone can view this graph. It will appear in
#' @param sharing If 'public', anyone can view this graph. It will appear in
#' your profile and can appear in search engines. You do not need to be
#' logged in to Plotly to view this chart.
#' If 'private', only you can view this plot. It will not appear in the
#' Plotly feed, your profile, or search engines. You must be logged in to
#' Plotly to view this graph. You can privately share this graph with other
#' Plotly users in your online Plotly account and they will need to be logged
#' Plotly feed, your profile, or search engines. You must be logged in to
#' Plotly to view this graph. You can privately share this graph with other
#' Plotly users in your online Plotly account and they will need to be logged
#' in to view this plot.
#' If 'secret', anyone with this secret link can view this chart. It will
#' not appear in the Plotly feed, your profile, or search engines.
#' If it is embedded inside a webpage or an IPython notebook, anybody who is
#' viewing that page will be able to view the graph.
#' not appear in the Plotly feed, your profile, or search engines.
#' If it is embedded inside a webpage or an IPython notebook, anybody who is
#' viewing that page will be able to view the graph.
#' You do not need to be logged in to view this plot.
#'
#' @param endpoint the endpoint (i.e., location) for the request.
#'
#' @param endpoint the endpoint (i.e., location) for the request.
#' To see a list of all available endpoints, call `api()`.
#' Any relevant query parameters should be included here (see examples).
#' @param verb name of the HTTP verb to use (as in, [httr::VERB()]).
#' @param body body of the HTTP request(as in, [httr::VERB()]).
#' If this value is not already converted to JSON
#' @param verb name of the HTTP verb to use (as in, [httr::RETRY()]).
#' @param body body of the HTTP request(as in, [httr::RETRY()]).
#' If this value is not already converted to JSON
#' (via [jsonlite::toJSON()]), it uses the internal `to_JSON()`
#' to ensure values are "automatically unboxed" (i.e., vec.
#'
#' @param ... For `api()`, these arguments are passed onto
#' [httr::VERB()]. For `api_create()`, these arguments are
#' @param ... For `api()`, these arguments are passed onto
#' [httr::RETRY()]. For `api_create()`, these arguments are
#' included in the body of the HTTP request.
#'
#'
#' @export
#' @rdname api
#' @author Carson Sievert
#' @references \url{https://api.plot.ly/v2}
#' @seealso [signup()]
#' @examples
#'
#' @examples
#'
#' \dontrun{
#'
#'
#' # ------------------------------------------------------------
#' # api_create() makes it easy to upload ggplot2/plotly objects
#' # and/or data frames to your plotly account
#' # ------------------------------------------------------------
#'
#' # A data frame creates a plotly "grid". Printing one will take you
#'
#' # A data frame creates a plotly "grid". Printing one will take you
#' # to the it's web address so you can start creating!
#' (m <- api_create(mtcars))
#'
#'
#' # A plotly/ggplot2 object create a plotly "plot".
#' p <- plot_ly(mtcars, x = ~factor(vs))
#' (r <- api_create(p))
#'
#'
#' # api_create() returns metadata about the remote "file". Here is
#' # one way you could use that metadata to download a plot for local use:
#' fileID <- strsplit(r$file$fid, ":")[[1]]
Expand All @@ -72,18 +72,18 @@
#' )
#'
#' ------------------------------------------------------------
#' # The api() function provides a low-level interface for performing
#' # The api() function provides a low-level interface for performing
#' # any action at any endpoint! It always returns a list.
#' # ------------------------------------------------------------
#'
#'
#' # list all the endpoints
#' api()
#'
#'
#' # search the entire platform!
#' # see https://api.plot.ly/v2/search
#' api("search?q=overdose")
#' api("search?q=plottype:pie trump fake")
#'
#'
#' # these examples will require a user account
#' usr <- Sys.getenv("plotly_username", NA)
#' if (!is.na(usr)) {
Expand All @@ -92,27 +92,27 @@
#' # your folders/files https://api.plot.ly/v2/folders#user
#' api(sprintf("folders/home?user=%s", usr))
#' }
#'
#'
#' # Retrieve a specific file https://api.plot.ly/v2/files#retrieve
#' api("files/cpsievert:14681")
#'
#'
#' # change the filename https://api.plot.ly/v2/files#update
#' # (note: this won't work unless you have proper credentials to the relevant account)
#' api("files/cpsievert:14681", "PATCH", list(filename = "toy file"))
#'
#' api("files/cpsievert:14681", "PATCH", list(filename = "toy file"))
#'
#' # Copy a file https://api.plot.ly/v2/files#lookup
#' api("files/cpsievert:14681/copy", "POST")
#'
#'
#' # Create a folder https://api.plot.ly/v2/folders#create
#' api("folders", "POST", list(path = "/starts/at/root/and/ends/here"))
#'
#'
#' }
#'
#'


#' @rdname api
#' @export
api_create <- function(x = last_plot(), filename = NULL,
api_create <- function(x = last_plot(), filename = NULL,
fileopt = c("overwrite", "new"),
sharing = c("public", "private", "secret"), ...) {
fileopt <- match.arg(fileopt, c("overwrite", "new"))
Expand Down Expand Up @@ -140,7 +140,7 @@ api_create.data.frame <- api_create_grid
api_download_plot <- function(id, username) {
f <- api_download_file(id, username)
api_expect_filetype(f, "plot")

as_widget(
api_download_file(id, username, "plots", "content?inline_data=true")
)
Expand All @@ -152,7 +152,7 @@ api_download_plot <- function(id, username) {
api_download_grid <- function(id, username) {
f <- api_download_file(id, username)
api_expect_filetype(f, "grid")

prefix_class(
api_download_file(id, username, "grids"), "api_grid_local"
)
Expand All @@ -170,27 +170,35 @@ api_download_file <- function(id, username, endpoint = "files", ...) {


#' @rdname api
#' @importFrom httr RETRY
#' @export
api <- function(endpoint = "/", verb = "GET", body = NULL, ...) {
api_check_endpoint(endpoint)

# construct the url
url <- httr::modify_url(
get_domain("api"),
get_domain("api"),
scheme = "https",
# TODO: should anything else in the endpoint (besides whitespace) be escaped?
path = file.path("v2", gsub("\\s+", "+", endpoint))
)

# default to unboxing (i.e., no arrays of length 1)
if (!is.null(body) && !inherits(body, "json")) {
body <- to_JSON(body)
}

resp <- httr::VERB(
verb = verb, url = url, api_headers(), api_auth(),
body = body, ...

resp <- httr::RETRY(
verb = verb
, url = url
, api_headers()
, api_auth()
, body = body
, times = 5
, terminate_on = c(400, 401, 403, 404)
, terminate_on_success = TRUE
, ...
)

structure(process(resp), class = "api")
}
8 changes: 4 additions & 4 deletions R/imports.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#' @importFrom tidyr unnest
#' @importFrom viridisLite viridis
#' @importFrom jsonlite toJSON parse_json read_json
#' @importFrom httr GET POST PATCH content config add_headers stop_for_status warn_for_status
#' @importFrom httr RETRY content config add_headers authenticate stop_for_status warn_for_status write_disk
#' @importFrom htmlwidgets createWidget sizingPolicy saveWidget onRender prependContent
#' @importFrom lazyeval f_eval is_formula all_dots is_lang f_new
#' @importFrom tibble as_tibble
Expand All @@ -18,7 +18,7 @@
NULL


#' @importFrom dplyr mutate
#' @importFrom dplyr mutate
#' @name mutate
#' @rdname reexports
#' @export
Expand Down Expand Up @@ -66,7 +66,7 @@ dplyr::rename
#' @export
dplyr::rename_

#' @importFrom dplyr group_by
#' @importFrom dplyr group_by
#' @name group_by
#' @rdname reexports
#' @export
Expand Down Expand Up @@ -169,7 +169,7 @@ dplyr::filter_
# #' @rdname reexports
# #' @export
# tidyr::gather
#
#
# #' @importFrom tidyr gather_
# #' @name gather_
# #' @rdname reexports
Expand Down
Loading