_struct.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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 collections.abc import Mapping, Sequence
  16. from typing import TYPE_CHECKING, Literal, TypeAlias, TypeVar
  17. if TYPE_CHECKING:
  18. from protobuf import Oneof
  19. from protobuf.wkt import ListValue, NullValue, Struct, Value
  20. ValueTypeParam: TypeAlias = (
  21. None
  22. | bool
  23. | int
  24. | float
  25. | str
  26. | Sequence["ValueTypeParam"]
  27. | Mapping[str, "ValueTypeParam"]
  28. )
  29. ValueTypeReturn: TypeAlias = (
  30. None | bool | float | str | list["ValueTypeReturn"] | dict[str, "ValueTypeReturn"]
  31. )
  32. SelfStruct = TypeVar("SelfStruct", bound="StructMixin")
  33. class StructMixin:
  34. __slots__ = ()
  35. if TYPE_CHECKING:
  36. def __init__(self, *, fields: dict[str, Value] | None = None) -> None:
  37. pass
  38. fields: dict[str, Value]
  39. @classmethod
  40. def from_python(
  41. cls: type[SelfStruct], data: Mapping[str, ValueTypeParam]
  42. ) -> SelfStruct:
  43. """Create a Struct from a Python dict.
  44. Examples:
  45. >>> from protobuf.wkt import Struct
  46. >>> Struct.from_python({"a": 1, "b": "bear"})
  47. Struct(fields={'a': Value(kind=Oneof(field='number_value', value=1.0)), 'b': Value(kind=Oneof(field='string_value', value='bear'))})
  48. """
  49. from protobuf.wkt import Value # noqa: PLC0415
  50. return cls(fields={k: Value.from_python(v) for k, v in data.items()})
  51. def to_python(self) -> dict[str, ValueTypeReturn]:
  52. """Convert this Struct to a Python dict.
  53. Examples:
  54. >>> from protobuf import Oneof
  55. >>> from protobuf.wkt import Struct, Value
  56. >>> Struct(
  57. ... fields={
  58. ... "a": Value(kind=Oneof(field="number_value", value=1.0)),
  59. ... "b": Value(kind=Oneof(field="string_value", value="bear")),
  60. ... }
  61. ... ).to_python()
  62. {'a': 1.0, 'b': 'bear'}
  63. """
  64. return {k: v.to_python() for k, v in self.fields.items()}
  65. SelfValue = TypeVar("SelfValue", bound="ValueMixin")
  66. class ValueMixin:
  67. __slots__ = ()
  68. if TYPE_CHECKING:
  69. def __init__(
  70. self,
  71. *,
  72. kind: Oneof[Literal["null_value"], NullValue]
  73. | Oneof[Literal["number_value"], float]
  74. | Oneof[Literal["string_value"], str]
  75. | Oneof[Literal["bool_value"], bool]
  76. | Oneof[Literal["struct_value"], Struct]
  77. | Oneof[Literal["list_value"], ListValue]
  78. | None = None,
  79. ) -> None: ...
  80. kind: (
  81. Oneof[Literal["null_value"], NullValue]
  82. | Oneof[Literal["number_value"], float]
  83. | Oneof[Literal["string_value"], str]
  84. | Oneof[Literal["bool_value"], bool]
  85. | Oneof[Literal["struct_value"], Struct]
  86. | Oneof[Literal["list_value"], ListValue]
  87. | None
  88. )
  89. @classmethod
  90. def from_python(cls: type[SelfValue], value: ValueTypeParam) -> SelfValue:
  91. """Create a Value from a Python value.
  92. Args:
  93. value: The Python value to convert. `int` values are converted to `float`.
  94. Returns:
  95. A Value representing the given Python value.
  96. Raises:
  97. TypeError: If the value is of an unsupported type, including `bytes`.
  98. Examples:
  99. >>> from protobuf.wkt import Value
  100. >>> Value.from_python(None)
  101. Value(kind=Oneof(field='null_value', value=NullValue.NULL_VALUE))
  102. >>> Value.from_python(True)
  103. Value(kind=Oneof(field='bool_value', value=True))
  104. >>> Value.from_python(2)
  105. Value(kind=Oneof(field='number_value', value=2.0))
  106. >>> Value.from_python(3.14)
  107. Value(kind=Oneof(field='number_value', value=3.14))
  108. >>> Value.from_python("hello")
  109. Value(kind=Oneof(field='string_value', value='hello'))
  110. >>> Value.from_python([1, "foo"])
  111. Value(kind=Oneof(field='list_value', value=ListValue(values=[Value(kind=Oneof(field='number_value', value=1.0)), Value(kind=Oneof(field='string_value', value='foo'))])))
  112. >>> Value.from_python({"a": 1, "b": "bear"})
  113. Value(kind=Oneof(field='struct_value', value=Struct(fields={'a': Value(kind=Oneof(field='number_value', value=1.0)), 'b': Value(kind=Oneof(field='string_value', value='bear'))})))
  114. """
  115. from protobuf import Oneof # noqa: PLC0415
  116. from protobuf.wkt import ListValue, NullValue, Struct # noqa: PLC0415
  117. if isinstance(value, bytes):
  118. msg = "unsupported type for Value: bytes"
  119. raise TypeError(msg)
  120. match value:
  121. case None:
  122. return cls(kind=Oneof(field="null_value", value=NullValue.NULL_VALUE))
  123. case bool():
  124. return cls(kind=Oneof(field="bool_value", value=value))
  125. case int() | float():
  126. return cls(kind=Oneof(field="number_value", value=float(value)))
  127. case str():
  128. return cls(kind=Oneof(field="string_value", value=value))
  129. case Mapping() as m:
  130. return cls(
  131. kind=Oneof(field="struct_value", value=Struct.from_python(m))
  132. )
  133. case Sequence() as s:
  134. return cls(
  135. kind=Oneof(field="list_value", value=ListValue.from_python(s))
  136. )
  137. case _:
  138. msg = f"unsupported type for Value: {type(value).__name__}"
  139. raise TypeError(msg)
  140. def to_python(self) -> ValueTypeReturn:
  141. """Convert this Value to a Python value.
  142. Examples:
  143. >>> from protobuf import Oneof
  144. >>> from protobuf.wkt import ListValue, NullValue, Struct, Value
  145. >>> Value(
  146. ... kind=Oneof(field="null_value", value=NullValue.NULL_VALUE)
  147. ... ).to_python()
  148. >>> Value(kind=Oneof(field="bool_value", value=True)).to_python()
  149. True
  150. >>> Value(kind=Oneof(field="number_value", value=3.14)).to_python()
  151. 3.14
  152. >>> Value(kind=Oneof(field="string_value", value="hello")).to_python()
  153. 'hello'
  154. >>> Value(
  155. ... kind=Oneof(
  156. ... field="list_value",
  157. ... value=ListValue(
  158. ... values=[
  159. ... Value(kind=Oneof(field="number_value", value=1.0)),
  160. ... Value(kind=Oneof(field="string_value", value="foo")),
  161. ... ]
  162. ... ),
  163. ... )
  164. ... ).to_python()
  165. [1.0, 'foo']
  166. >>> Value(
  167. ... kind=Oneof(
  168. ... field="struct_value",
  169. ... value=Struct(
  170. ... fields={
  171. ... "a": Value(kind=Oneof(field="number_value", value=1.0)),
  172. ... "b": Value(
  173. ... kind=Oneof(field="string_value", value="bear")
  174. ... ),
  175. ... }
  176. ... ),
  177. ... )
  178. ... ).to_python()
  179. {'a': 1.0, 'b': 'bear'}
  180. """
  181. from protobuf import Oneof # noqa: PLC0415
  182. match self.kind:
  183. case Oneof(
  184. field="null_value" # Protobuf implementations typically ignore value
  185. ):
  186. return None
  187. case Oneof(field="bool_value", value=b):
  188. return b
  189. case Oneof(field="number_value", value=n):
  190. return n
  191. case Oneof(field="string_value", value=s):
  192. return s
  193. case Oneof(field="struct_value", value=s):
  194. return s.to_python()
  195. case Oneof(field="list_value", value=l):
  196. return l.to_python()
  197. case None:
  198. msg = "no kind set"
  199. raise ValueError(msg)
  200. SelfListValue = TypeVar("SelfListValue", bound="ListValueMixin")
  201. class ListValueMixin:
  202. __slots__ = ()
  203. if TYPE_CHECKING:
  204. def __init__(self, *, values: list[Value] | None = None) -> None: ...
  205. values: list[Value]
  206. @classmethod
  207. def from_python(
  208. cls: type[SelfListValue], values: Sequence[ValueTypeParam]
  209. ) -> SelfListValue:
  210. """Create a ListValue from a list of Python values.
  211. Examples:
  212. >>> from protobuf.wkt import ListValue
  213. >>> ListValue.from_python([1, "foo"])
  214. ListValue(values=[Value(kind=Oneof(field='number_value', value=1.0)), Value(kind=Oneof(field='string_value', value='foo'))])
  215. """
  216. from protobuf.wkt import Value # noqa: PLC0415
  217. return cls(values=[Value.from_python(v) for v in values])
  218. def to_python(self) -> list[ValueTypeReturn]:
  219. """Convert this ListValue to a list of Python values.
  220. Examples:
  221. >>> from protobuf import Oneof
  222. >>> from protobuf.wkt import ListValue, Value
  223. >>> ListValue(
  224. ... values=[
  225. ... Value(kind=Oneof(field="number_value", value=1.0)),
  226. ... Value(kind=Oneof(field="string_value", value="foo")),
  227. ... ]
  228. ... ).to_python()
  229. [1.0, 'foo']
  230. """
  231. return [v.to_python() for v in self.values]