cledar-sdk 2.0.2__py3-none-any.whl → 2.1.0__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.
- cledar/__init__.py +1 -0
- cledar/kafka/README.md +239 -0
- cledar/kafka/__init__.py +42 -0
- cledar/kafka/clients/base.py +117 -0
- cledar/kafka/clients/consumer.py +138 -0
- cledar/kafka/clients/producer.py +97 -0
- cledar/kafka/config/schemas.py +262 -0
- cledar/kafka/exceptions.py +17 -0
- cledar/kafka/handlers/dead_letter.py +88 -0
- cledar/kafka/handlers/parser.py +83 -0
- cledar/kafka/logger.py +5 -0
- cledar/kafka/models/input.py +17 -0
- cledar/kafka/models/message.py +14 -0
- cledar/kafka/models/output.py +12 -0
- cledar/kafka/tests/.env.test.kafka +3 -0
- cledar/kafka/tests/README.md +216 -0
- cledar/kafka/tests/conftest.py +104 -0
- cledar/kafka/tests/integration/__init__.py +1 -0
- cledar/kafka/tests/integration/conftest.py +78 -0
- cledar/kafka/tests/integration/helpers.py +47 -0
- cledar/kafka/tests/integration/test_consumer_integration.py +375 -0
- cledar/kafka/tests/integration/test_integration.py +394 -0
- cledar/kafka/tests/integration/test_producer_consumer_interaction.py +388 -0
- cledar/kafka/tests/integration/test_producer_integration.py +217 -0
- cledar/kafka/tests/unit/__init__.py +1 -0
- cledar/kafka/tests/unit/test_base_kafka_client.py +391 -0
- cledar/kafka/tests/unit/test_config_validation.py +609 -0
- cledar/kafka/tests/unit/test_dead_letter_handler.py +443 -0
- cledar/kafka/tests/unit/test_error_handling.py +674 -0
- cledar/kafka/tests/unit/test_input_parser.py +310 -0
- cledar/kafka/tests/unit/test_input_parser_comprehensive.py +489 -0
- cledar/kafka/tests/unit/test_utils.py +25 -0
- cledar/kafka/tests/unit/test_utils_comprehensive.py +408 -0
- cledar/kafka/utils/callbacks.py +28 -0
- cledar/kafka/utils/messages.py +39 -0
- cledar/kafka/utils/topics.py +15 -0
- cledar/kserve/README.md +352 -0
- cledar/kserve/__init__.py +5 -0
- cledar/kserve/tests/__init__.py +0 -0
- cledar/kserve/tests/test_utils.py +64 -0
- cledar/kserve/utils.py +30 -0
- cledar/logging/README.md +53 -0
- cledar/logging/__init__.py +5 -0
- cledar/logging/tests/test_universal_plaintext_formatter.py +249 -0
- cledar/logging/universal_plaintext_formatter.py +99 -0
- cledar/monitoring/README.md +71 -0
- cledar/monitoring/__init__.py +5 -0
- cledar/monitoring/monitoring_server.py +156 -0
- cledar/monitoring/tests/integration/test_monitoring_server_int.py +162 -0
- cledar/monitoring/tests/test_monitoring_server.py +59 -0
- cledar/nonce/README.md +99 -0
- cledar/nonce/__init__.py +5 -0
- cledar/nonce/nonce_service.py +62 -0
- cledar/nonce/tests/__init__.py +0 -0
- cledar/nonce/tests/test_nonce_service.py +136 -0
- cledar/redis/README.md +536 -0
- cledar/redis/__init__.py +17 -0
- cledar/redis/async_example.py +112 -0
- cledar/redis/example.py +67 -0
- cledar/redis/exceptions.py +25 -0
- cledar/redis/logger.py +5 -0
- cledar/redis/model.py +14 -0
- cledar/redis/redis.py +764 -0
- cledar/redis/redis_config_store.py +333 -0
- cledar/redis/tests/test_async_integration_redis.py +158 -0
- cledar/redis/tests/test_async_redis_service.py +380 -0
- cledar/redis/tests/test_integration_redis.py +119 -0
- cledar/redis/tests/test_redis_service.py +319 -0
- cledar/storage/README.md +529 -0
- cledar/storage/__init__.py +6 -0
- cledar/storage/constants.py +5 -0
- cledar/storage/exceptions.py +79 -0
- cledar/storage/models.py +41 -0
- cledar/storage/object_storage.py +1274 -0
- cledar/storage/tests/conftest.py +18 -0
- cledar/storage/tests/test_abfs.py +164 -0
- cledar/storage/tests/test_integration_filesystem.py +359 -0
- cledar/storage/tests/test_integration_s3.py +453 -0
- cledar/storage/tests/test_local.py +384 -0
- cledar/storage/tests/test_s3.py +521 -0
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.1.0.dist-info}/METADATA +1 -1
- cledar_sdk-2.1.0.dist-info/RECORD +84 -0
- cledar_sdk-2.0.2.dist-info/RECORD +0 -4
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.1.0.dist-info}/WHEEL +0 -0
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integration tests for Kafka consumer using real Kafka instance.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from collections.abc import Generator
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from cledar.kafka.clients.consumer import KafkaConsumer
|
|
11
|
+
from cledar.kafka.clients.producer import KafkaProducer
|
|
12
|
+
from cledar.kafka.config.schemas import KafkaConsumerConfig, KafkaProducerConfig
|
|
13
|
+
from cledar.kafka.exceptions import KafkaConsumerNotConnectedError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def producer_config(kafka_bootstrap_servers: str) -> KafkaProducerConfig:
|
|
18
|
+
"""Create producer configuration for integration tests."""
|
|
19
|
+
return KafkaProducerConfig(
|
|
20
|
+
kafka_servers=kafka_bootstrap_servers,
|
|
21
|
+
kafka_group_id="integration-test-producer",
|
|
22
|
+
kafka_topic_prefix="integration-test.",
|
|
23
|
+
kafka_block_buffer_time_sec=1,
|
|
24
|
+
kafka_connection_check_timeout_sec=5,
|
|
25
|
+
kafka_connection_check_interval_sec=10,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def consumer_config(kafka_bootstrap_servers: str) -> KafkaConsumerConfig:
|
|
31
|
+
"""Create consumer configuration for integration tests."""
|
|
32
|
+
return KafkaConsumerConfig(
|
|
33
|
+
kafka_servers=kafka_bootstrap_servers,
|
|
34
|
+
kafka_group_id="integration-test-consumer",
|
|
35
|
+
kafka_offset="earliest",
|
|
36
|
+
kafka_topic_prefix="integration-test.",
|
|
37
|
+
kafka_block_consumer_time_sec=1,
|
|
38
|
+
kafka_connection_check_timeout_sec=5,
|
|
39
|
+
kafka_auto_commit_interval_ms=1000,
|
|
40
|
+
kafka_connection_check_interval_sec=10,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.fixture
|
|
45
|
+
def producer(
|
|
46
|
+
producer_config: KafkaProducerConfig,
|
|
47
|
+
) -> Generator[KafkaProducer, None, None]:
|
|
48
|
+
"""Create and connect a Kafka producer."""
|
|
49
|
+
producer = KafkaProducer(producer_config)
|
|
50
|
+
producer.connect()
|
|
51
|
+
yield producer
|
|
52
|
+
producer.shutdown()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.fixture
|
|
56
|
+
def consumer(
|
|
57
|
+
consumer_config: KafkaConsumerConfig,
|
|
58
|
+
) -> Generator[KafkaConsumer, None, None]:
|
|
59
|
+
"""Create and connect a Kafka consumer."""
|
|
60
|
+
consumer = KafkaConsumer(consumer_config)
|
|
61
|
+
consumer.connect()
|
|
62
|
+
yield consumer
|
|
63
|
+
consumer.shutdown()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_consumer_connect_and_subscribe(consumer: KafkaConsumer) -> None:
|
|
67
|
+
"""Test consumer connection and subscription."""
|
|
68
|
+
topic = "test-consumer-basic"
|
|
69
|
+
|
|
70
|
+
# Subscribe to topic
|
|
71
|
+
consumer.subscribe([topic])
|
|
72
|
+
|
|
73
|
+
# Verify consumer is connected
|
|
74
|
+
assert consumer.is_alive()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_consumer_consume_messages(
|
|
78
|
+
producer: KafkaProducer, consumer: KafkaConsumer
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Test consumer message consumption."""
|
|
81
|
+
topic = "test-consumer-consume"
|
|
82
|
+
test_value = '{"id": "test-1", "message": "Hello Consumer!"}'
|
|
83
|
+
test_key = "test-key"
|
|
84
|
+
|
|
85
|
+
# Send message first and subscribe via helper
|
|
86
|
+
producer.send(topic=topic, value=test_value, key=test_key)
|
|
87
|
+
time.sleep(2)
|
|
88
|
+
consumer.subscribe([topic])
|
|
89
|
+
time.sleep(1)
|
|
90
|
+
|
|
91
|
+
# Consume the message
|
|
92
|
+
message = consumer.consume_next()
|
|
93
|
+
|
|
94
|
+
assert message is not None
|
|
95
|
+
assert message.topic == f"integration-test.{topic}"
|
|
96
|
+
assert message.key == test_key
|
|
97
|
+
assert message.value == test_value
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_consumer_consume_multiple_messages(
|
|
101
|
+
producer: KafkaProducer, consumer: KafkaConsumer
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Test consumer consuming multiple messages."""
|
|
104
|
+
topic = "test-consumer-multiple"
|
|
105
|
+
|
|
106
|
+
# Send multiple messages first to create the topic
|
|
107
|
+
messages = []
|
|
108
|
+
for i in range(5):
|
|
109
|
+
test_value = f'{{"id": "test-{i}", "message": "Message {i}"}}'
|
|
110
|
+
test_key = f"key-{i}"
|
|
111
|
+
messages.append((test_value, test_key))
|
|
112
|
+
producer.send(topic=topic, value=test_value, key=test_key)
|
|
113
|
+
|
|
114
|
+
# Wait for topic and subscribe
|
|
115
|
+
time.sleep(3)
|
|
116
|
+
consumer.subscribe([topic])
|
|
117
|
+
time.sleep(1)
|
|
118
|
+
|
|
119
|
+
# Consume all messages
|
|
120
|
+
received_messages = []
|
|
121
|
+
for _ in range(5):
|
|
122
|
+
message = consumer.consume_next()
|
|
123
|
+
if message:
|
|
124
|
+
received_messages.append(message)
|
|
125
|
+
|
|
126
|
+
assert len(received_messages) == 5
|
|
127
|
+
|
|
128
|
+
# Verify message content
|
|
129
|
+
for i, message in enumerate(received_messages):
|
|
130
|
+
assert message.topic == f"integration-test.{topic}"
|
|
131
|
+
assert message.key == f"key-{i}"
|
|
132
|
+
assert message.value == messages[i][0]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_consumer_commit_messages(
|
|
136
|
+
producer: KafkaProducer, consumer: KafkaConsumer
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Test consumer message committing."""
|
|
139
|
+
topic = "test-consumer-commit"
|
|
140
|
+
test_value = '{"id": "test-commit", "message": "Commit test"}'
|
|
141
|
+
test_key = "commit-key"
|
|
142
|
+
|
|
143
|
+
# Send message first, then subscribe
|
|
144
|
+
producer.send(topic=topic, value=test_value, key=test_key)
|
|
145
|
+
time.sleep(2)
|
|
146
|
+
consumer.subscribe([topic])
|
|
147
|
+
time.sleep(1)
|
|
148
|
+
|
|
149
|
+
# Consume the message
|
|
150
|
+
message = consumer.consume_next()
|
|
151
|
+
assert message is not None
|
|
152
|
+
|
|
153
|
+
# Commit the message
|
|
154
|
+
consumer.commit(message)
|
|
155
|
+
|
|
156
|
+
# Verify commit was successful (no exception raised)
|
|
157
|
+
assert True
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_consumer_consume_with_special_characters(
|
|
161
|
+
producer: KafkaProducer, consumer: KafkaConsumer
|
|
162
|
+
) -> None:
|
|
163
|
+
"""Test consumer consuming messages with special characters."""
|
|
164
|
+
topic = "test-consumer-special-chars"
|
|
165
|
+
test_value = '{"id": "test-special", "message": "Special chars: @#$%^&*()"}'
|
|
166
|
+
test_key = "special-key-with-chars: @#$%^&*()"
|
|
167
|
+
|
|
168
|
+
# Send message first, then subscribe
|
|
169
|
+
producer.send(topic=topic, value=test_value, key=test_key)
|
|
170
|
+
time.sleep(2)
|
|
171
|
+
consumer.subscribe([topic])
|
|
172
|
+
time.sleep(1)
|
|
173
|
+
|
|
174
|
+
# Consume the message
|
|
175
|
+
message = consumer.consume_next()
|
|
176
|
+
|
|
177
|
+
assert message is not None
|
|
178
|
+
assert message.value == test_value
|
|
179
|
+
assert message.key == test_key
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test_consumer_consume_with_unicode(
|
|
183
|
+
producer: KafkaProducer, consumer: KafkaConsumer
|
|
184
|
+
) -> None:
|
|
185
|
+
"""Test consumer consuming messages with unicode characters."""
|
|
186
|
+
topic = "test-consumer-unicode"
|
|
187
|
+
test_value = '{"id": "test-unicode", "message": "Unicode: 测试名称"}'
|
|
188
|
+
test_key = "unicode-key-测试"
|
|
189
|
+
|
|
190
|
+
# Send message first to create the topic
|
|
191
|
+
producer.send(topic=topic, value=test_value, key=test_key)
|
|
192
|
+
|
|
193
|
+
# Wait for topic to be created and message to be sent
|
|
194
|
+
time.sleep(2)
|
|
195
|
+
|
|
196
|
+
# Subscribe consumer to topic
|
|
197
|
+
consumer.subscribe([topic])
|
|
198
|
+
|
|
199
|
+
# Wait for subscription to take effect
|
|
200
|
+
time.sleep(1)
|
|
201
|
+
|
|
202
|
+
# Consume the message
|
|
203
|
+
message = consumer.consume_next()
|
|
204
|
+
|
|
205
|
+
assert message is not None
|
|
206
|
+
assert message.value == test_value
|
|
207
|
+
assert message.key == test_key
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_consumer_consume_large_messages(
|
|
211
|
+
producer: KafkaProducer, consumer: KafkaConsumer
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Test consumer consuming large messages."""
|
|
214
|
+
topic = "test-consumer-large"
|
|
215
|
+
|
|
216
|
+
# Create a large message
|
|
217
|
+
large_data = "x" * 10000
|
|
218
|
+
test_value = f'{{"id": "test-large", "data": "{large_data}"}}'
|
|
219
|
+
test_key = "large-key"
|
|
220
|
+
|
|
221
|
+
# Send message first, then subscribe
|
|
222
|
+
producer.send(topic=topic, value=test_value, key=test_key)
|
|
223
|
+
time.sleep(3)
|
|
224
|
+
consumer.subscribe([topic])
|
|
225
|
+
time.sleep(1)
|
|
226
|
+
|
|
227
|
+
# Consume the message
|
|
228
|
+
message = consumer.consume_next()
|
|
229
|
+
|
|
230
|
+
assert message is not None
|
|
231
|
+
assert message.value == test_value
|
|
232
|
+
assert len(message.value) > 10000
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def test_consumer_connection_check(consumer: KafkaConsumer) -> None:
|
|
236
|
+
"""Test consumer connection checking."""
|
|
237
|
+
# Verify consumer is connected
|
|
238
|
+
assert consumer.is_alive()
|
|
239
|
+
|
|
240
|
+
# Check connection explicitly
|
|
241
|
+
consumer.check_connection()
|
|
242
|
+
|
|
243
|
+
# Should not raise any exception
|
|
244
|
+
assert True
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_consumer_not_connected_error() -> None:
|
|
248
|
+
"""Test consumer error when not connected."""
|
|
249
|
+
config = KafkaConsumerConfig(
|
|
250
|
+
kafka_servers="localhost:9092",
|
|
251
|
+
kafka_group_id="test-group",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
consumer = KafkaConsumer(config)
|
|
255
|
+
|
|
256
|
+
# Should raise error when trying to subscribe without connecting
|
|
257
|
+
with pytest.raises(KafkaConsumerNotConnectedError):
|
|
258
|
+
consumer.subscribe(["test-topic"])
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def test_consumer_shutdown(consumer_config: KafkaConsumerConfig) -> None:
|
|
262
|
+
"""Test consumer shutdown."""
|
|
263
|
+
consumer = KafkaConsumer(consumer_config)
|
|
264
|
+
consumer.connect()
|
|
265
|
+
|
|
266
|
+
# Verify consumer is connected
|
|
267
|
+
assert consumer.is_alive()
|
|
268
|
+
|
|
269
|
+
# Shutdown consumer
|
|
270
|
+
consumer.shutdown()
|
|
271
|
+
|
|
272
|
+
# Consumer should be disconnected after shutdown
|
|
273
|
+
assert not consumer.is_alive()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_consumer_topic_prefix(
|
|
277
|
+
producer: KafkaProducer, consumer: KafkaConsumer
|
|
278
|
+
) -> None:
|
|
279
|
+
"""Test consumer topic prefix functionality."""
|
|
280
|
+
topic = "test-prefix"
|
|
281
|
+
test_value = '{"id": "test-prefix", "message": "Prefix test"}'
|
|
282
|
+
test_key = "prefix-key"
|
|
283
|
+
|
|
284
|
+
# Send message first to create the topic
|
|
285
|
+
producer.send(topic=topic, value=test_value, key=test_key)
|
|
286
|
+
|
|
287
|
+
# Wait for topic to be created and message to be sent
|
|
288
|
+
time.sleep(2)
|
|
289
|
+
|
|
290
|
+
# Subscribe consumer to topic (should use prefix from config)
|
|
291
|
+
consumer.subscribe([topic])
|
|
292
|
+
|
|
293
|
+
# Wait for subscription to take effect
|
|
294
|
+
time.sleep(1)
|
|
295
|
+
|
|
296
|
+
# Consume the message
|
|
297
|
+
message = consumer.consume_next()
|
|
298
|
+
|
|
299
|
+
assert message is not None
|
|
300
|
+
assert message.topic == f"integration-test.{topic}"
|
|
301
|
+
assert message.value == test_value
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def test_consumer_offset_behavior(
|
|
305
|
+
producer: KafkaProducer, consumer: KafkaConsumer
|
|
306
|
+
) -> None:
|
|
307
|
+
"""Test consumer offset behavior."""
|
|
308
|
+
topic = "test-consumer-offset"
|
|
309
|
+
|
|
310
|
+
# Send a message first
|
|
311
|
+
test_value = '{"id": "test-offset", "message": "Offset test"}'
|
|
312
|
+
test_key = "offset-key"
|
|
313
|
+
producer.send(topic=topic, value=test_value, key=test_key)
|
|
314
|
+
|
|
315
|
+
# Wait and subscribe (earliest offset)
|
|
316
|
+
time.sleep(2)
|
|
317
|
+
consumer.subscribe([topic])
|
|
318
|
+
time.sleep(1)
|
|
319
|
+
|
|
320
|
+
# Consume the message
|
|
321
|
+
message = consumer.consume_next()
|
|
322
|
+
|
|
323
|
+
assert message is not None
|
|
324
|
+
assert message.value == test_value
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def test_consumer_group_behavior(
|
|
328
|
+
producer: KafkaProducer, consumer_config: KafkaConsumerConfig
|
|
329
|
+
) -> None:
|
|
330
|
+
"""Test consumer group behavior."""
|
|
331
|
+
topic = "test-consumer-group"
|
|
332
|
+
test_value = '{"id": "test-group", "message": "Group test"}'
|
|
333
|
+
test_key = "group-key"
|
|
334
|
+
|
|
335
|
+
# Send a message first to create the topic
|
|
336
|
+
producer.send(topic=topic, value=test_value, key=test_key)
|
|
337
|
+
|
|
338
|
+
# Wait for topic to be created
|
|
339
|
+
time.sleep(2)
|
|
340
|
+
|
|
341
|
+
# Create two consumers with the same group
|
|
342
|
+
consumer1 = KafkaConsumer(consumer_config)
|
|
343
|
+
consumer2 = KafkaConsumer(consumer_config)
|
|
344
|
+
|
|
345
|
+
consumer1.connect()
|
|
346
|
+
consumer2.connect()
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
# Subscribe both consumers to the same topic
|
|
350
|
+
consumer1.subscribe([topic])
|
|
351
|
+
consumer2.subscribe([topic])
|
|
352
|
+
|
|
353
|
+
# Wait for subscription to take effect
|
|
354
|
+
time.sleep(1)
|
|
355
|
+
|
|
356
|
+
# Send another message after consumers are subscribed
|
|
357
|
+
producer.send(topic=topic, value=test_value, key=test_key)
|
|
358
|
+
|
|
359
|
+
# Wait for message to be sent
|
|
360
|
+
time.sleep(1)
|
|
361
|
+
|
|
362
|
+
# Both consumers should be alive
|
|
363
|
+
assert consumer1.is_alive()
|
|
364
|
+
assert consumer2.is_alive()
|
|
365
|
+
|
|
366
|
+
# One of them should consume the message
|
|
367
|
+
message1 = consumer1.consume_next()
|
|
368
|
+
message2 = consumer2.consume_next()
|
|
369
|
+
|
|
370
|
+
# At least one should have received the message
|
|
371
|
+
assert message1 is not None or message2 is not None
|
|
372
|
+
|
|
373
|
+
finally:
|
|
374
|
+
consumer1.shutdown()
|
|
375
|
+
consumer2.shutdown()
|