Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/proto/message.py: 52%

259 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:45 +0000

1# Copyright 2018 Google LLC 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# https://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15import collections 

16import collections.abc 

17import copy 

18import re 

19from typing import List, Type 

20 

21from google.protobuf import descriptor_pb2 

22from google.protobuf import message 

23from google.protobuf.json_format import MessageToDict, MessageToJson, Parse 

24 

25from proto import _file_info 

26from proto import _package_info 

27from proto.fields import Field 

28from proto.fields import MapField 

29from proto.fields import RepeatedField 

30from proto.marshal import Marshal 

31from proto.primitives import ProtoType 

32from proto.utils import has_upb 

33 

34 

35_upb = has_upb() # Important to cache result here. 

36 

37 

38class MessageMeta(type): 

39 """A metaclass for building and registering Message subclasses.""" 

40 

41 def __new__(mcls, name, bases, attrs): 

42 # Do not do any special behavior for Message itself. 

43 if not bases: 

44 return super().__new__(mcls, name, bases, attrs) 

45 

46 # Get the essential information about the proto package, and where 

47 # this component belongs within the file. 

48 package, marshal = _package_info.compile(name, attrs) 

49 

50 # Determine the local path of this proto component within the file. 

51 local_path = tuple(attrs.get("__qualname__", name).split(".")) 

52 

53 # Sanity check: We get the wrong full name if a class is declared 

54 # inside a function local scope; correct this. 

55 if "<locals>" in local_path: 

56 ix = local_path.index("<locals>") 

57 local_path = local_path[: ix - 1] + local_path[ix + 1 :] 

58 

59 # Determine the full name in protocol buffers. 

60 full_name = ".".join((package,) + local_path).lstrip(".") 

61 

62 # Special case: Maps. Map fields are special; they are essentially 

63 # shorthand for a nested message and a repeated field of that message. 

64 # Decompose each map into its constituent form. 

65 # https://developers.google.com/protocol-buffers/docs/proto3#maps 

66 map_fields = {} 

67 for key, field in attrs.items(): 

68 if not isinstance(field, MapField): 

69 continue 

70 

71 # Determine the name of the entry message. 

72 msg_name = "{pascal_key}Entry".format( 

73 pascal_key=re.sub( 

74 r"_\w", 

75 lambda m: m.group()[1:].upper(), 

76 key, 

77 ).replace(key[0], key[0].upper(), 1), 

78 ) 

79 

80 # Create the "entry" message (with the key and value fields). 

81 # 

82 # Note: We instantiate an ordered dictionary here and then 

83 # attach key and value in order to ensure that the fields are 

84 # iterated in the correct order when the class is created. 

85 # This is only an issue in Python 3.5, where the order is 

86 # random (and the wrong order causes the pool to refuse to add 

87 # the descriptor because reasons). 

88 entry_attrs = collections.OrderedDict( 

89 { 

90 "__module__": attrs.get("__module__", None), 

91 "__qualname__": "{prefix}.{name}".format( 

92 prefix=attrs.get("__qualname__", name), 

93 name=msg_name, 

94 ), 

95 "_pb_options": {"map_entry": True}, 

96 } 

97 ) 

98 entry_attrs["key"] = Field(field.map_key_type, number=1) 

99 entry_attrs["value"] = Field( 

100 field.proto_type, 

101 number=2, 

102 enum=field.enum, 

103 message=field.message, 

104 ) 

105 map_fields[msg_name] = MessageMeta(msg_name, (Message,), entry_attrs) 

106 

107 # Create the repeated field for the entry message. 

108 map_fields[key] = RepeatedField( 

109 ProtoType.MESSAGE, 

110 number=field.number, 

111 message=map_fields[msg_name], 

112 ) 

113 

114 # Add the new entries to the attrs 

115 attrs.update(map_fields) 

116 

117 # Okay, now we deal with all the rest of the fields. 

118 # Iterate over all the attributes and separate the fields into 

119 # their own sequence. 

120 fields = [] 

121 new_attrs = {} 

122 oneofs = collections.OrderedDict() 

123 proto_imports = set() 

124 index = 0 

125 for key, field in attrs.items(): 

126 # Sanity check: If this is not a field, do nothing. 

127 if not isinstance(field, Field): 

128 # The field objects themselves should not be direct attributes. 

129 new_attrs[key] = field 

130 continue 

131 

132 # Add data that the field requires that we do not take in the 

133 # constructor because we can derive it from the metaclass. 

134 # (The goal is to make the declaration syntax as nice as possible.) 

135 field.mcls_data = { 

136 "name": key, 

137 "parent_name": full_name, 

138 "index": index, 

139 "package": package, 

140 } 

141 

142 # Add the field to the list of fields. 

143 fields.append(field) 

144 # If this field is part of a "oneof", ensure the oneof itself 

145 # is represented. 

146 if field.oneof: 

147 # Keep a running tally of the index of each oneof, and assign 

148 # that index to the field's descriptor. 

149 oneofs.setdefault(field.oneof, len(oneofs)) 

150 field.descriptor.oneof_index = oneofs[field.oneof] 

151 

152 # If this field references a message, it may be from another 

153 # proto file; ensure we know about the import (to faithfully 

154 # construct our file descriptor proto). 

155 if field.message and not isinstance(field.message, str): 

156 field_msg = field.message 

157 if hasattr(field_msg, "pb") and callable(field_msg.pb): 

158 field_msg = field_msg.pb() 

159 # Sanity check: The field's message may not yet be defined if 

160 # it was a Message defined in the same file, and the file 

161 # descriptor proto has not yet been generated. 

162 # 

163 # We do nothing in this situation; everything will be handled 

164 # correctly when the file descriptor is created later. 

165 if field_msg: 

166 proto_imports.add(field_msg.DESCRIPTOR.file.name) 

167 

168 # Same thing, but for enums. 

169 elif field.enum and not isinstance(field.enum, str): 

170 field_enum = ( 

171 field.enum._meta.pb 

172 if hasattr(field.enum, "_meta") 

173 else field.enum.DESCRIPTOR 

174 ) 

175 

176 if field_enum: 

177 proto_imports.add(field_enum.file.name) 

178 

179 # Increment the field index counter. 

180 index += 1 

181 

182 # As per descriptor.proto, all synthetic oneofs must be ordered after 

183 # 'real' oneofs. 

184 opt_attrs = {} 

185 for field in fields: 

186 if field.optional: 

187 field.oneof = "_{}".format(field.name) 

188 field.descriptor.oneof_index = oneofs[field.oneof] = len(oneofs) 

189 opt_attrs[field.name] = field.name 

190 

191 # Generating a metaclass dynamically provides class attributes that 

192 # instances can't see. This provides idiomatically named constants 

193 # that enable the following pattern to check for field presence: 

194 # 

195 # class MyMessage(proto.Message): 

196 # field = proto.Field(proto.INT32, number=1, optional=True) 

197 # 

198 # m = MyMessage() 

199 # MyMessage.field in m 

200 if opt_attrs: 

201 mcls = type("AttrsMeta", (mcls,), opt_attrs) 

202 

203 # Determine the filename. 

204 # We determine an appropriate proto filename based on the 

205 # Python module. 

206 filename = _file_info._FileInfo.proto_file_name( 

207 new_attrs.get("__module__", name.lower()) 

208 ) 

209 

210 # Get or create the information about the file, including the 

211 # descriptor to which the new message descriptor shall be added. 

212 file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) 

213 

214 # Ensure any imports that would be necessary are assigned to the file 

215 # descriptor proto being created. 

216 for proto_import in proto_imports: 

217 if proto_import not in file_info.descriptor.dependency: 

218 file_info.descriptor.dependency.append(proto_import) 

219 

220 # Retrieve any message options. 

221 opts = descriptor_pb2.MessageOptions(**new_attrs.pop("_pb_options", {})) 

222 

223 # Create the underlying proto descriptor. 

224 desc = descriptor_pb2.DescriptorProto( 

225 name=name, 

226 field=[i.descriptor for i in fields], 

227 oneof_decl=[ 

228 descriptor_pb2.OneofDescriptorProto(name=i) for i in oneofs.keys() 

229 ], 

230 options=opts, 

231 ) 

232 

233 # If any descriptors were nested under this one, they need to be 

234 # attached as nested types here. 

235 child_paths = [p for p in file_info.nested.keys() if local_path == p[:-1]] 

236 for child_path in child_paths: 

237 desc.nested_type.add().MergeFrom(file_info.nested.pop(child_path)) 

238 

239 # Same thing, but for enums 

240 child_paths = [p for p in file_info.nested_enum.keys() if local_path == p[:-1]] 

241 for child_path in child_paths: 

242 desc.enum_type.add().MergeFrom(file_info.nested_enum.pop(child_path)) 

243 

244 # Add the descriptor to the file if it is a top-level descriptor, 

245 # or to a "holding area" for nested messages otherwise. 

246 if len(local_path) == 1: 

247 file_info.descriptor.message_type.add().MergeFrom(desc) 

248 else: 

249 file_info.nested[local_path] = desc 

250 

251 # Create the MessageInfo instance to be attached to this message. 

252 new_attrs["_meta"] = _MessageInfo( 

253 fields=fields, 

254 full_name=full_name, 

255 marshal=marshal, 

256 options=opts, 

257 package=package, 

258 ) 

259 

260 # Run the superclass constructor. 

261 cls = super().__new__(mcls, name, bases, new_attrs) 

262 

263 # The info class and fields need a reference to the class just created. 

264 cls._meta.parent = cls 

265 for field in cls._meta.fields.values(): 

266 field.parent = cls 

267 

268 # Add this message to the _FileInfo instance; this allows us to 

269 # associate the descriptor with the message once the descriptor 

270 # is generated. 

271 file_info.messages[full_name] = cls 

272 

273 # Generate the descriptor for the file if it is ready. 

274 if file_info.ready(new_class=cls): 

275 file_info.generate_file_pb(new_class=cls, fallback_salt=full_name) 

276 

277 # Done; return the class. 

278 return cls 

279 

280 @classmethod 

281 def __prepare__(mcls, name, bases, **kwargs): 

282 return collections.OrderedDict() 

283 

284 @property 

285 def meta(cls): 

286 return cls._meta 

287 

288 def __dir__(self): 

289 try: 

290 names = set(dir(type)) 

291 names.update( 

292 ( 

293 "meta", 

294 "pb", 

295 "wrap", 

296 "serialize", 

297 "deserialize", 

298 "to_json", 

299 "from_json", 

300 "to_dict", 

301 "copy_from", 

302 ) 

303 ) 

304 desc = self.pb().DESCRIPTOR 

305 names.update(t.name for t in desc.nested_types) 

306 names.update(e.name for e in desc.enum_types) 

307 

308 return names 

309 except AttributeError: 

310 return dir(type) 

311 

312 def pb(cls, obj=None, *, coerce: bool = False): 

313 """Return the underlying protobuf Message class or instance. 

314 

315 Args: 

316 obj: If provided, and an instance of ``cls``, return the 

317 underlying protobuf instance. 

318 coerce (bool): If provided, will attempt to coerce ``obj`` to 

319 ``cls`` if it is not already an instance. 

320 """ 

321 if obj is None: 

322 return cls.meta.pb 

323 if not isinstance(obj, cls): 

324 if coerce: 

325 obj = cls(obj) 

326 else: 

327 raise TypeError( 

328 "%r is not an instance of %s" 

329 % ( 

330 obj, 

331 cls.__name__, 

332 ) 

333 ) 

334 return obj._pb 

335 

336 def wrap(cls, pb): 

337 """Return a Message object that shallowly wraps the descriptor. 

338 

339 Args: 

340 pb: A protocol buffer object, such as would be returned by 

341 :meth:`pb`. 

342 """ 

343 # Optimized fast path. 

344 instance = cls.__new__(cls) 

345 super(cls, instance).__setattr__("_pb", pb) 

346 return instance 

347 

348 def serialize(cls, instance) -> bytes: 

349 """Return the serialized proto. 

350 

351 Args: 

352 instance: An instance of this message type, or something 

353 compatible (accepted by the type's constructor). 

354 

355 Returns: 

356 bytes: The serialized representation of the protocol buffer. 

357 """ 

358 return cls.pb(instance, coerce=True).SerializeToString() 

359 

360 def deserialize(cls, payload: bytes) -> "Message": 

361 """Given a serialized proto, deserialize it into a Message instance. 

362 

363 Args: 

364 payload (bytes): The serialized proto. 

365 

366 Returns: 

367 ~.Message: An instance of the message class against which this 

368 method was called. 

369 """ 

370 return cls.wrap(cls.pb().FromString(payload)) 

371 

372 def to_json( 

373 cls, 

374 instance, 

375 *, 

376 use_integers_for_enums=True, 

377 including_default_value_fields=True, 

378 preserving_proto_field_name=False, 

379 sort_keys=False, 

380 indent=2, 

381 float_precision=None, 

382 ) -> str: 

383 """Given a message instance, serialize it to json 

384 

385 Args: 

386 instance: An instance of this message type, or something 

387 compatible (accepted by the type's constructor). 

388 use_integers_for_enums (Optional(bool)): An option that determines whether enum 

389 values should be represented by strings (False) or integers (True). 

390 Default is True. 

391 preserving_proto_field_name (Optional(bool)): An option that 

392 determines whether field name representations preserve 

393 proto case (snake_case) or use lowerCamelCase. Default is False. 

394 sort_keys (Optional(bool)): If True, then the output will be sorted by field names. 

395 Default is False. 

396 indent (Optional(int)): The JSON object will be pretty-printed with this indent level. 

397 An indent level of 0 or negative will only insert newlines. 

398 Pass None for the most compact representation without newlines. 

399 float_precision (Optional(int)): If set, use this to specify float field valid digits. 

400 Default is None. 

401 Returns: 

402 str: The json string representation of the protocol buffer. 

403 """ 

404 return MessageToJson( 

405 cls.pb(instance), 

406 use_integers_for_enums=use_integers_for_enums, 

407 including_default_value_fields=including_default_value_fields, 

408 preserving_proto_field_name=preserving_proto_field_name, 

409 sort_keys=sort_keys, 

410 indent=indent, 

411 float_precision=float_precision, 

412 ) 

413 

414 def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message": 

415 """Given a json string representing an instance, 

416 parse it into a message. 

417 

418 Args: 

419 paylod: A json string representing a message. 

420 ignore_unknown_fields (Optional(bool)): If True, do not raise errors 

421 for unknown fields. 

422 

423 Returns: 

424 ~.Message: An instance of the message class against which this 

425 method was called. 

426 """ 

427 instance = cls() 

428 Parse(payload, instance._pb, ignore_unknown_fields=ignore_unknown_fields) 

429 return instance 

430 

431 def to_dict( 

432 cls, 

433 instance, 

434 *, 

435 use_integers_for_enums=True, 

436 preserving_proto_field_name=True, 

437 including_default_value_fields=True, 

438 float_precision=None, 

439 ) -> "Message": 

440 """Given a message instance, return its representation as a python dict. 

441 

442 Args: 

443 instance: An instance of this message type, or something 

444 compatible (accepted by the type's constructor). 

445 use_integers_for_enums (Optional(bool)): An option that determines whether enum 

446 values should be represented by strings (False) or integers (True). 

447 Default is True. 

448 preserving_proto_field_name (Optional(bool)): An option that 

449 determines whether field name representations preserve 

450 proto case (snake_case) or use lowerCamelCase. Default is True. 

451 including_default_value_fields (Optional(bool)): An option that 

452 determines whether the default field values should be included in the results. 

453 Default is True. 

454 float_precision (Optional(int)): If set, use this to specify float field valid digits. 

455 Default is None. 

456 

457 Returns: 

458 dict: A representation of the protocol buffer using pythonic data structures. 

459 Messages and map fields are represented as dicts, 

460 repeated fields are represented as lists. 

461 """ 

462 return MessageToDict( 

463 cls.pb(instance), 

464 including_default_value_fields=including_default_value_fields, 

465 preserving_proto_field_name=preserving_proto_field_name, 

466 use_integers_for_enums=use_integers_for_enums, 

467 float_precision=float_precision, 

468 ) 

469 

470 def copy_from(cls, instance, other): 

471 """Equivalent for protobuf.Message.CopyFrom 

472 

473 Args: 

474 instance: An instance of this message type 

475 other: (Union[dict, ~.Message): 

476 A dictionary or message to reinitialize the values for this message. 

477 """ 

478 if isinstance(other, cls): 

479 # Just want the underlying proto. 

480 other = Message.pb(other) 

481 elif isinstance(other, cls.pb()): 

482 # Don't need to do anything. 

483 pass 

484 elif isinstance(other, collections.abc.Mapping): 

485 # Coerce into a proto 

486 other = cls._meta.pb(**other) 

487 else: 

488 raise TypeError( 

489 "invalid argument type to copy to {}: {}".format( 

490 cls.__name__, other.__class__.__name__ 

491 ) 

492 ) 

493 

494 # Note: we can't just run self.__init__ because this may be a message field 

495 # for a higher order proto; the memory layout for protos is NOT LIKE the 

496 # python memory model. We cannot rely on just setting things by reference. 

497 # Non-trivial complexity is (partially) hidden by the protobuf runtime. 

498 cls.pb(instance).CopyFrom(other) 

499 

500 

501class Message(metaclass=MessageMeta): 

502 """The abstract base class for a message. 

503 

504 Args: 

505 mapping (Union[dict, ~.Message]): A dictionary or message to be 

506 used to determine the values for this message. 

507 ignore_unknown_fields (Optional(bool)): If True, do not raise errors for 

508 unknown fields. Only applied if `mapping` is a mapping type or there 

509 are keyword parameters. 

510 kwargs (dict): Keys and values corresponding to the fields of the 

511 message. 

512 """ 

513 

514 def __init__( 

515 self, 

516 mapping=None, 

517 *, 

518 ignore_unknown_fields=False, 

519 **kwargs, 

520 ): 

521 # We accept several things for `mapping`: 

522 # * An instance of this class. 

523 # * An instance of the underlying protobuf descriptor class. 

524 # * A dict 

525 # * Nothing (keyword arguments only). 

526 if mapping is None: 

527 if not kwargs: 

528 # Special fast path for empty construction. 

529 super().__setattr__("_pb", self._meta.pb()) 

530 return 

531 

532 mapping = kwargs 

533 elif isinstance(mapping, self._meta.pb): 

534 # Make a copy of the mapping. 

535 # This is a constructor for a new object, so users will assume 

536 # that it will not have side effects on the arguments being 

537 # passed in. 

538 # 

539 # The `wrap` method on the metaclass is the public API for taking 

540 # ownership of the passed in protobuf object. 

541 mapping = copy.deepcopy(mapping) 

542 if kwargs: 

543 mapping.MergeFrom(self._meta.pb(**kwargs)) 

544 

545 super().__setattr__("_pb", mapping) 

546 return 

547 elif isinstance(mapping, type(self)): 

548 # Just use the above logic on mapping's underlying pb. 

549 self.__init__(mapping=mapping._pb, **kwargs) 

550 return 

551 elif isinstance(mapping, collections.abc.Mapping): 

552 # Can't have side effects on mapping. 

553 mapping = copy.copy(mapping) 

554 # kwargs entries take priority for duplicate keys. 

555 mapping.update(kwargs) 

556 else: 

557 # Sanity check: Did we get something not a map? Error if so. 

558 raise TypeError( 

559 "Invalid constructor input for %s: %r" 

560 % ( 

561 self.__class__.__name__, 

562 mapping, 

563 ) 

564 ) 

565 

566 params = {} 

567 # Update the mapping to address any values that need to be 

568 # coerced. 

569 marshal = self._meta.marshal 

570 for key, value in mapping.items(): 

571 (key, pb_type) = self._get_pb_type_from_key(key) 

572 if pb_type is None: 

573 if ignore_unknown_fields: 

574 continue 

575 

576 raise ValueError( 

577 "Unknown field for {}: {}".format(self.__class__.__name__, key) 

578 ) 

579 

580 try: 

581 pb_value = marshal.to_proto(pb_type, value) 

582 except ValueError: 

583 # Underscores may be appended to field names 

584 # that collide with python or proto-plus keywords. 

585 # In case a key only exists with a `_` suffix, coerce the key 

586 # to include the `_` suffix. It's not possible to 

587 # natively define the same field with a trailing underscore in protobuf. 

588 # See related issue 

589 # https://github.com/googleapis/python-api-core/issues/227 

590 if isinstance(value, dict): 

591 if _upb: 

592 # In UPB, pb_type is MessageMeta which doesn't expose attrs like it used to in Python/CPP. 

593 keys_to_update = [ 

594 item 

595 for item in value 

596 if item not in pb_type.DESCRIPTOR.fields_by_name 

597 and f"{item}_" in pb_type.DESCRIPTOR.fields_by_name 

598 ] 

599 else: 

600 keys_to_update = [ 

601 item 

602 for item in value 

603 if not hasattr(pb_type, item) 

604 and hasattr(pb_type, f"{item}_") 

605 ] 

606 for item in keys_to_update: 

607 value[f"{item}_"] = value.pop(item) 

608 

609 pb_value = marshal.to_proto(pb_type, value) 

610 

611 if pb_value is not None: 

612 params[key] = pb_value 

613 

614 # Create the internal protocol buffer. 

615 super().__setattr__("_pb", self._meta.pb(**params)) 

616 

617 def _get_pb_type_from_key(self, key): 

618 """Given a key, return the corresponding pb_type. 

619 

620 Args: 

621 key(str): The name of the field. 

622 

623 Returns: 

624 A tuple containing a key and pb_type. The pb_type will be 

625 the composite type of the field, or the primitive type if a primitive. 

626 If no corresponding field exists, return None. 

627 """ 

628 

629 pb_type = None 

630 

631 try: 

632 pb_type = self._meta.fields[key].pb_type 

633 except KeyError: 

634 # Underscores may be appended to field names 

635 # that collide with python or proto-plus keywords. 

636 # In case a key only exists with a `_` suffix, coerce the key 

637 # to include the `_` suffix. It's not possible to 

638 # natively define the same field with a trailing underscore in protobuf. 

639 # See related issue 

640 # https://github.com/googleapis/python-api-core/issues/227 

641 if f"{key}_" in self._meta.fields: 

642 key = f"{key}_" 

643 pb_type = self._meta.fields[key].pb_type 

644 

645 return (key, pb_type) 

646 

647 def __dir__(self): 

648 desc = type(self).pb().DESCRIPTOR 

649 names = {f_name for f_name in self._meta.fields.keys()} 

650 names.update(m.name for m in desc.nested_types) 

651 names.update(e.name for e in desc.enum_types) 

652 names.update(dir(object())) 

653 # Can't think of a better way of determining 

654 # the special methods than manually listing them. 

655 names.update( 

656 ( 

657 "__bool__", 

658 "__contains__", 

659 "__dict__", 

660 "__getattr__", 

661 "__getstate__", 

662 "__module__", 

663 "__setstate__", 

664 "__weakref__", 

665 ) 

666 ) 

667 

668 return names 

669 

670 def __bool__(self): 

671 """Return True if any field is truthy, False otherwise.""" 

672 return any(k in self and getattr(self, k) for k in self._meta.fields.keys()) 

673 

674 def __contains__(self, key): 

675 """Return True if this field was set to something non-zero on the wire. 

676 

677 In most cases, this method will return True when ``__getattr__`` 

678 would return a truthy value and False when it would return a falsy 

679 value, so explicitly calling this is not useful. 

680 

681 The exception case is empty messages explicitly set on the wire, 

682 which are falsy from ``__getattr__``. This method allows to 

683 distinguish between an explicitly provided empty message and the 

684 absence of that message, which is useful in some edge cases. 

685 

686 The most common edge case is the use of ``google.protobuf.BoolValue`` 

687 to get a boolean that distinguishes between ``False`` and ``None`` 

688 (or the same for a string, int, etc.). This library transparently 

689 handles that case for you, but this method remains available to 

690 accommodate cases not automatically covered. 

691 

692 Args: 

693 key (str): The name of the field. 

694 

695 Returns: 

696 bool: Whether the field's value corresponds to a non-empty 

697 wire serialization. 

698 """ 

699 pb_value = getattr(self._pb, key) 

700 try: 

701 # Protocol buffers "HasField" is unfriendly; it only works 

702 # against composite, non-repeated fields, and raises ValueError 

703 # against any repeated field or primitive. 

704 # 

705 # There is no good way to test whether it is valid to provide 

706 # a field to this method, so sadly we are stuck with a 

707 # somewhat inefficient try/except. 

708 return self._pb.HasField(key) 

709 except ValueError: 

710 return bool(pb_value) 

711 

712 def __delattr__(self, key): 

713 """Delete the value on the given field. 

714 

715 This is generally equivalent to setting a falsy value. 

716 """ 

717 self._pb.ClearField(key) 

718 

719 def __eq__(self, other): 

720 """Return True if the messages are equal, False otherwise.""" 

721 # If these are the same type, use internal protobuf's equality check. 

722 if isinstance(other, type(self)): 

723 return self._pb == other._pb 

724 

725 # If the other type is the target protobuf object, honor that also. 

726 if isinstance(other, self._meta.pb): 

727 return self._pb == other 

728 

729 # Ask the other object. 

730 return NotImplemented 

731 

732 def __getattr__(self, key): 

733 """Retrieve the given field's value. 

734 

735 In protocol buffers, the presence of a field on a message is 

736 sufficient for it to always be "present". 

737 

738 For primitives, a value of the correct type will always be returned 

739 (the "falsy" values in protocol buffers consistently match those 

740 in Python). For repeated fields, the falsy value is always an empty 

741 sequence. 

742 

743 For messages, protocol buffers does distinguish between an empty 

744 message and absence, but this distinction is subtle and rarely 

745 relevant. Therefore, this method always returns an empty message 

746 (following the official implementation). To check for message 

747 presence, use ``key in self`` (in other words, ``__contains__``). 

748 

749 .. note:: 

750 

751 Some well-known protocol buffer types 

752 (e.g. ``google.protobuf.Timestamp``) will be converted to 

753 their Python equivalents. See the ``marshal`` module for 

754 more details. 

755 """ 

756 (key, pb_type) = self._get_pb_type_from_key(key) 

757 if pb_type is None: 

758 raise AttributeError( 

759 "Unknown field for {}: {}".format(self.__class__.__name__, key) 

760 ) 

761 pb_value = getattr(self._pb, key) 

762 marshal = self._meta.marshal 

763 return marshal.to_python(pb_type, pb_value, absent=key not in self) 

764 

765 def __ne__(self, other): 

766 """Return True if the messages are unequal, False otherwise.""" 

767 return not self == other 

768 

769 def __repr__(self): 

770 return repr(self._pb) 

771 

772 def __setattr__(self, key, value): 

773 """Set the value on the given field. 

774 

775 For well-known protocol buffer types which are marshalled, either 

776 the protocol buffer object or the Python equivalent is accepted. 

777 """ 

778 if key[0] == "_": 

779 return super().__setattr__(key, value) 

780 marshal = self._meta.marshal 

781 (key, pb_type) = self._get_pb_type_from_key(key) 

782 if pb_type is None: 

783 raise AttributeError( 

784 "Unknown field for {}: {}".format(self.__class__.__name__, key) 

785 ) 

786 

787 pb_value = marshal.to_proto(pb_type, value) 

788 

789 # Clear the existing field. 

790 # This is the only way to successfully write nested falsy values, 

791 # because otherwise MergeFrom will no-op on them. 

792 self._pb.ClearField(key) 

793 

794 # Merge in the value being set. 

795 if pb_value is not None: 

796 self._pb.MergeFrom(self._meta.pb(**{key: pb_value})) 

797 

798 def __getstate__(self): 

799 """Serialize for pickling.""" 

800 return self._pb.SerializeToString() 

801 

802 def __setstate__(self, value): 

803 """Deserialization for pickling.""" 

804 new_pb = self._meta.pb().FromString(value) 

805 super().__setattr__("_pb", new_pb) 

806 

807 

808class _MessageInfo: 

809 """Metadata about a message. 

810 

811 Args: 

812 fields (Tuple[~.fields.Field]): The fields declared on the message. 

813 package (str): The proto package. 

814 full_name (str): The full name of the message. 

815 file_info (~._FileInfo): The file descriptor and messages for the 

816 file containing this message. 

817 marshal (~.Marshal): The marshal instance to which this message was 

818 automatically registered. 

819 options (~.descriptor_pb2.MessageOptions): Any options that were 

820 set on the message. 

821 """ 

822 

823 def __init__( 

824 self, 

825 *, 

826 fields: List[Field], 

827 package: str, 

828 full_name: str, 

829 marshal: Marshal, 

830 options: descriptor_pb2.MessageOptions, 

831 ) -> None: 

832 self.package = package 

833 self.full_name = full_name 

834 self.options = options 

835 self.fields = collections.OrderedDict((i.name, i) for i in fields) 

836 self.fields_by_number = collections.OrderedDict((i.number, i) for i in fields) 

837 self.marshal = marshal 

838 self._pb = None 

839 

840 @property 

841 def pb(self) -> Type[message.Message]: 

842 """Return the protobuf message type for this descriptor. 

843 

844 If a field on the message references another message which has not 

845 loaded, then this method returns None. 

846 """ 

847 return self._pb 

848 

849 

850__all__ = ("Message",)