_any.py 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  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 typing import TYPE_CHECKING, TypeVar, cast
  16. from protobuf import DescMessage, Message
  17. T = TypeVar("T", bound=Message)
  18. Self = TypeVar("Self", bound="AnyMixin")
  19. class AnyMixin:
  20. __slots__ = ()
  21. if TYPE_CHECKING:
  22. def __init__(self, *, type_url: str = "", value: bytes = b"") -> None:
  23. pass
  24. type_url: str
  25. value: bytes
  26. @classmethod
  27. def pack(cls: type[Self], message: Message) -> Self:
  28. """Creates an `Any` from a message."""
  29. type_url = f"type.googleapis.com/{message._desc.type_name}"
  30. return cls(type_url=type_url, value=message.to_binary())
  31. def is_type(self, type_info: type[Message] | DescMessage | str) -> bool:
  32. """Returns true if the Any contains the type given by schema or type name."""
  33. if self.type_url == "":
  34. return False
  35. type_name: str
  36. match type_info:
  37. case str():
  38. type_name = type_info
  39. case DescMessage():
  40. type_name = type_info.type_name
  41. case _:
  42. type_name = type_info._desc.type_name
  43. return type_name == type_url_to_name(self.type_url)
  44. def unpack(self, type_info: DescMessage | type[T]) -> T | None:
  45. """Unpacks the message the Any represents.
  46. Returns:
  47. Returns None if the Any is empty, or if it does not contain the type
  48. given by schema. Otherwise, the unpacked message.
  49. """
  50. if not self.is_type(type_info):
  51. return None
  52. stub: type[T]
  53. match type_info:
  54. case DescMessage() as desc:
  55. stub = cast("type[T]", desc.type)
  56. case _:
  57. stub = type_info
  58. return stub.from_binary(self.value)
  59. def type_url_to_name(url: str) -> str:
  60. # rindex raises an error if sub str is not found
  61. name = url[(url.rindex("/") if "/" in url else -1) + 1 :]
  62. if len(name) == 0:
  63. msg = f"invalid type url: {url}"
  64. raise ValueError(msg)
  65. return name