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"""Provides a container for DescriptorProtos.""" 
    9 
    10__author__ = 'matthewtoia@google.com (Matt Toia)' 
    11 
    12import warnings 
    13 
    14 
    15class Error(Exception): 
    16  pass 
    17 
    18 
    19class DescriptorDatabaseConflictingDefinitionError(Error): 
    20  """Raised when a proto is added with the same name & different descriptor.""" 
    21 
    22 
    23class DescriptorDatabase(object): 
    24  """A container accepting FileDescriptorProtos and maps DescriptorProtos.""" 
    25 
    26  def __init__(self): 
    27    self._file_desc_protos_by_file = {} 
    28    self._file_desc_protos_by_symbol = {} 
    29 
    30  def Add(self, file_desc_proto): 
    31    """Adds the FileDescriptorProto and its types to this database. 
    32 
    33    Args: 
    34      file_desc_proto: The FileDescriptorProto to add. 
    35    Raises: 
    36      DescriptorDatabaseConflictingDefinitionError: if an attempt is made to 
    37        add a proto with the same name but different definition than an 
    38        existing proto in the database. 
    39    """ 
    40    proto_name = file_desc_proto.name 
    41    if proto_name not in self._file_desc_protos_by_file: 
    42      self._file_desc_protos_by_file[proto_name] = file_desc_proto 
    43    elif self._file_desc_protos_by_file[proto_name] != file_desc_proto: 
    44      raise DescriptorDatabaseConflictingDefinitionError( 
    45          '%s already added, but with different descriptor.' % proto_name) 
    46    else: 
    47      return 
    48 
    49    # Add all the top-level descriptors to the index. 
    50    package = file_desc_proto.package 
    51    for message in file_desc_proto.message_type: 
    52      for name in _ExtractSymbols(message, package): 
    53        self._AddSymbol(name, file_desc_proto) 
    54    for enum in file_desc_proto.enum_type: 
    55      self._AddSymbol( 
    56          ('.'.join((package, enum.name)) if package else enum.name), 
    57          file_desc_proto, 
    58      ) 
    59      for enum_value in enum.value: 
    60        self._file_desc_protos_by_symbol[ 
    61            '.'.join((package, enum_value.name)) if package else enum_value.name 
    62        ] = file_desc_proto 
    63    for extension in file_desc_proto.extension: 
    64      self._AddSymbol( 
    65          ('.'.join((package, extension.name)) if package else extension.name), 
    66          file_desc_proto, 
    67      ) 
    68    for service in file_desc_proto.service: 
    69      self._AddSymbol( 
    70          ('.'.join((package, service.name)) if package else service.name), 
    71          file_desc_proto, 
    72      ) 
    73 
    74  def FindFileByName(self, name): 
    75    """Finds the file descriptor proto by file name. 
    76 
    77    Typically the file name is a relative path ending to a .proto file. The 
    78    proto with the given name will have to have been added to this database 
    79    using the Add method or else an error will be raised. 
    80 
    81    Args: 
    82      name: The file name to find. 
    83 
    84    Returns: 
    85      The file descriptor proto matching the name. 
    86 
    87    Raises: 
    88      KeyError if no file by the given name was added. 
    89    """ 
    90 
    91    return self._file_desc_protos_by_file[name] 
    92 
    93  def FindFileContainingSymbol(self, symbol): 
    94    """Finds the file descriptor proto containing the specified symbol. 
    95 
    96    The symbol should be a fully qualified name including the file descriptor's 
    97    package and any containing messages. Some examples: 
    98 
    99    'some.package.name.Message' 
    100    'some.package.name.Message.NestedEnum' 
    101    'some.package.name.Message.some_field' 
    102 
    103    The file descriptor proto containing the specified symbol must be added to 
    104    this database using the Add method or else an error will be raised. 
    105 
    106    Args: 
    107      symbol: The fully qualified symbol name. 
    108 
    109    Returns: 
    110      The file descriptor proto containing the symbol. 
    111 
    112    Raises: 
    113      KeyError if no file contains the specified symbol. 
    114    """ 
    115    if symbol.count('.') == 1 and symbol[0] == '.': 
    116      symbol = symbol.lstrip('.') 
    117      warnings.warn( 
    118          'Please remove the leading "." when ' 
    119          'FindFileContainingSymbol, this will turn to error ' 
    120          'in 2026 Jan.', 
    121          RuntimeWarning, 
    122      ) 
    123    try: 
    124      return self._file_desc_protos_by_symbol[symbol] 
    125    except KeyError: 
    126      # Fields, enum values, and nested extensions are not in 
    127      # _file_desc_protos_by_symbol. Try to find the top level 
    128      # descriptor. Non-existent nested symbol under a valid top level 
    129      # descriptor can also be found. The behavior is the same with 
    130      # protobuf C++. 
    131      top_level, _, _ = symbol.rpartition('.') 
    132      try: 
    133        return self._file_desc_protos_by_symbol[top_level] 
    134      except KeyError: 
    135        # Raise the original symbol as a KeyError for better diagnostics. 
    136        raise KeyError(symbol) 
    137 
    138  def FindFileContainingExtension(self, extendee_name, extension_number): 
    139    # TODO: implement this API. 
    140    return None 
    141 
    142  def FindAllExtensionNumbers(self, extendee_name): 
    143    # TODO: implement this API. 
    144    return [] 
    145 
    146  def _AddSymbol(self, name, file_desc_proto): 
    147    if name in self._file_desc_protos_by_symbol: 
    148      warn_msg = ('Conflict register for file "' + file_desc_proto.name + 
    149                  '": ' + name + 
    150                  ' is already defined in file "' + 
    151                  self._file_desc_protos_by_symbol[name].name + '"') 
    152      warnings.warn(warn_msg, RuntimeWarning) 
    153    self._file_desc_protos_by_symbol[name] = file_desc_proto 
    154 
    155 
    156def _ExtractSymbols(desc_proto, package): 
    157  """Pulls out all the symbols from a descriptor proto. 
    158 
    159  Args: 
    160    desc_proto: The proto to extract symbols from. 
    161    package: The package containing the descriptor type. 
    162 
    163  Yields: 
    164    The fully qualified name found in the descriptor. 
    165  """ 
    166  message_name = package + '.' + desc_proto.name if package else desc_proto.name 
    167  yield message_name 
    168  for nested_type in desc_proto.nested_type: 
    169    for symbol in _ExtractSymbols(nested_type, message_name): 
    170      yield symbol 
    171  for enum_type in desc_proto.enum_type: 
    172    yield '.'.join((message_name, enum_type.name))