• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(('.'.join((package, enum.name))), file_desc_proto)
56      for enum_value in enum.value:
57        self._file_desc_protos_by_symbol[
58            '.'.join((package, enum_value.name))] = file_desc_proto
59    for extension in file_desc_proto.extension:
60      self._AddSymbol(('.'.join((package, extension.name))), file_desc_proto)
61    for service in file_desc_proto.service:
62      self._AddSymbol(('.'.join((package, service.name))), file_desc_proto)
63
64  def FindFileByName(self, name):
65    """Finds the file descriptor proto by file name.
66
67    Typically the file name is a relative path ending to a .proto file. The
68    proto with the given name will have to have been added to this database
69    using the Add method or else an error will be raised.
70
71    Args:
72      name: The file name to find.
73
74    Returns:
75      The file descriptor proto matching the name.
76
77    Raises:
78      KeyError if no file by the given name was added.
79    """
80
81    return self._file_desc_protos_by_file[name]
82
83  def FindFileContainingSymbol(self, symbol):
84    """Finds the file descriptor proto containing the specified symbol.
85
86    The symbol should be a fully qualified name including the file descriptor's
87    package and any containing messages. Some examples:
88
89    'some.package.name.Message'
90    'some.package.name.Message.NestedEnum'
91    'some.package.name.Message.some_field'
92
93    The file descriptor proto containing the specified symbol must be added to
94    this database using the Add method or else an error will be raised.
95
96    Args:
97      symbol: The fully qualified symbol name.
98
99    Returns:
100      The file descriptor proto containing the symbol.
101
102    Raises:
103      KeyError if no file contains the specified symbol.
104    """
105    try:
106      return self._file_desc_protos_by_symbol[symbol]
107    except KeyError:
108      # Fields, enum values, and nested extensions are not in
109      # _file_desc_protos_by_symbol. Try to find the top level
110      # descriptor. Non-existent nested symbol under a valid top level
111      # descriptor can also be found. The behavior is the same with
112      # protobuf C++.
113      top_level, _, _ = symbol.rpartition('.')
114      try:
115        return self._file_desc_protos_by_symbol[top_level]
116      except KeyError:
117        # Raise the original symbol as a KeyError for better diagnostics.
118        raise KeyError(symbol)
119
120  def FindFileContainingExtension(self, extendee_name, extension_number):
121    # TODO: implement this API.
122    return None
123
124  def FindAllExtensionNumbers(self, extendee_name):
125    # TODO: implement this API.
126    return []
127
128  def _AddSymbol(self, name, file_desc_proto):
129    if name in self._file_desc_protos_by_symbol:
130      warn_msg = ('Conflict register for file "' + file_desc_proto.name +
131                  '": ' + name +
132                  ' is already defined in file "' +
133                  self._file_desc_protos_by_symbol[name].name + '"')
134      warnings.warn(warn_msg, RuntimeWarning)
135    self._file_desc_protos_by_symbol[name] = file_desc_proto
136
137
138def _ExtractSymbols(desc_proto, package):
139  """Pulls out all the symbols from a descriptor proto.
140
141  Args:
142    desc_proto: The proto to extract symbols from.
143    package: The package containing the descriptor type.
144
145  Yields:
146    The fully qualified name found in the descriptor.
147  """
148  message_name = package + '.' + desc_proto.name if package else desc_proto.name
149  yield message_name
150  for nested_type in desc_proto.nested_type:
151    for symbol in _ExtractSymbols(nested_type, message_name):
152      yield symbol
153  for enum_type in desc_proto.enum_type:
154    yield '.'.join((message_name, enum_type.name))
155