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

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

278 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 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_upb = has_upb() # Important to cache result here. 

40 

41 

42class MessageMeta(type): 

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

44 

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

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

47 if not bases: 

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

49 

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

51 # this component belongs within the file. 

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

53 

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

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

56 

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

58 # inside a function local scope; correct this. 

59 if "<locals>" in local_path: 

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

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

62 

63 # Determine the full name in protocol buffers. 

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

65 

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

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

68 # Decompose each map into its constituent form. 

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

70 map_fields = {} 

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

72 if not isinstance(field, MapField): 

73 continue 

74 

75 # Determine the name of the entry message. 

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

77 pascal_key=re.sub( 

78 r"_\w", 

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

80 key, 

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

82 ) 

83 

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

85 # 

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

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

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

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

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

91 # the descriptor because reasons). 

92 entry_attrs = collections.OrderedDict( 

93 { 

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

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

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

97 name=msg_name, 

98 ), 

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

100 } 

101 ) 

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

103 entry_attrs["value"] = Field( 

104 field.proto_type, 

105 number=2, 

106 enum=field.enum, 

107 message=field.message, 

108 ) 

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

110 

111 # Create the repeated field for the entry message. 

112 map_fields[key] = RepeatedField( 

113 ProtoType.MESSAGE, 

114 number=field.number, 

115 message=map_fields[msg_name], 

116 ) 

117 

118 # Add the new entries to the attrs 

119 attrs.update(map_fields) 

120 

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

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

123 # their own sequence. 

124 fields = [] 

125 new_attrs = {} 

126 oneofs = collections.OrderedDict() 

127 proto_imports = set() 

128 index = 0 

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

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

131 if not isinstance(field, Field): 

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

133 new_attrs[key] = field 

134 continue 

135 

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

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

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

139 field.mcls_data = { 

140 "name": key, 

141 "parent_name": full_name, 

142 "index": index, 

143 "package": package, 

144 } 

145 

146 # Add the field to the list of fields. 

147 fields.append(field) 

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

149 # is represented. 

150 if field.oneof: 

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

152 # that index to the field's descriptor. 

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

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

155 

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

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

158 # construct our file descriptor proto). 

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

160 field_msg = field.message 

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

162 field_msg = field_msg.pb() 

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

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

165 # descriptor proto has not yet been generated. 

166 # 

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

168 # correctly when the file descriptor is created later. 

169 if field_msg: 

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

171 

172 # Same thing, but for enums. 

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

174 field_enum = ( 

175 field.enum._meta.pb 

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

177 else field.enum.DESCRIPTOR 

178 ) 

179 

180 if field_enum: 

181 proto_imports.add(field_enum.file.name) 

182 

183 # Increment the field index counter. 

184 index += 1 

185 

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

187 # 'real' oneofs. 

188 opt_attrs = {} 

189 for field in fields: 

190 if field.optional: 

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

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

193 opt_attrs[field.name] = field.name 

194 

195 # Generating a metaclass dynamically provides class attributes that 

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

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

198 # 

199 # class MyMessage(proto.Message): 

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

201 # 

202 # m = MyMessage() 

203 # MyMessage.field in m 

204 if opt_attrs: 

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

206 

207 # Determine the filename. 

208 # We determine an appropriate proto filename based on the 

209 # Python module. 

210 filename = _file_info._FileInfo.proto_file_name( 

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

212 ) 

213 

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

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

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

217 

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

219 # descriptor proto being created. 

220 for proto_import in proto_imports: 

221 if proto_import not in file_info.descriptor.dependency: 

222 file_info.descriptor.dependency.append(proto_import) 

223 

224 # Retrieve any message options. 

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

226 

227 # Create the underlying proto descriptor. 

228 desc = descriptor_pb2.DescriptorProto( 

229 name=name, 

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

231 oneof_decl=[ 

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

233 ], 

234 options=opts, 

235 ) 

236 

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

238 # attached as nested types here. 

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

240 for child_path in child_paths: 

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

242 

243 # Same thing, but for enums 

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

245 for child_path in child_paths: 

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

247 

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

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

250 if len(local_path) == 1: 

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

252 else: 

253 file_info.nested[local_path] = desc 

254 

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

256 new_attrs["_meta"] = _MessageInfo( 

257 fields=fields, 

258 full_name=full_name, 

259 marshal=marshal, 

260 options=opts, 

261 package=package, 

262 ) 

263 

264 # Run the superclass constructor. 

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

266 

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

268 cls._meta.parent = cls 

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

270 field.parent = cls 

271 

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

273 # associate the descriptor with the message once the descriptor 

274 # is generated. 

275 file_info.messages[full_name] = cls 

276 

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

278 if file_info.ready(new_class=cls): 

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

280 

281 # Done; return the class. 

282 return cls 

283 

284 @classmethod 

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

286 return collections.OrderedDict() 

287 

288 @property 

289 def meta(cls): 

290 return cls._meta 

291 

292 def __dir__(self): 

293 try: 

294 names = set(dir(type)) 

295 names.update( 

296 ( 

297 "meta", 

298 "pb", 

299 "wrap", 

300 "serialize", 

301 "deserialize", 

302 "to_json", 

303 "from_json", 

304 "to_dict", 

305 "copy_from", 

306 ) 

307 ) 

308 desc = self.pb().DESCRIPTOR 

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

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

311 

312 return names 

313 except AttributeError: 

314 return dir(type) 

315 

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

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

318 

319 Args: 

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

321 underlying protobuf instance. 

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

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

324 """ 

325 if obj is None: 

326 return cls.meta.pb 

327 if not isinstance(obj, cls): 

328 if coerce: 

329 obj = cls(obj) 

330 else: 

331 raise TypeError( 

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

333 % ( 

334 obj, 

335 cls.__name__, 

336 ) 

337 ) 

338 return obj._pb 

339 

340 def wrap(cls, pb): 

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

342 

343 Args: 

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

345 :meth:`pb`. 

346 """ 

347 # Optimized fast path. 

348 instance = cls.__new__(cls) 

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

350 return instance 

351 

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

353 """Return the serialized proto. 

354 

355 Args: 

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

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

358 

359 Returns: 

360 bytes: The serialized representation of the protocol buffer. 

361 """ 

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

363 

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

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

366 

367 Args: 

368 payload (bytes): The serialized proto. 

369 

370 Returns: 

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

372 method was called. 

373 """ 

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

375 

376 def _warn_if_including_default_value_fields_is_used_protobuf_5( 

377 cls, including_default_value_fields: Optional[bool] 

378 ) -> None: 

379 """ 

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

381 

382 Args: 

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

384 """ 

385 if ( 

386 PROTOBUF_VERSION[0] not in ("3", "4") 

387 and including_default_value_fields is not None 

388 ): 

389 warnings.warn( 

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

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

392 """, 

393 DeprecationWarning, 

394 ) 

395 

396 def _raise_if_print_fields_values_are_set_and_differ( 

397 cls, 

398 always_print_fields_with_no_presence: Optional[bool], 

399 including_default_value_fields: Optional[bool], 

400 ) -> None: 

401 """ 

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

403 and the values differ. 

404 

405 Args: 

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

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

408 Returns: 

409 None 

410 Raises: 

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

412 the values differ. 

413 """ 

414 if ( 

415 always_print_fields_with_no_presence is not None 

416 and including_default_value_fields is not None 

417 and always_print_fields_with_no_presence != including_default_value_fields 

418 ): 

419 raise ValueError( 

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

421 ) 

422 

423 def _normalize_print_fields_without_presence( 

424 cls, 

425 always_print_fields_with_no_presence: Optional[bool], 

426 including_default_value_fields: Optional[bool], 

427 ) -> bool: 

428 """ 

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

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

431 when both `always_print_fields_with_no_presence` and 

432 `including_default_value_fields` are not set 

433 

434 Args: 

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

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

437 Returns: 

438 None 

439 Raises: 

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

441 the values differ. 

442 """ 

443 

444 cls._warn_if_including_default_value_fields_is_used_protobuf_5( 

445 including_default_value_fields 

446 ) 

447 cls._raise_if_print_fields_values_are_set_and_differ( 

448 always_print_fields_with_no_presence, including_default_value_fields 

449 ) 

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

451 return ( 

452 ( 

453 always_print_fields_with_no_presence is None 

454 and including_default_value_fields is None 

455 ) 

456 or always_print_fields_with_no_presence 

457 or including_default_value_fields 

458 ) 

459 

460 def to_json( 

461 cls, 

462 instance, 

463 *, 

464 use_integers_for_enums=True, 

465 including_default_value_fields=None, 

466 preserving_proto_field_name=False, 

467 sort_keys=False, 

468 indent=2, 

469 float_precision=None, 

470 always_print_fields_with_no_presence=None, 

471 ) -> str: 

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

473 

474 Args: 

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

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

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

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

479 Default is True. 

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

481 `always_print_fields_with_no_presence` instead. An option that 

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

483 This value must match `always_print_fields_with_no_presence`, 

484 if both arguments are explictly set. 

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

486 determines whether field name representations preserve 

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

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

489 Default is False. 

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

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

492 Pass None for the most compact representation without newlines. 

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

494 Default is None. 

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

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

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

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

499 This value must match `including_default_value_fields`, 

500 if both arguments are explictly set. 

501 Returns: 

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

503 """ 

504 

505 print_fields = cls._normalize_print_fields_without_presence( 

506 always_print_fields_with_no_presence, including_default_value_fields 

507 ) 

508 

509 if PROTOBUF_VERSION[0] in ("3", "4"): 

510 return MessageToJson( 

511 cls.pb(instance), 

512 use_integers_for_enums=use_integers_for_enums, 

513 including_default_value_fields=print_fields, 

514 preserving_proto_field_name=preserving_proto_field_name, 

515 sort_keys=sort_keys, 

516 indent=indent, 

517 float_precision=float_precision, 

518 ) 

519 else: 

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

521 # and replaced with `always_print_fields_with_no_presence` which very similar but has 

522 # handles optional fields consistently by not affecting them. 

523 # The old flag accidentally had inconsistent behavior between proto2 

524 # optional and proto3 optional fields. 

525 return MessageToJson( 

526 cls.pb(instance), 

527 use_integers_for_enums=use_integers_for_enums, 

528 always_print_fields_with_no_presence=print_fields, 

529 preserving_proto_field_name=preserving_proto_field_name, 

530 sort_keys=sort_keys, 

531 indent=indent, 

532 float_precision=float_precision, 

533 ) 

534 

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

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

537 parse it into a message. 

538 

539 Args: 

540 paylod: A json string representing a message. 

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

542 for unknown fields. 

543 

544 Returns: 

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

546 method was called. 

547 """ 

548 instance = cls() 

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

550 return instance 

551 

552 def to_dict( 

553 cls, 

554 instance, 

555 *, 

556 use_integers_for_enums=True, 

557 preserving_proto_field_name=True, 

558 including_default_value_fields=None, 

559 float_precision=None, 

560 always_print_fields_with_no_presence=None, 

561 ) -> "Message": 

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

563 

564 Args: 

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

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

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

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

569 Default is True. 

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

571 determines whether field name representations preserve 

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

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

574 `always_print_fields_with_no_presence` instead. An option that 

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

576 This value must match `always_print_fields_with_no_presence`, 

577 if both arguments are explictly set. 

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

579 Default is None. 

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

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

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

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

584 must match `including_default_value_fields`, if both arguments are explictly set. 

585 

586 Returns: 

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

588 Messages and map fields are represented as dicts, 

589 repeated fields are represented as lists. 

590 """ 

591 

592 print_fields = cls._normalize_print_fields_without_presence( 

593 always_print_fields_with_no_presence, including_default_value_fields 

594 ) 

595 

596 if PROTOBUF_VERSION[0] in ("3", "4"): 

597 return MessageToDict( 

598 cls.pb(instance), 

599 including_default_value_fields=print_fields, 

600 preserving_proto_field_name=preserving_proto_field_name, 

601 use_integers_for_enums=use_integers_for_enums, 

602 float_precision=float_precision, 

603 ) 

604 else: 

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

606 # and replaced with `always_print_fields_with_no_presence` which very similar but has 

607 # handles optional fields consistently by not affecting them. 

608 # The old flag accidentally had inconsistent behavior between proto2 

609 # optional and proto3 optional fields. 

610 return MessageToDict( 

611 cls.pb(instance), 

612 always_print_fields_with_no_presence=print_fields, 

613 preserving_proto_field_name=preserving_proto_field_name, 

614 use_integers_for_enums=use_integers_for_enums, 

615 float_precision=float_precision, 

616 ) 

617 

618 def copy_from(cls, instance, other): 

619 """Equivalent for protobuf.Message.CopyFrom 

620 

621 Args: 

622 instance: An instance of this message type 

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

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

625 """ 

626 if isinstance(other, cls): 

627 # Just want the underlying proto. 

628 other = Message.pb(other) 

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

630 # Don't need to do anything. 

631 pass 

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

633 # Coerce into a proto 

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

635 else: 

636 raise TypeError( 

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

638 cls.__name__, other.__class__.__name__ 

639 ) 

640 ) 

641 

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

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

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

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

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

647 

648 

649class Message(metaclass=MessageMeta): 

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

651 

652 Args: 

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

654 used to determine the values for this message. 

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

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

657 are keyword parameters. 

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

659 message. 

660 """ 

661 

662 def __init__( 

663 self, 

664 mapping=None, 

665 *, 

666 ignore_unknown_fields=False, 

667 **kwargs, 

668 ): 

669 # We accept several things for `mapping`: 

670 # * An instance of this class. 

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

672 # * A dict 

673 # * Nothing (keyword arguments only). 

674 if mapping is None: 

675 if not kwargs: 

676 # Special fast path for empty construction. 

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

678 return 

679 

680 mapping = kwargs 

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

682 # Make a copy of the mapping. 

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

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

685 # passed in. 

686 # 

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

688 # ownership of the passed in protobuf object. 

689 mapping = copy.deepcopy(mapping) 

690 if kwargs: 

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

692 

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

694 return 

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

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

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

698 return 

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

700 # Can't have side effects on mapping. 

701 mapping = copy.copy(mapping) 

702 # kwargs entries take priority for duplicate keys. 

703 mapping.update(kwargs) 

704 else: 

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

706 raise TypeError( 

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

708 % ( 

709 self.__class__.__name__, 

710 mapping, 

711 ) 

712 ) 

713 

714 params = {} 

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

716 # coerced. 

717 marshal = self._meta.marshal 

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

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

720 if pb_type is None: 

721 if ignore_unknown_fields: 

722 continue 

723 

724 raise ValueError( 

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

726 ) 

727 

728 try: 

729 pb_value = marshal.to_proto(pb_type, value) 

730 except ValueError: 

731 # Underscores may be appended to field names 

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

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

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

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

736 # See related issue 

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

738 if isinstance(value, dict): 

739 if _upb: 

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

741 keys_to_update = [ 

742 item 

743 for item in value 

744 if item not in pb_type.DESCRIPTOR.fields_by_name 

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

746 ] 

747 else: 

748 keys_to_update = [ 

749 item 

750 for item in value 

751 if not hasattr(pb_type, item) 

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

753 ] 

754 for item in keys_to_update: 

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

756 

757 pb_value = marshal.to_proto(pb_type, value) 

758 

759 if pb_value is not None: 

760 params[key] = pb_value 

761 

762 # Create the internal protocol buffer. 

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

764 

765 def _get_pb_type_from_key(self, key): 

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

767 

768 Args: 

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

770 

771 Returns: 

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

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

774 If no corresponding field exists, return None. 

775 """ 

776 

777 pb_type = None 

778 

779 try: 

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

781 except KeyError: 

782 # Underscores may be appended to field names 

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

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

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

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

787 # See related issue 

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

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

790 key = f"{key}_" 

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

792 

793 return (key, pb_type) 

794 

795 def __dir__(self): 

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

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

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

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

800 names.update(dir(object())) 

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

802 # the special methods than manually listing them. 

803 names.update( 

804 ( 

805 "__bool__", 

806 "__contains__", 

807 "__dict__", 

808 "__getattr__", 

809 "__getstate__", 

810 "__module__", 

811 "__setstate__", 

812 "__weakref__", 

813 ) 

814 ) 

815 

816 return names 

817 

818 def __bool__(self): 

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

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

821 

822 def __contains__(self, key): 

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

824 

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

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

827 value, so explicitly calling this is not useful. 

828 

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

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

831 distinguish between an explicitly provided empty message and the 

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

833 

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

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

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

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

838 accommodate cases not automatically covered. 

839 

840 Args: 

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

842 

843 Returns: 

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

845 wire serialization. 

846 """ 

847 pb_value = getattr(self._pb, key) 

848 try: 

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

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

851 # against any repeated field or primitive. 

852 # 

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

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

855 # somewhat inefficient try/except. 

856 return self._pb.HasField(key) 

857 except ValueError: 

858 return bool(pb_value) 

859 

860 def __delattr__(self, key): 

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

862 

863 This is generally equivalent to setting a falsy value. 

864 """ 

865 self._pb.ClearField(key) 

866 

867 def __eq__(self, other): 

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

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

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

871 return self._pb == other._pb 

872 

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

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

875 return self._pb == other 

876 

877 # Ask the other object. 

878 return NotImplemented 

879 

880 def __getattr__(self, key): 

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

882 

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

884 sufficient for it to always be "present". 

885 

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

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

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

889 sequence. 

890 

891 For messages, protocol buffers does distinguish between an empty 

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

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

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

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

896 

897 .. note:: 

898 

899 Some well-known protocol buffer types 

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

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

902 more details. 

903 """ 

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

905 if pb_type is None: 

906 raise AttributeError( 

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

908 ) 

909 pb_value = getattr(self._pb, key) 

910 marshal = self._meta.marshal 

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

912 

913 def __ne__(self, other): 

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

915 return not self == other 

916 

917 def __repr__(self): 

918 return repr(self._pb) 

919 

920 def __setattr__(self, key, value): 

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

922 

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

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

925 """ 

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

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

928 marshal = self._meta.marshal 

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

930 if pb_type is None: 

931 raise AttributeError( 

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

933 ) 

934 

935 pb_value = marshal.to_proto(pb_type, value) 

936 

937 # Clear the existing field. 

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

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

940 self._pb.ClearField(key) 

941 

942 # Merge in the value being set. 

943 if pb_value is not None: 

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

945 

946 def __getstate__(self): 

947 """Serialize for pickling.""" 

948 return self._pb.SerializeToString() 

949 

950 def __setstate__(self, value): 

951 """Deserialization for pickling.""" 

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

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

954 

955 

956class _MessageInfo: 

957 """Metadata about a message. 

958 

959 Args: 

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

961 package (str): The proto package. 

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

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

964 file containing this message. 

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

966 automatically registered. 

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

968 set on the message. 

969 """ 

970 

971 def __init__( 

972 self, 

973 *, 

974 fields: List[Field], 

975 package: str, 

976 full_name: str, 

977 marshal: Marshal, 

978 options: descriptor_pb2.MessageOptions, 

979 ) -> None: 

980 self.package = package 

981 self.full_name = full_name 

982 self.options = options 

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

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

985 self.marshal = marshal 

986 self._pb = None 

987 

988 @property 

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

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

991 

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

993 loaded, then this method returns None. 

994 """ 

995 return self._pb 

996 

997 

998__all__ = ("Message",)