117117 'type' : 'string' ,
118118 'required' : False ,
119119}
120+ _PAGE_TOKEN_NAMES = ('pageToken' , 'nextPageToken' )
120121
121122# Parameters accepted by the stack, but not visible via discovery.
122123# TODO(dhermes): Remove 'userip' in 'v2'.
@@ -724,7 +725,11 @@ def method(self, **kwargs):
724725
725726 for name in parameters .required_params :
726727 if name not in kwargs :
727- raise TypeError ('Missing required parameter "%s"' % name )
728+ # temporary workaround for non-paging methods incorrectly requiring
729+ # page token parameter (cf. drive.changes.watch vs. drive.changes.list)
730+ if name not in _PAGE_TOKEN_NAMES or _findPageTokenName (
731+ _methodProperties (methodDesc , schema , 'response' )):
732+ raise TypeError ('Missing required parameter "%s"' % name )
728733
729734 for name , regex in six .iteritems (parameters .pattern_params ):
730735 if name in kwargs :
@@ -927,13 +932,20 @@ def method(self, **kwargs):
927932 return (methodName , method )
928933
929934
930- def createNextMethod (methodName ):
935+ def createNextMethod (methodName ,
936+ pageTokenName = 'pageToken' ,
937+ nextPageTokenName = 'nextPageToken' ,
938+ isPageTokenParameter = True ):
931939 """Creates any _next methods for attaching to a Resource.
932940
933941 The _next methods allow for easy iteration through list() responses.
934942
935943 Args:
936944 methodName: string, name of the method to use.
945+ pageTokenName: string, name of request page token field.
946+ nextPageTokenName: string, name of response page token field.
947+ isPageTokenParameter: Boolean, True if request page token is a query
948+ parameter, False if request page token is a field of the request body.
937949 """
938950 methodName = fix_method_name (methodName )
939951
@@ -951,24 +963,24 @@ def methodNext(self, previous_request, previous_response):
951963 # Retrieve nextPageToken from previous_response
952964 # Use as pageToken in previous_request to create new request.
953965
954- if 'nextPageToken' not in previous_response or not previous_response ['nextPageToken' ]:
966+ nextPageToken = previous_response .get (nextPageTokenName , None )
967+ if not nextPageToken :
955968 return None
956969
957970 request = copy .copy (previous_request )
958971
959- pageToken = previous_response ['nextPageToken' ]
960- parsed = list (urlparse (request .uri ))
961- q = parse_qsl (parsed [4 ])
962-
963- # Find and remove old 'pageToken' value from URI
964- newq = [(key , value ) for (key , value ) in q if key != 'pageToken' ]
965- newq .append (('pageToken' , pageToken ))
966- parsed [4 ] = urlencode (newq )
967- uri = urlunparse (parsed )
968-
969- request .uri = uri
970-
971- logger .info ('URL being requested: %s %s' % (methodName ,uri ))
972+ if isPageTokenParameter :
973+ # Replace pageToken value in URI
974+ request .uri = _add_query_parameter (
975+ request .uri , pageTokenName , nextPageToken )
976+ logger .info ('Next page request URL: %s %s' % (methodName , request .uri ))
977+ else :
978+ # Replace pageToken value in request body
979+ model = self ._model
980+ body = model .deserialize (request .body )
981+ body [pageTokenName ] = nextPageToken
982+ request .body = model .serialize (body )
983+ logger .info ('Next page request body: %s %s' % (methodName , body ))
972984
973985 return request
974986
@@ -1116,19 +1128,59 @@ def methodResource(self):
11161128 method .__get__ (self , self .__class__ ))
11171129
11181130 def _add_next_methods (self , resourceDesc , schema ):
1119- # Add _next() methods
1120- # Look for response bodies in schema that contain nextPageToken, and methods
1121- # that take a pageToken parameter.
1122- if 'methods' in resourceDesc :
1123- for methodName , methodDesc in six .iteritems (resourceDesc ['methods' ]):
1124- if 'response' in methodDesc :
1125- responseSchema = methodDesc ['response' ]
1126- if '$ref' in responseSchema :
1127- responseSchema = schema .get (responseSchema ['$ref' ])
1128- hasNextPageToken = 'nextPageToken' in responseSchema .get ('properties' ,
1129- {})
1130- hasPageToken = 'pageToken' in methodDesc .get ('parameters' , {})
1131- if hasNextPageToken and hasPageToken :
1132- fixedMethodName , method = createNextMethod (methodName + '_next' )
1133- self ._set_dynamic_attr (fixedMethodName ,
1134- method .__get__ (self , self .__class__ ))
1131+ # Add _next() methods if and only if one of the names 'pageToken' or
1132+ # 'nextPageToken' occurs among the fields of both the method's response
1133+ # type either the method's request (query parameters) or request body.
1134+ if 'methods' not in resourceDesc :
1135+ return
1136+ for methodName , methodDesc in six .iteritems (resourceDesc ['methods' ]):
1137+ nextPageTokenName = _findPageTokenName (
1138+ _methodProperties (methodDesc , schema , 'response' ))
1139+ if not nextPageTokenName :
1140+ continue
1141+ isPageTokenParameter = True
1142+ pageTokenName = _findPageTokenName (methodDesc .get ('parameters' , {}))
1143+ if not pageTokenName :
1144+ isPageTokenParameter = False
1145+ pageTokenName = _findPageTokenName (
1146+ _methodProperties (methodDesc , schema , 'request' ))
1147+ if not pageTokenName :
1148+ continue
1149+ fixedMethodName , method = createNextMethod (
1150+ methodName + '_next' , pageTokenName , nextPageTokenName ,
1151+ isPageTokenParameter )
1152+ self ._set_dynamic_attr (fixedMethodName ,
1153+ method .__get__ (self , self .__class__ ))
1154+
1155+
1156+ def _findPageTokenName (fields ):
1157+ """Search field names for one like a page token.
1158+
1159+ Args:
1160+ fields: container of string, names of fields.
1161+
1162+ Returns:
1163+ First name that is either 'pageToken' or 'nextPageToken' if one exists,
1164+ otherwise None.
1165+ """
1166+ return next ((tokenName for tokenName in _PAGE_TOKEN_NAMES
1167+ if tokenName in fields ), None )
1168+
1169+ def _methodProperties (methodDesc , schema , name ):
1170+ """Get properties of a field in a method description.
1171+
1172+ Args:
1173+ methodDesc: object, fragment of deserialized discovery document that
1174+ describes the method.
1175+ schema: object, mapping of schema names to schema descriptions.
1176+ name: string, name of top-level field in method description.
1177+
1178+ Returns:
1179+ Object representing fragment of deserialized discovery document
1180+ corresponding to 'properties' field of object corresponding to named field
1181+ in method description, if it exists, otherwise empty dict.
1182+ """
1183+ desc = methodDesc .get (name , {})
1184+ if '$ref' in desc :
1185+ desc = schema .get (desc ['$ref' ], {})
1186+ return desc .get ('properties' , {})
0 commit comments