cledar-sdk 2.0.2__py3-none-any.whl → 2.0.3__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.
Files changed (85) hide show
  1. cledar/__init__.py +0 -0
  2. cledar/kafka/README.md +239 -0
  3. cledar/kafka/__init__.py +40 -0
  4. cledar/kafka/clients/base.py +98 -0
  5. cledar/kafka/clients/consumer.py +110 -0
  6. cledar/kafka/clients/producer.py +80 -0
  7. cledar/kafka/config/schemas.py +178 -0
  8. cledar/kafka/exceptions.py +22 -0
  9. cledar/kafka/handlers/dead_letter.py +82 -0
  10. cledar/kafka/handlers/parser.py +49 -0
  11. cledar/kafka/logger.py +3 -0
  12. cledar/kafka/models/input.py +13 -0
  13. cledar/kafka/models/message.py +10 -0
  14. cledar/kafka/models/output.py +8 -0
  15. cledar/kafka/tests/.env.test.kafka +3 -0
  16. cledar/kafka/tests/README.md +216 -0
  17. cledar/kafka/tests/conftest.py +104 -0
  18. cledar/kafka/tests/integration/__init__.py +1 -0
  19. cledar/kafka/tests/integration/conftest.py +78 -0
  20. cledar/kafka/tests/integration/helpers.py +47 -0
  21. cledar/kafka/tests/integration/test_consumer_integration.py +375 -0
  22. cledar/kafka/tests/integration/test_integration.py +394 -0
  23. cledar/kafka/tests/integration/test_producer_consumer_interaction.py +388 -0
  24. cledar/kafka/tests/integration/test_producer_integration.py +217 -0
  25. cledar/kafka/tests/unit/__init__.py +1 -0
  26. cledar/kafka/tests/unit/test_base_kafka_client.py +391 -0
  27. cledar/kafka/tests/unit/test_config_validation.py +609 -0
  28. cledar/kafka/tests/unit/test_dead_letter_handler.py +443 -0
  29. cledar/kafka/tests/unit/test_error_handling.py +674 -0
  30. cledar/kafka/tests/unit/test_input_parser.py +310 -0
  31. cledar/kafka/tests/unit/test_input_parser_comprehensive.py +489 -0
  32. cledar/kafka/tests/unit/test_utils.py +25 -0
  33. cledar/kafka/tests/unit/test_utils_comprehensive.py +408 -0
  34. cledar/kafka/utils/callbacks.py +19 -0
  35. cledar/kafka/utils/messages.py +28 -0
  36. cledar/kafka/utils/topics.py +2 -0
  37. cledar/kserve/README.md +352 -0
  38. cledar/kserve/__init__.py +3 -0
  39. cledar/kserve/tests/__init__.py +0 -0
  40. cledar/kserve/tests/test_utils.py +64 -0
  41. cledar/kserve/utils.py +27 -0
  42. cledar/logging/README.md +53 -0
  43. cledar/logging/__init__.py +3 -0
  44. cledar/logging/tests/test_universal_plaintext_formatter.py +249 -0
  45. cledar/logging/universal_plaintext_formatter.py +94 -0
  46. cledar/monitoring/README.md +71 -0
  47. cledar/monitoring/__init__.py +3 -0
  48. cledar/monitoring/monitoring_server.py +112 -0
  49. cledar/monitoring/tests/integration/test_monitoring_server_int.py +162 -0
  50. cledar/monitoring/tests/test_monitoring_server.py +59 -0
  51. cledar/nonce/README.md +99 -0
  52. cledar/nonce/__init__.py +3 -0
  53. cledar/nonce/nonce_service.py +36 -0
  54. cledar/nonce/tests/__init__.py +0 -0
  55. cledar/nonce/tests/test_nonce_service.py +136 -0
  56. cledar/redis/README.md +536 -0
  57. cledar/redis/__init__.py +15 -0
  58. cledar/redis/async_example.py +111 -0
  59. cledar/redis/example.py +37 -0
  60. cledar/redis/exceptions.py +22 -0
  61. cledar/redis/logger.py +3 -0
  62. cledar/redis/model.py +10 -0
  63. cledar/redis/redis.py +525 -0
  64. cledar/redis/redis_config_store.py +252 -0
  65. cledar/redis/tests/test_async_integration_redis.py +158 -0
  66. cledar/redis/tests/test_async_redis_service.py +380 -0
  67. cledar/redis/tests/test_integration_redis.py +119 -0
  68. cledar/redis/tests/test_redis_service.py +319 -0
  69. cledar/storage/README.md +529 -0
  70. cledar/storage/__init__.py +4 -0
  71. cledar/storage/constants.py +3 -0
  72. cledar/storage/exceptions.py +50 -0
  73. cledar/storage/models.py +19 -0
  74. cledar/storage/object_storage.py +955 -0
  75. cledar/storage/tests/conftest.py +18 -0
  76. cledar/storage/tests/test_abfs.py +164 -0
  77. cledar/storage/tests/test_integration_filesystem.py +359 -0
  78. cledar/storage/tests/test_integration_s3.py +453 -0
  79. cledar/storage/tests/test_local.py +384 -0
  80. cledar/storage/tests/test_s3.py +521 -0
  81. {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.0.3.dist-info}/METADATA +1 -1
  82. cledar_sdk-2.0.3.dist-info/RECORD +84 -0
  83. cledar_sdk-2.0.2.dist-info/RECORD +0 -4
  84. {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.0.3.dist-info}/WHEEL +0 -0
  85. {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.0.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,453 @@
1
+ # mypy: disable-error-code=no-untyped-def
2
+ import io
3
+ import tempfile
4
+ from collections.abc import Generator
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+ from faker import Faker
9
+ from testcontainers.minio import MinioContainer
10
+
11
+ from cledar.storage.exceptions import ReadFileError, RequiredBucketNotFoundError
12
+ from cledar.storage.models import ObjectStorageServiceConfig
13
+ from cledar.storage.object_storage import ObjectStorageService
14
+
15
+ fake = Faker()
16
+
17
+
18
+ @pytest.fixture(scope="module")
19
+ def minio_container():
20
+ """
21
+ Start a MinIO container for testing.
22
+ """
23
+ with MinioContainer(
24
+ access_key="minioadmin",
25
+ secret_key="minioadmin",
26
+ ) as minio:
27
+ yield minio
28
+
29
+
30
+ @pytest.fixture(scope="module")
31
+ def object_storage_service(minio_container: MinioContainer) -> ObjectStorageService:
32
+ """
33
+ Create an ObjectStorageService connected to MinIO testcontainer.
34
+ """
35
+ host = minio_container.get_container_host_ip()
36
+ port = minio_container.get_exposed_port(minio_container.port)
37
+ endpoint_url = f"http://{host}:{port}"
38
+
39
+ config = ObjectStorageServiceConfig(
40
+ s3_endpoint_url=endpoint_url,
41
+ s3_access_key=minio_container.access_key,
42
+ s3_secret_key=minio_container.secret_key,
43
+ s3_max_concurrency=10,
44
+ )
45
+ return ObjectStorageService(config)
46
+
47
+
48
+ @pytest.fixture
49
+ def test_bucket(
50
+ object_storage_service: ObjectStorageService,
51
+ ) -> Generator[str, None, None]:
52
+ """
53
+ Create a test bucket and clean it up after test.
54
+ """
55
+ bucket_name = f"test-bucket-{str(fake.uuid4())}"
56
+ object_storage_service.client.mkdir(f"s3://{bucket_name}")
57
+ yield bucket_name
58
+ objects = object_storage_service.list_objects(bucket=bucket_name, recursive=True)
59
+ for obj in objects:
60
+ object_storage_service.delete_file(bucket=bucket_name, key=obj)
61
+ object_storage_service.client.rmdir(bucket_name)
62
+
63
+
64
+ def test_is_alive(object_storage_service: ObjectStorageService) -> None:
65
+ """
66
+ Test that the service can connect to MinIO.
67
+ """
68
+ assert object_storage_service.is_alive() is True
69
+
70
+
71
+ def test_has_bucket_exists(
72
+ object_storage_service: ObjectStorageService, test_bucket: str
73
+ ) -> None:
74
+ """
75
+ Test checking if a bucket exists.
76
+ """
77
+ result = object_storage_service.has_bucket(bucket=test_bucket)
78
+ assert result is True
79
+
80
+
81
+ def test_has_bucket_not_exists(
82
+ object_storage_service: ObjectStorageService,
83
+ ) -> None:
84
+ """
85
+ Test checking if a non-existent bucket does not exist.
86
+ """
87
+ non_existent_bucket = f"non-existent-{str(fake.uuid4())}"
88
+ result = object_storage_service.has_bucket(bucket=non_existent_bucket)
89
+ assert result is False
90
+
91
+
92
+ def test_has_bucket_throw_not_exists(
93
+ object_storage_service: ObjectStorageService,
94
+ ) -> None:
95
+ """
96
+ Test that has_bucket throws exception when throw=True.
97
+ """
98
+ non_existent_bucket = f"non-existent-{str(fake.uuid4())}"
99
+ with pytest.raises(RequiredBucketNotFoundError):
100
+ object_storage_service.has_bucket(bucket=non_existent_bucket, throw=True)
101
+
102
+
103
+ def test_upload_and_read_buffer(
104
+ object_storage_service: ObjectStorageService, test_bucket: str
105
+ ) -> None:
106
+ """
107
+ Test uploading a buffer and reading it back.
108
+ """
109
+ test_content = fake.text().encode()
110
+ buffer = io.BytesIO(test_content)
111
+ key = f"test/buffer/{fake.file_name()}"
112
+
113
+ object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
114
+
115
+ result = object_storage_service.read_file(bucket=test_bucket, key=key)
116
+ assert result == test_content
117
+
118
+
119
+ def test_upload_buffer_invalid_params(
120
+ object_storage_service: ObjectStorageService,
121
+ ) -> None:
122
+ """
123
+ Test that upload_buffer raises error with invalid params.
124
+ """
125
+ buffer = io.BytesIO(b"test")
126
+ with pytest.raises(
127
+ ValueError,
128
+ match="Either destination_path or bucket and key must be provided",
129
+ ):
130
+ object_storage_service.upload_buffer(buffer=buffer)
131
+
132
+
133
+ def test_upload_and_download_file(
134
+ object_storage_service: ObjectStorageService, test_bucket: str
135
+ ) -> None:
136
+ """
137
+ Test uploading a file and downloading it.
138
+ """
139
+ test_content = fake.text().encode()
140
+ key = f"test/files/{fake.file_name()}"
141
+
142
+ with tempfile.NamedTemporaryFile(mode="wb", delete=False) as temp_file:
143
+ temp_file.write(test_content)
144
+ temp_file_path = temp_file.name
145
+
146
+ try:
147
+ object_storage_service.upload_file(
148
+ file_path=temp_file_path, bucket=test_bucket, key=key
149
+ )
150
+
151
+ with tempfile.NamedTemporaryFile(mode="rb", delete=False) as download_file:
152
+ download_path = download_file.name
153
+
154
+ try:
155
+ object_storage_service.download_file(
156
+ dest_path=download_path, bucket=test_bucket, key=key
157
+ )
158
+
159
+ with open(download_path, "rb") as f:
160
+ downloaded_content = f.read()
161
+ assert downloaded_content == test_content
162
+ finally:
163
+ Path(download_path).unlink(missing_ok=True)
164
+ finally:
165
+ Path(temp_file_path).unlink(missing_ok=True)
166
+
167
+
168
+ def test_read_file_retry_mechanism(
169
+ object_storage_service: ObjectStorageService, test_bucket: str
170
+ ) -> None:
171
+ """
172
+ Test that read_file succeeds with valid file.
173
+ """
174
+ test_content = fake.text().encode()
175
+ buffer = io.BytesIO(test_content)
176
+ key = f"test/retry/{fake.file_name()}"
177
+
178
+ object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
179
+
180
+ result = object_storage_service.read_file(bucket=test_bucket, key=key, max_tries=3)
181
+ assert result == test_content
182
+
183
+
184
+ def test_list_objects_recursive(
185
+ object_storage_service: ObjectStorageService, test_bucket: str
186
+ ) -> None:
187
+ """
188
+ Test listing objects recursively.
189
+ """
190
+ files = [
191
+ "folder1/file1.txt",
192
+ "folder1/file2.txt",
193
+ "folder1/subfolder/file3.txt",
194
+ "folder2/file4.txt",
195
+ ]
196
+
197
+ for file_key in files:
198
+ buffer = io.BytesIO(fake.text().encode())
199
+ object_storage_service.upload_buffer(
200
+ buffer=buffer, bucket=test_bucket, key=file_key
201
+ )
202
+ result = object_storage_service.list_objects(bucket=test_bucket, recursive=True)
203
+
204
+ files_only = [r for r in result if not r.endswith("/")]
205
+
206
+ assert len(files_only) >= 4
207
+ for file_key in files:
208
+ assert file_key in result or file_key in files_only
209
+
210
+
211
+ def test_list_objects_with_prefix(
212
+ object_storage_service: ObjectStorageService, test_bucket: str
213
+ ) -> None:
214
+ """
215
+ Test listing objects with prefix filter.
216
+ """
217
+ files = [
218
+ "prefix1/file1.txt",
219
+ "prefix1/file2.txt",
220
+ "prefix2/file3.txt",
221
+ ]
222
+
223
+ for file_key in files:
224
+ buffer = io.BytesIO(fake.text().encode())
225
+ object_storage_service.upload_buffer(
226
+ buffer=buffer, bucket=test_bucket, key=file_key
227
+ )
228
+
229
+ result = object_storage_service.list_objects(
230
+ bucket=test_bucket, prefix="prefix1/", recursive=True
231
+ )
232
+
233
+ files_only = [r for r in result if not r.endswith("/")]
234
+
235
+ assert len(files_only) >= 2
236
+ assert "prefix1/file1.txt" in result or "prefix1/file1.txt" in files_only
237
+ assert "prefix1/file2.txt" in result or "prefix1/file2.txt" in files_only
238
+ assert "prefix2/file3.txt" not in result
239
+
240
+
241
+ def test_list_objects_non_recursive(
242
+ object_storage_service: ObjectStorageService, test_bucket: str
243
+ ) -> None:
244
+ """
245
+ Test listing objects non-recursively.
246
+ """
247
+ files = [
248
+ "root1.txt",
249
+ "root2.txt",
250
+ "folder/nested.txt",
251
+ ]
252
+
253
+ for file_key in files:
254
+ buffer = io.BytesIO(fake.text().encode())
255
+ object_storage_service.upload_buffer(
256
+ buffer=buffer, bucket=test_bucket, key=file_key
257
+ )
258
+
259
+ result = object_storage_service.list_objects(bucket=test_bucket, recursive=False)
260
+
261
+ assert len(result) >= 1
262
+ has_root_file = any("root" in r for r in result)
263
+ assert has_root_file or len(result) > 0
264
+
265
+
266
+ def test_file_exists(
267
+ object_storage_service: ObjectStorageService, test_bucket: str
268
+ ) -> None:
269
+ """
270
+ Test checking if file exists.
271
+ """
272
+ key = f"test/exists/{fake.file_name()}"
273
+
274
+ assert object_storage_service.file_exists(bucket=test_bucket, key=key) is False
275
+
276
+ buffer = io.BytesIO(fake.text().encode())
277
+ object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
278
+
279
+ assert object_storage_service.file_exists(bucket=test_bucket, key=key) is True
280
+
281
+
282
+ def test_delete_file(
283
+ object_storage_service: ObjectStorageService, test_bucket: str
284
+ ) -> None:
285
+ """
286
+ Test deleting a file.
287
+ """
288
+ key = f"test/delete/{fake.file_name()}"
289
+
290
+ buffer = io.BytesIO(fake.text().encode())
291
+ object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
292
+
293
+ assert object_storage_service.file_exists(bucket=test_bucket, key=key) is True
294
+
295
+ object_storage_service.delete_file(bucket=test_bucket, key=key)
296
+
297
+ assert object_storage_service.file_exists(bucket=test_bucket, key=key) is False
298
+
299
+
300
+ def test_get_file_size(
301
+ object_storage_service: ObjectStorageService, test_bucket: str
302
+ ) -> None:
303
+ """
304
+ Test getting file size.
305
+ """
306
+ test_content = fake.text().encode()
307
+ key = f"test/size/{fake.file_name()}"
308
+
309
+ buffer = io.BytesIO(test_content)
310
+ object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
311
+
312
+ size = object_storage_service.get_file_size(bucket=test_bucket, key=key)
313
+ assert size == len(test_content)
314
+
315
+
316
+ def test_get_file_info(
317
+ object_storage_service: ObjectStorageService, test_bucket: str
318
+ ) -> None:
319
+ """
320
+ Test getting file metadata.
321
+ """
322
+ test_content = fake.text().encode()
323
+ key = f"test/info/{fake.file_name()}"
324
+
325
+ buffer = io.BytesIO(test_content)
326
+ object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
327
+
328
+ info = object_storage_service.get_file_info(bucket=test_bucket, key=key)
329
+
330
+ assert "size" in info
331
+ assert info["size"] == len(test_content)
332
+ assert info is not None
333
+
334
+
335
+ def test_copy_file(
336
+ object_storage_service: ObjectStorageService, test_bucket: str
337
+ ) -> None:
338
+ """
339
+ Test copying a file.
340
+ """
341
+ test_content = fake.text().encode()
342
+ source_key = f"test/copy/source/{fake.file_name()}"
343
+ dest_key = f"test/copy/dest/{fake.file_name()}"
344
+
345
+ buffer = io.BytesIO(test_content)
346
+ object_storage_service.upload_buffer(
347
+ buffer=buffer, bucket=test_bucket, key=source_key
348
+ )
349
+
350
+ object_storage_service.copy_file(
351
+ source_bucket=test_bucket,
352
+ source_key=source_key,
353
+ dest_bucket=test_bucket,
354
+ dest_key=dest_key,
355
+ )
356
+
357
+ assert (
358
+ object_storage_service.file_exists(bucket=test_bucket, key=source_key) is True
359
+ )
360
+ assert object_storage_service.file_exists(bucket=test_bucket, key=dest_key) is True
361
+
362
+ dest_content = object_storage_service.read_file(bucket=test_bucket, key=dest_key)
363
+ assert dest_content == test_content
364
+
365
+
366
+ def test_move_file(
367
+ object_storage_service: ObjectStorageService, test_bucket: str
368
+ ) -> None:
369
+ """
370
+ Test moving a file.
371
+ """
372
+ test_content = fake.text().encode()
373
+ source_key = f"test/move/source/{fake.file_name()}"
374
+ dest_key = f"test/move/dest/{fake.file_name()}"
375
+
376
+ buffer = io.BytesIO(test_content)
377
+ object_storage_service.upload_buffer(
378
+ buffer=buffer, bucket=test_bucket, key=source_key
379
+ )
380
+
381
+ object_storage_service.move_file(
382
+ source_bucket=test_bucket,
383
+ source_key=source_key,
384
+ dest_bucket=test_bucket,
385
+ dest_key=dest_key,
386
+ )
387
+
388
+ assert (
389
+ object_storage_service.file_exists(bucket=test_bucket, key=source_key) is False
390
+ )
391
+ assert object_storage_service.file_exists(bucket=test_bucket, key=dest_key) is True
392
+
393
+ dest_content = object_storage_service.read_file(bucket=test_bucket, key=dest_key)
394
+ assert dest_content == test_content
395
+
396
+
397
+ def test_read_nonexistent_file(
398
+ object_storage_service: ObjectStorageService, test_bucket: str
399
+ ) -> None:
400
+ """
401
+ Test reading a file that doesn't exist.
402
+ """
403
+ non_existent_key = f"test/nonexistent/{str(fake.uuid4())}.txt"
404
+
405
+ with pytest.raises(ReadFileError):
406
+ object_storage_service.read_file(
407
+ bucket=test_bucket, key=non_existent_key, max_tries=1
408
+ )
409
+
410
+
411
+ def test_invalid_parameters(
412
+ object_storage_service: ObjectStorageService,
413
+ ) -> None:
414
+ """
415
+ Test operations with invalid parameters.
416
+ """
417
+ with pytest.raises(
418
+ ValueError, match="Either path or bucket and key must be provided"
419
+ ):
420
+ object_storage_service.read_file()
421
+
422
+ with pytest.raises(
423
+ ValueError,
424
+ match="Either destination_path or bucket and key must be provided",
425
+ ):
426
+ object_storage_service.upload_buffer(buffer=io.BytesIO(b"test"))
427
+
428
+ with pytest.raises(
429
+ ValueError,
430
+ match="Either source_path or source_bucket and source_key must be provided",
431
+ ):
432
+ object_storage_service.copy_file(dest_bucket="test", dest_key="test")
433
+
434
+
435
+ def test_large_file_upload_download(
436
+ object_storage_service: ObjectStorageService, test_bucket: str
437
+ ) -> None:
438
+ """
439
+ Test uploading and downloading a larger file (10MB).
440
+ """
441
+ size_mb = 10
442
+ test_content = fake.binary(length=size_mb * 1024 * 1024)
443
+ key = f"test/large/{fake.file_name()}"
444
+
445
+ buffer = io.BytesIO(test_content)
446
+ object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
447
+
448
+ size = object_storage_service.get_file_size(bucket=test_bucket, key=key)
449
+ assert size == len(test_content)
450
+
451
+ result = object_storage_service.read_file(bucket=test_bucket, key=key)
452
+ assert result == test_content
453
+ assert len(result) == size_mb * 1024 * 1024