Make body optional for requests with no parameters (#446)

diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py
index 163f3c9..7762d84 100644
--- a/googleapiclient/discovery.py
+++ b/googleapiclient/discovery.py
@@ -448,7 +448,7 @@
   }
 
 
-def _fix_up_parameters(method_desc, root_desc, http_method):
+def _fix_up_parameters(method_desc, root_desc, http_method, schema):
   """Updates parameters of an API method with values specific to this library.
 
   Specifically, adds whatever global parameters are specified by the API to the
@@ -466,6 +466,7 @@
     root_desc: Dictionary; the entire original deserialized discovery document.
     http_method: String; the HTTP method used to call the API method described
         in method_desc.
+    schema: Object, mapping of schema names to schema descriptions.
 
   Returns:
     The updated Dictionary stored in the 'parameters' key of the method
@@ -486,6 +487,9 @@
   if http_method in HTTP_PAYLOAD_METHODS and 'request' in method_desc:
     body = BODY_PARAMETER_DEFAULT_VALUE.copy()
     body.update(method_desc['request'])
+    # Make body optional for requests with no parameters.
+    if not _methodProperties(method_desc, schema, 'request'):
+      body['required'] = False
     parameters['body'] = body
 
   return parameters
@@ -536,7 +540,7 @@
   return accept, max_size, media_path_url
 
 
-def _fix_up_method_description(method_desc, root_desc):
+def _fix_up_method_description(method_desc, root_desc, schema):
   """Updates a method description in a discovery document.
 
   SIDE EFFECTS: Changes the parameters dictionary in the method description with
@@ -547,6 +551,7 @@
         from the dictionary of methods stored in the 'methods' key in the
         deserialized discovery document.
     root_desc: Dictionary; the entire original deserialized discovery document.
+    schema: Object, mapping of schema names to schema descriptions.
 
   Returns:
     Tuple (path_url, http_method, method_id, accept, max_size, media_path_url)
@@ -571,7 +576,7 @@
   http_method = method_desc['httpMethod']
   method_id = method_desc['id']
 
-  parameters = _fix_up_parameters(method_desc, root_desc, http_method)
+  parameters = _fix_up_parameters(method_desc, root_desc, http_method, schema)
   # Order is important. `_fix_up_media_upload` needs `method_desc` to have a
   # 'parameters' key and needs to know if there is a 'body' parameter because it
   # also sets a 'media_body' parameter.
@@ -699,7 +704,7 @@
   """
   methodName = fix_method_name(methodName)
   (pathUrl, httpMethod, methodId, accept,
-   maxSize, mediaPathUrl) = _fix_up_method_description(methodDesc, rootDesc)
+   maxSize, mediaPathUrl) = _fix_up_method_description(methodDesc, rootDesc, schema)
 
   parameters = ResourceMethodParameters(methodDesc)
 
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
index 01c67ef..941cfb0 100644
--- a/tests/test_discovery.py
+++ b/tests/test_discovery.py
@@ -75,6 +75,7 @@
 from googleapiclient.http import MediaUploadProgress
 from googleapiclient.http import tunnel_patch
 from googleapiclient.model import JsonModel
+from googleapiclient.schema import Schemas
 from oauth2client import GOOGLE_TOKEN_URI
 from oauth2client.client import OAuth2Credentials, GoogleCredentials
 
@@ -122,18 +123,21 @@
     self.zoo_get_method_desc = self.zoo_root_desc['methods']['query']
     self.zoo_animals_resource = self.zoo_root_desc['resources']['animals']
     self.zoo_insert_method_desc = self.zoo_animals_resource['methods']['insert']
+    self.zoo_schema = Schemas(self.zoo_root_desc)
 
   def test_key2param(self):
     self.assertEqual('max_results', key2param('max-results'))
     self.assertEqual('x007_bond', key2param('007-bond'))
 
-  def _base_fix_up_parameters_test(self, method_desc, http_method, root_desc):
+  def _base_fix_up_parameters_test(
+          self, method_desc, http_method, root_desc, schema):
     self.assertEqual(method_desc['httpMethod'], http_method)
 
     method_desc_copy = copy.deepcopy(method_desc)
     self.assertEqual(method_desc, method_desc_copy)
 
-    parameters = _fix_up_parameters(method_desc_copy, root_desc, http_method)
+    parameters = _fix_up_parameters(method_desc_copy, root_desc, http_method,
+                                    schema)
 
     self.assertNotEqual(method_desc, method_desc_copy)
 
@@ -147,14 +151,14 @@
     return parameters
 
   def test_fix_up_parameters_get(self):
-    parameters = self._base_fix_up_parameters_test(self.zoo_get_method_desc,
-                                                   'GET', self.zoo_root_desc)
+    parameters = self._base_fix_up_parameters_test(
+      self.zoo_get_method_desc, 'GET', self.zoo_root_desc, self.zoo_schema)
     # Since http_method is 'GET'
     self.assertFalse('body' in parameters)
 
   def test_fix_up_parameters_insert(self):
-    parameters = self._base_fix_up_parameters_test(self.zoo_insert_method_desc,
-                                                   'POST', self.zoo_root_desc)
+    parameters = self._base_fix_up_parameters_test(
+      self.zoo_insert_method_desc, 'POST', self.zoo_root_desc, self.zoo_schema)
     body = {
         'description': 'The request body.',
         'type': 'object',
@@ -165,35 +169,58 @@
 
   def test_fix_up_parameters_check_body(self):
     dummy_root_desc = {}
+    dummy_schema = {
+      'Request': {
+        'properties': {
+          "description": "Required. Dummy parameter.",
+          "type": "string"
+        }
+      }
+    }
     no_payload_http_method = 'DELETE'
     with_payload_http_method = 'PUT'
 
     invalid_method_desc = {'response': 'Who cares'}
-    valid_method_desc = {'request': {'key1': 'value1', 'key2': 'value2'}}
+    valid_method_desc = {
+      'request': {
+        'key1': 'value1',
+        'key2': 'value2',
+        '$ref': 'Request'
+      }
+    }
 
     parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
-                                    no_payload_http_method)
+                                    no_payload_http_method, dummy_schema)
     self.assertFalse('body' in parameters)
 
     parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
-                                    no_payload_http_method)
+                                    no_payload_http_method, dummy_schema)
     self.assertFalse('body' in parameters)
 
     parameters = _fix_up_parameters(invalid_method_desc, dummy_root_desc,
-                                    with_payload_http_method)
+                                    with_payload_http_method, dummy_schema)
     self.assertFalse('body' in parameters)
 
     parameters = _fix_up_parameters(valid_method_desc, dummy_root_desc,
-                                    with_payload_http_method)
+                                    with_payload_http_method, dummy_schema)
     body = {
         'description': 'The request body.',
         'type': 'object',
         'required': True,
+        '$ref': 'Request',
         'key1': 'value1',
         'key2': 'value2',
     }
     self.assertEqual(parameters['body'], body)
 
+  def test_fix_up_parameters_optional_body(self):
+    # Request with no parameters
+    dummy_schema = {'Request': {'properties': {}}}
+    method_desc = {'request': {'$ref': 'Request'}}
+
+    parameters = _fix_up_parameters(method_desc, {}, 'POST', dummy_schema)
+    self.assertFalse(parameters['body']['required'])
+
   def _base_fix_up_method_description_test(
       self, method_desc, initial_parameters, final_parameters,
       final_accept, final_max_size, final_media_path_url):
@@ -260,7 +287,7 @@
 
   def test_fix_up_method_description_get(self):
     result = _fix_up_method_description(self.zoo_get_method_desc,
-                                        self.zoo_root_desc)
+                                        self.zoo_root_desc, self.zoo_schema)
     path_url = 'query'
     http_method = 'GET'
     method_id = 'bigquery.query'
@@ -272,7 +299,7 @@
 
   def test_fix_up_method_description_insert(self):
     result = _fix_up_method_description(self.zoo_insert_method_desc,
-                                        self.zoo_root_desc)
+                                        self.zoo_root_desc, self.zoo_schema)
     path_url = 'animals'
     http_method = 'POST'
     method_id = 'zoo.animals.insert'