Giter VIP home page Giter VIP logo

Comments (16)

hadley avatar hadley commented on July 26, 2024

Oh you also need the url so:

  • expose(normalMean) <- apiGET("/mean")
  • expose(normalMean, "GET") <- "/mean"
  • Or even just expose(normalMean, "GET", "/mean"): you could just maintain a global list of endpoints and this would add to it - that might be a nicer approach than using attributes

from plumber.

bergant avatar bergant commented on July 26, 2024

I guess it is not compatible with roxigen - (error: unknown key...).

from plumber.

hadley avatar hadley commented on July 26, 2024

Another advantage of using attributes is that you could easily apply to it a package

from plumber.

trestletech avatar trestletech commented on July 26, 2024

That's a good point. I hadn't considered using this internally on a package, as the current implementation would cause problems with Roxygen and you may want different API "views" into your package which you can't get if all you can use is annotations.

I am really fond of the simplicity of just adding a comment to your existing function, though. But maybe expose would be simple enough that it could still be a no-brainer. I guess I could always support both.

I'll chew on it.

(Thanks for the feedback, btw)

from plumber.

gaborcsardi avatar gaborcsardi commented on July 26, 2024

I am with @hadley on this one. IMO R code should always be in a package, that the most convenient way to express dependencies on (other) packages and package versions.

Simplicity is great, but I think you can still keep it simple. Here are some more possibilities. Actually you might not even need to name the function, if you only want to expose it in the API, but don't use it otherwise.

get("/mean", function(samples = 10){
  data <- rnorm(samples)
  mean(data)
})

Then if you still want to name the function:

normal_mean <- get("/mean", function(samples = 10) {
  data <- rnorm(samples)
  mean(data)
})

Or:

get("/mean",
  normal_mean <- function(samples = 10) {
    data <- rnorm(samples)
    mean(data)
  }
)

This last one need some NSE, so might not be the best option.

from plumber.

gaborcsardi avatar gaborcsardi commented on July 26, 2024

Great idea, btw!

from plumber.

hadley avatar hadley commented on July 26, 2024

I think get() is a bit too short given the specificity of the problem, but rapier_get() or api_get() would be good.

@gaborcsardi I like a setter based approach because then it feels more like you're adding an API on to existing code, rather than writing a web service

from plumber.

gaborcsardi avatar gaborcsardi commented on July 26, 2024

Yeah, get() is not the best name, but you can always write rapier::get().

I personally don't like the setter approach that much, because when you look at the function head, you don't see if the function is part of the API, or not, and you need to search for the setter function. It might even be in another file. The current syntax is good in this respect because it has everything important at the top.

Btw. with my syntax you can even do sg like this:

normal_mean <- function(samples = 10) {
  data <- rnorm(samples)
  mean(data)
} %>% add_to_api("GET", "/mean")

:) Although this again puts the API part in the end. :(

from plumber.

hadley avatar hadley commented on July 26, 2024

@gaborcsardi I'd actually see that as an advantage - you could have an API.R where you define the complete API in one place. But I think it depends on whether you think of the package primarily as creating a REST endpoint, or whether it's just an add on.

from plumber.

gaborcsardi avatar gaborcsardi commented on July 26, 2024

You don't actually need a setter function to be able to put the API in a separate file. The same rapier function can be used both ways:

normal_mean <- rapier("GET", "/mean", function(samples = 10) {
  data <- rnorm(samples)
  mean(data)
})
normal_mean <- function(samples = 10) {
  data <- rnorm(samples)
  mean(data)
})
rapier("GET", "/mean", normal_mean)

and the last line can go into another file if you like. All you need is that rapier returns the function itself.

from plumber.

trestletech avatar trestletech commented on July 26, 2024

Here's my current thinking on the matter. I really feel like the "niche" here is in "programming-free APIs" i.e. annotating functions or something equally dead-simple to get up-and-running. At least for the simple case, I like that the info about the API endpoint is so close to the function header, yet you needn't go wrap all your functions in some new semantics. But I agree that there needs to be another mode of operation for e.g. package authors.

I like the brevity of the expose variants, but I can't get over the global state, both in that it seems likely that you'd get collisions/leftovers if this were to become popular, and in that you no longer have nestable routers. I don't think nestability is critical, but I really like the idea that I could have a carRouter and a houseRouter and then expose one with the /car prefix and the other with the /house prefix. I imagine that would be helpful when you get to large APIs, though it's not a must-have.

So I'm realizing that the current R6 classes already support this, though the naming could certainly use some massaging. Here are a couple of possibilities:

mathRouter <- RapierRouter$new() #just Router$new() ? router?
mathRouter$addEndpoint("GET", "/mean") <- normalMean
mathRouter$addEndpoint("GET", "/append") <- appendNum

router <- RapierRouter$new()
router$addRouter("/math", mathRouter)

router$serve()

or

mathRouter <- RapierRouter$new()
mathRouter$expose(normalMean, "GET", "/mean")
mathRouter$expose(appendNum, "POST", "/append")

router <- RapierRouter$new()
router$addRouter("/math", mathRouter)

router$serve()

Bearing in mind that this mode is targeting the "advanced" user (R package authors, etc.), do you think the OO R6 style is too foreign to users?

from plumber.

gaborcsardi avatar gaborcsardi commented on July 26, 2024

@trestletech Yes, I think what you wrote is one step further than what @hadley and I suggested and it is great! AFAIK it is very similar how express (and I guess other similar software) does this.

So I am looking forward to this.

I think to use it in production, we would still need (1) a proper web server, and (2) a faster R interpreter. These are kind of independent of your package. (1) might have been solved already by httpuv, I am not sure, and there is work for (2).

Personally I still don't like the setter functions, I think it is somewhat strange for non-hadrcore R users. R6 objects are mutable, so instead of

mathRouter$addEndpoint("GET", "/mean") <- normalMean

you can just do

mathRouter$addEndpoint("GET", "/mean", normalMean)

from plumber.

hadley avatar hadley commented on July 26, 2024

To me, using roxygen style comments is a bit too clever, because:

  • You lose all the nice IDE features associated with calling functions: help, autocomplete, ...
  • They look like roxygen, but aren't, which is likely to cause confusion
  • It's difficult for other tools work with the additional metadata added by rapier

from plumber.

bergant avatar bergant commented on July 26, 2024

From a lay person perspective I somehow like the idea with comments. If I can describe the function interface via roxygen to a human why not to application? (It should be easier.)
I agree about IDE, but it could be supported if/when the thing gets popular (as with shiny and dplyr?)
About the compatibility issue - would it be possible with an additional roxygen roclet (api_roclet)? But I have no experience here.

from plumber.

trestletech avatar trestletech commented on July 26, 2024

Note to self: I think my decision is to...

  • fine-tune and document the object-oriented approach discussed above.
  • Support a non-Roxygen-conflicting syntax like ##' whatever to give Package authors an easy out while maintaining backwards compatibility (after confirming that this indeed won't conflict)
  • Migrate all examples to the ##' style.

from plumber.

trestletech avatar trestletech commented on July 26, 2024

Settled on the #* convention, but maintain #' for backwards compatibility. All docs and examples have been updated as of 3e6ca06 and the "programmatic" approach is now documented here: http://plumber.trestletech.com/docs/programmatic/ . Thanks for the feedback!

from plumber.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.