_interceptor_async.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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 AsyncIterator, Awaitable, Callable, Iterable, Sequence
  5. from .request import RequestContext
  6. REQ = TypeVar("REQ")
  7. RES = TypeVar("RES")
  8. T = TypeVar("T")
  9. @runtime_checkable
  10. class UnaryInterceptor(Protocol):
  11. """An interceptor of an asynchronous unary RPC method."""
  12. async def intercept_unary(
  13. self,
  14. call_next: Callable[[REQ, RequestContext], Awaitable[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 ClientStreamInterceptor(Protocol):
  33. """An interceptor of an asynchronous client-streaming RPC method."""
  34. async def intercept_client_stream(
  35. self,
  36. call_next: Callable[[AsyncIterator[REQ], RequestContext], Awaitable[RES]],
  37. request: AsyncIterator[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 ServerStreamInterceptor(Protocol):
  55. """An interceptor of an asynchronous server-streaming RPC method."""
  56. def intercept_server_stream(
  57. self,
  58. call_next: Callable[[REQ, RequestContext], AsyncIterator[RES]],
  59. request: REQ,
  60. ctx: RequestContext,
  61. ) -> AsyncIterator[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 BidiStreamInterceptor(Protocol):
  77. """An interceptor of an asynchronous bidirectional-streaming RPC method."""
  78. def intercept_bidi_stream(
  79. self,
  80. call_next: Callable[[AsyncIterator[REQ], RequestContext], AsyncIterator[RES]],
  81. request: AsyncIterator[REQ],
  82. ctx: RequestContext,
  83. ) -> AsyncIterator[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 MetadataInterceptor(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 [UnaryInterceptor][].
  103. """
  104. async def on_start(self, ctx: RequestContext) -> T:
  105. """Called when the RPC starts. The return value will be passed to [on_end][] as-is.
  106. For example, if measuring RPC invocation time, on_start may return the current
  107. time. If a return value isn't needed or [on_end][] won't be used, return None.
  108. """
  109. ...
  110. async def on_end(
  111. self, token: T, ctx: RequestContext, error: Exception | None
  112. ) -> None:
  113. """Called when the RPC ends."""
  114. return
  115. Interceptor = (
  116. UnaryInterceptor
  117. | ClientStreamInterceptor
  118. | ServerStreamInterceptor
  119. | BidiStreamInterceptor
  120. | MetadataInterceptor
  121. )
  122. """An interceptor to apply to an asynchronous RPC server or client."""
  123. class MetadataInterceptorInvoker(Generic[T]):
  124. _delegate: MetadataInterceptor[T]
  125. def __init__(self, delegate: MetadataInterceptor[T]) -> None:
  126. self._delegate = delegate
  127. async def intercept_unary(
  128. self,
  129. call_next: Callable[[REQ, RequestContext], Awaitable[RES]],
  130. request: REQ,
  131. ctx: RequestContext,
  132. ) -> RES:
  133. token = await self._delegate.on_start(ctx)
  134. error: Exception | None = None
  135. try:
  136. return await call_next(request, ctx)
  137. except Exception as e:
  138. error = e
  139. raise
  140. finally:
  141. await self._delegate.on_end(token, ctx, error)
  142. async def intercept_client_stream(
  143. self,
  144. call_next: Callable[[AsyncIterator[REQ], RequestContext], Awaitable[RES]],
  145. request: AsyncIterator[REQ],
  146. ctx: RequestContext,
  147. ) -> RES:
  148. token = await self._delegate.on_start(ctx)
  149. error: Exception | None = None
  150. try:
  151. return await call_next(request, ctx)
  152. except Exception as e:
  153. error = e
  154. raise
  155. finally:
  156. await self._delegate.on_end(token, ctx, error)
  157. async def intercept_server_stream(
  158. self,
  159. call_next: Callable[[REQ, RequestContext], AsyncIterator[RES]],
  160. request: REQ,
  161. ctx: RequestContext,
  162. ) -> AsyncIterator[RES]:
  163. token = await self._delegate.on_start(ctx)
  164. error: Exception | None = None
  165. try:
  166. async for response in call_next(request, ctx):
  167. yield response
  168. except Exception as e:
  169. error = e
  170. raise
  171. finally:
  172. await self._delegate.on_end(token, ctx, error)
  173. async def intercept_bidi_stream(
  174. self,
  175. call_next: Callable[[AsyncIterator[REQ], RequestContext], AsyncIterator[RES]],
  176. request: AsyncIterator[REQ],
  177. ctx: RequestContext,
  178. ) -> AsyncIterator[RES]:
  179. token = await self._delegate.on_start(ctx)
  180. error: Exception | None = None
  181. try:
  182. async for response in call_next(request, ctx):
  183. yield response
  184. except Exception as e:
  185. error = e
  186. raise
  187. finally:
  188. await self._delegate.on_end(token, ctx, error)
  189. def resolve_interceptors(
  190. interceptors: Iterable[Interceptor],
  191. ) -> Sequence[
  192. UnaryInterceptor
  193. | ClientStreamInterceptor
  194. | ServerStreamInterceptor
  195. | BidiStreamInterceptor
  196. ]:
  197. return [
  198. MetadataInterceptorInvoker(interceptor)
  199. if isinstance(interceptor, MetadataInterceptor)
  200. else interceptor
  201. for interceptor in interceptors
  202. ]