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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

284 statements  

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 Any, Dict, List, Optional, Type 

20import warnings 

21 

22import google.protobuf 

23from google.protobuf import descriptor_pb2 

24from google.protobuf import message 

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

26 

27from proto import _file_info 

28from proto import _package_info 

29from proto.fields import Field 

30from proto.fields import MapField 

31from proto.fields import RepeatedField 

32from proto.marshal import Marshal 

33from proto.primitives import ProtoType 

34from proto.utils import has_upb 

35 

36 

37PROTOBUF_VERSION = google.protobuf.__version__ 

38 

39# extract the major version code 

40_PROTOBUF_MAJOR_VERSION = PROTOBUF_VERSION.partition(".")[0] 

41 

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

43 

44 

45class MessageMeta(type): 

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

47 

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

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

50 if not bases: 

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

52 

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

54 # this component belongs within the file. 

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

56 

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

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

59 

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

61 # inside a function local scope; correct this. 

62 if "<locals>" in local_path: 

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

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

65 

66 # Determine the full name in protocol buffers. 

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

68 

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

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

71 # Decompose each map into its constituent form. 

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

73 map_fields = {} 

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

75 if not isinstance(field, MapField): 

76 continue 

77 

78 # Determine the name of the entry message. 

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

80 pascal_key=re.sub( 

81 r"_\w", 

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

83 key, 

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

85 ) 

86 

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

88 # 

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

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

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

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

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

94 # the descriptor because reasons). 

95 entry_attrs = collections.OrderedDict( 

96 { 

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

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

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

100 name=msg_name, 

101 ), 

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

103 } 

104 ) 

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

106 entry_attrs["value"] = Field( 

107 field.proto_type, 

108 number=2, 

109 enum=field.enum, 

110 message=field.message, 

111 ) 

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

113 

114 # Create the repeated field for the entry message. 

115 map_fields[key] = RepeatedField( 

116 ProtoType.MESSAGE, 

117 number=field.number, 

118 message=map_fields[msg_name], 

119 ) 

120 

121 # Add the new entries to the attrs 

122 attrs.update(map_fields) 

123 

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

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

126 # their own sequence. 

127 fields = [] 

128 new_attrs = {} 

129 oneofs = collections.OrderedDict() 

130 proto_imports = set() 

131 index = 0 

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

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

134 if not isinstance(field, Field): 

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

136 new_attrs[key] = field 

137 continue 

138 

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

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

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

142 field.mcls_data = { 

143 "name": key, 

144 "parent_name": full_name, 

145 "index": index, 

146 "package": package, 

147 } 

148 

149 # Add the field to the list of fields. 

150 fields.append(field) 

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

152 # is represented. 

153 if field.oneof: 

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

155 # that index to the field's descriptor. 

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

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

158 

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

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

161 # construct our file descriptor proto). 

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

163 field_msg = field.message 

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

165 field_msg = field_msg.pb() 

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

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

168 # descriptor proto has not yet been generated. 

169 # 

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

171 # correctly when the file descriptor is created later. 

172 if field_msg: 

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

174 

175 # Same thing, but for enums. 

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

177 field_enum = ( 

178 field.enum._meta.pb 

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

180 else field.enum.DESCRIPTOR 

181 ) 

182 

183 if field_enum: 

184 proto_imports.add(field_enum.file.name) 

185 

186 # Increment the field index counter. 

187 index += 1 

188 

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

190 # 'real' oneofs. 

191 opt_attrs = {} 

192 for field in fields: 

193 if field.optional: 

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

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

196 opt_attrs[field.name] = field.name 

197 

198 # Generating a metaclass dynamically provides class attributes that 

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

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

201 # 

202 # class MyMessage(proto.Message): 

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

204 # 

205 # m = MyMessage() 

206 # MyMessage.field in m 

207 if opt_attrs: 

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

209 

210 # Determine the filename. 

211 # We determine an appropriate proto filename based on the 

212 # Python module. 

213 filename = _file_info._FileInfo.proto_file_name( 

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

215 ) 

216 

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

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

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

220 

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

222 # descriptor proto being created. 

223 for proto_import in proto_imports: 

224 if proto_import not in file_info.descriptor.dependency: 

225 file_info.descriptor.dependency.append(proto_import) 

226 

227 # Retrieve any message options. 

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

229 

230 # Create the underlying proto descriptor. 

231 desc = descriptor_pb2.DescriptorProto( 

232 name=name, 

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

234 oneof_decl=[ 

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

236 ], 

237 options=opts, 

238 ) 

239 

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

241 # attached as nested types here. 

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

243 for child_path in child_paths: 

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

245 

246 # Same thing, but for enums 

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

248 for child_path in child_paths: 

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

250 

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

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

253 if len(local_path) == 1: 

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

255 else: 

256 file_info.nested[local_path] = desc 

257 

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

259 new_attrs["_meta"] = _MessageInfo( 

260 fields=fields, 

261 full_name=full_name, 

262 marshal=marshal, 

263 options=opts, 

264 package=package, 

265 ) 

266 

267 # Run the superclass constructor. 

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

269 

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

271 cls._meta.parent = cls 

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

273 field.parent = cls 

274 

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

276 # associate the descriptor with the message once the descriptor 

277 # is generated. 

278 file_info.messages[full_name] = cls 

279 

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

281 if file_info.ready(new_class=cls): 

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

283 

284 # Done; return the class. 

285 return cls 

286 

287 @classmethod 

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

289 return collections.OrderedDict() 

290 

291 @property 

292 def meta(cls): 

293 return cls._meta 

294 

295 def __dir__(self): 

296 try: 

297 names = set(dir(type)) 

298 names.update( 

299 ( 

300 "meta", 

301 "pb", 

302 "wrap", 

303 "serialize", 

304 "deserialize", 

305 "to_json", 

306 "from_json", 

307 "to_dict", 

308 "copy_from", 

309 ) 

310 ) 

311 desc = self.pb().DESCRIPTOR 

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

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

314 

315 return names 

316 except AttributeError: 

317 return dir(type) 

318 

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

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

321 

322 Args: 

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

324 underlying protobuf instance. 

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

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

327 """ 

328 if obj is None: 

329 return cls.meta.pb 

330 if not isinstance(obj, cls): 

331 if coerce: 

332 obj = cls(obj) 

333 else: 

334 raise TypeError( 

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

336 % ( 

337 obj, 

338 cls.__name__, 

339 ) 

340 ) 

341 return obj._pb 

342 

343 def wrap(cls, pb): 

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

345 

346 Args: 

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

348 :meth:`pb`. 

349 """ 

350 # Optimized fast path. 

351 instance = cls.__new__(cls) 

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

353 return instance 

354 

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

356 """Return the serialized proto. 

357 

358 Args: 

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

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

361 

362 Returns: 

363 bytes: The serialized representation of the protocol buffer. 

364 """ 

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

366 

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

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

369 

370 Args: 

371 payload (bytes): The serialized proto. 

372 

373 Returns: 

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

375 method was called. 

376 """ 

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

378 

379 def _warn_if_including_default_value_fields_is_used_protobuf_5( 

380 cls, including_default_value_fields: Optional[bool] 

381 ) -> None: 

382 """ 

383 Warn Protobuf 5.x+ users that `including_default_value_fields` is deprecated if it is set. 

384 

385 Args: 

386 including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user. 

387 """ 

388 if ( 

389 _PROTOBUF_MAJOR_VERSION not in ("3", "4") 

390 and including_default_value_fields is not None 

391 ): 

392 warnings.warn( 

393 """The argument `including_default_value_fields` has been removed from 

394 Protobuf 5.x. Please use `always_print_fields_with_no_presence` instead. 

395 """, 

396 DeprecationWarning, 

397 ) 

398 

399 def _raise_if_print_fields_values_are_set_and_differ( 

400 cls, 

401 always_print_fields_with_no_presence: Optional[bool], 

402 including_default_value_fields: Optional[bool], 

403 ) -> None: 

404 """ 

405 Raise Exception if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set 

406 and the values differ. 

407 

408 Args: 

409 always_print_fields_with_no_presence (Optional(bool)): The value of `always_print_fields_with_no_presence` set by the user. 

410 including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user. 

411 Returns: 

412 None 

413 Raises: 

414 ValueError: if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set and 

415 the values differ. 

416 """ 

417 if ( 

418 always_print_fields_with_no_presence is not None 

419 and including_default_value_fields is not None 

420 and always_print_fields_with_no_presence != including_default_value_fields 

421 ): 

422 raise ValueError( 

423 "Arguments `always_print_fields_with_no_presence` and `including_default_value_fields` must match" 

424 ) 

425 

426 def _normalize_print_fields_without_presence( 

427 cls, 

428 always_print_fields_with_no_presence: Optional[bool], 

429 including_default_value_fields: Optional[bool], 

430 ) -> bool: 

431 """ 

432 Return true if fields with no presence should be included in the results. 

433 By default, fields with no presence will be included in the results 

434 when both `always_print_fields_with_no_presence` and 

435 `including_default_value_fields` are not set 

436 

437 Args: 

438 always_print_fields_with_no_presence (Optional(bool)): The value of `always_print_fields_with_no_presence` set by the user. 

439 including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user. 

440 Returns: 

441 None 

442 Raises: 

443 ValueError: if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set and 

444 the values differ. 

445 """ 

446 

447 cls._warn_if_including_default_value_fields_is_used_protobuf_5( 

448 including_default_value_fields 

449 ) 

450 cls._raise_if_print_fields_values_are_set_and_differ( 

451 always_print_fields_with_no_presence, including_default_value_fields 

452 ) 

453 # Default to True if neither `always_print_fields_with_no_presence` or `including_default_value_fields` is set 

454 return ( 

455 ( 

456 always_print_fields_with_no_presence is None 

457 and including_default_value_fields is None 

458 ) 

459 or always_print_fields_with_no_presence 

460 or including_default_value_fields 

461 ) 

462 

463 def to_json( 

464 cls, 

465 instance, 

466 *, 

467 use_integers_for_enums=True, 

468 including_default_value_fields=None, 

469 preserving_proto_field_name=False, 

470 sort_keys=False, 

471 indent=2, 

472 float_precision=None, 

473 always_print_fields_with_no_presence=None, 

474 ) -> str: 

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

476 

477 Args: 

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

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

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

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

482 Default is True. 

483 including_default_value_fields (Optional(bool)): Deprecated. Use argument 

484 `always_print_fields_with_no_presence` instead. An option that 

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

486 This value must match `always_print_fields_with_no_presence`, 

487 if both arguments are explicitly set. 

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

489 determines whether field name representations preserve 

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

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

492 Default is False. 

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

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

495 Pass None for the most compact representation without newlines. 

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

497 Default is None. [DEPRECATED] float_precision was removed in Protobuf 7.x. 

498 always_print_fields_with_no_presence (Optional(bool)): If True, fields without 

499 presence (implicit presence scalars, repeated fields, and map fields) will 

500 always be serialized. Any field that supports presence is not affected by 

501 this option (including singular message fields and oneof fields). 

502 This value must match `including_default_value_fields`, 

503 if both arguments are explicitly set. 

504 Returns: 

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

506 """ 

507 return _message_to_map(map_fn=MessageToJson, **locals()) 

508 

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

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

511 parse it into a message. 

512 

513 Args: 

514 payload: A json string representing a message. 

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

516 for unknown fields. 

517 

518 Returns: 

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

520 method was called. 

521 """ 

522 instance = cls() 

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

524 return instance 

525 

526 def to_dict( 

527 cls, 

528 instance, 

529 *, 

530 use_integers_for_enums=True, 

531 preserving_proto_field_name=True, 

532 including_default_value_fields=None, 

533 float_precision=None, 

534 always_print_fields_with_no_presence=None, 

535 ) -> Dict[str, Any]: 

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

537 

538 Args: 

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

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

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

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

543 Default is True. 

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

545 determines whether field name representations preserve 

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

547 including_default_value_fields (Optional(bool)): Deprecated. Use argument 

548 `always_print_fields_with_no_presence` instead. An option that 

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

550 This value must match `always_print_fields_with_no_presence`, 

551 if both arguments are explicitly set. 

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

553 Default is None. [DEPRECATED] float_precision was removed in Protobuf 7.x. 

554 always_print_fields_with_no_presence (Optional(bool)): If True, fields without 

555 presence (implicit presence scalars, repeated fields, and map fields) will 

556 always be serialized. Any field that supports presence is not affected by 

557 this option (including singular message fields and oneof fields). This value 

558 must match `including_default_value_fields`, if both arguments are explicitly set. 

559 

560 Returns: 

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

562 Messages and map fields are represented as dicts, 

563 repeated fields are represented as lists. 

564 """ 

565 return _message_to_map(map_fn=MessageToDict, **locals()) 

566 

567 def copy_from(cls, instance, other): 

568 """Equivalent for protobuf.Message.CopyFrom 

569 

570 Args: 

571 instance: An instance of this message type 

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

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

574 """ 

575 if isinstance(other, cls): 

576 # Just want the underlying proto. 

577 other = Message.pb(other) 

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

579 # Don't need to do anything. 

580 pass 

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

582 # Coerce into a proto 

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

584 else: 

585 raise TypeError( 

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

587 cls.__name__, other.__class__.__name__ 

588 ) 

589 ) 

590 

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

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

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

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

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

596 

597 

598class Message(metaclass=MessageMeta): 

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

600 

601 Args: 

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

603 used to determine the values for this message. 

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

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

606 are keyword parameters. 

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

608 message. 

609 """ 

610 

611 def __init__( 

612 self, 

613 mapping=None, 

614 *, 

615 ignore_unknown_fields=False, 

616 **kwargs, 

617 ): 

618 # We accept several things for `mapping`: 

619 # * An instance of this class. 

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

621 # * A dict 

622 # * Nothing (keyword arguments only). 

623 if mapping is None: 

624 if not kwargs: 

625 # Special fast path for empty construction. 

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

627 return 

628 

629 mapping = kwargs 

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

631 # Make a copy of the mapping. 

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

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

634 # passed in. 

635 # 

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

637 # ownership of the passed in protobuf object. 

638 mapping = copy.deepcopy(mapping) 

639 if kwargs: 

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

641 

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

643 return 

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

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

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

647 return 

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

649 # Can't have side effects on mapping. 

650 mapping = copy.copy(mapping) 

651 # kwargs entries take priority for duplicate keys. 

652 mapping.update(kwargs) 

653 else: 

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

655 raise TypeError( 

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

657 % ( 

658 self.__class__.__name__, 

659 mapping, 

660 ) 

661 ) 

662 params = {} 

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

664 # coerced. 

665 marshal = self._meta.marshal 

666 try: 

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

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

669 if pb_type is None: 

670 if ignore_unknown_fields: 

671 continue 

672 

673 raise ValueError( 

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

675 ) 

676 

677 pb_value = marshal.to_proto(pb_type, value) 

678 

679 if pb_value is not None: 

680 params[key] = pb_value 

681 

682 # Create the internal protocol buffer. 

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

684 except TypeError as e: 

685 raise TypeError( 

686 "Failed to initialize {}. Underlying error: {}".format( 

687 self.__class__.__name__, e 

688 ) 

689 ) from e 

690 

691 def _get_pb_type_from_key(self, key): 

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

693 

694 Args: 

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

696 

697 Returns: 

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

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

700 If no corresponding field exists, return None. 

701 """ 

702 

703 pb_type = None 

704 

705 try: 

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

707 except KeyError: 

708 # Underscores may be appended to field names 

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

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

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

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

713 # See related issue 

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

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

716 key = f"{key}_" 

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

718 

719 return (key, pb_type) 

720 

721 def __dir__(self): 

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

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

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

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

726 names.update(dir(object())) 

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

728 # the special methods than manually listing them. 

729 names.update( 

730 ( 

731 "__bool__", 

732 "__contains__", 

733 "__dict__", 

734 "__getattr__", 

735 "__getstate__", 

736 "__module__", 

737 "__setstate__", 

738 "__weakref__", 

739 ) 

740 ) 

741 

742 return names 

743 

744 def __bool__(self): 

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

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

747 

748 def __contains__(self, key): 

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

750 

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

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

753 value, so explicitly calling this is not useful. 

754 

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

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

757 distinguish between an explicitly provided empty message and the 

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

759 

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

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

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

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

764 accommodate cases not automatically covered. 

765 

766 Args: 

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

768 

769 Returns: 

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

771 wire serialization. 

772 """ 

773 pb_value = getattr(self._pb, key) 

774 try: 

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

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

777 # against any repeated field or primitive. 

778 # 

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

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

781 # somewhat inefficient try/except. 

782 return self._pb.HasField(key) 

783 except ValueError: 

784 return bool(pb_value) 

785 

786 def __delattr__(self, key): 

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

788 

789 This is generally equivalent to setting a falsy value. 

790 """ 

791 self._pb.ClearField(key) 

792 

793 def __eq__(self, other): 

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

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

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

797 return self._pb == other._pb 

798 

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

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

801 return self._pb == other 

802 

803 # Ask the other object. 

804 return NotImplemented 

805 

806 def __getattr__(self, key): 

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

808 

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

810 sufficient for it to always be "present". 

811 

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

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

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

815 sequence. 

816 

817 For messages, protocol buffers does distinguish between an empty 

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

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

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

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

822 

823 .. note:: 

824 

825 Some well-known protocol buffer types 

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

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

828 more details. 

829 """ 

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

831 if pb_type is None: 

832 raise AttributeError( 

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

834 ) 

835 pb_value = getattr(self._pb, key) 

836 marshal = self._meta.marshal 

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

838 

839 def __ne__(self, other): 

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

841 return not self == other 

842 

843 def __repr__(self): 

844 return repr(self._pb) 

845 

846 def __setattr__(self, key, value): 

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

848 

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

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

851 """ 

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

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

854 marshal = self._meta.marshal 

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

856 if pb_type is None: 

857 raise AttributeError( 

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

859 ) 

860 

861 try: 

862 pb_value = marshal.to_proto(pb_type, value) 

863 

864 # Clear the existing field. 

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

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

867 self._pb.ClearField(key) 

868 

869 # Merge in the value being set. 

870 if pb_value is not None: 

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

872 except TypeError as e: 

873 raise TypeError( 

874 "Failed to set field '{}' on {} to value {}. Underlying error: {}".format( 

875 key, self.__class__.__name__, value, e 

876 ) 

877 ) from e 

878 

879 def __getstate__(self): 

880 """Serialize for pickling.""" 

881 return self._pb.SerializeToString() 

882 

883 def __setstate__(self, value): 

884 """Deserialization for pickling.""" 

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

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

887 

888 

889class _MessageInfo: 

890 """Metadata about a message. 

891 

892 Args: 

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

894 package (str): The proto package. 

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

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

897 file containing this message. 

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

899 automatically registered. 

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

901 set on the message. 

902 """ 

903 

904 def __init__( 

905 self, 

906 *, 

907 fields: List[Field], 

908 package: str, 

909 full_name: str, 

910 marshal: Marshal, 

911 options: descriptor_pb2.MessageOptions, 

912 ) -> None: 

913 self.package = package 

914 self.full_name = full_name 

915 self.options = options 

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

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

918 self.marshal = marshal 

919 self._pb = None 

920 

921 @property 

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

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

924 

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

926 loaded, then this method returns None. 

927 """ 

928 return self._pb 

929 

930 

931def _message_to_map( 

932 cls, 

933 map_fn, 

934 instance, 

935 *, 

936 including_default_value_fields=None, 

937 always_print_fields_with_no_presence=None, 

938 float_precision=None, 

939 **kwargs, 

940): 

941 """ 

942 Helper for logic for Message.to_dict and Message.to_json 

943 """ 

944 

945 # The `including_default_value_fields` argument was removed from protobuf 5.x 

946 # and replaced with `always_print_fields_with_no_presence` which is similar but 

947 # handles optional fields consistently by not affecting them. 

948 # The old flag accidentally had inconsistent behavior between optional fields 

949 # in proto2 and proto3. 

950 print_fields = cls._normalize_print_fields_without_presence( 

951 always_print_fields_with_no_presence, including_default_value_fields 

952 ) 

953 if _PROTOBUF_MAJOR_VERSION in ("3", "4"): 

954 kwargs["including_default_value_fields"] = print_fields 

955 else: 

956 kwargs["always_print_fields_with_no_presence"] = print_fields 

957 

958 if float_precision: 

959 # float_precision removed in protobuf 7 

960 if _PROTOBUF_MAJOR_VERSION in ("3", "4", "5", "6"): 

961 kwargs["float_precision"] = float_precision 

962 warning_msg = "`float_precision` will be removed in Protobuf 7.x." 

963 

964 else: # pragma: NO COVER 

965 warning_msg = ( 

966 "`float_precision` was removed in Protobuf 7.x+, and will be ignored." 

967 ) 

968 warnings.warn(warning_msg, DeprecationWarning, stacklevel=3) 

969 # suppress similar float_precision warning from protobuf library. 

970 with warnings.catch_warnings(): 

971 warnings.filterwarnings("ignore", message=".*float_precision.*") 

972 return map_fn(cls.pb(instance), **kwargs) 

973 

974 

975__all__ = ("Message",)