2

我想避免爲我的Google App Engine應用程序的不同模型創建Google Cloud Endpoints API的樣板代碼。假設我有一個PostUserCategory模型。數據存儲在數據存儲區中。我想用資源postsuserscategories創建一個REST API。我寫了下面的代碼,posts資源:Google Cloud Endpoints API的DRY代碼

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創建子類?還是有更好的方法來參數化PostPostMessage,PostMessageCollection,post_to_message和資源路徑(「/ posts」,「/ categories」和「/ users」),以便我不必複製/粘貼類爲每個資源?這些類將使用相同的裝飾器具有相同的方法,並且我不想爲每個資源重複該方法。我使用Python 2.7。

回答

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. 

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

0

這個線程已經兩歲了,但我會在那裏提出一個想法。它有一個明顯的缺陷;在下面更多。

我打算假設您通過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類擴充了消息字段,其中只有兩個被任何類實例使用。我不知道事情是如何工作的,所以我無法評估這種嚴重性。

相關問題