I am trying to use ellipsis with plumber as input json may vary
#' #post /predict
calculate_prediction <- function(...){
arguments =list(...)
print(arguments)
return(arguments)
This throws me error below :
<simpleError: No method asJSON S3 class: R6>
How to resolve this issue
The issue with using ... in your function is that the first two arguments are the request body and the response body that are passed in the API call. If you add those in first, you can capture all the remaining unnamed args.
#' #post /predict
calculate_prediction <- function(req, res, ...){
arguments = list(...)
print(arguments)
return(arguments)
}
Related
I have followed the Rstudio example here for logging for logging requests in Plumber (R API package) and would like to add other variables to the log. However, the registerHooks statement does not recognise global variables (<<-).
# Enable CORS Filtering
#' #filter auth_filter
auth_filter <- function(req, res) {
req_user <<- req$HEADERS['authorization'] %>% as.character()
req_tenant <<- req$HTTP_TENANT
}
pr$registerHooks(
list(
preroute = function() {
# Start timer for log info
tictoc::tic()
},
postroute = function(req, res) {
end <- tictoc::toc(quiet = TRUE)
# Log details about the request and the response
log_info('{convert_empty(req_user)} {convert_empty(req_tenant)} {convert_empty(req$REMOTE_ADDR)} "{convert_empty(req$HTTP_USER_AGENT)}" {convert_empty(req$HTTP_HOST)} {convert_empty(req$REQUEST_METHOD)} {convert_empty(req$PATH_INFO)} {convert_empty(res$status)} {round(end$toc - end$tic, digits = getOption("digits", 5))}')
}))
In the above example, req_user and req_tenant change for every request. The above example gives an error message, stating that req_user and req_tenant do not exist. I have also tried preserialize as an alternative to postroute. How can these variables be logged? The don't need to be global, this was just an additional attempt to solve the problem.
I believe the error is produced because of the convert_empty function not being able to handle null's or na's.
I adjusted this function to handle null's or na's in case they do occur:
convert_empty <- function(string) {
if (is.null(string) || is.na(string) || string == "") {
"-"
} else {
string
}
}
Using the sample plumber.R code that's produced in RStudio, this should work:
#Plumber.R file
library(plumber)
library(logger)
# Specify how logs are written
log_dir <- "logs"
if (!fs::dir_exists(log_dir)) fs::dir_create(log_dir)
log_appender(appender_tee(tempfile("plumber_", log_dir, ".log")))
#* Return the sum of two numbers
#* #param a The first number to add
#* #param b The second number to add
#* #post /sum
function(a, b) {
as.numeric(a) + as.numeric(b)
}
Then we register the hooks as follows:
library(plumber)
pr <- plumb("plumber.R")
convert_empty <- function(string) {
if (is.null(string) || is.na(string) || string == "") {
"-"
} else {
string
}
}
pr$registerHooks(
list(
preroute = function() {
# Start timer for log info
tictoc::tic()
},
postroute = function(req, res) {
end <- tictoc::toc(quiet = TRUE)
# Log details about the request and the response
log_info('{convert_empty(as.character(req$HEADERS["authorization"]))} {convert_empty(req$HTTP_TENANT)} {convert_empty(req$REMOTE_ADDR)} {convert_empty(req$HTTP_USER_AGENT)} {convert_empty(req$HTTP_HOST)} {convert_empty(req$REQUEST_METHOD)} {convert_empty(req$PATH_INFO)} {convert_empty(res$status)} {round(end$toc - end$tic, digits = getOption("digits", 5))}')
}
)
)
pr
In the console, you can do:
pr$run()
so serve your API locally.
From there, go to the terminal in RStudio and do a curl. Assuming is your port, an example would be:
curl -H "Authorization: Bearer my_token" -H "TENANT: 123" -X POST "http://127.0.0.1:9520/sum?a=1&b=2"
You should see a return of 3 in the terminal, and if you look at the R console, you will see the Authorization and tenant headers logged
So it turns out my specific problem was actually the format of tenant name, e.g. req$HTTP_TENANT = "Company A/S". When this is passed by log_info, it gets split into two columns, which is more than the logging table expects. Maybe the forward slash is escaping something.
For some reason, you have to put it in quotes in log_info, so log_info('"{convert_empty(req$HTTP_TENANT)}"') instead of log_info('{convert_empty(req$HTTP_TENANT)}') as with req$HTTP_USER_AGENT in the original post.
How do I define asynchronous API endpoints in plumber?
I didn't really find plumber-specific documentation on the topic except this example and this GitHub issue
When I try to reproduce the example, I get an error that R doesn't know how to turn a promise into JSON (at least that's what I think the problem is):
<simpleError: No method asJSON S3 class: promise>
Example
library(promises)
sleep_count <- 5
# add 5 seconds of sleep time
add_async_sleep <- function(p) {
n <- 20
for (i in 1:(sleep_count * n)) {
p <- then(p, function(value) {
Sys.sleep(1/n)
"" # return value
})
}
p
}
# use name_ as a placeholder for name when there are extra args
time <- function(name_, name = name_) {
paste0(name, ": ", Sys.time())
}
new_promise <- function() {
promise(function(resolve, reject){ resolve(NULL) })
}
#' #get /async
function() {
new_promise() %>%
add_async_sleep() %...>%
time("async")
}
Say this code lives in file plumber.R, then you should be able to start the API server and bring up Swagger with
r <- plumber::plumb(here::here("plumber.R"))
r$run()
Once I try out the endpoint /async, my R console reports
Starting server to listen on port 7361
Running the swagger UI at http://127.0.0.1:7361/__swagger__/
<simpleError: No method asJSON S3 class: promise>
and Swagger looks like this:
Disclaimer
I'm new to future and promises and only made it mid-way through the docs on https://rstudio.github.io/promises/ yet.
I am trying to pass header "X-Name" from postman then access it from API which is build using Plumber R.
However, I am not able to access it. using following code,
req$HTTP_X-NAME, then i am getting following error.
Error in (function (id, time_frame, req, res) : object 'name' not
found\n"
If I use following code, I get NULL values,
req$HTTP_X_NAME
or
req[["HTTP_X-NAME"]
or
req[["HTTP_X-Name"]
Following is my code:
#* #get /
healthCheck <- function(){
print("It's Running")
}
#* #param id:int Random Number
#* #post /test/values
#* #get /test/values
test <- function(id, req, res){
print(req[["HTTP_X-Name"]])
return(req[["HTTP_X-Name"]])
}
I am trying to make async requests to a REST API from R. The below curl command illustrates the parameters that I need to the pass to the api. I'm giving you guys the linux curl command as I'm hoping that will make it clear:
curl -v -X POST https://app.example.com/api/ \
-H 'Authorization: somepwd' \
-H "Content-Type: application/json" \
-d {key1: value1, key2: value2}
Right now, I'm accomplishing the same thing in R by executing the following:
library(httr)
library(jsonlite)
content(POST('https://app.example.com/api/'
,add_headers(Authorization = 'somepwd')
,body = toJSON(rDataFrame)
,content_type_json()
)
)
The goal is to submit the above POST request from R but to vary the json string that is sent in the body, and do that asynchronously.
I have been searching for packages that will help me make asynchronous requests rather than making requests serially. The closest thing I could find is the getURIAsynchronous() function from the RCurl package (https://cran.r-project.org/web/packages/RCurl/RCurl.pdf) but do not understand how to submit a PUT request with headers and a body using their function. I would really like to make the above POST request from R but asynchronously where the URI is the same, but the data sent is different for each request.
I found this http://www.omegahat.org/RCurl/concurrent.html
getURIs =
function(uris, ..., multiHandle = getCurlMultiHandle(), .perform = TRUE)
{
content = list()
curls = list()
for(i in uris) {
curl = getCurlHandle()
content[[i]] = basicTextGatherer()
opts = curlOptions(URL = i, writefunction = content[[i]]$update, ...)
curlSetOpt(.opts = opts, curl = curl)
multiHandle = push(multiHandle, curl)
}
if(.perform) {
complete(multiHandle)
lapply(content, function(x) x$value())
} else {
return(list(multiHandle = multiHandle, content = content))
}
}
My idea is that I could replace for (i in uris) with for(i in jsons) where I am looping over the different data that I want to send to the same URL, however I am having trouble understanding the following concepts from the RCurl Package:
How do I pass a header as part of a PUT request. How do I pass data in the body of the request? This was pretty straight forward using the httr package as I have illustrated above.
I tried passing in the header in the curl options and alternatively the header. The thing is I don't understand where to pass the the component parts of the post request: authentication, header, and body within the getURIAsynchronous() function, or any of the resources I have described above.
Does anyone know how to accomplish this? An example would be incredibly helpful.
The curl package has been recently updated to handle async requests (see here)
Using the curl, magrittr and jsonlite packages you can create asynchronous post requests by:
Creating a generic handle with your header and body content using the handle_setform function
Writing a call back function to retrieve your results
Initializing a pool and adding your concurrent requests to it
Running your pool via multi_run
Example code is below:
library(curl)
library(jsonlite)
library(magrittr)
#create a handle object
h <- new_handle() %>%
handle_setheaders(Authorization = "somepwd",
"Content-Type" = "application/json") %>%
handle_setform(body = toJSON(iris))
pool <- new_pool()
# results only available through call back function
cb <- function(req){cat("done:", req$url, ": HTTP:", req$status, "\n", "content:", rawToChar(req$content), "\n")}
# example vector of uris to loop through
uris <- c("https://app.example.com/api/endpoint1"
,"https://app.example.com/api/endpoint2"
,"https://app.example.com/api/endpoint3")
# all scheduled requests are performed concurrently
sapply(uris, curl_fetch_multi, done=cb, pool=pool)
# This actually performs requests
out <- multi_run(pool = pool)
Unable to Validate a template using Heat-API client,when used below method
from heatclient.client import Client
heat = Client('1', endpoint=heat_url, token=auth_token)
heat.stacks.validate(template_file)
Error mesage:
TypeError: validate() takes exactly 1 argument (2 given)
Here is the source code for heat client api:
def validate(self, **kwargs):
"""Validate a stack template."""
resp, body = self.client.json_request('POST', '/validate', data=kwargs)
return body
So, you should not put any argument in the validate() function and I would try run: heat.stacks.validate() and see what it gives you
source code
Try
heat.stacks.validate(template=template_file)
OR
heat.stacks.validate(template=template_file["template"])
#If your template is an inner dict