Is there any way to add multiple on_get methods under single class resource in Falcon?

If suppose we want to implement the following end points in single class resource, Is there any way to put multiple on_get methods within single resource that would differentiate each end point ? Because it makes sense to put under single class as they are pretty much closely related.

Possible end points :

/api/{id}

/api/v1/{id}

/api/{id}/appended

/api/v1/appended/{id}


Solution 1:

Yes, in Falcon 2.0, the add_route method supports a suffix keyword argument which allows you to use a single resource class for multiple endpoints. Some example code:

class UserResource:

    def on_get(self, req, resp):
        resp.media = self.repository.find_all()

    def on_get_single(self, req, resp, user_id):
        resp.media = self.repository.find_user(user_id)


resource = UserResource()

api = falcon.API()
api.add_route('/users', resource)
api.add_route('/users/{user_id}', resource, suffix='single')

From the docs of falcon.API.add_route

If a suffix is provided, Falcon will map GET requests to on_get_{suffix}(), POST requests to on_post_{suffix}(), etc. In this way, multiple closely-related routes can be mapped to the same resource. For example, a single resource class can use suffixed responders to distinguish requests for a single item vs. a collection of those same items.

If you will to see another relevant example of this usage present in Falcon's documentation, read it here.

Solution 2:

Update on Falcon 2.0:

Now Falcon 2.0 support "suffix" out of the box in add_route. For more info refer this. You can use the following code:

class CatalogItem(object):
    # Logic for /api/{id}
    def on_get(self, req, resp, id):
        resp.res.status = falcon.HTTP_200
        res.body = json.dumps({'status': True, 'message': 'success'})


    # Logic for /api/v1/{id}
    def on_get_v1(self, req, resp, id):        
        resp.res.status = falcon.HTTP_200
        res.body = json.dumps({'status': True, 'message': 'success'})


    # Logic for /api/v1/appended/{id}
    def on_get_appended(self, req, resp, id):
        resp.res.status = falcon.HTTP_200
        res.body = json.dumps({'status': True, 'message': 'success'})

app = falcon.API()
app.add_route('/api/{id}', CatalogItem())
app.add_route('/api/v1/{id}', CatalogItem(), suffix='v1')
app.add_route('/api/v1/appended/{id}', CatalogItem(), suffix='appended')

For Falcon older than V2.0:

I hope the following code will help you if you have the same logic for all said route:

class CatalogItem(object):
    def on_get(self, id):
        return self._collection.find_one(id)

app = falcon.API()
app.add_route('/api/{id}', CatalogItem())
app.add_route('/api/v1/{id}', CatalogItem())
app.add_route('/api/v1/appended/{id}', CatalogItem())

This code route to the same on_get method. But I still suggest that you write a separate class with the different on_get method. And if most of the code is similar than you can write helper function and call it whenever needed.

Also, As you mention that "you want to put multiple on_get methods within a single resource that would differentiate each endpoint" which is not possible, but there is a workaround.

You can use the same on_get method and based on the route you can use different logic for each.

class CatalogItem(object):
    def on_get(self, req, resp, id):
        route_path = str(req.path) 
        if route_path.startswith("/api/v1/appended/"):
            # Logic for /api/v1/appended/
            resp.res.status = falcon.HTTP_200
            res.body = json.dumps({'status': True, 'message': 'success'})
        elif route_path.startswith("/api/v1/"):
            # Logic for /api/v1/
            resp.res.status = falcon.HTTP_200
            res.body = json.dumps({'status': True, 'message': 'success'}) 
        elif route_path.startswith("/api/"):
            # Logic for /api/
            resp.res.status = falcon.HTTP_200
            res.body = json.dumps({'status': True, 'message': 'success'})



app = falcon.API()
app.add_route('/api/{id}', CatalogItem())
app.add_route('/api/v1/{id}', CatalogItem())
app.add_route('/api/v1/appended/{id}', CatalogItem())