Skip to content

Commit 92eed98

Browse files
committed
enable pyright for aiokafka, fix key type
1 parent 3a05425 commit 92eed98

File tree

8 files changed

+61
-26
lines changed

8 files changed

+61
-26
lines changed

instrumentation/opentelemetry-instrumentation-aiokafka/pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies = [
2929
"opentelemetry-api ~= 1.27",
3030
"opentelemetry-instrumentation == 0.54b0.dev",
3131
"opentelemetry-semantic-conventions == 0.54b0.dev",
32+
"typing_extensions ~= 4.1",
3233
]
3334

3435
[project.optional-dependencies]

instrumentation/opentelemetry-instrumentation-aiokafka/src/opentelemetry/instrumentation/aiokafka/__init__.py

+23-4
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,15 @@ async def async_consume_hook(span, record, args, kwargs):
6969
___
7070
"""
7171

72+
from __future__ import annotations
73+
7274
from asyncio import iscoroutinefunction
73-
from typing import Collection
75+
from typing import TYPE_CHECKING, Collection
7476

7577
import aiokafka
76-
from wrapt import wrap_function_wrapper
78+
from wrapt import (
79+
wrap_function_wrapper, # type: ignore[reportUnknownVariableType]
80+
)
7781

7882
from opentelemetry import trace
7983
from opentelemetry.instrumentation.aiokafka.package import _instruments
@@ -87,6 +91,21 @@ async def async_consume_hook(span, record, args, kwargs):
8791
from opentelemetry.instrumentation.utils import unwrap
8892
from opentelemetry.semconv.schemas import Schemas
8993

94+
if TYPE_CHECKING:
95+
from typing import TypedDict
96+
97+
from typing_extensions import Unpack
98+
99+
from .utils import ConsumeHookT, ProduceHookT
100+
101+
class InstrumentKwargs(TypedDict, total=False):
102+
tracer_provider: trace.TracerProvider
103+
async_produce_hook: ProduceHookT
104+
async_consume_hook: ConsumeHookT
105+
106+
class UninstrumentKwargs(TypedDict, total=False):
107+
pass
108+
90109

91110
class AIOKafkaInstrumentor(BaseInstrumentor):
92111
"""An instrumentor for kafka module
@@ -96,7 +115,7 @@ class AIOKafkaInstrumentor(BaseInstrumentor):
96115
def instrumentation_dependencies(self) -> Collection[str]:
97116
return _instruments
98117

99-
def _instrument(self, **kwargs):
118+
def _instrument(self, **kwargs: Unpack[InstrumentKwargs]):
100119
"""Instruments the kafka module
101120
102121
Args:
@@ -138,7 +157,7 @@ def _instrument(self, **kwargs):
138157
_wrap_getmany(tracer, async_consume_hook),
139158
)
140159

141-
def _uninstrument(self, **kwargs):
160+
def _uninstrument(self, **kwargs: Unpack[UninstrumentKwargs]):
142161
unwrap(aiokafka.AIOKafkaProducer, "send")
143162
unwrap(aiokafka.AIOKafkaConsumer, "getone")
144163
unwrap(aiokafka.AIOKafkaConsumer, "getmany")

instrumentation/opentelemetry-instrumentation-aiokafka/src/opentelemetry/instrumentation/aiokafka/py.typed

Whitespace-only changes.

instrumentation/opentelemetry-instrumentation-aiokafka/src/opentelemetry/instrumentation/aiokafka/utils.py

+31-21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import asyncio
4+
import contextlib
45
import json
56
from logging import getLogger
67
from typing import (
@@ -14,6 +15,7 @@
1415
Protocol,
1516
Sequence,
1617
Tuple,
18+
cast,
1719
)
1820

1921
import aiokafka
@@ -89,7 +91,7 @@ def _extract_client_id(client: aiokafka.AIOKafkaClient) -> str:
8991
def _extract_consumer_group(
9092
consumer: aiokafka.AIOKafkaConsumer,
9193
) -> str | None:
92-
return consumer._group_id
94+
return consumer._group_id # type: ignore[reportUnknownVariableType]
9395

9496

9597
def _extract_argument(
@@ -139,6 +141,17 @@ def _move_headers_to_kwargs(
139141
return args[:5], kwargs
140142

141143

144+
def _deserialize_key(key: object | None) -> str | None:
145+
if key is None:
146+
return None
147+
148+
if isinstance(key, bytes):
149+
with contextlib.suppress(UnicodeDecodeError):
150+
return key.decode()
151+
152+
return str(key)
153+
154+
142155
async def _extract_send_partition(
143156
instance: aiokafka.AIOKafkaProducer,
144157
args: tuple[Any, ...],
@@ -150,17 +163,20 @@ async def _extract_send_partition(
150163
key = _extract_send_key(args, kwargs)
151164
value = _extract_send_value(args, kwargs)
152165
partition = _extract_argument("partition", 3, None, args, kwargs)
153-
key_bytes, value_bytes = instance._serialize(topic, key, value)
166+
key_bytes, value_bytes = cast(
167+
tuple[bytes | None, bytes | None],
168+
instance._serialize(topic, key, value), # type: ignore[reportUnknownMemberType]
169+
)
154170
valid_types = (bytes, bytearray, memoryview, type(None))
155171
if (
156172
type(key_bytes) not in valid_types
157173
or type(value_bytes) not in valid_types
158174
):
159175
return None
160176

161-
await instance.client._wait_on_metadata(topic)
177+
await instance.client._wait_on_metadata(topic) # type: ignore[reportUnknownMemberType]
162178

163-
return instance._partition(
179+
return instance._partition( # type: ignore[reportUnknownMemberType]
164180
topic, partition, key, value, key_bytes, value_bytes
165181
)
166182
except Exception as exception: # pylint: disable=W0703
@@ -170,26 +186,21 @@ async def _extract_send_partition(
170186

171187
class AIOKafkaContextGetter(textmap.Getter["HeadersT"]):
172188
def get(self, carrier: HeadersT, key: str) -> list[str] | None:
173-
if carrier is None:
174-
return None
175-
176189
for item_key, value in carrier:
177190
if item_key == key:
178191
if value is not None:
179192
return [value.decode()]
180193
return None
181194

182195
def keys(self, carrier: HeadersT) -> list[str]:
183-
if carrier is None:
184-
return []
185-
return [key for (key, value) in carrier]
196+
return [key for (key, _) in carrier]
186197

187198

188199
class AIOKafkaContextSetter(textmap.Setter["HeadersT"]):
189200
def set(
190201
self, carrier: HeadersT, key: str | None, value: str | None
191202
) -> None:
192-
if carrier is None or key is None:
203+
if key is None:
193204
return
194205

195206
if not isinstance(carrier, MutableSequence):
@@ -215,7 +226,7 @@ def _enrich_base_span(
215226
client_id: str,
216227
topic: str,
217228
partition: int | None,
218-
key: object | None,
229+
key: str | None,
219230
) -> None:
220231
span.set_attribute(
221232
messaging_attributes.MESSAGING_SYSTEM,
@@ -235,8 +246,7 @@ def _enrich_base_span(
235246

236247
if key is not None:
237248
span.set_attribute(
238-
messaging_attributes.MESSAGING_KAFKA_MESSAGE_KEY,
239-
key, # FIXME: serialize key to str?
249+
messaging_attributes.MESSAGING_KAFKA_MESSAGE_KEY, key
240250
)
241251

242252

@@ -247,7 +257,7 @@ def _enrich_send_span(
247257
client_id: str,
248258
topic: str,
249259
partition: int | None,
250-
key: object | None,
260+
key: str | None,
251261
) -> None:
252262
if not span.is_recording():
253263
return
@@ -276,7 +286,7 @@ def _enrich_getone_span(
276286
consumer_group: str | None,
277287
topic: str,
278288
partition: int | None,
279-
key: object | None,
289+
key: str | None,
280290
offset: int,
281291
) -> None:
282292
if not span.is_recording():
@@ -399,7 +409,7 @@ def _get_span_name(operation: str, topic: str):
399409
return f"{topic} {operation}"
400410

401411

402-
def _wrap_send(
412+
def _wrap_send( # type: ignore[reportUnusedFunction]
403413
tracer: Tracer, async_produce_hook: ProduceHookT
404414
) -> Callable[..., Awaitable[asyncio.Future[RecordMetadata]]]:
405415
async def _traced_send(
@@ -417,7 +427,7 @@ async def _traced_send(
417427
topic = _extract_send_topic(args, kwargs)
418428
bootstrap_servers = _extract_bootstrap_servers(instance.client)
419429
client_id = _extract_client_id(instance.client)
420-
key = _extract_send_key(args, kwargs)
430+
key = _deserialize_key(_extract_send_key(args, kwargs))
421431
partition = await _extract_send_partition(instance, args, kwargs)
422432
span_name = _get_span_name("send", topic)
423433
with tracer.start_as_current_span(
@@ -473,7 +483,7 @@ async def _create_consumer_span(
473483
consumer_group=consumer_group,
474484
topic=record.topic,
475485
partition=record.partition,
476-
key=record.key,
486+
key=_deserialize_key(record.key),
477487
offset=record.offset,
478488
)
479489
try:
@@ -486,7 +496,7 @@ async def _create_consumer_span(
486496
return span
487497

488498

489-
def _wrap_getone(
499+
def _wrap_getone( # type: ignore[reportUnusedFunction]
490500
tracer: Tracer, async_consume_hook: ConsumeHookT
491501
) -> Callable[..., Awaitable[aiokafka.ConsumerRecord[object, object]]]:
492502
async def _traced_getone(
@@ -521,7 +531,7 @@ async def _traced_getone(
521531
return _traced_getone
522532

523533

524-
def _wrap_getmany(
534+
def _wrap_getmany( # type: ignore[reportUnusedFunction]
525535
tracer: Tracer, async_consume_hook: ConsumeHookT
526536
) -> Callable[
527537
...,

instrumentation/opentelemetry-instrumentation-aiokafka/tests/test_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ async def test_create_consumer_span(
360360
consumer_group=consumer_group,
361361
topic=record.topic,
362362
partition=record.partition,
363-
key=record.key,
363+
key=str(record.key),
364364
offset=record.offset,
365365
)
366366
consume_hook.assert_awaited_once_with(

pyproject.toml

+2
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,15 @@ pythonVersion = "3.8"
196196
reportPrivateUsage = false # Ignore private attributes added by instrumentation packages.
197197
# Add progressively instrumentation packages here.
198198
include = [
199+
"instrumentation/opentelemetry-instrumentation-aiokafka",
199200
"instrumentation/opentelemetry-instrumentation-asyncclick",
200201
"instrumentation/opentelemetry-instrumentation-threading",
201202
"instrumentation-genai/opentelemetry-instrumentation-vertexai",
202203
]
203204
# We should also add type hints to the test suite - It helps on finding bugs.
204205
# We are excluding for now because it's easier, and more important to add to the instrumentation packages.
205206
exclude = [
207+
"instrumentation/opentelemetry-instrumentation-aiokafka/tests/**/*.py",
206208
"instrumentation/opentelemetry-instrumentation-asyncclick/tests/**/*.py",
207209
"instrumentation/opentelemetry-instrumentation-threading/tests/**",
208210
"instrumentation-genai/opentelemetry-instrumentation-vertexai/tests/**/*.py",

tox.ini

+1
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,7 @@ deps =
10471047
{toxinidir}/util/opentelemetry-util-http
10481048
{toxinidir}/instrumentation-genai/opentelemetry-instrumentation-vertexai[instruments]
10491049
{toxinidir}/instrumentation-genai/opentelemetry-instrumentation-google-genai[instruments]
1050+
{toxinidir}/instrumentation/opentelemetry-instrumentation-aiokafka[instruments]
10501051
{toxinidir}/instrumentation/opentelemetry-instrumentation-asyncclick[instruments]
10511052

10521053
commands =

uv.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)