Home | History | Annotate | Download | only in internal
      1 # Protocol Buffers - Google's data interchange format
      2 # Copyright 2008 Google Inc.  All rights reserved.
      3 # https://developers.google.com/protocol-buffers/
      4 #
      5 # Redistribution and use in source and binary forms, with or without
      6 # modification, are permitted provided that the following conditions are
      7 # met:
      8 #
      9 #     * Redistributions of source code must retain the above copyright
     10 # notice, this list of conditions and the following disclaimer.
     11 #     * Redistributions in binary form must reproduce the above
     12 # copyright notice, this list of conditions and the following disclaimer
     13 # in the documentation and/or other materials provided with the
     14 # distribution.
     15 #     * Neither the name of Google Inc. nor the names of its
     16 # contributors may be used to endorse or promote products derived from
     17 # this software without specific prior written permission.
     18 #
     19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30 
     31 """Contains well known classes.
     32 
     33 This files defines well known classes which need extra maintenance including:
     34   - Any
     35   - Duration
     36   - FieldMask
     37   - Struct
     38   - Timestamp
     39 """
     40 
     41 __author__ = 'jieluo (at] google.com (Jie Luo)'
     42 
     43 from datetime import datetime
     44 from datetime import timedelta
     45 import six
     46 
     47 from google.protobuf.descriptor import FieldDescriptor
     48 
     49 _TIMESTAMPFOMAT = '%Y-%m-%dT%H:%M:%S'
     50 _NANOS_PER_SECOND = 1000000000
     51 _NANOS_PER_MILLISECOND = 1000000
     52 _NANOS_PER_MICROSECOND = 1000
     53 _MILLIS_PER_SECOND = 1000
     54 _MICROS_PER_SECOND = 1000000
     55 _SECONDS_PER_DAY = 24 * 3600
     56 
     57 
     58 class Error(Exception):
     59   """Top-level module error."""
     60 
     61 
     62 class ParseError(Error):
     63   """Thrown in case of parsing error."""
     64 
     65 
     66 class Any(object):
     67   """Class for Any Message type."""
     68 
     69   def Pack(self, msg, type_url_prefix='type.googleapis.com/'):
     70     """Packs the specified message into current Any message."""
     71     if len(type_url_prefix) < 1 or type_url_prefix[-1] != '/':
     72       self.type_url = '%s/%s' % (type_url_prefix, msg.DESCRIPTOR.full_name)
     73     else:
     74       self.type_url = '%s%s' % (type_url_prefix, msg.DESCRIPTOR.full_name)
     75     self.value = msg.SerializeToString()
     76 
     77   def Unpack(self, msg):
     78     """Unpacks the current Any message into specified message."""
     79     descriptor = msg.DESCRIPTOR
     80     if not self.Is(descriptor):
     81       return False
     82     msg.ParseFromString(self.value)
     83     return True
     84 
     85   def TypeName(self):
     86     """Returns the protobuf type name of the inner message."""
     87     # Only last part is to be used: b/25630112
     88     return self.type_url.split('/')[-1]
     89 
     90   def Is(self, descriptor):
     91     """Checks if this Any represents the given protobuf type."""
     92     return self.TypeName() == descriptor.full_name
     93 
     94 
     95 class Timestamp(object):
     96   """Class for Timestamp message type."""
     97 
     98   def ToJsonString(self):
     99     """Converts Timestamp to RFC 3339 date string format.
    100 
    101     Returns:
    102       A string converted from timestamp. The string is always Z-normalized
    103       and uses 3, 6 or 9 fractional digits as required to represent the
    104       exact time. Example of the return format: '1972-01-01T10:00:20.021Z'
    105     """
    106     nanos = self.nanos % _NANOS_PER_SECOND
    107     total_sec = self.seconds + (self.nanos - nanos) // _NANOS_PER_SECOND
    108     seconds = total_sec % _SECONDS_PER_DAY
    109     days = (total_sec - seconds) // _SECONDS_PER_DAY
    110     dt = datetime(1970, 1, 1) + timedelta(days, seconds)
    111 
    112     result = dt.isoformat()
    113     if (nanos % 1e9) == 0:
    114       # If there are 0 fractional digits, the fractional
    115       # point '.' should be omitted when serializing.
    116       return result + 'Z'
    117     if (nanos % 1e6) == 0:
    118       # Serialize 3 fractional digits.
    119       return result + '.%03dZ' % (nanos / 1e6)
    120     if (nanos % 1e3) == 0:
    121       # Serialize 6 fractional digits.
    122       return result + '.%06dZ' % (nanos / 1e3)
    123     # Serialize 9 fractional digits.
    124     return result + '.%09dZ' % nanos
    125 
    126   def FromJsonString(self, value):
    127     """Parse a RFC 3339 date string format to Timestamp.
    128 
    129     Args:
    130       value: A date string. Any fractional digits (or none) and any offset are
    131           accepted as long as they fit into nano-seconds precision.
    132           Example of accepted format: '1972-01-01T10:00:20.021-05:00'
    133 
    134     Raises:
    135       ParseError: On parsing problems.
    136     """
    137     timezone_offset = value.find('Z')
    138     if timezone_offset == -1:
    139       timezone_offset = value.find('+')
    140     if timezone_offset == -1:
    141       timezone_offset = value.rfind('-')
    142     if timezone_offset == -1:
    143       raise ParseError(
    144           'Failed to parse timestamp: missing valid timezone offset.')
    145     time_value = value[0:timezone_offset]
    146     # Parse datetime and nanos.
    147     point_position = time_value.find('.')
    148     if point_position == -1:
    149       second_value = time_value
    150       nano_value = ''
    151     else:
    152       second_value = time_value[:point_position]
    153       nano_value = time_value[point_position + 1:]
    154     date_object = datetime.strptime(second_value, _TIMESTAMPFOMAT)
    155     td = date_object - datetime(1970, 1, 1)
    156     seconds = td.seconds + td.days * _SECONDS_PER_DAY
    157     if len(nano_value) > 9:
    158       raise ParseError(
    159           'Failed to parse Timestamp: nanos {0} more than '
    160           '9 fractional digits.'.format(nano_value))
    161     if nano_value:
    162       nanos = round(float('0.' + nano_value) * 1e9)
    163     else:
    164       nanos = 0
    165     # Parse timezone offsets.
    166     if value[timezone_offset] == 'Z':
    167       if len(value) != timezone_offset + 1:
    168         raise ParseError('Failed to parse timestamp: invalid trailing'
    169                          ' data {0}.'.format(value))
    170     else:
    171       timezone = value[timezone_offset:]
    172       pos = timezone.find(':')
    173       if pos == -1:
    174         raise ParseError(
    175             'Invalid timezone offset value: {0}.'.format(timezone))
    176       if timezone[0] == '+':
    177         seconds -= (int(timezone[1:pos])*60+int(timezone[pos+1:]))*60
    178       else:
    179         seconds += (int(timezone[1:pos])*60+int(timezone[pos+1:]))*60
    180     # Set seconds and nanos
    181     self.seconds = int(seconds)
    182     self.nanos = int(nanos)
    183 
    184   def GetCurrentTime(self):
    185     """Get the current UTC into Timestamp."""
    186     self.FromDatetime(datetime.utcnow())
    187 
    188   def ToNanoseconds(self):
    189     """Converts Timestamp to nanoseconds since epoch."""
    190     return self.seconds * _NANOS_PER_SECOND + self.nanos
    191 
    192   def ToMicroseconds(self):
    193     """Converts Timestamp to microseconds since epoch."""
    194     return (self.seconds * _MICROS_PER_SECOND +
    195             self.nanos // _NANOS_PER_MICROSECOND)
    196 
    197   def ToMilliseconds(self):
    198     """Converts Timestamp to milliseconds since epoch."""
    199     return (self.seconds * _MILLIS_PER_SECOND +
    200             self.nanos // _NANOS_PER_MILLISECOND)
    201 
    202   def ToSeconds(self):
    203     """Converts Timestamp to seconds since epoch."""
    204     return self.seconds
    205 
    206   def FromNanoseconds(self, nanos):
    207     """Converts nanoseconds since epoch to Timestamp."""
    208     self.seconds = nanos // _NANOS_PER_SECOND
    209     self.nanos = nanos % _NANOS_PER_SECOND
    210 
    211   def FromMicroseconds(self, micros):
    212     """Converts microseconds since epoch to Timestamp."""
    213     self.seconds = micros // _MICROS_PER_SECOND
    214     self.nanos = (micros % _MICROS_PER_SECOND) * _NANOS_PER_MICROSECOND
    215 
    216   def FromMilliseconds(self, millis):
    217     """Converts milliseconds since epoch to Timestamp."""
    218     self.seconds = millis // _MILLIS_PER_SECOND
    219     self.nanos = (millis % _MILLIS_PER_SECOND) * _NANOS_PER_MILLISECOND
    220 
    221   def FromSeconds(self, seconds):
    222     """Converts seconds since epoch to Timestamp."""
    223     self.seconds = seconds
    224     self.nanos = 0
    225 
    226   def ToDatetime(self):
    227     """Converts Timestamp to datetime."""
    228     return datetime.utcfromtimestamp(
    229         self.seconds + self.nanos / float(_NANOS_PER_SECOND))
    230 
    231   def FromDatetime(self, dt):
    232     """Converts datetime to Timestamp."""
    233     td = dt - datetime(1970, 1, 1)
    234     self.seconds = td.seconds + td.days * _SECONDS_PER_DAY
    235     self.nanos = td.microseconds * _NANOS_PER_MICROSECOND
    236 
    237 
    238 class Duration(object):
    239   """Class for Duration message type."""
    240 
    241   def ToJsonString(self):
    242     """Converts Duration to string format.
    243 
    244     Returns:
    245       A string converted from self. The string format will contains
    246       3, 6, or 9 fractional digits depending on the precision required to
    247       represent the exact Duration value. For example: "1s", "1.010s",
    248       "1.000000100s", "-3.100s"
    249     """
    250     if self.seconds < 0 or self.nanos < 0:
    251       result = '-'
    252       seconds = - self.seconds + int((0 - self.nanos) // 1e9)
    253       nanos = (0 - self.nanos) % 1e9
    254     else:
    255       result = ''
    256       seconds = self.seconds + int(self.nanos // 1e9)
    257       nanos = self.nanos % 1e9
    258     result += '%d' % seconds
    259     if (nanos % 1e9) == 0:
    260       # If there are 0 fractional digits, the fractional
    261       # point '.' should be omitted when serializing.
    262       return result + 's'
    263     if (nanos % 1e6) == 0:
    264       # Serialize 3 fractional digits.
    265       return result + '.%03ds' % (nanos / 1e6)
    266     if (nanos % 1e3) == 0:
    267       # Serialize 6 fractional digits.
    268       return result + '.%06ds' % (nanos / 1e3)
    269     # Serialize 9 fractional digits.
    270     return result + '.%09ds' % nanos
    271 
    272   def FromJsonString(self, value):
    273     """Converts a string to Duration.
    274 
    275     Args:
    276       value: A string to be converted. The string must end with 's'. Any
    277           fractional digits (or none) are accepted as long as they fit into
    278           precision. For example: "1s", "1.01s", "1.0000001s", "-3.100s
    279 
    280     Raises:
    281       ParseError: On parsing problems.
    282     """
    283     if len(value) < 1 or value[-1] != 's':
    284       raise ParseError(
    285           'Duration must end with letter "s": {0}.'.format(value))
    286     try:
    287       pos = value.find('.')
    288       if pos == -1:
    289         self.seconds = int(value[:-1])
    290         self.nanos = 0
    291       else:
    292         self.seconds = int(value[:pos])
    293         if value[0] == '-':
    294           self.nanos = int(round(float('-0{0}'.format(value[pos: -1])) *1e9))
    295         else:
    296           self.nanos = int(round(float('0{0}'.format(value[pos: -1])) *1e9))
    297     except ValueError:
    298       raise ParseError(
    299           'Couldn\'t parse duration: {0}.'.format(value))
    300 
    301   def ToNanoseconds(self):
    302     """Converts a Duration to nanoseconds."""
    303     return self.seconds * _NANOS_PER_SECOND + self.nanos
    304 
    305   def ToMicroseconds(self):
    306     """Converts a Duration to microseconds."""
    307     micros = _RoundTowardZero(self.nanos, _NANOS_PER_MICROSECOND)
    308     return self.seconds * _MICROS_PER_SECOND + micros
    309 
    310   def ToMilliseconds(self):
    311     """Converts a Duration to milliseconds."""
    312     millis = _RoundTowardZero(self.nanos, _NANOS_PER_MILLISECOND)
    313     return self.seconds * _MILLIS_PER_SECOND + millis
    314 
    315   def ToSeconds(self):
    316     """Converts a Duration to seconds."""
    317     return self.seconds
    318 
    319   def FromNanoseconds(self, nanos):
    320     """Converts nanoseconds to Duration."""
    321     self._NormalizeDuration(nanos // _NANOS_PER_SECOND,
    322                             nanos % _NANOS_PER_SECOND)
    323 
    324   def FromMicroseconds(self, micros):
    325     """Converts microseconds to Duration."""
    326     self._NormalizeDuration(
    327         micros // _MICROS_PER_SECOND,
    328         (micros % _MICROS_PER_SECOND) * _NANOS_PER_MICROSECOND)
    329 
    330   def FromMilliseconds(self, millis):
    331     """Converts milliseconds to Duration."""
    332     self._NormalizeDuration(
    333         millis // _MILLIS_PER_SECOND,
    334         (millis % _MILLIS_PER_SECOND) * _NANOS_PER_MILLISECOND)
    335 
    336   def FromSeconds(self, seconds):
    337     """Converts seconds to Duration."""
    338     self.seconds = seconds
    339     self.nanos = 0
    340 
    341   def ToTimedelta(self):
    342     """Converts Duration to timedelta."""
    343     return timedelta(
    344         seconds=self.seconds, microseconds=_RoundTowardZero(
    345             self.nanos, _NANOS_PER_MICROSECOND))
    346 
    347   def FromTimedelta(self, td):
    348     """Convertd timedelta to Duration."""
    349     self._NormalizeDuration(td.seconds + td.days * _SECONDS_PER_DAY,
    350                             td.microseconds * _NANOS_PER_MICROSECOND)
    351 
    352   def _NormalizeDuration(self, seconds, nanos):
    353     """Set Duration by seconds and nonas."""
    354     # Force nanos to be negative if the duration is negative.
    355     if seconds < 0 and nanos > 0:
    356       seconds += 1
    357       nanos -= _NANOS_PER_SECOND
    358     self.seconds = seconds
    359     self.nanos = nanos
    360 
    361 
    362 def _RoundTowardZero(value, divider):
    363   """Truncates the remainder part after division."""
    364   # For some languanges, the sign of the remainder is implementation
    365   # dependent if any of the operands is negative. Here we enforce
    366   # "rounded toward zero" semantics. For example, for (-5) / 2 an
    367   # implementation may give -3 as the result with the remainder being
    368   # 1. This function ensures we always return -2 (closer to zero).
    369   result = value // divider
    370   remainder = value % divider
    371   if result < 0 and remainder > 0:
    372     return result + 1
    373   else:
    374     return result
    375 
    376 
    377 class FieldMask(object):
    378   """Class for FieldMask message type."""
    379 
    380   def ToJsonString(self):
    381     """Converts FieldMask to string according to proto3 JSON spec."""
    382     return ','.join(self.paths)
    383 
    384   def FromJsonString(self, value):
    385     """Converts string to FieldMask according to proto3 JSON spec."""
    386     self.Clear()
    387     for path in value.split(','):
    388       self.paths.append(path)
    389 
    390   def IsValidForDescriptor(self, message_descriptor):
    391     """Checks whether the FieldMask is valid for Message Descriptor."""
    392     for path in self.paths:
    393       if not _IsValidPath(message_descriptor, path):
    394         return False
    395     return True
    396 
    397   def AllFieldsFromDescriptor(self, message_descriptor):
    398     """Gets all direct fields of Message Descriptor to FieldMask."""
    399     self.Clear()
    400     for field in message_descriptor.fields:
    401       self.paths.append(field.name)
    402 
    403   def CanonicalFormFromMask(self, mask):
    404     """Converts a FieldMask to the canonical form.
    405 
    406     Removes paths that are covered by another path. For example,
    407     "foo.bar" is covered by "foo" and will be removed if "foo"
    408     is also in the FieldMask. Then sorts all paths in alphabetical order.
    409 
    410     Args:
    411       mask: The original FieldMask to be converted.
    412     """
    413     tree = _FieldMaskTree(mask)
    414     tree.ToFieldMask(self)
    415 
    416   def Union(self, mask1, mask2):
    417     """Merges mask1 and mask2 into this FieldMask."""
    418     _CheckFieldMaskMessage(mask1)
    419     _CheckFieldMaskMessage(mask2)
    420     tree = _FieldMaskTree(mask1)
    421     tree.MergeFromFieldMask(mask2)
    422     tree.ToFieldMask(self)
    423 
    424   def Intersect(self, mask1, mask2):
    425     """Intersects mask1 and mask2 into this FieldMask."""
    426     _CheckFieldMaskMessage(mask1)
    427     _CheckFieldMaskMessage(mask2)
    428     tree = _FieldMaskTree(mask1)
    429     intersection = _FieldMaskTree()
    430     for path in mask2.paths:
    431       tree.IntersectPath(path, intersection)
    432     intersection.ToFieldMask(self)
    433 
    434   def MergeMessage(
    435       self, source, destination,
    436       replace_message_field=False, replace_repeated_field=False):
    437     """Merges fields specified in FieldMask from source to destination.
    438 
    439     Args:
    440       source: Source message.
    441       destination: The destination message to be merged into.
    442       replace_message_field: Replace message field if True. Merge message
    443           field if False.
    444       replace_repeated_field: Replace repeated field if True. Append
    445           elements of repeated field if False.
    446     """
    447     tree = _FieldMaskTree(self)
    448     tree.MergeMessage(
    449         source, destination, replace_message_field, replace_repeated_field)
    450 
    451 
    452 def _IsValidPath(message_descriptor, path):
    453   """Checks whether the path is valid for Message Descriptor."""
    454   parts = path.split('.')
    455   last = parts.pop()
    456   for name in parts:
    457     field = message_descriptor.fields_by_name[name]
    458     if (field is None or
    459         field.label == FieldDescriptor.LABEL_REPEATED or
    460         field.type != FieldDescriptor.TYPE_MESSAGE):
    461       return False
    462     message_descriptor = field.message_type
    463   return last in message_descriptor.fields_by_name
    464 
    465 
    466 def _CheckFieldMaskMessage(message):
    467   """Raises ValueError if message is not a FieldMask."""
    468   message_descriptor = message.DESCRIPTOR
    469   if (message_descriptor.name != 'FieldMask' or
    470       message_descriptor.file.name != 'google/protobuf/field_mask.proto'):
    471     raise ValueError('Message {0} is not a FieldMask.'.format(
    472         message_descriptor.full_name))
    473 
    474 
    475 class _FieldMaskTree(object):
    476   """Represents a FieldMask in a tree structure.
    477 
    478   For example, given a FieldMask "foo.bar,foo.baz,bar.baz",
    479   the FieldMaskTree will be:
    480       [_root] -+- foo -+- bar
    481             |       |
    482             |       +- baz
    483             |
    484             +- bar --- baz
    485   In the tree, each leaf node represents a field path.
    486   """
    487 
    488   def __init__(self, field_mask=None):
    489     """Initializes the tree by FieldMask."""
    490     self._root = {}
    491     if field_mask:
    492       self.MergeFromFieldMask(field_mask)
    493 
    494   def MergeFromFieldMask(self, field_mask):
    495     """Merges a FieldMask to the tree."""
    496     for path in field_mask.paths:
    497       self.AddPath(path)
    498 
    499   def AddPath(self, path):
    500     """Adds a field path into the tree.
    501 
    502     If the field path to add is a sub-path of an existing field path
    503     in the tree (i.e., a leaf node), it means the tree already matches
    504     the given path so nothing will be added to the tree. If the path
    505     matches an existing non-leaf node in the tree, that non-leaf node
    506     will be turned into a leaf node with all its children removed because
    507     the path matches all the node's children. Otherwise, a new path will
    508     be added.
    509 
    510     Args:
    511       path: The field path to add.
    512     """
    513     node = self._root
    514     for name in path.split('.'):
    515       if name not in node:
    516         node[name] = {}
    517       elif not node[name]:
    518         # Pre-existing empty node implies we already have this entire tree.
    519         return
    520       node = node[name]
    521     # Remove any sub-trees we might have had.
    522     node.clear()
    523 
    524   def ToFieldMask(self, field_mask):
    525     """Converts the tree to a FieldMask."""
    526     field_mask.Clear()
    527     _AddFieldPaths(self._root, '', field_mask)
    528 
    529   def IntersectPath(self, path, intersection):
    530     """Calculates the intersection part of a field path with this tree.
    531 
    532     Args:
    533       path: The field path to calculates.
    534       intersection: The out tree to record the intersection part.
    535     """
    536     node = self._root
    537     for name in path.split('.'):
    538       if name not in node:
    539         return
    540       elif not node[name]:
    541         intersection.AddPath(path)
    542         return
    543       node = node[name]
    544     intersection.AddLeafNodes(path, node)
    545 
    546   def AddLeafNodes(self, prefix, node):
    547     """Adds leaf nodes begin with prefix to this tree."""
    548     if not node:
    549       self.AddPath(prefix)
    550     for name in node:
    551       child_path = prefix + '.' + name
    552       self.AddLeafNodes(child_path, node[name])
    553 
    554   def MergeMessage(
    555       self, source, destination,
    556       replace_message, replace_repeated):
    557     """Merge all fields specified by this tree from source to destination."""
    558     _MergeMessage(
    559         self._root, source, destination, replace_message, replace_repeated)
    560 
    561 
    562 def _StrConvert(value):
    563   """Converts value to str if it is not."""
    564   # This file is imported by c extension and some methods like ClearField
    565   # requires string for the field name. py2/py3 has different text
    566   # type and may use unicode.
    567   if not isinstance(value, str):
    568     return value.encode('utf-8')
    569   return value
    570 
    571 
    572 def _MergeMessage(
    573     node, source, destination, replace_message, replace_repeated):
    574   """Merge all fields specified by a sub-tree from source to destination."""
    575   source_descriptor = source.DESCRIPTOR
    576   for name in node:
    577     child = node[name]
    578     field = source_descriptor.fields_by_name[name]
    579     if field is None:
    580       raise ValueError('Error: Can\'t find field {0} in message {1}.'.format(
    581           name, source_descriptor.full_name))
    582     if child:
    583       # Sub-paths are only allowed for singular message fields.
    584       if (field.label == FieldDescriptor.LABEL_REPEATED or
    585           field.cpp_type != FieldDescriptor.CPPTYPE_MESSAGE):
    586         raise ValueError('Error: Field {0} in message {1} is not a singular '
    587                          'message field and cannot have sub-fields.'.format(
    588                              name, source_descriptor.full_name))
    589       _MergeMessage(
    590           child, getattr(source, name), getattr(destination, name),
    591           replace_message, replace_repeated)
    592       continue
    593     if field.label == FieldDescriptor.LABEL_REPEATED:
    594       if replace_repeated:
    595         destination.ClearField(_StrConvert(name))
    596       repeated_source = getattr(source, name)
    597       repeated_destination = getattr(destination, name)
    598       if field.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE:
    599         for item in repeated_source:
    600           repeated_destination.add().MergeFrom(item)
    601       else:
    602         repeated_destination.extend(repeated_source)
    603     else:
    604       if field.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE:
    605         if replace_message:
    606           destination.ClearField(_StrConvert(name))
    607         if source.HasField(name):
    608           getattr(destination, name).MergeFrom(getattr(source, name))
    609       else:
    610         setattr(destination, name, getattr(source, name))
    611 
    612 
    613 def _AddFieldPaths(node, prefix, field_mask):
    614   """Adds the field paths descended from node to field_mask."""
    615   if not node:
    616     field_mask.paths.append(prefix)
    617     return
    618   for name in sorted(node):
    619     if prefix:
    620       child_path = prefix + '.' + name
    621     else:
    622       child_path = name
    623     _AddFieldPaths(node[name], child_path, field_mask)
    624 
    625 
    626 _INT_OR_FLOAT = six.integer_types + (float,)
    627 
    628 
    629 def _SetStructValue(struct_value, value):
    630   if value is None:
    631     struct_value.null_value = 0
    632   elif isinstance(value, bool):
    633     # Note: this check must come before the number check because in Python
    634     # True and False are also considered numbers.
    635     struct_value.bool_value = value
    636   elif isinstance(value, six.string_types):
    637     struct_value.string_value = value
    638   elif isinstance(value, _INT_OR_FLOAT):
    639     struct_value.number_value = value
    640   else:
    641     raise ValueError('Unexpected type')
    642 
    643 
    644 def _GetStructValue(struct_value):
    645   which = struct_value.WhichOneof('kind')
    646   if which == 'struct_value':
    647     return struct_value.struct_value
    648   elif which == 'null_value':
    649     return None
    650   elif which == 'number_value':
    651     return struct_value.number_value
    652   elif which == 'string_value':
    653     return struct_value.string_value
    654   elif which == 'bool_value':
    655     return struct_value.bool_value
    656   elif which == 'list_value':
    657     return struct_value.list_value
    658   elif which is None:
    659     raise ValueError('Value not set')
    660 
    661 
    662 class Struct(object):
    663   """Class for Struct message type."""
    664 
    665   __slots__ = []
    666 
    667   def __getitem__(self, key):
    668     return _GetStructValue(self.fields[key])
    669 
    670   def __setitem__(self, key, value):
    671     _SetStructValue(self.fields[key], value)
    672 
    673   def get_or_create_list(self, key):
    674     """Returns a list for this key, creating if it didn't exist already."""
    675     return self.fields[key].list_value
    676 
    677   def get_or_create_struct(self, key):
    678     """Returns a struct for this key, creating if it didn't exist already."""
    679     return self.fields[key].struct_value
    680 
    681   # TODO(haberman): allow constructing/merging from dict.
    682 
    683 
    684 class ListValue(object):
    685   """Class for ListValue message type."""
    686 
    687   def __len__(self):
    688     return len(self.values)
    689 
    690   def append(self, value):
    691     _SetStructValue(self.values.add(), value)
    692 
    693   def extend(self, elem_seq):
    694     for value in elem_seq:
    695       self.append(value)
    696 
    697   def __getitem__(self, index):
    698     """Retrieves item by the specified index."""
    699     return _GetStructValue(self.values.__getitem__(index))
    700 
    701   def __setitem__(self, index, value):
    702     _SetStructValue(self.values.__getitem__(index), value)
    703 
    704   def items(self):
    705     for i in range(len(self)):
    706       yield self[i]
    707 
    708   def add_struct(self):
    709     """Appends and returns a struct value as the next value in the list."""
    710     return self.values.add().struct_value
    711 
    712   def add_list(self):
    713     """Appends and returns a list value as the next value in the list."""
    714     return self.values.add().list_value
    715 
    716 
    717 WKTBASES = {
    718     'google.protobuf.Any': Any,
    719     'google.protobuf.Duration': Duration,
    720     'google.protobuf.FieldMask': FieldMask,
    721     'google.protobuf.ListValue': ListValue,
    722     'google.protobuf.Struct': Struct,
    723     'google.protobuf.Timestamp': Timestamp,
    724 }
    725