File: request_make.R

package info (click to toggle)
r-cran-googledrive 2.1.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,584 kB
  • sloc: sh: 13; makefile: 2
file content (143 lines) | stat: -rw-r--r-- 5,692 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#' Make a request for the Google Drive v3 API
#'
#' Low-level functions to execute one or more Drive API requests and, perhaps,
#' process the response(s). Most users should, instead, use higher-level
#' wrappers that facilitate common tasks, such as uploading or downloading Drive
#' files. The functions here are intended for internal use and for programming
#' around the Drive API. Three functions are documented here:
#'   * `request_make()` does the bare minimum: calls [gargle::request_make()],
#'     only adding the googledrive user agent. Typically the input is created
#'     with [request_generate()] and the output is processed with
#'     [gargle::response_process()].
#'   * `do_request()` is simply
#'     `gargle::response_process(request_make(x, ...))`. It exists only because
#'     we had to make `do_paginated_request()` and it felt weird to not make the
#'     equivalent for a single request.
#'   * `do_paginated_request()` executes the input request **with page
#'     traversal**. It is impossible to separate paginated requests into a "make
#'     request" step and a "process request" step, because the token for the
#'     next page must be extracted from the content of the current page.
#'     Therefore this function does both and returns a list of processed
#'     responses, one per page.
#'
#' @param x List, holding the components for an HTTP request, presumably created
#'   with [request_generate()] Should contain the `method`, `url`, `body`,
#'   and `token`.
#' @param ... Optional arguments passed through to the HTTP method.
#' @template verbose
#'
#' @return `request_make()`: Object of class `response` from [httr].
#' @export
#' @family low-level API functions
request_make <- function(x, ...) {
  gargle::request_retry(x, ..., user_agent = drive_ua())
}

#' @rdname request_make
#' @export
#' @return `do_request()`: List representing the content returned by a single
#'   request.
do_request <- function(x, ...) {
  gargle::response_process(request_make(x, ...))
}

#' @rdname request_make
#' @param n_max Maximum number of items to return. Defaults to `Inf`, i.e. there
#'   is no limit and we keep making requests until we get all items.
#' @param n Function that computes the number of items in one response or page.
#'   The default function always returns `1` and therefore treats each page as
#'   an item. If you know more about the structure of the response, you can
#'   pass another function to count and threshhold, for example, the number of
#'   files or comments.
#' @export
#' @return `do_paginated_request()`: List of lists, representing the returned
#'   content, one component per page.
#' @examples
#' \dontrun{
#' # build a request for an endpoint that is:
#' #   * paginated
#' #   * NOT privileged in googledrive, i.e. not covered by request_generate()
#' # "comments" are a great example
#' # https://developers.google.com/drive/v3/reference/comments
#' #
#' # Practice with a target file with > 2 comments
#' # Note that we request 2 items (comments) per page
#' req <- gargle::request_build(
#'   path = "drive/v3/files/{fileId}/comments",
#'   method = "GET",
#'   params = list(
#'     fileId = "your-file-id-goes-here",
#'     fields = "*",
#'     pageSize = 2
#'   ),
#'   token = googledrive::drive_token()
#' )
#' # make the paginated request, but cap it at 1 page
#' # should get back exactly two comments
#' do_paginated_request(req, n_max = 1)
#' }
do_paginated_request <- function(x,
                                 ...,
                                 n_max = Inf,
                                 n = function(res) 1,
                                 verbose = deprecated()) {
  warn_for_verbose(verbose)

  ## when traversing pages, you can't cleanly separate the task into
  ## request_make() and gargle::response_process(), because you need to process
  ## response / page i to get the pageToken for request / page i + 1
  ## so this function does both
  stopifnot(identical(x$method, "GET"))

  ## if fields does not exist yet, you will need something to prepend
  ## "nextPageToken" to ... "all fields" seems like best (only?) default choice
  x$query$fields <- x$query$fields %||% "*"
  if (!grepl("nextPageToken", x$query$fields)) {
    x$query$fields <- glue("nextPageToken,{x$query$fields}")
  }

  responses <- list()
  i <- 1
  total <- 0
  # TODO: do I have anything to say here at the beginning?
  # what's a non-jargon-y and general way to say:
  # "we're hitting a paginated endpoint and we're working with pageSize n"
  st <- show_status()
  if (st) sb <- cli::cli_status(msg = character())
  repeat {
    page <- request_make(x, ...)
    responses[[i]] <- gargle::response_process(page)
    x$query$pageToken <- responses[[i]]$nextPageToken
    x$url <- httr::modify_url(x$url, query = x$query)
    # TODO: presumably this might need some generalization when this moves
    # to gargle (how we determine how much 'stuff' we've seen and how we
    # talk about it in the status bar)
    # TODO: need to do something re: non-interactive work, e.g. Rmd
    total <- total + n(responses[[i]])
    if (is.null(x$query$pageToken) || total >= n_max) {
      break
    }
    if (st) {
      cli::cli_status_update(
        id = sb,
        "{cli::symbol$arrow_right} Files retrieved so far: {total}"
      )
    }
    i <- i + 1
  }

  responses
}

show_status <- function() {
  is_interactive() && is_false(getOption("googledrive_quiet", FALSE))
}

drive_ua <- function() {
  httr::user_agent(paste0(
    "googledrive/", utils::packageVersion("googledrive"), " ",
    "(GPN:RStudio; )", " ",
    "gargle/", utils::packageVersion("gargle"), " ",
    "httr/", utils::packageVersion("httr")
  ))
}