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 urllib.parse
  15. import requests
  16. class _RequestObjectProxy(object):
  17. """A wrapper around a requests.Request that gives some extra information.
  18. This will be important both for matching and so that when it's save into
  19. the request_history users will be able to access these properties.
  20. """
  21. def __init__(self, request, **kwargs):
  22. self._request = request
  23. self._matcher = None
  24. self._url_parts_ = None
  25. self._qs = None
  26. # All of these params should always exist but we use a default
  27. # to make the test setup easier.
  28. self._timeout = kwargs.pop('timeout', None)
  29. self._allow_redirects = kwargs.pop('allow_redirects', None)
  30. self._verify = kwargs.pop('verify', None)
  31. self._stream = kwargs.pop('stream', None)
  32. self._cert = kwargs.pop('cert', None)
  33. self._proxies = copy.deepcopy(kwargs.pop('proxies', {}))
  34. # FIXME(jamielennox): This is part of bug #1584008 and should default
  35. # to True (or simply removed) in a major version bump.
  36. self._case_sensitive = kwargs.pop('case_sensitive', False)
  37. def __getattr__(self, name):
  38. # there should be a better way to exclude this, but I don't want to
  39. # implement __setstate__ just not forward it to the request. You can't
  40. # actually define the method and raise AttributeError there either.
  41. if name in ('__setstate__',):
  42. raise AttributeError(name)
  43. return getattr(self._request, name)
  44. @property
  45. def _url_parts(self):
  46. if self._url_parts_ is None:
  47. url = self._request.url
  48. if not self._case_sensitive:
  49. url = url.lower()
  50. self._url_parts_ = urllib.parse.urlparse(url)
  51. return self._url_parts_
  52. @property
  53. def scheme(self):
  54. return self._url_parts.scheme
  55. @property
  56. def netloc(self):
  57. return self._url_parts.netloc
  58. @property
  59. def hostname(self):
  60. try:
  61. return self.netloc.split(':')[0]
  62. except IndexError:
  63. return ''
  64. @property
  65. def port(self):
  66. components = self.netloc.split(':')
  67. try:
  68. return int(components[1])
  69. except (IndexError, ValueError):
  70. pass
  71. if self.scheme == 'https':
  72. return 443
  73. if self.scheme == 'http':
  74. return 80
  75. # The default return shouldn't matter too much because if you are
  76. # wanting to test this value you really should be explicitly setting it
  77. # somewhere. 0 at least is a boolean False and an int.
  78. return 0
  79. @property
  80. def path(self):
  81. return self._url_parts.path
  82. @property
  83. def query(self):
  84. return self._url_parts.query
  85. @property
  86. def qs(self):
  87. if self._qs is None:
  88. self._qs = urllib.parse.parse_qs(self.query,
  89. 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, bytes):
  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)