簡體   English   中英

Google Cloud Endpoints API的DRY代碼

[英]DRY code for Google Cloud Endpoints APIs

我想避免為我的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,))

我可以復制/粘貼此代碼,然后將“ Post”更改為“ Category”,然后編輯PostMessagePostMessageCollectionpost_to_message ,但這似乎是不好的做法。 我不想重復我自己。 是否可以創建一個抽象API類並為PostAPICategoryAPIUserAPI創建子類? 還是有更好的方法來參數化PostPostMessagePostMessageCollectionpost_to_message和資源的路徑(“ / posts”,“ / categories”和“ / users”),這樣我就不必復制/粘貼該類。每一種資源? 這些類將使用相同的裝飾器使用相同的方法,並且我不想對每個資源都重復一遍。 我使用Python 2.7。

我也偶然發現了相同的問題,不幸的是,這對於google cloud endpoints是不可能的。 方法裝飾器需要請求描述(此處為PostMessageCollection )。 message.Message子類的請求描述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.

顯然,它不會節省很多時間。

該線程已有兩年歷史了,但是無論如何我都會提出一個想法。 它有一個明顯的缺陷。 在下面的更多內容。

我將假設您正在通過ndb API使用數據存儲。

這個想法是要引入通用消息類( EntityContainerEntityContainerList ),它們可以包含您的任何特定於應用程序的消息類的實例,並包括將路由映射到protorpcndb類的字典:

model.py

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

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

main.py

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

當然,您也可以使用DELETEPUTPATCH方法。

消息類越有用,此方法可以節省的代碼越多。 (我剛剛包括了兩個BookMovie作演示。)

上面提到的缺陷是EntityContainer類充斥着消息字段,任何類實例僅使用其中的兩個。 我不知道事情是如何進行的,所以我無法評估其嚴重性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM