Skip to content

Commit 59ad8fc

Browse files
committed
feat userver/testsuite: publish PreCallClientInterceptor
commit_hash:8059a020621a6c99cb964c5e6b52572932130c39
1 parent a90cfc7 commit 59ad8fc

File tree

5 files changed

+96
-18
lines changed

5 files changed

+96
-18
lines changed

.mapping.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4929,6 +4929,7 @@
49294929
"testsuite/pytest_plugins/pytest_userver/client.py":"taxi/uservices/userver/testsuite/pytest_plugins/pytest_userver/client.py",
49304930
"testsuite/pytest_plugins/pytest_userver/dynconf.py":"taxi/uservices/userver/testsuite/pytest_plugins/pytest_userver/dynconf.py",
49314931
"testsuite/pytest_plugins/pytest_userver/grpc/__init__.py":"taxi/uservices/userver/testsuite/pytest_plugins/pytest_userver/grpc/__init__.py",
4932+
"testsuite/pytest_plugins/pytest_userver/grpc/_client.py":"taxi/uservices/userver/testsuite/pytest_plugins/pytest_userver/grpc/_client.py",
49324933
"testsuite/pytest_plugins/pytest_userver/grpc/_mocked_errors.py":"taxi/uservices/userver/testsuite/pytest_plugins/pytest_userver/grpc/_mocked_errors.py",
49334934
"testsuite/pytest_plugins/pytest_userver/grpc/_reflection.py":"taxi/uservices/userver/testsuite/pytest_plugins/pytest_userver/grpc/_reflection.py",
49344935
"testsuite/pytest_plugins/pytest_userver/grpc/_servicer_mock.py":"taxi/uservices/userver/testsuite/pytest_plugins/pytest_userver/grpc/_servicer_mock.py",

testsuite/pytest_plugins/pytest_userver/grpc/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Mocks for the gRPC servers, a.k.a. `pytest_userver.grpc.
2+
Mocks for the gRPC servers, a.k.a. `pytest_userver.grpc`.
33
44
@sa @ref scripts/docs/en/userver/tutorial/grpc_service.md
55
@sa @ref pytest_userver.plugins.grpc.mockserver
@@ -22,6 +22,7 @@
2222

2323
import testsuite.utils.callinfo
2424

25+
from ._client import PreCallClientInterceptor # noqa: F401
2526
from ._mocked_errors import MockedError # noqa: F401
2627
from ._mocked_errors import NetworkError # noqa: F401
2728
from ._mocked_errors import TimeoutError # noqa: F401
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import abc
2+
from collections.abc import AsyncIterator
3+
from collections.abc import Awaitable
4+
from collections.abc import Callable
5+
from typing import Any
6+
7+
import grpc.aio
8+
9+
10+
class PreCallClientInterceptor(
11+
grpc.aio.UnaryUnaryClientInterceptor,
12+
grpc.aio.UnaryStreamClientInterceptor,
13+
grpc.aio.StreamUnaryClientInterceptor,
14+
grpc.aio.StreamStreamClientInterceptor,
15+
abc.ABC,
16+
):
17+
"""
18+
@brief A simple generic interceptor that allows to insert some code before each RPC is initiated.
19+
20+
To use, inherit from this class and pass an instance to
21+
@ref pytest_userver.plugins.grpc._hookspec.pytest_grpc_client_interceptors "pytest_grpc_client_interceptors".
22+
23+
@note Exported as `pytest_userver.grpc.PreCallClientInterceptor`.
24+
@ingroup userver_testsuite
25+
"""
26+
27+
@abc.abstractmethod
28+
async def pre_call_hook(self, client_call_details: grpc.aio.ClientCallDetails) -> None:
29+
"""
30+
Runs before the RPC is initiated. `client_call_details` can be observed or modified as needed.
31+
"""
32+
33+
async def intercept_unary_unary(
34+
self,
35+
continuation: Callable[[grpc.aio.ClientCallDetails, Any], Awaitable[grpc.aio.UnaryUnaryCall]],
36+
client_call_details: grpc.aio.ClientCallDetails,
37+
request: Any,
38+
) -> grpc.aio.UnaryUnaryCall:
39+
await self.pre_call_hook(client_call_details)
40+
return await continuation(client_call_details, request)
41+
42+
# Note: full type of this function is Callable[[...], Awaitable[AsyncIterator[Any]]]
43+
async def intercept_unary_stream(
44+
self,
45+
continuation: Callable[[grpc.aio.ClientCallDetails, Any], grpc.aio.UnaryStreamCall],
46+
client_call_details: grpc.aio.ClientCallDetails,
47+
request: Any,
48+
) -> AsyncIterator[Any]:
49+
await self.pre_call_hook(client_call_details)
50+
return await continuation(client_call_details, request)
51+
52+
async def intercept_stream_unary(
53+
self,
54+
continuation: Callable[[grpc.aio.ClientCallDetails, AsyncIterator[Any]], Awaitable[grpc.aio.StreamUnaryCall]],
55+
client_call_details: grpc.aio.ClientCallDetails,
56+
request_iterator: AsyncIterator[Any],
57+
) -> grpc.aio.StreamUnaryCall:
58+
await self.pre_call_hook(client_call_details)
59+
return await continuation(client_call_details, request_iterator)
60+
61+
# Note: full type of this function is Callable[[...], Awaitable[AsyncIterator[Any]]]
62+
async def intercept_stream_stream(
63+
self,
64+
continuation: Callable[[grpc.aio.ClientCallDetails, AsyncIterator[Any]], grpc.aio.StreamStreamCall],
65+
client_call_details: grpc.aio.ClientCallDetails,
66+
request_iterator: AsyncIterator[Any],
67+
) -> AsyncIterator[Any]:
68+
await self.pre_call_hook(client_call_details)
69+
return await continuation(client_call_details, request_iterator)

testsuite/pytest_plugins/pytest_userver/plugins/grpc/_hookspec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ def pytest_grpc_client_interceptors(request: pytest.FixtureRequest) -> Sequence[
1414
Interceptors are accomulated over all implementations of this hook from all plugins.
1515
1616
@see @ref scripts/docs/en/userver/grpc/grpc.md
17-
@ingroup userver_testsuite_fixtures
17+
@ingroup userver_testsuite
1818
"""
1919
raise NotImplementedError()

testsuite/pytest_plugins/pytest_userver/plugins/grpc/client.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import grpc
2121
import grpc.aio
2222
import pytest
23+
from typing_extensions import override
2324

2425
import pytest_userver.client
26+
import pytest_userver.grpc
2527
from . import _hookspec
2628

2729
DEFAULT_TIMEOUT = 15.0
@@ -194,41 +196,37 @@ def pytest_addhooks(pluginmanager: pytest.PytestPluginManager):
194196
pluginmanager.add_hookspecs(_hookspec)
195197

196198

197-
class _UpdateServerStateInterceptor(
199+
class _AsyncExcCheckInterceptor(
198200
grpc.aio.UnaryUnaryClientInterceptor,
199201
grpc.aio.UnaryStreamClientInterceptor,
200202
grpc.aio.StreamUnaryClientInterceptor,
201203
grpc.aio.StreamStreamClientInterceptor,
202204
):
203-
def __init__(self, service_client: pytest_userver.client.Client, asyncexc_check: _AsyncExcCheck):
204-
self._service_client = service_client
205+
def __init__(self, asyncexc_check: _AsyncExcCheck):
205206
self._asyncexc_check = asyncexc_check
206207

207-
async def _before_call_hook(self) -> None:
208-
self._asyncexc_check()
209-
if hasattr(self._service_client, 'update_server_state'):
210-
await self._service_client.update_server_state()
211-
208+
@override
212209
async def intercept_unary_unary(
213210
self,
214211
continuation: Callable[[grpc.aio.ClientCallDetails, Any], Awaitable[grpc.aio.UnaryUnaryCall]],
215212
client_call_details: grpc.aio.ClientCallDetails,
216213
request: Any,
217214
) -> grpc.aio.UnaryUnaryCall:
218-
await self._before_call_hook()
215+
self._asyncexc_check()
219216
try:
220217
return await continuation(client_call_details, request)
221218
finally:
222219
self._asyncexc_check()
223220

224221
# Note: full type of this function is Callable[[...], Awaitable[AsyncIterator[Any]]]
222+
@override
225223
async def intercept_unary_stream(
226224
self,
227225
continuation: Callable[[grpc.aio.ClientCallDetails, Any], grpc.aio.UnaryStreamCall],
228226
client_call_details: grpc.aio.ClientCallDetails,
229227
request: Any,
230228
) -> AsyncIterator[Any]:
231-
await self._before_call_hook()
229+
self._asyncexc_check()
232230
call = await continuation(client_call_details, request)
233231

234232
async def response_stream() -> AsyncIterator[Any]:
@@ -240,27 +238,28 @@ async def response_stream() -> AsyncIterator[Any]:
240238

241239
return response_stream()
242240

241+
@override
243242
async def intercept_stream_unary(
244243
self,
245244
continuation: Callable[[grpc.aio.ClientCallDetails, AsyncIterator[Any]], Awaitable[grpc.aio.StreamUnaryCall]],
246245
client_call_details: grpc.aio.ClientCallDetails,
247246
request_iterator: AsyncIterator[Any],
248247
) -> grpc.aio.StreamUnaryCall:
249-
await self._before_call_hook()
248+
self._asyncexc_check()
250249
try:
251250
return await continuation(client_call_details, request_iterator)
252251
finally:
253252
self._asyncexc_check()
254253

255254
# Note: full type of this function is Callable[[...], Awaitable[AsyncIterator[Any]]]
255+
@override
256256
async def intercept_stream_stream(
257257
self,
258258
continuation: Callable[[grpc.aio.ClientCallDetails, AsyncIterator[Any]], grpc.aio.StreamStreamCall],
259259
client_call_details: grpc.aio.ClientCallDetails,
260260
request_iterator: AsyncIterator[Any],
261261
) -> AsyncIterator[Any]:
262262
self._asyncexc_check()
263-
await self._before_call_hook()
264263
call = await continuation(client_call_details, request_iterator)
265264

266265
async def response_stream() -> AsyncIterator[Any]:
@@ -273,12 +272,20 @@ async def response_stream() -> AsyncIterator[Any]:
273272
return response_stream()
274273

275274

275+
class _UpdateServerStateInterceptor(pytest_userver.grpc.PreCallClientInterceptor):
276+
def __init__(self, service_client: pytest_userver.client.Client):
277+
self._service_client = service_client
278+
279+
@override
280+
async def pre_call_hook(self, client_call_details: grpc.aio.ClientCallDetails) -> None:
281+
if hasattr(self._service_client, 'update_server_state'):
282+
await self._service_client.update_server_state()
283+
284+
276285
def pytest_grpc_client_interceptors(request: pytest.FixtureRequest) -> Sequence[grpc.aio.ClientInterceptor]:
277286
return [
278-
_UpdateServerStateInterceptor(
279-
request.getfixturevalue('service_client'),
280-
request.getfixturevalue('asyncexc_check'),
281-
),
287+
_AsyncExcCheckInterceptor(request.getfixturevalue('asyncexc_check')),
288+
_UpdateServerStateInterceptor(request.getfixturevalue('service_client')),
282289
]
283290

284291

0 commit comments

Comments
 (0)