Skip to content

Commit 032b6d7

Browse files
authored
Merge pull request #132 from dsweber2/largeRequests
Extreme requests: adding the `timeout_seconds` and `return_empty` parameters
2 parents 35ae86b + cfe5521 commit 032b6d7

7 files changed

+51
-20
lines changed

R/covidcast.R

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,16 @@ print.covidcast_data_source <- function(source, ...) {
100100
#' print(smoothed_cli)
101101
#' df <- smoothed_cli$call("nation", "us", epirange(20210405, 20210410))
102102
#' @param base_url optional alternative API base url
103+
#' @param timeout_seconds the maximum amount of time to wait for a response
103104
#' @importFrom httr stop_for_status content http_type
104105
#' @importFrom jsonlite fromJSON
105106
#' @importFrom xml2 read_html xml_find_all xml_text
106107
#' @return an instance of covidcast_epidata
107108
#'
108109
#' @export
109-
covidcast_epidata <- function(base_url = global_base_url) {
110+
covidcast_epidata <- function(base_url = global_base_url, timeout_seconds = 30) {
110111
url <- join_url(base_url, "covidcast/meta")
111-
response <- do_request(url, list())
112+
response <- do_request(url, list(), timeout_seconds)
112113

113114
if (response$status_code != 200) {
114115
# 500, 429, 401 are possible

R/epidatacall.R

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,23 @@ print.epidata_call <- function(epidata_call) {
111111
#' time_value and value fields or c("-direction") to return everything except
112112
#' the direction field
113113
#' @param disable_date_parsing disable automatic date parsing
114+
#' @param return_empty boolean that allows returning an empty tibble if there is no data
115+
#' @param timeout_seconds the maximum amount of time to wait for a response
114116
#' @return
115117
#' - For `fetch`: a tibble or a JSON-like list
116118
#' @export
117119
#'
118-
fetch <- function(epidata_call, fields = NULL, disable_date_parsing = FALSE) {
120+
fetch <- function(epidata_call, fields = NULL, disable_date_parsing = FALSE, return_empty = FALSE, timeout_seconds = 30) {
119121
stopifnot(inherits(epidata_call, "epidata_call"))
120122
stopifnot(is.null(fields) || is.character(fields))
121123
stopifnot(is.logical(disable_date_parsing), length(disable_date_parsing) == 1)
124+
stopifnot(is.logical(return_empty))
125+
stopifnot(is.numeric(timeout_seconds))
122126

123127
if (epidata_call$only_supports_classic) {
124-
return(fetch_classic(epidata_call, fields))
128+
return(fetch_classic(epidata_call, fields, return_empty = return_empty, timeout_seconds = timeout_seconds))
125129
} else {
126-
return(fetch_tbl(epidata_call, fields, disable_date_parsing))
130+
return(fetch_tbl(epidata_call, fields, disable_date_parsing, return_empty, timeout_seconds = timeout_seconds))
127131
}
128132
}
129133

@@ -136,17 +140,21 @@ fetch <- function(epidata_call, fields = NULL, disable_date_parsing = FALSE) {
136140
#' time_value and value fields or c("-direction") to return everything except
137141
#' the direction field
138142
#' @param disable_date_parsing disable automatic date parsing
143+
#' @param return_empty boolean that allows returning an empty tibble if there is no data.
144+
#' @param timeout_seconds the maximum amount of time to wait for a response
139145
#' @importFrom readr read_csv
140146
#' @importFrom httr stop_for_status content
141147
#' @importFrom rlang abort
142148
#' @return
143149
#' - For `fetch_tbl`: a [`tibble::tibble`]
144150
#' @importFrom tibble as_tibble
145151
#' @keywords internal
146-
fetch_tbl <- function(epidata_call, fields = NULL, disable_date_parsing = FALSE) {
152+
fetch_tbl <- function(epidata_call, fields = NULL, disable_date_parsing = FALSE, return_empty = FALSE, timeout_seconds = 30) {
147153
stopifnot(inherits(epidata_call, "epidata_call"))
148154
stopifnot(is.null(fields) || is.character(fields))
149155
stopifnot(is.logical(disable_date_parsing), length(disable_date_parsing) == 1)
156+
stopifnot(is.logical(return_empty))
157+
stopifnot(is.numeric(timeout_seconds))
150158

151159
if (epidata_call$only_supports_classic) {
152160
rlang::abort("This endpoint only supports the classic message format, due to a non-standard behavior. Use fetch_classic instead.",
@@ -155,7 +163,10 @@ fetch_tbl <- function(epidata_call, fields = NULL, disable_date_parsing = FALSE)
155163
)
156164
}
157165

158-
response_content <- fetch_classic(epidata_call, fields, disable_data_frame_parsing = FALSE)
166+
response_content <- fetch_classic(epidata_call, fields, disable_data_frame_parsing = FALSE, return_empty = return_empty, timeout_seconds = timeout_seconds)
167+
if (return_empty && length(response_content) == 0) {
168+
return(tibble())
169+
}
159170
return(parse_data_frame(epidata_call, response_content, disable_date_parsing) %>% as_tibble())
160171
}
161172

@@ -172,32 +183,38 @@ fetch_tbl <- function(epidata_call, fields = NULL, disable_date_parsing = FALSE)
172183
#' @param disable_data_frame_parsing do not automatically cast the epidata
173184
#' output to a data frame (some endpoints return a list of lists, which is not
174185
#' a data frame)
186+
#' @param return_empty boolean that allows returning an empty tibble if there is no data.
187+
#' @param timeout_seconds the maximum amount of time to wait for a response
175188
#' @importFrom httr stop_for_status content http_error
176189
#' @importFrom jsonlite fromJSON
177190
#' @return
178191
#' - For `fetch_classic`: a JSON-like list
179192
#' @keywords internal
180-
fetch_classic <- function(epidata_call, fields = NULL, disable_data_frame_parsing = TRUE) {
193+
fetch_classic <- function(epidata_call, fields = NULL, disable_data_frame_parsing = TRUE, return_empty = FALSE, timeout_seconds = 30) {
181194
stopifnot(inherits(epidata_call, "epidata_call"))
182195
stopifnot(is.null(fields) || is.character(fields))
196+
stopifnot(is.logical(return_empty))
197+
stopifnot(is.numeric(timeout_seconds))
183198

184-
response <- request_impl(epidata_call, "classic", fields)
199+
response <- request_impl(epidata_call, "classic", fields, timeout_seconds)
185200
response_content <- httr::content(response, as = "text", encoding = "UTF-8")
186201

187202
response_content <- jsonlite::fromJSON(response_content, simplifyDataFrame = !disable_data_frame_parsing)
188203

189204
# success is 1, no results is -2, truncated is 2, -1 is generic error
190205
if (response_content$result != 1) {
191-
rlang::abort(paste0("epidata error: ", response_content$message), "epidata_error")
206+
if ((response_content$result != -2) && !(return_empty)) {
207+
rlang::abort(paste0("epidata error: ", response_content$message), "epidata_error")
208+
}
192209
}
193210
if (response_content$message != "success") {
194211
rlang::warn(paste0("epidata warning: ", response_content$message), "epidata_warning")
195212
}
196213
return(response_content$epidata)
197214
}
198215

199-
fetch_debug <- function(epidata_call, format_type = "classic", fields = NULL) {
200-
response <- request_impl(epidata_call, format_type, fields)
216+
fetch_debug <- function(epidata_call, format_type = "classic", fields = NULL, timeout_seconds = 30) {
217+
response <- request_impl(epidata_call, format_type, fields, timeout_seconds)
201218
content <- httr::content(response, "text", encoding = "UTF-8")
202219
content
203220
}
@@ -247,13 +264,13 @@ with_base_url <- function(epidata_call, base_url) {
247264
#' HTTP errors and forwarding the HTTP body in R errors
248265
#' @importFrom httr stop_for_status content http_type
249266
#' @importFrom xml2 read_html xml_find_all xml_text
250-
request_impl <- function(epidata_call, format_type, fields = NULL) {
267+
request_impl <- function(epidata_call, format_type, fields = NULL, timeout_seconds = 30) {
251268
stopifnot(inherits(epidata_call, "epidata_call"))
252269
stopifnot(format_type %in% c("json", "csv", "classic"))
253270

254271
url <- full_url(epidata_call)
255272
params <- request_arguments(epidata_call, format_type, fields)
256-
response <- do_request(url, params)
273+
response <- do_request(url, params, timeout_seconds)
257274

258275
if (response$status_code != 200) {
259276
# 500, 429, 401 are possible

R/request.R

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ join_url <- function(url, endpoint) {
2121
#' }
2222
#'
2323
#' @importFrom httr RETRY
24-
do_request <- function(url, params) {
24+
do_request <- function(url, params, timeout_seconds = 30) {
2525
# don't retry in case of certain status codes
2626
res <- httr::RETRY("GET",
2727
url = url,
2828
query = params,
2929
terminate_on = c(400, 401, 403, 405, 414, 500),
3030
http_headers,
31-
httr::authenticate("epidata", get_auth_key())
31+
httr::authenticate("epidata", get_auth_key()),
32+
httr::timeout(timeout_seconds)
3233
)
3334
if (res$status_code == 414) {
3435
res <- httr::RETRY("POST",

man/covidcast_epidata.Rd

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/do_request.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/epidata_call.Rd

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/request_impl.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)