diff options
author | alexv-smirnov <alex@ydb.tech> | 2023-12-01 12:02:50 +0300 |
---|---|---|
committer | alexv-smirnov <alex@ydb.tech> | 2023-12-01 13:28:10 +0300 |
commit | 0e578a4c44d4abd539d9838347b9ebafaca41dfb (patch) | |
tree | a0c1969c37f818c830ebeff9c077eacf30be6ef8 /contrib/python/requests-mock/py3 | |
parent | 84f2d3d4cc985e63217cff149bd2e6d67ae6fe22 (diff) | |
download | ydb-0e578a4c44d4abd539d9838347b9ebafaca41dfb.tar.gz |
Change "ya.make"
Diffstat (limited to 'contrib/python/requests-mock/py3')
18 files changed, 1866 insertions, 0 deletions
diff --git a/contrib/python/requests-mock/py3/.dist-info/METADATA b/contrib/python/requests-mock/py3/.dist-info/METADATA new file mode 100644 index 00000000000..d8eadeaec20 --- /dev/null +++ b/contrib/python/requests-mock/py3/.dist-info/METADATA @@ -0,0 +1,144 @@ +Metadata-Version: 2.1 +Name: requests-mock +Version: 1.11.0 +Summary: Mock out responses from the requests package +Home-page: https://requests-mock.readthedocs.io/ +Author: Jamie Lennox +Author-email: jamielennox@gmail.com +License: Apache-2 +Project-URL: Source, https://github.com/jamielennox/requests-mock +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Information Technology +Classifier: License :: OSI Approved :: Apache Software License +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: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Testing +License-File: LICENSE +Requires-Dist: requests (<3,>=2.3) +Requires-Dist: six +Provides-Extra: fixture +Requires-Dist: fixtures ; extra == 'fixture' +Provides-Extra: test +Requires-Dist: fixtures ; extra == 'test' +Requires-Dist: purl ; extra == 'test' +Requires-Dist: pytest ; extra == 'test' +Requires-Dist: sphinx ; extra == 'test' +Requires-Dist: testtools ; extra == 'test' +Requires-Dist: requests-futures ; extra == 'test' +Requires-Dist: mock ; (( python_version < '3.3')) and extra == 'test' + +=============================== +requests-mock +=============================== + +.. image:: https://badge.fury.io/py/requests-mock.png + :target: https://pypi.org/project/requests-mock/ + +Intro +===== + +`requests-mock` provides a building block to stub out the HTTP `requests`_ portions of your testing code. +You should checkout the `docs`_ for more information. + +The Basics +========== + +Everything in `requests`_ eventually goes through an adapter to do the transport work. +`requests-mock` creates a custom `adapter` that allows you to predefine responses when certain URIs are called. + +There are then a number of methods provided to get the adapter used. + +A simple example: + +.. code:: python + + >>> import requests + >>> import requests_mock + + >>> session = requests.Session() + >>> adapter = requests_mock.Adapter() + >>> session.mount('mock://', adapter) + + >>> adapter.register_uri('GET', 'mock://test.com', text='data') + >>> resp = session.get('mock://test.com') + >>> resp.status_code, resp.text + (200, 'data') + +Obviously having all URLs be `mock://` prefixed isn't going to be useful, +so you can use `requests_mock.Mocker` to get the adapter into place. + +As a context manager: + +.. code:: python + + >>> with requests_mock.Mocker() as m: + ... m.get('http://test.com', text='data') + ... requests.get('http://test.com').text + ... + 'data' + +Or as a decorator: + +.. code:: python + + >>> @requests_mock.Mocker() + ... def test_func(m): + ... m.get('http://test.com', text='data') + ... return requests.get('http://test.com').text + ... + >>> test_func() + 'data' + +Or as a pytest fixture: + +.. code:: python + + >>> def test_simple(requests_mock): + ... requests_mock.get('http://test.com', text='data') + ... assert 'data' == requests.get('http://test.com').text + +For more information checkout the `docs`_. + +Reporting Bugs +============== + +Development and bug tracking is performed on `GitHub`_. + +Questions +========= + +There is a tag dedicated to `requests-mock` on `StackOverflow`_ where you can ask usage questions. + +License +======= + +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 + + https://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. + +.. _requests: https://requests.readthedocs.io +.. _docs: https://requests-mock.readthedocs.io/ +.. _GitHub: https://github.com/jamielennox/requests-mock +.. _StackOverflow: https://stackoverflow.com/questions/tagged/requests-mock + diff --git a/contrib/python/requests-mock/py3/.dist-info/entry_points.txt b/contrib/python/requests-mock/py3/.dist-info/entry_points.txt new file mode 100644 index 00000000000..b157e5a5ec9 --- /dev/null +++ b/contrib/python/requests-mock/py3/.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[pytest11] +requests_mock = requests_mock.contrib._pytest_plugin diff --git a/contrib/python/requests-mock/py3/.dist-info/top_level.txt b/contrib/python/requests-mock/py3/.dist-info/top_level.txt new file mode 100644 index 00000000000..65a92dd61d2 --- /dev/null +++ b/contrib/python/requests-mock/py3/.dist-info/top_level.txt @@ -0,0 +1 @@ +requests_mock diff --git a/contrib/python/requests-mock/py3/AUTHORS b/contrib/python/requests-mock/py3/AUTHORS new file mode 100644 index 00000000000..ae2e02a6123 --- /dev/null +++ b/contrib/python/requests-mock/py3/AUTHORS @@ -0,0 +1,50 @@ +Adam Johnson <me@adamj.eu> +Alex Peters <alex@peters.net> +Allan Lewis <allanlewis99@gmail.com> +Andreas Jaeger <aj@suse.com> +Andrii Oriekhov <andriyorehov@gmail.com> +Arjan Keeman <arjan.keeman@falckon.nl> +Axel H <noirbizarre@users.noreply.github.com> +Christian Clauss <cclauss@me.com> +Colas Le Guernic <clslgrnc@users.noreply.github.com> +Cyrille Corpet <cyrille@bayesimpact.org> +Darragh Bailey <dbailey@hpe.com> +David Kremer <courrier@david-kremer.fr> +Ian Cordasco <ian.cordasco@rackspace.com> +Ilya Konstantinov <ilya.konstantinov@gmail.com> +Jamie Lennox <jamie.lennox@agoda.com> +Jamie Lennox <jamie@vibrato.com.au> +Jamie Lennox <jamielennox@gmail.com> +Jamie Lennox <jamielennox@redhat.com> +Janne Pulkkinen <janne.pulkkinen@protonmail.com> +Janonymous <janonymous.codevulture@gmail.com> +Jelle van der Waa <jelle@archlinux.org> +Jeremy Stanley <fungi@yuggoth.org> +Jochen Kupperschmidt <homework@nwsnet.de> +Joel Andrews <oldsneerjaw@gmail.com> +Jon Dufresne <jon.dufresne@gmail.com> +Kenny Nguyen <kkenny.nguyen@pm.me> +Louis Taylor <louis@kragniz.eu> +Manuel Kaufmann <humitos@gmail.com> +Matthias Bilger <matthias@bilger.info> +Michał Górny <mgorny@gentoo.org> +Miroslav Šedivý <6774676+eumiro@users.noreply.github.com> +Monty Taylor <mordred@inaugust.com> +Noam <noamkush@gmail.com> +Pascal Corpet <pascal@bayesimpact.org> +Peter Hodge <peter.hodge84@gmail.com> +Petre Mierlutiu <petrem@users.noreply.github.com> +Rick van de Loo <rickvandeloo@gmail.com> +Ryan Brooke Payne <ryan.payne@daveramsey.com> +Sebastian Kalinowski <sebastian@kalinowski.eu> +Simon Willison <swillison@gmail.com> +Stefaan Lippens <stefaan.lippens@vito.be> +Swapnil Kulkarni (coolsvap) <me@coolsvap.net> +Ville Skyttä <ville.skytta@iki.fi> +boncheff <boncheff@users.noreply.github.com> +clslgrnc <clslgrnc@users.noreply.github.com> +dongfangtianyu <7629022+dongfangtianyu@users.noreply.github.com> +popokatapepel <jan-seins@hotmail.de> +reedip <reedip.banerjee@nectechnologies.in> +rfportilla <rfportilla@yahoo.com> +voith <voithjm1@gmail.com> diff --git a/contrib/python/requests-mock/py3/LICENSE b/contrib/python/requests-mock/py3/LICENSE new file mode 100644 index 00000000000..d88b5784ba5 --- /dev/null +++ b/contrib/python/requests-mock/py3/LICENSE @@ -0,0 +1,180 @@ +Copyright (c) 2014, Jamie Lennox +All rights reserved. + + + Apache License + Version 2.0, January 2004 + https://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. + + diff --git a/contrib/python/requests-mock/py3/README.rst b/contrib/python/requests-mock/py3/README.rst new file mode 100644 index 00000000000..1281d339a59 --- /dev/null +++ b/contrib/python/requests-mock/py3/README.rst @@ -0,0 +1,101 @@ +=============================== +requests-mock +=============================== + +.. image:: https://badge.fury.io/py/requests-mock.png + :target: https://pypi.org/project/requests-mock/ + +Intro +===== + +`requests-mock` provides a building block to stub out the HTTP `requests`_ portions of your testing code. +You should checkout the `docs`_ for more information. + +The Basics +========== + +Everything in `requests`_ eventually goes through an adapter to do the transport work. +`requests-mock` creates a custom `adapter` that allows you to predefine responses when certain URIs are called. + +There are then a number of methods provided to get the adapter used. + +A simple example: + +.. code:: python + + >>> import requests + >>> import requests_mock + + >>> session = requests.Session() + >>> adapter = requests_mock.Adapter() + >>> session.mount('mock://', adapter) + + >>> adapter.register_uri('GET', 'mock://test.com', text='data') + >>> resp = session.get('mock://test.com') + >>> resp.status_code, resp.text + (200, 'data') + +Obviously having all URLs be `mock://` prefixed isn't going to be useful, +so you can use `requests_mock.Mocker` to get the adapter into place. + +As a context manager: + +.. code:: python + + >>> with requests_mock.Mocker() as m: + ... m.get('http://test.com', text='data') + ... requests.get('http://test.com').text + ... + 'data' + +Or as a decorator: + +.. code:: python + + >>> @requests_mock.Mocker() + ... def test_func(m): + ... m.get('http://test.com', text='data') + ... return requests.get('http://test.com').text + ... + >>> test_func() + 'data' + +Or as a pytest fixture: + +.. code:: python + + >>> def test_simple(requests_mock): + ... requests_mock.get('http://test.com', text='data') + ... assert 'data' == requests.get('http://test.com').text + +For more information checkout the `docs`_. + +Reporting Bugs +============== + +Development and bug tracking is performed on `GitHub`_. + +Questions +========= + +There is a tag dedicated to `requests-mock` on `StackOverflow`_ where you can ask usage questions. + +License +======= + +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 + + https://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. + +.. _requests: https://requests.readthedocs.io +.. _docs: https://requests-mock.readthedocs.io/ +.. _GitHub: https://github.com/jamielennox/requests-mock +.. _StackOverflow: https://stackoverflow.com/questions/tagged/requests-mock diff --git a/contrib/python/requests-mock/py3/requests_mock/__init__.py b/contrib/python/requests-mock/py3/requests_mock/__init__.py new file mode 100644 index 00000000000..799b752ee7c --- /dev/null +++ b/contrib/python/requests-mock/py3/requests_mock/__init__.py @@ -0,0 +1,37 @@ +# 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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from requests_mock.adapter import Adapter, ANY +from requests_mock.exceptions import MockException, NoMockAddress +from requests_mock.mocker import mock, Mocker, MockerCore +from requests_mock.mocker import DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT +from requests_mock.response import create_response, CookieJar + + +__all__ = ['Adapter', + 'ANY', + 'create_response', + 'CookieJar', + 'mock', + 'Mocker', + 'MockerCore', + 'MockException', + 'NoMockAddress', + + 'DELETE', + 'GET', + 'HEAD', + 'OPTIONS', + 'PATCH', + 'POST', + 'PUT', + ] diff --git a/contrib/python/requests-mock/py3/requests_mock/adapter.py b/contrib/python/requests-mock/py3/requests_mock/adapter.py new file mode 100644 index 00000000000..e0560b22265 --- /dev/null +++ b/contrib/python/requests-mock/py3/requests_mock/adapter.py @@ -0,0 +1,323 @@ +# 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 +# +# https://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. + +import weakref + +from requests.adapters import BaseAdapter +from requests.utils import requote_uri +import six +from six.moves.urllib import parse as urlparse + +from requests_mock import exceptions +from requests_mock.request import _RequestObjectProxy +from requests_mock.response import _MatcherResponse + +import logging + +logger = logging.getLogger(__name__) + +try: + import purl + purl_types = (purl.URL,) +except ImportError: + purl = None + purl_types = () + +ANY = object() + + +class _RequestHistoryTracker(object): + + def __init__(self): + self.request_history = [] + + def _add_to_history(self, request): + self.request_history.append(request) + + @property + def last_request(self): + """Retrieve the latest request sent""" + try: + return self.request_history[-1] + except IndexError: + return None + + @property + def called(self): + return self.call_count > 0 + + @property + def called_once(self): + return self.call_count == 1 + + @property + def call_count(self): + return len(self.request_history) + + def reset(self): + self.request_history = [] + + +class _RunRealHTTP(Exception): + """A fake exception to jump out of mocking and allow a real request. + + This exception is caught at the mocker level and allows it to execute this + request through the real requests mechanism rather than the mocker. + + It should never be exposed to a user. + """ + + +class _Matcher(_RequestHistoryTracker): + """Contains all the information about a provided URL to match.""" + + def __init__(self, method, url, responses, complete_qs, request_headers, + additional_matcher, real_http, case_sensitive): + """ + :param bool complete_qs: Match the entire query string. By default URLs + match if all the provided matcher query arguments are matched and + extra query arguments are ignored. Set complete_qs to true to + require that the entire query string needs to match. + """ + super(_Matcher, self).__init__() + + self._method = method + self._url = url + self._responses = responses + self._complete_qs = complete_qs + self._request_headers = request_headers + self._real_http = real_http + self._additional_matcher = additional_matcher + + # url can be a regex object or ANY so don't always run urlparse + if isinstance(url, six.string_types): + url_parts = urlparse.urlparse(url) + self._scheme = url_parts.scheme.lower() + self._netloc = url_parts.netloc.lower() + self._path = requote_uri(url_parts.path or '/') + self._query = url_parts.query + + if not case_sensitive: + self._path = self._path.lower() + self._query = self._query.lower() + + elif isinstance(url, purl_types): + self._scheme = url.scheme() + self._netloc = url.netloc() + self._path = url.path() + self._query = url.query() + + if not case_sensitive: + self._path = self._path.lower() + self._query = self._query.lower() + + else: + self._scheme = None + self._netloc = None + self._path = None + self._query = None + + def _match_method(self, request): + if self._method is ANY: + return True + + if request.method.lower() == self._method.lower(): + return True + + return False + + def _match_url(self, request): + if self._url is ANY: + return True + + # regular expression matching + if hasattr(self._url, 'search'): + return self._url.search(request.url) is not None + + # scheme is always matched case insensitive + if self._scheme and request.scheme.lower() != self._scheme: + return False + + # netloc is always matched case insensitive + if self._netloc and request.netloc.lower() != self._netloc: + return False + + if (request.path or '/') != self._path: + return False + + # construct our own qs structure as we remove items from it below + request_qs = urlparse.parse_qs(request.query, keep_blank_values=True) + matcher_qs = urlparse.parse_qs(self._query, keep_blank_values=True) + + for k, vals in six.iteritems(matcher_qs): + for v in vals: + try: + request_qs.get(k, []).remove(v) + except ValueError: + return False + + if self._complete_qs: + for v in six.itervalues(request_qs): + if v: + return False + + return True + + def _match_headers(self, request): + for k, vals in six.iteritems(self._request_headers): + + try: + header = request.headers[k] + except KeyError: + # NOTE(jamielennox): This seems to be a requests 1.2/2 + # difference, in 2 they are just whatever the user inputted in + # 1 they are bytes. Let's optionally handle both and look at + # removing this when we depend on requests 2. + if not isinstance(k, six.text_type): + return False + + try: + header = request.headers[k.encode('utf-8')] + except KeyError: + return False + + if header != vals: + return False + + return True + + def _match_additional(self, request): + if callable(self._additional_matcher): + return self._additional_matcher(request) + + if self._additional_matcher is not None: + raise TypeError("Unexpected format of additional matcher.") + + return True + + def _match(self, request): + return (self._match_method(request) and + self._match_url(request) and + self._match_headers(request) and + self._match_additional(request)) + + def __call__(self, request): + if not self._match(request): + return None + + # doing this before _add_to_history means real requests are not stored + # in the request history. I'm not sure what is better here. + if self._real_http: + raise _RunRealHTTP() + + if len(self._responses) > 1: + response_matcher = self._responses.pop(0) + else: + response_matcher = self._responses[0] + + self._add_to_history(request) + return response_matcher.get_response(request) + + +class Adapter(BaseAdapter, _RequestHistoryTracker): + """A fake adapter than can return predefined responses. + + """ + def __init__(self, case_sensitive=False): + super(Adapter, self).__init__() + self._case_sensitive = case_sensitive + self._matchers = [] + + def send(self, request, **kwargs): + request = _RequestObjectProxy(request, + case_sensitive=self._case_sensitive, + **kwargs) + self._add_to_history(request) + + for matcher in reversed(self._matchers): + try: + resp = matcher(request) + except Exception: + request._matcher = weakref.ref(matcher) + raise + + if resp is not None: + request._matcher = weakref.ref(matcher) + resp.connection = self + logger.debug('{} {} {}'.format(request._request.method, + request._request.url, + resp.status_code)) + return resp + + raise exceptions.NoMockAddress(request) + + def close(self): + pass + + def register_uri(self, method, url, response_list=None, **kwargs): + """Register a new URI match and fake response. + + :param str method: The HTTP method to match. + :param str url: The URL to match. + """ + complete_qs = kwargs.pop('complete_qs', False) + additional_matcher = kwargs.pop('additional_matcher', None) + request_headers = kwargs.pop('request_headers', {}) + real_http = kwargs.pop('_real_http', False) + json_encoder = kwargs.pop('json_encoder', None) + + if response_list and kwargs: + raise RuntimeError('You should specify either a list of ' + 'responses OR response kwargs. Not both.') + elif real_http and (response_list or kwargs): + raise RuntimeError('You should specify either response data ' + 'OR real_http. Not both.') + elif not response_list: + if json_encoder is not None: + kwargs['json_encoder'] = json_encoder + response_list = [] if real_http else [kwargs] + + # NOTE(jamielennox): case_sensitive is not present as a kwarg because i + # think there would be an edge case where the adapter and register_uri + # had different values. + # Ideally case_sensitive would be a value passed to match() however + # this would change the contract of matchers so we pass ito to the + # proxy and the matcher separately. + responses = [_MatcherResponse(**k) for k in response_list] + matcher = _Matcher(method, + url, + responses, + case_sensitive=self._case_sensitive, + complete_qs=complete_qs, + additional_matcher=additional_matcher, + request_headers=request_headers, + real_http=real_http) + self.add_matcher(matcher) + return matcher + + def add_matcher(self, matcher): + """Register a custom matcher. + + A matcher is a callable that takes a `requests.Request` and returns a + `requests.Response` if it matches or None if not. + + :param callable matcher: The matcher to execute. + """ + self._matchers.append(matcher) + + def reset(self): + super(Adapter, self).reset() + for matcher in self._matchers: + matcher.reset() + + +__all__ = ['Adapter'] diff --git a/contrib/python/requests-mock/py3/requests_mock/compat.py b/contrib/python/requests-mock/py3/requests_mock/compat.py new file mode 100644 index 00000000000..8b6293af157 --- /dev/null +++ b/contrib/python/requests-mock/py3/requests_mock/compat.py @@ -0,0 +1,30 @@ +# 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 +# +# https://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. + + +class _FakeHTTPMessage(object): + + def __init__(self, headers): + self.headers = headers + + def getheaders(self, name): + try: + return [self.headers[name]] + except KeyError: + return [] + + def get_all(self, name, failobj=None): + # python 3 only, overrides email.message.Message.get_all + try: + return [self.headers[name]] + except KeyError: + return failobj diff --git a/contrib/python/requests-mock/py3/requests_mock/contrib/__init__.py b/contrib/python/requests-mock/py3/requests_mock/contrib/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/contrib/python/requests-mock/py3/requests_mock/contrib/__init__.py diff --git a/contrib/python/requests-mock/py3/requests_mock/contrib/_pytest_plugin.py b/contrib/python/requests-mock/py3/requests_mock/contrib/_pytest_plugin.py new file mode 100644 index 00000000000..bb6cd2b973e --- /dev/null +++ b/contrib/python/requests-mock/py3/requests_mock/contrib/_pytest_plugin.py @@ -0,0 +1,86 @@ +import pytest + + +# RHEL 7 ships pytest 2.7 which doesn't have the 'bool' type to addini. This +# broke pytest for EPEL: https://bugzilla.redhat.com/show_bug.cgi?id=1605138 +# If it's older than 2.9 we handle bool conversion ourselves. Remove this when +# we can rely on a newer pytest. +# +# Version 3 is also where the @yield_fixture decorator was deprecated and you +# can now just use @fixture, so we handle both of those cases as well. + +try: + _pytest_version = tuple([ + int(x) for x in pytest.__version__.split('.')[:2] + ]) + _pytest29 = _pytest_version >= (2, 9) + _pytest30 = _pytest_version >= (3, 0) +except Exception: + _pytest29 = False + _pytest30 = False + + +if not _pytest29: + _case_type = None + _case_default = 'false' + + # Copied from pytest 2.9.0 where bool was introduced. It's what happens + # internally if we specify a bool type argument. + def _strtobool(val): + """Convert a string representation of truth to true (1) or false (0). + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + + .. note:: copied from distutils.util + """ + val = val.lower() + if val in ('y', 'yes', 't', 'true', 'on', '1'): + return 1 + elif val in ('n', 'no', 'f', 'false', 'off', '0'): + return 0 + else: + raise ValueError("invalid truth value %r" % (val,)) + + def _bool_value(value): + return bool(_strtobool(value.strip())) + +else: + _case_type = 'bool' + _case_default = False + + def _bool_value(value): + return value + + +if _pytest30: + _fixture_type = pytest.fixture +else: + _fixture_type = pytest.yield_fixture + + +def pytest_addoption(parser): + parser.addini('requests_mock_case_sensitive', + 'Use case sensitive matching in requests_mock', + type=_case_type, + default=_case_default) + + +@_fixture_type(scope='function') # executed on every test +def requests_mock(request): + """Mock out the requests component of your code with defined responses. + + Mocks out any requests made through the python requests library with useful + responses for unit testing. See: + https://requests-mock.readthedocs.io/en/latest/ + """ + # pytest plugins get loaded immediately. If we import requests_mock it + # imports requests and then SSL which prevents gevent patching. Late load. + import requests_mock as rm_module + + case_sensitive = request.config.getini('requests_mock_case_sensitive') + kw = {'case_sensitive': _bool_value(case_sensitive)} + + with rm_module.Mocker(**kw) as m: + yield m diff --git a/contrib/python/requests-mock/py3/requests_mock/contrib/fixture.py b/contrib/python/requests-mock/py3/requests_mock/contrib/fixture.py new file mode 100644 index 00000000000..0c239475664 --- /dev/null +++ b/contrib/python/requests-mock/py3/requests_mock/contrib/fixture.py @@ -0,0 +1,27 @@ +# 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 +# +# https://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. + +import fixtures + +from requests_mock import mocker + + +class Fixture(fixtures.Fixture, mocker.MockerCore): + + def __init__(self, **kwargs): + fixtures.Fixture.__init__(self) + mocker.MockerCore.__init__(self, **kwargs) + + def setUp(self): + super(Fixture, self).setUp() + self.start() + self.addCleanup(self.stop) diff --git a/contrib/python/requests-mock/py3/requests_mock/exceptions.py b/contrib/python/requests-mock/py3/requests_mock/exceptions.py new file mode 100644 index 00000000000..feeb1aa312f --- /dev/null +++ b/contrib/python/requests-mock/py3/requests_mock/exceptions.py @@ -0,0 +1,30 @@ +# 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 +# +# https://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. + + +class MockException(Exception): + """Base Exception for library""" + + +class NoMockAddress(MockException): + """The requested URL was not mocked""" + + def __init__(self, request): + self.request = request + + def __str__(self): + return "No mock address: %s %s" % (self.request.method, + self.request.url) + + +class InvalidRequest(MockException): + """This call cannot be made under a mocked environment""" diff --git a/contrib/python/requests-mock/py3/requests_mock/mocker.py b/contrib/python/requests-mock/py3/requests_mock/mocker.py new file mode 100644 index 00000000000..d3bc85538e3 --- /dev/null +++ b/contrib/python/requests-mock/py3/requests_mock/mocker.py @@ -0,0 +1,342 @@ +# 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 +# +# https://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. + +import contextlib +import functools +import sys +import threading +import types + +import requests +import six + +from requests_mock import adapter +from requests_mock import exceptions + +DELETE = 'DELETE' +GET = 'GET' +HEAD = 'HEAD' +OPTIONS = 'OPTIONS' +PATCH = 'PATCH' +POST = 'POST' +PUT = 'PUT' + +_original_send = requests.Session.send + +# NOTE(phodge): we need to use an RLock (reentrant lock) here because +# requests.Session.send() is reentrant. See further comments where we +# monkeypatch get_adapter() +_send_lock = threading.RLock() + + +@contextlib.contextmanager +def threading_rlock(timeout): + kwargs = {} + if sys.version_info.major >= 3: + # python2 doesn't support the timeout argument + kwargs['timeout'] = timeout + + if not _send_lock.acquire(**kwargs): + m = "Could not acquire threading lock - possible deadlock scenario" + raise Exception(m) + + try: + yield + finally: + _send_lock.release() + + +def _is_bound_method(method): + """ + bound_method 's self is a obj + unbound_method 's self is None + """ + if isinstance(method, types.MethodType) and six.get_method_self(method): + return True + return False + + +def _set_method(target, name, method): + """ Set a mocked method onto the target. + + Target may be either an instance of a Session object of the + requests.Session class. First we Bind the method if it's an instance. + + If method is a bound_method, can direct setattr + """ + if not isinstance(target, type) and not _is_bound_method(method): + method = six.create_bound_method(method, target) + + setattr(target, name, method) + + +class MockerCore(object): + """A wrapper around common mocking functions. + + Automate the process of mocking the requests library. This will keep the + same general options available and prevent repeating code. + """ + + _PROXY_FUNCS = { + 'last_request', + 'add_matcher', + 'request_history', + 'called', + 'called_once', + 'call_count', + 'reset', + } + + case_sensitive = False + """case_sensitive handles a backwards incompatible bug. The URL used to + match against our matches and that is saved in request_history is always + lowercased. This is incorrect as it reports incorrect history to the user + and doesn't allow case sensitive path matching. + + Unfortunately fixing this change is backwards incompatible in the 1.X + series as people may rely on this behaviour. To work around this you can + globally set: + + requests_mock.mock.case_sensitive = True + + or for pytest set in your configuration: + + [pytest] + requests_mock_case_sensitive = True + + which will prevent the lowercase being executed and return case sensitive + url and query information. + + This will become the default in a 2.X release. See bug: #1584008. + """ + + def __init__(self, session=None, **kwargs): + if session and not isinstance(session, requests.Session): + raise TypeError("Only a requests.Session object can be mocked") + + self._mock_target = session or requests.Session + self.case_sensitive = kwargs.pop('case_sensitive', self.case_sensitive) + self._adapter = ( + kwargs.pop('adapter', None) or + adapter.Adapter(case_sensitive=self.case_sensitive) + ) + + self._json_encoder = kwargs.pop('json_encoder', None) + self.real_http = kwargs.pop('real_http', False) + self._last_send = None + + if kwargs: + raise TypeError('Unexpected Arguments: %s' % ', '.join(kwargs)) + + def start(self): + """Start mocking requests. + + Install the adapter and the wrappers required to intercept requests. + """ + if self._last_send: + raise RuntimeError('Mocker has already been started') + + # backup last `send` for restoration on `self.stop` + self._last_send = self._mock_target.send + self._last_get_adapter = self._mock_target.get_adapter + + def _fake_get_adapter(session, url): + return self._adapter + + def _fake_send(session, request, **kwargs): + # NOTE(phodge): we need to use a threading lock here in case there + # are multiple threads running - one thread could restore the + # original get_adapter() just as a second thread is about to + # execute _original_send() below + with threading_rlock(timeout=10): + # mock get_adapter + # + # NOTE(phodge): requests.Session.send() is actually + # reentrant due to how it resolves redirects with nested + # calls to send(), however the reentry occurs _after_ the + # call to self.get_adapter(), so it doesn't matter that we + # will restore _last_get_adapter before a nested send() has + # completed as long as we monkeypatch get_adapter() each + # time immediately before calling original send() like we + # are doing here. + _set_method(session, "get_adapter", _fake_get_adapter) + + # NOTE(jamielennox): self._last_send vs _original_send. Whilst + # it seems like here we would use _last_send there is the + # possibility that the user has messed up and is somehow + # nesting their mockers. If we call last_send at this point + # then we end up calling this function again and the outer + # level adapter ends up winning. All we really care about here + # is that our adapter is in place before calling send so we + # always jump directly to the real function so that our most + # recently patched send call ends up putting in the most recent + # adapter. It feels funny, but it works. + + try: + return _original_send(session, request, **kwargs) + except exceptions.NoMockAddress: + if not self.real_http: + raise + except adapter._RunRealHTTP: + # this mocker wants you to run the request through the real + # requests library rather than the mocking. Let it. + pass + finally: + # restore get_adapter + _set_method(session, "get_adapter", self._last_get_adapter) + + # if we are here it means we must run the real http request + # Or, with nested mocks, to the parent mock, that is why we use + # _last_send here instead of _original_send + if isinstance(self._mock_target, type): + return self._last_send(session, request, **kwargs) + else: + return self._last_send(request, **kwargs) + + _set_method(self._mock_target, "send", _fake_send) + + def stop(self): + """Stop mocking requests. + + This should have no impact if mocking has not been started. + When nesting mockers, make sure to stop the innermost first. + """ + if self._last_send: + self._mock_target.send = self._last_send + self._last_send = None + + # for familiarity with MagicMock + def reset_mock(self): + self.reset() + + def __getattr__(self, name): + if name in self._PROXY_FUNCS: + try: + return getattr(self._adapter, name) + except AttributeError: + pass + + raise AttributeError(name) + + def register_uri(self, *args, **kwargs): + # you can pass real_http here, but it's private to pass direct to the + # adapter, because if you pass direct to the adapter you'll see the exc + kwargs['_real_http'] = kwargs.pop('real_http', False) + kwargs.setdefault('json_encoder', self._json_encoder) + return self._adapter.register_uri(*args, **kwargs) + + def request(self, *args, **kwargs): + return self.register_uri(*args, **kwargs) + + def get(self, *args, **kwargs): + return self.request(GET, *args, **kwargs) + + def options(self, *args, **kwargs): + return self.request(OPTIONS, *args, **kwargs) + + def head(self, *args, **kwargs): + return self.request(HEAD, *args, **kwargs) + + def post(self, *args, **kwargs): + return self.request(POST, *args, **kwargs) + + def put(self, *args, **kwargs): + return self.request(PUT, *args, **kwargs) + + def patch(self, *args, **kwargs): + return self.request(PATCH, *args, **kwargs) + + def delete(self, *args, **kwargs): + return self.request(DELETE, *args, **kwargs) + + +class Mocker(MockerCore): + """The standard entry point for mock Adapter loading. + """ + + #: Defines with what should method name begin to be patched + TEST_PREFIX = 'test' + + def __init__(self, **kwargs): + """Create a new mocker adapter. + + :param str kw: Pass the mock object through to the decorated function + as this named keyword argument, rather than a positional argument. + :param bool real_http: True to send the request to the real requested + uri if there is not a mock installed for it. Defaults to False. + """ + self._kw = kwargs.pop('kw', None) + super(Mocker, self).__init__(**kwargs) + + def __enter__(self): + self.start() + return self + + def __exit__(self, type, value, traceback): + self.stop() + + def __call__(self, obj): + if isinstance(obj, type): + return self.decorate_class(obj) + + return self.decorate_callable(obj) + + def copy(self): + """Returns an exact copy of current mock + """ + m = type(self)( + kw=self._kw, + real_http=self.real_http, + case_sensitive=self.case_sensitive + ) + return m + + def decorate_callable(self, func): + """Decorates a callable + + :param callable func: callable to decorate + """ + @functools.wraps(func) + def inner(*args, **kwargs): + with self.copy() as m: + if self._kw: + kwargs[self._kw] = m + else: + args = list(args) + args.append(m) + + return func(*args, **kwargs) + + return inner + + def decorate_class(self, klass): + """Decorates methods in a class with request_mock + + Method will be decorated only if it name begins with `TEST_PREFIX` + + :param object klass: class which methods will be decorated + """ + for attr_name in dir(klass): + if not attr_name.startswith(self.TEST_PREFIX): + continue + + attr = getattr(klass, attr_name) + if not hasattr(attr, '__call__'): + continue + + m = self.copy() + setattr(klass, attr_name, m(attr)) + + return klass + + +mock = Mocker diff --git a/contrib/python/requests-mock/py3/requests_mock/py.typed b/contrib/python/requests-mock/py3/requests_mock/py.typed new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/contrib/python/requests-mock/py3/requests_mock/py.typed diff --git a/contrib/python/requests-mock/py3/requests_mock/request.py b/contrib/python/requests-mock/py3/requests_mock/request.py new file mode 100644 index 00000000000..05cbc3d4a35 --- /dev/null +++ b/contrib/python/requests-mock/py3/requests_mock/request.py @@ -0,0 +1,178 @@ +# 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 +# +# https://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. + +import copy +import json + +import requests +import six +from six.moves.urllib import parse as urlparse + + +class _RequestObjectProxy(object): + """A wrapper around a requests.Request that gives some extra information. + + This will be important both for matching and so that when it's save into + the request_history users will be able to access these properties. + """ + + def __init__(self, request, **kwargs): + self._request = request + self._matcher = None + self._url_parts_ = None + self._qs = None + + # All of these params should always exist but we use a default + # to make the test setup easier. + self._timeout = kwargs.pop('timeout', None) + self._allow_redirects = kwargs.pop('allow_redirects', None) + self._verify = kwargs.pop('verify', None) + self._stream = kwargs.pop('stream', None) + self._cert = kwargs.pop('cert', None) + self._proxies = copy.deepcopy(kwargs.pop('proxies', {})) + + # FIXME(jamielennox): This is part of bug #1584008 and should default + # to True (or simply removed) in a major version bump. + self._case_sensitive = kwargs.pop('case_sensitive', False) + + def __getattr__(self, name): + # there should be a better way to exclude this, but I don't want to + # implement __setstate__ just not forward it to the request. You can't + # actually define the method and raise AttributeError there either. + if name in ('__setstate__',): + raise AttributeError(name) + + return getattr(self._request, name) + + @property + def _url_parts(self): + if self._url_parts_ is None: + url = self._request.url + + if not self._case_sensitive: + url = url.lower() + + self._url_parts_ = urlparse.urlparse(url) + + return self._url_parts_ + + @property + def scheme(self): + return self._url_parts.scheme + + @property + def netloc(self): + return self._url_parts.netloc + + @property + def hostname(self): + try: + return self.netloc.split(':')[0] + except IndexError: + return '' + + @property + def port(self): + components = self.netloc.split(':') + + try: + return int(components[1]) + except (IndexError, ValueError): + pass + + if self.scheme == 'https': + return 443 + if self.scheme == 'http': + return 80 + + # The default return shouldn't matter too much because if you are + # wanting to test this value you really should be explicitly setting it + # somewhere. 0 at least is a boolean False and an int. + return 0 + + @property + def path(self): + return self._url_parts.path + + @property + def query(self): + return self._url_parts.query + + @property + def qs(self): + if self._qs is None: + self._qs = urlparse.parse_qs(self.query, keep_blank_values=True) + + return self._qs + + @property + def timeout(self): + return self._timeout + + @property + def allow_redirects(self): + return self._allow_redirects + + @property + def verify(self): + return self._verify + + @property + def stream(self): + return self._stream + + @property + def cert(self): + return self._cert + + @property + def proxies(self): + return self._proxies + + @classmethod + def _create(cls, *args, **kwargs): + return cls(requests.Request(*args, **kwargs).prepare()) + + @property + def text(self): + body = self.body + + if isinstance(body, six.binary_type): + body = body.decode('utf-8') + + return body + + def json(self, **kwargs): + return json.loads(self.text, **kwargs) + + def __getstate__(self): + # Can't pickle a weakref, but it's a weakref so ok to drop it. + d = self.__dict__.copy() + d['_matcher'] = None + return d + + @property + def matcher(self): + """The matcher that this request was handled by. + + The matcher object is handled by a weakref. It will return the matcher + object if it is still available - so if the mock is still in place. If + the matcher is not available it will return None. + """ + # if unpickled or not from a response this will be None + if self._matcher is None: + return None + + return self._matcher() + + def __str__(self): + return "{0.method} {0.url}".format(self._request) diff --git a/contrib/python/requests-mock/py3/requests_mock/response.py b/contrib/python/requests-mock/py3/requests_mock/response.py new file mode 100644 index 00000000000..58555392731 --- /dev/null +++ b/contrib/python/requests-mock/py3/requests_mock/response.py @@ -0,0 +1,281 @@ +# 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 +# +# https://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. + +import json as jsonutils + +from requests.adapters import HTTPAdapter +from requests.cookies import MockRequest, MockResponse +from requests.cookies import RequestsCookieJar +from requests.cookies import merge_cookies, cookiejar_from_dict +from requests.packages.urllib3.response import HTTPResponse +from requests.utils import get_encoding_from_headers +import six + +from requests_mock import compat +from requests_mock import exceptions + +_BODY_ARGS = frozenset(['raw', 'body', 'content', 'text', 'json']) +_HTTP_ARGS = frozenset([ + 'status_code', + 'reason', + 'headers', + 'cookies', + 'json_encoder', +]) + +_DEFAULT_STATUS = 200 +_http_adapter = HTTPAdapter() + + +class CookieJar(RequestsCookieJar): + + def set(self, name, value, **kwargs): + """Add a cookie to the Jar. + + :param str name: cookie name/key. + :param str value: cookie value. + :param int version: Integer or None. Netscape cookies have version 0. + RFC 2965 and RFC 2109 cookies have a version cookie-attribute of 1. + However, note that cookielib may 'downgrade' RFC 2109 cookies to + Netscape cookies, in which case version is 0. + :param str port: String representing a port or a set of ports + (eg. '80', or '80,8080'), + :param str domain: The domain the cookie should apply to. + :param str path: Cookie path (a string, eg. '/acme/rocket_launchers'). + :param bool secure: True if cookie should only be returned over a + secure connection. + :param int expires: Integer expiry date in seconds since epoch or None. + :param bool discard: True if this is a session cookie. + :param str comment: String comment from the server explaining the + function of this cookie. + :param str comment_url: URL linking to a comment from the server + explaining the function of this cookie. + """ + # just here to provide the function documentation + return super(CookieJar, self).set(name, value, **kwargs) + + +def _check_body_arguments(**kwargs): + # mutual exclusion, only 1 body method may be provided + provided = [x for x in _BODY_ARGS if kwargs.pop(x, None) is not None] + + if len(provided) > 1: + raise RuntimeError('You may only supply one body element. You ' + 'supplied %s' % ', '.join(provided)) + + extra = [x for x in kwargs if x not in _HTTP_ARGS] + + if extra: + raise TypeError('Too many arguments provided. Unexpected ' + 'arguments %s.' % ', '.join(extra)) + + +class _FakeConnection(object): + """An object that can mock the necessary parts of a socket interface.""" + + def send(self, request, **kwargs): + msg = 'This response was created without a connection. You are ' \ + 'therefore unable to make a request directly on that connection.' + raise exceptions.InvalidRequest(msg) + + def close(self): + pass + + +def _extract_cookies(request, response, cookies): + """Add cookies to the response. + + Cookies in requests are extracted from the headers in the original_response + httplib.HTTPMessage which we don't create so we have to do this step + manually. + """ + # This will add cookies set manually via the Set-Cookie or Set-Cookie2 + # header but this only allows 1 cookie to be set. + http_message = compat._FakeHTTPMessage(response.headers) + response.cookies.extract_cookies(MockResponse(http_message), + MockRequest(request)) + + # This allows you to pass either a CookieJar or a dictionary to request_uri + # or directly to create_response. To allow more than one cookie to be set. + if cookies: + merge_cookies(response.cookies, cookies) + + +class _IOReader(six.BytesIO): + """A reader that makes a BytesIO look like a HTTPResponse. + + A HTTPResponse will return an empty string when you read from it after + the socket has been closed. A BytesIO will raise a ValueError. For + compatibility we want to do the same thing a HTTPResponse does. + """ + + def read(self, *args, **kwargs): + if self.closed: + return six.b('') + + # if the file is open, but you asked for zero bytes read you should get + # back zero without closing the stream. + if len(args) > 0 and args[0] == 0: + return six.b('') + + # not a new style object in python 2 + result = six.BytesIO.read(self, *args, **kwargs) + + # when using resp.iter_content(None) it'll go through a different + # request path in urllib3. This path checks whether the object is + # marked closed instead of the return value. see gh124. + if result == six.b(''): + self.close() + + return result + + +def create_response(request, **kwargs): + """ + :param int status_code: The status code to return upon a successful + match. Defaults to 200. + :param HTTPResponse raw: A HTTPResponse object to return upon a + successful match. + :param io.IOBase body: An IO object with a read() method that can + return a body on successful match. + :param bytes content: A byte string to return upon a successful match. + :param unicode text: A text string to return upon a successful match. + :param object json: A python object to be converted to a JSON string + and returned upon a successful match. + :param class json_encoder: Encoder object to use for JOSON. + :param dict headers: A dictionary object containing headers that are + returned upon a successful match. + :param CookieJar cookies: A cookie jar with cookies to set on the + response. + + :returns requests.Response: A response object that can + be returned to requests. + """ + connection = kwargs.pop('connection', _FakeConnection()) + + _check_body_arguments(**kwargs) + + raw = kwargs.pop('raw', None) + body = kwargs.pop('body', None) + content = kwargs.pop('content', None) + text = kwargs.pop('text', None) + json = kwargs.pop('json', None) + headers = kwargs.pop('headers', {}) + encoding = None + + if content is not None and not isinstance(content, six.binary_type): + raise TypeError('Content should be binary data') + if text is not None and not isinstance(text, six.string_types): + raise TypeError('Text should be string data') + + if json is not None: + encoder = kwargs.pop('json_encoder', None) or jsonutils.JSONEncoder + text = jsonutils.dumps(json, cls=encoder) + if text is not None: + encoding = get_encoding_from_headers(headers) or 'utf-8' + content = text.encode(encoding) + if content is not None: + body = _IOReader(content) + if not raw: + status = kwargs.get('status_code', _DEFAULT_STATUS) + reason = kwargs.get('reason', + six.moves.http_client.responses.get(status)) + + raw = HTTPResponse(status=status, + reason=reason, + headers=headers, + body=body or _IOReader(six.b('')), + decode_content=False, + enforce_content_length=False, + preload_content=False, + original_response=None) + + response = _http_adapter.build_response(request, raw) + response.connection = connection + + if encoding and not response.encoding: + response.encoding = encoding + + _extract_cookies(request, response, kwargs.get('cookies')) + + return response + + +class _Context(object): + """Stores the data being used to process a current URL match.""" + + def __init__(self, headers, status_code, reason, cookies): + self.headers = headers + self.status_code = status_code + self.reason = reason + self.cookies = cookies + + +class _MatcherResponse(object): + + def __init__(self, **kwargs): + self._exc = kwargs.pop('exc', None) + + # If the user is asking for an exception to be thrown then prevent them + # specifying any sort of body or status response as it won't be used. + # This may be protecting the user too much but can be removed later. + if self._exc and kwargs: + raise TypeError('Cannot provide other arguments with exc.') + + _check_body_arguments(**kwargs) + self._params = kwargs + + # whilst in general you shouldn't do type checking in python this + # makes sure we don't end up with differences between the way types + # are handled between python 2 and 3. + content = self._params.get('content') + text = self._params.get('text') + + if content is not None and not (callable(content) or + isinstance(content, six.binary_type)): + raise TypeError('Content should be a callback or binary data') + + if text is not None and not (callable(text) or + isinstance(text, six.string_types)): + raise TypeError('Text should be a callback or string data') + + def get_response(self, request): + # if an error was requested then raise that instead of doing response + if self._exc: + raise self._exc + + # If a cookie dict is passed convert it into a CookieJar so that the + # cookies object available in a callback context is always a jar. + cookies = self._params.get('cookies', CookieJar()) + if isinstance(cookies, dict): + cookies = cookiejar_from_dict(cookies, CookieJar()) + + context = _Context(self._params.get('headers', {}).copy(), + self._params.get('status_code', _DEFAULT_STATUS), + self._params.get('reason'), + cookies) + + # if a body element is a callback then execute it + def _call(f, *args, **kwargs): + return f(request, context, *args, **kwargs) if callable(f) else f + + return create_response(request, + json=_call(self._params.get('json')), + text=_call(self._params.get('text')), + content=_call(self._params.get('content')), + body=_call(self._params.get('body')), + raw=self._params.get('raw'), + json_encoder=self._params.get('json_encoder'), + status_code=context.status_code, + reason=context.reason, + headers=context.headers, + cookies=context.cookies) diff --git a/contrib/python/requests-mock/py3/ya.make b/contrib/python/requests-mock/py3/ya.make new file mode 100644 index 00000000000..b022b84019b --- /dev/null +++ b/contrib/python/requests-mock/py3/ya.make @@ -0,0 +1,54 @@ +# Generated by devtools/yamaker (pypi). + +PY3_LIBRARY() + +VERSION(1.11.0) + +LICENSE(Apache-2.0) + +PEERDIR( + contrib/python/requests + contrib/python/six +) + +NO_LINT() + +NO_CHECK_IMPORTS( + requests_mock.contrib._pytest_plugin + requests_mock.contrib.fixture +) + +PY_SRCS( + TOP_LEVEL + requests_mock/__init__.py + requests_mock/__init__.pyi + requests_mock/adapter.py + requests_mock/adapter.pyi + requests_mock/compat.py + requests_mock/contrib/__init__.py + requests_mock/contrib/_pytest_plugin.py + requests_mock/contrib/_pytest_plugin.pyi + requests_mock/contrib/fixture.py + requests_mock/exceptions.py + requests_mock/exceptions.pyi + requests_mock/mocker.py + requests_mock/mocker.pyi + requests_mock/request.py + requests_mock/request.pyi + requests_mock/response.py + requests_mock/response.pyi +) + +RESOURCE_FILES( + PREFIX contrib/python/requests-mock/py3/ + .dist-info/METADATA + .dist-info/entry_points.txt + .dist-info/top_level.txt + requests_mock/py.typed +) + +END() + +RECURSE_FOR_TESTS( + tests +) |