Skip to content

Commit

Permalink
Replace httplib2 with requests. Should provide thread safety amongst
Browse files Browse the repository at this point in the history
other things.
  • Loading branch information
julienr committed Jun 19, 2015
1 parent d2d0262 commit b9f7c75
Show file tree
Hide file tree
Showing 10 changed files with 58 additions and 75 deletions.
2 changes: 1 addition & 1 deletion docs/http.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
The http Module
===============

The http module wraps the underlying raw HTTP request module (httplib2 currently) to separate the job of exposing the API from the job of dealing with lower-level interfacing with the HTTP client in use today.
The http module wraps the underlying raw HTTP request module (requests currently) to separate the job of exposing the API from the job of dealing with lower-level interfacing with the HTTP client in use today.

.. automodule:: pyrabbit.http
:members:
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ great w/ Python 3!
Here's a quick demo::

>>> from pyrabbit.api import Client
>>> cl = Client('localhost:55672', 'guest', 'guest')
>>> cl = Client('http://localhost:55672/api', 'guest', 'guest')
>>> cl.is_alive()
True
>>> cl.create_vhost('example_vhost')
Expand Down
12 changes: 5 additions & 7 deletions pyrabbit/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,23 +94,21 @@ class Client(object):

json_headers = {"content-type": "application/json"}

def __init__(self, host, user, passwd, timeout=5):
def __init__(self, api_url, user, passwd, timeout=5):
"""
:param string host: string of the form 'host:port'
:param string server: The base URL for the broker API.
:param string user: username used to authenticate to the API.
:param string passwd: password used to authenticate to the API.
Populates server attributes using passed-in parameters and
the HTTP API's 'overview' information. It also instantiates
an httplib2 HTTP client and adds credentia ls
the HTTP API's 'overview' information.
"""
self.host = host
self.api_url = api_url
self.user = user
self.passwd = passwd
self.timeout = timeout
self.http = http.HTTPClient(
self.host,
self.api_url,
self.user,
self.passwd,
self.timeout
Expand Down
78 changes: 31 additions & 47 deletions pyrabbit/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import json
import os
import socket
import httplib2
import requests
import requests.exceptions
from requests.auth import HTTPBasicAuth
try:
from urlparse import urljoin
from urlparse import urljoin, urlparse, urlunparse
except ImportError:
from urllib.parse import urljoin
from urllib.parse import urljoin, urlparse, urlunparse

class HTTPError(Exception):
"""
Expand Down Expand Up @@ -47,49 +49,33 @@ class NetworkError(Exception):

class HTTPClient(object):
"""
A wrapper for (currently) httplib2. Abstracts away
A wrapper for requests. Abstracts away
things like path building, return value parsing, etc.,
so the api module code stays clean and easy to read/use.
"""

def __init__(self, server, uname, passwd, timeout=5):
def __init__(self, api_url, uname, passwd, timeout=5):
"""
:param string server: 'host:port' string denoting the location of the
broker and the port for interfacing with its REST API.
:param string api_url: The base URL for the broker API.
:param string uname: Username credential used to authenticate.
:param string passwd: Password used to authenticate w/ REST API
:param int timeout: Integer number of seconds to wait for each call.
"""

self.client = httplib2.Http(timeout=timeout)
self.client.add_credentials(uname, passwd)
self.base_url = 'http://%s/api/' % server

def decode_json_content(self, content):
"""
Returns the JSON-decoded Python representation of 'content'.
:param json content: A Python JSON object.
"""
try:
py_ct = json.loads(content)
except ValueError as out:
# If there's a 404 or other error, the response will not be JSON.
return None
except TypeError:
# in later Python 3.x versions, some calls return bytes objects.
py_ct = json.loads(content.decode())
return py_ct

def do_call(self, path, reqtype, body=None, headers=None):
self.auth = HTTPBasicAuth(uname, passwd)
self.timeout = timeout
# Need to add http:// if not specified for urlparse to work
if not api_url.startswith('http'):
api_url = 'http://%s' % api_url
self.base_url = api_url

def do_call(self, path, method, body=None, headers=None):
"""
Send an HTTP request to the REST API.
:param string path: A URL
:param string reqtype: The HTTP method (GET, POST, etc.) to use
:param string method: The HTTP method (GET, POST, etc.) to use
in the request.
:param string body: A string representing any data to be sent in the
body of the HTTP request.
Expand All @@ -98,25 +84,23 @@ def do_call(self, path, reqtype, body=None, headers=None):
"""
url = urljoin(self.base_url, path)
print(url)
try:
resp, content = self.client.request(url,
reqtype,
body,
headers)
except socket.timeout as out:
raise NetworkError("Timout while trying to connect to RabbitMQ")
except Exception as out:
# net-related exception types from httplib2 are unpredictable.
raise NetworkError("Error: %s %s" % (type(out), out))

# RabbitMQ will return content even on certain failures.
if content:
content = self.decode_json_content(content)
resp = requests.request(method, url, data=body, headers=headers,
auth=self.auth, timeout=self.timeout)
except requests.exceptions.Timeout as out:
raise NetworkError("Timeout while trying to connect to RabbitMQ")
except requests.exceptions.RequestException as err:
# All other requests exceptions inherit from RequestException
raise NetworkError("Error during request %s %s" % (type(err), err))

try:
content = resp.json()
except ValueError as out:
content = None

# 'success' HTTP status codes are 200-206
if resp.status < 200 or resp.status > 206:
raise HTTPError(content, resp.status, resp.reason, path, body)
if resp.status_code < 200 or resp.status_code > 206:
raise HTTPError(content, resp.status_code, resp.text, path, body)
else:
if content:
return content
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
nose
httplib2
requests
mock
unittest2
2 changes: 1 addition & 1 deletion requirements3.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
nose
httplib2
requests
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"Topic :: Software Development :: Libraries :: Python Modules",
],
keywords='python http amqp rabbit rabbitmq management',
install_requires = ['httplib2'],
install_requires = ['requests'],
author='Brian K. Jones',
author_email='bkjones@gmail.com',
url='http://www.github.com/bkjones/pyrabbit',
Expand Down
10 changes: 4 additions & 6 deletions tests/test_httpclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@ def test_client_init(self):
self.assertIsInstance(c, http.HTTPClient)

def test_client_init_sets_credentials(self):
domain = ''
expected_credentials = [(domain, self.testuser, self.testpass)]
self.assertEqual(
self.c.client.credentials.credentials, expected_credentials)
self.assertEqual(self.c.auth.username, self.testuser)
self.assertEqual(self.c.auth.password, self.testpass)

def test_client_init_sets_default_timeout(self):
self.assertEqual(self.c.client.timeout, 5)
self.assertEqual(self.c.timeout, 5)

def test_client_init_with_timeout(self):
c = http.HTTPClient(self.testhost, self.testuser, self.testpass, 1)
self.assertEqual(c.client.timeout, 1)
self.assertEqual(c.timeout, 1)

15 changes: 9 additions & 6 deletions tests/test_pyrabbit.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@
import unittest

import sys
import requests
sys.path.append('..')
import pyrabbit
from mock import Mock, patch

class TestClient(unittest.TestCase):
def setUp(self):
self.client = pyrabbit.api.Client('localhost:55672', 'guest', 'guest')
self.client = pyrabbit.api.Client('localhost:55672/api', 'guest', 'guest')

def tearDown(self):
del self.client

def test_server_init_200(self):
self.assertIsInstance(self.client, pyrabbit.api.Client)
self.assertEqual(self.client.host, 'localhost:55672')
self.assertEqual(self.client.api_url, 'localhost:55672/api')

def test_server_is_alive_default_vhost(self):
response = {'status': 'ok'}
Expand Down Expand Up @@ -102,13 +103,15 @@ def test_get_queue_depth_2(self):
q = {'messages': 8}
json_q = json.dumps(q)

with patch('httplib2.Response') as resp:
resp.reason = 'response reason here'
resp.status = 200
self.client.http.client.request = Mock(return_value=(resp, json_q))
with patch('requests.request') as req:
resp = requests.Response()
resp._content = json_q
resp.status_code = 200
req.return_value = resp
depth = self.client.get_queue_depth('/', 'test')
self.assertEqual(depth, q['messages'])


def test_purge_queue(self):
self.client.http.do_call = Mock(return_value=True)
self.assertTrue(self.client.purge_queue('vname', 'qname'))
Expand Down
8 changes: 4 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ envlist = py26,py27,py33,py34

[testenv:py34]
deps=
httplib2
requests

[testenv:py33]
deps=
httplib2
requests

[testenv:py27]
deps=
nose
httplib2
requests
mock
changedir=tests
commands=nosetests []

[testenv:py26]
deps=
nose
httplib2
requests
mock
unittest2
changedir=tests
Expand Down

0 comments on commit b9f7c75

Please sign in to comment.