_interceptor_sync.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. from __future__ import annotations
  2. from typing import TYPE_CHECKING, Generic, Protocol, TypeVar, runtime_checkable
  3. if TYPE_CHECKING:
  4. from collections.abc import Callable, Iterable, Iterator, Sequence
  5. from .request import RequestContext
  6. REQ = TypeVar("REQ")
  7. RES = TypeVar("RES")
  8. T = TypeVar("T")
  9. @runtime_checkable
  10. class UnaryInterceptorSync(Protocol):
  11. """An interceptor of a synchronous unary RPC method."""
  12. def intercept_unary_sync(
  13. self,
  14. call_next: Callable[[REQ, RequestContext], RES],
  15. request: REQ,
  16. ctx: RequestContext,
  17. ) -> RES:
  18. """Intercepts a unary RPC.
  19. Args:
  20. call_next: A callable to invoke to continue processing, either to another
  21. interceptor or the actual RPC. Generally will be called with the same
  22. request the interceptor received but the request can be replaced as
  23. needed. Can be skipped if returning a response from the interceptor
  24. directly.
  25. request: The request message.
  26. ctx: The request context.
  27. Returns:
  28. The response message.
  29. """
  30. ...
  31. @runtime_checkable
  32. class ClientStreamInterceptorSync(Protocol):
  33. """An interceptor of a synchronous client-streaming RPC method."""
  34. def intercept_client_stream_sync(
  35. self,
  36. call_next: Callable[[Iterator[REQ], RequestContext], RES],
  37. request: Iterator[REQ],
  38. ctx: RequestContext,
  39. ) -> RES:
  40. """Intercepts a client-streaming RPC.
  41. Args:
  42. call_next: A callable to invoke to continue processing, either to another
  43. interceptor or the actual RPC. Generally will be called with the same
  44. request the interceptor received but the request can be replaced as
  45. needed. Can be skipped if returning a response from the interceptor
  46. directly.
  47. request: The request message iterator.
  48. ctx: The request context.
  49. Returns:
  50. The response message.
  51. """
  52. ...
  53. @runtime_checkable
  54. class ServerStreamInterceptorSync(Protocol):
  55. """An interceptor of a synchronous server-streaming RPC method."""
  56. def intercept_server_stream_sync(
  57. self,
  58. call_next: Callable[[REQ, RequestContext], Iterator[RES]],
  59. request: REQ,
  60. ctx: RequestContext,
  61. ) -> Iterator[RES]:
  62. """Intercepts a server-streaming RPC.
  63. Args:
  64. call_next: A callable to invoke to continue processing, either to another
  65. interceptor or the actual RPC. Generally will be called with the same
  66. request the interceptor received but the request can be replaced as
  67. needed. Can be skipped if returning a response from the interceptor
  68. directly.
  69. request: The request message.
  70. ctx: The request context.
  71. Returns:
  72. The response message iterator.
  73. """
  74. ...
  75. @runtime_checkable
  76. class BidiStreamInterceptorSync(Protocol):
  77. """An interceptor of a synchronous bidirectional-streaming RPC method."""
  78. def intercept_bidi_stream_sync(
  79. self,
  80. call_next: Callable[[Iterator[REQ], RequestContext], Iterator[RES]],
  81. request: Iterator[REQ],
  82. ctx: RequestContext,
  83. ) -> Iterator[RES]:
  84. """Intercepts a bidirectional-streaming RPC.
  85. Args:
  86. call_next: A callable to invoke to continue processing, either to another
  87. interceptor or the actual RPC. Generally will be called with the same
  88. request the interceptor received but the request can be replaced as
  89. needed. Can be skipped if returning a response from the interceptor
  90. directly.
  91. request: The request message iterator.
  92. ctx: The request context.
  93. Returns:
  94. The response message iterator.
  95. """
  96. ...
  97. @runtime_checkable
  98. class MetadataInterceptorSync(Protocol[T]):
  99. """An interceptor that can be applied to any type of method, only having
  100. access to metadata such as headers and trailers.
  101. To access request and response bodies of a method, instead use an interceptor
  102. corresponding to the type of method such as [UnaryInterceptorSync][].
  103. """
  104. def on_start_sync(self, ctx: RequestContext) -> T:
  105. """Called when the RPC starts. The return value will be passed to [on_end_sync][] as-is.
  106. For example, if measuring RPC invocation time, on_start_sync may return the current
  107. time. If a return value isn't needed or [on_end_sync][] won't be used, return None.
  108. """
  109. ...
  110. def on_end_sync(
  111. self, token: T, ctx: RequestContext, error: Exception | None
  112. ) -> None:
  113. """Called when the RPC ends."""
  114. return
  115. InterceptorSync = (
  116. UnaryInterceptorSync
  117. | ClientStreamInterceptorSync
  118. | ServerStreamInterceptorSync
  119. | BidiStreamInterceptorSync
  120. | MetadataInterceptorSync
  121. )
  122. """An interceptor to apply to a synchronous RPC server or client."""
  123. class MetadataInterceptorInvokerSync(Generic[T]):
  124. _delegate: MetadataInterceptorSync[T]
  125. def __init__(self, delegate: MetadataInterceptorSync[T]) -> None:
  126. self._delegate = delegate
  127. def intercept_unary_sync(
  128. self,
  129. call_next: Callable[[REQ, RequestContext], RES],
  130. request: REQ,
  131. ctx: RequestContext,
  132. ) -> RES:
  133. token = self._delegate.on_start_sync(ctx)
  134. error: Exception | None = None
  135. try:
  136. return call_next(request, ctx)
  137. except Exception as e:
  138. error = e
  139. raise
  140. finally:
  141. self._delegate.on_end_sync(token, ctx, error)
  142. def intercept_client_stream_sync(
  143. self,
  144. call_next: Callable[[Iterator[REQ], RequestContext], RES],
  145. request: Iterator[REQ],
  146. ctx: RequestContext,
  147. ) -> RES:
  148. token = self._delegate.on_start_sync(ctx)
  149. error: Exception | None = None
  150. try:
  151. return call_next(request, ctx)
  152. except Exception as e:
  153. error = e
  154. raise
  155. finally:
  156. self._delegate.on_end_sync(token, ctx, error)
  157. def intercept_server_stream_sync(
  158. self,
  159. call_next: Callable[[REQ, RequestContext], Iterator[RES]],
  160. request: REQ,
  161. ctx: RequestContext,
  162. ) -> Iterator[RES]:
  163. token = self._delegate.on_start_sync(ctx)
  164. error: Exception | None = None
  165. try:
  166. yield from call_next(request, ctx)
  167. except Exception as e:
  168. error = e
  169. raise
  170. finally:
  171. self._delegate.on_end_sync(token, ctx, error)
  172. def intercept_bidi_stream_sync(
  173. self,
  174. call_next: Callable[[Iterator[REQ], RequestContext], Iterator[RES]],
  175. request: Iterator[REQ],
  176. ctx: RequestContext,
  177. ) -> Iterator[RES]:
  178. token = self._delegate.on_start_sync(ctx)
  179. error: Exception | None = None
  180. try:
  181. yield from call_next(request, ctx)
  182. except Exception as e:
  183. error = e
  184. raise
  185. finally:
  186. self._delegate.on_end_sync(token, ctx, error)
  187. def resolve_interceptors(
  188. interceptors: Iterable[InterceptorSync],
  189. ) -> Sequence[
  190. UnaryInterceptorSync
  191. | ClientStreamInterceptorSync
  192. | ServerStreamInterceptorSync
  193. | BidiStreamInterceptorSync
  194. ]:
  195. return [
  196. MetadataInterceptorInvokerSync(interceptor)
  197. if isinstance(interceptor, MetadataInterceptorSync)
  198. else interceptor
  199. for interceptor in interceptors
  200. ]