METADATA 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  1. Metadata-Version: 2.1
  2. Name: responses
  3. Version: 0.17.0
  4. Summary: A utility library for mocking out the `requests` Python library.
  5. Home-page: https://github.com/getsentry/responses
  6. Author: David Cramer
  7. License: Apache 2.0
  8. Platform: UNKNOWN
  9. Classifier: Intended Audience :: Developers
  10. Classifier: Intended Audience :: System Administrators
  11. Classifier: Operating System :: OS Independent
  12. Classifier: Programming Language :: Python
  13. Classifier: Programming Language :: Python :: 2
  14. Classifier: Programming Language :: Python :: 2.7
  15. Classifier: Programming Language :: Python :: 3
  16. Classifier: Programming Language :: Python :: 3.5
  17. Classifier: Programming Language :: Python :: 3.6
  18. Classifier: Programming Language :: Python :: 3.7
  19. Classifier: Programming Language :: Python :: 3.8
  20. Classifier: Programming Language :: Python :: 3.9
  21. Classifier: Programming Language :: Python :: 3.10
  22. Classifier: Topic :: Software Development
  23. Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
  24. Description-Content-Type: text/x-rst
  25. License-File: LICENSE
  26. Requires-Dist: requests (>=2.0)
  27. Requires-Dist: urllib3 (>=1.25.10)
  28. Requires-Dist: six
  29. Requires-Dist: mock ; python_version < "3.3"
  30. Requires-Dist: cookies ; python_version < "3.4"
  31. Provides-Extra: tests
  32. Requires-Dist: coverage (<6.0.0,>=3.7.1) ; extra == 'tests'
  33. Requires-Dist: pytest-cov ; extra == 'tests'
  34. Requires-Dist: pytest-localserver ; extra == 'tests'
  35. Requires-Dist: flake8 ; extra == 'tests'
  36. Requires-Dist: types-mock ; extra == 'tests'
  37. Requires-Dist: types-requests ; extra == 'tests'
  38. Requires-Dist: types-six ; extra == 'tests'
  39. Requires-Dist: pytest (<5.0,>=4.6) ; (python_version < "3.5") and extra == 'tests'
  40. Requires-Dist: pytest (>=4.6) ; (python_version >= "3.5") and extra == 'tests'
  41. Requires-Dist: mypy ; (python_version >= "3.5") and extra == 'tests'
  42. Responses
  43. =========
  44. .. image:: https://img.shields.io/pypi/v/responses.svg
  45. :target: https://pypi.python.org/pypi/responses/
  46. .. image:: https://img.shields.io/pypi/pyversions/responses.svg
  47. :target: https://pypi.org/project/responses/
  48. .. image:: https://codecov.io/gh/getsentry/responses/branch/master/graph/badge.svg
  49. :target: https://codecov.io/gh/getsentry/responses/
  50. A utility library for mocking out the ``requests`` Python library.
  51. .. note::
  52. Responses requires Python 2.7 or newer, and requests >= 2.0
  53. Installing
  54. ----------
  55. ``pip install responses``
  56. Basics
  57. ------
  58. The core of ``responses`` comes from registering mock responses:
  59. .. code-block:: python
  60. import responses
  61. import requests
  62. @responses.activate
  63. def test_simple():
  64. responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
  65. json={'error': 'not found'}, status=404)
  66. resp = requests.get('http://twitter.com/api/1/foobar')
  67. assert resp.json() == {"error": "not found"}
  68. assert len(responses.calls) == 1
  69. assert responses.calls[0].request.url == 'http://twitter.com/api/1/foobar'
  70. assert responses.calls[0].response.text == '{"error": "not found"}'
  71. If you attempt to fetch a url which doesn't hit a match, ``responses`` will raise
  72. a ``ConnectionError``:
  73. .. code-block:: python
  74. import responses
  75. import requests
  76. from requests.exceptions import ConnectionError
  77. @responses.activate
  78. def test_simple():
  79. with pytest.raises(ConnectionError):
  80. requests.get('http://twitter.com/api/1/foobar')
  81. Lastly, you can pass an ``Exception`` as the body to trigger an error on the request:
  82. .. code-block:: python
  83. import responses
  84. import requests
  85. @responses.activate
  86. def test_simple():
  87. responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
  88. body=Exception('...'))
  89. with pytest.raises(Exception):
  90. requests.get('http://twitter.com/api/1/foobar')
  91. Response Parameters
  92. -------------------
  93. Responses are automatically registered via params on ``add``, but can also be
  94. passed directly:
  95. .. code-block:: python
  96. import responses
  97. responses.add(
  98. responses.Response(
  99. method='GET',
  100. url='http://example.com',
  101. )
  102. )
  103. The following attributes can be passed to a Response mock:
  104. method (``str``)
  105. The HTTP method (GET, POST, etc).
  106. url (``str`` or compiled regular expression)
  107. The full resource URL.
  108. match_querystring (``bool``)
  109. DEPRECATED: Use `responses.matchers.query_param_matcher` or
  110. `responses.matchers.query_string_matcher`
  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.
  114. body (``str`` or ``BufferedReader``)
  115. The response body.
  116. json
  117. A Python object representing the JSON response body. Automatically configures
  118. the appropriate Content-Type.
  119. status (``int``)
  120. The HTTP status code.
  121. content_type (``content_type``)
  122. Defaults to ``text/plain``.
  123. headers (``dict``)
  124. Response headers.
  125. stream (``bool``)
  126. DEPRECATED: use ``stream`` argument in request directly
  127. auto_calculate_content_length (``bool``)
  128. Disabled by default. Automatically calculates the length of a supplied string or JSON body.
  129. match (``list``)
  130. A list of callbacks to match requests based on request attributes.
  131. Current module provides multiple matchers that you can use to match:
  132. * body contents in JSON format
  133. * body contents in URL encoded data format
  134. * request query parameters
  135. * request query string (similar to query parameters but takes string as input)
  136. * kwargs provided to request e.g. ``stream``, ``verify``
  137. * 'multipart/form-data' content and headers in request
  138. * request headers
  139. * request fragment identifier
  140. Alternatively user can create custom matcher.
  141. Read more `Matching Requests`_
  142. Matching Requests
  143. -----------------
  144. When adding responses for endpoints that are sent request data you can add
  145. matchers to ensure your code is sending the right parameters and provide
  146. different responses based on the request body contents. Responses provides
  147. matchers for JSON and URL-encoded request bodies and you can supply your own for
  148. other formats.
  149. .. code-block:: python
  150. import responses
  151. import requests
  152. from responses import matchers
  153. @responses.activate
  154. def test_calc_api():
  155. responses.add(
  156. responses.POST,
  157. url='http://calc.com/sum',
  158. body="4",
  159. match=[
  160. matchers.urlencoded_params_matcher({"left": "1", "right": "3"})
  161. ]
  162. )
  163. requests.post("http://calc.com/sum", data={"left": 1, "right": 3})
  164. Matching JSON encoded data can be done with ``matchers.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`` parameter to be provided by responses.
  168. Similarly, you can use the ``matchers.query_param_matcher`` function to match
  169. against the ``params`` request parameter.
  170. Note, you must set ``match_querystring=False``
  171. .. code-block:: python
  172. import responses
  173. import requests
  174. from responses import matchers
  175. @responses.activate
  176. def test_calc_api():
  177. url = "http://example.com/test"
  178. params = {"hello": "world", "I am": "a big test"}
  179. responses.add(
  180. method=responses.GET,
  181. url=url,
  182. body="test",
  183. match=[matchers.query_param_matcher(params)],
  184. match_querystring=False,
  185. )
  186. resp = requests.get(url, params=params)
  187. constructed_url = r"http://example.com/test?I+am=a+big+test&hello=world"
  188. assert resp.url == constructed_url
  189. assert resp.request.url == constructed_url
  190. assert resp.request.params == params
  191. As alternative, you can use query string value in ``matchers.query_string_matcher``
  192. .. code-block:: python
  193. import requests
  194. import responses
  195. from responses import matchers
  196. @responses.activate
  197. def my_func():
  198. responses.add(
  199. responses.GET,
  200. "https://httpbin.org/get",
  201. match=[matchers.query_string_matcher("didi=pro&test=1")],
  202. )
  203. resp = requests.get("https://httpbin.org/get", params={"test": 1, "didi": "pro"})
  204. my_func()
  205. To validate request arguments use the ``matchers.request_kwargs_matcher`` function to match
  206. against the request kwargs.
  207. Note, only arguments provided to ``matchers.request_kwargs_matcher`` will be validated
  208. .. code-block:: python
  209. import responses
  210. import requests
  211. from responses import matchers
  212. with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
  213. req_kwargs = {
  214. "stream": True,
  215. "verify": False,
  216. }
  217. rsps.add(
  218. "GET",
  219. "http://111.com",
  220. match=[matchers.request_kwargs_matcher(req_kwargs)],
  221. )
  222. requests.get("http://111.com", stream=True)
  223. # >>> Arguments don't match: {stream: True, verify: True} doesn't match {stream: True, verify: False}
  224. To validate request body and headers for ``multipart/form-data`` data you can use
  225. ``matchers.multipart_matcher``. The ``data``, and ``files`` parameters provided will be compared
  226. to the request:
  227. .. code-block:: python
  228. import requests
  229. import responses
  230. from responses.matchers import multipart_matcher
  231. @responses.activate
  232. def my_func():
  233. req_data = {"some": "other", "data": "fields"}
  234. req_files = {"file_name": b"Old World!"}
  235. responses.add(
  236. responses.POST, url="http://httpbin.org/post",
  237. match=[multipart_matcher(req_files, data=req_data)]
  238. )
  239. resp = requests.post("http://httpbin.org/post", files={"file_name": b"New World!"})
  240. my_func()
  241. # >>> raises ConnectionError: multipart/form-data doesn't match. Request body differs.
  242. To validate request URL fragment identifier you can use ``matchers.fragment_identifier_matcher``.
  243. The matcher takes fragment string (everything after ``#`` sign) as input for comparison:
  244. .. code-block:: python
  245. import requests
  246. import responses
  247. from responses.matchers import fragment_identifier_matcher
  248. @responses.activate
  249. def run():
  250. url = "http://example.com?ab=xy&zed=qwe#test=1&foo=bar"
  251. responses.add(
  252. responses.GET,
  253. url,
  254. match_querystring=True,
  255. match=[fragment_identifier_matcher("test=1&foo=bar")],
  256. body=b"test",
  257. )
  258. # two requests to check reversed order of fragment identifier
  259. resp = requests.get("http://example.com?ab=xy&zed=qwe#test=1&foo=bar")
  260. resp = requests.get("http://example.com?zed=qwe&ab=xy#foo=bar&test=1")
  261. run()
  262. Matching Request Headers
  263. ------------------------
  264. When adding responses you can specify matchers to ensure that your code is
  265. sending the right headers and provide different responses based on the request
  266. headers.
  267. .. code-block:: python
  268. import responses
  269. import requests
  270. from responses import matchers
  271. @responses.activate
  272. def test_content_type():
  273. responses.add(
  274. responses.GET,
  275. url="http://example.com/",
  276. body="hello world",
  277. match=[
  278. matchers.header_matcher({"Accept": "text/plain"})
  279. ]
  280. )
  281. responses.add(
  282. responses.GET,
  283. url="http://example.com/",
  284. json={"content": "hello world"},
  285. match=[
  286. matchers.header_matcher({"Accept": "application/json"})
  287. ]
  288. )
  289. # request in reverse order to how they were added!
  290. resp = requests.get("http://example.com/", headers={"Accept": "application/json"})
  291. assert resp.json() == {"content": "hello world"}
  292. resp = requests.get("http://example.com/", headers={"Accept": "text/plain"})
  293. assert resp.text == "hello world"
  294. Because ``requests`` will send several standard headers in addition to what was
  295. specified by your code, request headers that are additional to the ones
  296. passed to the matcher are ignored by default. You can change this behaviour by
  297. passing ``strict_match=True`` to the matcher to ensure that only the headers
  298. that you're expecting are sent and no others. Note that you will probably have
  299. to use a ``PreparedRequest`` in your code to ensure that ``requests`` doesn't
  300. include any additional headers.
  301. .. code-block:: python
  302. import responses
  303. import requests
  304. from responses import matchers
  305. @responses.activate
  306. def test_content_type():
  307. responses.add(
  308. responses.GET,
  309. url="http://example.com/",
  310. body="hello world",
  311. match=[
  312. matchers.header_matcher({"Accept": "text/plain"}, strict_match=True)
  313. ]
  314. )
  315. # this will fail because requests adds its own headers
  316. with pytest.raises(ConnectionError):
  317. requests.get("http://example.com/", headers={"Accept": "text/plain"})
  318. # a prepared request where you overwrite the headers before sending will work
  319. session = requests.Session()
  320. prepped = session.prepare_request(
  321. requests.Request(
  322. method="GET",
  323. url="http://example.com/",
  324. )
  325. )
  326. prepped.headers = {"Accept": "text/plain"}
  327. resp = session.send(prepped)
  328. assert resp.text == "hello world"
  329. Response Registry
  330. ---------------------------
  331. By default, ``responses`` will search all registered``Response`` objects and
  332. return a match. If only one ``Response`` is registered, the registry is kept unchanged.
  333. However, if multiple matches are found for the same request, then first match is returned and
  334. removed from registry.
  335. Such behavior is suitable for most of use cases, but to handle special conditions, you can
  336. implement custom registry which must follow interface of ``registries.FirstMatchRegistry``.
  337. Redefining the ``find`` method will allow you to create custom search logic and return
  338. appropriate ``Response``
  339. Example that shows how to set custom registry
  340. .. code-block:: python
  341. import responses
  342. from responses import registries
  343. class CustomRegistry(registries.FirstMatchRegistry):
  344. pass
  345. """ Before tests: <responses.registries.FirstMatchRegistry object> """
  346. # using function decorator
  347. @responses.activate(registry=CustomRegistry)
  348. def run():
  349. """ Within test: <__main__.CustomRegistry object> """
  350. run()
  351. """ After test: <responses.registries.FirstMatchRegistry object> """
  352. # using context manager
  353. with responses.RequestsMock(registry=CustomRegistry) as rsps:
  354. """ In context manager: <__main__.CustomRegistry object> """
  355. """
  356. After exit from context manager: <responses.registries.FirstMatchRegistry object>
  357. """
  358. Dynamic Responses
  359. -----------------
  360. You can utilize callbacks to provide dynamic responses. The callback must return
  361. a tuple of (``status``, ``headers``, ``body``).
  362. .. code-block:: python
  363. import json
  364. import responses
  365. import requests
  366. @responses.activate
  367. def test_calc_api():
  368. def request_callback(request):
  369. payload = json.loads(request.body)
  370. resp_body = {'value': sum(payload['numbers'])}
  371. headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'}
  372. return (200, headers, json.dumps(resp_body))
  373. responses.add_callback(
  374. responses.POST, 'http://calc.com/sum',
  375. callback=request_callback,
  376. content_type='application/json',
  377. )
  378. resp = requests.post(
  379. 'http://calc.com/sum',
  380. json.dumps({'numbers': [1, 2, 3]}),
  381. headers={'content-type': 'application/json'},
  382. )
  383. assert resp.json() == {'value': 6}
  384. assert len(responses.calls) == 1
  385. assert responses.calls[0].request.url == 'http://calc.com/sum'
  386. assert responses.calls[0].response.text == '{"value": 6}'
  387. assert (
  388. responses.calls[0].response.headers['request-id'] ==
  389. '728d329e-0e86-11e4-a748-0c84dc037c13'
  390. )
  391. You can also pass a compiled regex to ``add_callback`` to match multiple urls:
  392. .. code-block:: python
  393. import re, json
  394. from functools import reduce
  395. import responses
  396. import requests
  397. operators = {
  398. 'sum': lambda x, y: x+y,
  399. 'prod': lambda x, y: x*y,
  400. 'pow': lambda x, y: x**y
  401. }
  402. @responses.activate
  403. def test_regex_url():
  404. def request_callback(request):
  405. payload = json.loads(request.body)
  406. operator_name = request.path_url[1:]
  407. operator = operators[operator_name]
  408. resp_body = {'value': reduce(operator, payload['numbers'])}
  409. headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'}
  410. return (200, headers, json.dumps(resp_body))
  411. responses.add_callback(
  412. responses.POST,
  413. re.compile('http://calc.com/(sum|prod|pow|unsupported)'),
  414. callback=request_callback,
  415. content_type='application/json',
  416. )
  417. resp = requests.post(
  418. 'http://calc.com/prod',
  419. json.dumps({'numbers': [2, 3, 4]}),
  420. headers={'content-type': 'application/json'},
  421. )
  422. assert resp.json() == {'value': 24}
  423. test_regex_url()
  424. If you want to pass extra keyword arguments to the callback function, for example when reusing
  425. a callback function to give a slightly different result, you can use ``functools.partial``:
  426. .. code-block:: python
  427. from functools import partial
  428. ...
  429. def request_callback(request, id=None):
  430. payload = json.loads(request.body)
  431. resp_body = {'value': sum(payload['numbers'])}
  432. headers = {'request-id': id}
  433. return (200, headers, json.dumps(resp_body))
  434. responses.add_callback(
  435. responses.POST, 'http://calc.com/sum',
  436. callback=partial(request_callback, id='728d329e-0e86-11e4-a748-0c84dc037c13'),
  437. content_type='application/json',
  438. )
  439. You can see params passed in the original ``request`` in ``responses.calls[].request.params``:
  440. .. code-block:: python
  441. import responses
  442. import requests
  443. @responses.activate
  444. def test_request_params():
  445. responses.add(
  446. method=responses.GET,
  447. url="http://example.com?hello=world",
  448. body="test",
  449. match_querystring=False,
  450. )
  451. resp = requests.get('http://example.com', params={"hello": "world"})
  452. assert responses.calls[0].request.params == {"hello": "world"}
  453. Responses as a context manager
  454. ------------------------------
  455. .. code-block:: python
  456. import responses
  457. import requests
  458. def test_my_api():
  459. with responses.RequestsMock() as rsps:
  460. rsps.add(responses.GET, 'http://twitter.com/api/1/foobar',
  461. body='{}', status=200,
  462. content_type='application/json')
  463. resp = requests.get('http://twitter.com/api/1/foobar')
  464. assert resp.status_code == 200
  465. # outside the context manager requests will hit the remote server
  466. resp = requests.get('http://twitter.com/api/1/foobar')
  467. resp.status_code == 404
  468. Responses as a pytest fixture
  469. -----------------------------
  470. .. code-block:: python
  471. @pytest.fixture
  472. def mocked_responses():
  473. with responses.RequestsMock() as rsps:
  474. yield rsps
  475. def test_api(mocked_responses):
  476. mocked_responses.add(
  477. responses.GET, 'http://twitter.com/api/1/foobar',
  478. body='{}', status=200,
  479. content_type='application/json')
  480. resp = requests.get('http://twitter.com/api/1/foobar')
  481. assert resp.status_code == 200
  482. Responses inside a unittest setUp()
  483. -----------------------------------
  484. When run with unittest tests, this can be used to set up some
  485. generic class-level responses, that may be complemented by each test
  486. .. code-block:: python
  487. class TestMyApi(unittest.TestCase):
  488. def setUp(self):
  489. responses.add(responses.GET, 'https://example.com', body="within setup")
  490. # here go other self.responses.add(...)
  491. @responses.activate
  492. def test_my_func(self):
  493. responses.add(
  494. responses.GET,
  495. "https://httpbin.org/get",
  496. match=[matchers.query_param_matcher({"test": "1", "didi": "pro"})],
  497. body="within test"
  498. )
  499. resp = requests.get("https://example.com")
  500. resp2 = requests.get("https://httpbin.org/get", params={"test": "1", "didi": "pro"})
  501. print(resp.text)
  502. # >>> within setup
  503. print(resp2.text)
  504. # >>> within test
  505. Assertions on declared responses
  506. --------------------------------
  507. When used as a context manager, Responses will, by default, raise an assertion
  508. error if a url was registered but not accessed. This can be disabled by passing
  509. the ``assert_all_requests_are_fired`` value:
  510. .. code-block:: python
  511. import responses
  512. import requests
  513. def test_my_api():
  514. with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
  515. rsps.add(responses.GET, 'http://twitter.com/api/1/foobar',
  516. body='{}', status=200,
  517. content_type='application/json')
  518. assert_call_count
  519. -----------------
  520. Assert that the request was called exactly n times.
  521. .. code-block:: python
  522. import responses
  523. import requests
  524. @responses.activate
  525. def test_assert_call_count():
  526. responses.add(responses.GET, "http://example.com")
  527. requests.get("http://example.com")
  528. assert responses.assert_call_count("http://example.com", 1) is True
  529. requests.get("http://example.com")
  530. with pytest.raises(AssertionError) as excinfo:
  531. responses.assert_call_count("http://example.com", 1)
  532. assert "Expected URL 'http://example.com' to be called 1 times. Called 2 times." in str(excinfo.value)
  533. Multiple Responses
  534. ------------------
  535. You can also add multiple responses for the same url:
  536. .. code-block:: python
  537. import responses
  538. import requests
  539. @responses.activate
  540. def test_my_api():
  541. responses.add(responses.GET, 'http://twitter.com/api/1/foobar', status=500)
  542. responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
  543. body='{}', status=200,
  544. content_type='application/json')
  545. resp = requests.get('http://twitter.com/api/1/foobar')
  546. assert resp.status_code == 500
  547. resp = requests.get('http://twitter.com/api/1/foobar')
  548. assert resp.status_code == 200
  549. Using a callback to modify the response
  550. ---------------------------------------
  551. If you use customized processing in `requests` via subclassing/mixins, or if you
  552. have library tools that interact with `requests` at a low level, you may need
  553. to add extended processing to the mocked Response object to fully simulate the
  554. environment for your tests. A `response_callback` can be used, which will be
  555. wrapped by the library before being returned to the caller. The callback
  556. accepts a `response` as it's single argument, and is expected to return a
  557. single `response` object.
  558. .. code-block:: python
  559. import responses
  560. import requests
  561. def response_callback(resp):
  562. resp.callback_processed = True
  563. return resp
  564. with responses.RequestsMock(response_callback=response_callback) as m:
  565. m.add(responses.GET, 'http://example.com', body=b'test')
  566. resp = requests.get('http://example.com')
  567. assert resp.text == "test"
  568. assert hasattr(resp, 'callback_processed')
  569. assert resp.callback_processed is True
  570. Passing through real requests
  571. -----------------------------
  572. In some cases you may wish to allow for certain requests to pass through responses
  573. and hit a real server. This can be done with the ``add_passthru`` methods:
  574. .. code-block:: python
  575. import responses
  576. @responses.activate
  577. def test_my_api():
  578. responses.add_passthru('https://percy.io')
  579. This will allow any requests matching that prefix, that is otherwise not
  580. registered as a mock response, to passthru using the standard behavior.
  581. Pass through endpoints can be configured with regex patterns if you
  582. need to allow an entire domain or path subtree to send requests:
  583. .. code-block:: python
  584. responses.add_passthru(re.compile('https://percy.io/\\w+'))
  585. Lastly, you can use the `response.passthrough` attribute on `BaseResponse` or
  586. use ``PassthroughResponse`` to enable a response to behave as a pass through.
  587. .. code-block:: python
  588. # Enable passthrough for a single response
  589. response = Response(responses.GET, 'http://example.com', body='not used')
  590. response.passthrough = True
  591. responses.add(response)
  592. # Use PassthroughResponse
  593. response = PassthroughResponse(responses.GET, 'http://example.com')
  594. responses.add(response)
  595. Viewing/Modifying registered responses
  596. --------------------------------------
  597. Registered responses are available as a public method of the RequestMock
  598. instance. It is sometimes useful for debugging purposes to view the stack of
  599. registered responses which can be accessed via ``responses.registered()``.
  600. The ``replace`` function allows a previously registered ``response`` to be
  601. changed. The method signature is identical to ``add``. ``response`` s are
  602. identified using ``method`` and ``url``. Only the first matched ``response`` is
  603. replaced.
  604. .. code-block:: python
  605. import responses
  606. import requests
  607. @responses.activate
  608. def test_replace():
  609. responses.add(responses.GET, 'http://example.org', json={'data': 1})
  610. responses.replace(responses.GET, 'http://example.org', json={'data': 2})
  611. resp = requests.get('http://example.org')
  612. assert resp.json() == {'data': 2}
  613. The ``upsert`` function allows a previously registered ``response`` to be
  614. changed like ``replace``. If the response is registered, the ``upsert`` function
  615. will registered it like ``add``.
  616. ``remove`` takes a ``method`` and ``url`` argument and will remove **all**
  617. matched responses from the registered list.
  618. Finally, ``reset`` will reset all registered responses.
  619. Contributing
  620. ------------
  621. Responses uses several linting and autoformatting utilities, so it's important that when
  622. submitting patches you use the appropriate toolchain:
  623. Clone the repository:
  624. .. code-block:: shell
  625. git clone https://github.com/getsentry/responses.git
  626. Create an environment (e.g. with ``virtualenv``):
  627. .. code-block:: shell
  628. virtualenv .env && source .env/bin/activate
  629. Configure development requirements:
  630. .. code-block:: shell
  631. make develop
  632. Responses uses `Pytest <https://docs.pytest.org/en/latest/>`_ for
  633. testing. You can run all tests by:
  634. .. code-block:: shell
  635. pytest
  636. And run a single test by:
  637. .. code-block:: shell
  638. pytest -k '<test_function_name>'
  639. To verify ``type`` compliance, run `mypy <https://github.com/python/mypy>`_ linter:
  640. .. code-block:: shell
  641. mypy --config-file=./mypy.ini -p responses
  642. To check code style and reformat it run:
  643. .. code-block:: shell
  644. pre-commit run --all-files
  645. Note: on some OS, you have to use ``pre_commit``