def testUrlGeneration(self): IATA_RESOURCE = resource_container.ResourceContainer( iata=messages.StringField(1)) class IataParam(messages.Message): iata = messages.StringField(1) class Airport(messages.Message): iata = messages.StringField(1, required=True) name = messages.StringField(2, required=True) @api_config.api(name='iata', version='1.6.9') class IataApi(remote.Service): @api_config.method(IATA_RESOURCE, Airport, path='airport/{iata}', http_method='GET', name='get_airport') def get_airport(self, request): return Airport(iata=request.iata, name='irrelevant') doc = self.generator.get_discovery_doc([IataApi], hostname='iata.appspot.com') assert doc['baseUrl'] == 'https://iata.appspot.com/_ah/api/iata/v1/' assert doc['rootUrl'] == 'https://iata.appspot.com/_ah/api/' assert doc['servicePath'] == 'iata/v1/'
def testDefaultScope(self): IATA_RESOURCE = resource_container.ResourceContainer( iata=messages.StringField(1)) class IataParam(messages.Message): iata = messages.StringField(1) class Airport(messages.Message): iata = messages.StringField(1, required=True) name = messages.StringField(2, required=True) @api_config.api(name='iata', version='v1', auth_level=api_config.AUTH_LEVEL.REQUIRED, allowed_client_ids=users_id_token.SKIP_CLIENT_ID_CHECK) class IataApi(remote.Service): @api_config.method(IATA_RESOURCE, Airport, path='airport/{iata}', http_method='GET', name='get_airport') def get_airport(self, request): return Airport(iata=request.iata, name='irrelevant') doc = self.generator.get_discovery_doc([IataApi]) auth = doc['auth'] assert auth == { 'oauth2': { 'scopes': { 'https://www.googleapis.com/auth/userinfo.email': { 'description': 'View your email address' } } } }
def testMultipleClassService(self): '''If multiple classes of a single service are passed to the generator, the document should show all methods from all classes.''' class Airport(messages.Message): iata = messages.StringField(1, required=True) name = messages.StringField(2, required=True) IATA_RESOURCE = resource_container.ResourceContainer( iata=messages.StringField(1, required=True) ) class AirportList(messages.Message): airports = messages.MessageField(Airport, 1, repeated=True) @api_config.api(name='iata', version='v1') class ServicePart1(remote.Service): @api_config.method( message_types.VoidMessage, AirportList, path='airports', http_method='GET', name='list_airports') def list_airports(self, request): return AirportList(airports=[ Airport(iata=u'DEN', name=u'Denver International Airport'), Airport(iata=u'SEA', name=u'Seattle Tacoma International Airport'), ]) @api_config.api(name='iata', version='v1') class ServicePart2(remote.Service): @api_config.method( IATA_RESOURCE, Airport, path='airport/{iata}', http_method='GET', name='get_airport') def get_airport(self, request): airports = { 'DEN': 'Denver International Airport' } if request.iata not in airports: raise endpoints.NotFoundException() return Airport(iata=request.iata, name=airports[request.iata]) doc = self.generator.get_discovery_doc([ServicePart1, ServicePart2]) self.assertItemsEqual(doc['methods'].keys(), [u'get_airport', u'list_airports'])
def testCustomScope(self): SCOPE = endpoints_types.OAuth2Scope( scope='https://www.googleapis.com/auth/santa', description='Access your letter to Santa') IATA_RESOURCE = resource_container.ResourceContainer( iata=messages.StringField(1) ) class IataParam(messages.Message): iata = messages.StringField(1) class Airport(messages.Message): iata = messages.StringField(1, required=True) name = messages.StringField(2, required=True) @api_config.api( name='iata', version='v1', scopes=[SCOPE], auth_level=api_config.AUTH_LEVEL.REQUIRED, allowed_client_ids=users_id_token.SKIP_CLIENT_ID_CHECK) class IataApi(remote.Service): @api_config.method( IATA_RESOURCE, Airport, path='airport/{iata}', http_method='GET', name='get_airport') def get_airport(self, request): return Airport(iata=request.iata, name='irrelevant') doc = self.generator.get_discovery_doc([IataApi]) auth = doc['auth'] assert auth == { 'oauth2': { 'scopes': { SCOPE.scope: { 'description': SCOPE.description } } } }
def testAllFieldTypes(self): class PutRequest(messages.Message): """Message with just a body field.""" body = messages.MessageField(AllFields, 1) # pylint: disable=invalid-name class ItemsPutRequest(messages.Message): """Message with path params and a body field.""" body = messages.MessageField(AllFields, 1) entryId = messages.StringField(2, required=True) class ItemsPutRequestForContainer(messages.Message): """Message with path params and a body field.""" body = messages.MessageField(AllFields, 1) items_put_request_container = resource_container.ResourceContainer( ItemsPutRequestForContainer, entryId=messages.StringField(2, required=True)) # pylint: disable=invalid-name class EntryPublishRequest(messages.Message): """Message with two required params, one in path, one in body.""" title = messages.StringField(1, required=True) entryId = messages.StringField(2, required=True) class EntryPublishRequestForContainer(messages.Message): """Message with two required params, one in path, one in body.""" title = messages.StringField(1, required=True) entry_publish_request_container = resource_container.ResourceContainer( EntryPublishRequestForContainer, entryId=messages.StringField(2, required=True)) class BooleanMessageResponse(messages.Message): result = messages.BooleanField(1, required=True) @api_config.api(name='root', hostname='example.appspot.com', version='v1') class MyService(remote.Service): """Describes MyService.""" @api_config.method(AllFields, message_types.VoidMessage, path='entries', http_method='GET', name='entries.get') def entries_get(self, unused_request): """All field types in the query parameters.""" return message_types.VoidMessage() @api_config.method(ALL_FIELDS_AS_PARAMETERS, message_types.VoidMessage, path='entries/container', http_method='GET', name='entries.getContainer') def entries_get_container(self, unused_request): """All field types in the query parameters.""" return message_types.VoidMessage() @api_config.method(PutRequest, BooleanMessageResponse, path='entries', name='entries.put') def entries_put(self, unused_request): """Request body is in the body field.""" return BooleanMessageResponse(result=True) @api_config.method(AllFields, message_types.VoidMessage, path='process', name='entries.process') def entries_process(self, unused_request): """Message is the request body.""" return message_types.VoidMessage() @api_config.method(message_types.VoidMessage, message_types.VoidMessage, name='entries.nested.collection.action', path='nested') def entries_nested_collection_action(self, unused_request): """A VoidMessage for a request body.""" return message_types.VoidMessage() @api_config.method(AllFields, AllFields, name='entries.roundtrip', path='roundtrip') def entries_roundtrip(self, unused_request): """All field types in the request and response.""" pass # Test a method with a required parameter in the request body. @api_config.method(EntryPublishRequest, message_types.VoidMessage, path='entries/{entryId}/publish', name='entries.publish') def entries_publish(self, unused_request): """Path has a parameter and request body has a required param.""" return message_types.VoidMessage() @api_config.method(entry_publish_request_container, message_types.VoidMessage, path='entries/container/{entryId}/publish', name='entries.publishContainer') def entries_publish_container(self, unused_request): """Path has a parameter and request body has a required param.""" return message_types.VoidMessage() # Test a method with a parameter in the path and a request body. @api_config.method(ItemsPutRequest, message_types.VoidMessage, path='entries/{entryId}/items', name='entries.items.put') def items_put(self, unused_request): """Path has a parameter and request body is in the body field.""" return message_types.VoidMessage() @api_config.method(items_put_request_container, message_types.VoidMessage, path='entries/container/{entryId}/items', name='entries.items.putContainer') def items_put_container(self, unused_request): """Path has a parameter and request body is in the body field.""" return message_types.VoidMessage() api = json.loads(self.generator.pretty_print_config_to_json(MyService)) # Some constants to shorten line length in expected OpenAPI output prefix = 'OpenApiGeneratorTest' boolean_response = prefix + 'BooleanMessageResponse' all_fields = prefix + 'AllFields' nested = prefix + 'Nested' entry_publish_request = prefix + 'EntryPublishRequest' publish_request_for_container = prefix + 'EntryPublishRequestForContainer' items_put_request = prefix + 'ItemsPutRequest' put_request_for_container = prefix + 'ItemsPutRequestForContainer' put_request = prefix + 'PutRequest' expected_openapi = { 'swagger': '2.0', 'info': { 'title': 'root', 'description': 'Describes MyService.', 'version': 'v1', }, 'host': 'example.appspot.com', 'consumes': ['application/json'], 'produces': ['application/json'], 'schemes': ['https'], 'basePath': '/_ah/api', 'paths': { '/root/v1/entries': { 'get': { 'operationId': 'MyService_entriesGet', 'parameters': [{ 'name': 'bool_value', 'in': 'query', 'type': 'boolean', }, { 'name': 'bytes_value', 'in': 'query', 'type': 'string', 'format': 'byte', }, { 'name': 'double_value', 'in': 'query', 'type': 'number', 'format': 'double', }, { 'name': 'enum_value', 'in': 'query', 'type': 'string', 'enum': [ 'VAL1', 'VAL2', ], }, { 'name': 'float_value', 'in': 'query', 'type': 'number', 'format': 'float', }, { 'name': 'int32_value', 'in': 'query', 'type': 'integer', 'format': 'int32', }, { 'name': 'int64_value', 'in': 'query', 'type': 'string', 'format': 'int64', }, { 'name': 'string_value', 'in': 'query', 'type': 'string', }, { 'name': 'uint32_value', 'in': 'query', 'type': 'integer', 'format': 'uint32', }, { 'name': 'uint64_value', 'in': 'query', 'type': 'string', 'format': 'uint64', }, { 'name': 'sint32_value', 'in': 'query', 'type': 'integer', 'format': 'int32', }, { 'name': 'sint64_value', 'in': 'query', 'type': 'string', 'format': 'int64', }], 'responses': { '200': { 'description': 'A successful response', }, }, }, 'post': { 'operationId': 'MyService_entriesPut', 'parameters': [], 'responses': { '200': { 'description': 'A successful response', 'schema': { '$ref': self._def_path(boolean_response), }, }, }, }, }, '/root/v1/entries/container': { 'get': { 'operationId': 'MyService_entriesGetContainer', 'parameters': [ { 'name': 'bool_value', 'in': 'query', 'type': 'boolean', }, { 'name': 'bytes_value', 'in': 'query', 'type': 'string', 'format': 'byte', }, { 'name': 'double_value', 'in': 'query', 'type': 'number', 'format': 'double', }, { 'name': 'enum_value', 'in': 'query', 'type': 'string', 'enum': [ 'VAL1', 'VAL2', ], }, { 'name': 'float_value', 'in': 'query', 'type': 'number', 'format': 'float', }, { 'name': 'int32_value', 'in': 'query', 'type': 'integer', 'format': 'int32', }, { 'name': 'int64_value', 'in': 'query', 'type': 'string', 'format': 'int64', }, { 'name': 'string_value', 'in': 'query', 'type': 'string', }, { 'name': 'uint32_value', 'in': 'query', 'type': 'integer', 'format': 'uint32', }, { 'name': 'uint64_value', 'in': 'query', 'type': 'string', 'format': 'uint64', }, { 'name': 'sint32_value', 'in': 'query', 'type': 'integer', 'format': 'int32', }, { 'name': 'sint64_value', 'in': 'query', 'type': 'string', 'format': 'int64', }, ], 'responses': { '200': { 'description': 'A successful response', }, }, }, }, '/root/v1/entries/container/{entryId}/items': { 'post': { 'operationId': 'MyService_itemsPutContainer', 'parameters': [ { 'name': 'entryId', 'in': 'path', 'required': True, 'type': 'string', }, ], 'responses': { '200': { 'description': 'A successful response', }, }, }, }, '/root/v1/entries/container/{entryId}/publish': { 'post': { 'operationId': 'MyService_entriesPublishContainer', 'parameters': [ { 'name': 'entryId', 'in': 'path', 'required': True, 'type': 'string', }, ], 'responses': { '200': { 'description': 'A successful response', }, }, }, }, '/root/v1/entries/{entryId}/items': { 'post': { 'operationId': 'MyService_itemsPut', 'parameters': [ { 'name': 'entryId', 'in': 'path', 'required': True, 'type': 'string', }, ], 'responses': { '200': { 'description': 'A successful response', }, }, }, }, '/root/v1/entries/{entryId}/publish': { 'post': { 'operationId': 'MyService_entriesPublish', 'parameters': [ { 'name': 'entryId', 'in': 'path', 'required': True, 'type': 'string', }, ], 'responses': { '200': { 'description': 'A successful response', }, }, }, }, '/root/v1/nested': { 'post': { 'operationId': 'MyService_entriesNestedCollectionAction', 'parameters': [], 'responses': { '200': { 'description': 'A successful response', }, }, }, }, '/root/v1/process': { 'post': { 'operationId': 'MyService_entriesProcess', 'parameters': [], 'responses': { '200': { 'description': 'A successful response', }, }, }, }, '/root/v1/roundtrip': { 'post': { 'operationId': 'MyService_entriesRoundtrip', 'parameters': [], 'responses': { '200': { 'description': 'A successful response', 'schema': { '$ref': self._def_path(all_fields) }, }, }, }, }, }, 'definitions': { all_fields: { 'type': 'object', 'properties': { 'bool_value': { 'type': 'boolean', }, 'bytes_value': { 'type': 'string', 'format': 'byte', }, 'datetime_value': { 'type': 'string', 'format': 'date-time', }, 'double_value': { 'type': 'number', 'format': 'double', }, 'enum_value': { 'type': 'string', 'enum': [ 'VAL1', 'VAL2', ], }, 'float_value': { 'type': 'number', 'format': 'float', }, 'int32_value': { 'type': 'integer', 'format': 'int32', }, 'int64_value': { 'type': 'string', 'format': 'int64', }, 'message_field_value': { '$ref': self._def_path(nested), 'description': 'Message class to be used in a message field.', }, 'sint32_value': { 'type': 'integer', 'format': 'int32', }, 'sint64_value': { 'type': 'string', 'format': 'int64', }, 'string_value': { 'type': 'string', }, 'uint32_value': { 'type': 'integer', 'format': 'uint32', }, 'uint64_value': { 'type': 'string', 'format': 'uint64', }, }, }, boolean_response: { 'type': 'object', 'properties': { 'result': { 'type': 'boolean', }, }, 'required': ['result'], }, entry_publish_request: { 'type': 'object', 'properties': { 'entryId': { 'type': 'string', }, 'title': { 'type': 'string', }, }, 'required': [ 'entryId', 'title', ] }, publish_request_for_container: { 'type': 'object', 'properties': { 'title': { 'type': 'string', }, }, 'required': [ 'title', ] }, items_put_request: { 'type': 'object', 'properties': { 'body': { '$ref': self._def_path(all_fields), 'description': 'Contains all field types.' }, 'entryId': { 'type': 'string', }, }, 'required': [ 'entryId', ] }, nested: { 'type': 'object', 'properties': { 'int_value': { 'type': 'string', 'format': 'int64', }, 'string_value': { 'type': 'string', }, }, }, put_request: { 'type': 'object', 'properties': { 'body': { '$ref': self._def_path(all_fields), 'description': 'Contains all field types.', }, }, }, put_request_for_container: { 'type': 'object', 'properties': { 'body': { '$ref': self._def_path(all_fields), 'description': 'Contains all field types.', }, }, }, }, 'securityDefinitions': { 'google_id_token': { 'authorizationUrl': '', 'flow': 'implicit', 'type': 'oauth2', 'x-issuer': 'accounts.google.com', 'x-jwks_uri': 'https://www.googleapis.com/oauth2/v1/certs', }, }, } test_util.AssertDictEqual(expected_openapi, api, self)
float_value = messages.FloatField(5, variant=messages.Variant.FLOAT) int32_value = messages.IntegerField(6, variant=messages.Variant.INT32) int64_value = messages.IntegerField(7, variant=messages.Variant.INT64) string_value = messages.StringField(8, variant=messages.Variant.STRING) uint32_value = messages.IntegerField(9, variant=messages.Variant.UINT32) uint64_value = messages.IntegerField(10, variant=messages.Variant.UINT64) sint32_value = messages.IntegerField(11, variant=messages.Variant.SINT32) sint64_value = messages.IntegerField(12, variant=messages.Variant.SINT64) message_field_value = messages.MessageField(Nested, 13) datetime_value = message_types.DateTimeField(14) # This is used test "all fields" as query parameters instead of the body # in a request. ALL_FIELDS_AS_PARAMETERS = resource_container.ResourceContainer( **{field.name: field for field in AllFields.all_fields()}) class BaseOpenApiGeneratorTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.maxDiff = None def setUp(self): self.generator = openapi_generator.OpenApiGenerator() def _def_path(self, path): return '#/definitions/' + path
exception_class = (getattr(api_exceptions, request.exception_type, None) or globals().get(request.exception_type)) raise exception_class(request.message) @api_config.api('bservice', 'v3', hostname='bservice.appspot.com', description='Another Service API') class BService(remote.Service): @api_config.method(path='noop') def Noop(self, unused_request): return message_types.VoidMessage() test_request = resource_container.ResourceContainer( message_types.VoidMessage, id=messages.IntegerField(1, required=True)) @api_config.api(name='testapi', version='v3', description='A wonderful API.') class TestService(remote.Service): @api_config.method(test_request, message_types.VoidMessage, http_method='DELETE', path='items/{id}') # Silence lint warning about method naming conventions # pylint: disable=g-bad-name def delete(self, unused_request): return message_types.VoidMessage()
def testAllFieldTypes(self): class PutRequest(messages.Message): """Message with just a body field.""" body = messages.MessageField(AllFields, 1) # pylint: disable=invalid-name class ItemsPutRequest(messages.Message): """Message with path params and a body field.""" body = messages.MessageField(AllFields, 1) entryId = messages.StringField(2, required=True) class ItemsPutRequestForContainer(messages.Message): """Message with path params and a body field.""" body = messages.MessageField(AllFields, 1) items_put_request_container = resource_container.ResourceContainer( ItemsPutRequestForContainer, entryId=messages.StringField(2, required=True)) # pylint: disable=invalid-name class EntryPublishRequest(messages.Message): """Message with two required params, one in path, one in body.""" title = messages.StringField(1, required=True) entryId = messages.StringField(2, required=True) class EntryPublishRequestForContainer(messages.Message): """Message with two required params, one in path, one in body.""" title = messages.StringField(1, required=True) entry_publish_request_container = resource_container.ResourceContainer( EntryPublishRequestForContainer, entryId=messages.StringField(2, required=True)) class BooleanMessageResponse(messages.Message): result = messages.BooleanField(1, required=True) @api_config.api(name='root', hostname='example.appspot.com', version='v1', description='This is an API') class MyService(remote.Service): """Describes MyService.""" @api_config.method(message_types.VoidMessage, BooleanMessageResponse, path='toplevel:withcolon', http_method='GET', name='toplevelwithcolon') def toplevel(self, unused_request): return BooleanMessageResponse(result=True) @api_config.method(AllFields, message_types.VoidMessage, path='entries', http_method='GET', name='entries.get') def entries_get(self, unused_request): """All field types in the query parameters.""" return message_types.VoidMessage() @api_config.method(ALL_FIELDS_AS_PARAMETERS, message_types.VoidMessage, path='entries/container', http_method='GET', name='entries.getContainer') def entries_get_container(self, unused_request): """All field types in the query parameters.""" return message_types.VoidMessage() @api_config.method(PutRequest, BooleanMessageResponse, path='entries', name='entries.put') def entries_put(self, unused_request): """Request body is in the body field.""" return BooleanMessageResponse(result=True) @api_config.method(AllFields, message_types.VoidMessage, path='process', name='entries.process') def entries_process(self, unused_request): """Message is the request body.""" return message_types.VoidMessage() @api_config.method(message_types.VoidMessage, message_types.VoidMessage, name='entries.nested.collection.action', path='nested') def entries_nested_collection_action(self, unused_request): """A VoidMessage for a request body.""" return message_types.VoidMessage() @api_config.method(AllFields, AllFields, name='entries.roundtrip', path='roundtrip') def entries_roundtrip(self, unused_request): """All field types in the request and response.""" pass # Test a method with a required parameter in the request body. @api_config.method(EntryPublishRequest, message_types.VoidMessage, path='entries/{entryId}/publish', name='entries.publish') def entries_publish(self, unused_request): """Path has a parameter and request body has a required param.""" return message_types.VoidMessage() @api_config.method(entry_publish_request_container, message_types.VoidMessage, path='entries/container/{entryId}/publish', name='entries.publishContainer') def entries_publish_container(self, unused_request): """Path has a parameter and request body has a required param.""" return message_types.VoidMessage() # Test a method with a parameter in the path and a request body. @api_config.method(ItemsPutRequest, message_types.VoidMessage, path='entries/{entryId}/items', name='entries.items.put') def items_put(self, unused_request): """Path has a parameter and request body is in the body field.""" return message_types.VoidMessage() @api_config.method(items_put_request_container, message_types.VoidMessage, path='entries/container/{entryId}/items', name='entries.items.putContainer') def items_put_container(self, unused_request): """Path has a parameter and request body is in the body field.""" return message_types.VoidMessage() api = json.loads(self.generator.pretty_print_config_to_json(MyService)) try: pwd = os.path.dirname(os.path.realpath(__file__)) test_file = os.path.join(pwd, 'testdata', 'discovery', 'allfields.json') with open(test_file) as f: expected_discovery = json.loads(f.read()) except IOError as e: print 'Could not find expected output file ' + test_file raise e test_util.AssertDictEqual(expected_discovery, api, self)
def testResourceContainer(self): rc = resource_container.ResourceContainer( **{field.name: field for field in AllBasicFields.all_fields()}) self.assertEqual(rc.body_message_class, message_types.VoidMessage)