| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- # Copyright (c) 2025-2026 Buf Technologies, Inc.
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- from __future__ import annotations
- from dataclasses import dataclass
- from typing import TYPE_CHECKING, Any
- from ._descriptors import (
- DescEnum,
- DescFieldValueEnum,
- DescFieldValueList,
- DescFieldValueMap,
- DescFieldValueMessage,
- DescFieldValueScalar,
- DescMessage,
- ScalarType,
- )
- from ._typing import assert_never
- from ._wire._binary_writer import BinaryWriter
- from ._wire._wire_type import WireType
- if TYPE_CHECKING:
- from ._message import Message
- @dataclass(slots=True, frozen=True)
- class ToBinaryOptions:
- """Options to control the behavior of to_binary.
- Args:
- write_unknown_fields: If `True`, unknown fields are written to the output.
- """
- write_unknown_fields: bool
- def _scalar_wire_type(scalar_type: ScalarType) -> WireType:
- match scalar_type:
- case ScalarType.FIXED64 | ScalarType.SFIXED64 | ScalarType.DOUBLE:
- return WireType.BIT64
- case ScalarType.FIXED32 | ScalarType.SFIXED32 | ScalarType.FLOAT:
- return WireType.BIT32
- case ScalarType.STRING | ScalarType.BYTES:
- return WireType.LENGTH_DELIMITED
- case _:
- return WireType.VARINT
- # Dispatch table for writing scalar values. CPython currently does not generate
- # jump tables for ``match`` statements, and it is still fairly simple to use this
- # table instead.
- # https://github.com/python/cpython/issues/88449
- _SCALAR_WRITERS = (
- None, # 0: unused
- BinaryWriter.double, # 1: DOUBLE
- BinaryWriter.float_, # 2: FLOAT
- BinaryWriter.int64, # 3: INT64
- BinaryWriter.uint64, # 4: UINT64
- BinaryWriter.int32, # 5: INT32
- BinaryWriter.fixed64, # 6: FIXED64
- BinaryWriter.fixed32, # 7: FIXED32
- BinaryWriter.bool_, # 8: BOOL
- BinaryWriter.string, # 9: STRING
- None, # 10: GROUP
- None, # 11: MESSAGE
- BinaryWriter.bytes_, # 12: BYTES
- BinaryWriter.uint32, # 13: UINT32
- None, # 14: ENUM
- BinaryWriter.sfixed32, # 15: SFIXED32
- BinaryWriter.sfixed64, # 16: SFIXED64
- BinaryWriter.sint32, # 17: SINT32
- BinaryWriter.sint64, # 18: SINT64
- )
- def _write_scalar(scalar_type: ScalarType, value: Any, writer: BinaryWriter) -> None:
- writer_method = _SCALAR_WRITERS[scalar_type.value]
- assert writer_method is not None # noqa: S101
- writer_method(writer, value)
- def write_scalar_field(
- number: int, scalar_type: ScalarType, value: Any, writer: BinaryWriter
- ) -> None:
- writer.tag(number, _scalar_wire_type(scalar_type))
- _write_scalar(scalar_type, value, writer)
- def write_message_field(
- number: int,
- value: Message,
- writer: BinaryWriter,
- opts: ToBinaryOptions,
- *,
- delimited_encoding: bool,
- ) -> None:
- if delimited_encoding:
- writer.tag(number, WireType.SGROUP)
- write_message(value, writer, opts)
- writer.tag(number, WireType.EGROUP)
- else:
- writer.tag(number, WireType.LENGTH_DELIMITED)
- writer.fork()
- write_message(value, writer, opts)
- writer.join()
- def write_list_field(
- field_number: int,
- field_value: DescFieldValueList,
- value: list,
- writer: BinaryWriter,
- opts: ToBinaryOptions,
- ) -> None:
- element_type = field_value.element
- if field_value.packed:
- element_type = (
- element_type if isinstance(element_type, ScalarType) else ScalarType.INT32
- )
- writer.tag(field_number, WireType.LENGTH_DELIMITED)
- writer.fork()
- for v in value:
- _write_scalar(element_type, v, writer)
- writer.join()
- return
- for v in value:
- match element_type:
- case ScalarType():
- write_scalar_field(field_number, element_type, v, writer)
- case DescEnum():
- write_scalar_field(field_number, ScalarType.INT32, v, writer)
- case DescMessage():
- write_message_field(
- field_number,
- v,
- writer,
- opts,
- delimited_encoding=field_value.delimited_encoding,
- )
- case _:
- assert_never(element_type)
- def write_map_field(
- number: int,
- field_value: DescFieldValueMap,
- map_: dict,
- writer: BinaryWriter,
- opts: ToBinaryOptions,
- ) -> None:
- for key, value in map_.items():
- writer.tag(number, WireType.LENGTH_DELIMITED)
- writer.fork()
- write_scalar_field(1, field_value.key, key, writer)
- match field_value.value:
- case ScalarType() as scalar_type:
- write_scalar_field(2, scalar_type, value, writer)
- case DescEnum():
- write_scalar_field(2, ScalarType.INT32, value, writer)
- case DescMessage():
- write_message_field(2, value, writer, opts, delimited_encoding=False)
- case _:
- assert_never(field_value.value)
- writer.join()
- def write_message(
- message: Message, writer: BinaryWriter, opts: ToBinaryOptions
- ) -> None:
- for desc_field in message:
- value = message._get_member(desc_field)
- match field_value := desc_field.value:
- case DescFieldValueScalar():
- write_scalar_field(desc_field.number, field_value.scalar, value, writer)
- case DescFieldValueMessage():
- write_message_field(
- desc_field.number,
- value,
- writer,
- opts,
- delimited_encoding=field_value.delimited_encoding,
- )
- case DescFieldValueEnum():
- write_scalar_field(desc_field.number, ScalarType.INT32, value, writer)
- case DescFieldValueList():
- write_list_field(desc_field.number, field_value, value, writer, opts)
- case DescFieldValueMap():
- write_map_field(desc_field.number, field_value, value, writer, opts)
- case _:
- assert_never(field_value)
- # Add unknown fields
- if (uf := message._unknown_fields) and opts.write_unknown_fields:
- for field_bytes_list in uf.values():
- for field_bytes in field_bytes_list:
- writer.raw(field_bytes)
|