R Plumber posting a PDF - r

I am trying to access a PDF through an HTTP post request with R Plumber, read it with the tabulizer package, and respond with the PDF in JSON format. I am posting a 53kb PDF through Postman to my route and receiving the error:
Error in normalizePath(path.expand(path), winslash, mustWork).
My R API route code is below:
#' #post /tab
#' #json
function(req){
library("tabulizer")
f <- req$postBody
extract_tables(f)
}
When I use the extract_tables() function with a local path to the PDF that I am using it works perfectly as a get route.
#' #get /tab
#' #json
function(){
library("tabulizer")
f <- "C:/Users/kelse/Desktop/Rscripts/Tessaract/table.pdf"
extract_tables(f)
}
Does anybody know how to post a pdf file through Plumber and access it in a function?

You can try by using # serializer to get posted through HTTP
#* #serializer contentType list(type="application/pdf")
#* #get /pdf
function(){
tmp <- tempfile()
pdf(tmp)
plot(1:10, type="b")
text(4, 8, "PDF from plumber!")
text(6, 2, paste("The time is", Sys.time()))
dev.off()
readBin(tmp, "raw", n=file.info(tmp)$size)
}

Related

How to use Plumber in an API to upload multiple images to a sub directory?

I have sub directory whose location is 'data/images/' and I need my API service to upload images into that sub directory. I am using R and Plumber here. I understand the basic setup, but I can't seem to get my code to deliver my uploaded files into my directory.
This is my attempt:
library(plumber)
library(Rook)
#* Upload file
#* #param req:[file]
#* #post /uploadfile
function(req, res){
names(req)
print(names(req))
fileInfo <- list(formContents = Rook::Multipart$parse(req))
print(fileInfo)
## The file is downloaded in a temporary folder
tmpfile <- fileInfo$formContents$upload$tempfile
## Copy the file to a new folder, with its original name
fn <- file.path(paste0("data/images/",req, sepp=''))
file.copy(tmpfile, fn)
print(fn)
## Send a message with the location of the file
res$body <- paste0("Your file is now stored in ", fn, "\n")
res
}
Any help would be greatly appreciated.
So after a bunch of pulled hairs, this is essentially the code that makes allows you upload pictures to a designated sub directory. This code needs the plumber and Rook packages to work:
library(plumber)
library(Rook)
#* #param req:[file]
#* #post /upload_test27
function(req, res) {
# Required for multiple file uploads
names(req)
# Parses into a Rook multipart file type;needed for API conversions
fileInfo <- list(formContents = Rook::Multipart$parse(req))
# This is where the file name is stored
# print(fileInfo$formContents$req$filename)
file_name <- fileInfo$formContents$req$filename
# The file is downloaded in a temporary folder
tmpfile <- fileInfo$formContents$req$tempfile
# Create a file path
fn <- (paste0("data/images/",file_name, sepp=''))
#Copies the file into the designated folder
file.copy(tmpfile, fn)
res$body <- paste0("Your file is now stored in ", fn, "\n")
res
}

REST APIs with R Plumber - GIS Shapefiles

I am trying to wrap a function into REST API using plumber R Package. As a Input , function takes a shapefile and returns shapefile in terms of .shp format as well as GeoJSON format after the transformation. With the help of decorator now I need to enhance it into a web service.
R File:
#* Spatial Polygon Object
#* #param pathtoshpfile:character Path to Shapefile with Name
#* #param design:character one or two
#* #post /sprayermap
function(pathtoshpfile, design = c("one", "two")) {
# library
require(rgeos)
require(sp)
require(rgdal)
require(raster)
#Importing Shapefile
a_shape <- raster::shapefile(pathtoshpfile)
if (class(a_shape) == "SpatialPolygonsDataFrame") {
if (design == "one") {
a_shape <- tryCatch (
rgeos::gBuffer(a_shape, byid = TRUE, width = 0),
error = function(err) {
return(paste("sprayer map : ", err))
}
)
sprayer_map <- tryCatch (
aggregate(a_shape, "Rx"),
error = function(err) {
return(paste("sprayer map : ", err))
}
)
sprayer_map#data$Rx <- as.integer(sprayer_map#data$Rx)
raster::shapefile(sprayer_map, filename = "field_sprayer_map", overwrite =
TRUE)
rgdal::writeOGR(
sprayer_map,
dsn = "field_sprayer_map.GeoJSON",
layer = "geojson",
driver = "GeoJSON",
overwrite_layer = TRUE
)
return(paste0("SpatialPolygonsDataFrame"))
} else {
return(paste0("design two"))
}
} else {
return(paste0("Please provide spatial polygon object !"))
}
}
Plumber Part :
library(plumber)
# 'plumber.R' is the location of the file shown above
pr("plumber.R") %>%
pr_run(port=8000)
#############################
curl "http://127.0.0.1:8000/sprayermap?pathtoshpfile=/path/to/directory/test.shp&design=one"
Based on my limited understanding on creation of APIs , I created above script and got below error.
Error: unexpected string constant in "curl "http://127.0.0.1:8000/sprayermap?
Though plain R Function script works good
Looking for guidance how the decorators can be used to convert the above function into Restful APIs where Input and output both are shapefiles.
Test Shape File: Test Shape File
I believe your function is setup correctly. The following curl calls should help you out:
curl -X POST "http://127.0.0.1:8000/sprayermap?pathtoshpfile=../Downloads/jnk/test.shp&design=one"
(ie. insert -X POST in your existing call). Alternatively, via --data:
curl --data "pathtoshpfile=../Downloads/jnk/test.shp" "http://127.0.0.1:8000/sprayermap"
On my machine, both queries return
["SpatialPolygonsDataFrame"]
and field_sprayer_map is written to disk. For the record, this works with both the current plumber CRAN version 0.4.6:
p = plumb("../Downloads/jnk/plumber.R")
p$run(port=8000)
And the upcoming v1.0.0 release candidate:
pr("../Downloads/jnk/plumber.R") %>%
pr_run(port=8000)

How to send curl variables to plumber function dynamically?

I want to dynamically call the plumber API based on any number of input variables. I need to map the curl input to the input of a function name. For example if the function has an input hi then, curl -s --data 'hi=2' means that hi=2 should be passed as an input parameter to the function. This can be done directly in R with match.call() but it is failing while calling it through the plumber API.
Take the function
#' #post /API
#' #serializer unboxedJSON
tmp <- function(hi) {
out <- list(hi=hi)
out <- toJSON(out, pretty = TRUE, auto_unbox = TRUE)
return(out)
}
tmp(hi=2)
out: {hi:2}
Then
curl -s --data 'hi=10' http://127.0.0.1/8081/API
out: {\n \"hi\": \"2\"\n}
Everything looks good. However, take the function
#' #post /API
#' #serializer unboxedJSON
tmp <- function(...) {
out <- match.call() %>%
as.list() %>%
.[2:length(.)] # %>%
out <- toJSON(out, pretty = TRUE, auto_unbox = TRUE)
return(out)
}
tmp(hi=2)
out: {hi:2}
Then
curl -s --data 'hi=10' http://127.0.0.1/8081/API
out: {"error":"500 - Internal server error","message":"Error: No method asJSON S3 class: R6\n"}
In practice what I really want to do is load my ML model to predict a score with the plumber API. For example
model <- readRDS('model.rds') # Load model as a global variable
predict_score <- function(...) {
df_in <- match.call() %>%
as.list() %>%
.[2:length(.)] %>%
as.data.frame()
json_out <- list(
score_out = predict(model, df_in) %>%
toJSON(., pretty = T, auto_unbox = T)
return(json_out)
}
This function works as expected when running locally, but running through the API via curl -s --data 'var1=1&var2=2...etc' http://listen_address
I get the following error: {"error":"500 - Internal server error","message":"Error in as.data.frame.default(x[[i]], optional = TRUE): cannot coerce class \"c(\"PlumberResponse\", \"R6\")\" to a data.frame\n"}
Internally plumber match parameters in your request to the name of the parameters in your function. There are special arguments that you could use to explore all args in the request. If you have an argument named req, it will give you an environnement containing the entire request metadata, one of which is req$args. Which you could then parse. The first two args are self reference to special arguments req and res. They are environment and should not be serialized. I would not advise doing what is shown here in any production code as it opens up the api to abuse.
model <- readRDS('model.rds') # Load model as a global variable
#' #post /API
#' #serializer unboxedJSON
predict_score <- function(req) {
df_in <- as.data.frame(req$args[-(1:2)])
json_out <- list(
score_out = predict(model, df_in)
return(json_out)
}
But for your use case, what I would actually advise is having a single parameter named df_in. Here is how you would set that up.
model <- readRDS('model.rds') # Load model as a global variable
#' #post /API
#' #param df_in
#' #serializer unboxedJSON
predict_score <- function(df_in) {
json_out <- list(
score_out = predict(model, df_in)
return(json_out)
}
Then with curl
curl --header "Content-Type: application/json" \
--request POST \
--data '{"df_in":{"hi":2, "othercrap":4}}' \
http://listen_address
When the body of request starts with "{" plumber will parse the content of the body with jsonlite:fromJSON and use the name of the parsed objects to maps to parameters in your function.
Currently both CRAN and master branch on github do not handle this correctly via the swagger api but it will works just fine via curl or other direct calling method. Next plumber version will handle all that and more I believe.
See a similar answer to this of question here : https://github.com/rstudio/plumber/issues/512#issuecomment-605735332

How remove backslashes in response for jsonlite in R?

I have the following code for R Plumber API server
library(jsonlite)
library(data.table)
#' Home endpoint
#' #get /
function(){
df <- data.table(msg = "Welcome")
toJSON(df)
}
It gives me ["[{\"msg\":\"Welcome\"}]"] result on API.
How to replace \" on " symbol to make it more human-friendly when working in a browser or Postman? Expected result is "msg":"Welcome".
Thanks!
plumber already jsonifies it, you are doubling it. Try this:
#' Home endpoint
#' #get /
function(){
df <- data.table::data.table(msg = "Welcome")
return(df)
}
And then in my console, I ran:
pr <- plumber::plumb("~/StackOverflow/4393334/60918243.R")
pr$run()
# Starting server to listen on port 5225
# Running the swagger UI at http://127.0.0.1:5225/__swagger__/
And then on my bash shell:
$ curl -s localhost:5225
[{"msg":"Welcome"}]

Download files from FTP folder using Loop

I am trying to download all the files inside FTP folder
temp <- tempfile()
destination <- "D:/test"
url <- "ftp://XX.XX.net/"
userpwd <- "USER:Password"
filenames <- getURL(url, userpwd = userpwd,ftp.use.epsv = FALSE,dirlistonly = TRUE)
filenames <- strsplit(filenames, "\r*\n")[[1]]
When I am printing "filenames" I am getting all the file names which are inside the FTP folder - correct output till here
[1] "2018-08-28-00.gz" "2018-08-28-01.gz"
[3] "2018-08-28-02.gz" "2018-08-28-03.gz"
[5] "2018-08-28-04.gz" "2018-08-28-05.gz"
[7] "2018-08-28-08.gz" "2018-08-28-09.gz"
[9] "2018-08-28-10.gz" "2018-08-28-11.gz"
[11] "2018-08-28-12.gz" "2018-08-28-13.gz"
[13] "2018-08-28-14.gz" "2018-08-28-15.gz"
[15] "2018-08-28-16.gz" "2018-08-28-17.gz"
[17] "2018-08-28-18.gz" "2018-08-28-23.gz"
for ( i in filenames ) {
download.file(paste0(url,i), paste0(destination,i), mode="w")
}
I got this error
trying URL 'ftp://XXX.net/2018-08-28-00.gz'
Error in download.file(paste0(url, i), paste0(destination, i), mode = "w") :
cannot open URL 'ftp://XXX.net/2018-08-28-00.gz'
In addition: Warning message:
In download.file(paste0(url, i), paste0(destination, i), mode = "w") :
InternetOpenUrl failed: 'The login request was denied'
I modified the code to
for ( i in filenames )
{
#download.file(paste0(url,i), paste0(destination,i), mode="w")
download.file(getURL(paste(url,filenames[i],sep=""), userpwd =
"USER:PASSWORD"), paste0(destination,i), mode="w")
}
After that, I got this error
Error in function (type, msg, asError = TRUE) : RETR response: 550
Without a minimal, complete, and verifiable example it is a challenge to directly replicate your problem. Assuming the file names don't include the URL, you'll need to combine them to access the files.
download.file() requires a file to be read, an output file, as well as additional flags regarding whether you want a binary download or not.
For example, I have data from Alberto Barradas' Pokémon Stats kaggle.com data set stored on my Github site. To download some of the files to the test subdirectory of my R Working Directory, I can use the following code:
filenames <- c("gen01.csv","gen02.csv","gen03.csv")
fileLocation <- "https://raw.githubusercontent.com/lgreski/pokemonData/master/"
# use ./ for subdirectory of current directory, end with / to work with paste0()
destination <- "./test/"
# note that these are character files, so use mode="w"
for (i in filenames){
download.file(paste0(fileLocation,i),
paste0(destination,i),
mode="w")
}
...and the output:
The paste0() function concatenates text without spaces, which allows the code to generate a fully qualified path name for the url of each source file, as well as the subdirectory where the destination file will be stored.
To illustrate what's happening with paste0() in the for() loop, we can use message() to print to the R console.
> # illustrate what paste0() does
> for (i in filenames){
+ message(paste("Source is: ",paste0(fileLocation,i)))
+ message(paste("Destination is:",paste0(destination,i)))
+ }
Source is: https://raw.githubusercontent.com/lgreski/pokemonData/master/gen01.csv
Destination is: ./test/gen01.csv
Source is: https://raw.githubusercontent.com/lgreski/pokemonData/master/gen02.csv
Destination is: ./test/gen02.csv
Source is: https://raw.githubusercontent.com/lgreski/pokemonData/master/gen03.csv
Destination is: ./test/gen03.csv
>

Resources