PageRenderTime 25ms CodeModel.GetById 10ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 1ms

/atom/mock_http.py

http://radioappz.googlecode.com/
Python | 132 lines | 85 code | 16 blank | 31 comment | 12 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
 17
 18__author__ = 'api.jscudder (Jeff Scudder)'
 19
 20
 21import atom.http_interface
 22import atom.url
 23
 24
 25class Error(Exception):
 26  pass
 27
 28
 29class NoRecordingFound(Error):
 30  pass
 31
 32
 33class MockRequest(object):
 34  """Holds parameters of an HTTP request for matching against future requests.
 35  """
 36  def __init__(self, operation, url, data=None, headers=None):
 37    self.operation = operation
 38    if isinstance(url, (str, unicode)):
 39      url = atom.url.parse_url(url)
 40    self.url = url
 41    self.data = data
 42    self.headers = headers
 43
 44
 45class MockResponse(atom.http_interface.HttpResponse):
 46  """Simulates an httplib.HTTPResponse object."""
 47  def __init__(self, body=None, status=None, reason=None, headers=None):
 48    if body and hasattr(body, 'read'):
 49      self.body = body.read()
 50    else:
 51      self.body = body
 52    if status is not None:
 53      self.status = int(status)
 54    else:
 55      self.status = None
 56    self.reason = reason
 57    self._headers = headers or {}
 58
 59  def read(self):
 60    return self.body
 61
 62
 63class MockHttpClient(atom.http_interface.GenericHttpClient):
 64  def __init__(self, headers=None, recordings=None, real_client=None):
 65    """An HttpClient which responds to request with stored data.
 66
 67    The request-response pairs are stored as tuples in a member list named
 68    recordings.
 69
 70    The MockHttpClient can be switched from replay mode to record mode by
 71    setting the real_client member to an instance of an HttpClient which will
 72    make real HTTP requests and store the server's response in list of 
 73    recordings.
 74    
 75    Args:
 76      headers: dict containing HTTP headers which should be included in all
 77          HTTP requests.
 78      recordings: The initial recordings to be used for responses. This list
 79          contains tuples in the form: (MockRequest, MockResponse)
 80      real_client: An HttpClient which will make a real HTTP request. The 
 81          response will be converted into a MockResponse and stored in 
 82          recordings.
 83    """
 84    self.recordings = recordings or []
 85    self.real_client = real_client
 86    self.headers = headers or {}
 87
 88  def add_response(self, response, operation, url, data=None, headers=None):
 89    """Adds a request-response pair to the recordings list.
 90    
 91    After the recording is added, future matching requests will receive the
 92    response.
 93    
 94    Args:
 95      response: MockResponse
 96      operation: str
 97      url: str
 98      data: str, Currently the data is ignored when looking for matching
 99          requests.
100      headers: dict of strings: Currently the headers are ignored when
101          looking for matching requests.
102    """
103    request = MockRequest(operation, url, data=data, headers=headers)
104    self.recordings.append((request, response))
105
106  def request(self, operation, url, data=None, headers=None):
107    """Returns a matching MockResponse from the recordings.
108    
109    If the real_client is set, the request will be passed along and the 
110    server's response will be added to the recordings and also returned. 
111
112    If there is no match, a NoRecordingFound error will be raised.
113    """
114    if self.real_client is None:
115      if isinstance(url, (str, unicode)):
116        url = atom.url.parse_url(url)
117      for recording in self.recordings:
118        if recording[0].operation == operation and recording[0].url == url:
119          return recording[1]
120      raise NoRecordingFound('No recodings found for %s %s' % (
121          operation, url))
122    else:
123      # There is a real HTTP client, so make the request, and record the 
124      # response.
125      response = self.real_client.request(operation, url, data=data, 
126          headers=headers)
127      # TODO: copy the headers
128      stored_response = MockResponse(body=response, status=response.status,
129          reason=response.reason)
130      self.add_response(stored_response, operation, url, data=data, 
131          headers=headers)
132      return stored_response