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