From 2251972e798f1a305ee5c018068b01adb34972b8 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 21 Apr 2016 14:24:21 -0700 Subject: [PATCH] Adding endpoints sample Change-Id: Id13fe3250662f13fbbcea3700eb5d7267c6f2d1b --- appengine/endpoints/app.yaml | 16 ++++ appengine/endpoints/main.py | 149 +++++++++++++++++++++++++++++++ appengine/endpoints/main_test.py | 53 +++++++++++ 3 files changed, 218 insertions(+) create mode 100644 appengine/endpoints/app.yaml create mode 100644 appengine/endpoints/main.py create mode 100644 appengine/endpoints/main_test.py diff --git a/appengine/endpoints/app.yaml b/appengine/endpoints/app.yaml new file mode 100644 index 000000000000..31030b73367e --- /dev/null +++ b/appengine/endpoints/app.yaml @@ -0,0 +1,16 @@ +runtime: python27 +threadsafe: true +api_version: 1 + +handlers: +# The endpoints handler must be mapped to /_ah/spi. +# Apps send requests to /_ah/api, but the endpoints service handles mapping +# those requests to /_ah/spi. +- url: /_ah/spi/.* + script: main.api + +libraries: +- name: pycrypto + version: 2.6 +- name: endpoints + version: 1.0 diff --git a/appengine/endpoints/main.py b/appengine/endpoints/main.py new file mode 100644 index 000000000000..fc4049b1309a --- /dev/null +++ b/appengine/endpoints/main.py @@ -0,0 +1,149 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This is a sample Hello World API implemented using Google Cloud +Endpoints.""" + +# [START imports] +import endpoints +from protorpc import message_types +from protorpc import messages +from protorpc import remote +# [END imports] + + +# [START messages] +class Greeting(messages.Message): + """Greeting that stores a message.""" + message = messages.StringField(1) + + +class GreetingCollection(messages.Message): + """Collection of Greetings.""" + items = messages.MessageField(Greeting, 1, repeated=True) + + +STORED_GREETINGS = GreetingCollection(items=[ + Greeting(message='hello world!'), + Greeting(message='goodbye world!'), +]) +# [END messages] + + +# [START greeting_api] +@endpoints.api(name='greeting', version='v1') +class GreetingApi(remote.Service): + + @endpoints.method( + # This method does not take a request message. + message_types.VoidMessage, + # This method returns a GreetingCollection message. + GreetingCollection, + path='greetings', + http_method='GET', + name='greetings.list') + def list_greetings(self, unused_request): + return STORED_GREETINGS + + # ResourceContainers are used to encapsuate a request body and url + # parameters. This one is used to represent the Greeting ID for the + # greeting_get method. + GET_RESOURCE = endpoints.ResourceContainer( + # The request body should be empty. + message_types.VoidMessage, + # Accept one url parameter: and integer named 'id' + id=messages.IntegerField(1, variant=messages.Variant.INT32)) + + @endpoints.method( + # Use the ResourceContainer defined above to accept an empty body + # but an ID in the query string. + GET_RESOURCE, + # This method returns a Greeting message. + Greeting, + # The path defines the source of the URL parameter 'id'. If not + # specified here, it would need to be in the query string. + path='greetings/{id}', + http_method='GET', + name='greetings.get') + def get_greeting(self, request): + try: + # request.id is used to access the URL parameter. + return STORED_GREETINGS.items[request.id] + except (IndexError, TypeError): + raise endpoints.NotFoundException( + 'Greeting {} not found'.format(request.id)) + # [END greeting_api] + + # [START multiply] + # This ResourceContainer is similar to the one used for get_greeting, but + # this one also contains a request body in the form of a Greeting message. + MULTIPLY_RESOURCE = endpoints.ResourceContainer( + Greeting, + times=messages.IntegerField(2, variant=messages.Variant.INT32, + required=True)) + + @endpoints.method( + # This method accepts a request body containing a Greeting message + # and a URL parameter specifying how many times to multiply the + # message. + MULTIPLY_RESOURCE, + # This method returns a Greeting message. + Greeting, + path='greetings/multiply/{times}', + http_method='POST', + name='greetings.multiply') + def multiply_greeting(self, request): + return Greeting(message=request.message * request.times) + # [END multiply] + + +# [START auth_config] +WEB_CLIENT_ID = 'replace this with your web client application ID' +ANDROID_CLIENT_ID = 'replace this with your Android client ID' +IOS_CLIENT_ID = 'replace this with your iOS client ID' +ANDROID_AUDIENCE = WEB_CLIENT_ID +ALLOWED_CLIENT_IDS = [ + WEB_CLIENT_ID, ANDROID_CLIENT_ID, IOS_CLIENT_ID, + endpoints.API_EXPLORER_CLIENT_ID] +# [END auth_config] + + +# [START authed_greeting_api] +@endpoints.api( + name='authed_greeting', + version='v1', + # Only allowed configured Client IDs to access this API. + allowed_client_ids=ALLOWED_CLIENT_IDS, + # Only allow auth tokens with the given audience to access this API. + audiences=[ANDROID_AUDIENCE], + # Require auth tokens to have the following scopes to access this API. + scopes=[endpoints.EMAIL_SCOPE]) +class AuthedGreetingApi(remote.Service): + + @endpoints.method( + message_types.VoidMessage, + Greeting, + path='greet', + http_method='POST', + name='greet') + def greet(self, request): + user = endpoints.get_current_user() + user_name = user.email() if user else 'Anonymous' + return Greeting(message='Hello, {}'.format(user_name)) +# [END authed_greeting_api] + + +# [START api_server] +api = endpoints.api_server([GreetingApi, AuthedGreetingApi]) +# [END api_server] diff --git a/appengine/endpoints/main_test.py b/appengine/endpoints/main_test.py new file mode 100644 index 000000000000..56fefa2ead66 --- /dev/null +++ b/appengine/endpoints/main_test.py @@ -0,0 +1,53 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import main +import mock +from protorpc import message_types + + +def test_list_greetings(testbed): + api = main.GreetingApi() + response = api.list_greetings(message_types.VoidMessage()) + assert len(response.items) == 2 + + +def test_get_greeting(testbed): + api = main.GreetingApi() + request = main.GreetingApi.get_greeting.remote.request_type(id=1) + response = api.get_greeting(request) + assert response.message == 'goodbye world!' + + +def test_multiply_greeting(testbed): + api = main.GreetingApi() + request = main.GreetingApi.multiply_greeting.remote.request_type( + times=4, + message='help I\'m trapped in a test case.') + response = api.multiply_greeting(request) + assert response.message == 'help I\'m trapped in a test case.' * 4 + + +def test_authed_greet(testbed): + api = main.AuthedGreetingApi() + + with mock.patch('main.endpoints.get_current_user') as user_mock: + user_mock.return_value = None + response = api.greet(message_types.VoidMessage()) + assert response.message == 'Hello, Anonymous' + + user_mock.return_value = mock.Mock() + user_mock.return_value.email.return_value = 'user@example.com' + response = api.greet(message_types.VoidMessage()) + assert response.message == 'Hello, user@example.com'