Title: | A Framework for Web API Packages |
---|---|
Description: | An opinionated framework for use within api-wrapping R packages. |
Authors: | Jon Harmon [aut, cre, cph] |
Maintainer: | Jon Harmon <[email protected]> |
License: | MIT + file LICENSE |
Version: | 0.0.0.9005 |
Built: | 2025-01-31 22:29:43 UTC |
Source: | https://github.com/jonthegeek/nectar |
This function implements an opinionated framework for making API calls. It
is intended to be used inside an API client package. It serves as a wrapper
around the req_
family of functions, such as httr2::request()
, as well
as httr2::req_perform()
and httr2::req_perform_iterative()
, and, by
default, httr2::resp_body_json()
.
call_api( base_url, ..., path = NULL, query = NULL, body = NULL, mime_type = NULL, method = NULL, auth_fn = NULL, auth_args = list(), response_parser = resp_tidy, response_parser_args = list(), next_req_fn = NULL, max_reqs = Inf, max_tries_per_req = 3, additional_user_agent = NULL )
call_api( base_url, ..., path = NULL, query = NULL, body = NULL, mime_type = NULL, method = NULL, auth_fn = NULL, auth_args = list(), response_parser = resp_tidy, response_parser_args = list(), next_req_fn = NULL, max_reqs = Inf, max_tries_per_req = 3, additional_user_agent = NULL )
base_url |
( |
... |
These dots are for future extensions and must be empty. |
path |
( |
query |
( |
body |
(multiple types) An object to use as the body of the request. If
any component of the body is a path, pass it through |
mime_type |
( |
method |
( |
auth_fn |
( |
auth_args |
( |
response_parser |
( |
response_parser_args |
( |
next_req_fn |
( |
max_reqs |
( |
max_tries_per_req |
( |
additional_user_agent |
( |
The response from the API, parsed by the response_parser
.
req_prepare()
, req_perform_opinionated()
, and resp_parse()
for
finer control of the process.
If a request has a pagination policy defined by req_pagination_policy()
,
extract the pagination_fn
from that policy. Otherwise return NULL
.
choose_pagination_fn(req)
choose_pagination_fn(req)
req |
( |
The pagination function, or NULL
.
req <- httr2::request("https://example.com") req <- req_pagination_policy(req, httr2::iterate_with_offset("page")) choose_pagination_fn(req)
req <- httr2::request("https://example.com") req <- req_pagination_policy(req, httr2::iterate_with_offset("page")) choose_pagination_fn(req)
Discard empty elements in nested lists.
compact_nested_list(lst)
compact_nested_list(lst)
lst |
A (nested) list to filter. |
The list, minus empty elements and branches.
x <- list( a = list( b = letters, c = NULL, d = 1:5 ), e = NULL, f = 1:3 ) compact_nested_list(x)
x <- list( a = list( b = letters, c = NULL, d = 1:5 ), e = NULL, f = 1:3 ) compact_nested_list(x)
When constructing API calls programmatically, you may encounter situations where an upstream task should indicate which function to apply. For example, one endpoint might use a special auth function that isn't used by other endpoints. This function exists to make coding such situations easier.
do_if_fn_defined(x, fn = NULL, ..., call = rlang::caller_env())
do_if_fn_defined(x, fn = NULL, ..., call = rlang::caller_env())
x |
An object to potentially modify, such as a |
fn |
A function to apply to |
... |
Additional arguments to pass to |
call |
( |
The object, potentially modified.
build_api_req <- function(endpoint, auth_fn = NULL, ...) { req <- httr2::request("https://example.com") req <- httr2::req_url_path_append(req, endpoint) do_if_fn_defined(req, auth_fn, ...) } # Most endpoints of this API do not require authentication. unsecure_req <- build_api_req("unsecure_endpoint") unsecure_req$headers # But one endpoint requires authentication. secure_req <- build_api_req( "secure_endpoint", httr2::req_auth_bearer_token, "secret-token" ) secure_req$headers$Authorization
build_api_req <- function(endpoint, auth_fn = NULL, ...) { req <- httr2::request("https://example.com") req <- httr2::req_url_path_append(req, endpoint) do_if_fn_defined(req, auth_fn, ...) } # Most endpoints of this API do not require authentication. unsecure_req <- build_api_req("unsecure_endpoint") unsecure_req$headers # But one endpoint requires authentication. secure_req <- build_api_req( "secure_endpoint", httr2::req_auth_bearer_token, "secret-token" ) secure_req$headers$Authorization
This function is intended as a replacement for httr2::iterate_with_cursor()
for the common situation where the response body is json, and the cursor can
be "empty" in various ways. Even within a single API, some endpoints might
return a NULL
next_cursor
to indicate that there are no more pages of
results, while other endpoints might return ""
. This function normalizes
all such results to NULL
.
iterate_with_json_cursor(param_name = "cursor", next_cursor_path)
iterate_with_json_cursor(param_name = "cursor", next_cursor_path)
param_name |
( |
next_cursor_path |
( |
A function that takes the response and the previous request, and returns the next request if there are more results.
Many APIs provide API keys that can be used to authenticate requests (or, often, provide other information about the user). This function helps to apply those keys to requests.
req_auth_api_key( req, parameter_name, ..., api_key = NULL, location = c("header", "query", "cookie"), call = rlang::caller_env() )
req_auth_api_key( req, parameter_name, ..., api_key = NULL, location = c("header", "query", "cookie"), call = rlang::caller_env() )
req |
( |
parameter_name |
( |
... |
These dots are for future extensions and must be empty. |
api_key |
( |
location |
( |
call |
( |
A httr2::request()
object with additional class nectar_request
.
For a given API, the base_url
and user agent will generally be the same for
every call to that API. Use this function to prepare that piece of the
request once for easy reuse.
req_init( base_url, ..., additional_user_agent = NULL, call = rlang::caller_env() )
req_init( base_url, ..., additional_user_agent = NULL, call = rlang::caller_env() )
base_url |
( |
... |
These dots are for future extensions and must be empty. |
additional_user_agent |
( |
call |
( |
A httr2::request()
object with additional class nectar_request
.
Other opinionated request functions:
req_modify()
,
req_pagination_policy()
,
req_prepare()
,
req_tidy_policy()
req_init("https://example.com") req_init( "https://example.com", additional_user_agent = "my_api_client (https://my.api.client)" )
req_init("https://example.com") req_init( "https://example.com", additional_user_agent = "my_api_client (https://my.api.client)" )
Modify the basic request for an API by adding a path and any other path-specific properties.
req_modify( req, ..., path = NULL, query = NULL, body = NULL, mime_type = NULL, method = NULL, call = rlang::caller_env() )
req_modify( req, ..., path = NULL, query = NULL, body = NULL, mime_type = NULL, method = NULL, call = rlang::caller_env() )
req |
( |
... |
These dots are for future extensions and must be empty. |
path |
( |
query |
( |
body |
(multiple types) An object to use as the body of the request. If
any component of the body is a path, pass it through |
mime_type |
( |
method |
( |
call |
( |
A httr2::request()
object with additional class nectar_request
.
Other opinionated request functions:
req_init()
,
req_pagination_policy()
,
req_prepare()
,
req_tidy_policy()
req_base <- req_init("https://example.com") req_modify(req_base, path = c("specific/{path}", path = "endpoint")) req_modify(req_base, query = c("param1" = "value1", "param2" = "value2"))
req_base <- req_init("https://example.com") req_modify(req_base, path = c("specific/{path}", path = "endpoint")) req_modify(req_base, query = c("param1" = "value1", "param2" = "value2"))
APIs generally have a specified method for requesting multiple pages of
results (or sometimes two or three methods). The methods are sometimes
documented within a given endpoint, and sometimes documented at the "top" of
the documentation. Use this function to attach a pagination policy to a
request, so that req_perform_opinionated()
can automatically handle
pagination.
req_pagination_policy(req, pagination_fn, call = rlang::caller_env())
req_pagination_policy(req, pagination_fn, call = rlang::caller_env())
req |
( |
pagination_fn |
( |
call |
( |
A httr2::request()
object with additional class nectar_request
.
Other opinionated request functions:
req_init()
,
req_modify()
,
req_prepare()
,
req_tidy_policy()
req <- httr2::request("https://example.com") req_pagination_policy(req, httr2::iterate_with_offset("page"))
req <- httr2::request("https://example.com") req_pagination_policy(req, httr2::iterate_with_offset("page"))
This function ensures that a request has httr2::req_retry()
applied, and
then performs the request, using either httr2::req_perform_iterative()
(if
a next_req_fn
function is supplied) or httr2::req_perform()
(if not).
req_perform_opinionated( req, ..., next_req_fn = choose_pagination_fn(req), max_reqs = 2, max_tries_per_req = 3 )
req_perform_opinionated( req, ..., next_req_fn = choose_pagination_fn(req), max_reqs = 2, max_tries_per_req = 3 )
req |
The first request to perform. |
... |
These dots are for future extensions and must be empty. |
next_req_fn |
( |
max_reqs |
( |
max_tries_per_req |
( |
A list of httr2::response()
objects, one for each request
performed. The list has additional class nectar_responses
.
Add information about nectar and the calling package (if called from a package) to the user agent string.
req_pkg_user_agent( req, pkg_name = get_pkg_name(call), pkg_url = NULL, call = rlang::caller_env() )
req_pkg_user_agent( req, pkg_name = get_pkg_name(call), pkg_url = NULL, call = rlang::caller_env() )
req |
( |
pkg_name |
( |
pkg_url |
( |
call |
( |
A httr2::request()
object with additional class nectar_request
.
req <- httr2::request("https://example.com") req$options$useragent req_pkg_user_agent(req)$options$useragent req_pkg_user_agent(req, "stbl")$options$useragent
req <- httr2::request("https://example.com") req$options$useragent req_pkg_user_agent(req)$options$useragent req_pkg_user_agent(req, "stbl")$options$useragent
This function implements an opinionated framework for preparing an API
request. It is intended to be used inside an API client package. It serves as
a wrapper around the req_
family of functions, such as httr2::request()
.
req_prepare( base_url, ..., path = NULL, query = NULL, body = NULL, mime_type = NULL, method = NULL, additional_user_agent = NULL, auth_fn = NULL, auth_args = list(), tidy_fn = NULL, tidy_args = list(), pagination_fn = NULL, call = rlang::caller_env() )
req_prepare( base_url, ..., path = NULL, query = NULL, body = NULL, mime_type = NULL, method = NULL, additional_user_agent = NULL, auth_fn = NULL, auth_args = list(), tidy_fn = NULL, tidy_args = list(), pagination_fn = NULL, call = rlang::caller_env() )
base_url |
( |
... |
These dots are for future extensions and must be empty. |
path |
( |
query |
( |
body |
(multiple types) An object to use as the body of the request. If
any component of the body is a path, pass it through |
mime_type |
( |
method |
( |
additional_user_agent |
( |
auth_fn |
( |
auth_args |
( |
tidy_fn |
( |
tidy_args |
( |
pagination_fn |
( |
call |
( |
A httr2::request()
object with additional class nectar_request
.
Other opinionated request functions:
req_init()
,
req_modify()
,
req_pagination_policy()
,
req_tidy_policy()
API responses generally follow a structured format. Use this function to
define a policy that will be used by resp_tidy()
to extract the relevant
portion of a response and wrangle it into a desired format.
req_tidy_policy( req, tidy_fn = resp_body_auto, tidy_args = list(), call = rlang::caller_env() )
req_tidy_policy( req, tidy_fn = resp_body_auto, tidy_args = list(), call = rlang::caller_env() )
req |
( |
tidy_fn |
( |
tidy_args |
( |
call |
( |
A httr2::request()
object with additional class nectar_request
.
Other opinionated request functions:
req_init()
,
req_modify()
,
req_pagination_policy()
,
req_prepare()
req <- httr2::request("https://example.com") req_tidy_policy(req, httr2::resp_body_json, list(simplifyVector = TRUE))
req <- httr2::request("https://example.com") req_tidy_policy(req, httr2::resp_body_json, list(simplifyVector = TRUE))
Use the Content-Type
header (extracted using httr2::resp_content_type()
)
of a response to automatically choose and apply a body parser, such as
httr2::resp_body_json()
or resp_body_csv()
.
resp_body_auto(resp)
resp_body_auto(resp)
resp |
( |
The parsed response body.
Extract tabular data in comma-separated or tab-separated format from a response body.
resp_body_csv(resp, check_type = TRUE) resp_body_tsv(resp, check_type = TRUE)
resp_body_csv(resp, check_type = TRUE) resp_body_tsv(resp, check_type = TRUE)
resp |
( |
check_type |
( |
The parsed response body as a data frame.
Extract response body into list
resp_body_separate(resp, resp_body_fn = resp_body_auto)
resp_body_separate(resp, resp_body_fn = resp_body_auto)
resp |
( |
resp_body_fn |
( |
The parsed response body wrapped in a list()
. This is useful for
things like raw vectors that you wish to parse with httr2::resps_data()
.
If you have implemented the full nectar
framework, use resp_tidy()
directly to parse your responses. We may continue to support
resp_parse()
, but it is most useful as a bridge to the full framework.
httr2
provides two methods for performing requests:
httr2::req_perform()
, which returns a single httr2::response()
object,
and httr2::req_perform_iterative()
, which returns a list of
httr2::response()
objects. This function automatically determines whether
a single response or multiple responses have been returned, and parses the
responses appropriately.
resp_parse(resps, ...) ## Default S3 method: resp_parse( resps, ..., arg = rlang::caller_arg(resps), call = rlang::caller_env() ) ## S3 method for class 'httr2_response' resp_parse(resps, ..., response_parser = resp_tidy)
resp_parse(resps, ...) ## Default S3 method: resp_parse( resps, ..., arg = rlang::caller_arg(resps), call = rlang::caller_env() ) ## S3 method for class 'httr2_response' resp_parse(resps, ..., response_parser = resp_tidy)
resps |
( |
... |
Additional arguments passed on to the |
arg |
( |
call |
( |
response_parser |
( |
The response parsed by the response_parser
. If resps
was a list,
the parsed responses are concatenated when possible. Unlike
httr2::resps_data, this function does not concatenate raw vector
responses.
API responses generally follow a structured format. Use this function to
extract the relevant portion of a response, and wrangle it into a desired
format. This function is most useful when the response was fetched with a
request that includes a tidying policy defined via req_tidy_policy()
.
resp_tidy(resps)
resp_tidy(resps)
resps |
( |
The extracted and cleaned response, or, for a list of responses,
those responses cleaned then concatenated via httr2::resps_data()
. By
default, the response is processed with resp_body_auto()
.
resp_tidy_json()
for an opinionated response parser for JSON
responses, resp_body_auto()
(etc) for a family of response parsers that
attempts to automatically select the appropriate parser based on the
response content type, httr2::resp_body_raw()
(etc) for the underlying
httr2 response parsers, and resp_parse()
for an alternative approach to
dealing with responses (particularly useful if the request does not include
a resp_tidy
policy).
Parse the body of a response with httr2::resp_body_json()
, extract a named
subset of that body, and tidy the result with tibblify::tibblify()
.
resp_tidy_json(resp, spec = NULL, unspecified = "list", subset_path = NULL)
resp_tidy_json(resp, spec = NULL, unspecified = "list", subset_path = NULL)
resp |
( |
spec |
( |
unspecified |
( |
subset_path |
( |
The tibblified response body.
If you have not defined a parser for a response type, use this function to return useful information to help construct a parser.
resp_tidy_unknown(resp, call = rlang::caller_env())
resp_tidy_unknown(resp, call = rlang::caller_env())
resp |
( |
call |
( |
This function always throws an error. The error lists the names of
the response pieces after parsing with resp_body_auto()
.
Calls to APIs often require a string argument. This function ensures that
those arguments are length-1, non-NA
character vectors, or length-1,
non-NA
vectors that can be coerced to character vectors. This is intended
to ensure that calls to the API will not fail with predictable errors, thus
avoiding unnecessary internet traffic.
stabilize_string( x, ..., regex = NULL, arg = rlang::caller_arg(x), call = rlang::caller_env() )
stabilize_string( x, ..., regex = NULL, arg = rlang::caller_arg(x), call = rlang::caller_env() )
x |
The argument to stabilize. |
... |
Arguments passed on to
|
regex |
Character scalar. An optional regex pattern to compare the
value(s) of |
arg |
An argument name as a string. This argument will be mentioned in error messages as the input that is at the origin of a problem. |
call |
The execution environment of a currently
running function, e.g. |
x
coerced to a length-1 character vector, if possible.
stabilize_string("a") stabilize_string(1.1) x <- letters try(stabilize_string(x)) x <- NULL try(stabilize_string(x)) x <- character() try(stabilize_string(x)) x <- NA try(stabilize_string(x))
stabilize_string("a") stabilize_string(1.1) x <- letters try(stabilize_string(x)) x <- NULL try(stabilize_string(x)) x <- character() try(stabilize_string(x)) x <- NA try(stabilize_string(x))
This function normalizes a URL by adding a trailing slash to the base if it is missing. It is mainly for testing and other comparisons.
url_normalize(url)
url_normalize(url)
url |
A URL to normalize. |
A normalized URL
identical( url_normalize("https://example.com"), url_normalize("https://example.com/") ) identical( url_normalize("https://example.com?param=value"), url_normalize("https://example.com/?param=value") )
identical( url_normalize("https://example.com"), url_normalize("https://example.com/") ) identical( url_normalize("https://example.com?param=value"), url_normalize("https://example.com/?param=value") )
Append zero or more path elements to a URL without duplicating "/"
characters. Based on httr2::req_url_path_append()
.
url_path_append(url, ...)
url_path_append(url, ...)
url |
A URL to modify. |
... |
Path elements to append, as strings. |
A modified URL.
url_path_append("https://example.com", "api", "v1", "users") url_path_append("https://example.com/", "/api", "/v1", "/users") url_path_append("https://example.com/", "/api/v1/users")
url_path_append("https://example.com", "api", "v1", "users") url_path_append("https://example.com/", "/api", "/v1", "/users") url_path_append("https://example.com/", "/api/v1/users")