Some methods don't specify a schema for their response, which means that
they are returning non-JSON such as CSV, images, etc. In that case
we return the bytes on the wire instead of trying to parse the response
as JSON.

Reviewed in http://codereview.appspot.com/5448123/
diff --git a/apiclient/discovery.py b/apiclient/discovery.py
index a3d4beb..ca26025 100644
--- a/apiclient/discovery.py
+++ b/apiclient/discovery.py
@@ -52,6 +52,7 @@
 from http import MediaUpload
 from http import MediaFileUpload
 from model import JsonModel
+from model import RawModel
 
 URITEMPLATE = re.compile('{[^}]*}')
 VARNAME = re.compile('[a-zA-Z0-9_-]+')
@@ -437,8 +438,13 @@
       if self._developerKey:
         actual_query_params['key'] = self._developerKey
 
+      model = self._model
+      # If there is no schema for the response then presume a binary blob.
+      if 'response' not in methodDesc:
+        model = RawModel()
+
       headers = {}
-      headers, params, query, body = self._model.request(headers,
+      headers, params, query, body = model.request(headers,
           actual_path_params, actual_query_params, body_value)
 
       expanded_url = uritemplate.expand(pathUrl, params)
@@ -541,7 +547,7 @@
 
       logging.info('URL being requested: %s' % url)
       return self._requestBuilder(self._http,
-                                  self._model.response,
+                                  model.response,
                                   url,
                                   method=httpMethod,
                                   body=body,
diff --git a/apiclient/model.py b/apiclient/model.py
index b8271f9..7f40efa 100644
--- a/apiclient/model.py
+++ b/apiclient/model.py
@@ -161,7 +161,8 @@
     Returns:
       The query parameters properly encoded into an HTTP URI query string.
     """
-    params.update({'alt': self.alt_param})
+    if self.alt_param is not None:
+      params.update({'alt': self.alt_param})
     astuples = []
     for key, value in params.iteritems():
       if type(value) == type([]):
@@ -269,6 +270,25 @@
     return {}
 
 
+class RawModel(JsonModel):
+  """Model class for requests that don't return JSON.
+
+  Serializes and de-serializes between JSON and the Python
+  object representation of HTTP request, and returns the raw bytes
+  of the response body.
+  """
+  accept = '*/*'
+  content_type = 'application/json'
+  alt_param = None
+
+  def deserialize(self, content):
+    return content
+
+  @property
+  def no_content_response(self):
+    return ''
+
+
 class ProtocolBufferModel(BaseModel):
   """Model class for protocol buffers.
 
diff --git a/tests/data/zoo.json b/tests/data/zoo.json
index 6e42da2..0bb0a79 100644
--- a/tests/data/zoo.json
+++ b/tests/data/zoo.json
@@ -289,6 +289,33 @@
       "$ref": "Animal"
      }
     },
+    "getmedia": {
+     "path": "animals/{name}",
+     "id": "zoo.animals.get",
+     "httpMethod": "GET",
+     "description": "Get animals",
+     "parameters": {
+      "name": {
+       "location": "path",
+       "required": true,
+       "description": "Name of the animal to load",
+       "type": "string"
+      },
+      "projection": {
+       "location": "query",
+       "type": "string",
+       "enum": [
+        "full"
+       ],
+       "enumDescriptions": [
+        "Include everything"
+       ]
+      }
+     },
+     "parameterOrder": [
+      "name"
+     ]
+    },
     "insert": {
      "path": "animals",
      "id": "zoo.animals.insert",
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
index 9e47e8f..e1ef94f 100644
--- a/tests/test_discovery.py
+++ b/tests/test_discovery.py
@@ -187,6 +187,26 @@
     self.assertEqual(q['trace'], ['html'])
     self.assertEqual(q['fields'], ['description'])
 
+  def test_model_added_query_parameters(self):
+    http = HttpMock(datafile('zoo.json'), {'status': '200'})
+    zoo = build('zoo', 'v1', http)
+    request = zoo.animals().get(name='Lion')
+
+    parsed = urlparse.urlparse(request.uri)
+    q = parse_qs(parsed[4])
+    self.assertEqual(q['alt'], ['json'])
+    self.assertEqual(request.headers['accept'], 'application/json')
+
+  def test_fallback_to_raw_model(self):
+    http = HttpMock(datafile('zoo.json'), {'status': '200'})
+    zoo = build('zoo', 'v1', http)
+    request = zoo.animals().getmedia(name='Lion')
+
+    parsed = urlparse.urlparse(request.uri)
+    q = parse_qs(parsed[4])
+    self.assertTrue('alt' not in q)
+    self.assertEqual(request.headers['accept'], '*/*')
+
   def test_patch(self):
     http = HttpMock(datafile('zoo.json'), {'status': '200'})
     zoo = build('zoo', 'v1', http)