Allow doing real_http per mock via the mocker
If you set up requests_mock to catch all requests (which I would recommend) you sometimes get caught with things like file:// paths that should be allowed to reach the filesystem. To do this we should allow you to add a matcher that says a specific route can bypass the catch all and do a real request. This is a bit of a layer violation but I thought it was easy to start with, then realized why it wasn't. Change-Id: Ic2516f78413b88a489c8d6bd2bc39b8ebb5bf273 Closes-Bug: #1501665
This commit is contained in:
parent
b3de408600
commit
b2026313e3
@ -132,3 +132,18 @@ The Mocker object takes the following parameters:
|
||||
...
|
||||
'resp'
|
||||
200
|
||||
|
||||
*New in 1.1*
|
||||
|
||||
Similarly when using a mocker you can register an individual URI to bypass the mocking infrastructure and make a real request. Note this only works when using the mocker and not when directly mounting an adapter.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> with requests_mock.Mocker() as m:
|
||||
... m.register_uri('GET', 'http://test.com', text='resp')
|
||||
... m.register_uri('GET', 'http://www.google.com', real_http=True)
|
||||
... print(requests.get('http://test.com').text)
|
||||
... print(requests.get('http://www.google.com').status_code) # doctest: +SKIP
|
||||
...
|
||||
'resp'
|
||||
200
|
||||
|
@ -151,10 +151,21 @@ class _RequestHistoryTracker(object):
|
||||
return len(self.request_history)
|
||||
|
||||
|
||||
class _RunRealHTTP(Exception):
|
||||
"""A fake exception to jump out of mocking and allow a real request.
|
||||
|
||||
This exception is caught at the mocker level and allows it to execute this
|
||||
request through the real requests mechanism rather than the mocker.
|
||||
|
||||
It should never be exposed to a user.
|
||||
"""
|
||||
|
||||
|
||||
class _Matcher(_RequestHistoryTracker):
|
||||
"""Contains all the information about a provided URL to match."""
|
||||
|
||||
def __init__(self, method, url, responses, complete_qs, request_headers):
|
||||
def __init__(self, method, url, responses, complete_qs, request_headers,
|
||||
real_http):
|
||||
"""
|
||||
:param bool complete_qs: Match the entire query string. By default URLs
|
||||
match if all the provided matcher query arguments are matched and
|
||||
@ -162,6 +173,7 @@ class _Matcher(_RequestHistoryTracker):
|
||||
require that the entire query string needs to match.
|
||||
"""
|
||||
super(_Matcher, self).__init__()
|
||||
|
||||
self._method = method
|
||||
self._url = url
|
||||
try:
|
||||
@ -171,6 +183,7 @@ class _Matcher(_RequestHistoryTracker):
|
||||
self._responses = responses
|
||||
self._complete_qs = complete_qs
|
||||
self._request_headers = request_headers
|
||||
self._real_http = real_http
|
||||
|
||||
def _match_method(self, request):
|
||||
if self._method is ANY:
|
||||
@ -248,6 +261,11 @@ class _Matcher(_RequestHistoryTracker):
|
||||
if not self._match(request):
|
||||
return None
|
||||
|
||||
# doing this before _add_to_history means real requests are not stored
|
||||
# in the request history. I'm not sure what is better here.
|
||||
if self._real_http:
|
||||
raise _RunRealHTTP()
|
||||
|
||||
if len(self._responses) > 1:
|
||||
response_matcher = self._responses.pop(0)
|
||||
else:
|
||||
@ -294,19 +312,24 @@ class Adapter(BaseAdapter, _RequestHistoryTracker):
|
||||
"""
|
||||
complete_qs = kwargs.pop('complete_qs', False)
|
||||
request_headers = kwargs.pop('request_headers', {})
|
||||
real_http = kwargs.pop('_real_http', False)
|
||||
|
||||
if response_list and kwargs:
|
||||
raise RuntimeError('You should specify either a list of '
|
||||
'responses OR response kwargs. Not both.')
|
||||
elif real_http and (response_list or kwargs):
|
||||
raise RuntimeError('You should specify either response data '
|
||||
'OR real_http. Not both.')
|
||||
elif not response_list:
|
||||
response_list = [kwargs]
|
||||
response_list = [] if real_http else [kwargs]
|
||||
|
||||
responses = [response._MatcherResponse(**k) for k in response_list]
|
||||
matcher = _Matcher(method,
|
||||
url,
|
||||
responses,
|
||||
complete_qs=complete_qs,
|
||||
request_headers=request_headers)
|
||||
request_headers=request_headers,
|
||||
real_http=real_http)
|
||||
self.add_matcher(matcher)
|
||||
return matcher
|
||||
|
||||
|
@ -34,7 +34,6 @@ class MockerCore(object):
|
||||
"""
|
||||
|
||||
_PROXY_FUNCS = set(['last_request',
|
||||
'register_uri',
|
||||
'add_matcher',
|
||||
'request_history',
|
||||
'called',
|
||||
@ -70,6 +69,10 @@ class MockerCore(object):
|
||||
except exceptions.NoMockAddress:
|
||||
if not self._real_http:
|
||||
raise
|
||||
except adapter._RunRealHTTP:
|
||||
# this mocker wants you to run the request through the real
|
||||
# requests library rather than the mocking. Let it.
|
||||
pass
|
||||
finally:
|
||||
requests.Session.get_adapter = real_get_adapter
|
||||
|
||||
@ -95,6 +98,12 @@ class MockerCore(object):
|
||||
|
||||
raise AttributeError(name)
|
||||
|
||||
def register_uri(self, *args, **kwargs):
|
||||
# you can pass real_http here, but it's private to pass direct to the
|
||||
# adapter, because if you pass direct to the adapter you'll see the exc
|
||||
kwargs['_real_http'] = kwargs.pop('real_http', False)
|
||||
return self._adapter.register_uri(*args, **kwargs)
|
||||
|
||||
def request(self, *args, **kwargs):
|
||||
return self.register_uri(*args, **kwargs)
|
||||
|
||||
|
@ -27,12 +27,14 @@ class TestMatcher(base.TestCase):
|
||||
request_method='GET',
|
||||
complete_qs=False,
|
||||
headers=None,
|
||||
request_headers={}):
|
||||
request_headers={},
|
||||
real_http=False):
|
||||
matcher = adapter._Matcher(matcher_method,
|
||||
target,
|
||||
[],
|
||||
complete_qs,
|
||||
request_headers)
|
||||
request_headers,
|
||||
real_http=real_http)
|
||||
request = adapter._RequestObjectProxy._create(request_method,
|
||||
url,
|
||||
headers)
|
||||
|
@ -15,6 +15,7 @@ import requests
|
||||
|
||||
import requests_mock
|
||||
from requests_mock import compat
|
||||
from requests_mock import exceptions
|
||||
from requests_mock.tests import base
|
||||
|
||||
original_send = requests.Session.send
|
||||
@ -243,3 +244,38 @@ class MockerHttpMethodsTests(base.TestCase):
|
||||
mock_obj = m.delete(self.URL, text=self.TEXT)
|
||||
self.assertResponse(requests.delete(self.URL))
|
||||
self.assertTrue(mock_obj.called)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_mocker_real_http_and_responses(self, m):
|
||||
self.assertRaises(RuntimeError,
|
||||
m.get,
|
||||
self.URL,
|
||||
text='abcd',
|
||||
real_http=True)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_mocker_real_http(self, m):
|
||||
data = 'testdata'
|
||||
|
||||
uri1 = 'fake://example.com/foo'
|
||||
uri2 = 'fake://example.com/bar'
|
||||
uri3 = 'fake://example.com/baz'
|
||||
|
||||
m.get(uri1, text=data)
|
||||
m.get(uri2, real_http=True)
|
||||
|
||||
self.assertEqual(data, requests.get(uri1).text)
|
||||
|
||||
# This should fail because requests can't get an adapter for mock://
|
||||
# but it shows that it has tried and would have made a request.
|
||||
self.assertRaises(requests.exceptions.InvalidSchema,
|
||||
requests.get,
|
||||
uri2)
|
||||
|
||||
# This fails because real_http is not set on the mocker
|
||||
self.assertRaises(exceptions.NoMockAddress,
|
||||
requests.get,
|
||||
uri3)
|
||||
|
||||
# do it again to make sure the mock is still in place
|
||||
self.assertEqual(data, requests.get(uri1).text)
|
||||
|
Loading…
Reference in New Issue
Block a user