Skip to content

Commit

Permalink
Add req_paginate_page_index()
Browse files Browse the repository at this point in the history
  • Loading branch information
mgirlich committed Sep 19, 2023
1 parent 34b0855 commit 0ab0567
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 70 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export(req_options)
export(req_paginate)
export(req_paginate_next_url)
export(req_paginate_offset)
export(req_paginate_page_index)
export(req_paginate_token)
export(req_perform)
export(req_progress)
Expand Down
81 changes: 60 additions & 21 deletions R/paginate.R
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,11 @@ paginate_next_request <- function(resp, req, parsed) {
)
}

#' @param next_url A function that extracts the url to the next page from the
#' [response] and the `body`.
#' @param next_url A function that extracts the url to the next page. It takes
#' two arguments:
#'
#' 1. `resp`: the response of the current request.
#' 2. `parsed`: the result of the argument `parse_resp`.
#' @rdname req_paginate
#' @export
req_paginate_next_url <- function(req,
Expand All @@ -193,8 +196,45 @@ req_paginate_next_url <- function(req,
)
}

#' @param set_token A function that applies the new token to the request. It
#' takes two arguments: a [request] and the new token.
#'
#' 1. `req`: the previous request.
#' 2. `token`: the token for the next page.
#' @param next_token A function that extracts the next token from the [response].
#' @rdname req_paginate
#' @export
req_paginate_token <- function(req,
set_token,
next_token,
parse_resp = NULL,
n_pages = NULL) {
check_function2(set_token, args = c("req", "token"))
check_function2(next_token, args = c("resp", "parsed"))

next_request <- function(req, resp, parsed) {
next_token <- next_token(resp, parsed)

if (is.null(next_token)) {
return(NULL)
}

set_token(req, next_token)
}

req_paginate(
req,
next_request,
parse_resp = parse_resp,
n_pages = n_pages
)
}

#' @param offset A function that applies the new offset to the request. It takes
#' two arguments: a [request] and an integer offset.
#' two arguments:
#'
#' 1. `req`: the previous request.
#' 2. `offset`: the integer offset for the next page.
#' @param page_size A whole number that specifies the page size i.e. the number
#' of elements per page.
#' @rdname req_paginate
Expand Down Expand Up @@ -225,35 +265,34 @@ req_paginate_offset <- function(req,
out
}

#' @param set_token A function that applies the new token to the request. It
#' takes two arguments: a [request] and the new token.
#' @param next_token A function that extracts the next token from the [response].
#' @param page_index A function that applies the page index to the request. It
#' takes two arguments:
#'
#' 1. `req`: the previous request.
#' 2. `offset`: the integer page index for the next page.
#' @rdname req_paginate
#' @export
req_paginate_token <- function(req,
set_token,
next_token,
parse_resp = NULL,
n_pages = NULL) {
check_function2(set_token, args = c("req", "token"))
check_function2(next_token, args = c("resp", "parsed"))
req_paginate_page_index <- function(req,
page_index,
parse_resp = NULL,
n_pages = NULL) {
check_function2(page_index, args = c("req", "page"))

next_request <- function(req, resp, parsed) {
next_token <- next_token(resp, parsed)

if (is.null(next_token)) {
return(NULL)
}

set_token(req, next_token)
new_page <- req$policies$paginate$page + 1L
req$policies$paginate$page <- new_page
page_index(req, new_page)
}

req_paginate(
out <- req_paginate(
req,
next_request,
parse_resp = parse_resp,
n_pages = n_pages
)

out$policies$paginate$page <- 1L
out
}

check_has_pagination_policy <- function(req, call = caller_env()) {
Expand Down
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ reference:
- secrets
- obfuscate
- url_parse
- progress_bars

- title: OAuth
desc: >
Expand Down
42 changes: 32 additions & 10 deletions man/req_paginate.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 31 additions & 18 deletions tests/testthat/_snaps/paginate.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,6 @@
Error in `req_paginate_next_url()`:
! `next_url` must have the arguments `resp` and `parsed`; it currently has `req` and `parsed`.

# req_paginate_offset() checks inputs

Code
req_paginate_offset(req, "a")
Condition
Error in `req_paginate_offset()`:
! `offset` must be a function, not the string "a".
Code
req_paginate_offset(req, function(req) req)
Condition
Error in `req_paginate_offset()`:
! `offset` must have the arguments `req` and `offset`; it currently has `req`.
Code
req_paginate_offset(req, function(req, offset) req, page_size = "a")
Condition
Error in `req_paginate_offset()`:
! `page_size` must be a whole number, not the string "a".

# req_paginate_token() checks inputs

Code
Expand All @@ -110,6 +92,37 @@
Error in `req_paginate_token()`:
! `next_token` must have the arguments `resp` and `parsed`; it currently has `req`.

# req_paginate_offset() checks inputs

Code
req_paginate_offset(req, "a")
Condition
Error in `req_paginate_offset()`:
! `offset` must be a function, not the string "a".
Code
req_paginate_offset(req, function(req) req)
Condition
Error in `req_paginate_offset()`:
! `offset` must have the arguments `req` and `offset`; it currently has `req`.
Code
req_paginate_offset(req, function(req, offset) req, page_size = "a")
Condition
Error in `req_paginate_offset()`:
! `page_size` must be a whole number, not the string "a".

# req_paginate_page_index() checks inputs

Code
req_paginate_page_index(req, "a")
Condition
Error in `req_paginate_page_index()`:
! `page_index` must be a function, not the string "a".
Code
req_paginate_page_index(req, function(req) req)
Condition
Error in `req_paginate_page_index()`:
! `page_index` must have the arguments `req` and `page`; it currently has `req`.

# paginate_req_perform() checks inputs

Code
Expand Down
69 changes: 48 additions & 21 deletions tests/testthat/test-paginate.R
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,42 @@ test_that("req_paginate_next_url() can paginate", {
expect_equal(req3$url, "https://pokeapi.co/api/v2/pokemon?offset=22&limit=11")
})

test_that("req_paginate_token() checks inputs", {
req <- request("http://example.com/")

expect_snapshot(error = TRUE, {
req_paginate_token(req, "a")
req_paginate_token(req, function(req) req)
req_paginate_token(req, function(req, token) req, next_token = "a")
req_paginate_token(req, function(req, token) req, next_token = function(req) req)
})
})

test_that("req_paginate_token() can paginate", {
local_mocked_responses(list(
response_json(body = list(x = 1, my_next_token = 2)),
response_json(body = list(x = 1, my_next_token = 3))
))

req1 <- request("http://example.com") %>%
req_paginate_token(
set_token = function(req, token) {
req_body_json(req, list(my_token = token))
},
next_token = function(resp, parsed) {
resp_body_json(resp)$my_next_token
}
)

resp <- req_perform(req1)
req2 <- paginate_next_request(resp, req1)
expect_equal(req2$body$data$my_token, 2L)

resp <- req_perform(req2)
req3 <- paginate_next_request(resp, req2)
expect_equal(req3$body$data$my_token, 3L)
})

test_that("req_paginate_offset() checks inputs", {
req <- request("http://example.com/")

Expand Down Expand Up @@ -104,40 +140,31 @@ test_that("req_paginate_offset() can paginate", {
expect_equal(req3$url, "https://pokeapi.co/api/v2/pokemon?limit=11&offset=22")
})

test_that("req_paginate_token() checks inputs", {
test_that("req_paginate_page_index() checks inputs", {
req <- request("http://example.com/")

expect_snapshot(error = TRUE, {
req_paginate_token(req, "a")
req_paginate_token(req, function(req) req)
req_paginate_token(req, function(req, token) req, next_token = "a")
req_paginate_token(req, function(req, token) req, next_token = function(req) req)
req_paginate_page_index(req, "a")
req_paginate_page_index(req, function(req) req)
})
})

test_that("req_paginate_token() can paginate", {
local_mocked_responses(list(
response_json(body = list(x = 1, my_next_token = 2)),
response_json(body = list(x = 1, my_next_token = 3))
))

req1 <- request("http://example.com") %>%
req_paginate_token(
set_token = function(req, token) {
req_body_json(req, list(my_token = token))
},
next_token = function(resp, parsed) {
resp_body_json(resp)$my_next_token
}
test_that("req_paginate_page_index() can paginate", {
req1 <- request("https://pokeapi.co/api/v2/pokemon") %>%
req_url_query(limit = 11) %>%
req_paginate_page_index(
page_index = function(req, page) req_url_query(req, page = page)
)

resp <- req_perform(req1)
req2 <- paginate_next_request(resp, req1)
expect_equal(req2$body$data$my_token, 2L)
expect_equal(req2$url, "https://pokeapi.co/api/v2/pokemon?limit=11&page=2")
# offset stays the same when applied twice
expect_equal(req2$url, "https://pokeapi.co/api/v2/pokemon?limit=11&page=2")

resp <- req_perform(req2)
req3 <- paginate_next_request(resp, req2)
expect_equal(req3$body$data$my_token, 3L)
expect_equal(req3$url, "https://pokeapi.co/api/v2/pokemon?limit=11&page=3")
})

test_that("paginate_req_perform() checks inputs", {
Expand Down

0 comments on commit 0ab0567

Please sign in to comment.