request.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  2. # not use this file except in compliance with the License. You may obtain
  3. # a copy of the License at
  4. #
  5. # https://www.apache.org/licenses/LICENSE-2.0
  6. #
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. # License for the specific language governing permissions and limitations
  11. # under the License.
  12. import copy
  13. import json
  14. import requests
  15. import six
  16. from six.moves.urllib import parse as urlparse
  17. class _RequestObjectProxy(object):
  18. """A wrapper around a requests.Request that gives some extra information.
  19. This will be important both for matching and so that when it's save into
  20. the request_history users will be able to access these properties.
  21. """
  22. def __init__(self, request, **kwargs):
  23. self._request = request
  24. self._matcher = None
  25. self._url_parts_ = None
  26. self._qs = None
  27. # All of these params should always exist but we use a default
  28. # to make the test setup easier.
  29. self._timeout = kwargs.pop('timeout', None)
  30. self._allow_redirects = kwargs.pop('allow_redirects', None)
  31. self._verify = kwargs.pop('verify', None)
  32. self._stream = kwargs.pop('stream', None)
  33. self._cert = kwargs.pop('cert', None)
  34. self._proxies = copy.deepcopy(kwargs.pop('proxies', {}))
  35. # FIXME(jamielennox): This is part of bug #1584008 and should default
  36. # to True (or simply removed) in a major version bump.
  37. self._case_sensitive = kwargs.pop('case_sensitive', False)
  38. def __getattr__(self, name):
  39. # there should be a better way to exclude this, but I don't want to
  40. # implement __setstate__ just not forward it to the request. You can't
  41. # actually define the method and raise AttributeError there either.
  42. if name in ('__setstate__',):
  43. raise AttributeError(name)
  44. return getattr(self._request, name)
  45. @property
  46. def _url_parts(self):
  47. if self._url_parts_ is None:
  48. url = self._request.url
  49. if not self._case_sensitive:
  50. url = url.lower()
  51. self._url_parts_ = urlparse.urlparse(url)
  52. return self._url_parts_
  53. @property
  54. def scheme(self):
  55. return self._url_parts.scheme
  56. @property
  57. def netloc(self):
  58. return self._url_parts.netloc
  59. @property
  60. def hostname(self):
  61. try:
  62. return self.netloc.split(':')[0]
  63. except IndexError:
  64. return ''
  65. @property
  66. def port(self):
  67. components = self.netloc.split(':')
  68. try:
  69. return int(components[1])
  70. except (IndexError, ValueError):
  71. pass
  72. if self.scheme == 'https':
  73. return 443
  74. if self.scheme == 'http':
  75. return 80
  76. # The default return shouldn't matter too much because if you are
  77. # wanting to test this value you really should be explicitly setting it
  78. # somewhere. 0 at least is a boolean False and an int.
  79. return 0
  80. @property
  81. def path(self):
  82. return self._url_parts.path
  83. @property
  84. def query(self):
  85. return self._url_parts.query
  86. @property
  87. def qs(self):
  88. if self._qs is None:
  89. self._qs = urlparse.parse_qs(self.query, keep_blank_values=True)
  90. return self._qs
  91. @property
  92. def timeout(self):
  93. return self._timeout
  94. @property
  95. def allow_redirects(self):
  96. return self._allow_redirects
  97. @property
  98. def verify(self):
  99. return self._verify
  100. @property
  101. def stream(self):
  102. return self._stream
  103. @property
  104. def cert(self):
  105. return self._cert
  106. @property
  107. def proxies(self):
  108. return self._proxies
  109. @classmethod
  110. def _create(cls, *args, **kwargs):
  111. return cls(requests.Request(*args, **kwargs).prepare())
  112. @property
  113. def text(self):
  114. body = self.body
  115. if isinstance(body, six.binary_type):
  116. body = body.decode('utf-8')
  117. return body
  118. def json(self, **kwargs):
  119. return json.loads(self.text, **kwargs)
  120. def __getstate__(self):
  121. # Can't pickle a weakref, but it's a weakref so ok to drop it.
  122. d = self.__dict__.copy()
  123. d['_matcher'] = None
  124. return d
  125. @property
  126. def matcher(self):
  127. """The matcher that this request was handled by.
  128. The matcher object is handled by a weakref. It will return the matcher
  129. object if it is still available - so if the mock is still in place. If
  130. the matcher is not available it will return None.
  131. """
  132. # if unpickled or not from a response this will be None
  133. if self._matcher is None:
  134. return None
  135. return self._matcher()
  136. def __str__(self):
  137. return "{0.method} {0.url}".format(self._request)