1# Protocol Buffers - Google's data interchange format 2# Copyright 2008 Google Inc. All rights reserved. 3# 4# Use of this source code is governed by a BSD-style 5# license that can be found in the LICENSE file or at 6# https://developers.google.com/open-source/licenses/bsd 7 8"""Dynamic Protobuf class creator.""" 9 10from collections import OrderedDict 11import hashlib 12import os 13 14from google.protobuf import descriptor_pb2 15from google.protobuf import descriptor 16from google.protobuf import descriptor_pool 17from google.protobuf import message_factory 18 19 20def _GetMessageFromFactory(pool, full_name): 21 """Get a proto class from the MessageFactory by name. 22 23 Args: 24 pool: a descriptor pool. 25 full_name: str, the fully qualified name of the proto type. 26 Returns: 27 A class, for the type identified by full_name. 28 Raises: 29 KeyError, if the proto is not found in the factory's descriptor pool. 30 """ 31 proto_descriptor = pool.FindMessageTypeByName(full_name) 32 proto_cls = message_factory.GetMessageClass(proto_descriptor) 33 return proto_cls 34 35 36def MakeSimpleProtoClass(fields, full_name=None, pool=None): 37 """Create a Protobuf class whose fields are basic types. 38 39 Note: this doesn't validate field names! 40 41 Args: 42 fields: dict of {name: field_type} mappings for each field in the proto. If 43 this is an OrderedDict the order will be maintained, otherwise the 44 fields will be sorted by name. 45 full_name: optional str, the fully-qualified name of the proto type. 46 pool: optional DescriptorPool instance. 47 Returns: 48 a class, the new protobuf class with a FileDescriptor. 49 """ 50 pool_instance = pool or descriptor_pool.DescriptorPool() 51 if full_name is not None: 52 try: 53 proto_cls = _GetMessageFromFactory(pool_instance, full_name) 54 return proto_cls 55 except KeyError: 56 # The factory's DescriptorPool doesn't know about this class yet. 57 pass 58 59 # Get a list of (name, field_type) tuples from the fields dict. If fields was 60 # an OrderedDict we keep the order, but otherwise we sort the field to ensure 61 # consistent ordering. 62 field_items = fields.items() 63 if not isinstance(fields, OrderedDict): 64 field_items = sorted(field_items) 65 66 # Use a consistent file name that is unlikely to conflict with any imported 67 # proto files. 68 fields_hash = hashlib.sha1() 69 for f_name, f_type in field_items: 70 fields_hash.update(f_name.encode('utf-8')) 71 fields_hash.update(str(f_type).encode('utf-8')) 72 proto_file_name = fields_hash.hexdigest() + '.proto' 73 74 # If the proto is anonymous, use the same hash to name it. 75 if full_name is None: 76 full_name = ('net.proto2.python.public.proto_builder.AnonymousProto_' + 77 fields_hash.hexdigest()) 78 try: 79 proto_cls = _GetMessageFromFactory(pool_instance, full_name) 80 return proto_cls 81 except KeyError: 82 # The factory's DescriptorPool doesn't know about this class yet. 83 pass 84 85 # This is the first time we see this proto: add a new descriptor to the pool. 86 pool_instance.Add( 87 _MakeFileDescriptorProto(proto_file_name, full_name, field_items)) 88 return _GetMessageFromFactory(pool_instance, full_name) 89 90 91def _MakeFileDescriptorProto(proto_file_name, full_name, field_items): 92 """Populate FileDescriptorProto for MessageFactory's DescriptorPool.""" 93 package, name = full_name.rsplit('.', 1) 94 file_proto = descriptor_pb2.FileDescriptorProto() 95 file_proto.name = os.path.join(package.replace('.', '/'), proto_file_name) 96 file_proto.package = package 97 desc_proto = file_proto.message_type.add() 98 desc_proto.name = name 99 for f_number, (f_name, f_type) in enumerate(field_items, 1): 100 field_proto = desc_proto.field.add() 101 field_proto.name = f_name 102 # # If the number falls in the reserved range, reassign it to the correct 103 # # number after the range. 104 if f_number >= descriptor.FieldDescriptor.FIRST_RESERVED_FIELD_NUMBER: 105 f_number += ( 106 descriptor.FieldDescriptor.LAST_RESERVED_FIELD_NUMBER - 107 descriptor.FieldDescriptor.FIRST_RESERVED_FIELD_NUMBER + 1) 108 field_proto.number = f_number 109 field_proto.label = descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL 110 field_proto.type = f_type 111 return file_proto 112