3 Graceful HTTP R packages
Based on the previous chapter, your package interacting with a web resource has a dependency on curl, httr, httr2 or crul. You have hopefully read the docs of the dependency you chose, including, in the case of httr, httr2 and crul, the vignette about best practices for HTTP packages. Now, in this chapter we want to give more tips aimed at making your HTTP R package graceful, part of which you’ll learn more about in this very book!
Why write a graceful HTTP R package? First of all, graceful is a nice adjective. 💃🕺Second, graceful is the adjective used in CRAN repository policy “Packages which use Internet resources should fail gracefully with an informative message if the resource is not available or has changed (and not give a check warning nor error).” Therefore, let’s review how to make your R package graceful from this day forward, in success and in failure.
3.1 Choose the HTTP resource wisely
First of all, your life and the life of your package’s users will be easier if the web service you’re wrapping is well maintained and well documented. When you have a choice, try not to rely on a fragile web service. Moreover, if you can, try to communicate with the API providers (telling them about your package; reporting feature requests and bug reports in their preferred way).
3.2 User-facing grace (how your package actually works)
If you can, do not request the API every time the user asks for something; cache data instead. No API call, no API call failure! 😉 See the R-hub blog post “Caching the results of functions of your R package”. To remember answers across sessions, see approaches presented in the R-hub blog post “Persistent config and data for R packages”. Caching behavior should be well documented for users, and there should probably be an expiration time for caches that’s based on how often data is updated on the remote service.
Try to send correct requests by knowing what the API expects and validating user inputs; at the correct rate.
- For instance, don’t even try interacting with a web API requiring authentication if the user does not provide authentication information.
- For limiting rate (not sending too many requests), automatically wait. If the API docs allow you to define an ideal or maximal rate, set the request rate in advance using the ratelimitr package (or, with httr2,
httr2::req_throttle()
).
If there’s a status API (a separate API indicating whether the web resource is up or down), use it. If it tells you the API is down,
stop()
(orrlang::abort()
) with an informative error message.-
If the API indicates an error, depending on the actual error,
If the server seems to be having issues, re-try with an exponential back-off. In httr2 there is
httr2::req_retry()
.Otherwise, transform the error into a useful error.
If you used retry and nothing was sent after the maximal number of retries, show an informative error message.
That was it for aspects the user will care about. Now, what might be more problematic for your package’s fate on CRAN are the automatic checks that happen there at submission and then regularly.
3.3 Graceful vignettes and examples
-
Pre-compute vignettes in some way. Don’t use them as tests; they are a showcase. Of course have a system to prevent them from going stale, maybe even simple reminders (potentially in the unexported
release_questions()
function). Don’t let vignettes run on a system where a failure has bad consequences. - Don’t run examples on CRAN. Now, for a first submission, CRAN maintainers might complain if there is no example. In that case, you might want to add some minimal example, e.g.
if (crul::ok("some-url")) {
my_fun() # some eg that uses some-url
}
These two precautions ensure that CRAN checks won’t end with some WARNINGs, e.g. because an example failed when the API was down.
3.4 Graceful code
For simplifying your own life and those of contributors, make sure to re-use code in your package by e.g. defining helper functions for making requests, handling responses etc. It will make it easier for you to support interactions with more parts of the web API. Writing DRY (don’t repeat yourself) code means less lines of code to test, and less API calls to make or fake!
Also, were you to export a function à la gh::gh()
, you’ll help users call any endpoint of the web API even if you haven’t written any high-level helper for it yet.
3.5 Graceful tests
We’re getting closer to the actual topic of this book!
- Read the rest of this book! Your tests should ideally run without needing an actual internet connection nor the API being up. Your tests that do need to interact with the API should be skipped on CRAN.
testthat::skip_on_cran()
(orskip_if_offline()
that skips if the test is run offline or on CRAN) will ensure that. - Do not only test “success” behavior! Test for the behavior of your package in case of API errors, which shall also be covered later in the book.
3.6 Conclusion
In summary, to have a graceful HTTP package, make the most of current best practice for the user interface; escape examples and vignettes on CRAN; make tests independent of actual HTTP requests. Do not forget CRAN’s “graceful failure” policy is mostly about ensuring a clean R CMD check result on CRAN platforms (0 ERROR, 0 WARNING, 0 NOTE) even when the web service you’re wrapping has some hiccups.