Flask-RESTful API: multiple and complex endpoints

2024/11/19 5:41:59

In my Flask-RESTful API, imagine I have two objects, users and cities. It is a 1-to-many relationship. Now when I create my API and add resources to it, all I can seem to do is map very easy and general URLs to them. Here is the code (with useless stuff not included):

class UserAPI(Resource):  # The API class that handles a single userdef __init__(self):# Initializedef get(self, id):# GET requestsdef put(self, id):# PUT requestsdef delete(self, id):# DELETE requestsclass UserListAPI(Resource):  # The API class that handles the whole group of Usersdef __init__(self):def get(self):def post(self):api.add_resource(UserAPI, '/api/user/<int:id>', endpoint='user')
api.add_resource(UserListAPI, '/api/users/', endpoint='users')class CityAPI(Resource):def __init__(self):def get(self, id):def put(self, id):def delete(self, id):class CityListAPI(Resource):def __init__(self):def get(self):def post(self):api.add_resource(CityListAPI, '/api/cities/', endpoint='cities')
api.add_resource(CityAPI, '/api/city/<int:id>', endpoint='city')

As you can see, I can do everything I want to implement a very basic functionality. I can GET, POST, PUT, and DELETE both objects. However, my goal is two-fold:

(1) To be able to request with other parameters like city name instead of just city id. It would look something like:
api.add_resource(CityAPI, '/api/city/<string:name>', endpoint='city')
except it wouldn't throw me this error:

AssertionError: View function mapping is overwriting an existing endpoint function

(2) To be able to combine the two Resources in a Request. Say I wanted to get all the users associated with some city. In REST URLs, it should look something like:
/api/cities/<int:id>/users

How do I do that with Flask? What endpoint do I map it to?

Basically, I'm looking for ways to take my API from basic to useful.

Answer

Your are making two mistakes.

First, Flask-RESTful leads you to think that a resource is implemented with a single URL. In reality, you can have many different URLs that return resources of the same type. In Flask-RESTful you will need to create a different Resource subclass for each URL, but conceptually those URLs belong to the same resource. Note that you have, in fact, created two instances per resource already to handle the list and the individual requests.

The second mistake that you are making is that you expect the client to know all the URLs in your API. This is not a good way to build APIs, ideally the client only knows a few top-level URLs and then discovers the rest from data in the responses from the top-level ones.

In your API you may want to expose the /api/users and /api/cities as top-level APIs. The URLs to individual cities and users will be included in the responses. For example, if I invoke http://example.com/api/users to get the list of users I may get this response:

{"users": [ {"url": "http://example.com/api/user/1","name": "John Smith","city": "http://example.com/api/city/35"},{"url": "http://example.com/api/user/2","name": "Susan Jones","city": "http://example.com/api/city/2"}]
}

Note that the JSON representation of a user includes the URL for that user, and also the URL for the city. The client does not need to know how to build these, because they are given to it.

Getting cities by their name

The URL for a city is /api/city/<id>, and the URL to get the complete list of cities is /api/cities, as you have it defined.

If you also need to search for cities by their name you can extend the "cities" endpoint to do that. For example, you could have URLs in the form /api/cities/<name> return the list of cities that match the search term given as <name>.

With Flask-RESTful you will need to define a new Resource subclass for that, for example:

    class CitiesByNameAPI(Resource):def __init__(self):# ...    def get(self, name):# ...api.add_resource(CitiesByNameAPI, '/api/cities/<name>', endpoint = 'cities_by_name')

Getting all the users that belong to a city

When the client asks for a city it should get a response that includes a URL to get the users in that city. For example, let's say that from the /api/users response above I want to find out about the city of the first user. So now I send a request to http://example/api/city/35, and I get back the following JSON response:

{"url": "http://example.com/api/city/35","name": "San Francisco","users": "http://example/com/api/city/35/users"
}

Now I have the city, and that gave me a URL that I can use to get all the users in that city.

Note that it does not matter that your URLs are ugly or hard to construct, because the client never needs to build most of these from scratch, it just gets them from the server. This also enables you to change the format of the URLs in the future.

To implement the URL that gets users by city you add yet another Resource subclass:

    class UsersByCityAPI(Resource):def __init__(self):# ...    def get(self, id):# ...api.add_resource(UsersByCityAPI, '/api/cities/<int:id>/users', endpoint = 'users_by_city')

I hope this helps!

https://en.xdnf.cn/q/26476.html

Related Q&A

Setting initial Django form field value in the __init__ method

Django 1.6I have a working block of code in a Django form class as shown below. The data set from which Im building the form field list can include an initial value for any of the fields, and Im having…

Should I use a main() method in a simple Python script?

I have a lot of simple scripts that calculate some stuff or so. They consist of just a single module.Should I write main methods for them and call them with the if __name__ construct, or just dump it a…

Where can I find mad (mean absolute deviation) in scipy?

It seems scipy once provided a function mad to calculate the mean absolute deviation for a set of numbers:http://projects.scipy.org/scipy/browser/trunk/scipy/stats/models/utils.py?rev=3473However, I c…

Map of all points below a certain time of travel?

My question is very simple and can be understood in one line:Is there a way, tool, etc. using Google Maps to get an overlay of all surface which is below a certain time of travel?I hope the question i…

Pandas populate new dataframe column based on matching columns in another dataframe

I have a df which contains my main data which has one million rows. My main data also has 30 columns. Now I want to add another column to my df called category. The category is a column in df2 which co…

Remove an imported python module [duplicate]

This question already has answers here:Closed 11 years ago.Possible Duplicate:Unload a module in Python After importing Numpy, lets say I want to delete/remove numpy import referenceimport sys import…

Should I create each class in its own .py file?

Im quite new to Python in general.Im aware that I can create multiple classes in the same .py file, but Im wondering if I should create each class in its own .py file.In C# for instance, I would have a…

Should you put quotes around type annotations in python

Whats the difference between these two functions? Ive seen people put quotes around type annotations and other times leave them out but I couldnt find why people choose to use one or the other.def do_…

Handling GET and POST in same Flask view

When I type request.form["name"], for example, to retrieve the name from a form submitted by POST, must I also write a separate branch that looks something like request.form.get["name&qu…

Overriding inherited properties’ getters and setters in Python

I’m currently using the @property decorator to achieve “getters and setters” in a couple of my classes. I wish to be able to inherit these @property methods in a child class.I have some Python code …