Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple GETs in same resource support/example? #114

Closed
harit-sunrun opened this issue Aug 11, 2013 · 24 comments
Closed

Multiple GETs in same resource support/example? #114

harit-sunrun opened this issue Aug 11, 2013 · 24 comments

Comments

@harit-sunrun
Copy link

Hello @RobSpectre @cummack @devinrader @dougblack @frankstratton

I have a following situation where I have User Resource and it needs following endpoints

GET /users/uuid/
# returns user

GET /users/uuid/financialmonthdetails
# returns a map like
{
 'time_from': some_time,
 'time_to': some_time,
 'remaining_days': some_days
}

Question
How can I have two @GET endpoints in same resource class?

Thank you

@dougblack
Copy link
Contributor

You can't have two different GET endpoints on one resource class.

To support GETs on those two routes, you'll need to break them into two different resources. It seems like you want to have a User resource and a FinancialMonth resource.

@harit-sunrun
Copy link
Author

I see, Although I am not sure what the roadmap looks like for the project, but don't you think one resource should be able to support multiple GETs?

@dougblack
Copy link
Contributor

I don't. Flask-RESTful is designed to make it very easy for users to design RESTful APIs. REST APIs have just one URL for a given resource. In a RESTful API, the two URLs in the example you provided above would be two separate resources and therefore would map to two separate classes each with their own implementation of the get method. A User isn't the same thing as a FinancialMonth, so you'd want to have two separate classes.

Basically, Flask-RESTful won't support multiple GETs for a given resource because that's not supported in REST.

@harit-sunrun
Copy link
Author

I think I understand you now, Thank you @dougblack

@dtheodor
Copy link
Contributor

What about the /users and /users/Tom urls, shouldn't they belong to the same resource? Following the flask-restful example you need to create two seperate resources to handle this situation, Users for the first url and User for the second. I feel this is not only redundant but breaks encapsulation and good modularity.

I have hacked around this by doing

class User(Resource):
    def get(self, id=None):
        if id is None:
            return users
        else:
            return user
    def post(self):
        #....

and then to register the resource

api.add_resource(User, '/users', methods=['GET', 'POST'])
api.add_resource(User, '/users/<id>', methods=['GET', 'PATCH', 'DELETE'])

but this breaks with Flask 0.10

@dougblack
Copy link
Contributor

Generally, RESTful APIs have List and Instance resources. It's uncommon for the List resource to have the same functionality as the Instance resource and thus they're recognized as two separate things. You can see this just by looking at your above example. You're supporting GET, PATCH, and DELETE on the Instance resource but only GET and POST on the List resource. Why didn't you add support for DELETE on /users? Because you correctly reasoned that deleting the List resource doesn't make sense. So we've got diverging functionality on a basic level--sounds like it's time to break things into classes! :)

Here's how I'd do what you're trying to accomplish.

class Users(Resource):
    def get(self):
        return users
    def post(self):
        #...

class User(Resource):
    def get(self, id=None):
        return find_user_by_id(id)
    def post(self):
        #...

api.add_resource(Users, '/users')
api.add_resource(User, '/users/<id>')

This is a perfectly normal way to build your API.

If you find yourself wanting support for multiple GET methods on a resource, chances are you're not being RESTful and should probably reevaluate your design.

@frankstratton
Copy link
Contributor

Just to follow up on @dtheodor's comment.

Your way is fine if you're okay managing the optional parameter. The issue in flask 0.10 is having conflicting endpoint names registered. By default flask-restful uses the classname as the endpoint but you can overwrite it as follows:

api.add_resource(User, '/users', endpoint="users")
api.add_resource(User, '/users/<id>', endpoint="user")

Also note that you don't need to specify methods as they are taken care of by the method functions you write/don't write in your Resource class.

@dougblack
Copy link
Contributor

Great points! Thanks @frankstratton.

@dtheodor
Copy link
Contributor

I understand the different behavior between List and Instance resources, as you correctly point out. However, a Resource class in a real application (i.e. not example/tutorial material) is a unit of modularity that encapsulates much more than the HTTP verbs. It will use authentication, access lists, common database-related calls, exception handling, and more often than not these are identical among both List and Instance. Therefore, you are ending up duplicating code to apply it in both classes, and having to change things twice when changes are needed.

Anyway, things work as they are now, but this is an interesting discussion on design, which can always turn philosophical unless someone presents a concrete problem with the current design, which I don't really have at the moment.

@enewhuis
Copy link

I'd like to open this question up again. What should we do if there really are two names for the same resource?

GET /object/
GET /object/<type>/<name>

...assuming <type>+<name> is a unique constraint (composite key). Here we can access the same resource using two different URIs. It seems quite natural to implement two gets on the same class because we're dealing with the same object in the server, the same representation of its state, the same behavior.

@dougblack
Copy link
Contributor

Sure, let's take a look.

Let's think about what those two routes might be for.

GET /object
GET /object/<type>/<name>

Based on your description it seems that object is actually a ListResource that returns multiple instances of some other object. It also sounds like the second route is used to filter the list by type and name. At Twilio, we accomplish this by using query parameters to define the filter. So instead of encoding the filter parameters type and name in the URL, you'd include them in the query parameters:

GET /object?type=mytype&name=myname

If all of the assumptions I've made are correct, this would solve your problem, and you'd still be able to only have one get method. It might look something like this:

from flask import Flask, request
from flask.ext import restful

class ObjectResource(restful.Resource):
    def get(self):
        obj_type = request.args.get('type', "")
        obj_name = request.args.get('name', "")
        objects = db.filter(type=obj_type, name=obj_name)

app = Flask(__name__)
api = restful.Api(app)
api.add_resource(ObjectResource, '/object')

Alternatively, if you really want to use the URL to capture those parameters, you could do something like this:

from flask import Flask, request
from flask.ext import restful

class ObjectResource(restful.Resource):
    def get(self, type, name):
        objects = db.filter(type=obj_type, name=obj_name)

app = Flask(__name__)
api = restful.Api(app)
api.add_resource(
    ObjectResource, 
    '/object', 
    '/object/<type>/<name>', 
    defaults={'type': '', 'name': ''}
)

Let me know if this looks okay.

@enewhuis
Copy link

Somehow the was dropped from my intended post.

The two routes are actually:

GET /object/
GET /object//

And both refer to a single object and not a list resource. is a unique key over all objects and + is a unique composite key over all objects. Therefore there are two ways to uniquely identify the same object--either by specifying an or by specifying both a and --and I would like to expose both ways through a RESTful URL since there are separate use cases for both.

I suspect this is quite common because many relational-object mappers will use an object ID even though there may be other natural keys or composite keys that also uniquely identify an object.

On Oct 14, 2013, at 10:42 PM, Doug Black <notifications@github.commailto:notifications@github.com> wrote:

Sure, let's take a look.

Let's think about what those two routes might be for.

GET /object
GET /object//

Based on your description it seems that object is actually a ListResource that returns multiple instances of some other object. It also sounds like the second route is used to filter the list by type and name. At Twilio, we accomplish this by using query parameters to define the filter. So instead of encoding the filter parameters type and name in the URL, you'd include them in the query parameters:

GET /object?type=mytype&name=myname

If all of the assumptions I've made are correct, this would solve your problem, and you'd still be able to only have one get method. It might look something like this:

from flask import Flask, request
from flask.ext import restful

class ObjectResource(restful.Resource):
def get(self):
obj_type = request.args.get('type', "")
obj_name = request.args.get('name', "")
objects = db.filter(type=obj_type, name=obj_name)

app = Flask(name)
api = restful.Api(app)
api.add_resource(ObjectResource, '/object')

Alternatively, if you really want to use the URL to capture those parameters, you could do something like this:

from flask import Flask, request
from flask.ext import restful

class ObjectResource(restful.Resource):
def get(self, type, name):
objects = db.filter(type=obj_type, name=obj_name)

app = Flask(name)
api = restful.Api(app)
api.add_resource(
ObjectResource,
'/object',
'/object//',
defaults={'type': '', 'name': ''}
)

Let me know if this looks okay.


Reply to this email directly or view it on GitHubhttps://github.com//issues/114#issuecomment-26307332.


This message (including any attachments) contains confidential information intended for a specific individual and purpose, and is protected by law. If you are not the intended recipient, you should delete this message. Any disclosure, copying, or distribution of this message, or the taking of any action based on it, is strictly prohibited. Unless specifically indicated, this message is not an offer to sell or a solicitation of any investment products or other financial product or service, an official confirmation of any transaction, or an official statement of StoneCastle. There is no guarantee that e-mail transmissions are secure or error-free as such transmissions can be intercepted, corrupted, lost or destroyed, or can arrive late or incomplete or can contain viruses. The sender does not accept liability for any errors or omissions in the contents, or delay in receipt, of this e-mail message or its attachments which arise in the course of its transmission or receipt, and does not guarantee that the message contains no viruses.

@neelay-shah
Copy link

what would you suggest to get user by id and email which belongs to same resource
/user/id/{user_id}
/user/email/{user_email}

@JohnLockwood
Copy link

To me it seems like the tail of Flask's add_url_rule implementation is wagging the dog of our definitions of RESTful design here. Granted the GET case is open to debate, since GET in the list case often needs query parameters or some sort of qualifier in the route to explain what's being gotten. But having worked with both Java and Ruby in the past, neither one of which follow this convention as far as I know, it really made sense to me to have PUT, POST, DELETE, and two flavors of GET in the same Resource. I've create a GIST for a proof of concept of the solution, https://gist.github.com/JohnLockwood/d9fa9393be269322df1a .

Cheers.

@slisznia
Copy link

slisznia commented Jul 4, 2016

If you look at the JAX-RS v1.1 specification and implementation of it such as Apache Wink, you will notice that handling multiple GETs in a single resource class is allowed and makes complete sense.

Lacking support for this pattern is a major inconvenience.

@binarytrails
Copy link

I'm late to the party...

However, it is totally double! I admit that it wasn't clear. As you see below, you have to define many routes for one resource and then set None the arguments which vary:

...
self.api.add_resource(account.AccountsCodecs,
            '/accounts/<account_id>/codecs/',
            '/accounts/<account_id>/codecs/<codec_id>/',
...
class AccountsCodecs(Resource):
...
    def get(self, account_id, codec_id=None):
        if (codec_id is not None):
            ...
....

Good luck!

Taken from https://github.com/sevaivanov/ring-api/.

@vimox-shah
Copy link

How can I create POST, GET, PUT on one resource in flask restful??

@binarytrails
Copy link

@vimox-shah look at my example and add your post, get & put methods into a class representing your resource by inheriting from a Resource parent class then, add the resource to your api instance and map your url arguments to the methods signatures and voilà! 🎉

@rayest
Copy link

rayest commented Mar 13, 2018

@neelay-shah,You solved the issue ?

@Rooni
Copy link

Rooni commented Mar 23, 2018

@rayest
It's not really a nice way to structure your endpoint.
If you just expect either an email or a username you're probably better off writing a simple check for it so it allows both.

also that comment is almost 4 years old now

@anshupitlia
Copy link

What if I have PATCH request. And all wanting to do separate things.

@rsanath
Copy link

rsanath commented Mar 18, 2019

What about the /users and /users/Tom urls, shouldn't they belong to the same resource? Following the flask-restful example you need to create two seperate resources to handle this situation, Users for the first url and User for the second. I feel this is not only redundant but breaks encapsulation and good modularity.

I have hacked around this by doing

class User(Resource):
    def get(self, id=None):
        if id is None:
            return users
        else:
            return user
    def post(self):
        #....

and then to register the resource

api.add_resource(User, '/users', methods=['GET', 'POST'])
api.add_resource(User, '/users/<id>', methods=['GET', 'PATCH', 'DELETE'])

but this breaks with Flask 0.10

Is there any other extension for Flask that respects this method?
I'm tired of searching for a good library to build restful API with Flask

@CaptainCsaba
Copy link

What about the /users and /users/Tom urls, shouldn't they belong to the same resource? Following the flask-restful example you need to create two seperate resources to handle this situation, Users for the first url and User for the second. I feel this is not only redundant but breaks encapsulation and good modularity.
I have hacked around this by doing

class User(Resource):
    def get(self, id=None):
        if id is None:
            return users
        else:
            return user
    def post(self):
        #....

and then to register the resource

api.add_resource(User, '/users', methods=['GET', 'POST'])
api.add_resource(User, '/users/<id>', methods=['GET', 'PATCH', 'DELETE'])

but this breaks with Flask 0.10

Is there any other extension for Flask that respects this method? I'm tired of searching for a good library to build restful API with Flask

Have you managed to find the extension, or workaround?

@tday
Copy link

tday commented Feb 16, 2022

If anyone's looking here for a solution, Flask can now do this out of the box with MethodView (which is recommended as a replacement to Flask-RESTful's Resource) .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests