Programmtically build plumber router with handlers using external function and specific environemtns

Hi,
I tried to convert our plumber APIs so that they were built up programmatically. I ran into some problems, which as far as I can see, where due to a lack of documentation (or due to me not finding what I was looking for).

It seems that it is not very clear from the documentation that in plumber router handlers only expressions and not functions are executed within the environment defined in the plumber router.

However, I have to call a function of a different namespace (another package) with a very large pre-loaded object. With a bit of research in the unit tests folder of the plumber package and by reading some of the tickets in github, I created the following code in order to resolve this problem (this is a reprex so it looks a bit different but the idea should be the same):

library(namespace)
library(plumber)
library(testthat)

new_env <- new.env()
new_env$data <- datasets::iris

ns <- namespace::makeNamespace("my_namespace")
ns$my_function <- function(req, res, data) {
  data
}
namespaceExport(ns, ls(ns))
#> <environment: 0x000000000c649d90>


pr <- plumber::pr(envir = new_env)

pr <- plumber::pr_post(pr,
                       path = "/path",
                       handler = local({
                         function(req, res, ...) {
                           my_namespace::my_function(req = req,
                                                     res = res,
                                                     data = data)
                         }
                       },
                       envir = pr$environment))

end <- pr$endpoints[[1]][[1]]
res <- plumber:::PlumberResponse$new()
req <- list()
testthat::expect_equal(end$exec(req = req, res = res), datasets::iris)

My question now is whether this is the best way to get around the problem or whether I have overlooked something and there are better / easier solutions for this.

1 Like

If you use a plumber decorated file, the function defined there will be evaluated in the router environment.

Since you are building your router programmatically, you are subject to R scoping, so if you want to evaluate a function in a particular environment, use eval.

Example using plumber decoration:
plumber.R

#* @post /path
function(req, res, ...) {
  my_function(req = req,
              res = res,
              data = data)
}

my_function <- function(req, res, data) {
  data
}

data <- datasets::iris

api.R

library(plumber)
pr <- plumb("plumber.R")

end <- pr$endpoints[[1]][[1]]
res <- plumber:::PlumberResponse$new()
req <- list()
testthat::expect_equal(end$exec(req = req, res = res), datasets::iris)

Similar, but programmatically

library(plumber)

pr <- pr()
eval(expression({
  data <- datasets::iris
  my_function <- function(req, res, data) {
    data
  }
  handler <- function(req, res, ...) {
    my_function(req = req, res = res, data = data)
  }
}), pr$environment)
pr_post(pr, path = "/path", handler = pr$environment$handler)

end <- pr$endpoints[[1]][[1]]
res <- plumber:::PlumberResponse$new()
req <- list()
testthat::expect_equal(end$exec(req = req, res = res), datasets::iris)

This topic was automatically closed 21 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.