# 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)