New upstream version 0.12.1
Dmitry Shachnev
3 years ago
0 | 0.12.1 | |
1 | ------ | |
2 | ||
3 | * `responses.urlencoded_params_matcher` and `responses.json_params_matcher` now | |
4 | accept None to match empty requests. | |
5 | * Fixed imports to work with new `urllib3` versions. | |
6 | * `request.params` now allows parameters to have multiple values for the same key. | |
7 | * Improved ConnectionError messages. | |
8 | ||
9 | 0.12.0 | |
10 | ------ | |
11 | ||
12 | - Remove support for Python 3.4. | |
13 | ||
14 | 0.11.0 | |
15 | ------ | |
16 | ||
17 | - Added the `match` parameter to `add()`. | |
18 | - Added `responses.urlencoded_params_matcher()` and `responses.json_params_matcher()`. | |
19 | ||
20 | 0.10.16 | |
21 | ------- | |
22 | ||
23 | - Add a requirements pin to urllib3. This helps prevent broken install states where | |
24 | cookie usage fails. | |
25 | ||
26 | 0.10.15 | |
27 | ------- | |
28 | ||
29 | - Added `assert_call_count` to improve ergonomics around ensuring a mock was called. | |
30 | - Fix incorrect handling of paths with query strings. | |
31 | - Add Python 3.9 support to CI matrix. | |
32 | ||
33 | 0.10.14 | |
34 | ------- | |
35 | ||
36 | - Retag of 0.10.13 | |
37 | ||
38 | 0.10.13 | |
39 | ------- | |
40 | ||
41 | - Improved README examples. | |
42 | - Improved handling of unicode bodies. The inferred content-type for unicode | |
43 | bodies is now `text/plain; charset=utf-8`. | |
44 | - Streamlined querysting matching code. | |
45 | ||
46 | 0.10.12 | |
47 | ------- | |
48 | ||
49 | - Fixed incorrect content-type in `add_callback()` when headers are provided as a list of tuples. | |
50 | ||
51 | 0.10.11 | |
52 | ------- | |
53 | ||
54 | - Fixed invalid README formatted. | |
55 | - Fixed string formatting in error message. | |
56 | ||
57 | 0.10.10 | |
58 | ------ | |
59 | ||
60 | - Added Python 3.8 support | |
61 | - Remove Python 3.4 from test suite matrix. | |
62 | - The `response.request` object now has a `params` attribute that contains the query string parameters from the request that was captured. | |
63 | - `add_passthru` now supports `re` pattern objects to match URLs. | |
64 | - ConnectionErrors raised by responses now include more details on the request that was attempted and the mocks registered. | |
65 | ||
66 | 0.10.9 | |
67 | ------ | |
68 | ||
69 | - Fixed regression with `add_callback()` and content-type header. | |
70 | - Fixed implicit dependency on urllib3>1.23.0 | |
71 | ||
72 | 0.10.8 | |
73 | ------ | |
74 | ||
75 | - Fixed cookie parsing and enabled multiple cookies to be set by using a list of | |
76 | tuple values. | |
77 | ||
78 | 0.10.7 | |
79 | ------ | |
80 | ||
81 | - Added pypi badges to README. | |
82 | - Fixed formatting issues in README. | |
83 | - Quoted cookie values are returned correctly now. | |
84 | - Improved compatibility for pytest 5 | |
85 | - Module level method names are no longer generated dynamically improving IDE navigation. | |
86 | ||
87 | 0.10.6 | |
88 | ------ | |
89 | ||
90 | - Improved documentation. | |
91 | - Improved installation requirements for py3 | |
92 | - ConnectionError's raised by responses now indicate which request | |
93 | path/method failed to match a mock. | |
94 | - `test_responses.py` is no longer part of the installation targets. | |
95 | ||
96 | 0.10.5 | |
97 | ------ | |
98 | ||
99 | - Improved support for raising exceptions from callback mocks. If a mock | |
100 | callback returns an exception object that exception will be raised. | |
101 | ||
102 | 0.10.4 | |
103 | ------ | |
104 | ||
105 | - Fixed generated wrapper when using `@responses.activate` in Python 3.6+ | |
106 | when decorated functions use parameter and/or return annotations. | |
107 | ||
108 | 0.10.3 | |
109 | ------ | |
110 | ||
111 | - Fixed deprecation warnings in python 3.7 for inspect module usage. | |
112 | ||
113 | 0.10.2 | |
114 | ------ | |
115 | ||
116 | - Fixed build setup to use undeprecated `pytest` bin stub. | |
117 | - Updated `tox` configuration. | |
118 | - Added example of using responses with `pytest.fixture` | |
119 | - Removed dependency on `biscuits` in py3. Instead `http.cookies` is being used. | |
120 | ||
121 | 0.10.1 | |
122 | ------ | |
123 | ||
124 | - Packaging fix to distribute wheel (#219) | |
125 | ||
126 | 0.10.0 | |
127 | ------ | |
128 | ||
129 | - Fix passing through extra settings (#207) | |
130 | - Fix collections.abc warning on Python 3.7 (#215) | |
131 | - Use 'biscuits' library instead of 'cookies' on Python 3.4+ (#218) | |
132 | ||
0 | 133 | 0.9.0 |
1 | 134 | ----- |
2 | 135 |
0 | 0 | Metadata-Version: 2.1 |
1 | 1 | Name: responses |
2 | Version: 0.9.0 | |
2 | Version: 0.12.1 | |
3 | 3 | Summary: A utility library for mocking out the `requests` Python library. |
4 | 4 | Home-page: https://github.com/getsentry/responses |
5 | 5 | Author: David Cramer |
7 | 7 | Description: Responses |
8 | 8 | ========= |
9 | 9 | |
10 | .. image:: https://travis-ci.org/getsentry/responses.svg?branch=master | |
11 | :target: https://travis-ci.org/getsentry/responses | |
12 | ||
13 | A utility library for mocking out the `requests` Python library. | |
14 | ||
15 | .. note:: Responses requires Python 2.7 or newer, and requests >= 2.0 | |
10 | .. image:: https://img.shields.io/pypi/v/responses.svg | |
11 | :target: https://pypi.python.org/pypi/responses/ | |
12 | ||
13 | .. image:: https://travis-ci.org/getsentry/responses.svg?branch=master | |
14 | :target: https://travis-ci.org/getsentry/responses | |
15 | ||
16 | .. image:: https://img.shields.io/pypi/pyversions/responses.svg | |
17 | :target: https://pypi.org/project/responses/ | |
18 | ||
19 | A utility library for mocking out the ``requests`` Python library. | |
20 | ||
21 | .. note:: | |
22 | ||
23 | Responses requires Python 2.7 or newer, and requests >= 2.0 | |
24 | ||
16 | 25 | |
17 | 26 | Installing |
18 | 27 | ---------- |
28 | ||
19 | 29 | ``pip install responses`` |
30 | ||
20 | 31 | |
21 | 32 | Basics |
22 | 33 | ------ |
23 | 34 | |
24 | 35 | The core of ``responses`` comes from registering mock responses: |
25 | 36 | |
26 | .. code-block:: python | |
27 | ||
28 | import responses | |
29 | import requests | |
37 | .. code-block:: python | |
38 | ||
39 | import responses | |
40 | import requests | |
30 | 41 | |
31 | 42 | @responses.activate |
32 | 43 | def test_simple(): |
44 | 55 | If you attempt to fetch a url which doesn't hit a match, ``responses`` will raise |
45 | 56 | a ``ConnectionError``: |
46 | 57 | |
47 | .. code-block:: python | |
48 | ||
49 | import responses | |
50 | import requests | |
58 | .. code-block:: python | |
59 | ||
60 | import responses | |
61 | import requests | |
51 | 62 | |
52 | 63 | from requests.exceptions import ConnectionError |
53 | 64 | |
58 | 69 | |
59 | 70 | Lastly, you can pass an ``Exception`` as the body to trigger an error on the request: |
60 | 71 | |
61 | .. code-block:: python | |
62 | ||
63 | import responses | |
64 | import requests | |
72 | .. code-block:: python | |
73 | ||
74 | import responses | |
75 | import requests | |
65 | 76 | |
66 | 77 | @responses.activate |
67 | 78 | def test_simple(): |
70 | 81 | with pytest.raises(Exception): |
71 | 82 | requests.get('http://twitter.com/api/1/foobar') |
72 | 83 | |
84 | ||
73 | 85 | Response Parameters |
74 | 86 | ------------------- |
75 | 87 | |
76 | 88 | Responses are automatically registered via params on ``add``, but can also be |
77 | 89 | passed directly: |
78 | 90 | |
79 | .. code-block:: python | |
91 | .. code-block:: python | |
80 | 92 | |
81 | 93 | import responses |
82 | 94 | |
84 | 96 | responses.Response( |
85 | 97 | method='GET', |
86 | 98 | url='http://example.com', |
87 | ), | |
99 | ) | |
88 | 100 | ) |
89 | 101 | |
90 | 102 | The following attributes can be passed to a Response mock: |
91 | 103 | |
92 | 104 | method (``str``) |
93 | The HTTP method (GET, POST, etc). | |
105 | The HTTP method (GET, POST, etc). | |
94 | 106 | |
95 | 107 | url (``str`` or compiled regular expression) |
96 | The full resource URL. | |
108 | The full resource URL. | |
97 | 109 | |
98 | 110 | match_querystring (``bool``) |
99 | Disabled by default. Include the query string when matching requests. | |
111 | Include the query string when matching requests. | |
112 | Enabled by default if the response URL contains a query string, | |
113 | disabled if it doesn't or the URL is a regular expression. | |
100 | 114 | |
101 | 115 | body (``str`` or ``BufferedReader``) |
102 | The response body. | |
116 | The response body. | |
103 | 117 | |
104 | 118 | json |
105 | A python object representing the JSON response body. Automatically configures | |
106 | the appropriate Content-Type. | |
119 | A Python object representing the JSON response body. Automatically configures | |
120 | the appropriate Content-Type. | |
107 | 121 | |
108 | 122 | status (``int``) |
109 | The HTTP status code. | |
123 | The HTTP status code. | |
110 | 124 | |
111 | 125 | content_type (``content_type``) |
112 | Defaults to ``text/plain``. | |
126 | Defaults to ``text/plain``. | |
113 | 127 | |
114 | 128 | headers (``dict``) |
115 | Response headers. | |
129 | Response headers. | |
116 | 130 | |
117 | 131 | stream (``bool``) |
118 | Disabled by default. Indicates the response should use the streaming API. | |
119 | ||
120 | ||
121 | ||
132 | Disabled by default. Indicates the response should use the streaming API. | |
133 | ||
134 | match (``list``) | |
135 | A list of callbacks to match requests based on request body contents. | |
136 | ||
137 | ||
138 | Matching Request Parameters | |
139 | --------------------------- | |
140 | ||
141 | When adding responses for endpoints that are sent request data you can add | |
142 | matchers to ensure your code is sending the right parameters and provide | |
143 | different responses based on the request body contents. Responses provides | |
144 | matchers for JSON and URLencoded request bodies and you can supply your own for | |
145 | other formats. | |
146 | ||
147 | .. code-block:: python | |
148 | ||
149 | import responses | |
150 | import requests | |
151 | ||
152 | @responses.activate | |
153 | def test_calc_api(): | |
154 | responses.add( | |
155 | responses.POST, | |
156 | url='http://calc.com/sum', | |
157 | body=4, | |
158 | match=[ | |
159 | responses.urlencoded_params_matcher({"left": 1, "right": 3}) | |
160 | ] | |
161 | ) | |
162 | requests.post("http://calc.com/sum", data={"left": 1, "right": 3}) | |
163 | ||
164 | Matching JSON encoded data can be done with ``responses.json_params_matcher()``. | |
165 | If your application uses other encodings you can build your own matcher that | |
166 | returns ``True`` or ``False`` if the request parameters match. Your matcher can | |
167 | expect a ``request_body`` parameter to be provided by responses. | |
122 | 168 | |
123 | 169 | Dynamic Responses |
124 | 170 | ----------------- |
126 | 172 | You can utilize callbacks to provide dynamic responses. The callback must return |
127 | 173 | a tuple of (``status``, ``headers``, ``body``). |
128 | 174 | |
129 | .. code-block:: python | |
175 | .. code-block:: python | |
130 | 176 | |
131 | 177 | import json |
132 | 178 | |
164 | 210 | '728d329e-0e86-11e4-a748-0c84dc037c13' |
165 | 211 | ) |
166 | 212 | |
213 | You can also pass a compiled regex to ``add_callback`` to match multiple urls: | |
214 | ||
215 | .. code-block:: python | |
216 | ||
217 | import re, json | |
218 | ||
219 | from functools import reduce | |
220 | ||
221 | import responses | |
222 | import requests | |
223 | ||
224 | operators = { | |
225 | 'sum': lambda x, y: x+y, | |
226 | 'prod': lambda x, y: x*y, | |
227 | 'pow': lambda x, y: x**y | |
228 | } | |
229 | ||
230 | @responses.activate | |
231 | def test_regex_url(): | |
232 | ||
233 | def request_callback(request): | |
234 | payload = json.loads(request.body) | |
235 | operator_name = request.path_url[1:] | |
236 | ||
237 | operator = operators[operator_name] | |
238 | ||
239 | resp_body = {'value': reduce(operator, payload['numbers'])} | |
240 | headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'} | |
241 | return (200, headers, json.dumps(resp_body)) | |
242 | ||
243 | responses.add_callback( | |
244 | responses.POST, | |
245 | re.compile('http://calc.com/(sum|prod|pow|unsupported)'), | |
246 | callback=request_callback, | |
247 | content_type='application/json', | |
248 | ) | |
249 | ||
250 | resp = requests.post( | |
251 | 'http://calc.com/prod', | |
252 | json.dumps({'numbers': [2, 3, 4]}), | |
253 | headers={'content-type': 'application/json'}, | |
254 | ) | |
255 | assert resp.json() == {'value': 24} | |
256 | ||
257 | test_regex_url() | |
258 | ||
259 | ||
260 | If you want to pass extra keyword arguments to the callback function, for example when reusing | |
261 | a callback function to give a slightly different result, you can use ``functools.partial``: | |
262 | ||
263 | .. code-block:: python | |
264 | ||
265 | from functools import partial | |
266 | ||
267 | ... | |
268 | ||
269 | def request_callback(request, id=None): | |
270 | payload = json.loads(request.body) | |
271 | resp_body = {'value': sum(payload['numbers'])} | |
272 | headers = {'request-id': id} | |
273 | return (200, headers, json.dumps(resp_body)) | |
274 | ||
275 | responses.add_callback( | |
276 | responses.POST, 'http://calc.com/sum', | |
277 | callback=partial(request_callback, id='728d329e-0e86-11e4-a748-0c84dc037c13'), | |
278 | content_type='application/json', | |
279 | ) | |
280 | ||
281 | ||
282 | You can see params passed in the original ``request`` in ``responses.calls[].request.params``: | |
283 | ||
284 | .. code-block:: python | |
285 | ||
286 | import responses | |
287 | import requests | |
288 | ||
289 | @responses.activate | |
290 | def test_request_params(): | |
291 | responses.add( | |
292 | method=responses.GET, | |
293 | url="http://example.com?hello=world", | |
294 | body="test", | |
295 | match_querystring=False, | |
296 | ) | |
297 | ||
298 | resp = requests.get('http://example.com', params={"hello": "world"}) | |
299 | assert responses.calls[0].request.params == {"hello": "world"} | |
167 | 300 | |
168 | 301 | Responses as a context manager |
169 | 302 | ------------------------------ |
170 | 303 | |
171 | .. code-block:: python | |
172 | ||
173 | import responses | |
174 | import requests | |
175 | ||
304 | .. code-block:: python | |
305 | ||
306 | import responses | |
307 | import requests | |
176 | 308 | |
177 | 309 | def test_my_api(): |
178 | 310 | with responses.RequestsMock() as rsps: |
187 | 319 | resp = requests.get('http://twitter.com/api/1/foobar') |
188 | 320 | resp.status_code == 404 |
189 | 321 | |
322 | Responses as a pytest fixture | |
323 | ----------------------------- | |
324 | ||
325 | .. code-block:: python | |
326 | ||
327 | @pytest.fixture | |
328 | def mocked_responses(): | |
329 | with responses.RequestsMock() as rsps: | |
330 | yield rsps | |
331 | ||
332 | def test_api(mocked_responses): | |
333 | mocked_responses.add( | |
334 | responses.GET, 'http://twitter.com/api/1/foobar', | |
335 | body='{}', status=200, | |
336 | content_type='application/json') | |
337 | resp = requests.get('http://twitter.com/api/1/foobar') | |
338 | assert resp.status_code == 200 | |
190 | 339 | |
191 | 340 | Assertions on declared responses |
192 | 341 | -------------------------------- |
199 | 348 | |
200 | 349 | import responses |
201 | 350 | import requests |
202 | ||
203 | 351 | |
204 | 352 | def test_my_api(): |
205 | 353 | with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: |
207 | 355 | body='{}', status=200, |
208 | 356 | content_type='application/json') |
209 | 357 | |
358 | assert_call_count | |
359 | ----------------- | |
360 | ||
361 | Assert that the request was called exactly n times. | |
362 | ||
363 | .. code-block:: python | |
364 | ||
365 | import responses | |
366 | import requests | |
367 | ||
368 | @responses.activate | |
369 | def test_assert_call_count(): | |
370 | responses.add(responses.GET, "http://example.com") | |
371 | ||
372 | requests.get("http://example.com") | |
373 | assert responses.assert_call_count("http://example.com", 1) is True | |
374 | ||
375 | requests.get("http://example.com") | |
376 | with pytest.raises(AssertionError) as excinfo: | |
377 | responses.assert_call_count("http://example.com", 1) | |
378 | assert "Expected URL 'http://example.com' to be called 1 times. Called 2 times." in str(excinfo.value) | |
379 | ||
380 | ||
210 | 381 | Multiple Responses |
211 | 382 | ------------------ |
383 | ||
212 | 384 | You can also add multiple responses for the same url: |
213 | 385 | |
214 | .. code-block:: python | |
386 | .. code-block:: python | |
215 | 387 | |
216 | 388 | import responses |
217 | 389 | import requests |
228 | 400 | resp = requests.get('http://twitter.com/api/1/foobar') |
229 | 401 | assert resp.status_code == 200 |
230 | 402 | |
403 | ||
231 | 404 | Using a callback to modify the response |
232 | 405 | --------------------------------------- |
233 | 406 | |
234 | 407 | If you use customized processing in `requests` via subclassing/mixins, or if you |
235 | 408 | have library tools that interact with `requests` at a low level, you may need |
236 | to add extended processing to the mocked Response object to fully simlulate the | |
409 | to add extended processing to the mocked Response object to fully simulate the | |
237 | 410 | environment for your tests. A `response_callback` can be used, which will be |
238 | 411 | wrapped by the library before being returned to the caller. The callback |
239 | 412 | accepts a `response` as it's single argument, and is expected to return a |
240 | 413 | single `response` object. |
241 | 414 | |
242 | ||
243 | .. code-block:: python | |
244 | ||
245 | import responses | |
246 | import requests | |
247 | ||
248 | def response_callback(resp): | |
249 | resp.callback_processed = True | |
250 | return resp | |
251 | ||
252 | with responses.RequestsMock(response_callback=response_callback) as m: | |
253 | m.add(responses.GET, 'http://example.com', body=b'test') | |
254 | resp = requests.get('http://example.com') | |
255 | assert resp.text == "test" | |
256 | assert hasattr(resp, 'callback_processed') | |
257 | assert resp.callback_processed is True | |
258 | ||
259 | ||
260 | Passing thru real requests | |
261 | -------------------------- | |
262 | ||
263 | In some cases you may wish to allow for certain requests to pass thru responses | |
264 | and hit a real server. This can be done with the 'passthru' methods: | |
415 | .. code-block:: python | |
416 | ||
417 | import responses | |
418 | import requests | |
419 | ||
420 | def response_callback(resp): | |
421 | resp.callback_processed = True | |
422 | return resp | |
423 | ||
424 | with responses.RequestsMock(response_callback=response_callback) as m: | |
425 | m.add(responses.GET, 'http://example.com', body=b'test') | |
426 | resp = requests.get('http://example.com') | |
427 | assert resp.text == "test" | |
428 | assert hasattr(resp, 'callback_processed') | |
429 | assert resp.callback_processed is True | |
430 | ||
431 | ||
432 | Passing through real requests | |
433 | ----------------------------- | |
434 | ||
435 | In some cases you may wish to allow for certain requests to pass through responses | |
436 | and hit a real server. This can be done with the ``add_passthru`` methods: | |
265 | 437 | |
266 | 438 | .. code-block:: python |
267 | 439 | |
273 | 445 | |
274 | 446 | This will allow any requests matching that prefix, that is otherwise not registered |
275 | 447 | as a mock response, to passthru using the standard behavior. |
448 | ||
449 | Regex can be used like: | |
450 | ||
451 | .. code-block:: python | |
452 | ||
453 | responses.add_passthru(re.compile('https://percy.io/\\w+')) | |
454 | ||
455 | ||
456 | Viewing/Modifying registered responses | |
457 | -------------------------------------- | |
458 | ||
459 | Registered responses are available as a private attribute of the RequestMock | |
460 | instance. It is sometimes useful for debugging purposes to view the stack of | |
461 | registered responses which can be accessed via ``responses.mock._matches``. | |
462 | ||
463 | The ``replace`` function allows a previously registered ``response`` to be | |
464 | changed. The method signature is identical to ``add``. ``response`` s are | |
465 | identified using ``method`` and ``url``. Only the first matched ``response`` is | |
466 | replaced. | |
467 | ||
468 | .. code-block:: python | |
469 | ||
470 | import responses | |
471 | import requests | |
472 | ||
473 | @responses.activate | |
474 | def test_replace(): | |
475 | ||
476 | responses.add(responses.GET, 'http://example.org', json={'data': 1}) | |
477 | responses.replace(responses.GET, 'http://example.org', json={'data': 2}) | |
478 | ||
479 | resp = requests.get('http://example.org') | |
480 | ||
481 | assert resp.json() == {'data': 2} | |
482 | ||
483 | ||
484 | ``remove`` takes a ``method`` and ``url`` argument and will remove **all** | |
485 | matched responses from the registered list. | |
486 | ||
487 | Finally, ``reset`` will reset all registered responses. | |
488 | ||
489 | Contributing | |
490 | ------------ | |
491 | ||
492 | Responses uses several linting and autoformatting utilities, so it's important that when | |
493 | submitting patches you use the appropriate toolchain: | |
494 | ||
495 | Clone the repository: | |
496 | ||
497 | .. code-block:: shell | |
498 | ||
499 | git clone https://github.com/getsentry/responses.git | |
500 | ||
501 | Create an environment (e.g. with ``virtualenv``): | |
502 | ||
503 | .. code-block:: shell | |
504 | ||
505 | virtualenv .env && source .env/bin/activate | |
506 | ||
507 | Configure development requirements: | |
508 | ||
509 | .. code-block:: shell | |
510 | ||
511 | make develop | |
512 | ||
513 | Responses uses `Pytest <https://docs.pytest.org/en/latest/>`_ for | |
514 | testing. You can run all tests by: | |
515 | ||
516 | .. code-block:: shell | |
517 | ||
518 | pytest | |
519 | ||
520 | And run a single test by: | |
521 | ||
522 | .. code-block:: shell | |
523 | ||
524 | pytest -k '<test_function_name>' | |
276 | 525 | |
277 | 526 | Platform: UNKNOWN |
278 | 527 | Classifier: Intended Audience :: Developers |
279 | 528 | Classifier: Intended Audience :: System Administrators |
280 | 529 | Classifier: Operating System :: OS Independent |
530 | Classifier: Programming Language :: Python | |
281 | 531 | Classifier: Programming Language :: Python :: 2 |
532 | Classifier: Programming Language :: Python :: 2.7 | |
282 | 533 | Classifier: Programming Language :: Python :: 3 |
534 | Classifier: Programming Language :: Python :: 3.5 | |
535 | Classifier: Programming Language :: Python :: 3.6 | |
536 | Classifier: Programming Language :: Python :: 3.7 | |
537 | Classifier: Programming Language :: Python :: 3.8 | |
538 | Classifier: Programming Language :: Python :: 3.9 | |
283 | 539 | Classifier: Topic :: Software Development |
540 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* | |
541 | Description-Content-Type: text/x-rst | |
284 | 542 | Provides-Extra: tests |
0 | 0 | Responses |
1 | 1 | ========= |
2 | 2 | |
3 | .. image:: https://travis-ci.org/getsentry/responses.svg?branch=master | |
4 | :target: https://travis-ci.org/getsentry/responses | |
5 | ||
6 | A utility library for mocking out the `requests` Python library. | |
7 | ||
8 | .. note:: Responses requires Python 2.7 or newer, and requests >= 2.0 | |
3 | .. image:: https://img.shields.io/pypi/v/responses.svg | |
4 | :target: https://pypi.python.org/pypi/responses/ | |
5 | ||
6 | .. image:: https://travis-ci.org/getsentry/responses.svg?branch=master | |
7 | :target: https://travis-ci.org/getsentry/responses | |
8 | ||
9 | .. image:: https://img.shields.io/pypi/pyversions/responses.svg | |
10 | :target: https://pypi.org/project/responses/ | |
11 | ||
12 | A utility library for mocking out the ``requests`` Python library. | |
13 | ||
14 | .. note:: | |
15 | ||
16 | Responses requires Python 2.7 or newer, and requests >= 2.0 | |
17 | ||
9 | 18 | |
10 | 19 | Installing |
11 | 20 | ---------- |
21 | ||
12 | 22 | ``pip install responses`` |
23 | ||
13 | 24 | |
14 | 25 | Basics |
15 | 26 | ------ |
16 | 27 | |
17 | 28 | The core of ``responses`` comes from registering mock responses: |
18 | 29 | |
19 | .. code-block:: python | |
20 | ||
21 | import responses | |
22 | import requests | |
30 | .. code-block:: python | |
31 | ||
32 | import responses | |
33 | import requests | |
23 | 34 | |
24 | 35 | @responses.activate |
25 | 36 | def test_simple(): |
37 | 48 | If you attempt to fetch a url which doesn't hit a match, ``responses`` will raise |
38 | 49 | a ``ConnectionError``: |
39 | 50 | |
40 | .. code-block:: python | |
41 | ||
42 | import responses | |
43 | import requests | |
51 | .. code-block:: python | |
52 | ||
53 | import responses | |
54 | import requests | |
44 | 55 | |
45 | 56 | from requests.exceptions import ConnectionError |
46 | 57 | |
51 | 62 | |
52 | 63 | Lastly, you can pass an ``Exception`` as the body to trigger an error on the request: |
53 | 64 | |
54 | .. code-block:: python | |
55 | ||
56 | import responses | |
57 | import requests | |
65 | .. code-block:: python | |
66 | ||
67 | import responses | |
68 | import requests | |
58 | 69 | |
59 | 70 | @responses.activate |
60 | 71 | def test_simple(): |
63 | 74 | with pytest.raises(Exception): |
64 | 75 | requests.get('http://twitter.com/api/1/foobar') |
65 | 76 | |
77 | ||
66 | 78 | Response Parameters |
67 | 79 | ------------------- |
68 | 80 | |
69 | 81 | Responses are automatically registered via params on ``add``, but can also be |
70 | 82 | passed directly: |
71 | 83 | |
72 | .. code-block:: python | |
84 | .. code-block:: python | |
73 | 85 | |
74 | 86 | import responses |
75 | 87 | |
77 | 89 | responses.Response( |
78 | 90 | method='GET', |
79 | 91 | url='http://example.com', |
80 | ), | |
92 | ) | |
81 | 93 | ) |
82 | 94 | |
83 | 95 | The following attributes can be passed to a Response mock: |
84 | 96 | |
85 | 97 | method (``str``) |
86 | The HTTP method (GET, POST, etc). | |
98 | The HTTP method (GET, POST, etc). | |
87 | 99 | |
88 | 100 | url (``str`` or compiled regular expression) |
89 | The full resource URL. | |
101 | The full resource URL. | |
90 | 102 | |
91 | 103 | match_querystring (``bool``) |
92 | Disabled by default. Include the query string when matching requests. | |
104 | Include the query string when matching requests. | |
105 | Enabled by default if the response URL contains a query string, | |
106 | disabled if it doesn't or the URL is a regular expression. | |
93 | 107 | |
94 | 108 | body (``str`` or ``BufferedReader``) |
95 | The response body. | |
109 | The response body. | |
96 | 110 | |
97 | 111 | json |
98 | A python object representing the JSON response body. Automatically configures | |
99 | the appropriate Content-Type. | |
112 | A Python object representing the JSON response body. Automatically configures | |
113 | the appropriate Content-Type. | |
100 | 114 | |
101 | 115 | status (``int``) |
102 | The HTTP status code. | |
116 | The HTTP status code. | |
103 | 117 | |
104 | 118 | content_type (``content_type``) |
105 | Defaults to ``text/plain``. | |
119 | Defaults to ``text/plain``. | |
106 | 120 | |
107 | 121 | headers (``dict``) |
108 | Response headers. | |
122 | Response headers. | |
109 | 123 | |
110 | 124 | stream (``bool``) |
111 | Disabled by default. Indicates the response should use the streaming API. | |
112 | ||
113 | ||
114 | ||
125 | Disabled by default. Indicates the response should use the streaming API. | |
126 | ||
127 | match (``list``) | |
128 | A list of callbacks to match requests based on request body contents. | |
129 | ||
130 | ||
131 | Matching Request Parameters | |
132 | --------------------------- | |
133 | ||
134 | When adding responses for endpoints that are sent request data you can add | |
135 | matchers to ensure your code is sending the right parameters and provide | |
136 | different responses based on the request body contents. Responses provides | |
137 | matchers for JSON and URLencoded request bodies and you can supply your own for | |
138 | other formats. | |
139 | ||
140 | .. code-block:: python | |
141 | ||
142 | import responses | |
143 | import requests | |
144 | ||
145 | @responses.activate | |
146 | def test_calc_api(): | |
147 | responses.add( | |
148 | responses.POST, | |
149 | url='http://calc.com/sum', | |
150 | body=4, | |
151 | match=[ | |
152 | responses.urlencoded_params_matcher({"left": 1, "right": 3}) | |
153 | ] | |
154 | ) | |
155 | requests.post("http://calc.com/sum", data={"left": 1, "right": 3}) | |
156 | ||
157 | Matching JSON encoded data can be done with ``responses.json_params_matcher()``. | |
158 | If your application uses other encodings you can build your own matcher that | |
159 | returns ``True`` or ``False`` if the request parameters match. Your matcher can | |
160 | expect a ``request_body`` parameter to be provided by responses. | |
115 | 161 | |
116 | 162 | Dynamic Responses |
117 | 163 | ----------------- |
119 | 165 | You can utilize callbacks to provide dynamic responses. The callback must return |
120 | 166 | a tuple of (``status``, ``headers``, ``body``). |
121 | 167 | |
122 | .. code-block:: python | |
168 | .. code-block:: python | |
123 | 169 | |
124 | 170 | import json |
125 | 171 | |
157 | 203 | '728d329e-0e86-11e4-a748-0c84dc037c13' |
158 | 204 | ) |
159 | 205 | |
206 | You can also pass a compiled regex to ``add_callback`` to match multiple urls: | |
207 | ||
208 | .. code-block:: python | |
209 | ||
210 | import re, json | |
211 | ||
212 | from functools import reduce | |
213 | ||
214 | import responses | |
215 | import requests | |
216 | ||
217 | operators = { | |
218 | 'sum': lambda x, y: x+y, | |
219 | 'prod': lambda x, y: x*y, | |
220 | 'pow': lambda x, y: x**y | |
221 | } | |
222 | ||
223 | @responses.activate | |
224 | def test_regex_url(): | |
225 | ||
226 | def request_callback(request): | |
227 | payload = json.loads(request.body) | |
228 | operator_name = request.path_url[1:] | |
229 | ||
230 | operator = operators[operator_name] | |
231 | ||
232 | resp_body = {'value': reduce(operator, payload['numbers'])} | |
233 | headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'} | |
234 | return (200, headers, json.dumps(resp_body)) | |
235 | ||
236 | responses.add_callback( | |
237 | responses.POST, | |
238 | re.compile('http://calc.com/(sum|prod|pow|unsupported)'), | |
239 | callback=request_callback, | |
240 | content_type='application/json', | |
241 | ) | |
242 | ||
243 | resp = requests.post( | |
244 | 'http://calc.com/prod', | |
245 | json.dumps({'numbers': [2, 3, 4]}), | |
246 | headers={'content-type': 'application/json'}, | |
247 | ) | |
248 | assert resp.json() == {'value': 24} | |
249 | ||
250 | test_regex_url() | |
251 | ||
252 | ||
253 | If you want to pass extra keyword arguments to the callback function, for example when reusing | |
254 | a callback function to give a slightly different result, you can use ``functools.partial``: | |
255 | ||
256 | .. code-block:: python | |
257 | ||
258 | from functools import partial | |
259 | ||
260 | ... | |
261 | ||
262 | def request_callback(request, id=None): | |
263 | payload = json.loads(request.body) | |
264 | resp_body = {'value': sum(payload['numbers'])} | |
265 | headers = {'request-id': id} | |
266 | return (200, headers, json.dumps(resp_body)) | |
267 | ||
268 | responses.add_callback( | |
269 | responses.POST, 'http://calc.com/sum', | |
270 | callback=partial(request_callback, id='728d329e-0e86-11e4-a748-0c84dc037c13'), | |
271 | content_type='application/json', | |
272 | ) | |
273 | ||
274 | ||
275 | You can see params passed in the original ``request`` in ``responses.calls[].request.params``: | |
276 | ||
277 | .. code-block:: python | |
278 | ||
279 | import responses | |
280 | import requests | |
281 | ||
282 | @responses.activate | |
283 | def test_request_params(): | |
284 | responses.add( | |
285 | method=responses.GET, | |
286 | url="http://example.com?hello=world", | |
287 | body="test", | |
288 | match_querystring=False, | |
289 | ) | |
290 | ||
291 | resp = requests.get('http://example.com', params={"hello": "world"}) | |
292 | assert responses.calls[0].request.params == {"hello": "world"} | |
160 | 293 | |
161 | 294 | Responses as a context manager |
162 | 295 | ------------------------------ |
163 | 296 | |
164 | .. code-block:: python | |
165 | ||
166 | import responses | |
167 | import requests | |
168 | ||
297 | .. code-block:: python | |
298 | ||
299 | import responses | |
300 | import requests | |
169 | 301 | |
170 | 302 | def test_my_api(): |
171 | 303 | with responses.RequestsMock() as rsps: |
180 | 312 | resp = requests.get('http://twitter.com/api/1/foobar') |
181 | 313 | resp.status_code == 404 |
182 | 314 | |
315 | Responses as a pytest fixture | |
316 | ----------------------------- | |
317 | ||
318 | .. code-block:: python | |
319 | ||
320 | @pytest.fixture | |
321 | def mocked_responses(): | |
322 | with responses.RequestsMock() as rsps: | |
323 | yield rsps | |
324 | ||
325 | def test_api(mocked_responses): | |
326 | mocked_responses.add( | |
327 | responses.GET, 'http://twitter.com/api/1/foobar', | |
328 | body='{}', status=200, | |
329 | content_type='application/json') | |
330 | resp = requests.get('http://twitter.com/api/1/foobar') | |
331 | assert resp.status_code == 200 | |
183 | 332 | |
184 | 333 | Assertions on declared responses |
185 | 334 | -------------------------------- |
192 | 341 | |
193 | 342 | import responses |
194 | 343 | import requests |
195 | ||
196 | 344 | |
197 | 345 | def test_my_api(): |
198 | 346 | with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: |
200 | 348 | body='{}', status=200, |
201 | 349 | content_type='application/json') |
202 | 350 | |
351 | assert_call_count | |
352 | ----------------- | |
353 | ||
354 | Assert that the request was called exactly n times. | |
355 | ||
356 | .. code-block:: python | |
357 | ||
358 | import responses | |
359 | import requests | |
360 | ||
361 | @responses.activate | |
362 | def test_assert_call_count(): | |
363 | responses.add(responses.GET, "http://example.com") | |
364 | ||
365 | requests.get("http://example.com") | |
366 | assert responses.assert_call_count("http://example.com", 1) is True | |
367 | ||
368 | requests.get("http://example.com") | |
369 | with pytest.raises(AssertionError) as excinfo: | |
370 | responses.assert_call_count("http://example.com", 1) | |
371 | assert "Expected URL 'http://example.com' to be called 1 times. Called 2 times." in str(excinfo.value) | |
372 | ||
373 | ||
203 | 374 | Multiple Responses |
204 | 375 | ------------------ |
376 | ||
205 | 377 | You can also add multiple responses for the same url: |
206 | 378 | |
207 | .. code-block:: python | |
379 | .. code-block:: python | |
208 | 380 | |
209 | 381 | import responses |
210 | 382 | import requests |
221 | 393 | resp = requests.get('http://twitter.com/api/1/foobar') |
222 | 394 | assert resp.status_code == 200 |
223 | 395 | |
396 | ||
224 | 397 | Using a callback to modify the response |
225 | 398 | --------------------------------------- |
226 | 399 | |
227 | 400 | If you use customized processing in `requests` via subclassing/mixins, or if you |
228 | 401 | have library tools that interact with `requests` at a low level, you may need |
229 | to add extended processing to the mocked Response object to fully simlulate the | |
402 | to add extended processing to the mocked Response object to fully simulate the | |
230 | 403 | environment for your tests. A `response_callback` can be used, which will be |
231 | 404 | wrapped by the library before being returned to the caller. The callback |
232 | 405 | accepts a `response` as it's single argument, and is expected to return a |
233 | 406 | single `response` object. |
234 | 407 | |
235 | ||
236 | .. code-block:: python | |
237 | ||
238 | import responses | |
239 | import requests | |
240 | ||
241 | def response_callback(resp): | |
242 | resp.callback_processed = True | |
243 | return resp | |
244 | ||
245 | with responses.RequestsMock(response_callback=response_callback) as m: | |
246 | m.add(responses.GET, 'http://example.com', body=b'test') | |
247 | resp = requests.get('http://example.com') | |
248 | assert resp.text == "test" | |
249 | assert hasattr(resp, 'callback_processed') | |
250 | assert resp.callback_processed is True | |
251 | ||
252 | ||
253 | Passing thru real requests | |
254 | -------------------------- | |
255 | ||
256 | In some cases you may wish to allow for certain requests to pass thru responses | |
257 | and hit a real server. This can be done with the 'passthru' methods: | |
408 | .. code-block:: python | |
409 | ||
410 | import responses | |
411 | import requests | |
412 | ||
413 | def response_callback(resp): | |
414 | resp.callback_processed = True | |
415 | return resp | |
416 | ||
417 | with responses.RequestsMock(response_callback=response_callback) as m: | |
418 | m.add(responses.GET, 'http://example.com', body=b'test') | |
419 | resp = requests.get('http://example.com') | |
420 | assert resp.text == "test" | |
421 | assert hasattr(resp, 'callback_processed') | |
422 | assert resp.callback_processed is True | |
423 | ||
424 | ||
425 | Passing through real requests | |
426 | ----------------------------- | |
427 | ||
428 | In some cases you may wish to allow for certain requests to pass through responses | |
429 | and hit a real server. This can be done with the ``add_passthru`` methods: | |
258 | 430 | |
259 | 431 | .. code-block:: python |
260 | 432 | |
266 | 438 | |
267 | 439 | This will allow any requests matching that prefix, that is otherwise not registered |
268 | 440 | as a mock response, to passthru using the standard behavior. |
441 | ||
442 | Regex can be used like: | |
443 | ||
444 | .. code-block:: python | |
445 | ||
446 | responses.add_passthru(re.compile('https://percy.io/\\w+')) | |
447 | ||
448 | ||
449 | Viewing/Modifying registered responses | |
450 | -------------------------------------- | |
451 | ||
452 | Registered responses are available as a private attribute of the RequestMock | |
453 | instance. It is sometimes useful for debugging purposes to view the stack of | |
454 | registered responses which can be accessed via ``responses.mock._matches``. | |
455 | ||
456 | The ``replace`` function allows a previously registered ``response`` to be | |
457 | changed. The method signature is identical to ``add``. ``response`` s are | |
458 | identified using ``method`` and ``url``. Only the first matched ``response`` is | |
459 | replaced. | |
460 | ||
461 | .. code-block:: python | |
462 | ||
463 | import responses | |
464 | import requests | |
465 | ||
466 | @responses.activate | |
467 | def test_replace(): | |
468 | ||
469 | responses.add(responses.GET, 'http://example.org', json={'data': 1}) | |
470 | responses.replace(responses.GET, 'http://example.org', json={'data': 2}) | |
471 | ||
472 | resp = requests.get('http://example.org') | |
473 | ||
474 | assert resp.json() == {'data': 2} | |
475 | ||
476 | ||
477 | ``remove`` takes a ``method`` and ``url`` argument and will remove **all** | |
478 | matched responses from the registered list. | |
479 | ||
480 | Finally, ``reset`` will reset all registered responses. | |
481 | ||
482 | Contributing | |
483 | ------------ | |
484 | ||
485 | Responses uses several linting and autoformatting utilities, so it's important that when | |
486 | submitting patches you use the appropriate toolchain: | |
487 | ||
488 | Clone the repository: | |
489 | ||
490 | .. code-block:: shell | |
491 | ||
492 | git clone https://github.com/getsentry/responses.git | |
493 | ||
494 | Create an environment (e.g. with ``virtualenv``): | |
495 | ||
496 | .. code-block:: shell | |
497 | ||
498 | virtualenv .env && source .env/bin/activate | |
499 | ||
500 | Configure development requirements: | |
501 | ||
502 | .. code-block:: shell | |
503 | ||
504 | make develop | |
505 | ||
506 | Responses uses `Pytest <https://docs.pytest.org/en/latest/>`_ for | |
507 | testing. You can run all tests by: | |
508 | ||
509 | .. code-block:: shell | |
510 | ||
511 | pytest | |
512 | ||
513 | And run a single test by: | |
514 | ||
515 | .. code-block:: shell | |
516 | ||
517 | pytest -k '<test_function_name>' |
0 | 0 | Metadata-Version: 2.1 |
1 | 1 | Name: responses |
2 | Version: 0.9.0 | |
2 | Version: 0.12.1 | |
3 | 3 | Summary: A utility library for mocking out the `requests` Python library. |
4 | 4 | Home-page: https://github.com/getsentry/responses |
5 | 5 | Author: David Cramer |
7 | 7 | Description: Responses |
8 | 8 | ========= |
9 | 9 | |
10 | .. image:: https://travis-ci.org/getsentry/responses.svg?branch=master | |
11 | :target: https://travis-ci.org/getsentry/responses | |
12 | ||
13 | A utility library for mocking out the `requests` Python library. | |
14 | ||
15 | .. note:: Responses requires Python 2.7 or newer, and requests >= 2.0 | |
10 | .. image:: https://img.shields.io/pypi/v/responses.svg | |
11 | :target: https://pypi.python.org/pypi/responses/ | |
12 | ||
13 | .. image:: https://travis-ci.org/getsentry/responses.svg?branch=master | |
14 | :target: https://travis-ci.org/getsentry/responses | |
15 | ||
16 | .. image:: https://img.shields.io/pypi/pyversions/responses.svg | |
17 | :target: https://pypi.org/project/responses/ | |
18 | ||
19 | A utility library for mocking out the ``requests`` Python library. | |
20 | ||
21 | .. note:: | |
22 | ||
23 | Responses requires Python 2.7 or newer, and requests >= 2.0 | |
24 | ||
16 | 25 | |
17 | 26 | Installing |
18 | 27 | ---------- |
28 | ||
19 | 29 | ``pip install responses`` |
30 | ||
20 | 31 | |
21 | 32 | Basics |
22 | 33 | ------ |
23 | 34 | |
24 | 35 | The core of ``responses`` comes from registering mock responses: |
25 | 36 | |
26 | .. code-block:: python | |
27 | ||
28 | import responses | |
29 | import requests | |
37 | .. code-block:: python | |
38 | ||
39 | import responses | |
40 | import requests | |
30 | 41 | |
31 | 42 | @responses.activate |
32 | 43 | def test_simple(): |
44 | 55 | If you attempt to fetch a url which doesn't hit a match, ``responses`` will raise |
45 | 56 | a ``ConnectionError``: |
46 | 57 | |
47 | .. code-block:: python | |
48 | ||
49 | import responses | |
50 | import requests | |
58 | .. code-block:: python | |
59 | ||
60 | import responses | |
61 | import requests | |
51 | 62 | |
52 | 63 | from requests.exceptions import ConnectionError |
53 | 64 | |
58 | 69 | |
59 | 70 | Lastly, you can pass an ``Exception`` as the body to trigger an error on the request: |
60 | 71 | |
61 | .. code-block:: python | |
62 | ||
63 | import responses | |
64 | import requests | |
72 | .. code-block:: python | |
73 | ||
74 | import responses | |
75 | import requests | |
65 | 76 | |
66 | 77 | @responses.activate |
67 | 78 | def test_simple(): |
70 | 81 | with pytest.raises(Exception): |
71 | 82 | requests.get('http://twitter.com/api/1/foobar') |
72 | 83 | |
84 | ||
73 | 85 | Response Parameters |
74 | 86 | ------------------- |
75 | 87 | |
76 | 88 | Responses are automatically registered via params on ``add``, but can also be |
77 | 89 | passed directly: |
78 | 90 | |
79 | .. code-block:: python | |
91 | .. code-block:: python | |
80 | 92 | |
81 | 93 | import responses |
82 | 94 | |
84 | 96 | responses.Response( |
85 | 97 | method='GET', |
86 | 98 | url='http://example.com', |
87 | ), | |
99 | ) | |
88 | 100 | ) |
89 | 101 | |
90 | 102 | The following attributes can be passed to a Response mock: |
91 | 103 | |
92 | 104 | method (``str``) |
93 | The HTTP method (GET, POST, etc). | |
105 | The HTTP method (GET, POST, etc). | |
94 | 106 | |
95 | 107 | url (``str`` or compiled regular expression) |
96 | The full resource URL. | |
108 | The full resource URL. | |
97 | 109 | |
98 | 110 | match_querystring (``bool``) |
99 | Disabled by default. Include the query string when matching requests. | |
111 | Include the query string when matching requests. | |
112 | Enabled by default if the response URL contains a query string, | |
113 | disabled if it doesn't or the URL is a regular expression. | |
100 | 114 | |
101 | 115 | body (``str`` or ``BufferedReader``) |
102 | The response body. | |
116 | The response body. | |
103 | 117 | |
104 | 118 | json |
105 | A python object representing the JSON response body. Automatically configures | |
106 | the appropriate Content-Type. | |
119 | A Python object representing the JSON response body. Automatically configures | |
120 | the appropriate Content-Type. | |
107 | 121 | |
108 | 122 | status (``int``) |
109 | The HTTP status code. | |
123 | The HTTP status code. | |
110 | 124 | |
111 | 125 | content_type (``content_type``) |
112 | Defaults to ``text/plain``. | |
126 | Defaults to ``text/plain``. | |
113 | 127 | |
114 | 128 | headers (``dict``) |
115 | Response headers. | |
129 | Response headers. | |
116 | 130 | |
117 | 131 | stream (``bool``) |
118 | Disabled by default. Indicates the response should use the streaming API. | |
119 | ||
120 | ||
121 | ||
132 | Disabled by default. Indicates the response should use the streaming API. | |
133 | ||
134 | match (``list``) | |
135 | A list of callbacks to match requests based on request body contents. | |
136 | ||
137 | ||
138 | Matching Request Parameters | |
139 | --------------------------- | |
140 | ||
141 | When adding responses for endpoints that are sent request data you can add | |
142 | matchers to ensure your code is sending the right parameters and provide | |
143 | different responses based on the request body contents. Responses provides | |
144 | matchers for JSON and URLencoded request bodies and you can supply your own for | |
145 | other formats. | |
146 | ||
147 | .. code-block:: python | |
148 | ||
149 | import responses | |
150 | import requests | |
151 | ||
152 | @responses.activate | |
153 | def test_calc_api(): | |
154 | responses.add( | |
155 | responses.POST, | |
156 | url='http://calc.com/sum', | |
157 | body=4, | |
158 | match=[ | |
159 | responses.urlencoded_params_matcher({"left": 1, "right": 3}) | |
160 | ] | |
161 | ) | |
162 | requests.post("http://calc.com/sum", data={"left": 1, "right": 3}) | |
163 | ||
164 | Matching JSON encoded data can be done with ``responses.json_params_matcher()``. | |
165 | If your application uses other encodings you can build your own matcher that | |
166 | returns ``True`` or ``False`` if the request parameters match. Your matcher can | |
167 | expect a ``request_body`` parameter to be provided by responses. | |
122 | 168 | |
123 | 169 | Dynamic Responses |
124 | 170 | ----------------- |
126 | 172 | You can utilize callbacks to provide dynamic responses. The callback must return |
127 | 173 | a tuple of (``status``, ``headers``, ``body``). |
128 | 174 | |
129 | .. code-block:: python | |
175 | .. code-block:: python | |
130 | 176 | |
131 | 177 | import json |
132 | 178 | |
164 | 210 | '728d329e-0e86-11e4-a748-0c84dc037c13' |
165 | 211 | ) |
166 | 212 | |
213 | You can also pass a compiled regex to ``add_callback`` to match multiple urls: | |
214 | ||
215 | .. code-block:: python | |
216 | ||
217 | import re, json | |
218 | ||
219 | from functools import reduce | |
220 | ||
221 | import responses | |
222 | import requests | |
223 | ||
224 | operators = { | |
225 | 'sum': lambda x, y: x+y, | |
226 | 'prod': lambda x, y: x*y, | |
227 | 'pow': lambda x, y: x**y | |
228 | } | |
229 | ||
230 | @responses.activate | |
231 | def test_regex_url(): | |
232 | ||
233 | def request_callback(request): | |
234 | payload = json.loads(request.body) | |
235 | operator_name = request.path_url[1:] | |
236 | ||
237 | operator = operators[operator_name] | |
238 | ||
239 | resp_body = {'value': reduce(operator, payload['numbers'])} | |
240 | headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'} | |
241 | return (200, headers, json.dumps(resp_body)) | |
242 | ||
243 | responses.add_callback( | |
244 | responses.POST, | |
245 | re.compile('http://calc.com/(sum|prod|pow|unsupported)'), | |
246 | callback=request_callback, | |
247 | content_type='application/json', | |
248 | ) | |
249 | ||
250 | resp = requests.post( | |
251 | 'http://calc.com/prod', | |
252 | json.dumps({'numbers': [2, 3, 4]}), | |
253 | headers={'content-type': 'application/json'}, | |
254 | ) | |
255 | assert resp.json() == {'value': 24} | |
256 | ||
257 | test_regex_url() | |
258 | ||
259 | ||
260 | If you want to pass extra keyword arguments to the callback function, for example when reusing | |
261 | a callback function to give a slightly different result, you can use ``functools.partial``: | |
262 | ||
263 | .. code-block:: python | |
264 | ||
265 | from functools import partial | |
266 | ||
267 | ... | |
268 | ||
269 | def request_callback(request, id=None): | |
270 | payload = json.loads(request.body) | |
271 | resp_body = {'value': sum(payload['numbers'])} | |
272 | headers = {'request-id': id} | |
273 | return (200, headers, json.dumps(resp_body)) | |
274 | ||
275 | responses.add_callback( | |
276 | responses.POST, 'http://calc.com/sum', | |
277 | callback=partial(request_callback, id='728d329e-0e86-11e4-a748-0c84dc037c13'), | |
278 | content_type='application/json', | |
279 | ) | |
280 | ||
281 | ||
282 | You can see params passed in the original ``request`` in ``responses.calls[].request.params``: | |
283 | ||
284 | .. code-block:: python | |
285 | ||
286 | import responses | |
287 | import requests | |
288 | ||
289 | @responses.activate | |
290 | def test_request_params(): | |
291 | responses.add( | |
292 | method=responses.GET, | |
293 | url="http://example.com?hello=world", | |
294 | body="test", | |
295 | match_querystring=False, | |
296 | ) | |
297 | ||
298 | resp = requests.get('http://example.com', params={"hello": "world"}) | |
299 | assert responses.calls[0].request.params == {"hello": "world"} | |
167 | 300 | |
168 | 301 | Responses as a context manager |
169 | 302 | ------------------------------ |
170 | 303 | |
171 | .. code-block:: python | |
172 | ||
173 | import responses | |
174 | import requests | |
175 | ||
304 | .. code-block:: python | |
305 | ||
306 | import responses | |
307 | import requests | |
176 | 308 | |
177 | 309 | def test_my_api(): |
178 | 310 | with responses.RequestsMock() as rsps: |
187 | 319 | resp = requests.get('http://twitter.com/api/1/foobar') |
188 | 320 | resp.status_code == 404 |
189 | 321 | |
322 | Responses as a pytest fixture | |
323 | ----------------------------- | |
324 | ||
325 | .. code-block:: python | |
326 | ||
327 | @pytest.fixture | |
328 | def mocked_responses(): | |
329 | with responses.RequestsMock() as rsps: | |
330 | yield rsps | |
331 | ||
332 | def test_api(mocked_responses): | |
333 | mocked_responses.add( | |
334 | responses.GET, 'http://twitter.com/api/1/foobar', | |
335 | body='{}', status=200, | |
336 | content_type='application/json') | |
337 | resp = requests.get('http://twitter.com/api/1/foobar') | |
338 | assert resp.status_code == 200 | |
190 | 339 | |
191 | 340 | Assertions on declared responses |
192 | 341 | -------------------------------- |
199 | 348 | |
200 | 349 | import responses |
201 | 350 | import requests |
202 | ||
203 | 351 | |
204 | 352 | def test_my_api(): |
205 | 353 | with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: |
207 | 355 | body='{}', status=200, |
208 | 356 | content_type='application/json') |
209 | 357 | |
358 | assert_call_count | |
359 | ----------------- | |
360 | ||
361 | Assert that the request was called exactly n times. | |
362 | ||
363 | .. code-block:: python | |
364 | ||
365 | import responses | |
366 | import requests | |
367 | ||
368 | @responses.activate | |
369 | def test_assert_call_count(): | |
370 | responses.add(responses.GET, "http://example.com") | |
371 | ||
372 | requests.get("http://example.com") | |
373 | assert responses.assert_call_count("http://example.com", 1) is True | |
374 | ||
375 | requests.get("http://example.com") | |
376 | with pytest.raises(AssertionError) as excinfo: | |
377 | responses.assert_call_count("http://example.com", 1) | |
378 | assert "Expected URL 'http://example.com' to be called 1 times. Called 2 times." in str(excinfo.value) | |
379 | ||
380 | ||
210 | 381 | Multiple Responses |
211 | 382 | ------------------ |
383 | ||
212 | 384 | You can also add multiple responses for the same url: |
213 | 385 | |
214 | .. code-block:: python | |
386 | .. code-block:: python | |
215 | 387 | |
216 | 388 | import responses |
217 | 389 | import requests |
228 | 400 | resp = requests.get('http://twitter.com/api/1/foobar') |
229 | 401 | assert resp.status_code == 200 |
230 | 402 | |
403 | ||
231 | 404 | Using a callback to modify the response |
232 | 405 | --------------------------------------- |
233 | 406 | |
234 | 407 | If you use customized processing in `requests` via subclassing/mixins, or if you |
235 | 408 | have library tools that interact with `requests` at a low level, you may need |
236 | to add extended processing to the mocked Response object to fully simlulate the | |
409 | to add extended processing to the mocked Response object to fully simulate the | |
237 | 410 | environment for your tests. A `response_callback` can be used, which will be |
238 | 411 | wrapped by the library before being returned to the caller. The callback |
239 | 412 | accepts a `response` as it's single argument, and is expected to return a |
240 | 413 | single `response` object. |
241 | 414 | |
242 | ||
243 | .. code-block:: python | |
244 | ||
245 | import responses | |
246 | import requests | |
247 | ||
248 | def response_callback(resp): | |
249 | resp.callback_processed = True | |
250 | return resp | |
251 | ||
252 | with responses.RequestsMock(response_callback=response_callback) as m: | |
253 | m.add(responses.GET, 'http://example.com', body=b'test') | |
254 | resp = requests.get('http://example.com') | |
255 | assert resp.text == "test" | |
256 | assert hasattr(resp, 'callback_processed') | |
257 | assert resp.callback_processed is True | |
258 | ||
259 | ||
260 | Passing thru real requests | |
261 | -------------------------- | |
262 | ||
263 | In some cases you may wish to allow for certain requests to pass thru responses | |
264 | and hit a real server. This can be done with the 'passthru' methods: | |
415 | .. code-block:: python | |
416 | ||
417 | import responses | |
418 | import requests | |
419 | ||
420 | def response_callback(resp): | |
421 | resp.callback_processed = True | |
422 | return resp | |
423 | ||
424 | with responses.RequestsMock(response_callback=response_callback) as m: | |
425 | m.add(responses.GET, 'http://example.com', body=b'test') | |
426 | resp = requests.get('http://example.com') | |
427 | assert resp.text == "test" | |
428 | assert hasattr(resp, 'callback_processed') | |
429 | assert resp.callback_processed is True | |
430 | ||
431 | ||
432 | Passing through real requests | |
433 | ----------------------------- | |
434 | ||
435 | In some cases you may wish to allow for certain requests to pass through responses | |
436 | and hit a real server. This can be done with the ``add_passthru`` methods: | |
265 | 437 | |
266 | 438 | .. code-block:: python |
267 | 439 | |
273 | 445 | |
274 | 446 | This will allow any requests matching that prefix, that is otherwise not registered |
275 | 447 | as a mock response, to passthru using the standard behavior. |
448 | ||
449 | Regex can be used like: | |
450 | ||
451 | .. code-block:: python | |
452 | ||
453 | responses.add_passthru(re.compile('https://percy.io/\\w+')) | |
454 | ||
455 | ||
456 | Viewing/Modifying registered responses | |
457 | -------------------------------------- | |
458 | ||
459 | Registered responses are available as a private attribute of the RequestMock | |
460 | instance. It is sometimes useful for debugging purposes to view the stack of | |
461 | registered responses which can be accessed via ``responses.mock._matches``. | |
462 | ||
463 | The ``replace`` function allows a previously registered ``response`` to be | |
464 | changed. The method signature is identical to ``add``. ``response`` s are | |
465 | identified using ``method`` and ``url``. Only the first matched ``response`` is | |
466 | replaced. | |
467 | ||
468 | .. code-block:: python | |
469 | ||
470 | import responses | |
471 | import requests | |
472 | ||
473 | @responses.activate | |
474 | def test_replace(): | |
475 | ||
476 | responses.add(responses.GET, 'http://example.org', json={'data': 1}) | |
477 | responses.replace(responses.GET, 'http://example.org', json={'data': 2}) | |
478 | ||
479 | resp = requests.get('http://example.org') | |
480 | ||
481 | assert resp.json() == {'data': 2} | |
482 | ||
483 | ||
484 | ``remove`` takes a ``method`` and ``url`` argument and will remove **all** | |
485 | matched responses from the registered list. | |
486 | ||
487 | Finally, ``reset`` will reset all registered responses. | |
488 | ||
489 | Contributing | |
490 | ------------ | |
491 | ||
492 | Responses uses several linting and autoformatting utilities, so it's important that when | |
493 | submitting patches you use the appropriate toolchain: | |
494 | ||
495 | Clone the repository: | |
496 | ||
497 | .. code-block:: shell | |
498 | ||
499 | git clone https://github.com/getsentry/responses.git | |
500 | ||
501 | Create an environment (e.g. with ``virtualenv``): | |
502 | ||
503 | .. code-block:: shell | |
504 | ||
505 | virtualenv .env && source .env/bin/activate | |
506 | ||
507 | Configure development requirements: | |
508 | ||
509 | .. code-block:: shell | |
510 | ||
511 | make develop | |
512 | ||
513 | Responses uses `Pytest <https://docs.pytest.org/en/latest/>`_ for | |
514 | testing. You can run all tests by: | |
515 | ||
516 | .. code-block:: shell | |
517 | ||
518 | pytest | |
519 | ||
520 | And run a single test by: | |
521 | ||
522 | .. code-block:: shell | |
523 | ||
524 | pytest -k '<test_function_name>' | |
276 | 525 | |
277 | 526 | Platform: UNKNOWN |
278 | 527 | Classifier: Intended Audience :: Developers |
279 | 528 | Classifier: Intended Audience :: System Administrators |
280 | 529 | Classifier: Operating System :: OS Independent |
530 | Classifier: Programming Language :: Python | |
281 | 531 | Classifier: Programming Language :: Python :: 2 |
532 | Classifier: Programming Language :: Python :: 2.7 | |
282 | 533 | Classifier: Programming Language :: Python :: 3 |
534 | Classifier: Programming Language :: Python :: 3.5 | |
535 | Classifier: Programming Language :: Python :: 3.6 | |
536 | Classifier: Programming Language :: Python :: 3.7 | |
537 | Classifier: Programming Language :: Python :: 3.8 | |
538 | Classifier: Programming Language :: Python :: 3.9 | |
283 | 539 | Classifier: Topic :: Software Development |
540 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* | |
541 | Description-Content-Type: text/x-rst | |
284 | 542 | Provides-Extra: tests |
5 | 5 | setup.cfg |
6 | 6 | setup.py |
7 | 7 | test_responses.py |
8 | tox.ini | |
8 | 9 | responses.egg-info/PKG-INFO |
9 | 10 | responses.egg-info/SOURCES.txt |
10 | 11 | responses.egg-info/dependency_links.txt |
0 | 0 | requests>=2.0 |
1 | cookies | |
1 | urllib3>=1.25.10 | |
2 | 2 | six |
3 | 3 | |
4 | [:python_version in "2.6, 2.7, 3.2"] | |
4 | [:python_version < "3.3"] | |
5 | 5 | mock |
6 | 6 | |
7 | [:python_version < "3.4"] | |
8 | cookies | |
9 | ||
7 | 10 | [tests] |
8 | pytest | |
9 | coverage<5.0.0,>=3.7.1 | |
11 | coverage<6.0.0,>=3.7.1 | |
10 | 12 | pytest-cov |
11 | 13 | pytest-localserver |
12 | 14 | flake8 |
15 | ||
16 | [tests:python_version < "3.5"] | |
17 | pytest<5.0,>=4.6 | |
18 | ||
19 | [tests:python_version >= "3.5"] | |
20 | pytest>=4.6 |
0 | from __future__ import (absolute_import, print_function, division, | |
1 | unicode_literals) | |
0 | from __future__ import absolute_import, print_function, division, unicode_literals | |
2 | 1 | |
3 | 2 | import _io |
4 | 3 | import inspect |
5 | 4 | import json as json_module |
6 | 5 | import logging |
7 | 6 | import re |
7 | from itertools import groupby | |
8 | ||
8 | 9 | import six |
9 | 10 | |
10 | from collections import namedtuple, Sequence, Sized | |
11 | from collections import namedtuple | |
11 | 12 | from functools import update_wrapper |
12 | from cookies import Cookies | |
13 | 13 | from requests.adapters import HTTPAdapter |
14 | 14 | from requests.exceptions import ConnectionError |
15 | 15 | from requests.sessions import REDIRECT_STATI |
16 | 16 | from requests.utils import cookiejar_from_dict |
17 | 17 | |
18 | 18 | try: |
19 | from collections.abc import Sequence, Sized | |
20 | except ImportError: | |
21 | from collections import Sequence, Sized | |
22 | ||
23 | try: | |
19 | 24 | from requests.packages.urllib3.response import HTTPResponse |
20 | 25 | except ImportError: |
21 | 26 | from urllib3.response import HTTPResponse |
27 | try: | |
28 | from requests.packages.urllib3.connection import HTTPHeaderDict | |
29 | except ImportError: | |
30 | from urllib3.response import HTTPHeaderDict | |
22 | 31 | |
23 | 32 | if six.PY2: |
24 | 33 | from urlparse import urlparse, parse_qsl, urlsplit, urlunsplit |
45 | 54 | # Python 3.7 |
46 | 55 | Pattern = re.Pattern |
47 | 56 | |
57 | try: | |
58 | from json.decoder import JSONDecodeError | |
59 | except ImportError: | |
60 | JSONDecodeError = ValueError | |
61 | ||
48 | 62 | UNSET = object() |
49 | 63 | |
50 | Call = namedtuple('Call', ['request', 'response']) | |
64 | Call = namedtuple("Call", ["request", "response"]) | |
51 | 65 | |
52 | 66 | _real_send = HTTPAdapter.send |
53 | 67 | |
54 | _wrapper_template = """\ | |
55 | def wrapper%(signature)s: | |
56 | with responses: | |
57 | return func%(funcargs)s | |
58 | """ | |
59 | ||
60 | logger = logging.getLogger('responses') | |
68 | logger = logging.getLogger("responses") | |
61 | 69 | |
62 | 70 | |
63 | 71 | def _is_string(s): |
73 | 81 | urllist = list(urlsplit(url)) |
74 | 82 | netloc = urllist[1] |
75 | 83 | if _has_unicode(netloc): |
76 | domains = netloc.split('.') | |
84 | domains = netloc.split(".") | |
77 | 85 | for i, d in enumerate(domains): |
78 | 86 | if _has_unicode(d): |
79 | d = 'xn--' + d.encode('punycode').decode('ascii') | |
87 | d = "xn--" + d.encode("punycode").decode("ascii") | |
80 | 88 | domains[i] = d |
81 | urllist[1] = '.'.join(domains) | |
89 | urllist[1] = ".".join(domains) | |
82 | 90 | url = urlunsplit(urllist) |
83 | 91 | |
84 | 92 | # Clean up path/query/params, which use url-encoding to handle unicode chars |
85 | if isinstance(url.encode('utf8'), six.string_types): | |
86 | url = url.encode('utf8') | |
93 | if isinstance(url.encode("utf8"), six.string_types): | |
94 | url = url.encode("utf8") | |
87 | 95 | chars = list(url) |
88 | 96 | for i, x in enumerate(chars): |
89 | 97 | if ord(x) > 128: |
90 | 98 | chars[i] = quote(x) |
91 | 99 | |
92 | return ''.join(chars) | |
100 | return "".join(chars) | |
93 | 101 | |
94 | 102 | |
95 | 103 | def _is_redirect(response): |
96 | 104 | try: |
97 | 105 | # 2.0.0 <= requests <= 2.2 |
98 | 106 | return response.is_redirect |
107 | ||
99 | 108 | except AttributeError: |
100 | 109 | # requests > 2.2 |
101 | 110 | return ( |
102 | 111 | # use request.sessions conditional |
103 | response.status_code in REDIRECT_STATI and | |
104 | 'location' in response.headers) | |
105 | ||
106 | ||
107 | def get_wrapped(func, wrapper_template, evaldict): | |
108 | # Preserve the argspec for the wrapped function so that testing | |
109 | # tools such as pytest can continue to use their fixture injection. | |
112 | response.status_code in REDIRECT_STATI | |
113 | and "location" in response.headers | |
114 | ) | |
115 | ||
116 | ||
117 | def _ensure_str(s): | |
118 | if six.PY2: | |
119 | s = s.encode("utf-8") if isinstance(s, six.text_type) else s | |
120 | return s | |
121 | ||
122 | ||
123 | def _cookies_from_headers(headers): | |
124 | try: | |
125 | import http.cookies as cookies | |
126 | ||
127 | resp_cookie = cookies.SimpleCookie() | |
128 | resp_cookie.load(headers["set-cookie"]) | |
129 | ||
130 | cookies_dict = {name: v.value for name, v in resp_cookie.items()} | |
131 | except ImportError: | |
132 | from cookies import Cookies | |
133 | ||
134 | resp_cookies = Cookies.from_request(_ensure_str(headers["set-cookie"])) | |
135 | cookies_dict = { | |
136 | v.name: quote(_ensure_str(v.value)) for _, v in resp_cookies.items() | |
137 | } | |
138 | return cookiejar_from_dict(cookies_dict) | |
139 | ||
140 | ||
141 | _wrapper_template = """\ | |
142 | def wrapper%(wrapper_args)s: | |
143 | with responses: | |
144 | return func%(func_args)s | |
145 | """ | |
146 | ||
147 | ||
148 | def get_wrapped(func, responses): | |
110 | 149 | if six.PY2: |
111 | 150 | args, a, kw, defaults = inspect.getargspec(func) |
151 | wrapper_args = inspect.formatargspec(args, a, kw, defaults) | |
152 | ||
153 | # Preserve the argspec for the wrapped function so that testing | |
154 | # tools such as pytest can continue to use their fixture injection. | |
155 | if hasattr(func, "__self__"): | |
156 | args = args[1:] # Omit 'self' | |
157 | func_args = inspect.formatargspec(args, a, kw, None) | |
112 | 158 | else: |
113 | args, a, kw, defaults, kwonlyargs, kwonlydefaults, annotations = \ | |
114 | inspect.getfullargspec(func) | |
115 | ||
116 | signature = inspect.formatargspec(args, a, kw, defaults) | |
117 | is_bound_method = hasattr(func, '__self__') | |
118 | if is_bound_method: | |
119 | args = args[1:] # Omit 'self' | |
120 | callargs = inspect.formatargspec(args, a, kw, None) | |
121 | ||
122 | ctx = {'signature': signature, 'funcargs': callargs} | |
123 | six.exec_(wrapper_template % ctx, evaldict) | |
124 | ||
125 | wrapper = evaldict['wrapper'] | |
126 | ||
159 | signature = inspect.signature(func) | |
160 | signature = signature.replace(return_annotation=inspect.Signature.empty) | |
161 | # If the function is wrapped, switch to *args, **kwargs for the parameters | |
162 | # as we can't rely on the signature to give us the arguments the function will | |
163 | # be called with. For example unittest.mock.patch uses required args that are | |
164 | # not actually passed to the function when invoked. | |
165 | if hasattr(func, "__wrapped__"): | |
166 | wrapper_params = [ | |
167 | inspect.Parameter("args", inspect.Parameter.VAR_POSITIONAL), | |
168 | inspect.Parameter("kwargs", inspect.Parameter.VAR_KEYWORD), | |
169 | ] | |
170 | else: | |
171 | wrapper_params = [ | |
172 | param.replace(annotation=inspect.Parameter.empty) | |
173 | for param in signature.parameters.values() | |
174 | ] | |
175 | signature = signature.replace(parameters=wrapper_params) | |
176 | ||
177 | wrapper_args = str(signature) | |
178 | params_without_defaults = [ | |
179 | param.replace( | |
180 | annotation=inspect.Parameter.empty, default=inspect.Parameter.empty | |
181 | ) | |
182 | for param in signature.parameters.values() | |
183 | ] | |
184 | signature = signature.replace(parameters=params_without_defaults) | |
185 | func_args = str(signature) | |
186 | ||
187 | evaldict = {"func": func, "responses": responses} | |
188 | six.exec_( | |
189 | _wrapper_template % {"wrapper_args": wrapper_args, "func_args": func_args}, | |
190 | evaldict, | |
191 | ) | |
192 | wrapper = evaldict["wrapper"] | |
127 | 193 | update_wrapper(wrapper, func) |
128 | if is_bound_method: | |
129 | wrapper = wrapper.__get__(func.__self__, type(func.__self__)) | |
130 | 194 | return wrapper |
131 | 195 | |
132 | 196 | |
153 | 217 | def _ensure_url_default_path(url): |
154 | 218 | if _is_string(url): |
155 | 219 | url_parts = list(urlsplit(url)) |
156 | if url_parts[2] == '': | |
157 | url_parts[2] = '/' | |
220 | if url_parts[2] == "": | |
221 | url_parts[2] = "/" | |
158 | 222 | url = urlunsplit(url_parts) |
159 | 223 | return url |
160 | 224 | |
161 | 225 | |
162 | 226 | def _handle_body(body): |
163 | 227 | if isinstance(body, six.text_type): |
164 | body = body.encode('utf-8') | |
228 | body = body.encode("utf-8") | |
165 | 229 | if isinstance(body, _io.BufferedReader): |
166 | 230 | return body |
231 | ||
167 | 232 | return BufferIO(body) |
233 | ||
234 | ||
235 | _unspecified = object() | |
236 | ||
237 | ||
238 | def urlencoded_params_matcher(params): | |
239 | def match(request_body): | |
240 | return ( | |
241 | params is None | |
242 | if request_body is None | |
243 | else sorted(params.items()) == sorted(parse_qsl(request_body)) | |
244 | ) | |
245 | ||
246 | return match | |
247 | ||
248 | ||
249 | def json_params_matcher(params): | |
250 | def match(request_body): | |
251 | try: | |
252 | if isinstance(request_body, bytes): | |
253 | request_body = request_body.decode("utf-8") | |
254 | return ( | |
255 | params is None | |
256 | if request_body is None | |
257 | else params == json_module.loads(request_body) | |
258 | ) | |
259 | except JSONDecodeError: | |
260 | return False | |
261 | ||
262 | return match | |
168 | 263 | |
169 | 264 | |
170 | 265 | class BaseResponse(object): |
173 | 268 | |
174 | 269 | stream = False |
175 | 270 | |
176 | def __init__(self, method, url, match_querystring=False): | |
271 | def __init__(self, method, url, match_querystring=_unspecified, match=[]): | |
177 | 272 | self.method = method |
178 | self.match_querystring = match_querystring | |
179 | 273 | # ensure the url has a default path set if the url is a string |
180 | 274 | self.url = _ensure_url_default_path(url) |
275 | self.match_querystring = self._should_match_querystring(match_querystring) | |
276 | self.match = match | |
181 | 277 | self.call_count = 0 |
182 | 278 | |
183 | 279 | def __eq__(self, other): |
187 | 283 | if self.method != other.method: |
188 | 284 | return False |
189 | 285 | |
190 | # Can't simply do a equality check on the objects directly here since __eq__ isn't | |
286 | # Can't simply do an equality check on the objects directly here since __eq__ isn't | |
191 | 287 | # implemented for regex. It might seem to work as regex is using a cache to return |
192 | 288 | # the same regex instances, but it doesn't in all cases. |
193 | self_url = self.url.pattern if isinstance(self.url, | |
194 | Pattern) else self.url | |
195 | other_url = other.url.pattern if isinstance(other.url, | |
196 | Pattern) else other.url | |
289 | self_url = self.url.pattern if isinstance(self.url, Pattern) else self.url | |
290 | other_url = other.url.pattern if isinstance(other.url, Pattern) else other.url | |
197 | 291 | |
198 | 292 | return self_url == other_url |
199 | 293 | |
210 | 304 | url_qsl = sorted(parse_qsl(url_parsed.query)) |
211 | 305 | other_qsl = sorted(parse_qsl(other_parsed.query)) |
212 | 306 | |
213 | if len(url_qsl) != len(other_qsl): | |
307 | return url_qsl == other_qsl | |
308 | ||
309 | def _should_match_querystring(self, match_querystring_argument): | |
310 | if match_querystring_argument is not _unspecified: | |
311 | return match_querystring_argument | |
312 | ||
313 | if isinstance(self.url, Pattern): | |
314 | # the old default from <= 0.9.0 | |
214 | 315 | return False |
215 | 316 | |
216 | for (a_k, a_v), (b_k, b_v) in zip(url_qsl, other_qsl): | |
217 | if a_k != b_k: | |
218 | return False | |
219 | if a_v != b_v: | |
220 | return False | |
221 | return True | |
317 | return bool(urlparse(self.url).query) | |
222 | 318 | |
223 | 319 | def _url_matches(self, url, other, match_querystring=False): |
224 | 320 | if _is_string(url): |
225 | 321 | if _has_unicode(url): |
226 | 322 | url = _clean_unicode(url) |
227 | 323 | if not isinstance(other, six.text_type): |
228 | other = other.encode('ascii').decode('utf8') | |
324 | other = other.encode("ascii").decode("utf8") | |
229 | 325 | if match_querystring: |
230 | 326 | return self._url_matches_strict(url, other) |
327 | ||
231 | 328 | else: |
232 | url_without_qs = url.split('?', 1)[0] | |
233 | other_without_qs = other.split('?', 1)[0] | |
329 | url_without_qs = url.split("?", 1)[0] | |
330 | other_without_qs = other.split("?", 1)[0] | |
234 | 331 | return url_without_qs == other_without_qs |
332 | ||
235 | 333 | elif isinstance(url, Pattern) and url.match(other): |
236 | 334 | return True |
335 | ||
237 | 336 | else: |
238 | 337 | return False |
239 | 338 | |
339 | def _body_matches(self, match, request_body): | |
340 | for matcher in match: | |
341 | if not matcher(request_body): | |
342 | return False | |
343 | ||
344 | return True | |
345 | ||
240 | 346 | def get_headers(self): |
241 | headers = {} | |
347 | headers = HTTPHeaderDict() # Duplicate headers are legal | |
242 | 348 | if self.content_type is not None: |
243 | headers['Content-Type'] = self.content_type | |
349 | headers["Content-Type"] = self.content_type | |
244 | 350 | if self.headers: |
245 | headers.update(self.headers) | |
351 | headers.extend(self.headers) | |
246 | 352 | return headers |
247 | 353 | |
248 | 354 | def get_response(self, request): |
250 | 356 | |
251 | 357 | def matches(self, request): |
252 | 358 | if request.method != self.method: |
253 | return False | |
254 | ||
255 | if not self._url_matches(self.url, request.url, | |
256 | self.match_querystring): | |
257 | return False | |
258 | ||
259 | return True | |
359 | return False, "Method does not match" | |
360 | ||
361 | if not self._url_matches(self.url, request.url, self.match_querystring): | |
362 | return False, "URL does not match" | |
363 | ||
364 | if not self._body_matches(self.match, request.body): | |
365 | return False, "Parameters do not match" | |
366 | ||
367 | return True, "" | |
260 | 368 | |
261 | 369 | |
262 | 370 | class Response(BaseResponse): |
263 | def __init__(self, | |
264 | method, | |
265 | url, | |
266 | body='', | |
267 | json=None, | |
268 | status=200, | |
269 | headers=None, | |
270 | stream=False, | |
271 | content_type=UNSET, | |
272 | **kwargs): | |
371 | def __init__( | |
372 | self, | |
373 | method, | |
374 | url, | |
375 | body="", | |
376 | json=None, | |
377 | status=200, | |
378 | headers=None, | |
379 | stream=False, | |
380 | content_type=UNSET, | |
381 | **kwargs | |
382 | ): | |
273 | 383 | # if we were passed a `json` argument, |
274 | 384 | # override the body and content_type |
275 | 385 | if json is not None: |
276 | 386 | assert not body |
277 | 387 | body = json_module.dumps(json) |
278 | 388 | if content_type is UNSET: |
279 | content_type = 'application/json' | |
389 | content_type = "application/json" | |
280 | 390 | |
281 | 391 | if content_type is UNSET: |
282 | content_type = 'text/plain' | |
283 | ||
284 | # body must be bytes | |
285 | if isinstance(body, six.text_type): | |
286 | body = body.encode('utf-8') | |
392 | if isinstance(body, six.text_type) and _has_unicode(body): | |
393 | content_type = "text/plain; charset=utf-8" | |
394 | else: | |
395 | content_type = "text/plain" | |
287 | 396 | |
288 | 397 | self.body = body |
289 | 398 | self.status = status |
299 | 408 | headers = self.get_headers() |
300 | 409 | status = self.status |
301 | 410 | body = _handle_body(self.body) |
302 | ||
303 | 411 | return HTTPResponse( |
304 | 412 | status=status, |
305 | 413 | reason=six.moves.http_client.responses.get(status), |
306 | 414 | body=body, |
307 | 415 | headers=headers, |
308 | preload_content=False, ) | |
416 | original_response=OriginalResponseShim(headers), | |
417 | preload_content=False, | |
418 | ) | |
309 | 419 | |
310 | 420 | |
311 | 421 | class CallbackResponse(BaseResponse): |
312 | def __init__(self, | |
313 | method, | |
314 | url, | |
315 | callback, | |
316 | stream=False, | |
317 | content_type='text/plain', | |
318 | **kwargs): | |
422 | def __init__( | |
423 | self, method, url, callback, stream=False, content_type="text/plain", **kwargs | |
424 | ): | |
319 | 425 | self.callback = callback |
320 | 426 | self.stream = stream |
321 | 427 | self.content_type = content_type |
329 | 435 | raise result |
330 | 436 | |
331 | 437 | status, r_headers, body = result |
438 | if isinstance(body, Exception): | |
439 | raise body | |
440 | ||
441 | # If the callback set a content-type remove the one | |
442 | # set in add_callback() so that we don't have multiple | |
443 | # content type values. | |
444 | has_content_type = False | |
445 | if isinstance(r_headers, dict) and "Content-Type" in r_headers: | |
446 | has_content_type = True | |
447 | elif isinstance(r_headers, list): | |
448 | has_content_type = any( | |
449 | [h for h in r_headers if h and h[0].lower() == "content-type"] | |
450 | ) | |
451 | if has_content_type: | |
452 | headers.pop("Content-Type", None) | |
453 | ||
332 | 454 | body = _handle_body(body) |
333 | headers.update(r_headers) | |
455 | headers.extend(r_headers) | |
334 | 456 | |
335 | 457 | return HTTPResponse( |
336 | 458 | status=status, |
337 | 459 | reason=six.moves.http_client.responses.get(status), |
338 | 460 | body=body, |
339 | 461 | headers=headers, |
340 | preload_content=False, ) | |
462 | original_response=OriginalResponseShim(headers), | |
463 | preload_content=False, | |
464 | ) | |
465 | ||
466 | ||
467 | class OriginalResponseShim(object): | |
468 | """ | |
469 | Shim for compatibility with older versions of urllib3 | |
470 | ||
471 | requests cookie handling depends on responses having a property chain of | |
472 | `response._original_response.msg` which contains the response headers [1] | |
473 | ||
474 | Using HTTPResponse() for this purpose causes compatibility errors with | |
475 | urllib3<1.23.0. To avoid adding more dependencies we can use this shim. | |
476 | ||
477 | [1]: https://github.com/psf/requests/blob/75bdc998e2d/requests/cookies.py#L125 | |
478 | """ | |
479 | ||
480 | def __init__(self, headers): | |
481 | self.msg = headers | |
482 | ||
483 | def isclosed(self): | |
484 | return True | |
341 | 485 | |
342 | 486 | |
343 | 487 | class RequestsMock(object): |
344 | DELETE = 'DELETE' | |
345 | GET = 'GET' | |
346 | HEAD = 'HEAD' | |
347 | OPTIONS = 'OPTIONS' | |
348 | PATCH = 'PATCH' | |
349 | POST = 'POST' | |
350 | PUT = 'PUT' | |
488 | DELETE = "DELETE" | |
489 | GET = "GET" | |
490 | HEAD = "HEAD" | |
491 | OPTIONS = "OPTIONS" | |
492 | PATCH = "PATCH" | |
493 | POST = "POST" | |
494 | PUT = "PUT" | |
351 | 495 | response_callback = None |
352 | 496 | |
353 | def __init__(self, | |
354 | assert_all_requests_are_fired=True, | |
355 | response_callback=None, | |
356 | passthru_prefixes=(), | |
357 | target='requests.adapters.HTTPAdapter.send'): | |
497 | def __init__( | |
498 | self, | |
499 | assert_all_requests_are_fired=True, | |
500 | response_callback=None, | |
501 | passthru_prefixes=(), | |
502 | target="requests.adapters.HTTPAdapter.send", | |
503 | ): | |
358 | 504 | self._calls = CallList() |
359 | 505 | self.reset() |
360 | 506 | self.assert_all_requests_are_fired = assert_all_requests_are_fired |
367 | 513 | self._calls.reset() |
368 | 514 | |
369 | 515 | def add( |
370 | self, | |
371 | method=None, # method or ``Response`` | |
372 | url=None, | |
373 | body='', | |
374 | adding_headers=None, | |
375 | *args, | |
376 | **kwargs): | |
516 | self, | |
517 | method=None, # method or ``Response`` | |
518 | url=None, | |
519 | body="", | |
520 | adding_headers=None, | |
521 | *args, | |
522 | **kwargs | |
523 | ): | |
377 | 524 | """ |
378 | 525 | A basic request: |
379 | 526 | |
414 | 561 | return |
415 | 562 | |
416 | 563 | if adding_headers is not None: |
417 | kwargs.setdefault('headers', adding_headers) | |
418 | ||
419 | self._matches.append( | |
420 | Response(method=method, url=url, body=body, **kwargs)) | |
564 | kwargs.setdefault("headers", adding_headers) | |
565 | ||
566 | self._matches.append(Response(method=method, url=url, body=body, **kwargs)) | |
421 | 567 | |
422 | 568 | def add_passthru(self, prefix): |
423 | 569 | """ |
424 | Register a URL prefix to passthru any non-matching mock requests to. | |
570 | Register a URL prefix or regex to passthru any non-matching mock requests to. | |
425 | 571 | |
426 | 572 | For example, to allow any request to 'https://example.com', but require |
427 | 573 | mocks for the remainder, you would add the prefix as so: |
428 | 574 | |
429 | 575 | >>> responses.add_passthru('https://example.com') |
576 | ||
577 | Regex can be used like: | |
578 | ||
579 | >>> responses.add_passthru(re.compile('https://example.com/\\w+')) | |
430 | 580 | """ |
431 | if _has_unicode(prefix): | |
581 | if not isinstance(prefix, Pattern) and _has_unicode(prefix): | |
432 | 582 | prefix = _clean_unicode(prefix) |
433 | self.passthru_prefixes += (prefix, ) | |
583 | self.passthru_prefixes += (prefix,) | |
434 | 584 | |
435 | 585 | def remove(self, method_or_response=None, url=None): |
436 | 586 | """ |
449 | 599 | while response in self._matches: |
450 | 600 | self._matches.remove(response) |
451 | 601 | |
452 | def replace(self, | |
453 | method_or_response=None, | |
454 | url=None, | |
455 | body='', | |
456 | *args, | |
457 | **kwargs): | |
602 | def replace(self, method_or_response=None, url=None, body="", *args, **kwargs): | |
458 | 603 | """ |
459 | 604 | Replaces a response previously added using ``add()``. The signature |
460 | 605 | is identical to ``add()``. The response is identified using ``method`` |
466 | 611 | if isinstance(method_or_response, BaseResponse): |
467 | 612 | response = method_or_response |
468 | 613 | else: |
469 | response = Response( | |
470 | method=method_or_response, url=url, body=body, **kwargs) | |
614 | response = Response(method=method_or_response, url=url, body=body, **kwargs) | |
471 | 615 | |
472 | 616 | index = self._matches.index(response) |
473 | 617 | self._matches[index] = response |
474 | 618 | |
475 | def add_callback(self, | |
476 | method, | |
477 | url, | |
478 | callback, | |
479 | match_querystring=False, | |
480 | content_type='text/plain'): | |
619 | def add_callback( | |
620 | self, method, url, callback, match_querystring=False, content_type="text/plain" | |
621 | ): | |
481 | 622 | # ensure the url has a default path set if the url is a string |
482 | 623 | # url = _ensure_url_default_path(url, match_querystring) |
483 | 624 | |
487 | 628 | method=method, |
488 | 629 | callback=callback, |
489 | 630 | content_type=content_type, |
490 | match_querystring=match_querystring, )) | |
631 | match_querystring=match_querystring, | |
632 | ) | |
633 | ) | |
491 | 634 | |
492 | 635 | @property |
493 | 636 | def calls(self): |
504 | 647 | return success |
505 | 648 | |
506 | 649 | def activate(self, func): |
507 | evaldict = {'responses': self, 'func': func} | |
508 | return get_wrapped(func, _wrapper_template, evaldict) | |
650 | return get_wrapped(func, self) | |
509 | 651 | |
510 | 652 | def _find_match(self, request): |
511 | 653 | found = None |
512 | 654 | found_match = None |
655 | match_failed_reasons = [] | |
513 | 656 | for i, match in enumerate(self._matches): |
514 | if match.matches(request): | |
657 | match_result, reason = match.matches(request) | |
658 | if match_result: | |
515 | 659 | if found is None: |
516 | 660 | found = i |
517 | 661 | found_match = match |
518 | 662 | else: |
519 | 663 | # Multiple matches found. Remove & return the first match. |
520 | return self._matches.pop(found) | |
521 | return found_match | |
664 | return self._matches.pop(found), match_failed_reasons | |
665 | else: | |
666 | match_failed_reasons.append(reason) | |
667 | return found_match, match_failed_reasons | |
668 | ||
669 | def _parse_request_params(self, url): | |
670 | params = {} | |
671 | for key, val in groupby(parse_qsl(urlparse(url).query), lambda kv: kv[0]): | |
672 | values = list(map(lambda x: x[1], val)) | |
673 | if len(values) == 1: | |
674 | values = values[0] | |
675 | params[key] = values | |
676 | return params | |
522 | 677 | |
523 | 678 | def _on_request(self, adapter, request, **kwargs): |
524 | match = self._find_match(request) | |
679 | match, match_failed_reasons = self._find_match(request) | |
525 | 680 | resp_callback = self.response_callback |
681 | request.params = self._parse_request_params(request.path_url) | |
526 | 682 | |
527 | 683 | if match is None: |
528 | if request.url.startswith(self.passthru_prefixes): | |
529 | logger.info( | |
530 | 'request.allowed-passthru', extra={ | |
531 | 'url': request.url, | |
532 | }) | |
533 | return _real_send(adapter, request) | |
534 | ||
535 | error_msg = 'Connection refused: {0} {1}'.format( | |
536 | request.method, request.url) | |
684 | if any( | |
685 | [ | |
686 | p.match(request.url) | |
687 | if isinstance(p, Pattern) | |
688 | else request.url.startswith(p) | |
689 | for p in self.passthru_prefixes | |
690 | ] | |
691 | ): | |
692 | logger.info("request.allowed-passthru", extra={"url": request.url}) | |
693 | return _real_send(adapter, request, **kwargs) | |
694 | ||
695 | error_msg = ( | |
696 | "Connection refused by Responses - the call doesn't " | |
697 | "match any registered mock.\n\n" | |
698 | "Request: \n" | |
699 | "- %s %s\n\n" | |
700 | "Available matches:\n" % (request.method, request.url) | |
701 | ) | |
702 | for i, m in enumerate(self._matches): | |
703 | error_msg += "- {} {} {}\n".format( | |
704 | m.method, m.url, match_failed_reasons[i] | |
705 | ) | |
706 | ||
537 | 707 | response = ConnectionError(error_msg) |
538 | 708 | response.request = request |
539 | 709 | |
542 | 712 | raise response |
543 | 713 | |
544 | 714 | try: |
545 | response = adapter.build_response( | |
546 | request, | |
547 | match.get_response(request), ) | |
548 | except Exception as response: | |
715 | response = adapter.build_response(request, match.get_response(request)) | |
716 | except BaseException as response: | |
549 | 717 | match.call_count += 1 |
550 | 718 | self._calls.add(request, response) |
551 | 719 | response = resp_callback(response) if resp_callback else response |
554 | 722 | if not match.stream: |
555 | 723 | response.content # NOQA |
556 | 724 | |
557 | try: | |
558 | resp_cookies = Cookies.from_request(response.headers['set-cookie']) | |
559 | response.cookies = cookiejar_from_dict( | |
560 | dict((v.name, v.value) for _, v in resp_cookies.items())) | |
561 | except (KeyError, TypeError): | |
562 | pass | |
563 | ||
564 | 725 | response = resp_callback(response) if resp_callback else response |
565 | 726 | match.call_count += 1 |
566 | 727 | self._calls.add(request, response) |
577 | 738 | self._patcher.stop() |
578 | 739 | if not self.assert_all_requests_are_fired: |
579 | 740 | return |
741 | ||
580 | 742 | if not allow_assert: |
581 | 743 | return |
744 | ||
582 | 745 | not_called = [m for m in self._matches if m.call_count == 0] |
583 | 746 | if not_called: |
584 | 747 | raise AssertionError( |
585 | 'Not all requests have been executed {0!r}'.format([( | |
586 | match.method, match.url) for match in not_called])) | |
748 | "Not all requests have been executed {0!r}".format( | |
749 | [(match.method, match.url) for match in not_called] | |
750 | ) | |
751 | ) | |
752 | ||
753 | def assert_call_count(self, url, count): | |
754 | call_count = len( | |
755 | [ | |
756 | 1 | |
757 | for call in self.calls | |
758 | if call.request.url == _ensure_url_default_path(url) | |
759 | ] | |
760 | ) | |
761 | if call_count == count: | |
762 | return True | |
763 | else: | |
764 | raise AssertionError( | |
765 | "Expected URL '{0}' to be called {1} times. Called {2} times.".format( | |
766 | url, count, call_count | |
767 | ) | |
768 | ) | |
587 | 769 | |
588 | 770 | |
589 | 771 | # expose default mock namespace |
590 | 772 | mock = _default_mock = RequestsMock(assert_all_requests_are_fired=False) |
591 | __all__ = ['CallbackResponse', 'Response', 'RequestsMock'] | |
592 | for __attr in (a for a in dir(_default_mock) if not a.startswith('_')): | |
593 | __all__.append(__attr) | |
594 | globals()[__attr] = getattr(_default_mock, __attr) | |
773 | __all__ = [ | |
774 | "CallbackResponse", | |
775 | "Response", | |
776 | "RequestsMock", | |
777 | # Exposed by the RequestsMock class: | |
778 | "activate", | |
779 | "add", | |
780 | "add_callback", | |
781 | "add_passthru", | |
782 | "assert_all_requests_are_fired", | |
783 | "assert_call_count", | |
784 | "calls", | |
785 | "DELETE", | |
786 | "GET", | |
787 | "HEAD", | |
788 | "OPTIONS", | |
789 | "passthru_prefixes", | |
790 | "PATCH", | |
791 | "POST", | |
792 | "PUT", | |
793 | "remove", | |
794 | "replace", | |
795 | "reset", | |
796 | "response_callback", | |
797 | "start", | |
798 | "stop", | |
799 | "target", | |
800 | ] | |
801 | ||
802 | activate = _default_mock.activate | |
803 | add = _default_mock.add | |
804 | add_callback = _default_mock.add_callback | |
805 | add_passthru = _default_mock.add_passthru | |
806 | assert_all_requests_are_fired = _default_mock.assert_all_requests_are_fired | |
807 | assert_call_count = _default_mock.assert_call_count | |
808 | calls = _default_mock.calls | |
809 | DELETE = _default_mock.DELETE | |
810 | GET = _default_mock.GET | |
811 | HEAD = _default_mock.HEAD | |
812 | OPTIONS = _default_mock.OPTIONS | |
813 | passthru_prefixes = _default_mock.passthru_prefixes | |
814 | PATCH = _default_mock.PATCH | |
815 | POST = _default_mock.POST | |
816 | PUT = _default_mock.PUT | |
817 | remove = _default_mock.remove | |
818 | replace = _default_mock.replace | |
819 | reset = _default_mock.reset | |
820 | response_callback = _default_mock.response_callback | |
821 | start = _default_mock.start | |
822 | stop = _default_mock.stop | |
823 | target = _default_mock.target |
9 | 9 | """ |
10 | 10 | |
11 | 11 | import sys |
12 | import logging | |
13 | 12 | |
14 | 13 | from setuptools import setup |
15 | 14 | from setuptools.command.test import test as TestCommand |
16 | import pkg_resources | |
17 | 15 | |
18 | 16 | setup_requires = [] |
19 | 17 | |
20 | if 'test' in sys.argv: | |
21 | setup_requires.append('pytest') | |
18 | if "test" in sys.argv: | |
19 | setup_requires.append("pytest") | |
22 | 20 | |
23 | 21 | install_requires = [ |
24 | 'requests>=2.0', | |
25 | 'cookies', | |
26 | 'six' | |
22 | "cookies; python_version < '3.4'", | |
23 | "mock; python_version < '3.3'", | |
24 | "requests>=2.0", | |
25 | "urllib3>=1.25.10", | |
26 | "six", | |
27 | 27 | ] |
28 | 28 | |
29 | 29 | tests_require = [ |
30 | 'pytest', | |
31 | 'coverage >= 3.7.1, < 5.0.0', | |
32 | 'pytest-cov', | |
33 | 'pytest-localserver', | |
34 | 'flake8', | |
30 | "pytest>=4.6,<5.0; python_version < '3.5'", | |
31 | "pytest>=4.6; python_version >= '3.5'", | |
32 | "coverage >= 3.7.1, < 6.0.0", | |
33 | "pytest-cov", | |
34 | "pytest-localserver", | |
35 | "flake8", | |
35 | 36 | ] |
36 | 37 | |
37 | extras_require = { | |
38 | ':python_version in "2.6, 2.7, 3.2"': ['mock'], | |
39 | 'tests': tests_require, | |
40 | } | |
41 | ||
42 | try: | |
43 | if 'bdist_wheel' not in sys.argv: | |
44 | for key, value in extras_require.items(): | |
45 | if key.startswith(':') and pkg_resources.evaluate_marker(key[1:]): | |
46 | install_requires.extend(value) | |
47 | except Exception: | |
48 | logging.getLogger(__name__).exception( | |
49 | 'Something went wrong calculating platform specific dependencies, so ' | |
50 | "you're getting them all!") | |
51 | for key, value in extras_require.items(): | |
52 | if key.startswith(':'): | |
53 | install_requires.extend(value) | |
38 | extras_require = {"tests": tests_require} | |
54 | 39 | |
55 | 40 | |
56 | 41 | class PyTest(TestCommand): |
57 | 42 | def finalize_options(self): |
58 | 43 | TestCommand.finalize_options(self) |
59 | self.test_args = ['test_responses.py'] | |
44 | self.test_args = ["test_responses.py"] | |
60 | 45 | self.test_suite = True |
61 | 46 | |
62 | 47 | def run_tests(self): |
63 | 48 | # import here, cause outside the eggs aren't loaded |
64 | 49 | import pytest |
50 | ||
65 | 51 | errno = pytest.main(self.test_args) |
66 | 52 | sys.exit(errno) |
67 | 53 | |
68 | 54 | |
69 | 55 | setup( |
70 | name='responses', | |
71 | version='0.9.0', | |
72 | author='David Cramer', | |
73 | description=( | |
74 | 'A utility library for mocking out the `requests` Python library.'), | |
75 | url='https://github.com/getsentry/responses', | |
76 | license='Apache 2.0', | |
77 | long_description=open('README.rst').read(), | |
78 | py_modules=['responses', 'test_responses'], | |
56 | name="responses", | |
57 | version="0.12.1", | |
58 | author="David Cramer", | |
59 | description=("A utility library for mocking out the `requests` Python library."), | |
60 | url="https://github.com/getsentry/responses", | |
61 | license="Apache 2.0", | |
62 | long_description=open("README.rst").read(), | |
63 | long_description_content_type="text/x-rst", | |
64 | py_modules=["responses"], | |
79 | 65 | zip_safe=False, |
66 | python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", | |
80 | 67 | install_requires=install_requires, |
81 | 68 | extras_require=extras_require, |
82 | 69 | tests_require=tests_require, |
83 | 70 | setup_requires=setup_requires, |
84 | cmdclass={'test': PyTest}, | |
71 | cmdclass={"test": PyTest}, | |
85 | 72 | include_package_data=True, |
86 | 73 | classifiers=[ |
87 | 'Intended Audience :: Developers', | |
88 | 'Intended Audience :: System Administrators', | |
89 | 'Operating System :: OS Independent', | |
90 | 'Programming Language :: Python :: 2', | |
91 | 'Programming Language :: Python :: 3', 'Topic :: Software Development' | |
92 | ], ) | |
74 | "Intended Audience :: Developers", | |
75 | "Intended Audience :: System Administrators", | |
76 | "Operating System :: OS Independent", | |
77 | "Programming Language :: Python", | |
78 | "Programming Language :: Python :: 2", | |
79 | "Programming Language :: Python :: 2.7", | |
80 | "Programming Language :: Python :: 3", | |
81 | "Programming Language :: Python :: 3.5", | |
82 | "Programming Language :: Python :: 3.6", | |
83 | "Programming Language :: Python :: 3.7", | |
84 | "Programming Language :: Python :: 3.8", | |
85 | "Programming Language :: Python :: 3.9", | |
86 | "Topic :: Software Development", | |
87 | ], | |
88 | ) |
0 | 0 | # coding: utf-8 |
1 | 1 | |
2 | from __future__ import (absolute_import, print_function, division, | |
3 | unicode_literals) | |
4 | ||
2 | from __future__ import absolute_import, print_function, division, unicode_literals | |
3 | ||
4 | import inspect | |
5 | 5 | import re |
6 | import six | |
7 | from io import BufferedReader, BytesIO | |
8 | ||
9 | import pytest | |
6 | 10 | import requests |
7 | 11 | import responses |
8 | import pytest | |
12 | from requests.exceptions import ConnectionError, HTTPError | |
9 | 13 | from responses import BaseResponse, Response |
10 | 14 | |
11 | from inspect import getargspec | |
12 | from requests.exceptions import ConnectionError, HTTPError | |
15 | try: | |
16 | from mock import patch, Mock | |
17 | except ImportError: | |
18 | from unittest.mock import patch, Mock | |
13 | 19 | |
14 | 20 | |
15 | 21 | def assert_reset(): |
17 | 23 | assert len(responses.calls) == 0 |
18 | 24 | |
19 | 25 | |
20 | def assert_response(resp, body=None, content_type='text/plain'): | |
26 | def assert_response(resp, body=None, content_type="text/plain"): | |
21 | 27 | assert resp.status_code == 200 |
22 | assert resp.reason == 'OK' | |
28 | assert resp.reason == "OK" | |
23 | 29 | if content_type is not None: |
24 | assert resp.headers['Content-Type'] == content_type | |
30 | assert resp.headers["Content-Type"] == content_type | |
25 | 31 | else: |
26 | assert 'Content-Type' not in resp.headers | |
32 | assert "Content-Type" not in resp.headers | |
27 | 33 | assert resp.text == body |
28 | 34 | |
29 | 35 | |
30 | 36 | def test_response(): |
31 | 37 | @responses.activate |
32 | 38 | def run(): |
33 | responses.add(responses.GET, 'http://example.com', body=b'test') | |
34 | resp = requests.get('http://example.com') | |
35 | assert_response(resp, 'test') | |
39 | responses.add(responses.GET, "http://example.com", body=b"test") | |
40 | resp = requests.get("http://example.com") | |
41 | assert_response(resp, "test") | |
36 | 42 | assert len(responses.calls) == 1 |
37 | assert responses.calls[0].request.url == 'http://example.com/' | |
38 | assert responses.calls[0].response.content == b'test' | |
39 | ||
40 | resp = requests.get('http://example.com?foo=bar') | |
41 | assert_response(resp, 'test') | |
43 | assert responses.calls[0].request.url == "http://example.com/" | |
44 | assert responses.calls[0].response.content == b"test" | |
45 | ||
46 | resp = requests.get("http://example.com?foo=bar") | |
47 | assert_response(resp, "test") | |
42 | 48 | assert len(responses.calls) == 2 |
43 | assert responses.calls[1].request.url == 'http://example.com/?foo=bar' | |
44 | assert responses.calls[1].response.content == b'test' | |
49 | assert responses.calls[1].request.url == "http://example.com/?foo=bar" | |
50 | assert responses.calls[1].response.content == b"test" | |
51 | ||
52 | run() | |
53 | assert_reset() | |
54 | ||
55 | ||
56 | def test_response_encoded(): | |
57 | @responses.activate | |
58 | def run(): | |
59 | # Path contains urlencoded =/()[] | |
60 | url = "http://example.org/foo.bar%3D%2F%28%29%5B%5D" | |
61 | responses.add(responses.GET, url, body="it works", status=200) | |
62 | resp = requests.get(url) | |
63 | assert_response(resp, "it works") | |
45 | 64 | |
46 | 65 | run() |
47 | 66 | assert_reset() |
51 | 70 | @responses.activate |
52 | 71 | def run(): |
53 | 72 | responses.add( |
54 | responses.Response( | |
55 | method=responses.GET, | |
56 | url='http://example.com', )) | |
57 | resp = requests.get('http://example.com') | |
58 | assert_response(resp, '') | |
73 | responses.Response(method=responses.GET, url="http://example.com") | |
74 | ) | |
75 | resp = requests.get("http://example.com") | |
76 | assert_response(resp, "") | |
59 | 77 | assert len(responses.calls) == 1 |
60 | assert responses.calls[0].request.url == 'http://example.com/' | |
61 | ||
62 | resp = requests.get('http://example.com?foo=bar') | |
63 | assert_response(resp, '') | |
78 | assert responses.calls[0].request.url == "http://example.com/" | |
79 | ||
80 | resp = requests.get("http://example.com?foo=bar") | |
81 | assert_response(resp, "") | |
64 | 82 | assert len(responses.calls) == 2 |
65 | assert responses.calls[1].request.url == 'http://example.com/?foo=bar' | |
66 | ||
67 | ||
68 | @pytest.mark.parametrize('original,replacement', | |
69 | [('http://example.com/two', | |
70 | 'http://example.com/two'), (Response( | |
71 | method=responses.GET, | |
72 | url='http://example.com/two'), Response( | |
73 | method=responses.GET, | |
74 | url='http://example.com/two', | |
75 | body='testtwo')), | |
76 | (re.compile(r'http://example\.com/two'), | |
77 | re.compile(r'http://example\.com/two'))]) | |
83 | assert responses.calls[1].request.url == "http://example.com/?foo=bar" | |
84 | ||
85 | ||
86 | @pytest.mark.parametrize( | |
87 | "original,replacement", | |
88 | [ | |
89 | ("http://example.com/two", "http://example.com/two"), | |
90 | ( | |
91 | Response(method=responses.GET, url="http://example.com/two"), | |
92 | Response( | |
93 | method=responses.GET, url="http://example.com/two", body="testtwo" | |
94 | ), | |
95 | ), | |
96 | ( | |
97 | re.compile(r"http://example\.com/two"), | |
98 | re.compile(r"http://example\.com/two"), | |
99 | ), | |
100 | ], | |
101 | ) | |
78 | 102 | def test_replace(original, replacement): |
79 | 103 | @responses.activate |
80 | 104 | def run(): |
81 | responses.add(responses.GET, 'http://example.com/one', body='test1') | |
105 | responses.add(responses.GET, "http://example.com/one", body="test1") | |
82 | 106 | |
83 | 107 | if isinstance(original, BaseResponse): |
84 | 108 | responses.add(original) |
85 | 109 | else: |
86 | responses.add(responses.GET, original, body='test2') | |
87 | ||
88 | responses.add(responses.GET, 'http://example.com/three', body='test3') | |
89 | responses.add( | |
90 | responses.GET, | |
91 | re.compile(r'http://example\.com/four'), | |
92 | body='test3') | |
110 | responses.add(responses.GET, original, body="test2") | |
111 | ||
112 | responses.add(responses.GET, "http://example.com/three", body="test3") | |
113 | responses.add( | |
114 | responses.GET, re.compile(r"http://example\.com/four"), body="test3" | |
115 | ) | |
93 | 116 | |
94 | 117 | if isinstance(replacement, BaseResponse): |
95 | 118 | responses.replace(replacement) |
96 | 119 | else: |
97 | responses.replace(responses.GET, replacement, body='testtwo') | |
98 | ||
99 | resp = requests.get('http://example.com/two') | |
100 | assert_response(resp, 'testtwo') | |
101 | ||
102 | run() | |
103 | assert_reset() | |
104 | ||
105 | ||
106 | @pytest.mark.parametrize('original,replacement', [ | |
107 | ('http://example.com/one', re.compile(r'http://example\.com/one')), | |
108 | (re.compile(r'http://example\.com/one'), 'http://example.com/one'), | |
109 | ]) | |
120 | responses.replace(responses.GET, replacement, body="testtwo") | |
121 | ||
122 | resp = requests.get("http://example.com/two") | |
123 | assert_response(resp, "testtwo") | |
124 | ||
125 | run() | |
126 | assert_reset() | |
127 | ||
128 | ||
129 | @pytest.mark.parametrize( | |
130 | "original,replacement", | |
131 | [ | |
132 | ("http://example.com/one", re.compile(r"http://example\.com/one")), | |
133 | (re.compile(r"http://example\.com/one"), "http://example.com/one"), | |
134 | ], | |
135 | ) | |
110 | 136 | def test_replace_error(original, replacement): |
111 | 137 | @responses.activate |
112 | 138 | def run(): |
121 | 147 | def test_remove(): |
122 | 148 | @responses.activate |
123 | 149 | def run(): |
124 | responses.add(responses.GET, 'http://example.com/zero') | |
125 | responses.add(responses.GET, 'http://example.com/one') | |
126 | responses.add(responses.GET, 'http://example.com/two') | |
127 | responses.add(responses.GET, re.compile(r'http://example\.com/three')) | |
128 | responses.add(responses.GET, re.compile(r'http://example\.com/four')) | |
150 | responses.add(responses.GET, "http://example.com/zero") | |
151 | responses.add(responses.GET, "http://example.com/one") | |
152 | responses.add(responses.GET, "http://example.com/two") | |
153 | responses.add(responses.GET, re.compile(r"http://example\.com/three")) | |
154 | responses.add(responses.GET, re.compile(r"http://example\.com/four")) | |
129 | 155 | re.purge() |
130 | responses.remove(responses.GET, 'http://example.com/two') | |
131 | responses.remove( | |
132 | Response(method=responses.GET, url='http://example.com/zero')) | |
133 | responses.remove(responses.GET, | |
134 | re.compile(r'http://example\.com/four')) | |
156 | responses.remove(responses.GET, "http://example.com/two") | |
157 | responses.remove(Response(method=responses.GET, url="http://example.com/zero")) | |
158 | responses.remove(responses.GET, re.compile(r"http://example\.com/four")) | |
135 | 159 | |
136 | 160 | with pytest.raises(ConnectionError): |
137 | requests.get('http://example.com/zero') | |
138 | requests.get('http://example.com/one') | |
161 | requests.get("http://example.com/zero") | |
162 | requests.get("http://example.com/one") | |
139 | 163 | with pytest.raises(ConnectionError): |
140 | requests.get('http://example.com/two') | |
141 | requests.get('http://example.com/three') | |
164 | requests.get("http://example.com/two") | |
165 | requests.get("http://example.com/three") | |
142 | 166 | with pytest.raises(ConnectionError): |
143 | requests.get('http://example.com/four') | |
144 | ||
145 | run() | |
146 | assert_reset() | |
147 | ||
148 | ||
149 | @pytest.mark.parametrize('args1,kwargs1,args2,kwargs2,expected', [ | |
150 | ((responses.GET, 'a'), {}, (responses.GET, 'a'), {}, True), | |
151 | ((responses.GET, 'a'), {}, (responses.GET, 'b'), {}, False), | |
152 | ((responses.GET, 'a'), {}, (responses.POST, 'a'), {}, False), | |
153 | ((responses.GET, 'a'), { | |
154 | 'match_querystring': True | |
155 | }, (responses.GET, 'a'), {}, True), | |
156 | ]) | |
167 | requests.get("http://example.com/four") | |
168 | ||
169 | run() | |
170 | assert_reset() | |
171 | ||
172 | ||
173 | @pytest.mark.parametrize( | |
174 | "args1,kwargs1,args2,kwargs2,expected", | |
175 | [ | |
176 | ((responses.GET, "a"), {}, (responses.GET, "a"), {}, True), | |
177 | ((responses.GET, "a"), {}, (responses.GET, "b"), {}, False), | |
178 | ((responses.GET, "a"), {}, (responses.POST, "a"), {}, False), | |
179 | ( | |
180 | (responses.GET, "a"), | |
181 | {"match_querystring": True}, | |
182 | (responses.GET, "a"), | |
183 | {}, | |
184 | True, | |
185 | ), | |
186 | ], | |
187 | ) | |
157 | 188 | def test_response_equality(args1, kwargs1, args2, kwargs2, expected): |
158 | 189 | o1 = BaseResponse(*args1, **kwargs1) |
159 | 190 | o2 = BaseResponse(*args2, **kwargs2) |
162 | 193 | |
163 | 194 | |
164 | 195 | def test_response_equality_different_objects(): |
165 | o1 = BaseResponse(method=responses.GET, url='a') | |
166 | o2 = 'str' | |
196 | o1 = BaseResponse(method=responses.GET, url="a") | |
197 | o2 = "str" | |
167 | 198 | assert (o1 == o2) is False |
168 | 199 | assert (o1 != o2) is True |
169 | 200 | |
171 | 202 | def test_connection_error(): |
172 | 203 | @responses.activate |
173 | 204 | def run(): |
174 | responses.add(responses.GET, 'http://example.com') | |
205 | responses.add(responses.GET, "http://example.com") | |
175 | 206 | |
176 | 207 | with pytest.raises(ConnectionError): |
177 | requests.get('http://example.com/foo') | |
208 | requests.get("http://example.com/foo") | |
178 | 209 | |
179 | 210 | assert len(responses.calls) == 1 |
180 | assert responses.calls[0].request.url == 'http://example.com/foo' | |
211 | assert responses.calls[0].request.url == "http://example.com/foo" | |
181 | 212 | assert type(responses.calls[0].response) is ConnectionError |
182 | 213 | assert responses.calls[0].response.request |
183 | 214 | |
188 | 219 | def test_match_querystring(): |
189 | 220 | @responses.activate |
190 | 221 | def run(): |
191 | url = 'http://example.com?test=1&foo=bar' | |
192 | responses.add(responses.GET, url, match_querystring=True, body=b'test') | |
193 | resp = requests.get('http://example.com?test=1&foo=bar') | |
194 | assert_response(resp, 'test') | |
195 | resp = requests.get('http://example.com?foo=bar&test=1') | |
196 | assert_response(resp, 'test') | |
197 | resp = requests.get('http://example.com/?foo=bar&test=1') | |
198 | assert_response(resp, 'test') | |
222 | url = "http://example.com?test=1&foo=bar" | |
223 | responses.add(responses.GET, url, match_querystring=True, body=b"test") | |
224 | resp = requests.get("http://example.com?test=1&foo=bar") | |
225 | assert_response(resp, "test") | |
226 | resp = requests.get("http://example.com?foo=bar&test=1") | |
227 | assert_response(resp, "test") | |
228 | resp = requests.get("http://example.com/?foo=bar&test=1") | |
229 | assert_response(resp, "test") | |
199 | 230 | |
200 | 231 | run() |
201 | 232 | assert_reset() |
205 | 236 | @responses.activate |
206 | 237 | def run(): |
207 | 238 | responses.add( |
208 | responses.GET, | |
209 | 'http://example.com', | |
210 | body=b'test', | |
211 | match_querystring=True) | |
212 | resp = requests.get('http://example.com') | |
213 | assert_response(resp, 'test') | |
214 | resp = requests.get('http://example.com/') | |
215 | assert_response(resp, 'test') | |
239 | responses.GET, "http://example.com", body=b"test", match_querystring=True | |
240 | ) | |
241 | resp = requests.get("http://example.com") | |
242 | assert_response(resp, "test") | |
243 | resp = requests.get("http://example.com/") | |
244 | assert_response(resp, "test") | |
216 | 245 | with pytest.raises(ConnectionError): |
217 | requests.get('http://example.com?query=foo') | |
246 | requests.get("http://example.com?query=foo") | |
218 | 247 | |
219 | 248 | run() |
220 | 249 | assert_reset() |
224 | 253 | @responses.activate |
225 | 254 | def run(): |
226 | 255 | responses.add( |
227 | responses.GET, | |
228 | 'http://example.com/?test=1', | |
229 | match_querystring=True) | |
256 | responses.GET, "http://example.com/?test=1", match_querystring=True | |
257 | ) | |
230 | 258 | |
231 | 259 | with pytest.raises(ConnectionError): |
232 | requests.get('http://example.com/foo/?test=2') | |
260 | requests.get("http://example.com/foo/?test=2") | |
233 | 261 | |
234 | 262 | run() |
235 | 263 | assert_reset() |
243 | 271 | |
244 | 272 | responses.add( |
245 | 273 | responses.GET, |
246 | re.compile(r'http://example\.com/foo/\?test=1'), | |
247 | body='test1', | |
248 | match_querystring=True) | |
249 | ||
250 | resp = requests.get('http://example.com/foo/?test=1') | |
251 | assert_response(resp, 'test1') | |
274 | re.compile(r"http://example\.com/foo/\?test=1"), | |
275 | body="test1", | |
276 | match_querystring=True, | |
277 | ) | |
278 | ||
279 | resp = requests.get("http://example.com/foo/?test=1") | |
280 | assert_response(resp, "test1") | |
252 | 281 | |
253 | 282 | responses.add( |
254 | 283 | responses.GET, |
255 | re.compile(r'http://example\.com/foo/\?test=2'), | |
256 | body='test2', | |
257 | match_querystring=False) | |
258 | ||
259 | resp = requests.get('http://example.com/foo/?test=2') | |
260 | assert_response(resp, 'test2') | |
284 | re.compile(r"http://example\.com/foo/\?test=2"), | |
285 | body="test2", | |
286 | match_querystring=False, | |
287 | ) | |
288 | ||
289 | resp = requests.get("http://example.com/foo/?test=2") | |
290 | assert_response(resp, "test2") | |
261 | 291 | |
262 | 292 | run() |
263 | 293 | assert_reset() |
271 | 301 | |
272 | 302 | responses.add( |
273 | 303 | responses.GET, |
274 | re.compile(r'http://example\.com/foo/\?test=1'), | |
275 | match_querystring=True) | |
304 | re.compile(r"http://example\.com/foo/\?test=1"), | |
305 | match_querystring=True, | |
306 | ) | |
276 | 307 | |
277 | 308 | with pytest.raises(ConnectionError): |
278 | requests.get('http://example.com/foo/?test=3') | |
309 | requests.get("http://example.com/foo/?test=3") | |
279 | 310 | |
280 | 311 | responses.add( |
281 | 312 | responses.GET, |
282 | re.compile(r'http://example\.com/foo/\?test=2'), | |
283 | match_querystring=False) | |
313 | re.compile(r"http://example\.com/foo/\?test=2"), | |
314 | match_querystring=False, | |
315 | ) | |
284 | 316 | |
285 | 317 | with pytest.raises(ConnectionError): |
286 | requests.get('http://example.com/foo/?test=4') | |
318 | requests.get("http://example.com/foo/?test=4") | |
319 | ||
320 | run() | |
321 | assert_reset() | |
322 | ||
323 | ||
324 | def test_match_querystring_auto_activates(): | |
325 | @responses.activate | |
326 | def run(): | |
327 | responses.add(responses.GET, "http://example.com?test=1", body=b"test") | |
328 | resp = requests.get("http://example.com?test=1") | |
329 | assert_response(resp, "test") | |
330 | with pytest.raises(ConnectionError): | |
331 | requests.get("http://example.com/?test=2") | |
332 | ||
333 | run() | |
334 | assert_reset() | |
335 | ||
336 | ||
337 | def test_match_querystring_missing_key(): | |
338 | @responses.activate | |
339 | def run(): | |
340 | responses.add(responses.GET, "http://example.com?foo=1&bar=2", body=b"test") | |
341 | with pytest.raises(ConnectionError): | |
342 | requests.get("http://example.com/?foo=1&baz=2") | |
343 | ||
344 | with pytest.raises(ConnectionError): | |
345 | requests.get("http://example.com/?bar=2&fez=1") | |
287 | 346 | |
288 | 347 | run() |
289 | 348 | assert_reset() |
292 | 351 | def test_accept_string_body(): |
293 | 352 | @responses.activate |
294 | 353 | def run(): |
295 | url = 'http://example.com/' | |
296 | responses.add(responses.GET, url, body='test') | |
297 | resp = requests.get(url) | |
298 | assert_response(resp, 'test') | |
354 | url = "http://example.com/" | |
355 | responses.add(responses.GET, url, body="test") | |
356 | resp = requests.get(url) | |
357 | assert_response(resp, "test") | |
299 | 358 | |
300 | 359 | run() |
301 | 360 | assert_reset() |
304 | 363 | def test_accept_json_body(): |
305 | 364 | @responses.activate |
306 | 365 | def run(): |
307 | content_type = 'application/json' | |
308 | ||
309 | url = 'http://example.com/' | |
366 | content_type = "application/json" | |
367 | ||
368 | url = "http://example.com/" | |
310 | 369 | responses.add(responses.GET, url, json={"message": "success"}) |
311 | 370 | resp = requests.get(url) |
312 | 371 | assert_response(resp, '{"message": "success"}', content_type) |
313 | 372 | |
314 | url = 'http://example.com/1/' | |
373 | url = "http://example.com/1/" | |
315 | 374 | responses.add(responses.GET, url, json=[]) |
316 | 375 | resp = requests.get(url) |
317 | assert_response(resp, '[]', content_type) | |
376 | assert_response(resp, "[]", content_type) | |
318 | 377 | |
319 | 378 | run() |
320 | 379 | assert_reset() |
323 | 382 | def test_no_content_type(): |
324 | 383 | @responses.activate |
325 | 384 | def run(): |
326 | url = 'http://example.com/' | |
327 | responses.add(responses.GET, url, body='test', content_type=None) | |
328 | resp = requests.get(url) | |
329 | assert_response(resp, 'test', content_type=None) | |
385 | url = "http://example.com/" | |
386 | responses.add(responses.GET, url, body="test", content_type=None) | |
387 | resp = requests.get(url) | |
388 | assert_response(resp, "test", content_type=None) | |
330 | 389 | |
331 | 390 | run() |
332 | 391 | assert_reset() |
335 | 394 | def test_arbitrary_status_code(): |
336 | 395 | @responses.activate |
337 | 396 | def run(): |
338 | url = 'http://example.com/' | |
339 | responses.add(responses.GET, url, body='test', status=418) | |
340 | resp = requests.get(url) | |
341 | assert resp.status_code == 418 | |
397 | url = "http://example.com/" | |
398 | responses.add(responses.GET, url, body="test", status=419) | |
399 | resp = requests.get(url) | |
400 | assert resp.status_code == 419 | |
342 | 401 | assert resp.reason is None |
343 | 402 | |
344 | 403 | run() |
348 | 407 | def test_throw_connection_error_explicit(): |
349 | 408 | @responses.activate |
350 | 409 | def run(): |
351 | url = 'http://example.com' | |
352 | exception = HTTPError('HTTP Error') | |
410 | url = "http://example.com" | |
411 | exception = HTTPError("HTTP Error") | |
353 | 412 | responses.add(responses.GET, url, exception) |
354 | 413 | |
355 | 414 | with pytest.raises(HTTPError) as HE: |
356 | 415 | requests.get(url) |
357 | 416 | |
358 | assert str(HE.value) == 'HTTP Error' | |
417 | assert str(HE.value) == "HTTP Error" | |
359 | 418 | |
360 | 419 | run() |
361 | 420 | assert_reset() |
362 | 421 | |
363 | 422 | |
364 | 423 | def test_callback(): |
365 | body = b'test callback' | |
424 | body = b"test callback" | |
366 | 425 | status = 400 |
367 | reason = 'Bad Request' | |
368 | headers = {'foo': 'bar'} | |
369 | url = 'http://example.com/' | |
426 | reason = "Bad Request" | |
427 | headers = { | |
428 | "foo": "bar", | |
429 | "Content-Type": "application/json", | |
430 | "Content-Length": "13", | |
431 | } | |
432 | url = "http://example.com/" | |
370 | 433 | |
371 | 434 | def request_callback(request): |
372 | 435 | return (status, headers, body) |
378 | 441 | assert resp.text == "test callback" |
379 | 442 | assert resp.status_code == status |
380 | 443 | assert resp.reason == reason |
381 | assert 'foo' in resp.headers | |
382 | assert resp.headers['foo'] == 'bar' | |
444 | assert "bar" == resp.headers.get("foo") | |
445 | assert "application/json" == resp.headers.get("Content-Type") | |
446 | assert "13" == resp.headers.get("Content-Length") | |
447 | ||
448 | run() | |
449 | assert_reset() | |
450 | ||
451 | ||
452 | def test_callback_exception_result(): | |
453 | result = Exception() | |
454 | url = "http://example.com/" | |
455 | ||
456 | def request_callback(request): | |
457 | return result | |
458 | ||
459 | @responses.activate | |
460 | def run(): | |
461 | responses.add_callback(responses.GET, url, request_callback) | |
462 | ||
463 | with pytest.raises(Exception) as e: | |
464 | requests.get(url) | |
465 | ||
466 | assert e.value is result | |
467 | ||
468 | run() | |
469 | assert_reset() | |
470 | ||
471 | ||
472 | def test_callback_exception_body(): | |
473 | body = Exception() | |
474 | url = "http://example.com/" | |
475 | ||
476 | def request_callback(request): | |
477 | return (200, {}, body) | |
478 | ||
479 | @responses.activate | |
480 | def run(): | |
481 | responses.add_callback(responses.GET, url, request_callback) | |
482 | ||
483 | with pytest.raises(Exception) as e: | |
484 | requests.get(url) | |
485 | ||
486 | assert e.value is body | |
383 | 487 | |
384 | 488 | run() |
385 | 489 | assert_reset() |
386 | 490 | |
387 | 491 | |
388 | 492 | def test_callback_no_content_type(): |
389 | body = b'test callback' | |
493 | body = b"test callback" | |
390 | 494 | status = 400 |
391 | reason = 'Bad Request' | |
392 | headers = {'foo': 'bar'} | |
393 | url = 'http://example.com/' | |
495 | reason = "Bad Request" | |
496 | headers = {"foo": "bar"} | |
497 | url = "http://example.com/" | |
394 | 498 | |
395 | 499 | def request_callback(request): |
396 | 500 | return (status, headers, body) |
397 | 501 | |
398 | 502 | @responses.activate |
399 | 503 | def run(): |
400 | responses.add_callback( | |
401 | responses.GET, url, request_callback, content_type=None) | |
504 | responses.add_callback(responses.GET, url, request_callback, content_type=None) | |
402 | 505 | resp = requests.get(url) |
403 | 506 | assert resp.text == "test callback" |
404 | 507 | assert resp.status_code == status |
405 | 508 | assert resp.reason == reason |
406 | assert 'foo' in resp.headers | |
407 | assert 'Content-Type' not in resp.headers | |
509 | assert "foo" in resp.headers | |
510 | assert "Content-Type" not in resp.headers | |
511 | ||
512 | run() | |
513 | assert_reset() | |
514 | ||
515 | ||
516 | def test_callback_content_type_dict(): | |
517 | def request_callback(request): | |
518 | return ( | |
519 | 200, | |
520 | {"Content-Type": "application/json"}, | |
521 | b"foo", | |
522 | ) | |
523 | ||
524 | @responses.activate | |
525 | def run(): | |
526 | responses.add_callback("GET", "http://mockhost/.foo", callback=request_callback) | |
527 | resp = requests.get("http://mockhost/.foo") | |
528 | assert resp.text == "foo" | |
529 | assert resp.headers["content-type"] == "application/json" | |
530 | ||
531 | run() | |
532 | assert_reset() | |
533 | ||
534 | ||
535 | def test_callback_content_type_tuple(): | |
536 | def request_callback(request): | |
537 | return ( | |
538 | 200, | |
539 | [("Content-Type", "application/json")], | |
540 | b"foo", | |
541 | ) | |
542 | ||
543 | @responses.activate | |
544 | def run(): | |
545 | responses.add_callback("GET", "http://mockhost/.foo", callback=request_callback) | |
546 | resp = requests.get("http://mockhost/.foo") | |
547 | assert resp.text == "foo" | |
548 | assert resp.headers["content-type"] == "application/json" | |
408 | 549 | |
409 | 550 | run() |
410 | 551 | assert_reset() |
413 | 554 | def test_regular_expression_url(): |
414 | 555 | @responses.activate |
415 | 556 | def run(): |
416 | url = re.compile(r'https?://(.*\.)?example.com') | |
417 | responses.add(responses.GET, url, body=b'test') | |
418 | ||
419 | resp = requests.get('http://example.com') | |
420 | assert_response(resp, 'test') | |
421 | ||
422 | resp = requests.get('https://example.com') | |
423 | assert_response(resp, 'test') | |
424 | ||
425 | resp = requests.get('https://uk.example.com') | |
426 | assert_response(resp, 'test') | |
557 | url = re.compile(r"https?://(.*\.)?example.com") | |
558 | responses.add(responses.GET, url, body=b"test") | |
559 | ||
560 | resp = requests.get("http://example.com") | |
561 | assert_response(resp, "test") | |
562 | ||
563 | resp = requests.get("https://example.com") | |
564 | assert_response(resp, "test") | |
565 | ||
566 | resp = requests.get("https://uk.example.com") | |
567 | assert_response(resp, "test") | |
427 | 568 | |
428 | 569 | with pytest.raises(ConnectionError): |
429 | requests.get('https://uk.exaaample.com') | |
570 | requests.get("https://uk.exaaample.com") | |
430 | 571 | |
431 | 572 | run() |
432 | 573 | assert_reset() |
436 | 577 | @responses.activate |
437 | 578 | def run(): |
438 | 579 | url = "http://example.com" |
439 | responses.add(responses.GET, url, body=b'test') | |
580 | responses.add(responses.GET, url, body=b"test") | |
440 | 581 | |
441 | 582 | calls = [0] |
442 | 583 | |
457 | 598 | session.mount("http://", DummyAdapter()) |
458 | 599 | |
459 | 600 | resp = session.get(url) |
460 | assert_response(resp, 'test') | |
601 | assert_response(resp, "test") | |
461 | 602 | |
462 | 603 | run() |
463 | 604 | |
465 | 606 | def test_responses_as_context_manager(): |
466 | 607 | def run(): |
467 | 608 | with responses.mock: |
468 | responses.add(responses.GET, 'http://example.com', body=b'test') | |
469 | resp = requests.get('http://example.com') | |
470 | assert_response(resp, 'test') | |
609 | responses.add(responses.GET, "http://example.com", body=b"test") | |
610 | resp = requests.get("http://example.com") | |
611 | assert_response(resp, "test") | |
471 | 612 | assert len(responses.calls) == 1 |
472 | assert responses.calls[0].request.url == 'http://example.com/' | |
473 | assert responses.calls[0].response.content == b'test' | |
474 | ||
475 | resp = requests.get('http://example.com?foo=bar') | |
476 | assert_response(resp, 'test') | |
613 | assert responses.calls[0].request.url == "http://example.com/" | |
614 | assert responses.calls[0].response.content == b"test" | |
615 | ||
616 | resp = requests.get("http://example.com?foo=bar") | |
617 | assert_response(resp, "test") | |
477 | 618 | assert len(responses.calls) == 2 |
478 | assert (responses.calls[1].request.url == | |
479 | 'http://example.com/?foo=bar') | |
480 | assert responses.calls[1].response.content == b'test' | |
619 | assert responses.calls[1].request.url == "http://example.com/?foo=bar" | |
620 | assert responses.calls[1].response.content == b"test" | |
481 | 621 | |
482 | 622 | run() |
483 | 623 | assert_reset() |
488 | 628 | return (a, b) |
489 | 629 | |
490 | 630 | decorated_test_function = responses.activate(test_function) |
491 | assert getargspec(test_function) == getargspec(decorated_test_function) | |
631 | if hasattr(inspect, "signature"): | |
632 | assert inspect.signature(test_function) == inspect.signature( | |
633 | decorated_test_function | |
634 | ) | |
635 | else: | |
636 | assert inspect.getargspec(test_function) == inspect.getargspec( | |
637 | decorated_test_function | |
638 | ) | |
639 | assert decorated_test_function(1, 2) == test_function(1, 2) | |
640 | assert decorated_test_function(3) == test_function(3) | |
641 | ||
642 | ||
643 | def test_activate_mock_interaction(): | |
644 | @patch("sys.stdout") | |
645 | def test_function(mock_stdout): | |
646 | return mock_stdout | |
647 | ||
648 | decorated_test_function = responses.activate(test_function) | |
649 | if hasattr(inspect, "signature"): | |
650 | assert inspect.signature(test_function) == inspect.signature( | |
651 | decorated_test_function | |
652 | ) | |
653 | else: | |
654 | assert inspect.getargspec(test_function) == inspect.getargspec( | |
655 | decorated_test_function | |
656 | ) | |
657 | ||
658 | value = test_function() | |
659 | assert isinstance(value, Mock) | |
660 | ||
661 | value = decorated_test_function() | |
662 | assert isinstance(value, Mock) | |
663 | ||
664 | ||
665 | @pytest.mark.skipif(six.PY2, reason="Cannot run in python2") | |
666 | def test_activate_doesnt_change_signature_with_return_type(): | |
667 | def test_function(a, b=None): | |
668 | return (a, b) | |
669 | ||
670 | # Add type annotations as they are syntax errors in py2. | |
671 | # Use a class to test for import errors in evaled code. | |
672 | test_function.__annotations__["return"] = Mock | |
673 | test_function.__annotations__["a"] = Mock | |
674 | ||
675 | decorated_test_function = responses.activate(test_function) | |
676 | if hasattr(inspect, "signature"): | |
677 | assert inspect.signature(test_function) == inspect.signature( | |
678 | decorated_test_function | |
679 | ) | |
680 | else: | |
681 | assert inspect.getargspec(test_function) == inspect.getargspec( | |
682 | decorated_test_function | |
683 | ) | |
492 | 684 | assert decorated_test_function(1, 2) == test_function(1, 2) |
493 | 685 | assert decorated_test_function(3) == test_function(3) |
494 | 686 | |
498 | 690 | def test_function(self, a, b=None): |
499 | 691 | return (self, a, b) |
500 | 692 | |
693 | decorated_test_function = responses.activate(test_function) | |
694 | ||
501 | 695 | test_case = TestCase() |
502 | argspec = getargspec(test_case.test_function) | |
503 | decorated_test_function = responses.activate(test_case.test_function) | |
504 | assert argspec == getargspec(decorated_test_function) | |
505 | assert decorated_test_function(1, 2) == test_case.test_function(1, 2) | |
506 | assert decorated_test_function(3) == test_case.test_function(3) | |
696 | assert test_case.decorated_test_function(1, 2) == test_case.test_function(1, 2) | |
697 | assert test_case.decorated_test_function(3) == test_case.test_function(3) | |
507 | 698 | |
508 | 699 | |
509 | 700 | def test_response_cookies(): |
510 | body = b'test callback' | |
701 | body = b"test callback" | |
511 | 702 | status = 200 |
512 | headers = {'set-cookie': 'session_id=12345; a=b; c=d'} | |
513 | url = 'http://example.com/' | |
703 | headers = {"set-cookie": "session_id=12345; a=b; c=d"} | |
704 | url = "http://example.com/" | |
514 | 705 | |
515 | 706 | def request_callback(request): |
516 | 707 | return (status, headers, body) |
521 | 712 | resp = requests.get(url) |
522 | 713 | assert resp.text == "test callback" |
523 | 714 | assert resp.status_code == status |
524 | assert 'session_id' in resp.cookies | |
525 | assert resp.cookies['session_id'] == '12345' | |
526 | assert resp.cookies['a'] == 'b' | |
527 | assert resp.cookies['c'] == 'd' | |
715 | assert "session_id" in resp.cookies | |
716 | assert resp.cookies["session_id"] == "12345" | |
717 | assert set(resp.cookies.keys()) == set(["session_id"]) | |
718 | ||
719 | run() | |
720 | assert_reset() | |
721 | ||
722 | ||
723 | def test_response_secure_cookies(): | |
724 | body = b"test callback" | |
725 | status = 200 | |
726 | headers = {"set-cookie": "session_id=12345; a=b; c=d; secure"} | |
727 | url = "http://example.com/" | |
728 | ||
729 | def request_callback(request): | |
730 | return (status, headers, body) | |
731 | ||
732 | @responses.activate | |
733 | def run(): | |
734 | responses.add_callback(responses.GET, url, request_callback) | |
735 | resp = requests.get(url) | |
736 | assert resp.text == "test callback" | |
737 | assert resp.status_code == status | |
738 | assert "session_id" in resp.cookies | |
739 | assert resp.cookies["session_id"] == "12345" | |
740 | assert set(resp.cookies.keys()) == set(["session_id"]) | |
741 | ||
742 | run() | |
743 | assert_reset() | |
744 | ||
745 | ||
746 | def test_response_cookies_multiple(): | |
747 | body = b"test callback" | |
748 | status = 200 | |
749 | headers = [ | |
750 | ("set-cookie", "1P_JAR=2019-12-31-23; path=/; domain=.example.com; HttpOnly"), | |
751 | ("set-cookie", "NID=some=value; path=/; domain=.example.com; secure"), | |
752 | ] | |
753 | url = "http://example.com/" | |
754 | ||
755 | def request_callback(request): | |
756 | return (status, headers, body) | |
757 | ||
758 | @responses.activate | |
759 | def run(): | |
760 | responses.add_callback(responses.GET, url, request_callback) | |
761 | resp = requests.get(url) | |
762 | assert resp.text == "test callback" | |
763 | assert resp.status_code == status | |
764 | assert set(resp.cookies.keys()) == set(["1P_JAR", "NID"]) | |
765 | assert resp.cookies["1P_JAR"] == "2019-12-31-23" | |
766 | assert resp.cookies["NID"] == "some=value" | |
528 | 767 | |
529 | 768 | run() |
530 | 769 | assert_reset() |
539 | 778 | return resp |
540 | 779 | |
541 | 780 | with responses.RequestsMock(response_callback=response_callback) as m: |
542 | m.add(responses.GET, 'http://example.com', body=b'test') | |
543 | resp = requests.get('http://example.com') | |
781 | m.add(responses.GET, "http://example.com", body=b"test") | |
782 | resp = requests.get("http://example.com") | |
544 | 783 | assert resp.text == "test" |
545 | assert hasattr(resp, '_is_mocked') | |
784 | assert hasattr(resp, "_is_mocked") | |
546 | 785 | assert resp._is_mocked is True |
547 | 786 | |
548 | 787 | run() |
554 | 793 | |
555 | 794 | def run(): |
556 | 795 | with responses.RequestsMock() as m: |
557 | with open('README.rst', 'rb') as out: | |
558 | m.add( | |
559 | responses.GET, 'http://example.com', body=out, stream=True) | |
560 | resp = requests.get('http://example.com') | |
561 | with open('README.rst', 'r') as out: | |
796 | with open("README.rst", "rb") as out: | |
797 | m.add(responses.GET, "http://example.com", body=out, stream=True) | |
798 | resp = requests.get("http://example.com") | |
799 | with open("README.rst", "r") as out: | |
562 | 800 | assert resp.text == out.read() |
563 | 801 | |
564 | 802 | |
565 | 803 | def test_assert_all_requests_are_fired(): |
804 | def request_callback(request): | |
805 | raise BaseException() | |
806 | ||
566 | 807 | def run(): |
567 | 808 | with pytest.raises(AssertionError) as excinfo: |
568 | with responses.RequestsMock( | |
569 | assert_all_requests_are_fired=True) as m: | |
570 | m.add(responses.GET, 'http://example.com', body=b'test') | |
571 | assert 'http://example.com' in str(excinfo.value) | |
572 | assert responses.GET in str(excinfo) | |
809 | with responses.RequestsMock(assert_all_requests_are_fired=True) as m: | |
810 | m.add(responses.GET, "http://example.com", body=b"test") | |
811 | assert "http://example.com" in str(excinfo.value) | |
812 | assert responses.GET in str(excinfo.value) | |
573 | 813 | |
574 | 814 | # check that assert_all_requests_are_fired default to True |
575 | 815 | with pytest.raises(AssertionError): |
576 | 816 | with responses.RequestsMock() as m: |
577 | m.add(responses.GET, 'http://example.com', body=b'test') | |
817 | m.add(responses.GET, "http://example.com", body=b"test") | |
578 | 818 | |
579 | 819 | # check that assert_all_requests_are_fired doesn't swallow exceptions |
580 | 820 | with pytest.raises(ValueError): |
581 | 821 | with responses.RequestsMock() as m: |
582 | m.add(responses.GET, 'http://example.com', body=b'test') | |
822 | m.add(responses.GET, "http://example.com", body=b"test") | |
583 | 823 | raise ValueError() |
584 | 824 | |
585 | 825 | # check that assert_all_requests_are_fired=True doesn't remove urls |
586 | 826 | with responses.RequestsMock(assert_all_requests_are_fired=True) as m: |
587 | m.add(responses.GET, 'http://example.com', body=b'test') | |
827 | m.add(responses.GET, "http://example.com", body=b"test") | |
588 | 828 | assert len(m._matches) == 1 |
589 | requests.get('http://example.com') | |
829 | requests.get("http://example.com") | |
590 | 830 | assert len(m._matches) == 1 |
591 | 831 | |
592 | 832 | # check that assert_all_requests_are_fired=True counts mocked errors |
593 | 833 | with responses.RequestsMock(assert_all_requests_are_fired=True) as m: |
594 | m.add(responses.GET, 'http://example.com', body=Exception()) | |
834 | m.add(responses.GET, "http://example.com", body=Exception()) | |
595 | 835 | assert len(m._matches) == 1 |
596 | 836 | with pytest.raises(Exception): |
597 | requests.get('http://example.com') | |
837 | requests.get("http://example.com") | |
598 | 838 | assert len(m._matches) == 1 |
599 | 839 | |
840 | with responses.RequestsMock(assert_all_requests_are_fired=True) as m: | |
841 | m.add_callback(responses.GET, "http://example.com", request_callback) | |
842 | assert len(m._matches) == 1 | |
843 | with pytest.raises(BaseException): | |
844 | requests.get("http://example.com") | |
845 | assert len(m._matches) == 1 | |
846 | ||
600 | 847 | run() |
601 | 848 | assert_reset() |
602 | 849 | |
603 | 850 | |
604 | 851 | def test_allow_redirects_samehost(): |
605 | redirecting_url = 'http://example.com' | |
606 | final_url_path = '/1' | |
607 | final_url = '{0}{1}'.format(redirecting_url, final_url_path) | |
608 | url_re = re.compile(r'^http://example.com(/)?(\d+)?$') | |
852 | redirecting_url = "http://example.com" | |
853 | final_url_path = "/1" | |
854 | final_url = "{0}{1}".format(redirecting_url, final_url_path) | |
855 | url_re = re.compile(r"^http://example.com(/)?(\d+)?$") | |
609 | 856 | |
610 | 857 | def request_callback(request): |
611 | 858 | # endpoint of chained redirect |
612 | 859 | if request.url.endswith(final_url_path): |
613 | return 200, (), b'test' | |
860 | return 200, (), b"test" | |
861 | ||
614 | 862 | # otherwise redirect to an integer path |
615 | 863 | else: |
616 | if request.url.endswith('/0'): | |
864 | if request.url.endswith("/0"): | |
617 | 865 | n = 1 |
618 | 866 | else: |
619 | 867 | n = 0 |
620 | redirect_headers = {'location': '/{0!s}'.format(n)} | |
868 | redirect_headers = {"location": "/{0!s}".format(n)} | |
621 | 869 | return 301, redirect_headers, None |
622 | 870 | |
623 | 871 | def run(): |
624 | 872 | # setup redirect |
625 | 873 | with responses.mock: |
626 | 874 | responses.add_callback(responses.GET, url_re, request_callback) |
627 | resp_no_redirects = requests.get( | |
628 | redirecting_url, allow_redirects=False) | |
875 | resp_no_redirects = requests.get(redirecting_url, allow_redirects=False) | |
629 | 876 | assert resp_no_redirects.status_code == 301 |
630 | 877 | assert len(responses.calls) == 1 # 1x300 |
631 | 878 | assert responses.calls[0][1].status_code == 301 |
633 | 880 | |
634 | 881 | with responses.mock: |
635 | 882 | responses.add_callback(responses.GET, url_re, request_callback) |
636 | resp_yes_redirects = requests.get( | |
637 | redirecting_url, allow_redirects=True) | |
883 | resp_yes_redirects = requests.get(redirecting_url, allow_redirects=True) | |
638 | 884 | assert len(responses.calls) == 3 # 2x300 + 1x200 |
639 | 885 | assert len(resp_yes_redirects.history) == 2 |
640 | 886 | assert resp_yes_redirects.status_code == 200 |
648 | 894 | |
649 | 895 | |
650 | 896 | def test_handles_unicode_querystring(): |
651 | url = u'http://example.com/test?type=2&ie=utf8&query=汉字' | |
652 | ||
653 | @responses.activate | |
654 | def run(): | |
655 | responses.add(responses.GET, url, body='test', match_querystring=True) | |
656 | ||
657 | resp = requests.get(url) | |
658 | ||
659 | assert_response(resp, 'test') | |
897 | url = "http://example.com/test?type=2&ie=utf8&query=汉字" | |
898 | ||
899 | @responses.activate | |
900 | def run(): | |
901 | responses.add(responses.GET, url, body="test", match_querystring=True) | |
902 | ||
903 | resp = requests.get(url) | |
904 | ||
905 | assert_response(resp, "test") | |
660 | 906 | |
661 | 907 | run() |
662 | 908 | assert_reset() |
663 | 909 | |
664 | 910 | |
665 | 911 | def test_handles_unicode_url(): |
666 | url = u'http://www.संजाल.भारत/hi/वेबसाइट-डिजाइन' | |
667 | ||
668 | @responses.activate | |
669 | def run(): | |
670 | responses.add(responses.GET, url, body='test') | |
671 | ||
672 | resp = requests.get(url) | |
673 | ||
674 | assert_response(resp, 'test') | |
912 | url = "http://www.संजाल.भारत/hi/वेबसाइट-डिजाइन" | |
913 | ||
914 | @responses.activate | |
915 | def run(): | |
916 | responses.add(responses.GET, url, body="test") | |
917 | ||
918 | resp = requests.get(url) | |
919 | ||
920 | assert_response(resp, "test") | |
921 | ||
922 | run() | |
923 | assert_reset() | |
924 | ||
925 | ||
926 | def test_handles_unicode_body(): | |
927 | url = "http://example.com/test" | |
928 | ||
929 | @responses.activate | |
930 | def run(): | |
931 | responses.add(responses.GET, url, body="михољско лето") | |
932 | ||
933 | resp = requests.get(url) | |
934 | ||
935 | assert_response(resp, "михољско лето", content_type="text/plain; charset=utf-8") | |
936 | ||
937 | run() | |
938 | assert_reset() | |
939 | ||
940 | ||
941 | def test_handles_buffered_reader_body(): | |
942 | url = "http://example.com/test" | |
943 | ||
944 | @responses.activate | |
945 | def run(): | |
946 | responses.add(responses.GET, url, body=BufferedReader(BytesIO(b"test"))) | |
947 | ||
948 | resp = requests.get(url) | |
949 | ||
950 | assert_response(resp, "test") | |
675 | 951 | |
676 | 952 | run() |
677 | 953 | assert_reset() |
681 | 957 | @responses.activate |
682 | 958 | def run(): |
683 | 959 | responses.add( |
960 | responses.GET, "http://example.com", body="", headers={"X-Test": "foo"} | |
961 | ) | |
962 | resp = requests.get("http://example.com") | |
963 | assert resp.headers["X-Test"] == "foo" | |
964 | ||
965 | run() | |
966 | assert_reset() | |
967 | ||
968 | ||
969 | def test_legacy_adding_headers(): | |
970 | @responses.activate | |
971 | def run(): | |
972 | responses.add( | |
684 | 973 | responses.GET, |
685 | 'http://example.com', | |
686 | body='', | |
687 | headers={ | |
688 | 'X-Test': 'foo', | |
689 | }) | |
690 | resp = requests.get('http://example.com') | |
691 | assert resp.headers['X-Test'] == 'foo' | |
692 | ||
693 | run() | |
694 | assert_reset() | |
695 | ||
696 | ||
697 | def test_legacy_adding_headers(): | |
698 | @responses.activate | |
699 | def run(): | |
700 | responses.add( | |
701 | responses.GET, | |
702 | 'http://example.com', | |
703 | body='', | |
704 | adding_headers={ | |
705 | 'X-Test': 'foo', | |
706 | }) | |
707 | resp = requests.get('http://example.com') | |
708 | assert resp.headers['X-Test'] == 'foo' | |
974 | "http://example.com", | |
975 | body="", | |
976 | adding_headers={"X-Test": "foo"}, | |
977 | ) | |
978 | resp = requests.get("http://example.com") | |
979 | assert resp.headers["X-Test"] == "foo" | |
709 | 980 | |
710 | 981 | run() |
711 | 982 | assert_reset() |
714 | 985 | def test_multiple_responses(): |
715 | 986 | @responses.activate |
716 | 987 | def run(): |
717 | responses.add(responses.GET, 'http://example.com', body='test') | |
718 | responses.add(responses.GET, 'http://example.com', body='rest') | |
719 | ||
720 | resp = requests.get('http://example.com') | |
721 | assert_response(resp, 'test') | |
722 | resp = requests.get('http://example.com') | |
723 | assert_response(resp, 'rest') | |
988 | responses.add(responses.GET, "http://example.com", body="test") | |
989 | responses.add(responses.GET, "http://example.com", body="rest") | |
990 | ||
991 | resp = requests.get("http://example.com") | |
992 | assert_response(resp, "test") | |
993 | resp = requests.get("http://example.com") | |
994 | assert_response(resp, "rest") | |
724 | 995 | # After all responses are used, last response should be repeated |
725 | resp = requests.get('http://example.com') | |
726 | assert_response(resp, 'rest') | |
996 | resp = requests.get("http://example.com") | |
997 | assert_response(resp, "rest") | |
727 | 998 | |
728 | 999 | run() |
729 | 1000 | assert_reset() |
732 | 1003 | def test_multiple_urls(): |
733 | 1004 | @responses.activate |
734 | 1005 | def run(): |
735 | responses.add(responses.GET, 'http://example.com/one', body='one') | |
736 | responses.add(responses.GET, 'http://example.com/two', body='two') | |
737 | ||
738 | resp = requests.get('http://example.com/two') | |
739 | assert_response(resp, 'two') | |
740 | resp = requests.get('http://example.com/one') | |
741 | assert_response(resp, 'one') | |
1006 | responses.add(responses.GET, "http://example.com/one", body="one") | |
1007 | responses.add(responses.GET, "http://example.com/two", body="two") | |
1008 | ||
1009 | resp = requests.get("http://example.com/two") | |
1010 | assert_response(resp, "two") | |
1011 | resp = requests.get("http://example.com/one") | |
1012 | assert_response(resp, "one") | |
1013 | ||
1014 | run() | |
1015 | assert_reset() | |
1016 | ||
1017 | ||
1018 | def test_multiple_methods(): | |
1019 | @responses.activate | |
1020 | def run(): | |
1021 | responses.add(responses.GET, "http://example.com/one", body="gotcha") | |
1022 | responses.add(responses.POST, "http://example.com/one", body="posted") | |
1023 | ||
1024 | resp = requests.get("http://example.com/one") | |
1025 | assert_response(resp, "gotcha") | |
1026 | resp = requests.post("http://example.com/one") | |
1027 | assert_response(resp, "posted") | |
742 | 1028 | |
743 | 1029 | run() |
744 | 1030 | assert_reset() |
745 | 1031 | |
746 | 1032 | |
747 | 1033 | def test_passthru(httpserver): |
748 | httpserver.serve_content('OK', headers={'Content-Type': 'text/plain'}) | |
1034 | httpserver.serve_content("OK", headers={"Content-Type": "text/plain"}) | |
749 | 1035 | |
750 | 1036 | @responses.activate |
751 | 1037 | def run(): |
752 | 1038 | responses.add_passthru(httpserver.url) |
753 | responses.add( | |
754 | responses.GET, '{}/one'.format(httpserver.url), body='one') | |
755 | responses.add(responses.GET, 'http://example.com/two', body='two') | |
756 | ||
757 | resp = requests.get('http://example.com/two') | |
758 | assert_response(resp, 'two') | |
759 | resp = requests.get('{}/one'.format(httpserver.url)) | |
760 | assert_response(resp, 'one') | |
1039 | responses.add(responses.GET, "{}/one".format(httpserver.url), body="one") | |
1040 | responses.add(responses.GET, "http://example.com/two", body="two") | |
1041 | ||
1042 | resp = requests.get("http://example.com/two") | |
1043 | assert_response(resp, "two") | |
1044 | resp = requests.get("{}/one".format(httpserver.url)) | |
1045 | assert_response(resp, "one") | |
761 | 1046 | resp = requests.get(httpserver.url) |
762 | assert_response(resp, 'OK') | |
1047 | assert_response(resp, "OK") | |
1048 | ||
1049 | run() | |
1050 | assert_reset() | |
1051 | ||
1052 | ||
1053 | def test_passthru_regex(httpserver): | |
1054 | httpserver.serve_content("OK", headers={"Content-Type": "text/plain"}) | |
1055 | ||
1056 | @responses.activate | |
1057 | def run(): | |
1058 | responses.add_passthru(re.compile("{}/\\w+".format(httpserver.url))) | |
1059 | responses.add(responses.GET, "{}/one".format(httpserver.url), body="one") | |
1060 | responses.add(responses.GET, "http://example.com/two", body="two") | |
1061 | ||
1062 | resp = requests.get("http://example.com/two") | |
1063 | assert_response(resp, "two") | |
1064 | resp = requests.get("{}/one".format(httpserver.url)) | |
1065 | assert_response(resp, "one") | |
1066 | resp = requests.get("{}/two".format(httpserver.url)) | |
1067 | assert_response(resp, "OK") | |
1068 | resp = requests.get("{}/three".format(httpserver.url)) | |
1069 | assert_response(resp, "OK") | |
763 | 1070 | |
764 | 1071 | run() |
765 | 1072 | assert_reset() |
768 | 1075 | def test_method_named_param(): |
769 | 1076 | @responses.activate |
770 | 1077 | def run(): |
771 | responses.add( | |
772 | method=responses.GET, url='http://example.com', body='OK') | |
773 | resp = requests.get('http://example.com') | |
774 | assert_response(resp, 'OK') | |
1078 | responses.add(method=responses.GET, url="http://example.com", body="OK") | |
1079 | resp = requests.get("http://example.com") | |
1080 | assert_response(resp, "OK") | |
775 | 1081 | |
776 | 1082 | run() |
777 | 1083 | assert_reset() |
781 | 1087 | @responses.activate |
782 | 1088 | def run(): |
783 | 1089 | with responses.RequestsMock() as m: |
784 | url = u'http://موقع.وزارة-الاتصالات.مصر/' | |
785 | clean_url = 'http://xn--4gbrim.xn----ymcbaaajlc6dj7bxne2c.xn--wgbh1c/' | |
1090 | url = "http://موقع.وزارة-الاتصالات.مصر/" | |
1091 | clean_url = "http://xn--4gbrim.xn----ymcbaaajlc6dj7bxne2c.xn--wgbh1c/" | |
786 | 1092 | m.add_passthru(url) |
787 | 1093 | assert m.passthru_prefixes[0] == clean_url |
788 | 1094 | |
791 | 1097 | |
792 | 1098 | |
793 | 1099 | def test_custom_target(monkeypatch): |
794 | requests_mock = responses.RequestsMock(target='something.else') | |
1100 | requests_mock = responses.RequestsMock(target="something.else") | |
795 | 1101 | std_mock_mock = responses.std_mock.MagicMock() |
796 | 1102 | patch_mock = std_mock_mock.patch |
797 | monkeypatch.setattr(responses, 'std_mock', std_mock_mock) | |
1103 | monkeypatch.setattr(responses, "std_mock", std_mock_mock) | |
798 | 1104 | requests_mock.start() |
799 | 1105 | assert len(patch_mock.call_args_list) == 1 |
800 | assert patch_mock.call_args[1]['target'] == 'something.else' | |
1106 | assert patch_mock.call_args[1]["target"] == "something.else" | |
1107 | ||
1108 | ||
1109 | def _quote(s): | |
1110 | return responses.quote(responses._ensure_str(s)) | |
1111 | ||
1112 | ||
1113 | def test_cookies_from_headers(): | |
1114 | text = "こんにちは/世界" | |
1115 | quoted_text = _quote(text) | |
1116 | expected = {"x": "a", "y": quoted_text} | |
1117 | headers = {"set-cookie": "; ".join(k + "=" + v for k, v in expected.items())} | |
1118 | cookiejar = responses._cookies_from_headers(headers) | |
1119 | for k, v in cookiejar.items(): | |
1120 | assert isinstance(v, str) | |
1121 | assert v == expected[k] | |
1122 | ||
1123 | ||
1124 | @pytest.mark.parametrize( | |
1125 | "url", | |
1126 | ( | |
1127 | "http://example.com", | |
1128 | "http://example.com/some/path", | |
1129 | "http://example.com/other/path/", | |
1130 | ), | |
1131 | ) | |
1132 | def test_request_param(url): | |
1133 | @responses.activate | |
1134 | def run(): | |
1135 | params = {"hello": "world", "example": "params"} | |
1136 | responses.add( | |
1137 | method=responses.GET, | |
1138 | url="{0}?hello=world".format(url), | |
1139 | body="test", | |
1140 | match_querystring=False, | |
1141 | ) | |
1142 | resp = requests.get(url, params=params) | |
1143 | assert_response(resp, "test") | |
1144 | assert resp.request.params == params | |
1145 | ||
1146 | resp = requests.get(url) | |
1147 | assert_response(resp, "test") | |
1148 | assert resp.request.params == {} | |
1149 | ||
1150 | run() | |
1151 | assert_reset() | |
1152 | ||
1153 | ||
1154 | def test_request_param_with_multiple_values_for_the_same_key(): | |
1155 | @responses.activate | |
1156 | def run(): | |
1157 | url = "http://example.com" | |
1158 | params = {"key1": ["one", "two"], "key2": "three"} | |
1159 | responses.add( | |
1160 | method=responses.GET, | |
1161 | url=url, | |
1162 | body="test", | |
1163 | ) | |
1164 | resp = requests.get(url, params=params) | |
1165 | assert_response(resp, "test") | |
1166 | assert resp.request.params == params | |
1167 | ||
1168 | run() | |
1169 | assert_reset() | |
1170 | ||
1171 | ||
1172 | @pytest.mark.parametrize( | |
1173 | "url", ("http://example.com", "http://example.com?hello=world") | |
1174 | ) | |
1175 | def test_assert_call_count(url): | |
1176 | @responses.activate | |
1177 | def run(): | |
1178 | responses.add(responses.GET, url) | |
1179 | responses.add(responses.GET, "http://example1.com") | |
1180 | ||
1181 | assert responses.assert_call_count(url, 0) is True | |
1182 | ||
1183 | with pytest.raises(AssertionError) as excinfo: | |
1184 | responses.assert_call_count(url, 2) | |
1185 | assert "Expected URL '{0}' to be called 2 times. Called 0 times.".format( | |
1186 | url | |
1187 | ) in str(excinfo.value) | |
1188 | ||
1189 | requests.get(url) | |
1190 | assert responses.assert_call_count(url, 1) is True | |
1191 | ||
1192 | requests.get("http://example1.com") | |
1193 | assert responses.assert_call_count(url, 1) is True | |
1194 | ||
1195 | requests.get(url) | |
1196 | with pytest.raises(AssertionError) as excinfo: | |
1197 | responses.assert_call_count(url, 3) | |
1198 | assert "Expected URL '{0}' to be called 3 times. Called 2 times.".format( | |
1199 | url | |
1200 | ) in str(excinfo.value) | |
1201 | ||
1202 | run() | |
1203 | assert_reset() | |
1204 | ||
1205 | ||
1206 | def test_request_matches_post_params(): | |
1207 | @responses.activate | |
1208 | def run(): | |
1209 | responses.add( | |
1210 | method=responses.POST, | |
1211 | url="http://example.com/", | |
1212 | body="one", | |
1213 | match=[ | |
1214 | responses.json_params_matcher( | |
1215 | {"page": {"name": "first", "type": "json"}} | |
1216 | ) | |
1217 | ], | |
1218 | ) | |
1219 | responses.add( | |
1220 | method=responses.POST, | |
1221 | url="http://example.com/", | |
1222 | body="two", | |
1223 | match=[ | |
1224 | responses.urlencoded_params_matcher( | |
1225 | {"page": "second", "type": "urlencoded"} | |
1226 | ) | |
1227 | ], | |
1228 | ) | |
1229 | ||
1230 | resp = requests.request( | |
1231 | "POST", | |
1232 | "http://example.com/", | |
1233 | headers={"Content-Type": "x-www-form-urlencoded"}, | |
1234 | data={"page": "second", "type": "urlencoded"}, | |
1235 | ) | |
1236 | assert_response(resp, "two") | |
1237 | ||
1238 | resp = requests.request( | |
1239 | "POST", | |
1240 | "http://example.com/", | |
1241 | headers={"Content-Type": "application/json"}, | |
1242 | json={"page": {"name": "first", "type": "json"}}, | |
1243 | ) | |
1244 | assert_response(resp, "one") | |
1245 | ||
1246 | run() | |
1247 | assert_reset() | |
1248 | ||
1249 | ||
1250 | def test_request_matches_empty_body(): | |
1251 | @responses.activate | |
1252 | def run(): | |
1253 | responses.add( | |
1254 | method=responses.POST, | |
1255 | url="http://example.com/", | |
1256 | body="one", | |
1257 | match=[responses.json_params_matcher(None)], | |
1258 | ) | |
1259 | ||
1260 | responses.add( | |
1261 | method=responses.POST, | |
1262 | url="http://example.com/", | |
1263 | body="two", | |
1264 | match=[responses.urlencoded_params_matcher(None)], | |
1265 | ) | |
1266 | ||
1267 | resp = requests.request("POST", "http://example.com/") | |
1268 | assert_response(resp, "one") | |
1269 | ||
1270 | resp = requests.request( | |
1271 | "POST", | |
1272 | "http://example.com/", | |
1273 | headers={"Content-Type": "x-www-form-urlencoded"}, | |
1274 | ) | |
1275 | assert_response(resp, "two") | |
1276 | ||
1277 | run() | |
1278 | assert_reset() | |
1279 | ||
1280 | ||
1281 | def test_fail_request_error(): | |
1282 | @responses.activate | |
1283 | def run(): | |
1284 | responses.add("POST", "http://example1.com") | |
1285 | responses.add("GET", "http://example.com") | |
1286 | responses.add( | |
1287 | "POST", | |
1288 | "http://example.com", | |
1289 | match=[responses.urlencoded_params_matcher({"foo": "bar"})], | |
1290 | ) | |
1291 | ||
1292 | with pytest.raises(ConnectionError) as excinfo: | |
1293 | requests.post("http://example.com", data={"id": "bad"}) | |
1294 | msg = str(excinfo.value) | |
1295 | assert "- POST http://example1.com/ URL does not match" in msg | |
1296 | assert "- GET http://example.com/ Method does not match" in msg | |
1297 | assert "- POST http://example.com/ Parameters do not match" in msg | |
1298 | ||
1299 | run() | |
1300 | assert_reset() |