_descriptors.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. # Copyright (c) 2025-2026 Buf Technologies, Inc.
  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. # http://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. from __future__ import annotations
  15. from dataclasses import dataclass, field as dataclassfield
  16. from enum import IntEnum
  17. from typing import TYPE_CHECKING, Literal, TypeAlias, cast, final
  18. from ._typing import assert_never
  19. from ._wire import WireType
  20. try:
  21. from protobuf_ext import initialize_message_type as _initialize_message_type
  22. initialize_message_type = _initialize_message_type
  23. except ImportError:
  24. initialize_message_type = None
  25. if TYPE_CHECKING:
  26. import builtins
  27. from collections.abc import Sequence
  28. from ._enum import Enum
  29. from ._extension import Extension
  30. from ._message import Message
  31. from .wkt._gen.descriptor_pb import (
  32. DescriptorProto,
  33. EnumDescriptorProto,
  34. EnumValueDescriptorProto,
  35. FieldDescriptorProto,
  36. FileDescriptorProto,
  37. MethodDescriptorProto,
  38. MethodOptions,
  39. OneofDescriptorProto,
  40. ServiceDescriptorProto,
  41. )
  42. @final
  43. class ScalarType(IntEnum):
  44. # 0 is reserved for errors.
  45. # Order is weird for historical reasons.
  46. DOUBLE = 1
  47. FLOAT = 2
  48. # Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if
  49. # negative values are likely.
  50. INT64 = 3
  51. UINT64 = 4
  52. # Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if
  53. # negative values are likely.
  54. INT32 = 5
  55. FIXED64 = 6
  56. FIXED32 = 7
  57. BOOL = 8
  58. STRING = 9
  59. # Tag-delimited aggregate.
  60. # Group type is deprecated and not supported in proto3. However, Proto3
  61. # implementations should still be able to parse the group wire format and
  62. # treat group fields as unknown fields.
  63. # TYPE_GROUP = 10, # noqa: ERA001
  64. # TYPE_MESSAGE = 11, # Length-delimited aggregate. # noqa: ERA001
  65. # New in version 2.
  66. BYTES = 12
  67. UINT32 = 13
  68. # TYPE_ENUM = 14, # noqa: ERA001
  69. SFIXED32 = 15
  70. SFIXED64 = 16
  71. SINT32 = 17 # Uses ZigZag encoding.
  72. SINT64 = 18 # Uses ZigZag encoding.
  73. def _scalar_wire_type(scalar_type: ScalarType) -> WireType:
  74. match scalar_type:
  75. case ScalarType.FIXED64 | ScalarType.SFIXED64 | ScalarType.DOUBLE:
  76. return WireType.BIT64
  77. case ScalarType.FIXED32 | ScalarType.SFIXED32 | ScalarType.FLOAT:
  78. return WireType.BIT32
  79. case ScalarType.STRING | ScalarType.BYTES:
  80. return WireType.LENGTH_DELIMITED
  81. case _:
  82. return WireType.VARINT
  83. @final
  84. class SupportedFieldPresence(IntEnum):
  85. EXPLICIT = 1 # FeatureSet.FieldPresence.EXPLICIT
  86. IMPLICIT = 2 # FeatureSet.FieldPresence.IMPLICIT
  87. LEGACY_REQUIRED = 3 # FeatureSet.FieldPresence.LEGACY_REQUIRED
  88. @final
  89. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  90. class DescFile:
  91. """Describes a protobuf source file.
  92. Attributes:
  93. edition: The edition of the protobuf file. Will be EDITION_PROTO2 for
  94. syntax="proto2", EDITION_PROTO3 for syntax="proto3".
  95. name: The name of the protobuf file, for example `foo/bar.proto`.
  96. dependencies: Files imported by this file.
  97. enums: Top-level enumerations declared in this file. Note that more
  98. enumerations might be declared within message declarations.
  99. messages: Top-level messages declared in this file. Note that more
  100. messages might be declared within message declarations.
  101. extensions: Top-level extensions declared in this file. Note that more
  102. extensions might be declared within message declarations.
  103. services: Services declared in this file.
  104. deprecated: Marked as deprecated in the protobuf source.
  105. proto: The compiler-generated descriptor.
  106. """
  107. edition: int
  108. name: str
  109. dependencies: Sequence[DescFile]
  110. enums: Sequence[DescEnum]
  111. messages: Sequence[DescMessage]
  112. extensions: Sequence[DescExtension]
  113. services: Sequence[DescService]
  114. deprecated: bool
  115. proto: FileDescriptorProto
  116. def __str__(self) -> str:
  117. return f"file {self.name}"
  118. @final
  119. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  120. class DescEnum:
  121. """Describes an enumeration in a protobuf source file.
  122. Attributes:
  123. type_name: The fully qualified name of the enumeration. (We omit the leading dot.)
  124. name: The name of the enumeration, as declared in the protobuf source.
  125. file: The file this enumeration was declared in.
  126. parent: The parent message, if this enumeration was declared inside a message
  127. declaration.
  128. open: Enumerations can be open or closed.
  129. See <https://protobuf.dev/programming-guides/enum/>.
  130. values: Values declared for this enumeration.
  131. deprecated: Marked as deprecated in the protobuf source.
  132. proto: The compiler-generated descriptor.
  133. """
  134. type_name: str
  135. name: str
  136. file: DescFile
  137. parent: DescMessage | None
  138. open: bool
  139. values: Sequence[DescEnumValue]
  140. deprecated: bool
  141. proto: EnumDescriptorProto
  142. _values_by_number: dict[int, DescEnumValue] = dataclassfield(
  143. repr=False, compare=False, hash=False
  144. )
  145. """Enum value descriptor mapping from enum number, for lookup during binary parsing."""
  146. _values_by_name: dict[str, DescEnumValue] = dataclassfield(
  147. repr=False, compare=False, hash=False
  148. )
  149. """Enum value descriptor mapping from protobuf source name, for lookup during JSON parsing."""
  150. _local_name: str
  151. """The name of the Python class that represents the enum."""
  152. _local_qualname: str
  153. """The qualified name of the Python class that represents the enum, including parents."""
  154. @property
  155. def type(self) -> builtins.type[Enum]:
  156. """The Python enum class for this descriptor."""
  157. if self._type is not None:
  158. return self._type
  159. _type = _create_enum(self)
  160. object.__setattr__(self, "_type", _type)
  161. return _type
  162. _type: builtins.type[Enum] | None = dataclassfield(
  163. default=None, init=True, repr=False, compare=False, hash=False, kw_only=True
  164. )
  165. def __str__(self) -> str:
  166. return f"enum {self.type_name}"
  167. @final
  168. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  169. class DescEnumValue:
  170. """Describes an individual value of an enumeration in a protobuf source file.
  171. Attributes:
  172. name: The name of the enumeration value, as specified in the protobuf source.
  173. local_name: A safe and idiomatic name for the value in Python.
  174. parent: The enumeration this value belongs to.
  175. number: The numeric enumeration value, as specified in the protobuf source.
  176. deprecated: Marked as deprecated in the protobuf source.
  177. proto: The compiler-generated descriptor.
  178. """
  179. name: str
  180. local_name: str
  181. parent: DescEnum
  182. number: int
  183. deprecated: bool
  184. proto: EnumValueDescriptorProto
  185. def __str__(self) -> str:
  186. return f"enum value {self.parent.type_name}.{self.name}"
  187. @final
  188. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  189. class DescMessage:
  190. """Describes a message declaration in a protobuf source file.
  191. Attributes:
  192. type_name: The fully qualified name of the message. (We omit the leading dot.)
  193. name: The name of the message, as specified in the protobuf source.
  194. file: The file this message was declared in.
  195. parent: The parent message, if this message was declared inside a message
  196. declaration.
  197. fields: Fields declared for this message, including fields declared in a oneof
  198. group.
  199. oneofs: Oneof groups declared for this message. This does not include synthetic
  200. oneofs for proto3 optionals.
  201. members: Standalone fields and oneof groups for this message, ordered by their
  202. appearance in the protobuf source.
  203. nested_enums: Enumerations declared within the message, if any.
  204. nested_messages: Messages declared within the message, if any. This does not
  205. include synthetic messages like map entries.
  206. nested_extensions: Extensions declared within the message, if any.
  207. deprecated: Marked as deprecated in the protobuf source.
  208. proto: The compiler-generated descriptor.
  209. """
  210. type_name: str
  211. name: str
  212. file: DescFile
  213. parent: DescMessage | None
  214. fields: Sequence[DescField]
  215. oneofs: Sequence[DescOneof]
  216. members: Sequence[DescField | DescOneof]
  217. nested_enums: Sequence[DescEnum]
  218. nested_messages: Sequence[DescMessage]
  219. nested_extensions: Sequence[DescExtension]
  220. deprecated: bool
  221. proto: DescriptorProto
  222. # Private attributes eagerly resolvable
  223. _local_name: str = dataclassfield(repr=False, compare=False, hash=False)
  224. """The name of the Python class that represents the message."""
  225. _local_qualname: str = dataclassfield(repr=False, compare=False, hash=False)
  226. # Private attributes populated in finish_init.
  227. _fields_by_local_name: dict[str, DescField] = dataclassfield(
  228. repr=False, compare=False, hash=False, init=False
  229. )
  230. """Field descriptor mapping from local name, for lookup of fields as Python attributes.
  231. Fields within a oneof do not support access as Python attributes.
  232. """
  233. _fields_by_tag: dict[int, DescField] = dataclassfield(
  234. repr=False, compare=False, hash=False, init=False
  235. )
  236. """Field descriptor mapping from field tag, for lookup during binary parsing."""
  237. _fields_by_json_name: dict[str, DescField] = dataclassfield(
  238. repr=False, compare=False, hash=False, init=False
  239. )
  240. """Field descriptor mapping by JSON name (lowerCamelCase if not user-overridden), for lookup during JSON parsing."""
  241. _fields_by_name: dict[str, DescField] = dataclassfield(
  242. repr=False, compare=False, hash=False, init=False
  243. )
  244. """Field descriptor mapping from protobuf source name, for lookup in container emulation and JSON parsing."""
  245. _oneofs_by_local_name: dict[str, DescOneof] = dataclassfield(
  246. repr=False, compare=False, hash=False, init=False
  247. )
  248. """Oneof descriptor mapping from local name, for lookup of oneof groups as Python attributes."""
  249. _oneofs_by_name: dict[str, DescOneof] = dataclassfield(
  250. repr=False, compare=False, hash=False, init=False
  251. )
  252. """Oneof descriptor mapping from protobuf source name, for lookup in container emulation."""
  253. """The qualified name of the Python class that represents the message, including parents."""
  254. _defaults: list[
  255. tuple[str, str | bool | int | float | bytes | list | dict | None]
  256. ] = dataclassfield(repr=False, compare=False, hash=False, init=False)
  257. """Pre-computed defaults for efficient message initialization in the constructor."""
  258. _requires_presence: bool = dataclassfield(
  259. repr=False, compare=False, hash=False, init=False
  260. )
  261. """Whether this message has any fields that require separate presence tracking."""
  262. def _finish_init(self) -> None:
  263. """Finish initialization of private attributes.
  264. Because messages can be recursive, we cannot eagerly initialize a message descriptor,
  265. instead initializing mutable fields and filling them up while traversing the descriptor graph.
  266. Because of this, we cannot use dataclass's `__post_init__` for all initialization, and instead
  267. manually call this when ready. It still allows initialization logic to be encapsulated here
  268. instead of across multiple call sites.
  269. """
  270. from ._field_values import default_value # noqa: PLC0415
  271. fields_by_local_name: dict[str, DescField] = {}
  272. fields_by_tag: dict[int, DescField] = {}
  273. fields_by_json_name: dict[str, DescField] = {}
  274. fields_by_name: dict[str, DescField] = {}
  275. for field in self.fields:
  276. if (
  277. not isinstance(field.value, DescFieldValueSingular)
  278. or field.value.oneof is None
  279. ):
  280. fields_by_local_name[field.local_name] = field
  281. fields_by_tag[field._tag] = field
  282. fields_by_json_name[field.name] = field
  283. fields_by_json_name[field.json_name] = field
  284. fields_by_name[field.name] = field
  285. fields_by_tag[field._tag] = field
  286. if isinstance(field.value, DescFieldValueList) and field.value._packable:
  287. # Allow lookup by both packed and unpacked type.
  288. if field.value.packed:
  289. wire_type = field.value._unpacked_wire_type
  290. else:
  291. wire_type = WireType.LENGTH_DELIMITED
  292. tag = field.number << 3 | wire_type
  293. fields_by_tag[tag] = field
  294. object.__setattr__(self, "_fields_by_local_name", fields_by_local_name)
  295. object.__setattr__(self, "_fields_by_tag", fields_by_tag)
  296. object.__setattr__(self, "_fields_by_json_name", fields_by_json_name)
  297. object.__setattr__(self, "_fields_by_name", fields_by_name)
  298. oneofs_by_local_name: dict[str, DescOneof] = {}
  299. oneofs_by_name: dict[str, DescOneof] = {}
  300. for oneof in self.oneofs:
  301. oneofs_by_local_name[oneof.local_name] = oneof
  302. oneofs_by_name[oneof.name] = oneof
  303. object.__setattr__(self, "_oneofs_by_local_name", oneofs_by_local_name)
  304. object.__setattr__(self, "_oneofs_by_name", oneofs_by_name)
  305. defaults: list[
  306. tuple[str, str | bool | int | float | bytes | list | dict | None]
  307. ] = []
  308. for member in self.members:
  309. if isinstance(member, DescOneof):
  310. defaults.append((member.local_name, None))
  311. else:
  312. defaults.append((member.local_name, default_value(member.value)))
  313. object.__setattr__(self, "_defaults", defaults)
  314. object.__setattr__(
  315. self,
  316. "_requires_presence",
  317. any(
  318. isinstance(member, DescField) and member._requires_presence
  319. for member in self.members
  320. ),
  321. )
  322. if initialize_message_type and self._type is not None:
  323. initialize_message_type(self._type)
  324. @property
  325. def type(self) -> builtins.type[Message]:
  326. """The Python message class for this descriptor."""
  327. if self._type is not None:
  328. return self._type
  329. _type = _create_message(self)
  330. object.__setattr__(self, "_type", _type)
  331. if initialize_message_type:
  332. initialize_message_type(_type)
  333. return _type
  334. _type: builtins.type[Message] | None = dataclassfield(
  335. default=None, init=True, repr=False, compare=False, hash=False, kw_only=True
  336. )
  337. def __str__(self) -> str:
  338. return f"message {self.type_name}"
  339. @final
  340. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  341. class DescFieldValueScalar:
  342. """Describes the value of a scalar field declaration in a protobuf source file.
  343. Attributes:
  344. scalar: The scalar type.
  345. default_value: The default value for the scalar field.
  346. oneof: The `oneof` group this field belongs to, if any. This does not include
  347. synthetic oneofs for proto3 optionals.
  348. """
  349. scalar: ScalarType
  350. default_value: str | bool | int | float | bytes | None
  351. oneof: DescOneof | None
  352. @final
  353. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  354. class DescFieldValueMessage:
  355. """Describes the value of a message field declaration in a protobuf source file.
  356. Attributes:
  357. message: The message type.
  358. delimited_encoding: Encode the message delimited (a.k.a. proto2 group encoding),
  359. or length-prefixed?
  360. oneof: The `oneof` group this field belongs to, if any. This does not include
  361. synthetic oneofs for proto3 optionals.
  362. """
  363. message: DescMessage
  364. delimited_encoding: bool
  365. oneof: DescOneof | None
  366. @final
  367. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  368. class DescFieldValueEnum:
  369. """Describes the value of an enum field declaration in a protobuf source file.
  370. Attributes:
  371. enum: The enum type.
  372. default_value: The default value for the enum field.
  373. oneof: The `oneof` group this field belongs to, if any. This does not include
  374. synthetic oneofs for proto3 optionals.
  375. """
  376. enum: DescEnum
  377. default_value: int | None
  378. oneof: DescOneof | None
  379. @final
  380. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  381. class DescFieldValueMap:
  382. """Describes the value of a map field declaration in a protobuf source file.
  383. Attributes:
  384. key: The scalar map key type.
  385. value: The map value type.
  386. """
  387. key: ScalarType
  388. value: ScalarType | DescMessage | DescEnum
  389. def __post_init__(self) -> None:
  390. object.__setattr__(self, "_key_wire_type", _scalar_wire_type(self.key))
  391. object.__setattr__(
  392. self,
  393. "_value_wire_type",
  394. element_wire_type(self.value, delimited_encoding=False),
  395. )
  396. _key_wire_type: WireType = dataclassfield(
  397. init=False, repr=False, compare=False, hash=False
  398. )
  399. """The wire type for the map key, used to validate during parsing."""
  400. _value_wire_type: WireType = dataclassfield(
  401. init=False, repr=False, compare=False, hash=False
  402. )
  403. """The wire type for the map value, used to validate during parsing."""
  404. @final
  405. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  406. class DescFieldValueList:
  407. """Describes the value of a repeated field declaration in a protobuf source file.
  408. Attributes:
  409. element: The element type of the repeated field.
  410. packed: Pack this repeated field? Only valid for repeated enum fields, and for
  411. repeated scalar fields except BYTES and STRING.
  412. delimited_encoding: Encode the message delimited (a.k.a. proto2 group encoding),
  413. or length-prefixed?
  414. """
  415. element: DescMessage | DescEnum | ScalarType
  416. packed: bool
  417. delimited_encoding: bool
  418. def __post_init__(self) -> None:
  419. if (
  420. isinstance(self.element, ScalarType)
  421. and self.element not in (ScalarType.BYTES, ScalarType.STRING)
  422. ) or (isinstance(self.element, DescEnum)):
  423. object.__setattr__(self, "_packable", True)
  424. else:
  425. object.__setattr__(self, "_packable", False)
  426. object.__setattr__(
  427. self,
  428. "_unpacked_wire_type",
  429. element_wire_type(self.element, delimited_encoding=self.delimited_encoding),
  430. )
  431. _packable: bool = dataclassfield(init=False, repr=False, compare=False, hash=False)
  432. """Whether the field can be represented in packed encoding, used to accept packed or unpacked when parsing."""
  433. _unpacked_wire_type: WireType = dataclassfield(
  434. init=False, repr=False, compare=False, hash=False
  435. )
  436. """The wire type for the unpacked encoding, used to validate when parsing an unpacked repeated field."""
  437. def element_wire_type( # noqa: RET503
  438. element_type: DescMessage | DescEnum | ScalarType, *, delimited_encoding: bool
  439. ) -> WireType:
  440. match element_type:
  441. case ScalarType() as scalar:
  442. return _scalar_wire_type(scalar)
  443. case DescEnum():
  444. return WireType.VARINT
  445. case DescMessage():
  446. return WireType.SGROUP if delimited_encoding else WireType.LENGTH_DELIMITED
  447. case _:
  448. assert_never(element_type)
  449. DescFieldValueSingular: TypeAlias = (
  450. DescFieldValueScalar | DescFieldValueMessage | DescFieldValueEnum
  451. )
  452. """Describes the value of a non-repeated, non-map field declaration in a protobuf source file."""
  453. DescFieldValue: TypeAlias = (
  454. DescFieldValueScalar
  455. | DescFieldValueMessage
  456. | DescFieldValueEnum
  457. | DescFieldValueMap
  458. | DescFieldValueList
  459. )
  460. """Describes the value of a field declaration in a protobuf source file."""
  461. @final
  462. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  463. class DescField:
  464. """Describes a field declaration in a protobuf source file.
  465. Attributes:
  466. name: The field name, as specified in the protobuf source.
  467. value: Description of the value of the field.
  468. parent: The message this field is declared on.
  469. local_name: A safe and idiomatic name for the field as a property in Python.
  470. number: The field number, as specified in the protobuf source.
  471. json_name: The field name in JSON.
  472. deprecated: Marked as deprecated in the protobuf source.
  473. presence: Presence of the field.
  474. See <https://protobuf.dev/programming-guides/field_presence/>.
  475. proto: The compiler-generated descriptor.
  476. """
  477. name: str
  478. value: DescFieldValue
  479. parent: DescMessage
  480. local_name: str
  481. number: int
  482. json_name: str
  483. deprecated: bool
  484. presence: SupportedFieldPresence
  485. proto: FieldDescriptorProto
  486. def __post_init__(self) -> None:
  487. match self.value:
  488. case DescFieldValueScalar(scalar=scalar):
  489. wire_type = _scalar_wire_type(scalar)
  490. case DescFieldValueEnum():
  491. wire_type = WireType.VARINT
  492. case DescFieldValueMessage():
  493. if not self.value.delimited_encoding:
  494. wire_type = WireType.LENGTH_DELIMITED
  495. else:
  496. wire_type = WireType.SGROUP
  497. case DescFieldValueList():
  498. if self.value.packed:
  499. wire_type = WireType.LENGTH_DELIMITED
  500. else:
  501. wire_type = element_wire_type(
  502. self.value.element,
  503. delimited_encoding=self.value.delimited_encoding,
  504. )
  505. case DescFieldValueMap():
  506. wire_type = WireType.LENGTH_DELIMITED
  507. case _:
  508. assert_never(self.value)
  509. object.__setattr__(self, "_tag", self.number << 3 | wire_type)
  510. # Scalar and enum fields with explicit presence need separate
  511. # tracking because their zero value (0, "", False, etc.) is a
  512. # valid user-provided value.
  513. if (
  514. isinstance(self.value, DescFieldValueSingular)
  515. and self.value.oneof is None
  516. and not isinstance(self.value, DescFieldValueMessage)
  517. ):
  518. requires_presence = self.presence != SupportedFieldPresence.IMPLICIT
  519. else:
  520. requires_presence = False
  521. object.__setattr__(self, "_requires_presence", requires_presence)
  522. _tag: int = dataclassfield(repr=False, compare=False, hash=False, init=False)
  523. """The field tag for serialization, which is a combination of the field number and wire type."""
  524. _requires_presence: bool = dataclassfield(
  525. repr=False, compare=False, hash=False, init=False
  526. )
  527. """Whether this field requires separate presence tracking."""
  528. def __str__(self) -> str:
  529. return f"field {self.parent.type_name}.{self.name}"
  530. @final
  531. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  532. class DescExtension:
  533. """Describes an extension in a protobuf source file.
  534. Attributes:
  535. name: The field name, as specified in the protobuf source.
  536. value: Description of the value of the extension field.
  537. type_name: The fully qualified name of the extension.
  538. file: The file this extension was declared in.
  539. parent: The parent message, if this extension was declared inside a message
  540. declaration.
  541. extendee: The message that this extension extends.
  542. number: The field number, as specified in the protobuf source.
  543. json_name: The field name in JSON.
  544. deprecated: Marked as deprecated in the protobuf source.
  545. presence: Presence of the field.
  546. See <https://protobuf.dev/programming-guides/field_presence/>.
  547. proto: The compiler-generated descriptor.
  548. """
  549. name: str
  550. value: (
  551. DescFieldValueScalar
  552. | DescFieldValueMessage
  553. | DescFieldValueEnum
  554. | DescFieldValueList
  555. )
  556. type_name: str
  557. file: DescFile
  558. parent: DescMessage | None
  559. extendee: DescMessage
  560. number: int
  561. json_name: str
  562. deprecated: bool
  563. presence: SupportedFieldPresence
  564. proto: FieldDescriptorProto
  565. @property
  566. def type(self) -> Extension:
  567. """The Python extension for this descriptor."""
  568. if self._type is not None:
  569. return self._type
  570. _type = _create_extension(cast("DescExtension", self))
  571. object.__setattr__(self, "_type", _type)
  572. return _type
  573. _type: Extension | None = dataclassfield(
  574. default=None, init=True, repr=False, compare=False, hash=False, kw_only=True
  575. )
  576. def __str__(self) -> str:
  577. return f"extension {self.type_name}"
  578. @final
  579. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  580. class DescOneof:
  581. """Describes a oneof group in a protobuf source file.
  582. Attributes:
  583. name: The name of the oneof group, as specified in the protobuf source.
  584. local_name: A safe and idiomatic name for the oneof group as a property in
  585. Python.
  586. parent: The message this oneof group was declared in.
  587. fields: The fields declared in this oneof group.
  588. proto: The compiler-generated descriptor.
  589. """
  590. name: str
  591. local_name: str
  592. parent: DescMessage
  593. fields: Sequence[DescField]
  594. proto: OneofDescriptorProto
  595. _fields_by_name: dict[str, DescField] = dataclassfield(
  596. repr=False, compare=False, hash=False
  597. )
  598. """Field descriptor mapping from protobuf source name, for lookup in validation."""
  599. def __str__(self) -> str:
  600. return f"oneof {self.parent.type_name}.{self.name}"
  601. @final
  602. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  603. class DescService:
  604. """Describes a service declaration in a protobuf source file.
  605. Attributes:
  606. type_name: The fully qualified name of the service. (We omit the leading dot.)
  607. name: The name of the service, as specified in the protobuf source.
  608. file: The file this service was declared in.
  609. methods: The RPCs this service declares.
  610. deprecated: Marked as deprecated in the protobuf source.
  611. proto: The compiler-generated descriptor.
  612. """
  613. type_name: str
  614. name: str
  615. file: DescFile
  616. methods: Sequence[DescMethod]
  617. deprecated: bool
  618. proto: ServiceDescriptorProto
  619. def __str__(self) -> str:
  620. return f"service {self.type_name}"
  621. @final
  622. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  623. class DescMethod:
  624. """Describes an RPC declaration in a protobuf source file.
  625. Attributes:
  626. name: The name of the RPC, as specified in the protobuf source.
  627. parent: The parent service.
  628. method_kind: One of the four available method types: "unary", "server_streaming",
  629. "client_streaming", "bidi_streaming".
  630. input: The message type for requests.
  631. output: The message type for responses.
  632. idempotency: The idempotency level declared in the protobuf source, if any.
  633. deprecated: Marked as deprecated in the protobuf source.
  634. proto: The compiler-generated descriptor.
  635. """
  636. name: str
  637. parent: DescService
  638. method_kind: Literal[
  639. "unary", "server_streaming", "client_streaming", "bidi_streaming"
  640. ]
  641. input: DescMessage
  642. output: DescMessage
  643. idempotency: MethodOptions.IdempotencyLevel
  644. deprecated: bool
  645. proto: MethodDescriptorProto
  646. def __str__(self) -> str:
  647. return f"method {self.parent.type_name}.{self.name}"
  648. @final
  649. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  650. class DescComments:
  651. """Comments associated with a protobuf source location.
  652. Attributes:
  653. leading_detached: Paragraphs of comments that appear before (but not
  654. connected to) the element.
  655. leading: Comment appearing before the element, if any.
  656. trailing: Comment appearing after the element, if any.
  657. source_path: The source code info path identifying the element.
  658. """
  659. leading_detached: Sequence[str]
  660. leading: str | None
  661. trailing: str | None
  662. source_path: Sequence[int]
  663. def _get_wkt_mixin(desc: DescMessage) -> type | None:
  664. """Return the WKT mixin class for `desc`, or None if it has no mixin."""
  665. from ._wkt_registry import match_wkt # noqa: PLC0415
  666. wkt = match_wkt(desc)
  667. return wkt.mixin() if wkt is not None else None
  668. def _create_message(desc: DescMessage) -> type[Message]:
  669. """Create a dynamic message from a descriptor."""
  670. from ._message import Message # noqa: PLC0415
  671. slots = [*desc._fields_by_local_name.keys(), *desc._oneofs_by_local_name.keys()]
  672. mixin = _get_wkt_mixin(desc)
  673. bases = (Message,) if mixin is None else (Message, mixin)
  674. dynamic_message = cast(
  675. "type[Message]",
  676. type(desc._local_name, bases, {"__slots__": tuple(slots), "_desc": desc}),
  677. )
  678. dynamic_message.__qualname__ = desc._local_qualname
  679. return dynamic_message
  680. def _create_enum(desc: DescEnum) -> type[Enum]:
  681. """Create a dynamic enum from a descriptor."""
  682. from ._enum import Enum # noqa: PLC0415
  683. members = {value.local_name: value.number for value in desc.values}
  684. dynamic_enum = cast("type[Enum]", Enum(desc._local_name, members))
  685. dynamic_enum._desc = desc # type: ignore[attr-defined]
  686. dynamic_enum.__qualname__ = desc._local_qualname
  687. return dynamic_enum
  688. def _create_extension(desc: DescExtension) -> Extension[Message, object]:
  689. """Create a dynamic extension from a descriptor."""
  690. from ._extension import Extension # noqa: PLC0415
  691. from ._message import Message # noqa: PLC0415
  692. ext = Extension[Message, object]()
  693. ext._desc = desc
  694. return ext
  695. @final
  696. @dataclass(repr=False, eq=False, frozen=True, slots=True)
  697. class DescUnknownField:
  698. """Description of an unknown field in a protobuf message.
  699. When a [Message][] is parsed from data with fields not present in the message definition,
  700. those fields are stored as unknown fields. This class can be initialized and passed to
  701. container methods to access structured data in unknown fields.
  702. Attributes:
  703. number: The field number of the unknown field.
  704. value: Description of the value of the unknown field.
  705. """
  706. number: int
  707. value: DescFieldValue