dmcqu009 created page: lab 17 authored by Dan McQuillan's avatar Dan McQuillan
## is52027c lab 17
apis
##
* step 0 - consuming an api
* step 1 - creating a simple json api
* step 2 - using flask-restful to create an api
# step 0: consuming apis
## using the OpenWeatherMap api
* to use the open weather map, sign up at http://openweathermap.org/appid
* then you can get an api key from https://home.openweathermap.org/api_keys
* the data returned by the current weather api is described at https://openweathermap.org/current
* note that according to the docs "Activation of an API key for Free and Startup accounts takes 10 minutes"
## accessing with python
* we will test the api in the python shell. type 'python' at the command line to enter the shell.
* create your url as a string variable in your shell:
url = "http://api.openweathermap.org/data/2.5/weather?q=London%2C%20UK&units=metric&appid=<YOUR API KEY HERE>"
* note the location "London, UK" is url encoded in the url string
* import the urllib2 library so you can request the url
import urllib2
* read the data returned by the url
data = urllib2.urlopen(url).read()
## parsing the data
* if you print this out you will see it is a long string in what looks like a json format i.e. it is serialised json.
* so we want to convert it back to json which we can do with the python function json.loads
* import the json library
import json
* parse the data in to json
weather = json.loads(data)
* we want to check the structure of this data, so we can 'pretty print' the json data using json.dumps
print json.dumps(weather, indent=4, sort_keys=True)
## accesssing the data
* now you can access the json data structure
* pay careful attention to which parts are a python dictionary (most of it) and which of those elements include lists
* for example the current temperature in London is accessed as
weather['main']['temp']
* whereas the 'weather' element itself has a list structure, so the description is accessed as
weather['weather'][0]['description']
* you now have access to the current weather in a form you an use in your code
* a call to the api will always return the latest up-to-date weather as made available by openweathermap
## using apis
* there are some apis which you can use without an api key but most will require you to register
* the most common format for the data is json
* you need to read the api develop docs to see the details of how to interrogate the api
# step 1: simple api
## using jsonify
* remember from step 0 that the most common format for returning api data is json
* we will create a simple api for our mytwits flask app using a flask function called jsonify http://flask.pocoo.org/docs/0.12/api/#flask.json.jsonify
* this will directly return your json structure to the browser
## db -> api
* a simple way to enable an api is to write a view which returns data directly from your db queries as json
* we could use json.dumps, which serialises a python object as a json formatted stream https://docs.python.org/3.5/library/json.html
* but jsonify adds the required http headers / mime types
* add the following view to your app (assuming you have a db method like get\_all\_twits):
@app.route('/api')
def api():
twits = db.get_all_twits()
return jsonify({'twits':twits})
## curl
* we will be using curl to check returns from our apis
* install curl on your vs
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install curl
* while your app is running, connect to your virtual server via another terminal
* test your api by connecting to it using curl
curl -i localhost:8000/api
## add api call to return data for a user
* extend your api to return only the twits for a specific user
* you will need to pass the username via the url
* and call the appropriate db method
* remember that you can have a route for a view that contains a variable e.g.
@app.route('/api/<username>')
# using flask-restful
* now we'll create a fulled-specced api using the flask-restful extension https://flask-restful.readthedocs.io/en/latest/
* this requires a bit more set up but rewards us with more powerful functionality that handles the full range of RESTful HTTP methods
* there is a template for this in the repo called 'mytwits\_mysqli\_template.py'
* you should be able to see from the template where to add the code referred to below
## flask-restful setup
* install flask-restful on your vs
pip install --user flask-restful
* the main building blocks of a flask-restful api are resources
* creating an api class based on flask-restful's Resource class means we can get easy access to all the HTTP methods
* you can see how this works at https://flask-restful.readthedocs.io/en/latest/quickstart.html#resourceful-routing
## basic steps
* you will see from the flask-restful quickstart that we need 3 basic steps to implement a flask-restful api
* initialise our api on the app
api = Api(app)
* creating our api resource class as something like this;
class TwitsApi(Resource):
def get(self):
return db.get_all_twits()
* add the resource to a route
api.add_resource(TwitsApi,'/api')
## data formatting and argument parsing
our api will go beyond the simplest flask-restful example in two ways
1. using data formatting to deal with objects such as datetime objects
1. using argument parsing to deal with request data validation
the validation is bsaically the same as we use for validating user form input, but here we are validating api requests.
## data formatting
* to format the data we will use the fields module and the @marshal_with() decorator
* they are described in the flask-restful quickstart: https://flask-restful.readthedocs.io/en/latest/quickstart.html#data-formatting
* first we need to import them
from flask_restful import fields, marshal_with
* we provide a set of resource fields
#----resource fields to filter the output for flask-restful
resource_fields = {
'twit_id': fields.Integer,
'twit': fields.String,
'user_id': fields.Integer,
'created_at': fields.DateTime(dt_format='rfc822')
}
* we use these to format the output using the decorator
class TwitsApi(Resource):
@marshal_with(resource_fields)
def get(self):
return db.get_all_twits()
* in our case this means we can handle the datetime objects; otherwise we would get a message saying "datetime.datetime is not JSON serializable"
## argument parsing
* we need to import the reqparse module
from flask_restful import reqparse
* this module is described in the flask-restful quickstart: https://flask-restful.readthedocs.io/en/latest/quickstart.html#argument-parsing
* as well as checking the input it can be used to provide helpful error messages to the user
#----parse and verify the api inputs
parser = reqparse.RequestParser()
parser.add_argument('twit', type=str, help='the text of the twit; must be a string')
parser.add_argument('twit_id', type=int, help='the id of the twit; must be an integer')
parser.add_argument('user_id', type=int, help='the id of the twit; must be an integer')
* this is used below when we are adding a twit via the api
## adding twits via the api
* flask-restful gives us easy access to the 4 HTTP actions; GET, POST, PUT, DELETE
* for example, to add a twit we simply define a post method on our TwitsApi resource
* we also use reqparse to vaildate the input
class TwitsApi(Resource):
@marshal_with(resource_fields)
def get(self):
return db.get_all_twits()
* add a post method to the same class:
@marshal_with(resource_fields)
def post(self):
args = parser.parse_args()
twit = args['twit']
user_id = args['user_id']
db.add_twit(twit,user_id)
return db.get_all_twits()
* now a correctly formatted POST request to the api will add a new twit
## testing
* if you add the above code snippets in the correct parts of the template you should be able to get twits using curl
curl -i localhost:8000/api
* flask-restful recognise a twit posted as form data
* see the curl manual for more details on how to emulate a web form: https://curl.haxx.se/docs/httpscripting.html#POST
curl -d "twit='posted using a form'&user_id=1&submit=SUBMIT" localhost:8000/api
* flask-restful will also recognise a twit posted as json:
curl -H "Content-Type: application/json" -X POST -d '{"twit":"posted using json","user_id":1}' http://localhost:8000/api
## extend to PUT and DELETE
* we can extend the api to include PUT and DELETE as well
* PUT will be used to modify (edit) twits and DELETE to delete them(!)
* these methods will require the id of the twit we want to act on
* so we can create another flask-restful resourcei...
class TwitsIdApi(Resource):
* and connect that to a path that includes a twit id
api.add_resource(TwitsIdApi,'/api/<int:twit_id>')
* use the db methods you previously implemented (or the ones from my repo code) to implement PUT and DELETE as part of the api
* you can also test these HTTP methods using curl; see the quickstart full example for examples of the correct curl commands: https://flask-restful.readthedocs.io/en/latest/quickstart.html#full-example
* e.g.
curl -d "twit=changed this" localhost:8000/api/7 -X PUT -v
curl localhost:8000/api/7 -X DELETE -v
## extension
* study the flask-restful docs https://flask-restful.readthedocs.io/en/latest/
* consider how to extend your api
* implement your changes