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

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

280 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 

19import warnings 

20from typing import Any, Dict, List, Optional, Type 

21 

22import google.protobuf 

23from google.protobuf import descriptor_pb2, message 

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

25 

26from proto import _file_info, _package_info 

27from proto.fields import Field, MapField, RepeatedField 

28from proto.marshal import Marshal 

29from proto.primitives import ProtoType 

30from proto.utils import has_upb 

31 

32PROTOBUF_VERSION = google.protobuf.__version__ 

33 

34# extract the major version code 

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

36 

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

38 

39 

40class MessageMeta(type): 

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

42 

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

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

45 if not bases: 

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

47 

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

49 # this component belongs within the file. 

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

51 

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

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

54 

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

56 # inside a function local scope; correct this. 

57 if "<locals>" in local_path: 

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

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

60 

61 # Determine the full name in protocol buffers. 

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

63 

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

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

66 # Decompose each map into its constituent form. 

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

68 map_fields = {} 

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

70 if not isinstance(field, MapField): 

71 continue 

72 

73 # Determine the name of the entry message. 

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

75 pascal_key=re.sub( 

76 r"_\w", 

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

78 key, 

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

80 ) 

81 

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

83 # 

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

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

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

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

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

89 # the descriptor because reasons). 

90 entry_attrs = collections.OrderedDict( 

91 { 

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

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

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

95 name=msg_name, 

96 ), 

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

98 } 

99 ) 

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

101 entry_attrs["value"] = Field( 

102 field.proto_type, 

103 number=2, 

104 enum=field.enum, 

105 message=field.message, 

106 ) 

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

108 

109 # Create the repeated field for the entry message. 

110 map_fields[key] = RepeatedField( 

111 ProtoType.MESSAGE, 

112 number=field.number, 

113 message=map_fields[msg_name], 

114 ) 

115 

116 # Add the new entries to the attrs 

117 attrs.update(map_fields) 

118 

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

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

121 # their own sequence. 

122 fields = [] 

123 new_attrs = {} 

124 oneofs = collections.OrderedDict() 

125 proto_imports = set() 

126 index = 0 

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

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

129 if not isinstance(field, Field): 

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

131 new_attrs[key] = field 

132 continue 

133 

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

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

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

137 field.mcls_data = { 

138 "name": key, 

139 "parent_name": full_name, 

140 "index": index, 

141 "package": package, 

142 } 

143 

144 # Add the field to the list of fields. 

145 fields.append(field) 

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

147 # is represented. 

148 if field.oneof: 

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

150 # that index to the field's descriptor. 

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

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

153 

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

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

156 # construct our file descriptor proto). 

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

158 field_msg = field.message 

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

160 field_msg = field_msg.pb() 

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

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

163 # descriptor proto has not yet been generated. 

164 # 

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

166 # correctly when the file descriptor is created later. 

167 if field_msg: 

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

169 

170 # Same thing, but for enums. 

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

172 field_enum = ( 

173 field.enum._meta.pb 

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

175 else field.enum.DESCRIPTOR 

176 ) 

177 

178 if field_enum: 

179 proto_imports.add(field_enum.file.name) 

180 

181 # Increment the field index counter. 

182 index += 1 

183 

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

185 # 'real' oneofs. 

186 opt_attrs = {} 

187 for field in fields: 

188 if field.optional: 

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

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

191 opt_attrs[field.name] = field.name 

192 

193 # Generating a metaclass dynamically provides class attributes that 

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

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

196 # 

197 # class MyMessage(proto.Message): 

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

199 # 

200 # m = MyMessage() 

201 # MyMessage.field in m 

202 if opt_attrs: 

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

204 

205 # Determine the filename. 

206 # We determine an appropriate proto filename based on the 

207 # Python module. 

208 filename = _file_info._FileInfo.proto_file_name( 

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

210 ) 

211 

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

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

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

215 

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

217 # descriptor proto being created. 

218 for proto_import in proto_imports: 

219 if proto_import not in file_info.descriptor.dependency: 

220 file_info.descriptor.dependency.append(proto_import) 

221 

222 # Retrieve any message options. 

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

224 

225 # Create the underlying proto descriptor. 

226 desc = descriptor_pb2.DescriptorProto( 

227 name=name, 

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

229 oneof_decl=[ 

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

231 ], 

232 options=opts, 

233 ) 

234 

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

236 # attached as nested types here. 

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

238 for child_path in child_paths: 

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

240 

241 # Same thing, but for enums 

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

243 for child_path in child_paths: 

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

245 

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

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

248 if len(local_path) == 1: 

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

250 else: 

251 file_info.nested[local_path] = desc 

252 

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

254 new_attrs["_meta"] = _MessageInfo( 

255 fields=fields, 

256 full_name=full_name, 

257 marshal=marshal, 

258 options=opts, 

259 package=package, 

260 ) 

261 

262 # Run the superclass constructor. 

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

264 

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

266 cls._meta.parent = cls 

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

268 field.parent = cls 

269 

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

271 # associate the descriptor with the message once the descriptor 

272 # is generated. 

273 file_info.messages[full_name] = cls 

274 

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

276 if file_info.ready(new_class=cls): 

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

278 

279 # Done; return the class. 

280 return cls 

281 

282 @classmethod 

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

284 return collections.OrderedDict() 

285 

286 @property 

287 def meta(cls): 

288 return cls._meta 

289 

290 def __dir__(self): 

291 try: 

292 names = set(dir(type)) 

293 names.update( 

294 ( 

295 "meta", 

296 "pb", 

297 "wrap", 

298 "serialize", 

299 "deserialize", 

300 "to_json", 

301 "from_json", 

302 "to_dict", 

303 "copy_from", 

304 ) 

305 ) 

306 desc = self.pb().DESCRIPTOR 

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

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

309 

310 return names 

311 except AttributeError: 

312 return dir(type) 

313 

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

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

316 

317 Args: 

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

319 underlying protobuf instance. 

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

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

322 """ 

323 if obj is None: 

324 return cls.meta.pb 

325 if not isinstance(obj, cls): 

326 if coerce: 

327 obj = cls(obj) 

328 else: 

329 raise TypeError( 

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

331 % ( 

332 obj, 

333 cls.__name__, 

334 ) 

335 ) 

336 return obj._pb 

337 

338 def wrap(cls, pb): 

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

340 

341 Args: 

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

343 :meth:`pb`. 

344 """ 

345 # Optimized fast path. 

346 instance = cls.__new__(cls) 

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

348 return instance 

349 

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

351 """Return the serialized proto. 

352 

353 Args: 

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

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

356 

357 Returns: 

358 bytes: The serialized representation of the protocol buffer. 

359 """ 

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

361 

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

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

364 

365 Args: 

366 payload (bytes): The serialized proto. 

367 

368 Returns: 

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

370 method was called. 

371 """ 

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

373 

374 def _warn_if_including_default_value_fields_is_used_protobuf_5( 

375 cls, including_default_value_fields: Optional[bool] 

376 ) -> None: 

377 """ 

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

379 

380 Args: 

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

382 """ 

383 if ( 

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

385 and including_default_value_fields is not None 

386 ): 

387 warnings.warn( 

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

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

390 """, 

391 DeprecationWarning, 

392 ) 

393 

394 def _raise_if_print_fields_values_are_set_and_differ( 

395 cls, 

396 always_print_fields_with_no_presence: Optional[bool], 

397 including_default_value_fields: Optional[bool], 

398 ) -> None: 

399 """ 

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

401 and the values differ. 

402 

403 Args: 

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

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

406 Returns: 

407 None 

408 Raises: 

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

410 the values differ. 

411 """ 

412 if ( 

413 always_print_fields_with_no_presence is not None 

414 and including_default_value_fields is not None 

415 and always_print_fields_with_no_presence != including_default_value_fields 

416 ): 

417 raise ValueError( 

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

419 ) 

420 

421 def _normalize_print_fields_without_presence( 

422 cls, 

423 always_print_fields_with_no_presence: Optional[bool], 

424 including_default_value_fields: Optional[bool], 

425 ) -> bool: 

426 """ 

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

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

429 when both `always_print_fields_with_no_presence` and 

430 `including_default_value_fields` are not set 

431 

432 Args: 

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

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

435 Returns: 

436 None 

437 Raises: 

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

439 the values differ. 

440 """ 

441 

442 cls._warn_if_including_default_value_fields_is_used_protobuf_5( 

443 including_default_value_fields 

444 ) 

445 cls._raise_if_print_fields_values_are_set_and_differ( 

446 always_print_fields_with_no_presence, including_default_value_fields 

447 ) 

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

449 return ( 

450 ( 

451 always_print_fields_with_no_presence is None 

452 and including_default_value_fields is None 

453 ) 

454 or always_print_fields_with_no_presence 

455 or including_default_value_fields 

456 ) 

457 

458 def to_json( 

459 cls, 

460 instance, 

461 *, 

462 use_integers_for_enums=True, 

463 including_default_value_fields=None, 

464 preserving_proto_field_name=False, 

465 sort_keys=False, 

466 indent=2, 

467 float_precision=None, 

468 always_print_fields_with_no_presence=None, 

469 ) -> str: 

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

471 

472 Args: 

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

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

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

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

477 Default is True. 

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

479 `always_print_fields_with_no_presence` instead. An option that 

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

481 This value must match `always_print_fields_with_no_presence`, 

482 if both arguments are explicitly set. 

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

484 determines whether field name representations preserve 

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

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

487 Default is False. 

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

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

490 Pass None for the most compact representation without newlines. 

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

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

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

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

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

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

497 This value must match `including_default_value_fields`, 

498 if both arguments are explicitly set. 

499 Returns: 

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

501 """ 

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

503 

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

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

506 parse it into a message. 

507 

508 Args: 

509 payload: A json string representing a message. 

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

511 for unknown fields. 

512 

513 Returns: 

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

515 method was called. 

516 """ 

517 instance = cls() 

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

519 return instance 

520 

521 def to_dict( 

522 cls, 

523 instance, 

524 *, 

525 use_integers_for_enums=True, 

526 preserving_proto_field_name=True, 

527 including_default_value_fields=None, 

528 float_precision=None, 

529 always_print_fields_with_no_presence=None, 

530 ) -> Dict[str, Any]: 

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

532 

533 Args: 

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

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

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

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

538 Default is True. 

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

540 determines whether field name representations preserve 

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

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

543 `always_print_fields_with_no_presence` instead. An option that 

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

545 This value must match `always_print_fields_with_no_presence`, 

546 if both arguments are explicitly set. 

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

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

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

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

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

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

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

554 

555 Returns: 

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

557 Messages and map fields are represented as dicts, 

558 repeated fields are represented as lists. 

559 """ 

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

561 

562 def copy_from(cls, instance, other): 

563 """Equivalent for protobuf.Message.CopyFrom 

564 

565 Args: 

566 instance: An instance of this message type 

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

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

569 """ 

570 if isinstance(other, cls): 

571 # Just want the underlying proto. 

572 other = Message.pb(other) 

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

574 # Don't need to do anything. 

575 pass 

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

577 # Coerce into a proto 

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

579 else: 

580 raise TypeError( 

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

582 cls.__name__, other.__class__.__name__ 

583 ) 

584 ) 

585 

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

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

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

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

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

591 

592 

593class Message(metaclass=MessageMeta): 

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

595 

596 Args: 

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

598 used to determine the values for this message. 

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

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

601 are keyword parameters. 

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

603 message. 

604 """ 

605 

606 def __init__( 

607 self, 

608 mapping=None, 

609 *, 

610 ignore_unknown_fields=False, 

611 **kwargs, 

612 ): 

613 # We accept several things for `mapping`: 

614 # * An instance of this class. 

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

616 # * A dict 

617 # * Nothing (keyword arguments only). 

618 if mapping is None: 

619 if not kwargs: 

620 # Special fast path for empty construction. 

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

622 return 

623 

624 mapping = kwargs 

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

626 # Make a copy of the mapping. 

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

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

629 # passed in. 

630 # 

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

632 # ownership of the passed in protobuf object. 

633 mapping = copy.deepcopy(mapping) 

634 if kwargs: 

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

636 

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

638 return 

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

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

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

642 return 

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

644 # Can't have side effects on mapping. 

645 mapping = copy.copy(mapping) 

646 # kwargs entries take priority for duplicate keys. 

647 mapping.update(kwargs) 

648 else: 

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

650 raise TypeError( 

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

652 % ( 

653 self.__class__.__name__, 

654 mapping, 

655 ) 

656 ) 

657 params = {} 

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

659 # coerced. 

660 marshal = self._meta.marshal 

661 try: 

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

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

664 if pb_type is None: 

665 if ignore_unknown_fields: 

666 continue 

667 

668 raise ValueError( 

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

670 ) 

671 

672 pb_value = marshal.to_proto(pb_type, value) 

673 

674 if pb_value is not None: 

675 params[key] = pb_value 

676 

677 # Create the internal protocol buffer. 

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

679 except TypeError as e: 

680 raise TypeError( 

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

682 self.__class__.__name__, e 

683 ) 

684 ) from e 

685 

686 def _get_pb_type_from_key(self, key): 

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

688 

689 Args: 

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

691 

692 Returns: 

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

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

695 If no corresponding field exists, return None. 

696 """ 

697 

698 pb_type = None 

699 

700 try: 

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

702 except KeyError: 

703 # Underscores may be appended to field names 

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

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

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

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

708 # See related issue 

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

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

711 key = f"{key}_" 

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

713 

714 return (key, pb_type) 

715 

716 def __dir__(self): 

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

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

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

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

721 names.update(dir(object())) 

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

723 # the special methods than manually listing them. 

724 names.update( 

725 ( 

726 "__bool__", 

727 "__contains__", 

728 "__dict__", 

729 "__getattr__", 

730 "__getstate__", 

731 "__module__", 

732 "__setstate__", 

733 "__weakref__", 

734 ) 

735 ) 

736 

737 return names 

738 

739 def __bool__(self): 

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

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

742 

743 def __contains__(self, key): 

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

745 

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

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

748 value, so explicitly calling this is not useful. 

749 

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

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

752 distinguish between an explicitly provided empty message and the 

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

754 

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

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

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

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

759 accommodate cases not automatically covered. 

760 

761 Args: 

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

763 

764 Returns: 

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

766 wire serialization. 

767 """ 

768 pb_value = getattr(self._pb, key) 

769 try: 

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

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

772 # against any repeated field or primitive. 

773 # 

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

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

776 # somewhat inefficient try/except. 

777 return self._pb.HasField(key) 

778 except ValueError: 

779 return bool(pb_value) 

780 

781 def __delattr__(self, key): 

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

783 

784 This is generally equivalent to setting a falsy value. 

785 """ 

786 self._pb.ClearField(key) 

787 

788 def __eq__(self, other): 

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

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

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

792 return self._pb == other._pb 

793 

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

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

796 return self._pb == other 

797 

798 # Ask the other object. 

799 return NotImplemented 

800 

801 def __getattr__(self, key): 

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

803 

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

805 sufficient for it to always be "present". 

806 

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

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

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

810 sequence. 

811 

812 For messages, protocol buffers does distinguish between an empty 

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

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

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

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

817 

818 .. note:: 

819 

820 Some well-known protocol buffer types 

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

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

823 more details. 

824 """ 

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

826 if pb_type is None: 

827 raise AttributeError( 

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

829 ) 

830 pb_value = getattr(self._pb, key) 

831 marshal = self._meta.marshal 

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

833 

834 def __ne__(self, other): 

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

836 return not self == other 

837 

838 def __repr__(self): 

839 return repr(self._pb) 

840 

841 def __setattr__(self, key, value): 

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

843 

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

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

846 """ 

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

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

849 marshal = self._meta.marshal 

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

851 if pb_type is None: 

852 raise AttributeError( 

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

854 ) 

855 

856 try: 

857 pb_value = marshal.to_proto(pb_type, value) 

858 

859 # Clear the existing field. 

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

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

862 self._pb.ClearField(key) 

863 

864 # Merge in the value being set. 

865 if pb_value is not None: 

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

867 except TypeError as e: 

868 raise TypeError( 

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

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

871 ) 

872 ) from e 

873 

874 def __getstate__(self): 

875 """Serialize for pickling.""" 

876 return self._pb.SerializeToString() 

877 

878 def __setstate__(self, value): 

879 """Deserialization for pickling.""" 

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

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

882 

883 

884class _MessageInfo: 

885 """Metadata about a message. 

886 

887 Args: 

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

889 package (str): The proto package. 

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

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

892 file containing this message. 

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

894 automatically registered. 

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

896 set on the message. 

897 """ 

898 

899 def __init__( 

900 self, 

901 *, 

902 fields: List[Field], 

903 package: str, 

904 full_name: str, 

905 marshal: Marshal, 

906 options: descriptor_pb2.MessageOptions, 

907 ) -> None: 

908 self.package = package 

909 self.full_name = full_name 

910 self.options = options 

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

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

913 self.marshal = marshal 

914 self._pb = None 

915 

916 @property 

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

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

919 

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

921 loaded, then this method returns None. 

922 """ 

923 return self._pb 

924 

925 

926def _message_to_map( 

927 cls, 

928 map_fn, 

929 instance, 

930 *, 

931 including_default_value_fields=None, 

932 always_print_fields_with_no_presence=None, 

933 float_precision=None, 

934 **kwargs, 

935): 

936 """ 

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

938 """ 

939 

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

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

942 # handles optional fields consistently by not affecting them. 

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

944 # in proto2 and proto3. 

945 print_fields = cls._normalize_print_fields_without_presence( 

946 always_print_fields_with_no_presence, including_default_value_fields 

947 ) 

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

949 kwargs["including_default_value_fields"] = print_fields 

950 else: 

951 kwargs["always_print_fields_with_no_presence"] = print_fields 

952 

953 if float_precision: 

954 # float_precision removed in protobuf 7 

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

956 kwargs["float_precision"] = float_precision 

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

958 

959 else: # pragma: NO COVER 

960 warning_msg = ( 

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

962 ) 

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

964 # suppress similar float_precision warning from protobuf library. 

965 with warnings.catch_warnings(): 

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

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

968 

969 

970__all__ = ("Message",)