Moving parameter data and data parsing into single object.

Reviewed in https://codereview.appspot.com/7374054/
diff --git a/apiclient/discovery.py b/apiclient/discovery.py
index c31d65e..580db0e 100644
--- a/apiclient/discovery.py
+++ b/apiclient/discovery.py
@@ -476,6 +476,95 @@
   return path_url, http_method, method_id, accept, max_size, media_path_url
 
 
+# TODO(dhermes): Convert this class to ResourceMethod and make it callable
+class ResourceMethodParameters(object):
+  """Represents the parameters associated with a method.
+
+  Attributes:
+    argmap: Map from method parameter name (string) to query parameter name
+        (string).
+    required_params: List of required parameters (represented by parameter
+        name as string).
+    repeated_params: List of repeated parameters (represented by parameter
+        name as string).
+    pattern_params: Map from method parameter name (string) to regular
+        expression (as a string). If the pattern is set for a parameter, the
+        value for that parameter must match the regular expression.
+    query_params: List of parameters (represented by parameter name as string)
+        that will be used in the query string.
+    path_params: Set of parameters (represented by parameter name as string)
+        that will be used in the base URL path.
+    param_types: Map from method parameter name (string) to parameter type. Type
+        can be any valid JSON schema type; valid values are 'any', 'array',
+        'boolean', 'integer', 'number', 'object', or 'string'. Reference:
+        http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1
+    enum_params: Map from method parameter name (string) to list of strings,
+       where each list of strings is the list of acceptable enum values.
+  """
+
+  def __init__(self, method_desc):
+    """Constructor for ResourceMethodParameters.
+
+    Sets default values and defers to set_parameters to populate.
+
+    Args:
+      method_desc: Dictionary with metadata describing an API method. Value
+          comes from the dictionary of methods stored in the 'methods' key in
+          the deserialized discovery document.
+    """
+    self.argmap = {}
+    self.required_params = []
+    self.repeated_params = []
+    self.pattern_params = {}
+    self.query_params = []
+    # TODO(dhermes): Change path_params to a list if the extra URITEMPLATE
+    #                parsing is gotten rid of.
+    self.path_params = set()
+    self.param_types = {}
+    self.enum_params = {}
+
+    self.set_parameters(method_desc)
+
+  def set_parameters(self, method_desc):
+    """Populates maps and lists based on method description.
+
+    Iterates through each parameter for the method and parses the values from
+    the parameter dictionary.
+
+    Args:
+      method_desc: Dictionary with metadata describing an API method. Value
+          comes from the dictionary of methods stored in the 'methods' key in
+          the deserialized discovery document.
+    """
+    for arg, desc in method_desc.get('parameters', {}).iteritems():
+      param = key2param(arg)
+      self.argmap[param] = arg
+
+      if desc.get('pattern'):
+        self.pattern_params[param] = desc['pattern']
+      if desc.get('enum'):
+        self.enum_params[param] = desc['enum']
+      if desc.get('required'):
+        self.required_params.append(param)
+      if desc.get('repeated'):
+        self.repeated_params.append(param)
+      if desc.get('location') == 'query':
+        self.query_params.append(param)
+      if desc.get('location') == 'path':
+        self.path_params.add(param)
+      self.param_types[param] = desc.get('type', 'string')
+
+    # TODO(dhermes): Determine if this is still necessary. Discovery based APIs
+    #                should have all path parameters already marked with
+    #                'location: path'.
+    for match in URITEMPLATE.finditer(method_desc['path']):
+      for namematch in VARNAME.finditer(match.group(0)):
+        name = key2param(namematch.group(0))
+        self.path_params.add(name)
+        if name in self.query_params:
+          self.query_params.remove(name)
+
+
 def createMethod(methodName, methodDesc, rootDesc, schema):
   """Creates a method for attaching to a Resource.
 
@@ -490,46 +579,13 @@
   (pathUrl, httpMethod, methodId, accept,
    maxSize, mediaPathUrl) = _fix_up_method_description(methodDesc, rootDesc)
 
-  argmap = {} # Map from method parameter name to query parameter name
-  required_params = [] # Required parameters
-  repeated_params = [] # Repeated parameters
-  pattern_params = {}  # Parameters that must match a regex
-  query_params = [] # Parameters that will be used in the query string
-  path_params = {} # Parameters that will be used in the base URL
-  param_type = {} # The type of the parameter
-  enum_params = {} # Allowable enumeration values for each parameter
-
-  if 'parameters' in methodDesc:
-    for arg, desc in methodDesc['parameters'].iteritems():
-      param = key2param(arg)
-      argmap[param] = arg
-
-      if desc.get('pattern', ''):
-        pattern_params[param] = desc['pattern']
-      if desc.get('enum', ''):
-        enum_params[param] = desc['enum']
-      if desc.get('required', False):
-        required_params.append(param)
-      if desc.get('repeated', False):
-        repeated_params.append(param)
-      if desc.get('location') == 'query':
-        query_params.append(param)
-      if desc.get('location') == 'path':
-        path_params[param] = param
-      param_type[param] = desc.get('type', 'string')
-
-  for match in URITEMPLATE.finditer(pathUrl):
-    for namematch in VARNAME.finditer(match.group(0)):
-      name = key2param(namematch.group(0))
-      path_params[name] = name
-      if name in query_params:
-        query_params.remove(name)
+  parameters = ResourceMethodParameters(methodDesc)
 
   def method(self, **kwargs):
     # Don't bother with doc string, it will be over-written by createMethod.
 
     for name in kwargs.iterkeys():
-      if name not in argmap:
+      if name not in parameters.argmap:
         raise TypeError('Got an unexpected keyword argument "%s"' % name)
 
     # Remove args that have a value of None.
@@ -538,11 +594,11 @@
       if kwargs[name] is None:
         del kwargs[name]
 
-    for name in required_params:
+    for name in parameters.required_params:
       if name not in kwargs:
         raise TypeError('Missing required parameter "%s"' % name)
 
-    for name, regex in pattern_params.iteritems():
+    for name, regex in parameters.pattern_params.iteritems():
       if name in kwargs:
         if isinstance(kwargs[name], basestring):
           pvalues = [kwargs[name]]
@@ -554,12 +610,12 @@
                 'Parameter "%s" value "%s" does not match the pattern "%s"' %
                 (name, pvalue, regex))
 
-    for name, enums in enum_params.iteritems():
+    for name, enums in parameters.enum_params.iteritems():
       if name in kwargs:
         # We need to handle the case of a repeated enum
         # name differently, since we want to handle both
         # arg='value' and arg=['value1', 'value2']
-        if (name in repeated_params and
+        if (name in parameters.repeated_params and
             not isinstance(kwargs[name], basestring)):
           values = kwargs[name]
         else:
@@ -573,16 +629,16 @@
     actual_query_params = {}
     actual_path_params = {}
     for key, value in kwargs.iteritems():
-      to_type = param_type.get(key, 'string')
+      to_type = parameters.param_types.get(key, 'string')
       # For repeated parameters we cast each member of the list.
-      if key in repeated_params and type(value) == type([]):
+      if key in parameters.repeated_params and type(value) == type([]):
         cast_value = [_cast(x, to_type) for x in value]
       else:
         cast_value = _cast(value, to_type)
-      if key in query_params:
-        actual_query_params[argmap[key]] = cast_value
-      if key in path_params:
-        actual_path_params[argmap[key]] = cast_value
+      if key in parameters.query_params:
+        actual_query_params[parameters.argmap[key]] = cast_value
+      if key in parameters.path_params:
+        actual_path_params[parameters.argmap[key]] = cast_value
     body_value = kwargs.get('body', None)
     media_filename = kwargs.get('media_body', None)
 
@@ -677,14 +733,14 @@
                                 resumable=resumable)
 
   docs = [methodDesc.get('description', DEFAULT_METHOD_DOC), '\n\n']
-  if len(argmap) > 0:
+  if len(parameters.argmap) > 0:
     docs.append('Args:\n')
 
   # Skip undocumented params and params common to all methods.
   skip_parameters = rootDesc.get('parameters', {}).keys()
   skip_parameters.extend(STACK_QUERY_PARAMETERS)
 
-  all_args = argmap.keys()
+  all_args = parameters.argmap.keys()
   args_ordered = [key2param(s) for s in methodDesc.get('parameterOrder', [])]
 
   # Move body to the front of the line.
@@ -700,12 +756,12 @@
       continue
 
     repeated = ''
-    if arg in repeated_params:
+    if arg in parameters.repeated_params:
       repeated = ' (repeated)'
     required = ''
-    if arg in required_params:
+    if arg in parameters.required_params:
       required = ' (required)'
-    paramdesc = methodDesc['parameters'][argmap[arg]]
+    paramdesc = methodDesc['parameters'][parameters.argmap[arg]]
     paramdoc = paramdesc.get('description', 'A parameter')
     if '$ref' in paramdesc:
       docs.append(