Skip to content

Commit

Permalink
Enhancement: Support for requests-mock
Browse files Browse the repository at this point in the history
- Add support for the requests-mock which is commonly used by OpenStack and more actively maintained than httpretty or responses
  • Loading branch information
BenjamenMeyer committed Feb 26, 2015
1 parent 75178a8 commit e3a25da
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 1 deletion.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ __pycache__/
# C extensions
*.so

# Editors
*.sw?

# Distribution / packaging
.Python
env/
*env*/
.*env*/
build/
develop-eggs/
dist/
Expand Down
25 changes: 25 additions & 0 deletions stackinabox/stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ def call_into(cls, method, request, uri, headers):
uri,
headers)

@classmethod
def hold_onto(cls, name, obj):
logger.debug('Holding on {0} of type {1}'
.format(name, type(obj)))
local_store.instance.into_hold(name, obj)

@classmethod
def hold_out(cls, name):
logger.debug('Retreiving {0} from hold'
.format(name))
return local_store.instance.from_hold(name)

@classmethod
def update_uri(cls, uri):
logger.debug('Request: Update URI to {0}'.format(uri))
Expand All @@ -45,6 +57,8 @@ def __init__(self):
self.__base_url = '/'
self.services = {
}
self.holds = {
}

@staticmethod
def __get_service_url(base_url, service_name):
Expand Down Expand Up @@ -90,6 +104,7 @@ def reset(self):
service.reset()

self.services = {}
self.holds = {}

logger.debug('StackInABox({0}): Reset Complete'
.format(self.__id))
Expand Down Expand Up @@ -138,5 +153,15 @@ def call(self, method, request, uri, headers):
'Service Handler had an error: {0}'.format(ex))
return (500, headers, 'Unknown service')

def into_hold(self, name, obj):
logger.debug('StackInABox({0}): Holding onto {1} of type {2}'
.format(self.__id, name, type(obj)))
self.holds[name] = obj

def from_hold(self, name):
logger.debug('StackInABox({0}): Retreiving {1} from the hold'
.format(self.__id, name))
return self.holds[name]

local_store = threading.local()
local_store.instance = StackInABox()
36 changes: 36 additions & 0 deletions stackinabox/test/test_advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import stackinabox.util_httpretty
import stackinabox.util_responses
import stackinabox.util_requests_mock
from stackinabox.stack import StackInABox
from stackinabox.services.service import StackInABoxService

Expand Down Expand Up @@ -122,3 +123,38 @@ def tb_basic_responses():
unittest.FunctionTestCase(tb_basic_responses,
setUp=tb_responses_setup,
tearDown=tb_responses_teardown)


class TestRequestMock(unittest.TestCase):

def setUp(self):
super(TestRequestMock, self).setUp()
StackInABox.register_service(AdvancedService())
self.session = requests.Session()

def tearDown(self):
super(TestRequestMock, self).tearDown()
StackInABox.reset_services()
self.session.close()

def test_basic(self):
stackinabox.util_requests_mock.requests_mock_registration(
'localhost', self.session)

res = self.session.get('http://localhost/advanced/')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.text, 'Hello')

res = sellf.session.get('http://localhost/advanced/h')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.text, 'Good-Bye')

expected_result = {
'bob': 'bob: Good-Bye alice',
'alice': 'alice: Good-Bye bob',
'joe': 'joe: Good-Bye jane'
}
res = self.session.get('http://localhost/advanced/g?bob=alice;'
'alice=bob&joe=jane')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.json(), expected_result)
30 changes: 30 additions & 0 deletions stackinabox/test/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import stackinabox.util_httpretty
import stackinabox.util_responses
import stackinabox.util_requests_mock
from stackinabox.stack import StackInABox
from stackinabox.services.hello import HelloService

Expand All @@ -34,10 +35,39 @@ def test_basic(self):

@responses.activate
def test_basic_responses():
print('Starting Test Basic Response')
StackInABox.reset_services()
print('StackInABox Services Reset')
StackInABox.register_service(HelloService())
print('StackInABox Hello Service Registerd')
stackinabox.util_responses.responses_registration('localhost')
print('StackInABox Python Responses Configured')

res = requests.get('http://localhost/hello/')
print('Request completed. Examining results...')
assert res.status_code == 200
assert res.text == 'Hello'


class TestRequestsMock(unittest.TestCase):

def setUp(self):
super(TestRequestsMock, self).setUp()
StackInABox.register_service(HelloService())
self.session = requests.Session()

def tearDown(self):
super(TestRequestsMock, self).tearDown()
StackInABox.reset_services()
self.session.close()

def test_basic_requests_mock(self):
stackinabox.util_requests_mock.requests_mock_registration(
'localhost', self.session)

res = self.session.get('http://localhost/hello/')
self.assertEqual(res.status_code, 200)

import pdb
pdb.set_trace()
self.assertEqual(res.text, 'Hello')
154 changes: 154 additions & 0 deletions stackinabox/util_requests_mock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
"""
Stack-In-A-Box: HTTPretty Support
"""
import io
import logging
import re
import types

import requests
import requests_mock

from stackinabox.stack import StackInABox


logger = logging.getLogger(__name__)


class MockRequestFileObject(object):

def __init__(self, data_to_wrap):
self.data = data_to_wrap
self.is_str = isinstance(self.data, types.StringTypes)
self.is_bytes = isinstance(self.data, bytes)
self.is_text = self.is_str or self.is_bytes
self.is_file = isinstance(self.data, types.FileType)
self.is_generator = isinstance(self.data, types.GeneratorType)
self.is_iterable = hasattr(self.data, '__iter__')

if not (self.is_str or
self.is_bytes or
self.is_generator):
raise ValueError(
'Message body must by str, bytes, generator, iterable, '
'or file type')

def iterable_to_generator(self):
for next_chunk in self.data:
yield next_chunk

def iterable_text(self, chunk_size):
data_position = 0

while data_position < len(self.data):
end_position = data_position + chunk_size
yield self.data[data_position:end_position]

data_position = end_position

def read(self, chunk_size):
if self.is_text:
try:
next_chunk = self.iterable_text(chunk_size)
return next_chunk
except StopIteration:
return None

if self.is_file:
return self.data.read(chunk_size)

# Must be either a generator or an iterable
if self.is_generator:
try:
next_chunk = self.data.next()
return next_chunk
except StopIteration:
return None

if self.is_iterable:
try:
next_chunk = self.iterable_to_generator()
return next_chunk
except StopIteration:
return None

return None

def close(self):
self.data_position = 0

def release_conn(self):
pass

class RequestMockCallable(object):

def __init__(self, uri):
self.regex = re.compile(
'(http)?s?(://)?{0}:?(\d+)?/'.format(uri), re.I)

def __call__(self, request):

uri = request.url

if self.regex.match(uri):
return self.handle(request, uri)

else:
# We don't handle it
return None

@staticmethod
def get_reason_for_status(status_code):

if status_code in requests.status_codes.codes:
return requests.status_codes._codes[status_code][0].replace('_', ' ')
else:
return 'Unknown status code - {0}'.format(status_code)

@staticmethod
def split_status(status):
if isinstance(status, int):
return (status, RequestMockCallable.get_reason_for_status(
status))

elif isinstance(status, str) or isinstance(status, bytes):
code, reason = status.split(' ', 1)
return (code, reason)

else:
return (status, 'Unknown')


def handle(self, request, uri):
method = request.method
headers = request.headers
stackinabox_result = StackInABox.call_into(method,
request,
uri,
headers)
status_code, output_headers, body = stackinabox_result

response = requests.Response()
response.url = request.url
response.headers['server'] = 'StackInABox/Requests-Mock'
response.headers.update(output_headers)
response.status_code, response.reason =\
RequestMockCallable.split_status(status_code)


if body is not None:
response.raw = MockRequestFileObject(body)

return response


def requests_mock_registration(uri, session):
logger.debug('Registering Stack-In-A-Box at {0} under Python Requests-Mock'
.format(uri))

StackInABox.update_uri(uri)
StackInABox.hold_onto('adapter', requests_mock.Adapter())
StackInABox.hold_out('adapter').add_matcher(RequestMockCallable(uri))

session.mount('http://{0}'.format(uri), StackInABox.hold_out('adapter'))
session.mount('https://{0}'.format(uri), StackInABox.hold_out('adapter'))
1 change: 1 addition & 0 deletions tools/test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ nose
requests
httpretty
responses
requests-mock

0 comments on commit e3a25da

Please sign in to comment.