HTTP Verbs

Requests provides access to almost the full range of HTTP verbs: GET, OPTIONS, HEAD, POST, PUT, PATCH and DELETE. The following provides detailed examples of using these various verbs in Requests, using the GitHub API.

We will begin with the verb most commonly used: GET. HTTP GET is an idempotent method that returns a resource from a given URL. As a result, it is the verb you ought to use when attempting to retrieve data from a web location. An example usage would be attempting to get information about a specific commit from GitHub. Suppose we wanted commit a050faf on Requests. We would get it like so:

  1. >>> import requests
  2. >>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')

We should confirm that GitHub responded correctly. If it has, we want to work out what type of content it is. Do this like so:

  1. >>> if r.status_code == requests.codes.ok:
  2. ... print(r.headers['content-type'])
  3. ...
  4. application/json; charset=utf-8

So, GitHub returns JSON. That’s great, we can use the r.json method to parse it into Python objects.

  1. >>> commit_data = r.json()
  2. >>> print(commit_data.keys())
  3. [u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message']
  4. >>> print(commit_data[u'committer'])
  5. {u'date': u'2012-05-10T11:10:50-07:00', u'email': u'me@kennethreitz.com', u'name': u'Kenneth Reitz'}
  6. >>> print(commit_data[u'message'])
  7. makin' history

So far, so simple. Well, let’s investigate the GitHub API a little bit. Now, we could look at the documentation, but we might have a little more fun if we use Requests instead. We can take advantage of the Requests OPTIONS verb to see what kinds of HTTP methods are supported on the url we just used.

  1. >>> verbs = requests.options(r.url)
  2. >>> verbs.status_code
  3. 500

Uh, what? That’s unhelpful! Turns out GitHub, like many API providers, don’t actually implement the OPTIONS method. This is an annoying oversight, but it’s OK, we can just use the boring documentation. If GitHub had correctly implemented OPTIONS, however, they should return the allowed methods in the headers, e.g.

  1. >>> verbs = requests.options('http://a-good-website.com/api/cats')
  2. >>> print(verbs.headers['allow'])
  3. GET,HEAD,POST,OPTIONS

Turning to the documentation, we see that the only other method allowed for commits is POST, which creates a new commit. As we’re using the Requests repo, we should probably avoid making ham-handed POSTS to it. Instead, let’s play with the Issues feature of GitHub.

This documentation was added in response to Issue #482. Given that this issue already exists, we will use it as an example. Let’s start by getting it.

  1. >>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/issues/482')
  2. >>> r.status_code
  3. 200
  4. >>> issue = json.loads(r.text)
  5. >>> print(issue[u'title'])
  6. Feature any http verb in docs
  7. >>> print(issue[u'comments'])
  8. 3

Cool, we have three comments. Let’s take a look at the last of them.

  1. >>> r = requests.get(r.url + u'/comments')
  2. >>> r.status_code
  3. 200
  4. >>> comments = r.json()
  5. >>> print(comments[0].keys())
  6. [u'body', u'url', u'created_at', u'updated_at', u'user', u'id']
  7. >>> print(comments[2][u'body'])
  8. Probably in the "advanced" section

Well, that seems like a silly place. Let’s post a comment telling the poster that he’s silly. Who is the poster, anyway?

  1. >>> print(comments[2][u'user'][u'login'])
  2. kennethreitz

OK, so let’s tell this Kenneth guy that we think this example should go in the quickstart guide instead. According to the GitHub API doc, the way to do this is to POST to the thread. Let’s do it.

  1. >>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"})
  2. >>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/482/comments"
  3. >>> r = requests.post(url=url, data=body)
  4. >>> r.status_code
  5. 404

Huh, that’s weird. We probably need to authenticate. That’ll be a pain, right? Wrong. Requests makes it easy to use many forms of authentication, including the very common Basic Auth.

  1. >>> from requests.auth import HTTPBasicAuth
  2. >>> auth = HTTPBasicAuth('fake@example.com', 'not_a_real_password')
  3. >>> r = requests.post(url=url, data=body, auth=auth)
  4. >>> r.status_code
  5. 201
  6. >>> content = r.json()
  7. >>> print(content[u'body'])
  8. Sounds great! I'll get right on it.

Brilliant. Oh, wait, no! I meant to add that it would take me a while, because I had to go feed my cat. If only I could edit this comment! Happily, GitHub allows us to use another HTTP verb, PATCH, to edit this comment. Let’s do that.

  1. >>> print(content[u"id"])
  2. 5804413
  3. >>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."})
  4. >>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/comments/5804413"
  5. >>> r = requests.patch(url=url, data=body, auth=auth)
  6. >>> r.status_code
  7. 200

Excellent. Now, just to torture this Kenneth guy, I’ve decided to let him sweat and not tell him that I’m working on this. That means I want to delete this comment. GitHub lets us delete comments using the incredibly aptly named DELETE method. Let’s get rid of it.

  1. >>> r = requests.delete(url=url, auth=auth)
  2. >>> r.status_code
  3. 204
  4. >>> r.headers['status']
  5. '204 No Content'

Excellent. All gone. The last thing I want to know is how much of my ratelimit I’ve used. Let’s find out. GitHub sends that information in the headers, so rather than download the whole page I’ll send a HEAD request to get the headers.

  1. >>> r = requests.head(url=url, auth=auth)
  2. >>> print(r.headers)
  3. ...
  4. 'x-ratelimit-remaining': '4995'
  5. 'x-ratelimit-limit': '5000'
  6. ...

Excellent. Time to write a Python program that abuses the GitHub API in all kinds of exciting ways, 4995 more times.