• 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"""Contains well known classes.
9
10This files defines well known classes which need extra maintenance including:
11  - Any
12  - Duration
13  - FieldMask
14  - Struct
15  - Timestamp
16"""
17
18__author__ = 'jieluo@google.com (Jie Luo)'
19
20import calendar
21import collections.abc
22import datetime
23import warnings
24from google.protobuf.internal import field_mask
25from typing import Union
26
27FieldMask = field_mask.FieldMask
28
29_TIMESTAMPFOMAT = '%Y-%m-%dT%H:%M:%S'
30_NANOS_PER_SECOND = 1000000000
31_NANOS_PER_MILLISECOND = 1000000
32_NANOS_PER_MICROSECOND = 1000
33_MILLIS_PER_SECOND = 1000
34_MICROS_PER_SECOND = 1000000
35_SECONDS_PER_DAY = 24 * 3600
36_DURATION_SECONDS_MAX = 315576000000
37_TIMESTAMP_SECONDS_MIN = -62135596800
38_TIMESTAMP_SECONDS_MAX = 253402300799
39
40_EPOCH_DATETIME_NAIVE = datetime.datetime(1970, 1, 1, tzinfo=None)
41_EPOCH_DATETIME_AWARE = _EPOCH_DATETIME_NAIVE.replace(
42    tzinfo=datetime.timezone.utc
43)
44
45
46class Any(object):
47  """Class for Any Message type."""
48
49  __slots__ = ()
50
51  def Pack(self, msg, type_url_prefix='type.googleapis.com/',
52           deterministic=None):
53    """Packs the specified message into current Any message."""
54    if len(type_url_prefix) < 1 or type_url_prefix[-1] != '/':
55      self.type_url = '%s/%s' % (type_url_prefix, msg.DESCRIPTOR.full_name)
56    else:
57      self.type_url = '%s%s' % (type_url_prefix, msg.DESCRIPTOR.full_name)
58    self.value = msg.SerializeToString(deterministic=deterministic)
59
60  def Unpack(self, msg):
61    """Unpacks the current Any message into specified message."""
62    descriptor = msg.DESCRIPTOR
63    if not self.Is(descriptor):
64      return False
65    msg.ParseFromString(self.value)
66    return True
67
68  def TypeName(self):
69    """Returns the protobuf type name of the inner message."""
70    # Only last part is to be used: b/25630112
71    return self.type_url.split('/')[-1]
72
73  def Is(self, descriptor):
74    """Checks if this Any represents the given protobuf type."""
75    return '/' in self.type_url and self.TypeName() == descriptor.full_name
76
77
78class Timestamp(object):
79  """Class for Timestamp message type."""
80
81  __slots__ = ()
82
83  def ToJsonString(self):
84    """Converts Timestamp to RFC 3339 date string format.
85
86    Returns:
87      A string converted from timestamp. The string is always Z-normalized
88      and uses 3, 6 or 9 fractional digits as required to represent the
89      exact time. Example of the return format: '1972-01-01T10:00:20.021Z'
90    """
91    _CheckTimestampValid(self.seconds, self.nanos)
92    nanos = self.nanos
93    seconds = self.seconds % _SECONDS_PER_DAY
94    days = (self.seconds - seconds) // _SECONDS_PER_DAY
95    dt = datetime.datetime(1970, 1, 1) + datetime.timedelta(days, seconds)
96
97    result = dt.isoformat()
98    if (nanos % 1e9) == 0:
99      # If there are 0 fractional digits, the fractional
100      # point '.' should be omitted when serializing.
101      return result + 'Z'
102    if (nanos % 1e6) == 0:
103      # Serialize 3 fractional digits.
104      return result + '.%03dZ' % (nanos / 1e6)
105    if (nanos % 1e3) == 0:
106      # Serialize 6 fractional digits.
107      return result + '.%06dZ' % (nanos / 1e3)
108    # Serialize 9 fractional digits.
109    return result + '.%09dZ' % nanos
110
111  def FromJsonString(self, value):
112    """Parse a RFC 3339 date string format to Timestamp.
113
114    Args:
115      value: A date string. Any fractional digits (or none) and any offset are
116          accepted as long as they fit into nano-seconds precision.
117          Example of accepted format: '1972-01-01T10:00:20.021-05:00'
118
119    Raises:
120      ValueError: On parsing problems.
121    """
122    if not isinstance(value, str):
123      raise ValueError('Timestamp JSON value not a string: {!r}'.format(value))
124    timezone_offset = value.find('Z')
125    if timezone_offset == -1:
126      timezone_offset = value.find('+')
127    if timezone_offset == -1:
128      timezone_offset = value.rfind('-')
129    if timezone_offset == -1:
130      raise ValueError(
131          'Failed to parse timestamp: missing valid timezone offset.')
132    time_value = value[0:timezone_offset]
133    # Parse datetime and nanos.
134    point_position = time_value.find('.')
135    if point_position == -1:
136      second_value = time_value
137      nano_value = ''
138    else:
139      second_value = time_value[:point_position]
140      nano_value = time_value[point_position + 1:]
141    if 't' in second_value:
142      raise ValueError(
143          'time data \'{0}\' does not match format \'%Y-%m-%dT%H:%M:%S\', '
144          'lowercase \'t\' is not accepted'.format(second_value))
145    date_object = datetime.datetime.strptime(second_value, _TIMESTAMPFOMAT)
146    td = date_object - datetime.datetime(1970, 1, 1)
147    seconds = td.seconds + td.days * _SECONDS_PER_DAY
148    if len(nano_value) > 9:
149      raise ValueError(
150          'Failed to parse Timestamp: nanos {0} more than '
151          '9 fractional digits.'.format(nano_value))
152    if nano_value:
153      nanos = round(float('0.' + nano_value) * 1e9)
154    else:
155      nanos = 0
156    # Parse timezone offsets.
157    if value[timezone_offset] == 'Z':
158      if len(value) != timezone_offset + 1:
159        raise ValueError('Failed to parse timestamp: invalid trailing'
160                         ' data {0}.'.format(value))
161    else:
162      timezone = value[timezone_offset:]
163      pos = timezone.find(':')
164      if pos == -1:
165        raise ValueError(
166            'Invalid timezone offset value: {0}.'.format(timezone))
167      if timezone[0] == '+':
168        seconds -= (int(timezone[1:pos])*60+int(timezone[pos+1:]))*60
169      else:
170        seconds += (int(timezone[1:pos])*60+int(timezone[pos+1:]))*60
171    # Set seconds and nanos
172    _CheckTimestampValid(seconds, nanos)
173    self.seconds = int(seconds)
174    self.nanos = int(nanos)
175
176  def GetCurrentTime(self):
177    """Get the current UTC into Timestamp."""
178    self.FromDatetime(datetime.datetime.utcnow())
179
180  def ToNanoseconds(self):
181    """Converts Timestamp to nanoseconds since epoch."""
182    _CheckTimestampValid(self.seconds, self.nanos)
183    return self.seconds * _NANOS_PER_SECOND + self.nanos
184
185  def ToMicroseconds(self):
186    """Converts Timestamp to microseconds since epoch."""
187    _CheckTimestampValid(self.seconds, self.nanos)
188    return (self.seconds * _MICROS_PER_SECOND +
189            self.nanos // _NANOS_PER_MICROSECOND)
190
191  def ToMilliseconds(self):
192    """Converts Timestamp to milliseconds since epoch."""
193    _CheckTimestampValid(self.seconds, self.nanos)
194    return (self.seconds * _MILLIS_PER_SECOND +
195            self.nanos // _NANOS_PER_MILLISECOND)
196
197  def ToSeconds(self):
198    """Converts Timestamp to seconds since epoch."""
199    _CheckTimestampValid(self.seconds, self.nanos)
200    return self.seconds
201
202  def FromNanoseconds(self, nanos):
203    """Converts nanoseconds since epoch to Timestamp."""
204    seconds = nanos // _NANOS_PER_SECOND
205    nanos = nanos % _NANOS_PER_SECOND
206    _CheckTimestampValid(seconds, nanos)
207    self.seconds = seconds
208    self.nanos = nanos
209
210  def FromMicroseconds(self, micros):
211    """Converts microseconds since epoch to Timestamp."""
212    seconds = micros // _MICROS_PER_SECOND
213    nanos = (micros % _MICROS_PER_SECOND) * _NANOS_PER_MICROSECOND
214    _CheckTimestampValid(seconds, nanos)
215    self.seconds = seconds
216    self.nanos = nanos
217
218  def FromMilliseconds(self, millis):
219    """Converts milliseconds since epoch to Timestamp."""
220    seconds = millis // _MILLIS_PER_SECOND
221    nanos = (millis % _MILLIS_PER_SECOND) * _NANOS_PER_MILLISECOND
222    _CheckTimestampValid(seconds, nanos)
223    self.seconds = seconds
224    self.nanos = nanos
225
226  def FromSeconds(self, seconds):
227    """Converts seconds since epoch to Timestamp."""
228    _CheckTimestampValid(seconds, 0)
229    self.seconds = seconds
230    self.nanos = 0
231
232  def ToDatetime(self, tzinfo=None):
233    """Converts Timestamp to a datetime.
234
235    Args:
236      tzinfo: A datetime.tzinfo subclass; defaults to None.
237
238    Returns:
239      If tzinfo is None, returns a timezone-naive UTC datetime (with no timezone
240      information, i.e. not aware that it's UTC).
241
242      Otherwise, returns a timezone-aware datetime in the input timezone.
243    """
244    # Using datetime.fromtimestamp for this would avoid constructing an extra
245    # timedelta object and possibly an extra datetime. Unfortunately, that has
246    # the disadvantage of not handling the full precision (on all platforms, see
247    # https://github.com/python/cpython/issues/109849) or full range (on some
248    # platforms, see https://github.com/python/cpython/issues/110042) of
249    # datetime.
250    _CheckTimestampValid(self.seconds, self.nanos)
251    delta = datetime.timedelta(
252        seconds=self.seconds,
253        microseconds=_RoundTowardZero(self.nanos, _NANOS_PER_MICROSECOND),
254    )
255    if tzinfo is None:
256      return _EPOCH_DATETIME_NAIVE + delta
257    else:
258      # Note the tz conversion has to come after the timedelta arithmetic.
259      return (_EPOCH_DATETIME_AWARE + delta).astimezone(tzinfo)
260
261  def FromDatetime(self, dt):
262    """Converts datetime to Timestamp.
263
264    Args:
265      dt: A datetime. If it's timezone-naive, it's assumed to be in UTC.
266    """
267    # Using this guide: http://wiki.python.org/moin/WorkingWithTime
268    # And this conversion guide: http://docs.python.org/library/time.html
269
270    # Turn the date parameter into a tuple (struct_time) that can then be
271    # manipulated into a long value of seconds.  During the conversion from
272    # struct_time to long, the source date in UTC, and so it follows that the
273    # correct transformation is calendar.timegm()
274    try:
275      seconds = calendar.timegm(dt.utctimetuple())
276      nanos = dt.microsecond * _NANOS_PER_MICROSECOND
277    except AttributeError as e:
278      raise AttributeError(
279          'Fail to convert to Timestamp. Expected a datetime like '
280          'object got {0} : {1}'.format(type(dt).__name__, e)
281      ) from e
282    _CheckTimestampValid(seconds, nanos)
283    self.seconds = seconds
284    self.nanos = nanos
285
286  def _internal_assign(self, dt):
287    self.FromDatetime(dt)
288
289  def __add__(self, value) -> datetime.datetime:
290    if isinstance(value, Duration):
291      return self.ToDatetime() + value.ToTimedelta()
292    return self.ToDatetime() + value
293
294  __radd__ = __add__
295
296  def __sub__(self, value) -> Union[datetime.datetime, datetime.timedelta]:
297    if isinstance(value, Timestamp):
298      return self.ToDatetime() - value.ToDatetime()
299    elif isinstance(value, Duration):
300      return self.ToDatetime() - value.ToTimedelta()
301    return self.ToDatetime() - value
302
303  def __rsub__(self, dt) -> datetime.timedelta:
304    return dt - self.ToDatetime()
305
306
307def _CheckTimestampValid(seconds, nanos):
308  if seconds < _TIMESTAMP_SECONDS_MIN or seconds > _TIMESTAMP_SECONDS_MAX:
309    raise ValueError(
310        'Timestamp is not valid: Seconds {0} must be in range '
311        '[-62135596800, 253402300799].'.format(seconds))
312  if nanos < 0 or nanos >= _NANOS_PER_SECOND:
313    raise ValueError(
314        'Timestamp is not valid: Nanos {} must be in a range '
315        '[0, 999999].'.format(nanos))
316
317
318class Duration(object):
319  """Class for Duration message type."""
320
321  __slots__ = ()
322
323  def ToJsonString(self):
324    """Converts Duration to string format.
325
326    Returns:
327      A string converted from self. The string format will contains
328      3, 6, or 9 fractional digits depending on the precision required to
329      represent the exact Duration value. For example: "1s", "1.010s",
330      "1.000000100s", "-3.100s"
331    """
332    _CheckDurationValid(self.seconds, self.nanos)
333    if self.seconds < 0 or self.nanos < 0:
334      result = '-'
335      seconds = - self.seconds + int((0 - self.nanos) // 1e9)
336      nanos = (0 - self.nanos) % 1e9
337    else:
338      result = ''
339      seconds = self.seconds + int(self.nanos // 1e9)
340      nanos = self.nanos % 1e9
341    result += '%d' % seconds
342    if (nanos % 1e9) == 0:
343      # If there are 0 fractional digits, the fractional
344      # point '.' should be omitted when serializing.
345      return result + 's'
346    if (nanos % 1e6) == 0:
347      # Serialize 3 fractional digits.
348      return result + '.%03ds' % (nanos / 1e6)
349    if (nanos % 1e3) == 0:
350      # Serialize 6 fractional digits.
351      return result + '.%06ds' % (nanos / 1e3)
352    # Serialize 9 fractional digits.
353    return result + '.%09ds' % nanos
354
355  def FromJsonString(self, value):
356    """Converts a string to Duration.
357
358    Args:
359      value: A string to be converted. The string must end with 's'. Any
360          fractional digits (or none) are accepted as long as they fit into
361          precision. For example: "1s", "1.01s", "1.0000001s", "-3.100s
362
363    Raises:
364      ValueError: On parsing problems.
365    """
366    if not isinstance(value, str):
367      raise ValueError('Duration JSON value not a string: {!r}'.format(value))
368    if len(value) < 1 or value[-1] != 's':
369      raise ValueError(
370          'Duration must end with letter "s": {0}.'.format(value))
371    try:
372      pos = value.find('.')
373      if pos == -1:
374        seconds = int(value[:-1])
375        nanos = 0
376      else:
377        seconds = int(value[:pos])
378        if value[0] == '-':
379          nanos = int(round(float('-0{0}'.format(value[pos: -1])) *1e9))
380        else:
381          nanos = int(round(float('0{0}'.format(value[pos: -1])) *1e9))
382      _CheckDurationValid(seconds, nanos)
383      self.seconds = seconds
384      self.nanos = nanos
385    except ValueError as e:
386      raise ValueError(
387          'Couldn\'t parse duration: {0} : {1}.'.format(value, e))
388
389  def ToNanoseconds(self):
390    """Converts a Duration to nanoseconds."""
391    return self.seconds * _NANOS_PER_SECOND + self.nanos
392
393  def ToMicroseconds(self):
394    """Converts a Duration to microseconds."""
395    micros = _RoundTowardZero(self.nanos, _NANOS_PER_MICROSECOND)
396    return self.seconds * _MICROS_PER_SECOND + micros
397
398  def ToMilliseconds(self):
399    """Converts a Duration to milliseconds."""
400    millis = _RoundTowardZero(self.nanos, _NANOS_PER_MILLISECOND)
401    return self.seconds * _MILLIS_PER_SECOND + millis
402
403  def ToSeconds(self):
404    """Converts a Duration to seconds."""
405    return self.seconds
406
407  def FromNanoseconds(self, nanos):
408    """Converts nanoseconds to Duration."""
409    self._NormalizeDuration(nanos // _NANOS_PER_SECOND,
410                            nanos % _NANOS_PER_SECOND)
411
412  def FromMicroseconds(self, micros):
413    """Converts microseconds to Duration."""
414    self._NormalizeDuration(
415        micros // _MICROS_PER_SECOND,
416        (micros % _MICROS_PER_SECOND) * _NANOS_PER_MICROSECOND)
417
418  def FromMilliseconds(self, millis):
419    """Converts milliseconds to Duration."""
420    self._NormalizeDuration(
421        millis // _MILLIS_PER_SECOND,
422        (millis % _MILLIS_PER_SECOND) * _NANOS_PER_MILLISECOND)
423
424  def FromSeconds(self, seconds):
425    """Converts seconds to Duration."""
426    self.seconds = seconds
427    self.nanos = 0
428
429  def ToTimedelta(self) -> datetime.timedelta:
430    """Converts Duration to timedelta."""
431    return datetime.timedelta(
432        seconds=self.seconds, microseconds=_RoundTowardZero(
433            self.nanos, _NANOS_PER_MICROSECOND))
434
435  def FromTimedelta(self, td):
436    """Converts timedelta to Duration."""
437    try:
438      self._NormalizeDuration(
439          td.seconds + td.days * _SECONDS_PER_DAY,
440          td.microseconds * _NANOS_PER_MICROSECOND,
441      )
442    except AttributeError as e:
443      raise AttributeError(
444          'Fail to convert to Duration. Expected a timedelta like '
445          'object got {0}: {1}'.format(type(td).__name__, e)
446      ) from e
447
448  def _internal_assign(self, td):
449    self.FromTimedelta(td)
450
451  def _NormalizeDuration(self, seconds, nanos):
452    """Set Duration by seconds and nanos."""
453    # Force nanos to be negative if the duration is negative.
454    if seconds < 0 and nanos > 0:
455      seconds += 1
456      nanos -= _NANOS_PER_SECOND
457    self.seconds = seconds
458    self.nanos = nanos
459
460  def __add__(self, value) -> Union[datetime.datetime, datetime.timedelta]:
461    if isinstance(value, Timestamp):
462      return self.ToTimedelta() + value.ToDatetime()
463    return self.ToTimedelta() + value
464
465  __radd__ = __add__
466
467  def __rsub__(self, dt) -> Union[datetime.datetime, datetime.timedelta]:
468    return dt - self.ToTimedelta()
469
470
471def _CheckDurationValid(seconds, nanos):
472  if seconds < -_DURATION_SECONDS_MAX or seconds > _DURATION_SECONDS_MAX:
473    raise ValueError(
474        'Duration is not valid: Seconds {0} must be in range '
475        '[-315576000000, 315576000000].'.format(seconds))
476  if nanos <= -_NANOS_PER_SECOND or nanos >= _NANOS_PER_SECOND:
477    raise ValueError(
478        'Duration is not valid: Nanos {0} must be in range '
479        '[-999999999, 999999999].'.format(nanos))
480  if (nanos < 0 and seconds > 0) or (nanos > 0 and seconds < 0):
481    raise ValueError(
482        'Duration is not valid: Sign mismatch.')
483
484
485def _RoundTowardZero(value, divider):
486  """Truncates the remainder part after division."""
487  # For some languages, the sign of the remainder is implementation
488  # dependent if any of the operands is negative. Here we enforce
489  # "rounded toward zero" semantics. For example, for (-5) / 2 an
490  # implementation may give -3 as the result with the remainder being
491  # 1. This function ensures we always return -2 (closer to zero).
492  result = value // divider
493  remainder = value % divider
494  if result < 0 and remainder > 0:
495    return result + 1
496  else:
497    return result
498
499
500def _SetStructValue(struct_value, value):
501  if value is None:
502    struct_value.null_value = 0
503  elif isinstance(value, bool):
504    # Note: this check must come before the number check because in Python
505    # True and False are also considered numbers.
506    struct_value.bool_value = value
507  elif isinstance(value, str):
508    struct_value.string_value = value
509  elif isinstance(value, (int, float)):
510    struct_value.number_value = value
511  elif isinstance(value, (dict, Struct)):
512    struct_value.struct_value.Clear()
513    struct_value.struct_value.update(value)
514  elif isinstance(value, (list, tuple, ListValue)):
515    struct_value.list_value.Clear()
516    struct_value.list_value.extend(value)
517  else:
518    raise ValueError('Unexpected type')
519
520
521def _GetStructValue(struct_value):
522  which = struct_value.WhichOneof('kind')
523  if which == 'struct_value':
524    return struct_value.struct_value
525  elif which == 'null_value':
526    return None
527  elif which == 'number_value':
528    return struct_value.number_value
529  elif which == 'string_value':
530    return struct_value.string_value
531  elif which == 'bool_value':
532    return struct_value.bool_value
533  elif which == 'list_value':
534    return struct_value.list_value
535  elif which is None:
536    raise ValueError('Value not set')
537
538
539class Struct(object):
540  """Class for Struct message type."""
541
542  __slots__ = ()
543
544  def __getitem__(self, key):
545    return _GetStructValue(self.fields[key])
546
547  def __setitem__(self, key, value):
548    _SetStructValue(self.fields[key], value)
549
550  def __delitem__(self, key):
551    del self.fields[key]
552
553  def __len__(self):
554    return len(self.fields)
555
556  def __iter__(self):
557    return iter(self.fields)
558
559  def _internal_assign(self, dictionary):
560    self.Clear()
561    self.update(dictionary)
562
563  def _internal_compare(self, other):
564    size = len(self)
565    if size != len(other):
566      return False
567    for key, value in self.items():
568      if key not in other:
569        return False
570      if isinstance(other[key], (dict, list)):
571        if not value._internal_compare(other[key]):
572          return False
573      elif value != other[key]:
574        return False
575    return True
576
577  def keys(self):  # pylint: disable=invalid-name
578    return self.fields.keys()
579
580  def values(self):  # pylint: disable=invalid-name
581    return [self[key] for key in self]
582
583  def items(self):  # pylint: disable=invalid-name
584    return [(key, self[key]) for key in self]
585
586  def get_or_create_list(self, key):
587    """Returns a list for this key, creating if it didn't exist already."""
588    if not self.fields[key].HasField('list_value'):
589      # Clear will mark list_value modified which will indeed create a list.
590      self.fields[key].list_value.Clear()
591    return self.fields[key].list_value
592
593  def get_or_create_struct(self, key):
594    """Returns a struct for this key, creating if it didn't exist already."""
595    if not self.fields[key].HasField('struct_value'):
596      # Clear will mark struct_value modified which will indeed create a struct.
597      self.fields[key].struct_value.Clear()
598    return self.fields[key].struct_value
599
600  def update(self, dictionary):  # pylint: disable=invalid-name
601    for key, value in dictionary.items():
602      _SetStructValue(self.fields[key], value)
603
604collections.abc.MutableMapping.register(Struct)
605
606
607class ListValue(object):
608  """Class for ListValue message type."""
609
610  __slots__ = ()
611
612  def __len__(self):
613    return len(self.values)
614
615  def append(self, value):
616    _SetStructValue(self.values.add(), value)
617
618  def extend(self, elem_seq):
619    for value in elem_seq:
620      self.append(value)
621
622  def __getitem__(self, index):
623    """Retrieves item by the specified index."""
624    return _GetStructValue(self.values.__getitem__(index))
625
626  def __setitem__(self, index, value):
627    _SetStructValue(self.values.__getitem__(index), value)
628
629  def __delitem__(self, key):
630    del self.values[key]
631
632  def _internal_assign(self, elem_seq):
633    self.Clear()
634    self.extend(elem_seq)
635
636  def _internal_compare(self, other):
637    size = len(self)
638    if size != len(other):
639      return False
640    for i in range(size):
641      if isinstance(other[i], (dict, list)):
642        if not self[i]._internal_compare(other[i]):
643          return False
644      elif self[i] != other[i]:
645        return False
646    return True
647
648  def items(self):
649    for i in range(len(self)):
650      yield self[i]
651
652  def add_struct(self):
653    """Appends and returns a struct value as the next value in the list."""
654    struct_value = self.values.add().struct_value
655    # Clear will mark struct_value modified which will indeed create a struct.
656    struct_value.Clear()
657    return struct_value
658
659  def add_list(self):
660    """Appends and returns a list value as the next value in the list."""
661    list_value = self.values.add().list_value
662    # Clear will mark list_value modified which will indeed create a list.
663    list_value.Clear()
664    return list_value
665
666collections.abc.MutableSequence.register(ListValue)
667
668
669# LINT.IfChange(wktbases)
670WKTBASES = {
671    'google.protobuf.Any': Any,
672    'google.protobuf.Duration': Duration,
673    'google.protobuf.FieldMask': FieldMask,
674    'google.protobuf.ListValue': ListValue,
675    'google.protobuf.Struct': Struct,
676    'google.protobuf.Timestamp': Timestamp,
677}
678# LINT.ThenChange(//depot/google.protobuf/compiler/python/pyi_generator.cc:wktbases)
679