Google Cloud Endpoints API的DRY代码

时间:2013-12-12 09:52:11

标签: python google-app-engine python-2.7 dry google-cloud-endpoints

我想避免使用样板代码为我的Google App Engine应用程序的不同模型创建Google Cloud Endpoints API。假设我有PostUserCategory模型。数据存储在数据存储区中。我想创建一个包含资源postsuserscategories的REST API。我为posts资源编写了以下代码:

import endpoints
from protorpc import messages
from protorpc import message_types
from protorpc import remote
from blog.models import Post
from cloud_endpoints import WEB_CLIENT_ID, ANDROID_CLIENT_ID, IOS_CLIENT_ID, ANDROID_AUDIENCE


class PostMessage(messages.Message):
    id = messages.StringField(1)
    title = messages.StringField(2)
    body = messages.StringField(3)


class PostMessageCollection(messages.Message):
    post_messages = messages.MessageField(PostMessage, 1, repeated=True)


def post_to_message(post):
    return PostMessage(
        id=str(post.key()),
        title=post.title,
        body=post.body)


ID_RESOURCE = endpoints.ResourceContainer(
    message_types.VoidMessage,
    id=messages.StringField(1, variant=messages.Variant.STRING))

PUT_RESOURCE = endpoints.ResourceContainer(
    PostMessage,
    id=messages.StringField(1, variant=messages.Variant.STRING))

POST_RESOURCE = endpoints.ResourceContainer(Post)


@endpoints.api(name='posts',
               version='v1',
               allowed_client_ids=[WEB_CLIENT_ID, ANDROID_CLIENT_ID, IOS_CLIENT_ID],
               audiences=[ANDROID_AUDIENCE])
class PostsApi(remote.Service):
    """List"""
    @endpoints.method(message_types.VoidMessage,
                      PostMessageCollection,
                      path='/posts',
                      http_method='GET',
                      name='posts.listPosts')
    def list(self, unused_request):
        post_messages = []
        for post in Post.all():
            post_messages.append(post_to_message(post))

        return PostCollection(post_messages=post_messages)

    """Get"""
    @endpoints.method(ID_RESOURCE,
                      PostMessage,
                      path='/posts/{id}',
                      http_method='GET',
                      name='posts.getPost')
    def get(self, request):
        try:
            return post_to_message(Post.get(request.id))

        except (IndexError, TypeError):
            raise endpoints.NotFoundException('Post %s not found.' % (request.id,))

    """Create"""
    @endpoints.method(POST_RESOURCE,
                      message_types.VoidMessage,
                      path='/posts',
                      http_method='POST',
                      name='posts.createPost')
    def create(self, request):
        post = Post(title=request.title, body=request.body)\
        post.put()
        return message_types.VoidMessage()

    """Update"""
    @endpoints.method(PUT_RESOURCE,
                      message_types.VoidMessage,
                      path='/posts/{id}',
                      http_method='POST',
                      name='posts.updatePost')
    def update(self, request):
        try:
            post = Post.get(request.id)
            post.title = request.title
            post.body = request.body
            return message_types.VoidMessage()
        except (IndexError, TypeError):
            raise endpoints.NotFoundException('Post %s not found.' % (request.id,))

    """Delete"""
    @endpoints.method(ID_RESOURCE,
                      message_types.VoidMessage,
                      path='/posts/{id}',
                      http_method='DELETE',
                      name='posts.deletePost')
    def delete(self, request):
        try:
            post = Post.get(request.id)
            post.delete()
            return message_types.VoidMessage()

        except (IndexError, TypeError):
            raise endpoints.NotFoundException('Post %s not found.' % (request.id,))

我可以复制/粘贴此代码,并在任何地方将“发布”更改为“类别”,然后修改PostMessagePostMessageCollectionpost_to_message,但这似乎是不好的做法。我不想重复自己。是否可以创建一个抽象API类并为PostAPICategoryAPIUserAPI创建子类?或者是否有更好的方法来参数化PostPostMessagePostMessageCollectionpost_to_message以及资源的路径(“/ posts”,“/ categories”和“/用户“)以便我不必为每个资源复制/粘贴类?这些类与相同的装饰器具有相同的方法,我不想为每个资源重复这一点。我使用Python 2.7。

2 个答案:

答案 0 :(得分:1)

我也偶然发现了同样的问题,遗憾的是google cloud endpoints无法做到这一点。方法装饰器需要请求描述(此处为PostMessageCollection)。对message.Message进行子类化的请求描述不允许通过继承进行重用,因此必须完全定义所有消息类而不进行任何继承。

然而,您可以在某种程度上实现这一点(虽然我没有测试过,现在想到它:))以下列方式:

# All the message and response definitions have to be here, complete.

class PostMessage(messages.Message):
    id = messages.StringField(1)
    title = messages.StringField(2)
    body = messages.StringField(3)


class PostMessageCollection(messages.Message):
    post_messages = messages.MessageField(PostMessage, 1, repeated=True)


def post_to_message(post):
    return PostMessage(
        id=str(post.key()),
        title=post.title,
        body=post.body)


ID_RESOURCE = endpoints.ResourceContainer(
    message_types.VoidMessage,
    id=messages.StringField(1, variant=messages.Variant.STRING))

PUT_RESOURCE = endpoints.ResourceContainer(
    PostMessage,
    id=messages.StringField(1, variant=messages.Variant.STRING))

POST_RESOURCE = endpoints.ResourceContainer(Post)

# Now define all the 'Category' related messages here.


@endpoints.api(name='posts_n_categories',  # The name can be a common one.
               version='v1',
               allowed_client_ids=[WEB_CLIENT_ID, ANDROID_CLIENT_ID, IOS_CLIENT_ID],
               audiences=[ANDROID_AUDIENCE])
class BaseAPI(remote.Service):
    """List"""
    # Common defs go here.
    MessageCollection = messages.Message
    PATH = '/'
    NAME = ''

    @staticmethod
    def converter(x):
        raise NotImplemented

    iterator = []
    collection = messages.Message
    @endpoints.method(message_types.VoidMessage,
                      MessageCollection,
                      path=PATH,
                      http_method='GET',
                      name=NAME)
    def list(self, unused_request):
        # Do the common work here. You can 
        _messages = []
        for post in self.__class__.iterator.all():
            _messages.append(self.__class__.converter(post))

        return self.__class__.collection(post_messages=_messages)




@endpoints.api(name='posts',  # The name can be different.
               version='v1',
               allowed_client_ids=[WEB_CLIENT_ID, ANDROID_CLIENT_ID, IOS_CLIENT_ID],
               audiences=[ANDROID_AUDIENCE])
class PostAPI(Base):
    # Post specific defs go here.
    MessageCollection = PostMessageCollection
    PATH = '/posts'
    NAME = 'posts.listPosts'
    converter = post_to_message
    iterator = Post
    collection = PostCollection


# Define the category class here.

显然,它不会节省太多时间。

答案 1 :(得分:0)

这个帖子已经两年了,不管怎样我都会提出一个想法。它有一个明显的缺陷;更多关于这一点。

我将假设您通过ndb API使用数据存储区。

我们的想法是引入通用消息类(EntityContainerEntityContainerList),它们可以包含任何特定于应用程序的消息类的实例,并包含将路由映射到{{1 }和protorpc类:

<强> model.py

ndb

<强> main.py

class Book(ndb.Model):
    title = ndb.StringProperty()
    author = ndb.StringProperty()

class Movie(ndb.Model):
    title = ndb.StringProperty()
    director = ndb.StringProperty()

当然,您也可以使用class Book(messages.Message): title = messages.StringField(1) author = messages.StringField(2) class Movie(messages.Message): title = messages.StringField(1) director = messages.StringField(2) class EntityContainer(messages.Message): book = messages.MessageField(Book, 1) movie = messages.MessageField(Movie, 2) id_ = messages.StringField(3) class EntityContainerList(messages.Message): entities = messages.MessageField(EntityContainer, 1, repeated=True) Map = { 'books': { 'message_class': Book, 'ndb_model_class': model.Book, 'entity_container_key': 'book' }, 'movies': { 'message_class': Movie, 'ndb_model_class': model.Movie, 'entity_container_key': 'movie' } } @endpoints.api(name='testApi', version='v1') class TestApi(remote.Service): GET_RESOURCE = endpoints.ResourceContainer( id_=messages.StringField(1), entities=messages.StringField(2) ) LIST_RESOURCE = endpoints.ResourceContainer( entities=messages.StringField(2) ) POST_RESOURCE = endpoints.ResourceContainer( EntityContainer, entities=messages.StringField(1), ) @endpoints.method( GET_RESOURCE, EntityContainer, path='{entities}/{id_}', http_method="GET") def get_entity(self, request): # The path tells us what kind of entity we're fetching. Entity = Map[request.entities]['message_class'] key = Map[request.entities]['entity_container_key'] # Pull from database. ndb_entity = ndb.Key(urlsafe=request.id_).get() # Formulate response. entity_container = EntityContainer(**{key: Entity(**ndb_entity.to_dict())}) entity_container.id_ = request.id_ logging.info("\n\nResponse: %s\n" % str(entity_container)) return entity_container @endpoints.method( LIST_RESOURCE, EntityContainerList, path='{entities}', http_method="GET") def list_entities(self, request): # The path tells us what kinds of entities we're fetching. Entity = Map[request.entities]['message_class'] NdbModel = Map[request.entities]['ndb_model_class'] key = Map[request.entities]['entity_container_key'] # Pull from database query = NdbModel.query() # Formulate response. entities = [ EntityContainer(**{'id_': q.key.urlsafe(), key: Entity(**q.to_dict())}) for q in query ] entity_container_list = EntityContainerList(entities=entities) logging.info("\n\nEntity list: %s\n" % str(entity_container_list)) return entity_container_list @endpoints.method( POST_RESOURCE, EntityContainer, path='{entities}', http_method='POST' ) def post_entity(self, request): # The path tells us what kind of entity we're' creating. Entity = Map[request.entities]['message_class'] NdbModel = Map[request.entities]['ndb_model_class'] key = Map[request.entities]['entity_container_key'] # Extract the body of the request body_message = getattr(request, key) body_dict = {f.name: getattr(body_message, f.name) for f in body_message.all_fields() if getattr(body_message, f.name)} # Write to database ndb_entity = NdbModel(**body_dict) ndb_key = ndb_entity.put() id_ = ndb_key.urlsafe() # Reload entity. Maybe some model hooks treated the data. ndb_entity = ndb_key.get() entity_container = EntityContainer(**{key: Entity(**ndb_entity.to_dict())}) entity_container.id_ = id_ logging.info("\n\nResponse: %s\n" % str(entity_container)) return entity_container DELETEPUT方法。

您拥有的消息类越多,此方法可以为您节省的代码越多。 (我刚刚包含了两个 - PATCHBook - 用于演示。)

上面提到的缺陷是Movie类使用消息字段膨胀,其中只有两个由任何类实例使用。我不知道事情是如何运作的,所以我无法评估其严重程度。