Skip to content

Commit b907213

Browse files
committed
feat(api-core): rename channel wrapper terminology to interceptor
1 parent 3687533 commit b907213

4 files changed

Lines changed: 78 additions & 79 deletions

File tree

packages/google-api-core/google/api_core/_observability.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
from google.api_core.client_options import ClientOptions
2323

2424
if TYPE_CHECKING:
25-
from google.api_core.grpc_helpers import ChannelWrapperCallable
25+
from google.api_core.grpc_helpers import ClientInterceptorCallable
2626
else:
27-
ChannelWrapperCallable = Callable[[Any], Any]
27+
ClientInterceptorCallable = Callable[[Any], Any]
2828

2929
_TRACER_PROVIDER = "tracer_provider"
3030

@@ -67,7 +67,7 @@ def _get_otel_interceptor(
6767
6868
Args:
6969
client_options: The client options object or dictionary.
70-
is_async: If True, returns an async interceptor (`aio_client_interceptor`),
70+
is_async: If True, returns an async interceptor (`aio_client_interceptors`),
7171
otherwise returns a sync interceptor (`client_interceptor`).
7272
7373
Returns:
@@ -86,17 +86,17 @@ def _get_otel_interceptor(
8686
return otel_grpc.client_interceptor(tracer_provider=tracer_provider)
8787

8888

89-
def get_otel_channel_wrapper(
89+
def get_otel_interceptor(
9090
client_options: ClientOptions | dict[str, Any] | None = None,
91-
) -> ChannelWrapperCallable | None:
92-
"""Returns a channel wrapper callable that wraps a sync gRPC channel with OpenTelemetry tracing.
91+
) -> ClientInterceptorCallable | None:
92+
"""Returns an interceptor callable that wraps a sync gRPC channel with OpenTelemetry tracing.
9393
9494
Args:
9595
client_options: The client options object or dictionary used for feature gating
9696
and extracting the tracer provider.
9797
9898
Returns:
99-
Optional[ChannelWrapperCallable]: A channel-wrapping callable if OpenTelemetry
99+
Optional[ClientInterceptorCallable]: An interceptor callable if OpenTelemetry
100100
tracing is enabled and installed, None otherwise.
101101
"""
102102
if not is_otel_capabilities_enabled(client_options):
@@ -106,23 +106,23 @@ def get_otel_channel_wrapper(
106106

107107
interceptor = _get_otel_interceptor(client_options, is_async=False)
108108

109-
def channel_wrapper(channel: Any) -> Any:
109+
def otel_interceptor(channel: Any) -> Any:
110110
return otel_grpc.intercept_channel(channel, interceptor)
111111

112-
return channel_wrapper
112+
return otel_interceptor
113113

114114

115115
def get_otel_async_interceptor(
116116
client_options: ClientOptions | dict[str, Any] | None = None,
117117
) -> Any | None:
118-
"""Returns an async gRPC client interceptor for OpenTelemetry tracing.
118+
"""Returns async gRPC client interceptors for OpenTelemetry tracing.
119119
120120
Args:
121121
client_options: The client options object or dictionary used for feature gating
122122
and extracting the tracer provider.
123123
124124
Returns:
125-
Optional[Any]: An instantiated OpenTelemetry async client interceptor
125+
Optional[Any]: Instantiated OpenTelemetry async client interceptors
126126
if tracing is enabled and installed, None otherwise.
127127
"""
128128
if not is_otel_capabilities_enabled(client_options):

packages/google-api-core/google/api_core/grpc_helpers.py

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import google.auth.transport.requests
3636
import google.protobuf
3737
import grpc
38-
3938
from google.api_core import exceptions, general_helpers
4039

4140
# The list of gRPC Callable interfaces that return iterators.
@@ -55,11 +54,11 @@
5554
# Runtime tuple of gRPC client interceptor base classes for isinstance checks
5655
_CLIENT_INTERCEPTOR_CLASSES = get_args(ClientInterceptor)
5756

58-
# Type alias representing a channel-wrapping callable
59-
ChannelWrapperCallable: TypeAlias = Callable[[grpc.Channel], grpc.Channel]
57+
# Type alias representing a channel-intercepting callable
58+
ClientInterceptorCallable: TypeAlias = Callable[[grpc.Channel], grpc.Channel]
6059

61-
# Generic type alias representing any channel wrapper (interceptor or callable)
62-
ChannelWrapper: TypeAlias = ClientInterceptor | ChannelWrapperCallable
60+
# Generic type alias representing any client interceptor (standard interceptor or callable)
61+
ClientInterceptorType: TypeAlias = ClientInterceptor | ClientInterceptorCallable
6362

6463

6564
def _patch_callable_name(callable_):
@@ -446,43 +445,43 @@ def _modify_target_for_direct_path(target: str) -> str:
446445
return target
447446

448447

449-
def apply_channel_wrappers(
448+
def apply_channel_interceptors(
450449
channel: grpc.Channel,
451-
wrappers: Sequence[ChannelWrapper] | None = None,
450+
interceptors: Sequence[ClientInterceptorType] | None = None,
452451
) -> grpc.Channel:
453-
"""Applies channel wrappers (client interceptors or channel-wrapping callables) to a gRPC channel.
452+
"""Applies client interceptors or channel-intercepting callables to a gRPC channel.
454453
455-
Executes in reverse order so the first wrapper in the sequence becomes the
456-
outermost layer on outbound requests and the innermost layer on inbound responses.
454+
Executes in reverse order so the first interceptor in the sequence becomes the
455+
outermost layer on outbound requests and the innermost layer on inbound responses,
456+
aligning with the behavior of ``grpc.intercept_channel``.
457457
458458
Args:
459-
channel (grpc.Channel): The channel to wrap.
460-
wrappers (Optional[Sequence[ChannelWrapper]]):
461-
An optional sequence of client interceptors or channel-wrapping
462-
callables to apply.
459+
channel (grpc.Channel): The channel to intercept.
460+
interceptors (Optional[Sequence[Union[ClientInterceptor, Callable[[grpc.Channel], grpc.Channel]]]]):
461+
Additional interceptors (or callables that apply interceptors) to apply to the gRPC channel.
463462
464463
Returns:
465-
grpc.Channel: The wrapped channel, or the original channel if no
466-
wrappers were provided.
464+
grpc.Channel: The intercepted channel, or the original channel if no
465+
interceptors were provided.
467466
468467
Raises:
469-
TypeError: If an item in ``wrappers`` is neither a gRPC ClientInterceptor
468+
TypeError: If an item in ``interceptors`` is neither a gRPC ClientInterceptor
470469
nor a Callable[[Channel], Channel].
471470
"""
472-
if not wrappers:
471+
if not interceptors:
473472
return channel
474473

475474
modified_channel = channel
476475
# Reverse the inputs to align with the behavior of grpc.create_channel(*interceptors)
477-
for wrapper in reversed(list(wrappers)):
478-
if isinstance(wrapper, _CLIENT_INTERCEPTOR_CLASSES):
479-
modified_channel = grpc.intercept_channel(modified_channel, wrapper)
480-
elif callable(wrapper):
481-
wrapper_callable = cast(ChannelWrapperCallable, wrapper)
482-
modified_channel = wrapper_callable(modified_channel)
476+
for interceptor in reversed(list(interceptors)):
477+
if isinstance(interceptor, _CLIENT_INTERCEPTOR_CLASSES):
478+
modified_channel = grpc.intercept_channel(modified_channel, interceptor)
479+
elif callable(interceptor):
480+
interceptor_callable = cast(ClientInterceptorCallable, interceptor)
481+
modified_channel = interceptor_callable(modified_channel)
483482
else:
484483
raise TypeError(
485-
f"Expected ChannelWrapper (ClientInterceptor or Callable[[Channel], Channel]), got {type(wrapper).__name__}"
484+
f"Expected ClientInterceptor or Callable[[Channel], Channel], got {type(interceptor).__name__}"
486485
)
487486

488487
return modified_channel

packages/google-api-core/tests/unit/test_grpc_helpers.py

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@
2424
pytest.skip("No GRPC", allow_module_level=True)
2525

2626
import google.auth.credentials
27-
from google.longrunning import operations_pb2
28-
2927
from google.api_core import exceptions, grpc_helpers
28+
from google.longrunning import operations_pb2
3029

3130

3231
def test__patch_callable_name():
@@ -934,15 +933,17 @@ def test_close(self):
934933
assert channel.close() is None
935934

936935

937-
@pytest.mark.parametrize("falsy_wrappers", [None, [], ()])
938-
def test_apply_channel_wrappers_passthrough(falsy_wrappers):
939-
"""Verify that falsy or empty wrapper sequences return the channel unmodified."""
936+
@pytest.mark.parametrize("falsy_interceptors", [None, [], ()])
937+
def test_apply_channel_interceptors_passthrough(falsy_interceptors):
938+
"""Verify that falsy or empty interceptor sequences return the channel unmodified."""
940939
mock_base_channel = mock.Mock(name="base_channel")
941-
result = grpc_helpers.apply_channel_wrappers(mock_base_channel, falsy_wrappers)
940+
result = grpc_helpers.apply_channel_interceptors(
941+
mock_base_channel, falsy_interceptors
942+
)
942943
assert result is mock_base_channel
943944

944945

945-
def test_apply_channel_wrappers_grpc_client_interceptors():
946+
def test_apply_channel_interceptors_grpc_client_interceptors():
946947
"""Verify that standard gRPC ClientInterceptor instances are applied via grpc.intercept_channel."""
947948

948949
class DummyUnaryInterceptor(grpc.UnaryUnaryClientInterceptor):
@@ -966,7 +967,7 @@ def intercept_stream_stream(
966967
"grpc.intercept_channel",
967968
side_effect=[mock_chan_after_i2, mock_chan_after_i1],
968969
) as mock_intercept:
969-
result = grpc_helpers.apply_channel_wrappers(
970+
result = grpc_helpers.apply_channel_interceptors(
970971
mock_base_channel, [interceptor1, interceptor2]
971972
)
972973

@@ -981,53 +982,53 @@ def intercept_stream_stream(
981982
)
982983

983984

984-
def test_apply_channel_wrappers_callables():
985-
"""Verify that channel-wrapping callables Callable[[Channel], Channel] are invoked in sequence."""
985+
def test_apply_channel_interceptors_callables():
986+
"""Verify that channel-intercepting callables Callable[[Channel], Channel] are invoked in sequence."""
986987
mock_base_channel = mock.Mock(name="base_channel")
987988
mock_chan_1 = mock.Mock(name="chan_1")
988989
mock_chan_2 = mock.Mock(name="chan_2")
989990

990-
wrapper1 = mock.Mock(side_effect=lambda ch: mock_chan_2)
991-
wrapper2 = mock.Mock(side_effect=lambda ch: mock_chan_1)
991+
interceptor1 = mock.Mock(side_effect=lambda ch: mock_chan_2)
992+
interceptor2 = mock.Mock(side_effect=lambda ch: mock_chan_1)
992993

993-
result = grpc_helpers.apply_channel_wrappers(
994-
mock_base_channel, [wrapper1, wrapper2]
994+
result = grpc_helpers.apply_channel_interceptors(
995+
mock_base_channel, [interceptor1, interceptor2]
995996
)
996997

997998
assert result is mock_chan_2
998-
# Executed in reverse order: wrapper2 runs first on base channel, then wrapper1
999-
wrapper2.assert_called_once_with(mock_base_channel)
1000-
wrapper1.assert_called_once_with(mock_chan_1)
999+
# Executed in reverse order: interceptor2 runs first on base channel, then interceptor1
1000+
interceptor2.assert_called_once_with(mock_base_channel)
1001+
interceptor1.assert_called_once_with(mock_chan_1)
10011002

10021003

1003-
def test_apply_channel_wrappers_interspersed():
1004-
"""Verify that a mixed sequence of gRPC interceptors and channel wrapper callables are applied."""
1004+
def test_apply_channel_interceptors_interspersed():
1005+
"""Verify that a mixed sequence of gRPC interceptors and interceptor callables are applied."""
10051006

10061007
class DummyUnaryInterceptor(grpc.UnaryUnaryClientInterceptor):
10071008
def intercept_unary_unary(self, continuation, client_call_details, request):
10081009
return continuation(client_call_details, request)
10091010

10101011
interceptor = DummyUnaryInterceptor()
10111012
mock_base_channel = mock.Mock(name="base_channel")
1012-
mock_chan_after_wrapper = mock.Mock(name="chan_after_wrapper")
1013+
mock_chan_after_callable = mock.Mock(name="chan_after_callable")
10131014
mock_chan_after_interceptor = mock.Mock(name="chan_after_interceptor")
10141015

1015-
wrapper = mock.Mock(return_value=mock_chan_after_wrapper)
1016+
interceptor_callable = mock.Mock(return_value=mock_chan_after_callable)
10161017

10171018
with mock.patch(
10181019
"grpc.intercept_channel", return_value=mock_chan_after_interceptor
10191020
) as mock_intercept:
1020-
result = grpc_helpers.apply_channel_wrappers(
1021-
mock_base_channel, [interceptor, wrapper]
1021+
result = grpc_helpers.apply_channel_interceptors(
1022+
mock_base_channel, [interceptor, interceptor_callable]
10221023
)
10231024

10241025
assert result is mock_chan_after_interceptor
1025-
wrapper.assert_called_once_with(mock_base_channel)
1026-
mock_intercept.assert_called_once_with(mock_chan_after_wrapper, interceptor)
1026+
interceptor_callable.assert_called_once_with(mock_base_channel)
1027+
mock_intercept.assert_called_once_with(mock_chan_after_callable, interceptor)
10271028

10281029

1029-
def test_apply_channel_wrappers_invalid_type_raises():
1030+
def test_apply_channel_interceptors_invalid_type_raises():
10301031
"""Verify that passing an invalid object that is neither an interceptor nor callable raises TypeError."""
10311032
mock_base_channel = mock.Mock(name="base_channel")
1032-
with pytest.raises(TypeError, match="Expected ChannelWrapper"):
1033-
grpc_helpers.apply_channel_wrappers(mock_base_channel, [12345])
1033+
with pytest.raises(TypeError, match="Expected ClientInterceptor or Callable"):
1034+
grpc_helpers.apply_channel_interceptors(mock_base_channel, [12345])

packages/google-api-core/tests/unit/test_observability.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from unittest import mock
1717

1818
import pytest
19-
2019
from google.api_core import _observability
2120
from google.api_core._feature_gating_helpers import FeatureGatingError
2221
from google.api_core.client_options import ClientOptions
@@ -178,18 +177,18 @@ def test_get_otel_interceptor_async(monkeypatch):
178177
)
179178

180179

181-
def test_get_otel_channel_wrapper_disabled(monkeypatch):
180+
def test_get_otel_interceptor_disabled(monkeypatch):
182181
monkeypatch.setenv("GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", "false")
183-
assert _observability.get_otel_channel_wrapper() is None
182+
assert _observability.get_otel_interceptor() is None
184183

185184

186-
def test_get_otel_channel_wrapper_otel_missing(monkeypatch):
185+
def test_get_otel_interceptor_otel_missing(monkeypatch):
187186
monkeypatch.setenv("GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", "true")
188187
monkeypatch.setitem(sys.modules, "opentelemetry.instrumentation.grpc", None)
189-
assert _observability.get_otel_channel_wrapper() is None
188+
assert _observability.get_otel_interceptor() is None
190189

191190

192-
def test_get_otel_channel_wrapper_enabled(monkeypatch):
191+
def test_get_otel_interceptor_enabled(monkeypatch):
193192
monkeypatch.setenv("GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", "true")
194193
mock_tracer_provider = object()
195194
options = ClientOptions(tracer_provider=mock_tracer_provider)
@@ -212,22 +211,22 @@ def test_get_otel_channel_wrapper_enabled(monkeypatch):
212211
sys.modules, "opentelemetry.instrumentation.grpc", mock_otel_grpc
213212
)
214213

215-
wrapper = _observability.get_otel_channel_wrapper(client_options=options)
216-
assert callable(wrapper)
214+
interceptor = _observability.get_otel_interceptor(client_options=options)
215+
assert callable(interceptor)
217216

218217
mock_otel_grpc.client_interceptor.assert_called_once_with(
219218
tracer_provider=mock_tracer_provider
220219
)
221220

222-
result = wrapper(mock_raw_channel)
221+
result = interceptor(mock_raw_channel)
223222
assert result is mock_wrapped_channel
224223
mock_otel_grpc.intercept_channel.assert_called_once_with(
225224
mock_raw_channel, mock_interceptor
226225
)
227226

228227

229-
def test_get_otel_channel_wrapper_with_apply_channel_wrappers(monkeypatch):
230-
"""Proves that get_otel_channel_wrapper integrates seamlessly into apply_channel_wrappers."""
228+
def test_get_otel_interceptor_with_apply_channel_interceptors(monkeypatch):
229+
"""Proves that get_otel_interceptor integrates seamlessly into apply_channel_interceptors."""
231230
pytest.importorskip("grpc")
232231
from google.api_core import grpc_helpers
233232

@@ -253,11 +252,11 @@ def test_get_otel_channel_wrapper_with_apply_channel_wrappers(monkeypatch):
253252
sys.modules, "opentelemetry.instrumentation.grpc", mock_otel_grpc
254253
)
255254

256-
otel_wrapper = _observability.get_otel_channel_wrapper(client_options=options)
257-
assert callable(otel_wrapper)
255+
otel_interceptor = _observability.get_otel_interceptor(client_options=options)
256+
assert callable(otel_interceptor)
258257

259-
result = grpc_helpers.apply_channel_wrappers(
260-
mock_raw_channel, wrappers=[otel_wrapper]
258+
result = grpc_helpers.apply_channel_interceptors(
259+
mock_raw_channel, interceptors=[otel_interceptor]
261260
)
262261
assert result is mock_wrapped_channel
263262
mock_otel_grpc.intercept_channel.assert_called_once_with(

0 commit comments

Comments
 (0)