[How to] Build an API wrapper package in 10 minutes.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Well… documentation not included (of course).
API are cool. They allow to retrieve data from the web, and if ever you’re familiar with {httr}, {jsonlite} and packages like these, you’re able to build requests and retrieve data in a matter of minutes.
But no worry, if you’re not familiar with http requests and web formats like html, JSON and such, you can still go and look for a package wrapper around that specific API: someone had probably been coding it already.
And, if you want to be that someone that code an API wrapper package, here’s a short tutorial that will allow you to create it.
Disclaimer: the 10 minutes workflow does not (of course) include the package documentation.
Find the API
Well, this first step can take a while… but, let’s say we have already found it, and want to build an package around the french database for addresses: https://adresse.data.gouv.fr/api/.
Step 0: be sure you have all the packages you’ll need
Run this if you want to be sure you have all the packages we’ll use here:
install.packages("devtools") install.packages("roxygen2") install.packages("usethis") install.packages("curl") install.packages("httr") install.packages("jsonlite") install.packages("attempt") install.packages("purrr") devtools::install_github("r-lib/desc")
Step 1: the project
Create a new project with RStudio, and click on “Create a package with devtools”.
Step 2: devstuffs
-
In your terminal, run
usethis::use_data_raw()
. -
Open a new R Script, save it into
/data_raw
as “devstuffs”, or any other name. Copy and paste into this script:
library(devtools) library(usethis) library(desc) # Remove default DESC unlink("DESCRIPTION") # Create and clean desc my_desc <- description$new("!new") # Set your package name my_desc$set("Package", "yourpackage") #Set your name my_desc$set("Authors@R", "person('Colin', 'Fay', email = '[email protected]', role = c('cre', 'aut'))") # Remove some author fields my_desc$del("Maintainer") # Set the version my_desc$set_version("0.0.0.9000") # The title of your package my_desc$set(Title = "My Supper API Wrapper") # The description of your package my_desc$set(Description = "A long description of this super package I'm working on.") # The urls my_desc$set("URL", "http://this") my_desc$set("BugReports", "http://that") # Save everyting my_desc$write(file = "DESCRIPTION") # If you want to use the MIT licence, code of conduct, and lifecycle badge use_mit_license(name = "Colin FAY") use_code_of_conduct() use_lifecycle_badge("Experimental") use_news_md() # Get the dependencies use_package("httr") use_package("jsonlite") use_package("curl") use_package("attempt") use_package("purrr") # Clean your description use_tidy_description()
Then, run everything from this script.
Copy and paste at the top of you README.Rmd :
[![lifecycle](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://www.tidyverse.org/lifecycle/#experimental)
And at the bottom :
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
Step 3: get the API url
There are two ways you can request on an API :
-
By building url:
url.and/path/to/the/data
-
With parameters:
url.that/q=this&data=that
This is the case for https://adresse.data.gouv.fr/api/. We’ve got the
base url: http 'https://api-adresse.data.gouv.fr/search/?q=8 bd du
port'
, and search parameters.
Here, the base url is everything before the ?
, and the parameters are
the key-value pairs after the
.
Step 4: utils
Before creating the main calling functions, we’ll create some utilitary functions that will run some tests: is the internet connexion running? Does the {httr} result return the right http code?
For this, we’ll create a file called utils.R
, save it in the R/
folder, and put into it:
#' @importFrom attempt stop_if_not #' @importFrom curl has_internet check_internet <- function(){ stop_if_not(.x = has_internet(), msg = "Please check your internet connexion") } #' @importFrom httr status_code check_status <- function(res){ stop_if_not(.x = status_code(res), .p = ~ .x == 200, msg = "The API returned an error") }
In this same file we’ll also create two objects that will contain the base API urls:
base_url <- "https://api-adresse.data.gouv.fr/search/" reverse_url <- "https://api-adresse.data.gouv.fr/reverse/"
Step 5 : the function that will call on the API
To call the API, we’ll use GET
function from {httr}. Let’s first try
just this:
httr::GET(url = base_url, query = list(q = "Yeaye")) ## Response [https://api-adresse.data.gouv.fr/search/?q=Yeaye] ## Date: 2018-02-04 21:40 ## Status: 200 ## Content-Type: application/json; charset=utf-8 ## Size: 574 B
As you can see, the status is 200. Which is a good sign: no error from the API.
In the API we have chosen, there are 4 entry points: search, reverse, and their counterparts with csv. These counterparts work by POSTing a csv to the API, so let’s forget them for now.
The search entrypoint can take 8 parameters:
q
: text searchlimit
: maximum number of results to returnautocomplete
: autocompletionlat
: latitudelon
: longitudetype
: type of elements to return (housenumber, street,place, municipality)postcode
: Postal codecitycode
: INSEE code
These are the elements which will be passed as a list in the query
parameter from httr::GET
.
Note: if you’re going for an url based request (
url.and/path/to/the/data
orurl.and/?path=this&to=that
), you don’t need to set the query parameter, you can simply paste the url with the elements (url <- paste0("url.and/?path=", this, "&to=", that)
).
Create a new R script and write the functions used to call the API:
#' Search the BAN #' #' @param q text search #' @param limit maximum number of results to return #' @param autocomplete autocompletion #' @param lat latitude #' @param lon longitude #' @param type type of elements to return (housenumber, street,place, municipality) #' @param postcode Postal code #' @param citycode INSEE code #' #' @importFrom attempt stop_if_all #' @importFrom purrr compact #' @importFrom jsonlite fromJSON #' @importFrom httr GET #' @export #' @rdname searchban #' #' @return the results from the search #' @examples #' \dontrun{ #' search_ban("Rennes") #' reverse_search_ban("48.11", "-1.68") #' } search_ban <- function(q = NULL, limit = NULL, autocomplete = NULL, lat = NULL, lon = NULL, type = NULL, postcode = NULL, citycode = NULL){ args <- list(q = q, limit = limit, autocomplete = autocomplete, lat = lat, lon = lon, type = type, postcode = postcode, citycode = citycode) # Check that at least one argument is not null stop_if_all(args, is.null, "You need to specify at least one argument") # Chek for internet check_internet() # Create the res <- GET(base_url, query = compact(args)) # Check the result check_status(res) # Get the content and return it as a data.frame fromJSON(rawToChar(res$content))$features } #' @export #' @rdname searchban reverse_search_ban <- function(lat = NULL, lon = NULL){ args <- list(lat = lat, lon = lon) # Check that at least one argument is not null stop_if_all(args, is.null, "You need to specify at least one argument") # Chek for internet check_internet() # Create the res <- GET(reverse_url, query = compact(args)) # Check the result check_status(res) # Get the content and return it as a data.frame fromJSON(rawToChar(res$content))$features }
Note: you’ll need to change the arguments and documentation for your specific API (obviously).
If ever you want to a part of this roxygen filling programmatically, you should check the excellent {sinew} package by Jonathan Sidi.
Step 6 : Roxygenise
Now, run in your console :
roxygen2::roxygenise()
And that’s it! You’ve got a working package 🙂
Step 7 : build your package
You can test that everything is ok with:
devtools::check() # Then build it with: devtools::build()
What’s left
As I said, the 10 minutes workflow does not include the doc, so here’s what left for you to do:
- Write the Readme
- Write a Vignette
- Write tests
For that, go back to your dev file, add, and run:
use_testthat() use_vignette("{your-package-name}") use_readme_rmd()
And now, grab you best pen and write documentation 😉
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.