typedkafka 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- typedkafka/__init__.py +53 -0
- typedkafka/admin.py +336 -0
- typedkafka/aio.py +328 -0
- typedkafka/config.py +405 -0
- typedkafka/consumer.py +415 -0
- typedkafka/exceptions.py +130 -0
- typedkafka/producer.py +492 -0
- typedkafka/retry.py +154 -0
- typedkafka/serializers.py +293 -0
- typedkafka/testing.py +523 -0
- typedkafka-0.3.1.dist-info/METADATA +263 -0
- typedkafka-0.3.1.dist-info/RECORD +14 -0
- typedkafka-0.3.1.dist-info/WHEEL +4 -0
- typedkafka-0.3.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Serialization framework for typedkafka.
|
|
3
|
+
|
|
4
|
+
Provides a pluggable serializer/deserializer interface and built-in
|
|
5
|
+
implementations for JSON, String, and Avro (with optional schema registry).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from typing import Any, Generic, Optional, TypeVar
|
|
11
|
+
|
|
12
|
+
from typedkafka.exceptions import SerializationError
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Serializer(ABC, Generic[T]):
|
|
18
|
+
"""
|
|
19
|
+
Abstract base class for message serializers.
|
|
20
|
+
|
|
21
|
+
Implement this interface to create custom serializers for use
|
|
22
|
+
with KafkaProducer.
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
>>> class MySerializer(Serializer[dict]):
|
|
26
|
+
... def serialize(self, topic, value):
|
|
27
|
+
... return json.dumps(value).encode()
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def serialize(self, topic: str, value: T) -> bytes:
|
|
32
|
+
"""
|
|
33
|
+
Serialize a value to bytes.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
topic: The topic the message will be sent to.
|
|
37
|
+
value: The value to serialize.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Serialized bytes.
|
|
41
|
+
"""
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Deserializer(ABC, Generic[T]):
|
|
46
|
+
"""
|
|
47
|
+
Abstract base class for message deserializers.
|
|
48
|
+
|
|
49
|
+
Implement this interface to create custom deserializers for use
|
|
50
|
+
with KafkaConsumer.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
>>> class MyDeserializer(Deserializer[dict]):
|
|
54
|
+
... def deserialize(self, topic, data):
|
|
55
|
+
... return json.loads(data.decode())
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def deserialize(self, topic: str, data: bytes) -> T:
|
|
60
|
+
"""
|
|
61
|
+
Deserialize bytes to a value.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
topic: The topic the message came from.
|
|
65
|
+
data: Raw bytes to deserialize.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Deserialized value.
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class JsonSerializer(Serializer[Any]):
|
|
74
|
+
"""
|
|
75
|
+
JSON serializer that encodes Python objects to UTF-8 JSON bytes.
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
>>> ser = JsonSerializer()
|
|
79
|
+
>>> ser.serialize("topic", {"user_id": 123})
|
|
80
|
+
b'{"user_id": 123}'
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def serialize(self, topic: str, value: Any) -> bytes:
|
|
84
|
+
"""Serialize a value to JSON bytes."""
|
|
85
|
+
try:
|
|
86
|
+
return json.dumps(value).encode("utf-8")
|
|
87
|
+
except (TypeError, ValueError) as e:
|
|
88
|
+
raise SerializationError(
|
|
89
|
+
f"Failed to serialize value to JSON: {e}",
|
|
90
|
+
value=value,
|
|
91
|
+
original_error=e,
|
|
92
|
+
) from e
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class JsonDeserializer(Deserializer[Any]):
|
|
96
|
+
"""
|
|
97
|
+
JSON deserializer that decodes UTF-8 JSON bytes to Python objects.
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
>>> deser = JsonDeserializer()
|
|
101
|
+
>>> deser.deserialize("topic", b'{"user_id": 123}')
|
|
102
|
+
{'user_id': 123}
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def deserialize(self, topic: str, data: bytes) -> Any:
|
|
106
|
+
"""Deserialize JSON bytes to a Python object."""
|
|
107
|
+
try:
|
|
108
|
+
return json.loads(data.decode("utf-8"))
|
|
109
|
+
except (json.JSONDecodeError, UnicodeDecodeError) as e:
|
|
110
|
+
raise SerializationError(
|
|
111
|
+
f"Failed to deserialize JSON: {e}",
|
|
112
|
+
value=data,
|
|
113
|
+
original_error=e,
|
|
114
|
+
) from e
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class StringSerializer(Serializer[str]):
|
|
118
|
+
"""
|
|
119
|
+
String serializer that encodes strings to bytes.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
encoding: Character encoding to use (default: utf-8).
|
|
123
|
+
|
|
124
|
+
Examples:
|
|
125
|
+
>>> ser = StringSerializer()
|
|
126
|
+
>>> ser.serialize("topic", "hello")
|
|
127
|
+
b'hello'
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def __init__(self, encoding: str = "utf-8"):
|
|
131
|
+
self.encoding = encoding
|
|
132
|
+
|
|
133
|
+
def serialize(self, topic: str, value: str) -> bytes:
|
|
134
|
+
"""Serialize a string to bytes."""
|
|
135
|
+
try:
|
|
136
|
+
return value.encode(self.encoding)
|
|
137
|
+
except (UnicodeEncodeError, AttributeError) as e:
|
|
138
|
+
raise SerializationError(
|
|
139
|
+
f"Failed to encode string: {e}",
|
|
140
|
+
value=value,
|
|
141
|
+
original_error=e,
|
|
142
|
+
) from e
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class StringDeserializer(Deserializer[str]):
|
|
146
|
+
"""
|
|
147
|
+
String deserializer that decodes bytes to strings.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
encoding: Character encoding to use (default: utf-8).
|
|
151
|
+
|
|
152
|
+
Examples:
|
|
153
|
+
>>> deser = StringDeserializer()
|
|
154
|
+
>>> deser.deserialize("topic", b"hello")
|
|
155
|
+
'hello'
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(self, encoding: str = "utf-8"):
|
|
159
|
+
self.encoding = encoding
|
|
160
|
+
|
|
161
|
+
def deserialize(self, topic: str, data: bytes) -> str:
|
|
162
|
+
"""Deserialize bytes to a string."""
|
|
163
|
+
try:
|
|
164
|
+
return data.decode(self.encoding)
|
|
165
|
+
except (UnicodeDecodeError, AttributeError) as e:
|
|
166
|
+
raise SerializationError(
|
|
167
|
+
f"Failed to decode bytes as {self.encoding}: {e}",
|
|
168
|
+
value=data,
|
|
169
|
+
original_error=e,
|
|
170
|
+
) from e
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class AvroSerializer(Serializer[Any]):
|
|
174
|
+
"""
|
|
175
|
+
Avro serializer with Confluent Schema Registry support.
|
|
176
|
+
|
|
177
|
+
Requires the ``confluent-kafka[avro]`` or ``fastavro`` package
|
|
178
|
+
and a running Schema Registry.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
schema_registry_url: URL of the Confluent Schema Registry.
|
|
182
|
+
schema_str: Avro schema as a JSON string.
|
|
183
|
+
schema_registry_config: Optional additional config for the schema registry client.
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
ImportError: If required schema registry dependencies are not installed.
|
|
187
|
+
|
|
188
|
+
Examples:
|
|
189
|
+
>>> schema = '{"type": "record", "name": "User", "fields": [{"name": "id", "type": "int"}]}'
|
|
190
|
+
>>> ser = AvroSerializer("http://localhost:8081", schema)
|
|
191
|
+
>>> ser.serialize("users", {"id": 123})
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
def __init__(
|
|
195
|
+
self,
|
|
196
|
+
schema_registry_url: str,
|
|
197
|
+
schema_str: str,
|
|
198
|
+
schema_registry_config: Optional[dict[str, Any]] = None,
|
|
199
|
+
):
|
|
200
|
+
try:
|
|
201
|
+
from confluent_kafka.schema_registry import SchemaRegistryClient
|
|
202
|
+
from confluent_kafka.schema_registry.avro import AvroSerializer as _AvroSerializer
|
|
203
|
+
except ImportError as exc:
|
|
204
|
+
raise ImportError(
|
|
205
|
+
"Schema Registry support requires confluent-kafka[avro]. "
|
|
206
|
+
"Install with: pip install confluent-kafka[avro]"
|
|
207
|
+
) from exc
|
|
208
|
+
|
|
209
|
+
sr_config: dict[str, Any] = {"url": schema_registry_url}
|
|
210
|
+
if schema_registry_config:
|
|
211
|
+
sr_config.update(schema_registry_config)
|
|
212
|
+
|
|
213
|
+
self._registry = SchemaRegistryClient(sr_config)
|
|
214
|
+
self._serializer = _AvroSerializer(
|
|
215
|
+
self._registry,
|
|
216
|
+
schema_str,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def serialize(self, topic: str, value: Any) -> bytes:
|
|
220
|
+
"""Serialize a value using Avro with Schema Registry."""
|
|
221
|
+
try:
|
|
222
|
+
from confluent_kafka.serialization import MessageField, SerializationContext
|
|
223
|
+
|
|
224
|
+
ctx = SerializationContext(topic, MessageField.VALUE)
|
|
225
|
+
return self._serializer(value, ctx)
|
|
226
|
+
except Exception as e:
|
|
227
|
+
raise SerializationError(
|
|
228
|
+
f"Avro serialization failed: {e}",
|
|
229
|
+
value=value,
|
|
230
|
+
original_error=e,
|
|
231
|
+
) from e
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class AvroDeserializer(Deserializer[Any]):
|
|
235
|
+
"""
|
|
236
|
+
Avro deserializer with Confluent Schema Registry support.
|
|
237
|
+
|
|
238
|
+
Requires the ``confluent-kafka[avro]`` package and a running Schema Registry.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
schema_registry_url: URL of the Confluent Schema Registry.
|
|
242
|
+
schema_str: Optional Avro schema as a JSON string. If not provided,
|
|
243
|
+
the schema is fetched from the registry.
|
|
244
|
+
schema_registry_config: Optional additional config for the schema registry client.
|
|
245
|
+
|
|
246
|
+
Raises:
|
|
247
|
+
ImportError: If required schema registry dependencies are not installed.
|
|
248
|
+
|
|
249
|
+
Examples:
|
|
250
|
+
>>> deser = AvroDeserializer("http://localhost:8081")
|
|
251
|
+
>>> data = deser.deserialize("users", raw_bytes)
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
def __init__(
|
|
255
|
+
self,
|
|
256
|
+
schema_registry_url: str,
|
|
257
|
+
schema_str: Optional[str] = None,
|
|
258
|
+
schema_registry_config: Optional[dict[str, Any]] = None,
|
|
259
|
+
):
|
|
260
|
+
try:
|
|
261
|
+
from confluent_kafka.schema_registry import SchemaRegistryClient
|
|
262
|
+
from confluent_kafka.schema_registry.avro import (
|
|
263
|
+
AvroDeserializer as _AvroDeserializer,
|
|
264
|
+
)
|
|
265
|
+
except ImportError as exc:
|
|
266
|
+
raise ImportError(
|
|
267
|
+
"Schema Registry support requires confluent-kafka[avro]. "
|
|
268
|
+
"Install with: pip install confluent-kafka[avro]"
|
|
269
|
+
) from exc
|
|
270
|
+
|
|
271
|
+
sr_config: dict[str, Any] = {"url": schema_registry_url}
|
|
272
|
+
if schema_registry_config:
|
|
273
|
+
sr_config.update(schema_registry_config)
|
|
274
|
+
|
|
275
|
+
self._registry = SchemaRegistryClient(sr_config)
|
|
276
|
+
self._deserializer = _AvroDeserializer(
|
|
277
|
+
self._registry,
|
|
278
|
+
schema_str,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def deserialize(self, topic: str, data: bytes) -> Any:
|
|
282
|
+
"""Deserialize Avro bytes using Schema Registry."""
|
|
283
|
+
try:
|
|
284
|
+
from confluent_kafka.serialization import MessageField, SerializationContext
|
|
285
|
+
|
|
286
|
+
ctx = SerializationContext(topic, MessageField.VALUE)
|
|
287
|
+
return self._deserializer(data, ctx)
|
|
288
|
+
except Exception as e:
|
|
289
|
+
raise SerializationError(
|
|
290
|
+
f"Avro deserialization failed: {e}",
|
|
291
|
+
value=data,
|
|
292
|
+
original_error=e,
|
|
293
|
+
) from e
|