responses-0.3.0/0000755€+~Oq€2›s®0000000000012416746520017355 5ustar cramerDROPBOX\Domain Users00000000000000responses-0.3.0/PKG-INFO0000644€+~Oq€2›s®0000001206512416746520020456 0ustar cramerDROPBOX\Domain Users00000000000000Metadata-Version: 1.1 Name: responses Version: 0.3.0 Summary: A utility library for mocking out the `requests` Python library. Home-page: UNKNOWN Author: David Cramer Author-email: UNKNOWN License: UNKNOWN Description: Responses ========= .. image:: https://travis-ci.org/dropbox/responses.png?branch=master :target: https://travis-ci.org/dropbox/responses A utility library for mocking out the `requests` Python library. Response body as string ----------------------- .. code-block:: python import responses import requests @responses.activate def test_my_api(): responses.add(responses.GET, 'http://twitter.com/api/1/foobar', body='{"error": "not found"}', status=404, content_type='application/json') resp = requests.get('http://twitter.com/api/1/foobar') assert resp.json() == {"error": "not found"} assert len(responses.calls) == 1 assert responses.calls[0].request.url == 'http://twitter.com/api/1/foobar' assert responses.calls[0].response.text == '{"error": "not found"}' Request callback ---------------- .. code-block:: python import json import responses import requests @responses.activate def test_calc_api(): def request_callback(request): payload = json.loads(request.body) resp_body = {'value': sum(payload['numbers'])} headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'} return (200, headers, json.dumps(resp_body)) responses.add_callback( responses.GET, 'http://calc.com/sum', callback=request_callback, content_type='application/json', ) resp = requests.post( 'http://calc.com/sum', json.dumps({'numbers': [1, 2, 3]}), headers={'content-type': 'application/json'}, ) assert resp.json() == {'value': 6} assert len(responses.calls) == 1 assert responses.calls[0].request.url == 'http://calc.com/sum' assert responses.calls[0].response.text == '{"value": 6}' assert ( responses.calls[0].response.headers['request-id'] == '728d329e-0e86-11e4-a748-0c84dc037c13' ) Instead of passing a string URL into `responses.add` or `responses.add_callback` you can also supply a compiled regular expression. .. code-block:: python import re import responses import requests # Instead of responses.add(responses.GET, 'http://twitter.com/api/1/foobar', body='{"error": "not found"}', status=404, content_type='application/json') # You can do the following url_re = re.compile(r'https?://twitter.com/api/\d+/foobar') responses.add(responses.GET, url_re, body='{"error": "not found"}', status=404, content_type='application/json') A response can also throw an exception as follows. .. code-block:: python import responses import requests from requests.exceptions import HTTPError exception = HTTPError('Something went wrong') responses.add(responses.GET, 'http://twitter.com/api/1/foobar', body=exception) # All calls to 'http://twitter.com/api/1/foobar' will throw exception. .. note:: Responses requires Requests >= 1.0 License ======= :: Copyright 2013 Dropbox, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development responses-0.3.0/README.rst0000644€+~Oq€2›s®0000000723312416743045021050 0ustar cramerDROPBOX\Domain Users00000000000000Responses ========= .. image:: https://travis-ci.org/dropbox/responses.png?branch=master :target: https://travis-ci.org/dropbox/responses A utility library for mocking out the `requests` Python library. Response body as string ----------------------- .. code-block:: python import responses import requests @responses.activate def test_my_api(): responses.add(responses.GET, 'http://twitter.com/api/1/foobar', body='{"error": "not found"}', status=404, content_type='application/json') resp = requests.get('http://twitter.com/api/1/foobar') assert resp.json() == {"error": "not found"} assert len(responses.calls) == 1 assert responses.calls[0].request.url == 'http://twitter.com/api/1/foobar' assert responses.calls[0].response.text == '{"error": "not found"}' Request callback ---------------- .. code-block:: python import json import responses import requests @responses.activate def test_calc_api(): def request_callback(request): payload = json.loads(request.body) resp_body = {'value': sum(payload['numbers'])} headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'} return (200, headers, json.dumps(resp_body)) responses.add_callback( responses.GET, 'http://calc.com/sum', callback=request_callback, content_type='application/json', ) resp = requests.post( 'http://calc.com/sum', json.dumps({'numbers': [1, 2, 3]}), headers={'content-type': 'application/json'}, ) assert resp.json() == {'value': 6} assert len(responses.calls) == 1 assert responses.calls[0].request.url == 'http://calc.com/sum' assert responses.calls[0].response.text == '{"value": 6}' assert ( responses.calls[0].response.headers['request-id'] == '728d329e-0e86-11e4-a748-0c84dc037c13' ) Instead of passing a string URL into `responses.add` or `responses.add_callback` you can also supply a compiled regular expression. .. code-block:: python import re import responses import requests # Instead of responses.add(responses.GET, 'http://twitter.com/api/1/foobar', body='{"error": "not found"}', status=404, content_type='application/json') # You can do the following url_re = re.compile(r'https?://twitter.com/api/\d+/foobar') responses.add(responses.GET, url_re, body='{"error": "not found"}', status=404, content_type='application/json') A response can also throw an exception as follows. .. code-block:: python import responses import requests from requests.exceptions import HTTPError exception = HTTPError('Something went wrong') responses.add(responses.GET, 'http://twitter.com/api/1/foobar', body=exception) # All calls to 'http://twitter.com/api/1/foobar' will throw exception. .. note:: Responses requires Requests >= 1.0 License ======= :: Copyright 2013 Dropbox, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. responses-0.3.0/responses.egg-info/0000755€+~Oq€2›s®0000000000012416746520023070 5ustar cramerDROPBOX\Domain Users00000000000000responses-0.3.0/responses.egg-info/dependency_links.txt0000644€+~Oq€2›s®0000000000112416746520027136 0ustar cramerDROPBOX\Domain Users00000000000000 responses-0.3.0/responses.egg-info/not-zip-safe0000644€+~Oq€2›s®0000000000112241516213025304 0ustar cramerDROPBOX\Domain Users00000000000000 responses-0.3.0/responses.egg-info/PKG-INFO0000644€+~Oq€2›s®0000001206512416746520024171 0ustar cramerDROPBOX\Domain Users00000000000000Metadata-Version: 1.1 Name: responses Version: 0.3.0 Summary: A utility library for mocking out the `requests` Python library. Home-page: UNKNOWN Author: David Cramer Author-email: UNKNOWN License: UNKNOWN Description: Responses ========= .. image:: https://travis-ci.org/dropbox/responses.png?branch=master :target: https://travis-ci.org/dropbox/responses A utility library for mocking out the `requests` Python library. Response body as string ----------------------- .. code-block:: python import responses import requests @responses.activate def test_my_api(): responses.add(responses.GET, 'http://twitter.com/api/1/foobar', body='{"error": "not found"}', status=404, content_type='application/json') resp = requests.get('http://twitter.com/api/1/foobar') assert resp.json() == {"error": "not found"} assert len(responses.calls) == 1 assert responses.calls[0].request.url == 'http://twitter.com/api/1/foobar' assert responses.calls[0].response.text == '{"error": "not found"}' Request callback ---------------- .. code-block:: python import json import responses import requests @responses.activate def test_calc_api(): def request_callback(request): payload = json.loads(request.body) resp_body = {'value': sum(payload['numbers'])} headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'} return (200, headers, json.dumps(resp_body)) responses.add_callback( responses.GET, 'http://calc.com/sum', callback=request_callback, content_type='application/json', ) resp = requests.post( 'http://calc.com/sum', json.dumps({'numbers': [1, 2, 3]}), headers={'content-type': 'application/json'}, ) assert resp.json() == {'value': 6} assert len(responses.calls) == 1 assert responses.calls[0].request.url == 'http://calc.com/sum' assert responses.calls[0].response.text == '{"value": 6}' assert ( responses.calls[0].response.headers['request-id'] == '728d329e-0e86-11e4-a748-0c84dc037c13' ) Instead of passing a string URL into `responses.add` or `responses.add_callback` you can also supply a compiled regular expression. .. code-block:: python import re import responses import requests # Instead of responses.add(responses.GET, 'http://twitter.com/api/1/foobar', body='{"error": "not found"}', status=404, content_type='application/json') # You can do the following url_re = re.compile(r'https?://twitter.com/api/\d+/foobar') responses.add(responses.GET, url_re, body='{"error": "not found"}', status=404, content_type='application/json') A response can also throw an exception as follows. .. code-block:: python import responses import requests from requests.exceptions import HTTPError exception = HTTPError('Something went wrong') responses.add(responses.GET, 'http://twitter.com/api/1/foobar', body=exception) # All calls to 'http://twitter.com/api/1/foobar' will throw exception. .. note:: Responses requires Requests >= 1.0 License ======= :: Copyright 2013 Dropbox, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development responses-0.3.0/responses.egg-info/requires.txt0000644€+~Oq€2›s®0000000006312416746520025467 0ustar cramerDROPBOX\Domain Users00000000000000requests mock six [tests] pytest pytest-cov flake8responses-0.3.0/responses.egg-info/SOURCES.txt0000644€+~Oq€2›s®0000000035612416746520024760 0ustar cramerDROPBOX\Domain Users00000000000000README.rst responses.py setup.cfg setup.py responses.egg-info/PKG-INFO responses.egg-info/SOURCES.txt responses.egg-info/dependency_links.txt responses.egg-info/not-zip-safe responses.egg-info/requires.txt responses.egg-info/top_level.txtresponses-0.3.0/responses.egg-info/top_level.txt0000644€+~Oq€2›s®0000000001212416746520025613 0ustar cramerDROPBOX\Domain Users00000000000000responses responses-0.3.0/responses.py0000644€+~Oq€2›s®0000001724112416746477021770 0ustar cramerDROPBOX\Domain Users00000000000000""" Copyright 2013 Dropbox, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from __future__ import ( absolute_import, print_function, division, unicode_literals ) import re import six if six.PY2: try: from six import cStringIO as BufferIO except ImportError: from six import StringIO as BufferIO else: from io import BytesIO as BufferIO import inspect from collections import namedtuple, Sequence, Sized from functools import update_wrapper as _update_wrapper from requests.exceptions import ConnectionError try: from requests.packages.urllib3.response import HTTPResponse except ImportError: from urllib3.response import HTTPResponse if six.PY2: from urlparse import urlparse, parse_qsl else: from urllib.parse import urlparse, parse_qsl Call = namedtuple('Call', ['request', 'response']) _wrapper_template = """\ def _wrapper_(%(signature)s): return %(tgt_func)s(%(signature)s) """ def update_wrapper(wrapper, wrapped): """Preserves the argspec for a wrapped function so that testing tools such as pytest can continue to use their fixture injection. :param wrapper: the wrapper function to update :param wrapped: the decorated test function """ newargspec = inspect.getargspec(wrapped) need_self = len(newargspec[0]) > 0 and newargspec[0][0] == 'self' if need_self: newargspec = (newargspec[0],) + newargspec[1:] signature = inspect.formatargspec(*newargspec)[1:-1] ctx = {'signature': signature, 'tgt_func': 'tgt_func'} evaldict = {'tgt_func': wrapper} six.exec_(_wrapper_template % ctx, evaldict) wrapper = evaldict['_wrapper_'] if hasattr(wrapped, 'func_defaults'): wrapper.func_defaults = wrapped.func_defaults _update_wrapper(wrapper, wrapped) return wrapper class CallList(Sequence, Sized): def __init__(self): self._calls = [] def __iter__(self): return iter(self._calls) def __len__(self): return len(self._calls) def __getitem__(self, idx): return self._calls[idx] def add(self, request, response): self._calls.append(Call(request, response)) def reset(self): self._calls = [] class RequestsMock(object): DELETE = 'DELETE' GET = 'GET' HEAD = 'HEAD' OPTIONS = 'OPTIONS' PATCH = 'PATCH' POST = 'POST' PUT = 'PUT' def __init__(self): self._calls = CallList() self.reset() def reset(self): self._urls = [] self._calls.reset() def add(self, method, url, body='', match_querystring=False, status=200, adding_headers=None, stream=False, content_type='text/plain'): # ensure the url has a default path set if the url is a string if self._is_string(url) and url.count('/') == 2: url = url.replace('?', '/?', 1) if match_querystring \ else url + '/' # body must be bytes if isinstance(body, six.text_type): body = body.encode('utf-8') self._urls.append({ 'url': url, 'method': method, 'body': body, 'content_type': content_type, 'match_querystring': match_querystring, 'status': status, 'adding_headers': adding_headers, 'stream': stream, }) def add_callback(self, method, url, callback, match_querystring=False, content_type='text/plain'): self._urls.append({ 'url': url, 'method': method, 'callback': callback, 'content_type': content_type, 'match_querystring': match_querystring, }) @property def calls(self): return self._calls def __enter__(self): self.start() def __exit__(self, *args): self.stop() self.reset() def activate(self, func): def wrapped(*args, **kwargs): with self: return func(*args, **kwargs) return update_wrapper(wrapped, func) def _find_match(self, request): for match in self._urls: if request.method != match['method']: continue if not self._has_url_match(match, request.url): continue return match return None def _has_url_match(self, match, request_url): url = match['url'] if self._is_string(url): if match['match_querystring']: return self._has_strict_url_match(url, request_url) else: url_without_qs = request_url.split('?', 1)[0] return url == url_without_qs elif isinstance(url, re._pattern_type) and url.match(request_url): return True else: return False def _has_strict_url_match(self, url, other): url_parsed = urlparse(url) other_parsed = urlparse(other) if url_parsed[:3] != other_parsed[:3]: return False url_qsl = sorted(parse_qsl(url_parsed.query)) other_qsl = sorted(parse_qsl(other_parsed.query)) return url_qsl == other_qsl def _is_string(self, s): return isinstance(s, (six.string_types, six.text_type)) def _on_request(self, session, request, **kwargs): match = self._find_match(request) # TODO(dcramer): find the correct class for this if match is None: error_msg = 'Connection refused: {0}'.format(request.url) response = ConnectionError(error_msg) self._calls.add(request, response) raise response if 'body' in match and isinstance(match['body'], Exception): self._calls.add(request, match['body']) raise match['body'] headers = { 'Content-Type': match['content_type'], } if 'callback' in match: # use callback status, r_headers, body = match['callback'](request) if isinstance(body, six.text_type): body = body.encode('utf-8') body = BufferIO(body) headers.update(r_headers) elif 'body' in match: if match['adding_headers']: headers.update(match['adding_headers']) status = match['status'] body = BufferIO(match['body']) response = HTTPResponse( status=status, body=body, headers=headers, preload_content=False, ) adapter = session.get_adapter(request.url) response = adapter.build_response(request, response) if not match.get('stream'): response.content # NOQA self._calls.add(request, response) return response def start(self): import mock def unbound_on_send(session, requests, *a, **kwargs): return self._on_request(session, requests, *a, **kwargs) self._patcher = mock.patch('requests.Session.send', unbound_on_send) self._patcher.start() def stop(self): self._patcher.stop() # expose default mock namespace mock = _default_mock = RequestsMock() __all__ = [] for __attr in (a for a in dir(_default_mock) if not a.startswith('_')): __all__.append(__attr) globals()[__attr] = getattr(_default_mock, __attr) responses-0.3.0/setup.cfg0000644€+~Oq€2›s®0000000013212416746520021172 0ustar cramerDROPBOX\Domain Users00000000000000[pytest] addopts = --tb=short [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 responses-0.3.0/setup.py0000644€+~Oq€2›s®0000000277612416744246021106 0ustar cramerDROPBOX\Domain Users00000000000000#!/usr/bin/env python """ responses ========= A utility library for mocking out the `requests` Python library. :copyright: (c) 2013 Dropbox, Inc. """ from setuptools import setup from setuptools.command.test import test as TestCommand import sys setup_requires = [] if 'test' in sys.argv: setup_requires.append('pytest') install_requires = [ 'requests', 'mock', 'six', ] tests_require = [ 'pytest', 'pytest-cov', 'flake8', ] class PyTest(TestCommand): def finalize_options(self): TestCommand.finalize_options(self) self.test_args = ['test_responses.py'] self.test_suite = True def run_tests(self): # import here, cause outside the eggs aren't loaded import pytest errno = pytest.main(self.test_args) sys.exit(errno) setup( name='responses', version='0.3.0', author='David Cramer', description=( 'A utility library for mocking out the `requests` Python library.' ), long_description=open('README.rst').read(), py_modules=['responses'], zip_safe=False, install_requires=install_requires, extras_require={ 'tests': tests_require, }, tests_require=tests_require, setup_requires=setup_requires, cmdclass={'test': PyTest}, include_package_data=True, classifiers=[ 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Operating System :: OS Independent', 'Topic :: Software Development' ], )