Skip to content

Commit 4ad7379

Browse files
authored
Merge cfe5521 into f121d72
2 parents f121d72 + cfe5521 commit 4ad7379

File tree

9 files changed

+65
-51
lines changed

9 files changed

+65
-51
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 & 28 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,48 +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

187-
# TODO Temporary workaround the first row of the response being a comment
188-
# Remove on 2023-06-21
189-
if (grepl("This request exceeded", response_content) && !epidata_call$only_supports_classic) {
190-
response_content <- jsonlite::fromJSON(response_content, simplifyDataFrame = FALSE)
191-
message <- response_content$epidata[[1L]]
192-
cli::cli_abort(c(
193-
"epidata warning, promoted to error: {message}",
194-
"i" = "Either:",
195-
"*" = "set the environment variable DELPHI_EPIDATA_KEY, or",
196-
"*" = 'set the option "delphi.epidata.key":',
197-
" " = '{.code options(delphi.epidata.key = "YOUR_KEY_OR_TEMP_KEY")}',
198-
"To save your key for later sessions (and hide it from your code), you can edit your .Renviron file with:",
199-
"*" = "usethis::edit_r_environ()"
200-
))
201-
}
202-
203202
response_content <- jsonlite::fromJSON(response_content, simplifyDataFrame = !disable_data_frame_parsing)
204203

205204
# success is 1, no results is -2, truncated is 2, -1 is generic error
206205
if (response_content$result != 1) {
207-
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+
}
208209
}
209210
if (response_content$message != "success") {
210211
rlang::warn(paste0("epidata warning: ", response_content$message), "epidata_warning")
211212
}
212213
return(response_content$epidata)
213214
}
214215

215-
fetch_debug <- function(epidata_call, format_type = "classic", fields = NULL) {
216-
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)
217218
content <- httr::content(response, "text", encoding = "UTF-8")
218219
content
219220
}
@@ -263,13 +264,13 @@ with_base_url <- function(epidata_call, base_url) {
263264
#' HTTP errors and forwarding the HTTP body in R errors
264265
#' @importFrom httr stop_for_status content http_type
265266
#' @importFrom xml2 read_html xml_find_all xml_text
266-
request_impl <- function(epidata_call, format_type, fields = NULL) {
267+
request_impl <- function(epidata_call, format_type, fields = NULL, timeout_seconds = 30) {
267268
stopifnot(inherits(epidata_call, "epidata_call"))
268269
stopifnot(format_type %in% c("json", "csv", "classic"))
269270

270271
url <- full_url(epidata_call)
271272
params <- request_arguments(epidata_call, format_type, fields)
272-
response <- do_request(url, params)
273+
response <- do_request(url, params, timeout_seconds)
273274

274275
if (response$status_code != 200) {
275276
# 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.

tests/testthat/test-epidatacall.R

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,21 +81,20 @@ test_that("fetch_tbl warns on non-success", {
8181
.package = "httr"
8282
)
8383
# TODO: Turn these tests back on, when the API is fully online
84-
# Remove on 2023-06-21
85-
# artificial_warning <- "* This is a warning with a leading asterisk and {braces} to make sure we don't have bulleting/glue bugs."
86-
# debug_triplet <- readRDS(testthat::test_path("data/test-classic.rds")) %>%
87-
# jsonlite::fromJSON() %>%
88-
# `[[<-`("message", artificial_warning)
89-
# local_mocked_bindings(
90-
# # see generation code above
91-
# fromJSON = function(...) debug_triplet,
92-
# .package = "jsonlite"
93-
# )
84+
artificial_warning <- "* This is a warning with a leading asterisk and {braces} to make sure we don't have bulleting/glue bugs."
85+
debug_triplet <- readRDS(testthat::test_path("data/test-classic.rds")) %>%
86+
jsonlite::fromJSON() %>%
87+
`[[<-`("message", artificial_warning)
88+
local_mocked_bindings(
89+
# see generation code above
90+
fromJSON = function(...) debug_triplet,
91+
.package = "jsonlite"
92+
)
9493

95-
# expect_warning(epidata_call %>% fetch_tbl(),
96-
# regexp = paste0("epidata warning: ", artificial_warning),
97-
# fixed = TRUE
98-
# )
94+
expect_warning(epidata_call %>% fetch_tbl(),
95+
regexp = paste0("epidata warning: ", artificial_warning),
96+
fixed = TRUE
97+
)
9998
})
10099

101100
test_that("classic only fetch", {

vignettes/endpoints.Rmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ Example call:
141141

142142
```{r}
143143
del <- delphi(system = "ec", epiweek = 201501) %>% fetch()
144-
names(del[[1L]]$forecast)
144+
names(del$forecast)
145145
```
146146

147147
### FluSurv hospitalization data

0 commit comments

Comments
 (0)