others-how to upload file using python flask-restful api ?
1. Purpose
In this post, I would demo how to upload file using flask-restful api when using python. I would write a python script which accept a HTTP get request to list all the objects and a HTTP post request to upload file. And I would also demo how to read both the uploaded file and a normal field in the HTTP request.
2. The Environment
- Python 3.7.6
- Flask 1.1.2
- Flask_restful 0.3.8
You can check your python dependencies version as follows:
$ pip show flask
Name: Flask
Version: 1.1.2
Summary: A simple framework for building complex web applications.
Home-page: https://palletsprojects.com/p/flask/
Author: Armin Ronacher
Author-email: [email protected]
License: BSD-3-Clause
Location: /Users/bswen/.pyenv/versions/3.7.6/lib/python3.7/site-packages
Requires: Werkzeug, click, itsdangerous, Jinja2
Required-by: Flask-RESTful, Flask-Bootstrap
And this:
$ pip show flask_restful
Name: Flask-RESTful
Version: 0.3.8
Summary: Simple framework for creating REST APIs
Home-page: https://www.github.com/flask-restful/flask-restful/
Author: Twilio API Team
Author-email: help@twilio.com
License: BSD
Location: /Users/bswen/.pyenv/versions/3.7.6/lib/python3.7/site-packages
Requires: aniso8601, six, Flask, pytz
Required-by:
3. Code
3.1 The domain object: The Book
Here we suppose we have a books store, and want to supply an API for uploading new books to the store. Here is the domain object that represents the Book:
Books = {
'book1': {'title': 'build an API'},
'book2': {'title': 'test 1'},
'book3': {'title': 'profit!'},
}
3.2 The book list function
We should provide the users to ability to list all the books in the store, here is the class:
class BookList(Resource):
def get(self):
return Books
3.3 Configure the Flask_restful http request route
Then , we should configure the flask_rest app to route the http requests to our BookList object, just as follows:
app = Flask(__name__)
api = Api(app)
api.add_resource(BookList, '/books')
The api.add_resource
function just map an url to our class:
add_resource
(resource, *urls, **kwargs)Adds a resource to the api.
- resource (
Type[Resource]
) – the class name of your resource- urls (str) – one or more url routes to match for the resource, standard flask routing rules apply. Any url variables will be passed to the resource method as args.
We should metion that the first parameter is a Class Name, the second is the url that we want to map.
3.4 The main function
if __name__ == '__main__':
app.run(debug=True, port=8080, host="0.0.0.0")
3.5 Run the app
Now we can run the app , just type python upload_file_demo.py
or run it in your pycharm, the result is:
/Users/bswen/.pyenv/versions/3.7.6/bin/python /Users/bswen/work/python/myutils/learn_flask/app_upload_demo.py
* Serving Flask app "app_upload_demo" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 717-204-539
127.0.0.1 - - [16/Mar/2021 20:54:44] "POST /books HTTP/1.1" 201 -
* Detected change in '/Users/bswen/work/python/myutils/learn_flask/app_upload_demo.py', reloading
* Restarting with stat
* Debugger is active!
* Debugger PIN: 717-204-539
Then we can test the ‘/books’ url as follows:
$ curl http://localhost:8080/books
{
"book1": {
"title": "build an API"
},
"book2": {
"title": "test 1"
},
"book3": {
"title": "profit!"
}
}
3.6 Let’s add the upload file function
We can add this function to the class ‘BookList’:
UPLOAD_DIR="/Users/bswen/work/python/myutils/learn_flask/uploaded_files"
def __init__(self):
self.parser = reqparse.RequestParser()
def post(self):
# Part 1: add arguments in the post request
self.parser.add_argument("bookfile", type=werkzeug.datastructures.FileStorage, location='files')
self.parser.add_argument('title')
# Part 2: parse the request
args = self.parser.parse_args()
# Part 3: compute and set the next id of the new book
book_id = int(max(Books.keys()).lstrip('book')) + 1
bookfile = args.get("bookfile")
book_id = 'book%i' % book_id
Books[book_id] = {'title': args['title']}
# Part 4: save the uploaded file to UPLOAD_DIR
bookfile.save(os.path.join(UPLOAD_DIR, bookfile.filename))
return Books, 201
Let’s explain the above codes :
- Part 1: The ‘self.parser.add_argument’ line declares two fields in the request, a file object and a normal text field in the request, here we used the type ‘werkzeug.datastructures.FileStorage’ to represent the file object in the request:
- The Werkzeug is a comprehensive WSGI web application library, it began as a simple collection of various utilities for WSGI applications and has become one of the most advanced WSGI utility libraries. … A threaded WSGI server for use while developing applications locally. Werkzeug provides some subclasses of common Python objects to extend them with additional features. Some of them are used to make them immutable, others are used to change some semantics to better work with HTTP.
- The
FileStorage
class is a thin wrapper over incoming files. It is used by the request object to represent uploaded files. All the attributes of the wrapper stream are proxied by the file storage so it’s possible to dostorage.read()
instead of the long formstorage.stream.read()
.
- Part 2: here we parse the request using the reqparser object , we read both the form data and the uploaded file (multipart/form-data) from the request, you should pay attention to the line:
- bookfile = args.get(“bookfile”), this code retrieve the file object from the request
- Part 3: We compute the next ID of the book , just add one based on the max ID of current books
- Part 4: We can use the file object to save the uploaded file to the specified UPLOAD_DIR
3.7 Run the code and test the upload file function
Now we use the curl to test the file upload function:
(There should exist a file named test.zip in the current working directory where we call the curl command)
$ curl -v -X POST -H "Content-Type: multipart/form-data" -F "title=aabb" -F "[email protected]" http://localhost:8080/books
we get this result:
{
"book1": {
"title": "build an API"
},
"book2": {
"title": "test 1"
},
"book3": {
"title": "profit!"
},
"book4": {
"title": "aabb"
}
}
And the uploaded file:
➜ uploaded_files git:(master) ✗ ls -l
total 32
-rw-r--r-- 1 bswen staff 13819 Mar 16 22:12 test.zip
➜ uploaded_files git:(master) ✗ pwd
/Users/bswen/work/python/myutils/learn_flask/uploaded_files
It works!
4. The whole codes
The code has been uploaded to github as gist,you can click the url to get all the code:
5. Summary
In this post, we demonstrated how to upload file using flask_restful and how to read both the uploaded file and the fields in the http request using flask . Thanks for your reading. Regards.