cloudendpoints / endpoints-python

A Python framework for building RESTful APIs on Google App Engine
Apache License 2.0
51 stars 17 forks source link

DateTime fields cause 500s as query parameters #49

Open aetherknight opened 7 years ago

aetherknight commented 7 years ago

Endpoints and/or protorpc triggers a 500 error if a DateTime field is used as a query parameter instead of in the body of a request.

Reference Code

import endpoints
from protorpc import message_types, messages, remote

class BodyMessage(messages.Message):
    body_date = message_types.DateTimeField(1)

@endpoints.api(
    name='someapi',
    version='v1',
    allowed_client_ids=[endpoints.API_EXPLORER_CLIENT_ID],
    scopes=[endpoints.EMAIL_SCOPE]
)
class SomeApiService(remote.Service):

    QUERY_RESOURCE = endpoints.ResourceContainer(message_types.VoidMessage, query_date=message_types.DateTimeField(1))

    @endpoints.method(QUERY_RESOURCE, message_types.VoidMessage, path='get', http_method='GET', name='get')
    def get(self, request):
        print request.query_date
        return message_types.VoidMessage()

    @endpoints.method(BodyMessage, message_types.VoidMessage, path='post', http_method='POST', name='post')
    def post(self, request):
        print request.body_date
        return message_types.VoidMessage()

APPLICATION = endpoints.api_server([SomeApiService])

DateTime in a JSON body works as expected

When the post method is called with the date time in the JSON body in an ISO 8601-like format, it works fine:

datetime_body

DateTime as a query parameter generates erroneous APIs Explorer UI that causes 500s

However, when a datetime is in a query parameter, the endpoints API specification tells APIs explorer to show the fields that make up the DateTimeField type instead of a single string field:

datetime_query

If I try to supply what I would think is a valid DateTime in milliseconds since the epoch and timezone offset, I get a 500 error:

ERROR    2017-02-23 15:30:56,505 wsgi.py:279]
Traceback (most recent call last):
  File "/Users/wsorvis/google-cloud-sdk/platform/google_appengine/google/appengine/runtime/wsgi.py", line 268, in Handle
    for chunk in result:
  File "/Users/wsorvis/src/datatheorem/endpoints_sample/lib/endpoints/endpoints_dispatcher.py", line 117, in __call__
    yield self.dispatch(request, start_response)
  File "/Users/wsorvis/src/datatheorem/endpoints_sample/lib/endpoints/endpoints_dispatcher.py", line 149, in dispatch
    return self.call_backend(request, start_response)
  File "/Users/wsorvis/src/datatheorem/endpoints_sample/lib/endpoints/endpoints_dispatcher.py", line 350, in call_backend
    body_iter = self._backend(transformed_environ, start_response_proxy.Proxy)
  File "/Users/wsorvis/src/datatheorem/endpoints_sample/lib/endpoints/apiserving.py", line 428, in __call__
    body_iter = self.service_app(environ, start_response_proxy.Proxy)
  File "/Users/wsorvis/google-cloud-sdk/platform/google_appengine/lib/protorpc-1.0/protorpc/wsgi/util.py", line 173, in first_found_app
    response = app(environ, first_found_start_response)
  File "/Users/wsorvis/google-cloud-sdk/platform/google_appengine/lib/protorpc-1.0/protorpc/wsgi/service.py", line 148, in protorpc_service_app
    remote_info.request_type, environ['wsgi.input'].read(content_length))
  File "/Users/wsorvis/google-cloud-sdk/platform/google_appengine/lib/protorpc-1.0/protorpc/remote.py", line 1121, in decode_message
    return self.__protocol.decode_message(message_type, encoded_message)
  File "/Users/wsorvis/google-cloud-sdk/platform/google_appengine/lib/protorpc-1.0/protorpc/protojson.py", line 201, in decode_message
    message = self.__decode_dictionary(message_type, dictionary)
  File "/Users/wsorvis/google-cloud-sdk/platform/google_appengine/lib/protorpc-1.0/protorpc/protojson.py", line 279, in __decode_dictionary
    valid_value.append(self.decode_field(field, item))
  File "/Users/wsorvis/src/datatheorem/endpoints_sample/lib/endpoints/protojson.py", line 106, in decode_field
    return super(EndpointsProtoJson, self).decode_field(field, value)
  File "/Users/wsorvis/google-cloud-sdk/platform/google_appengine/lib/protorpc-1.0/protorpc/protojson.py", line 312, in decode_field
    return util.decode_datetime(value)
  File "/Users/wsorvis/google-cloud-sdk/platform/google_appengine/lib/protorpc-1.0/protorpc/util.py", line 463, in decode_datetime
    time_zone_match = _TIME_ZONE_RE.search(encoded_datetime)
TypeError: expected string or buffer
INFO     2017-02-23 15:30:56,513 module.py:806] default: "GET /_ah/api/someapi/v1/get?query_date.milliseconds=0&query_date.time_zone_offset=0 HTTP/1.1" 500 -

Note: Locally on the dev_appserver and both endpoints 1 and endpoints-2.0.2, if I send the query without using the fields from APIs Explorer, then it works as expected. Eg, the URL http://localhost:8080/_ah/api/someapi/v1/get?query_date=2017-01-01T00:00:00 responds with a 204 (and it prints the supplied date).

When deployed to appengine with endpoints 1, trying to submit a string to the query_date directly causes a 500 that does not show up in my logs. I have not tested endpoints 2 when deployed yet.

Expected Behavior

the endpoints API description provided to APIs Explorer should expose a datetime query parameter as a string field, the same way it does when a datetime field is in the body.

aetherknight commented 7 years ago

To clarify further, there are 2 problems:

mchalek commented 7 years ago

I ran into the same problem today. I put a log message into protorpc util.py to see what was being passed to the decoded_datetime function, and it's a dictionary with the milliseconds and time_zone_offset arguments as keys. Of course, that method expects a string argument.