Repository: googleapis/google-api-python-client

Issue:
Make method documentation parameter descriptions have deterministic ordering
Currently, optional method parameters and nested object fields therein appear in nondeterministic order in Pydoc pages, presumably due to dict iteration. Regenerating documentation thereby leads to unnecessary changes in the listings of these parameters and fields, which can both confuse users and create misleading diff noise. Iterating over these parameters and fields in a deterministic order would resolve these problems. In particular, using an alphabetic ordering would improve users' ability to locate desired parameters in documentation.


Discussion:

Resolving patch:
diff --git a/describe.py b/describe.py
--- a/describe.py
+++ b/describe.py
@@ -234,7 +234,7 @@ def add_param(pname, desc):
             pname = m.group(1)
             desc = m.group(2)
         add_param(pname, desc)
-        parameters = ", ".join(parameters)
+        parameters = ", ".join(sorted(parameters))
     else:
         parameters = ""
     return parameters
diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py
--- a/googleapiclient/discovery.py
+++ b/googleapiclient/discovery.py
@@ -898,7 +898,9 @@ def set_parameters(self, method_desc):
           comes from the dictionary of methods stored in the 'methods' key in
           the deserialized discovery document.
     """
-        for arg, desc in six.iteritems(method_desc.get("parameters", {})):
+        parameters = method_desc.get("parameters", {})
+        sorted_parameters = OrderedDict(sorted(parameters.items()))
+        for arg, desc in six.iteritems(sorted_parameters):
             param = key2param(arg)
             self.argmap[param] = arg
 
@@ -1137,7 +1139,7 @@ def method(self, **kwargs):
     if "body" in all_args:
         args_ordered.append("body")
 
-    for name in all_args:
+    for name in sorted(all_args):
         if name not in args_ordered:
             args_ordered.append(name)
 
@@ -1155,7 +1157,7 @@ def method(self, **kwargs):
         paramdoc = paramdesc.get("description", "A parameter")
         if "$ref" in paramdesc:
             docs.append(
-                ("  %s: object, %s%s%s\n    The object takes the" " form of:\n\n%s\n\n")
+                ("  %s: object, %s%s%s\n    The object takes the form of:\n\n%s\n\n")
                 % (
                     arg,
                     paramdoc,
diff --git a/googleapiclient/schema.py b/googleapiclient/schema.py
--- a/googleapiclient/schema.py
+++ b/googleapiclient/schema.py
@@ -65,6 +65,7 @@
 
 import copy
 
+from collections import OrderedDict
 from googleapiclient import _helpers as util
 
 
@@ -124,7 +125,7 @@ def prettyPrintByName(self, name):
         comments that conforms to the given schema.
     """
         # Return with trailing comma and newline removed.
-        return self._prettyPrintByName(name, seen=[], dent=1)[:-2]
+        return self._prettyPrintByName(name, seen=[], dent=0)[:-2]
 
     @util.positional(2)
     def _prettyPrintSchema(self, schema, seen=None, dent=0):
@@ -155,7 +156,7 @@ def prettyPrintSchema(self, schema):
         comments that conforms to the given schema.
     """
         # Return with trailing comma and newline removed.
-        return self._prettyPrintSchema(schema, dent=1)[:-2]
+        return self._prettyPrintSchema(schema, dent=0)[:-2]
 
     def get(self, name, default=None):
         """Get deserialized JSON schema from the schema name.
@@ -253,7 +254,9 @@ def _to_str_impl(self, schema):
             self.emitEnd("{", schema.get("description", ""))
             self.indent()
             if "properties" in schema:
-                for pname, pschema in six.iteritems(schema.get("properties", {})):
+                properties = schema.get("properties", {})
+                sorted_properties = OrderedDict(sorted(properties.items()))
+                for pname, pschema in six.iteritems(sorted_properties):
                     self.emitBegin('"%s": ' % pname)
                     self._to_str_impl(pschema)
             elif "additionalProperties" in schema:


Test patch:
diff --git a/tests/test_schema.py b/tests/test_schema.py
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -32,20 +32,20 @@ def datafile(filename):
 
 
 LOAD_FEED = """{
-    "items": [
-      {
-        "longVal": 42,
-        "kind": "zoo#loadValue",
-        "enumVal": "A String",
-        "anyVal": "", # Anything will do.
-        "nullVal": None,
-        "stringVal": "A String",
-        "doubleVal": 3.14,
-        "booleanVal": True or False, # True or False.
-      },
-    ],
-    "kind": "zoo#loadFeed",
-  }"""
+  "items": [
+    {
+      "longVal": 42,
+      "kind": "zoo#loadValue",
+      "enumVal": "A String",
+      "anyVal": "", # Anything will do.
+      "nullVal": None,
+      "stringVal": "A String",
+      "doubleVal": 3.14,
+      "booleanVal": True or False, # True or False.
+    },
+  ],
+  "kind": "zoo#loadFeed",
+}"""
 
 
 class SchemasTest(unittest.TestCase):