summaryrefslogtreecommitdiffstats
path: root/third_party/python/responses
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/responses')
-rw-r--r--third_party/python/responses/responses-0.10.6.dist-info/LICENSE201
-rw-r--r--third_party/python/responses/responses-0.10.6.dist-info/METADATA454
-rw-r--r--third_party/python/responses/responses-0.10.6.dist-info/RECORD6
-rw-r--r--third_party/python/responses/responses-0.10.6.dist-info/WHEEL6
-rw-r--r--third_party/python/responses/responses-0.10.6.dist-info/top_level.txt1
-rw-r--r--third_party/python/responses/responses.py653
6 files changed, 1321 insertions, 0 deletions
diff --git a/third_party/python/responses/responses-0.10.6.dist-info/LICENSE b/third_party/python/responses/responses-0.10.6.dist-info/LICENSE
new file mode 100644
index 0000000000..52b44b20a3
--- /dev/null
+++ b/third_party/python/responses/responses-0.10.6.dist-info/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright 2015 David Cramer
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/third_party/python/responses/responses-0.10.6.dist-info/METADATA b/third_party/python/responses/responses-0.10.6.dist-info/METADATA
new file mode 100644
index 0000000000..45368b35d5
--- /dev/null
+++ b/third_party/python/responses/responses-0.10.6.dist-info/METADATA
@@ -0,0 +1,454 @@
+Metadata-Version: 2.1
+Name: responses
+Version: 0.10.6
+Summary: A utility library for mocking out the `requests` Python library.
+Home-page: https://github.com/getsentry/responses
+Author: David Cramer
+License: Apache 2.0
+Platform: UNKNOWN
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: System Administrators
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Topic :: Software Development
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Requires-Dist: requests (>=2.0)
+Requires-Dist: six
+Requires-Dist: mock ; python_version < "3.3"
+Requires-Dist: cookies ; python_version < "3.4"
+Provides-Extra: tests
+Requires-Dist: pytest ; extra == 'tests'
+Requires-Dist: coverage (<5.0.0,>=3.7.1) ; extra == 'tests'
+Requires-Dist: pytest-cov ; extra == 'tests'
+Requires-Dist: pytest-localserver ; extra == 'tests'
+Requires-Dist: flake8 ; extra == 'tests'
+
+Responses
+=========
+
+.. image:: https://travis-ci.org/getsentry/responses.svg?branch=master
+ :target: https://travis-ci.org/getsentry/responses
+
+A utility library for mocking out the `requests` Python library.
+
+.. note::
+
+ Responses requires Python 2.7 or newer, and requests >= 2.0
+
+
+Installing
+----------
+
+``pip install responses``
+
+
+Basics
+------
+
+The core of ``responses`` comes from registering mock responses:
+
+.. code-block:: python
+
+ import responses
+ import requests
+
+ @responses.activate
+ def test_simple():
+ responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
+ json={'error': 'not found'}, status=404)
+
+ resp = requests.get('http://twitter.com/api/1/foobar')
+
+ assert resp.json() == {"error": "not found"}
+
+ assert len(responses.calls) == 1
+ assert responses.calls[0].request.url == 'http://twitter.com/api/1/foobar'
+ assert responses.calls[0].response.text == '{"error": "not found"}'
+
+If you attempt to fetch a url which doesn't hit a match, ``responses`` will raise
+a ``ConnectionError``:
+
+.. code-block:: python
+
+ import responses
+ import requests
+
+ from requests.exceptions import ConnectionError
+
+ @responses.activate
+ def test_simple():
+ with pytest.raises(ConnectionError):
+ requests.get('http://twitter.com/api/1/foobar')
+
+Lastly, you can pass an ``Exception`` as the body to trigger an error on the request:
+
+.. code-block:: python
+
+ import responses
+ import requests
+
+ @responses.activate
+ def test_simple():
+ responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
+ body=Exception('...'))
+ with pytest.raises(Exception):
+ requests.get('http://twitter.com/api/1/foobar')
+
+
+Response Parameters
+-------------------
+
+Responses are automatically registered via params on ``add``, but can also be
+passed directly:
+
+.. code-block:: python
+
+ import responses
+
+ responses.add(
+ responses.Response(
+ method='GET',
+ url='http://example.com',
+ )
+ )
+
+The following attributes can be passed to a Response mock:
+
+method (``str``)
+ The HTTP method (GET, POST, etc).
+
+url (``str`` or compiled regular expression)
+ The full resource URL.
+
+match_querystring (``bool``)
+ Include the query string when matching requests.
+ Enabled by default if the response URL contains a query string,
+ disabled if it doesn't or the URL is a regular expression.
+
+body (``str`` or ``BufferedReader``)
+ The response body.
+
+json
+ A Python object representing the JSON response body. Automatically configures
+ the appropriate Content-Type.
+
+status (``int``)
+ The HTTP status code.
+
+content_type (``content_type``)
+ Defaults to ``text/plain``.
+
+headers (``dict``)
+ Response headers.
+
+stream (``bool``)
+ Disabled by default. Indicates the response should use the streaming API.
+
+
+Dynamic Responses
+-----------------
+
+You can utilize callbacks to provide dynamic responses. The callback must return
+a tuple of (``status``, ``headers``, ``body``).
+
+.. code-block:: python
+
+ import json
+
+ import responses
+ import requests
+
+ @responses.activate
+ def test_calc_api():
+
+ def request_callback(request):
+ payload = json.loads(request.body)
+ resp_body = {'value': sum(payload['numbers'])}
+ headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'}
+ return (200, headers, json.dumps(resp_body))
+
+ responses.add_callback(
+ responses.POST, 'http://calc.com/sum',
+ callback=request_callback,
+ content_type='application/json',
+ )
+
+ resp = requests.post(
+ 'http://calc.com/sum',
+ json.dumps({'numbers': [1, 2, 3]}),
+ headers={'content-type': 'application/json'},
+ )
+
+ assert resp.json() == {'value': 6}
+
+ assert len(responses.calls) == 1
+ assert responses.calls[0].request.url == 'http://calc.com/sum'
+ assert responses.calls[0].response.text == '{"value": 6}'
+ assert (
+ responses.calls[0].response.headers['request-id'] ==
+ '728d329e-0e86-11e4-a748-0c84dc037c13'
+ )
+
+You can also pass a compiled regex to `add_callback` to match multiple urls:
+
+.. code-block:: python
+
+ import re, json
+
+ from functools import reduce
+
+ import responses
+ import requests
+
+ operators = {
+ 'sum': lambda x, y: x+y,
+ 'prod': lambda x, y: x*y,
+ 'pow': lambda x, y: x**y
+ }
+
+ @responses.activate
+ def test_regex_url():
+
+ def request_callback(request):
+ payload = json.loads(request.body)
+ operator_name = request.path_url[1:]
+
+ operator = operators[operator_name]
+
+ resp_body = {'value': reduce(operator, payload['numbers'])}
+ headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'}
+ return (200, headers, json.dumps(resp_body))
+
+ responses.add_callback(
+ responses.POST,
+ re.compile('http://calc.com/(sum|prod|pow|unsupported)'),
+ callback=request_callback,
+ content_type='application/json',
+ )
+
+ resp = requests.post(
+ 'http://calc.com/prod',
+ json.dumps({'numbers': [2, 3, 4]}),
+ headers={'content-type': 'application/json'},
+ )
+ assert resp.json() == {'value': 24}
+
+ test_regex_url()
+
+
+If you want to pass extra keyword arguments to the callback function, for example when reusing
+a callback function to give a slightly different result, you can use ``functools.partial``:
+
+.. code-block:: python
+
+ from functools import partial
+
+ ...
+
+ def request_callback(request, id=None):
+ payload = json.loads(request.body)
+ resp_body = {'value': sum(payload['numbers'])}
+ headers = {'request-id': id}
+ return (200, headers, json.dumps(resp_body))
+
+ responses.add_callback(
+ responses.POST, 'http://calc.com/sum',
+ callback=partial(request_callback, id='728d329e-0e86-11e4-a748-0c84dc037c13'),
+ content_type='application/json',
+ )
+
+
+Responses as a context manager
+------------------------------
+
+.. code-block:: python
+
+ import responses
+ import requests
+
+ def test_my_api():
+ with responses.RequestsMock() as rsps:
+ rsps.add(responses.GET, 'http://twitter.com/api/1/foobar',
+ body='{}', status=200,
+ content_type='application/json')
+ resp = requests.get('http://twitter.com/api/1/foobar')
+
+ assert resp.status_code == 200
+
+ # outside the context manager requests will hit the remote server
+ resp = requests.get('http://twitter.com/api/1/foobar')
+ resp.status_code == 404
+
+Responses as a pytest fixture
+-----------------------------
+
+.. code-block:: python
+
+ @pytest.fixture
+ def mocked_responses():
+ with responses.RequestsMock() as rsps:
+ yield rsps
+
+ def test_api(mocked_responses):
+ mocked_responses.add(
+ responses.GET, 'http://twitter.com/api/1/foobar',
+ body='{}', status=200,
+ content_type='application/json')
+ resp = requests.get('http://twitter.com/api/1/foobar')
+ assert resp.status_code == 200
+
+Assertions on declared responses
+--------------------------------
+
+When used as a context manager, Responses will, by default, raise an assertion
+error if a url was registered but not accessed. This can be disabled by passing
+the ``assert_all_requests_are_fired`` value:
+
+.. code-block:: python
+
+ import responses
+ import requests
+
+ def test_my_api():
+ with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
+ rsps.add(responses.GET, 'http://twitter.com/api/1/foobar',
+ body='{}', status=200,
+ content_type='application/json')
+
+
+Multiple Responses
+------------------
+
+You can also add multiple responses for the same url:
+
+.. code-block:: python
+
+ import responses
+ import requests
+
+ @responses.activate
+ def test_my_api():
+ responses.add(responses.GET, 'http://twitter.com/api/1/foobar', status=500)
+ responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
+ body='{}', status=200,
+ content_type='application/json')
+
+ resp = requests.get('http://twitter.com/api/1/foobar')
+ assert resp.status_code == 500
+ resp = requests.get('http://twitter.com/api/1/foobar')
+ assert resp.status_code == 200
+
+
+Using a callback to modify the response
+---------------------------------------
+
+If you use customized processing in `requests` via subclassing/mixins, or if you
+have library tools that interact with `requests` at a low level, you may need
+to add extended processing to the mocked Response object to fully simulate the
+environment for your tests. A `response_callback` can be used, which will be
+wrapped by the library before being returned to the caller. The callback
+accepts a `response` as it's single argument, and is expected to return a
+single `response` object.
+
+.. code-block:: python
+
+ import responses
+ import requests
+
+ def response_callback(resp):
+ resp.callback_processed = True
+ return resp
+
+ with responses.RequestsMock(response_callback=response_callback) as m:
+ m.add(responses.GET, 'http://example.com', body=b'test')
+ resp = requests.get('http://example.com')
+ assert resp.text == "test"
+ assert hasattr(resp, 'callback_processed')
+ assert resp.callback_processed is True
+
+
+Passing thru real requests
+--------------------------
+
+In some cases you may wish to allow for certain requests to pass thru responses
+and hit a real server. This can be done with the 'passthru' methods:
+
+.. code-block:: python
+
+ import responses
+
+ @responses.activate
+ def test_my_api():
+ responses.add_passthru('https://percy.io')
+
+This will allow any requests matching that prefix, that is otherwise not registered
+as a mock response, to passthru using the standard behavior.
+
+
+Viewing/Modifying registered responses
+--------------------------------------
+
+Registered responses are available as a private attribute of the RequestMock
+instance. It is sometimes useful for debugging purposes to view the stack of
+registered responses which can be accessed via ``responses.mock._matches``.
+
+The ``replace`` function allows a previously registered ``response`` to be
+changed. The method signature is identical to ``add``. ``response``s are
+identified using ``method`` and ``url``. Only the first matched ``response`` is
+replaced.
+
+.. code-block:: python
+
+ import responses
+ import requests
+
+ @responses.activate
+ def test_replace():
+
+ responses.add(responses.GET, 'http://example.org', json={'data': 1})
+ responses.replace(responses.GET, 'http://example.org', json={'data': 2})
+
+ resp = requests.get('http://example.org')
+
+ assert resp.json() == {'data': 2}
+
+
+``remove`` takes a ``method`` and ``url`` argument and will remove *all*
+matched ``response``s from the registered list.
+
+Finally, ``clear`` will reset all registered ``response``s
+
+
+
+Contributing
+------------
+
+Responses uses several linting and autoformatting utilities, so it's important that when
+submitting patches you use the appropriate toolchain:
+
+Clone the repository:
+
+.. code-block:: shell
+
+ git clone https://github.com/getsentry/responses.git
+
+Create an environment (e.g. with ``virtualenv``):
+
+.. code-block:: shell
+
+ virtualenv .env && source .env/bin/activate
+
+Configure development requirements:
+
+.. code-block:: shell
+
+ make develop
+
+
diff --git a/third_party/python/responses/responses-0.10.6.dist-info/RECORD b/third_party/python/responses/responses-0.10.6.dist-info/RECORD
new file mode 100644
index 0000000000..ab6546d203
--- /dev/null
+++ b/third_party/python/responses/responses-0.10.6.dist-info/RECORD
@@ -0,0 +1,6 @@
+responses.py,sha256=We44OwS185MQp72HCzsUCoGnzmfOhI6AH_MQq0yLQuU,19550
+responses-0.10.6.dist-info/LICENSE,sha256=SJ7LcLREfANKEJeKSwjaAVyb2fqVyjrq8hnZgVQWpnw,10835
+responses-0.10.6.dist-info/METADATA,sha256=pW-WNNN44ZdoLe1C7UQstvt2ffO-WNFyQ_haglud19o,13088
+responses-0.10.6.dist-info/WHEEL,sha256=HX-v9-noUkyUoxyZ1PMSuS7auUxDAR4VBdoYLqD0xws,110
+responses-0.10.6.dist-info/top_level.txt,sha256=aQhzfC0bq4TkAaB_Yr-7cv4u2Xnc8WiVzvh4KdZo0Qo,10
+responses-0.10.6.dist-info/RECORD,,
diff --git a/third_party/python/responses/responses-0.10.6.dist-info/WHEEL b/third_party/python/responses/responses-0.10.6.dist-info/WHEEL
new file mode 100644
index 0000000000..c8240f03e8
--- /dev/null
+++ b/third_party/python/responses/responses-0.10.6.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.33.1)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/responses/responses-0.10.6.dist-info/top_level.txt b/third_party/python/responses/responses-0.10.6.dist-info/top_level.txt
new file mode 100644
index 0000000000..2cb24f43db
--- /dev/null
+++ b/third_party/python/responses/responses-0.10.6.dist-info/top_level.txt
@@ -0,0 +1 @@
+responses
diff --git a/third_party/python/responses/responses.py b/third_party/python/responses/responses.py
new file mode 100644
index 0000000000..9de936805c
--- /dev/null
+++ b/third_party/python/responses/responses.py
@@ -0,0 +1,653 @@
+from __future__ import absolute_import, print_function, division, unicode_literals
+
+import _io
+import inspect
+import json as json_module
+import logging
+import re
+import six
+
+from collections import namedtuple
+from functools import update_wrapper
+from requests.adapters import HTTPAdapter
+from requests.exceptions import ConnectionError
+from requests.sessions import REDIRECT_STATI
+from requests.utils import cookiejar_from_dict
+
+try:
+ from collections.abc import Sequence, Sized
+except ImportError:
+ from collections import Sequence, Sized
+
+try:
+ from requests.packages.urllib3.response import HTTPResponse
+except ImportError:
+ from urllib3.response import HTTPResponse
+
+if six.PY2:
+ from urlparse import urlparse, parse_qsl, urlsplit, urlunsplit
+ from urllib import quote
+else:
+ from urllib.parse import urlparse, parse_qsl, urlsplit, urlunsplit, quote
+
+if six.PY2:
+ try:
+ from six import cStringIO as BufferIO
+ except ImportError:
+ from six import StringIO as BufferIO
+else:
+ from io import BytesIO as BufferIO
+
+try:
+ from unittest import mock as std_mock
+except ImportError:
+ import mock as std_mock
+
+try:
+ Pattern = re._pattern_type
+except AttributeError:
+ # Python 3.7
+ Pattern = re.Pattern
+
+UNSET = object()
+
+Call = namedtuple("Call", ["request", "response"])
+
+_real_send = HTTPAdapter.send
+
+logger = logging.getLogger("responses")
+
+
+def _is_string(s):
+ return isinstance(s, six.string_types)
+
+
+def _has_unicode(s):
+ return any(ord(char) > 128 for char in s)
+
+
+def _clean_unicode(url):
+ # Clean up domain names, which use punycode to handle unicode chars
+ urllist = list(urlsplit(url))
+ netloc = urllist[1]
+ if _has_unicode(netloc):
+ domains = netloc.split(".")
+ for i, d in enumerate(domains):
+ if _has_unicode(d):
+ d = "xn--" + d.encode("punycode").decode("ascii")
+ domains[i] = d
+ urllist[1] = ".".join(domains)
+ url = urlunsplit(urllist)
+
+ # Clean up path/query/params, which use url-encoding to handle unicode chars
+ if isinstance(url.encode("utf8"), six.string_types):
+ url = url.encode("utf8")
+ chars = list(url)
+ for i, x in enumerate(chars):
+ if ord(x) > 128:
+ chars[i] = quote(x)
+
+ return "".join(chars)
+
+
+def _is_redirect(response):
+ try:
+ # 2.0.0 <= requests <= 2.2
+ return response.is_redirect
+
+ except AttributeError:
+ # requests > 2.2
+ return (
+ # use request.sessions conditional
+ response.status_code in REDIRECT_STATI
+ and "location" in response.headers
+ )
+
+
+def _cookies_from_headers(headers):
+ try:
+ import http.cookies as cookies
+
+ resp_cookie = cookies.SimpleCookie()
+ resp_cookie.load(headers["set-cookie"])
+
+ cookies_dict = {name: v.value for name, v in resp_cookie.items()}
+ except ImportError:
+ from cookies import Cookies
+
+ resp_cookies = Cookies.from_request(headers["set-cookie"])
+ cookies_dict = {v.name: v.value for _, v in resp_cookies.items()}
+ return cookiejar_from_dict(cookies_dict)
+
+
+_wrapper_template = """\
+def wrapper%(wrapper_args)s:
+ with responses:
+ return func%(func_args)s
+"""
+
+
+def get_wrapped(func, responses):
+ if six.PY2:
+ args, a, kw, defaults = inspect.getargspec(func)
+ wrapper_args = inspect.formatargspec(args, a, kw, defaults)
+
+ # Preserve the argspec for the wrapped function so that testing
+ # tools such as pytest can continue to use their fixture injection.
+ if hasattr(func, "__self__"):
+ args = args[1:] # Omit 'self'
+ func_args = inspect.formatargspec(args, a, kw, None)
+ else:
+ signature = inspect.signature(func)
+ signature = signature.replace(return_annotation=inspect.Signature.empty)
+ # If the function is wrapped, switch to *args, **kwargs for the parameters
+ # as we can't rely on the signature to give us the arguments the function will
+ # be called with. For example unittest.mock.patch uses required args that are
+ # not actually passed to the function when invoked.
+ if hasattr(func, "__wrapped__"):
+ wrapper_params = [
+ inspect.Parameter("args", inspect.Parameter.VAR_POSITIONAL),
+ inspect.Parameter("kwargs", inspect.Parameter.VAR_KEYWORD),
+ ]
+ else:
+ wrapper_params = [
+ param.replace(annotation=inspect.Parameter.empty)
+ for param in signature.parameters.values()
+ ]
+ signature = signature.replace(parameters=wrapper_params)
+
+ wrapper_args = str(signature)
+ params_without_defaults = [
+ param.replace(
+ annotation=inspect.Parameter.empty, default=inspect.Parameter.empty
+ )
+ for param in signature.parameters.values()
+ ]
+ signature = signature.replace(parameters=params_without_defaults)
+ func_args = str(signature)
+
+ evaldict = {"func": func, "responses": responses}
+ six.exec_(
+ _wrapper_template % {"wrapper_args": wrapper_args, "func_args": func_args},
+ evaldict,
+ )
+ wrapper = evaldict["wrapper"]
+ update_wrapper(wrapper, func)
+ return wrapper
+
+
+class CallList(Sequence, Sized):
+ def __init__(self):
+ self._calls = []
+
+ def __iter__(self):
+ return iter(self._calls)
+
+ def __len__(self):
+ return len(self._calls)
+
+ def __getitem__(self, idx):
+ return self._calls[idx]
+
+ def add(self, request, response):
+ self._calls.append(Call(request, response))
+
+ def reset(self):
+ self._calls = []
+
+
+def _ensure_url_default_path(url):
+ if _is_string(url):
+ url_parts = list(urlsplit(url))
+ if url_parts[2] == "":
+ url_parts[2] = "/"
+ url = urlunsplit(url_parts)
+ return url
+
+
+def _handle_body(body):
+ if isinstance(body, six.text_type):
+ body = body.encode("utf-8")
+ if isinstance(body, _io.BufferedReader):
+ return body
+
+ return BufferIO(body)
+
+
+_unspecified = object()
+
+
+class BaseResponse(object):
+ content_type = None
+ headers = None
+
+ stream = False
+
+ def __init__(self, method, url, match_querystring=_unspecified):
+ self.method = method
+ # ensure the url has a default path set if the url is a string
+ self.url = _ensure_url_default_path(url)
+ self.match_querystring = self._should_match_querystring(match_querystring)
+ self.call_count = 0
+
+ def __eq__(self, other):
+ if not isinstance(other, BaseResponse):
+ return False
+
+ if self.method != other.method:
+ return False
+
+ # Can't simply do a equality check on the objects directly here since __eq__ isn't
+ # implemented for regex. It might seem to work as regex is using a cache to return
+ # the same regex instances, but it doesn't in all cases.
+ self_url = self.url.pattern if isinstance(self.url, Pattern) else self.url
+ other_url = other.url.pattern if isinstance(other.url, Pattern) else other.url
+
+ return self_url == other_url
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def _url_matches_strict(self, url, other):
+ url_parsed = urlparse(url)
+ other_parsed = urlparse(other)
+
+ if url_parsed[:3] != other_parsed[:3]:
+ return False
+
+ url_qsl = sorted(parse_qsl(url_parsed.query))
+ other_qsl = sorted(parse_qsl(other_parsed.query))
+
+ if len(url_qsl) != len(other_qsl):
+ return False
+
+ for (a_k, a_v), (b_k, b_v) in zip(url_qsl, other_qsl):
+ if a_k != b_k:
+ return False
+
+ if a_v != b_v:
+ return False
+
+ return True
+
+ def _should_match_querystring(self, match_querystring_argument):
+ if match_querystring_argument is not _unspecified:
+ return match_querystring_argument
+
+ if isinstance(self.url, Pattern):
+ # the old default from <= 0.9.0
+ return False
+
+ return bool(urlparse(self.url).query)
+
+ def _url_matches(self, url, other, match_querystring=False):
+ if _is_string(url):
+ if _has_unicode(url):
+ url = _clean_unicode(url)
+ if not isinstance(other, six.text_type):
+ other = other.encode("ascii").decode("utf8")
+ if match_querystring:
+ return self._url_matches_strict(url, other)
+
+ else:
+ url_without_qs = url.split("?", 1)[0]
+ other_without_qs = other.split("?", 1)[0]
+ return url_without_qs == other_without_qs
+
+ elif isinstance(url, Pattern) and url.match(other):
+ return True
+
+ else:
+ return False
+
+ def get_headers(self):
+ headers = {}
+ if self.content_type is not None:
+ headers["Content-Type"] = self.content_type
+ if self.headers:
+ headers.update(self.headers)
+ return headers
+
+ def get_response(self, request):
+ raise NotImplementedError
+
+ def matches(self, request):
+ if request.method != self.method:
+ return False
+
+ if not self._url_matches(self.url, request.url, self.match_querystring):
+ return False
+
+ return True
+
+
+class Response(BaseResponse):
+ def __init__(
+ self,
+ method,
+ url,
+ body="",
+ json=None,
+ status=200,
+ headers=None,
+ stream=False,
+ content_type=UNSET,
+ **kwargs
+ ):
+ # if we were passed a `json` argument,
+ # override the body and content_type
+ if json is not None:
+ assert not body
+ body = json_module.dumps(json)
+ if content_type is UNSET:
+ content_type = "application/json"
+
+ if content_type is UNSET:
+ content_type = "text/plain"
+
+ # body must be bytes
+ if isinstance(body, six.text_type):
+ body = body.encode("utf-8")
+
+ self.body = body
+ self.status = status
+ self.headers = headers
+ self.stream = stream
+ self.content_type = content_type
+ super(Response, self).__init__(method, url, **kwargs)
+
+ def get_response(self, request):
+ if self.body and isinstance(self.body, Exception):
+ raise self.body
+
+ headers = self.get_headers()
+ status = self.status
+ body = _handle_body(self.body)
+
+ return HTTPResponse(
+ status=status,
+ reason=six.moves.http_client.responses.get(status),
+ body=body,
+ headers=headers,
+ preload_content=False,
+ )
+
+
+class CallbackResponse(BaseResponse):
+ def __init__(
+ self, method, url, callback, stream=False, content_type="text/plain", **kwargs
+ ):
+ self.callback = callback
+ self.stream = stream
+ self.content_type = content_type
+ super(CallbackResponse, self).__init__(method, url, **kwargs)
+
+ def get_response(self, request):
+ headers = self.get_headers()
+
+ result = self.callback(request)
+ if isinstance(result, Exception):
+ raise result
+
+ status, r_headers, body = result
+ if isinstance(body, Exception):
+ raise body
+
+ body = _handle_body(body)
+ headers.update(r_headers)
+
+ return HTTPResponse(
+ status=status,
+ reason=six.moves.http_client.responses.get(status),
+ body=body,
+ headers=headers,
+ preload_content=False,
+ )
+
+
+class RequestsMock(object):
+ DELETE = "DELETE"
+ GET = "GET"
+ HEAD = "HEAD"
+ OPTIONS = "OPTIONS"
+ PATCH = "PATCH"
+ POST = "POST"
+ PUT = "PUT"
+ response_callback = None
+
+ def __init__(
+ self,
+ assert_all_requests_are_fired=True,
+ response_callback=None,
+ passthru_prefixes=(),
+ target="requests.adapters.HTTPAdapter.send",
+ ):
+ self._calls = CallList()
+ self.reset()
+ self.assert_all_requests_are_fired = assert_all_requests_are_fired
+ self.response_callback = response_callback
+ self.passthru_prefixes = tuple(passthru_prefixes)
+ self.target = target
+
+ def reset(self):
+ self._matches = []
+ self._calls.reset()
+
+ def add(
+ self,
+ method=None, # method or ``Response``
+ url=None,
+ body="",
+ adding_headers=None,
+ *args,
+ **kwargs
+ ):
+ """
+ A basic request:
+
+ >>> responses.add(responses.GET, 'http://example.com')
+
+ You can also directly pass an object which implements the
+ ``BaseResponse`` interface:
+
+ >>> responses.add(Response(...))
+
+ A JSON payload:
+
+ >>> responses.add(
+ >>> method='GET',
+ >>> url='http://example.com',
+ >>> json={'foo': 'bar'},
+ >>> )
+
+ Custom headers:
+
+ >>> responses.add(
+ >>> method='GET',
+ >>> url='http://example.com',
+ >>> headers={'X-Header': 'foo'},
+ >>> )
+
+
+ Strict query string matching:
+
+ >>> responses.add(
+ >>> method='GET',
+ >>> url='http://example.com?foo=bar',
+ >>> match_querystring=True
+ >>> )
+ """
+ if isinstance(method, BaseResponse):
+ self._matches.append(method)
+ return
+
+ if adding_headers is not None:
+ kwargs.setdefault("headers", adding_headers)
+
+ self._matches.append(Response(method=method, url=url, body=body, **kwargs))
+
+ def add_passthru(self, prefix):
+ """
+ Register a URL prefix to passthru any non-matching mock requests to.
+
+ For example, to allow any request to 'https://example.com', but require
+ mocks for the remainder, you would add the prefix as so:
+
+ >>> responses.add_passthru('https://example.com')
+ """
+ if _has_unicode(prefix):
+ prefix = _clean_unicode(prefix)
+ self.passthru_prefixes += (prefix,)
+
+ def remove(self, method_or_response=None, url=None):
+ """
+ Removes a response previously added using ``add()``, identified
+ either by a response object inheriting ``BaseResponse`` or
+ ``method`` and ``url``. Removes all matching responses.
+
+ >>> response.add(responses.GET, 'http://example.org')
+ >>> response.remove(responses.GET, 'http://example.org')
+ """
+ if isinstance(method_or_response, BaseResponse):
+ response = method_or_response
+ else:
+ response = BaseResponse(method=method_or_response, url=url)
+
+ while response in self._matches:
+ self._matches.remove(response)
+
+ def replace(self, method_or_response=None, url=None, body="", *args, **kwargs):
+ """
+ Replaces a response previously added using ``add()``. The signature
+ is identical to ``add()``. The response is identified using ``method``
+ and ``url``, and the first matching response is replaced.
+
+ >>> responses.add(responses.GET, 'http://example.org', json={'data': 1})
+ >>> responses.replace(responses.GET, 'http://example.org', json={'data': 2})
+ """
+ if isinstance(method_or_response, BaseResponse):
+ response = method_or_response
+ else:
+ response = Response(method=method_or_response, url=url, body=body, **kwargs)
+
+ index = self._matches.index(response)
+ self._matches[index] = response
+
+ def add_callback(
+ self, method, url, callback, match_querystring=False, content_type="text/plain"
+ ):
+ # ensure the url has a default path set if the url is a string
+ # url = _ensure_url_default_path(url, match_querystring)
+
+ self._matches.append(
+ CallbackResponse(
+ url=url,
+ method=method,
+ callback=callback,
+ content_type=content_type,
+ match_querystring=match_querystring,
+ )
+ )
+
+ @property
+ def calls(self):
+ return self._calls
+
+ def __enter__(self):
+ self.start()
+ return self
+
+ def __exit__(self, type, value, traceback):
+ success = type is None
+ self.stop(allow_assert=success)
+ self.reset()
+ return success
+
+ def activate(self, func):
+ return get_wrapped(func, self)
+
+ def _find_match(self, request):
+ found = None
+ found_match = None
+ for i, match in enumerate(self._matches):
+ if match.matches(request):
+ if found is None:
+ found = i
+ found_match = match
+ else:
+ # Multiple matches found. Remove & return the first match.
+ return self._matches.pop(found)
+
+ return found_match
+
+ def _on_request(self, adapter, request, **kwargs):
+ match = self._find_match(request)
+ resp_callback = self.response_callback
+
+ if match is None:
+ if request.url.startswith(self.passthru_prefixes):
+ logger.info("request.allowed-passthru", extra={"url": request.url})
+ return _real_send(adapter, request, **kwargs)
+
+ error_msg = (
+ "Connection refused by Responses: {0} {1} doesn't "
+ "match Responses Mock".format(request.method, request.url)
+ )
+ response = ConnectionError(error_msg)
+ response.request = request
+
+ self._calls.add(request, response)
+ response = resp_callback(response) if resp_callback else response
+ raise response
+
+ try:
+ response = adapter.build_response(request, match.get_response(request))
+ except Exception as response:
+ match.call_count += 1
+ self._calls.add(request, response)
+ response = resp_callback(response) if resp_callback else response
+ raise
+
+ if not match.stream:
+ response.content # NOQA
+
+ try:
+ response.cookies = _cookies_from_headers(response.headers)
+ except (KeyError, TypeError):
+ pass
+
+ response = resp_callback(response) if resp_callback else response
+ match.call_count += 1
+ self._calls.add(request, response)
+ return response
+
+ def start(self):
+ def unbound_on_send(adapter, request, *a, **kwargs):
+ return self._on_request(adapter, request, *a, **kwargs)
+
+ self._patcher = std_mock.patch(target=self.target, new=unbound_on_send)
+ self._patcher.start()
+
+ def stop(self, allow_assert=True):
+ self._patcher.stop()
+ if not self.assert_all_requests_are_fired:
+ return
+
+ if not allow_assert:
+ return
+
+ not_called = [m for m in self._matches if m.call_count == 0]
+ if not_called:
+ raise AssertionError(
+ "Not all requests have been executed {0!r}".format(
+ [(match.method, match.url) for match in not_called]
+ )
+ )
+
+
+# expose default mock namespace
+mock = _default_mock = RequestsMock(assert_all_requests_are_fired=False)
+__all__ = ["CallbackResponse", "Response", "RequestsMock"]
+for __attr in (a for a in dir(_default_mock) if not a.startswith("_")):
+ __all__.append(__attr)
+ globals()[__attr] = getattr(_default_mock, __attr)