Comments (16)
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.
I guess it is not compatible with roxigen - (error: unknown key...).
from plumber.
Another advantage of using attributes is that you could easily apply to it a package
from plumber.
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.
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.
Great idea, btw!
from plumber.
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.
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.
@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.
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.
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.
@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.
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.
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.
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.
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)
- A love letter to Plumber ... and a request to find out more about its roadmap moving fowards. HOT 4
- Serializer for multipart/form-data HOT 3
- Make defaultErrorHandler more verbose in the Connect application logs HOT 8
- "Plumber Server" section in Tips & Tricks article HOT 2
- R process not releasing memory; Want aggressive garbage collection HOT 1
- Set plumber options through env HOT 3
- Default `path` value in `session_cookie()`
- set parser failed with attempt to apply non-function
- Plumber is not releasing memory HOT 11
- How to add operationId in Plumber? HOT 4
- how to redirect to /docs/ not the /__docs__/?
- Parse rds could avoid hitting disk
- Endpoint with octet serializer which receives a file in the request freezes whole Plumber.R execution
- Swagger is locked when using the `checkAuth` filter example from docs
- New hook after route has been found but before it's been evaluated HOT 1
- npm package should be switched from `@apidevtools/swagger-cli` to `@redocly/cli`
- Json and csv serializers generate error with large amounts of data HOT 1
- fix request object table layout in routing vignette
- Support mounting using `pr_mount()` without a trailing slash
- `pr_set_error()` doesn't apply to endpoints defined inside `pr_mount()`
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from plumber.