【问题标题】:Different classes for request and response请求和响应的不同类
【发布时间】:2015-10-09 07:12:14
【问题描述】:

我刚刚开始使用 Proto Datastore 创建 API。我想知道我的请求和响应是否可以有不同的类,前提是两者都是从 EndpointsModel 继承的。例如,

class my_request(EndpointsModel):
    #attributes

class my_response(EndpointsModel):
    #different set of attributes

@endpoints.api(name='Blah', version='v1')
    class testAPI(remote.Service):

    @my_request.method(name='myAPImethod', path='blah',
                      http_method='POST')
    def myAPImethod(self, req):
        #do something
        resp = my_response()
        return resp

这看起来行不通。所以有人可以告诉我如何创建这样的方法。我能想到的唯一另一种方法是恢复到原始的 protopc 方法,并将请求和响应类型都指定为装饰器的一部分。有没有办法使用 proto-datastore 来实现这一点? 提前谢谢你。

【问题讨论】:

    标签: python google-app-engine google-cloud-endpoints endpoints-proto-datastore


    【解决方案1】:

    您可以应用一种输入/输出字段filtering using request_fields= and response_fields= in your .method() 装饰器,但您不能指定不同的输入和输出消息类。

    如果您需要使用不同的对象,则必须使用标准端点和 protoRPC 类和装饰器。

    【讨论】:

      【解决方案2】:

      在您的示例中,您的布局非常简单,与所提出的问题或恕我直言,我认为您实际上想问的问题并不真正相符。

      我自己正在开发一个更复杂的 Endpoints API,但使用两种不同的模型来为我的 API 调用制作请求和响应对象;然而,它们是嵌套在另一个中的一个(或多个)。我在 App Engine 日志中找到了提示,该方法应用作警告:

      Method csapi.user.XXXXX specifies path parameters but you are not using a ResourceContainer. This will fail in future releases; please switch to using ResourceContainer as soon as possible.
      

      注意; ResourceContainers 可能非常棘手,需要您将 messages.Message 分类为您将从 protorpc 导入的 messages.Message。有关 ResourceContainers 的说明,请参阅 this StackOverflow 答案。

      以您的示例为基础,我们需要构建 ResourceContainers 来复制我们希望返回给被调用者的模型中的内容。

      import endpoints
      from protorpc import messages
      from protorpc import message_types
      from protorpc import remote
      from google.appengine.ext import ndb
      from google.appengine.ext.ndb import msgprop
      from endpoints_proto_datastore.ndb import EndpointsAliasProperty
      from endpoints_proto_datastore.ndb import EndpointsModel
      
      class MySubscriptionResponseMessage(messages.Message):
          id = messages.IntegerField(1)
          accountType = messages.EnumField(Account_Type, 2, required=True)
          isActive = messages.BooleanField(3, required=True, default=False)
          thisAcctEmail = messages.StringField(4)
          subscriberSince = message_types.DateTimeField(5)
      
      class MyUserResponseMessage(messages.Message):
          id = messages.IntegerField(1)
          subscriptions = messages.MessageField('SubscriptionReadResponseMessage', 2, repeated=True)
      
      class MySubscription(EndpointsModel):
          accountType = msgprop.EnumProperty(AccountType, choices=set([AccountType.POTENTIAL_LEAD, AccountType.BASIC, AccountType.ADVANCED, AccountType.PREMIUM]), required=True, default=AccountType.POTENTIAL_LEAD)
          isActive = ndb.BooleanProperty(required=True, indexed=True)
          thisAcctId = ndb.StringProperty(repeated=False, indexed=True, required=True)
          subscriberSince = ndb.DateTimeProperty(auto_now_add=True)
      
      class MyUser(EndpointsModel):
          subscription_key = ndb.KeyProperty(kind="Subscription", repeated=True)
          def IdSet(self, value):
              # By default, the property "id" assumes the "id" will be an integer in a
              # simple key -- e.g. ndb.Key(GSModel, 10) -- which is the default behavior
              # if no key is set. Instead, we wish to use a string value as the "id" here,
              # so first check if the value being set is a string.
              if not isinstance(value, basestring):
                  raise TypeError('ID must be a string.')
              # We call UpdateFromKey, which each of EndpointsModel.IdSet and
              # EndpointsModel.EntityKeySet use, to update the current entity using a
              # datastore key. This method sets the key on the current entity, attempts to
              # retrieve a corresponding entity from the datastore and then patch in any
              # missing values if an entity is found in the datastore.
              self.UpdateFromKey(ndb.Key(Csuser, value))
      
          @EndpointsAliasProperty(setter=IdSet, required=True)
          def id(self):
              # First check if the entity has a key.
              if self.key is not None:
                  # If the entity has a key, return only the string_id. The method id()
                  # would return any value, string, integer or otherwise, but we have a
                  # specific type we wish to use for the entity "id" and that is string.
                  return self.key.string_id()
      
          @EndpointsAliasProperty(repeated=True,property_type=Subscription.ProtoModel())
          def subscriptions(self):
          return ndb.get_multi(self.subscription_key)
      
      @endpoints.api(name='Blah', version='v1')
          class testAPI(remote.Service):
      
          @my_request.method(
              response_message=MyUserResourceContainer,
              name='myAPImethod', 
              path='blah',
              http_method='POST')
          def myAPImethod(self, req):
              #do something
              this_sub = MySubscription()
              subs = []
      
              ... how you manipulate this object is up to you ...
      
              for sub in subs
                  sub_msg = MySubscriptionResponseMessage(
                      id=this_sub.id, 
                      accountType=this_sub.accountType, 
                      isActive=this_sub.isActive, 
                      thisAcctEmail=this_sub.thisAcctEmail, 
                      subscriberSince=this_sub.subscriberSince, 
                  subs.append(sub_msg)
      
              return MyUserResponseMessage(
                  id=user1.id, 
                  subscriptions=subs)
      

      如您所见,这比您的简单示例更深入。

      加分:使用路径参数

      如果您希望为您的方法接受路径参数,例如我们假设的用户的 id 属性(即path='users/{id}/delete'),我们将在方法本身之前添加以下块:

      MY_REQUEST_RESOURCE_PAGE = endpoints.ResourceContainer(
          message_types.VoidMessage,
          id=messages.StringField(1, variant=messages.Variant.STRING),
          accountType=messages.EnumField(Account_Type, 2, required=True)
          isActive=messages.BooleanField(3, required=True),
          thisAcctEmail=messages.StringField(4, variant=messages.Variant.STRING),
          subscriberSince=messages.message_types.DateTimeField(5),
          cursor=messages.StringField(6, variant=messages.Variant.STRING, required=False, default="1"),
          limit=messages.IntegerField(7, variant=messages.Variant.INT32, required=False, default=10)
      )
      

      注意:注意附加属性cursorlimit,它们允许在您有数百个用户的情况下对返回的结果进行分页;这些在模型查询中经常用于此目的。

      现在,要完成接受路径参数的更改,请从上面的示例中替换此行:

      response_message=MyUserResponseMessage,
      

      有了这个:

      MY_REQUEST_RESOURCE_PAGE , MyUserResponseMessage,
      

      最后一点,无论是否使用路径参数,此设置都允许您在 MyUserResponseMessage 中嵌套一个或多个 MySubscriptionResponseMessage 项目以返回到被调用者,就像 MyUser 模型可能包含多个 @ 987654334@ 型号。这不需要向您的 api 方法添加任何内容,因为它已经是 MyUserResponseMessage 中的嵌套项。此外,如果不需要将这些项目返回给被调用者,则无需在响应消息中复制模型中的项目。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-27
        • 2020-07-07
        • 1970-01-01
        • 1970-01-01
        • 2016-07-18
        相关资源
        最近更新 更多