如何获取protobuf扩展属性的类型?

时间:2016-04-11 20:50:00

标签: python protocol-buffers

我有一个构建系统,必须从用户打包protobufs'投入。用户填写表单中的字段,提交它们,构建系统打包protobufs并沿着它们的方式发送它们。构建系统必须不知道它正在处理的类型。否则,每次项目团队更改他们的.protos时,我都必须进行构建更改以支持他们。

绝大部分都是这样的。 .protos可能看起来像这样:

#dataobj_base.proto
message DataObj {
  extensions 100 to 199;
  optional string property1 = 1;
}

#dataobj.proto
message DataObjExtension {
  optional string ext_property1 = 1;
}  

extend DataObj {
  optional DataObjExtension generic_extension = 100;
}

....这很简单,足以支持。到目前为止,我的构建系统已经通过知道它正在编写的类型的名称来访问generic_extension对象,仅此而已。我可以导入DataObj_pb2和DataObjExtension_pb2并像这样使用它们:

def do_the_thing(type_name, property1, ext_property1):
  #type_name, in this case, is 'DataObj'
  game_module = (
    importlib.import_module('generated_protobufs.{}_pb2'.format(type_name)))
  ext_module = (
    importlib.import_module('generated_protobufs.{}Extension_pb2'.format(type_name)))
  instance_constructor = getattr(game_module, type_name)
  instance = instance_constructor()

  #instance is now an instance of DataObj
  instance.property1 = property1
  setattr(pb_instance.Extensions[ext_module.generic_extension],
          property_name, property_value)
}

这很有效,但项目团队希望从同一个基础派生多个类型。如果我需要两种与DataObj" base一起使用的不同类型,它就会中断。"我希望能够做到这一点:

extend DataObj {
  optional SomeOtherBaseObj generic_extension = 100;
}

麻烦的是,在我的python代码中,我需要知道generic_extension的类型。如果我正在编写具有我正在处理的类型的知识的代码,我会这样做:

extensions = instance.Extensions[SomeOtherBaseObj_pb2.generic_extension]

......而extensions.__class__会告诉我我需要知道的一切。

有没有办法在不首先了解它们的情况下获取扩展中任何属性的名称 - 包括(尤其)它们的类型信息?我可以通过一百万种方式重新设计.protos是一项巨大的改进,但已经有相当多的代码基础依赖于它们。添加字段会很好,我可能会出售小的修改,但是不可能进行大的结构更改。

即使我只能获得扩展程序中的属性列表,我也是金色的。我这样修理它:

extend DataObj {
  optional SomeOtherBaseObj generic_extension = 100;
  optional bool generic_extension_type__SomeOtherBaseObj = 101;
}

generic_extension_type __...将无缘无故,除了给我一个generic_extension类型的名称。但是,如果我能做到这一点,我可以将其简化为:

extend DataObj {
  optional SomeOtherBaseObj generic_extension__SomeOtherBaseObj = 100;
}

1 个答案:

答案 0 :(得分:2)

instance.ListFields()列出邮件实例上设置的所有字段,包括扩展名。列表中的每个项目都是FieldDescriptor的元组 - 其中包含完整类型信息 - 以及字段值。 (FieldDescriptor也是您用作Extensions地图的关键字的内容。)您可以这样做:

for field, value in instance.ListFields():
  assert value == instance.Extensions[field]
  print field.full_name, value

如果您要查找邮件中已有的扩展程序,您可以选择以下几种选项:

  1. DataObj._extensions_by_name是一个dict,用于将相应扩展名的完全限定的Protobuf符号名称(如my_pkg.generic_extension)映射到FieldDescriptor

  2. DataObj._extensions_by_number是一个字典映射字段编号(在您的情况下为100到199)到相应扩展名的FieldDescriptor

  3. 你当然可以迭代任何一个dict来了解所有已知的扩展。

    不幸的是,这两者在技术上都是私有的,这意味着他们可能在未来的版本中破解。目前AFAIK没有公共界面。但是,这些成员自第一个版本以来就已存在,并且存在于纯Python和C扩展支持的实现中。而且,整个"扩展"功能已在proto3中删除,所以担心这个界面消失似乎没有实际意义。

    另请注意,依靠这些"全球注册机构"它被认为是不太好的设计。更好的设计是初始化组件的代码,以传递它需要知道的所有扩展的列表。一些高级代码应该轮询程序的其他组件,询问他们需要什么扩展,构建一个详尽的列表,然后将其传入。这种方法可以更容易地知道实际使用哪些扩展而不是哪些扩展。 #34;死码"可以删除。但是,我认识到现有的代码库并不总是如此精心编写,所以实现了正确的方式"正确的方式"可能比它的价值更麻烦。