/atom/mock_http.py

http://radioappz.googlecode.com/ · Python · 132 lines · 77 code · 19 blank · 36 comment · 13 complexity · 6494631e5d70033d33ccb0205c8dcc13 MD5 · raw file

  1. #!/usr/bin/python
  2. #
  3. # Copyright (C) 2008 Google Inc.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. __author__ = 'api.jscudder (Jeff Scudder)'
  17. import atom.http_interface
  18. import atom.url
  19. class Error(Exception):
  20. pass
  21. class NoRecordingFound(Error):
  22. pass
  23. class MockRequest(object):
  24. """Holds parameters of an HTTP request for matching against future requests.
  25. """
  26. def __init__(self, operation, url, data=None, headers=None):
  27. self.operation = operation
  28. if isinstance(url, (str, unicode)):
  29. url = atom.url.parse_url(url)
  30. self.url = url
  31. self.data = data
  32. self.headers = headers
  33. class MockResponse(atom.http_interface.HttpResponse):
  34. """Simulates an httplib.HTTPResponse object."""
  35. def __init__(self, body=None, status=None, reason=None, headers=None):
  36. if body and hasattr(body, 'read'):
  37. self.body = body.read()
  38. else:
  39. self.body = body
  40. if status is not None:
  41. self.status = int(status)
  42. else:
  43. self.status = None
  44. self.reason = reason
  45. self._headers = headers or {}
  46. def read(self):
  47. return self.body
  48. class MockHttpClient(atom.http_interface.GenericHttpClient):
  49. def __init__(self, headers=None, recordings=None, real_client=None):
  50. """An HttpClient which responds to request with stored data.
  51. The request-response pairs are stored as tuples in a member list named
  52. recordings.
  53. The MockHttpClient can be switched from replay mode to record mode by
  54. setting the real_client member to an instance of an HttpClient which will
  55. make real HTTP requests and store the server's response in list of
  56. recordings.
  57. Args:
  58. headers: dict containing HTTP headers which should be included in all
  59. HTTP requests.
  60. recordings: The initial recordings to be used for responses. This list
  61. contains tuples in the form: (MockRequest, MockResponse)
  62. real_client: An HttpClient which will make a real HTTP request. The
  63. response will be converted into a MockResponse and stored in
  64. recordings.
  65. """
  66. self.recordings = recordings or []
  67. self.real_client = real_client
  68. self.headers = headers or {}
  69. def add_response(self, response, operation, url, data=None, headers=None):
  70. """Adds a request-response pair to the recordings list.
  71. After the recording is added, future matching requests will receive the
  72. response.
  73. Args:
  74. response: MockResponse
  75. operation: str
  76. url: str
  77. data: str, Currently the data is ignored when looking for matching
  78. requests.
  79. headers: dict of strings: Currently the headers are ignored when
  80. looking for matching requests.
  81. """
  82. request = MockRequest(operation, url, data=data, headers=headers)
  83. self.recordings.append((request, response))
  84. def request(self, operation, url, data=None, headers=None):
  85. """Returns a matching MockResponse from the recordings.
  86. If the real_client is set, the request will be passed along and the
  87. server's response will be added to the recordings and also returned.
  88. If there is no match, a NoRecordingFound error will be raised.
  89. """
  90. if self.real_client is None:
  91. if isinstance(url, (str, unicode)):
  92. url = atom.url.parse_url(url)
  93. for recording in self.recordings:
  94. if recording[0].operation == operation and recording[0].url == url:
  95. return recording[1]
  96. raise NoRecordingFound('No recodings found for %s %s' % (
  97. operation, url))
  98. else:
  99. # There is a real HTTP client, so make the request, and record the
  100. # response.
  101. response = self.real_client.request(operation, url, data=data,
  102. headers=headers)
  103. # TODO: copy the headers
  104. stored_response = MockResponse(body=response, status=response.status,
  105. reason=response.reason)
  106. self.add_response(stored_response, operation, url, data=data,
  107. headers=headers)
  108. return stored_response