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.
Files changed (85) hide show
  1. cledar/__init__.py +1 -0
  2. cledar/kafka/README.md +239 -0
  3. cledar/kafka/__init__.py +42 -0
  4. cledar/kafka/clients/base.py +117 -0
  5. cledar/kafka/clients/consumer.py +138 -0
  6. cledar/kafka/clients/producer.py +97 -0
  7. cledar/kafka/config/schemas.py +262 -0
  8. cledar/kafka/exceptions.py +17 -0
  9. cledar/kafka/handlers/dead_letter.py +88 -0
  10. cledar/kafka/handlers/parser.py +83 -0
  11. cledar/kafka/logger.py +5 -0
  12. cledar/kafka/models/input.py +17 -0
  13. cledar/kafka/models/message.py +14 -0
  14. cledar/kafka/models/output.py +12 -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 +28 -0
  35. cledar/kafka/utils/messages.py +39 -0
  36. cledar/kafka/utils/topics.py +15 -0
  37. cledar/kserve/README.md +352 -0
  38. cledar/kserve/__init__.py +5 -0
  39. cledar/kserve/tests/__init__.py +0 -0
  40. cledar/kserve/tests/test_utils.py +64 -0
  41. cledar/kserve/utils.py +30 -0
  42. cledar/logging/README.md +53 -0
  43. cledar/logging/__init__.py +5 -0
  44. cledar/logging/tests/test_universal_plaintext_formatter.py +249 -0
  45. cledar/logging/universal_plaintext_formatter.py +99 -0
  46. cledar/monitoring/README.md +71 -0
  47. cledar/monitoring/__init__.py +5 -0
  48. cledar/monitoring/monitoring_server.py +156 -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 +5 -0
  53. cledar/nonce/nonce_service.py +62 -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 +17 -0
  58. cledar/redis/async_example.py +112 -0
  59. cledar/redis/example.py +67 -0
  60. cledar/redis/exceptions.py +25 -0
  61. cledar/redis/logger.py +5 -0
  62. cledar/redis/model.py +14 -0
  63. cledar/redis/redis.py +764 -0
  64. cledar/redis/redis_config_store.py +333 -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 +6 -0
  71. cledar/storage/constants.py +5 -0
  72. cledar/storage/exceptions.py +79 -0
  73. cledar/storage/models.py +41 -0
  74. cledar/storage/object_storage.py +1274 -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.1.0.dist-info}/METADATA +1 -1
  82. cledar_sdk-2.1.0.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.1.0.dist-info}/WHEEL +0 -0
  85. {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,319 @@
1
+ # mypy: disable-error-code=no-untyped-def
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from datetime import datetime
6
+ from enum import Enum
7
+ from typing import Any
8
+ from unittest.mock import MagicMock, patch
9
+
10
+ import pytest
11
+ import redis
12
+ from pydantic import BaseModel
13
+
14
+ from cledar.redis.redis import (
15
+ CustomEncoder,
16
+ FailedValue,
17
+ RedisService,
18
+ RedisServiceConfig,
19
+ )
20
+
21
+
22
+ class UserModel(BaseModel):
23
+ user_id: int
24
+ name: str
25
+
26
+
27
+ class Color(Enum):
28
+ RED = 1
29
+ BLUE = 2
30
+
31
+
32
+ @pytest.fixture(name="config")
33
+ def fixture_config() -> RedisServiceConfig:
34
+ return RedisServiceConfig(redis_host="localhost", redis_port=6379, redis_db=0)
35
+
36
+
37
+ @pytest.fixture(name="redis_client")
38
+ def fixture_redis_client() -> MagicMock:
39
+ client = MagicMock()
40
+ client.ping.return_value = True
41
+ return client
42
+
43
+
44
+ @pytest.fixture(name="service")
45
+ def fixture_service(
46
+ config: RedisServiceConfig, redis_client: MagicMock
47
+ ) -> RedisService:
48
+ with patch("cledar.redis.redis.redis.Redis", return_value=redis_client):
49
+ return RedisService(config)
50
+
51
+
52
+ def test_connect_success_initializes_client(config: RedisServiceConfig) -> None:
53
+ with patch("cledar.redis.redis.redis.Redis") as redis_instance:
54
+ RedisService(config)
55
+ redis_instance.assert_called_once_with(
56
+ host=config.redis_host,
57
+ port=config.redis_port,
58
+ db=config.redis_db,
59
+ password=config.redis_password,
60
+ decode_responses=True,
61
+ )
62
+
63
+
64
+ def test_connect_failure_raises_connection_error(config: RedisServiceConfig) -> None:
65
+ with patch("cledar.redis.redis.redis.Redis", side_effect=redis.ConnectionError()):
66
+ with pytest.raises(Exception) as exc:
67
+ RedisService(config)
68
+ assert "Could not initialize Redis client" in str(exc.value)
69
+
70
+
71
+ def test_is_alive_true(service: RedisService, redis_client: MagicMock) -> None:
72
+ redis_client.ping.return_value = True
73
+ assert service.is_alive() is True
74
+
75
+
76
+ def test_is_alive_false_on_exception(
77
+ service: RedisService, redis_client: MagicMock
78
+ ) -> None:
79
+ redis_client.ping.side_effect = redis.ConnectionError()
80
+ assert service.is_alive() is False
81
+
82
+
83
+ def test_set_with_pydantic_model_serializes_and_sets(
84
+ service: RedisService, redis_client: MagicMock
85
+ ) -> None:
86
+ model = UserModel(user_id=1, name="Alice")
87
+ redis_client.set.return_value = True
88
+
89
+ result = service.set("user:1", model)
90
+ assert result is True
91
+ value = redis_client.set.call_args.args[1]
92
+
93
+ as_dict = json.loads(value)
94
+ assert as_dict == model.model_dump()
95
+
96
+
97
+ def test_set_with_dict_enum_datetime_uses_custom_encoder(
98
+ service: RedisService, redis_client: MagicMock
99
+ ) -> None:
100
+ now = datetime(2024, 1, 2, 3, 4, 5)
101
+ payload = {"color": Color.RED, "when": now}
102
+ redis_client.set.return_value = True
103
+
104
+ assert service.set("meta", payload) is True
105
+ value = redis_client.set.call_args.args[1]
106
+ as_dict = json.loads(value)
107
+ assert as_dict["color"] == "red"
108
+ assert as_dict["when"] == now.isoformat()
109
+
110
+
111
+ def test_set_serialization_error_raises(service: RedisService) -> None:
112
+ bad = {"x": {1}}
113
+ with pytest.raises(Exception) as exc:
114
+ service.set("k", bad)
115
+ assert "Failed to serialize value" in str(exc.value)
116
+
117
+
118
+ def test_set_connection_error_maps(
119
+ service: RedisService, redis_client: MagicMock
120
+ ) -> None:
121
+ redis_client.set.side_effect = redis.ConnectionError("conn")
122
+ with pytest.raises(Exception) as exc:
123
+ service.set("k", {"a": 1})
124
+ assert "Error connecting to Redis host" in str(exc.value)
125
+
126
+
127
+ def test_set_redis_error_maps(service: RedisService, redis_client: MagicMock) -> None:
128
+ redis_client.set.side_effect = redis.RedisError("oops")
129
+ with pytest.raises(Exception) as exc:
130
+ service.set("k", {"a": 1})
131
+ assert "Failed to set key" in str(exc.value)
132
+
133
+
134
+ def test_get_returns_none_for_missing(
135
+ service: RedisService, redis_client: MagicMock
136
+ ) -> None:
137
+ redis_client.get.return_value = None
138
+ assert service.get("missing", UserModel) is None
139
+
140
+
141
+ def test_get_success_deserializes_to_model(
142
+ service: RedisService, redis_client: MagicMock
143
+ ) -> None:
144
+ model = UserModel(user_id=2, name="Bob")
145
+ redis_client.get.return_value = json.dumps(model.model_dump())
146
+ got = service.get("user:2", UserModel)
147
+ assert isinstance(got, UserModel)
148
+ assert got == model
149
+
150
+
151
+ def test_get_json_decode_error_maps(
152
+ service: RedisService, redis_client: MagicMock
153
+ ) -> None:
154
+ redis_client.get.return_value = "not-json"
155
+ with pytest.raises(Exception) as exc:
156
+ service.get("k", UserModel)
157
+ assert "Failed to decode JSON" in str(exc.value)
158
+
159
+
160
+ def test_get_validation_error_maps(
161
+ service: RedisService, redis_client: MagicMock
162
+ ) -> None:
163
+ redis_client.get.return_value = json.dumps({"user_id": 3})
164
+ with pytest.raises(Exception) as exc:
165
+ service.get("k", UserModel)
166
+ assert "Validation failed" in str(exc.value)
167
+
168
+
169
+ def test_get_connection_error_maps(
170
+ service: RedisService, redis_client: MagicMock
171
+ ) -> None:
172
+ redis_client.get.side_effect = redis.ConnectionError("down")
173
+ with pytest.raises(Exception) as exc:
174
+ service.get("k", UserModel)
175
+ assert "Error connecting to Redis host" in str(exc.value)
176
+
177
+
178
+ def test_get_redis_error_maps(service: RedisService, redis_client: MagicMock) -> None:
179
+ redis_client.get.side_effect = redis.RedisError("nope")
180
+ with pytest.raises(Exception) as exc:
181
+ service.get("k", UserModel)
182
+ assert "Failed to get key" in str(exc.value)
183
+
184
+
185
+ def test_get_raw_returns_value(service: RedisService, redis_client: MagicMock) -> None:
186
+ redis_client.get.return_value = "raw"
187
+ assert service.get_raw("k") == "raw"
188
+
189
+
190
+ def test_get_raw_errors_map(service: RedisService, redis_client: MagicMock) -> None:
191
+ redis_client.get.side_effect = redis.RedisError("err")
192
+ with pytest.raises(Exception) as exc:
193
+ service.get_raw("k")
194
+ assert "Failed to get key" in str(exc.value)
195
+
196
+
197
+ def test_list_keys_success(service: RedisService, redis_client: MagicMock) -> None:
198
+ redis_client.keys.return_value = ["a", "b"]
199
+ assert service.list_keys("*") == ["a", "b"]
200
+
201
+
202
+ def test_list_keys_connection_error(
203
+ service: RedisService, redis_client: MagicMock
204
+ ) -> None:
205
+ redis_client.keys.side_effect = redis.ConnectionError("err")
206
+ with pytest.raises(Exception) as exc:
207
+ service.list_keys("*")
208
+ assert "Error connecting to Redis host" in str(exc.value)
209
+
210
+
211
+ def test_list_keys_redis_error(service: RedisService, redis_client: MagicMock) -> None:
212
+ redis_client.keys.side_effect = redis.RedisError("err")
213
+ with pytest.raises(Exception) as exc:
214
+ service.list_keys("*")
215
+ assert "Failed to list keys" in str(exc.value)
216
+
217
+
218
+ def test_mget_empty_returns_empty(service: RedisService) -> None:
219
+ assert service.mget([], UserModel) == []
220
+
221
+
222
+ def test_mget_success_and_failures(
223
+ service: RedisService, redis_client: MagicMock
224
+ ) -> None:
225
+ good = UserModel(user_id=1, name="A").model_dump()
226
+ bad_json = "{not-json}"
227
+ bad_validation = json.dumps({"user_id": 2})
228
+ none_value = None
229
+ redis_client.mget.return_value = [
230
+ json.dumps(good),
231
+ bad_json,
232
+ bad_validation,
233
+ none_value,
234
+ ]
235
+ keys = ["k1", "k2", "k3", "k4"]
236
+ results = service.mget(keys, UserModel)
237
+
238
+ assert isinstance(results[0], UserModel)
239
+ assert isinstance(results[1], FailedValue)
240
+ assert results[1].key == "k2"
241
+ assert isinstance(results[2], FailedValue)
242
+ assert results[2].key == "k3"
243
+ assert results[3] is None
244
+
245
+
246
+ def test_mget_connection_error_maps(
247
+ service: RedisService, redis_client: MagicMock
248
+ ) -> None:
249
+ redis_client.mget.side_effect = redis.ConnectionError("down")
250
+ with pytest.raises(Exception) as exc:
251
+ service.mget(["a"], UserModel)
252
+ assert "Error connecting to Redis host" in str(exc.value)
253
+
254
+
255
+ def test_mget_redis_error_maps(service: RedisService, redis_client: MagicMock) -> None:
256
+ redis_client.mget.side_effect = redis.RedisError("err")
257
+ with pytest.raises(Exception) as exc:
258
+ service.mget(["a"], UserModel)
259
+ assert "Failed to mget keys" in str(exc.value)
260
+
261
+
262
+ def test_delete_success(service: RedisService, redis_client: MagicMock) -> None:
263
+ redis_client.delete.return_value = 1
264
+ assert service.delete("k") is True
265
+ redis_client.delete.assert_called_once_with("k")
266
+
267
+
268
+ def test_delete_connection_error_maps(
269
+ service: RedisService, redis_client: MagicMock
270
+ ) -> None:
271
+ redis_client.delete.side_effect = redis.ConnectionError("down")
272
+ with pytest.raises(Exception) as exc:
273
+ service.delete("k")
274
+ assert "Error connecting to Redis host" in str(exc.value)
275
+
276
+
277
+ def test_delete_redis_error_maps(
278
+ service: RedisService, redis_client: MagicMock
279
+ ) -> None:
280
+ redis_client.delete.side_effect = redis.RedisError("err")
281
+ with pytest.raises(Exception) as exc:
282
+ service.delete("k")
283
+ assert "Failed to delete key" in str(exc.value)
284
+
285
+
286
+ def test_custom_encoder_direct_usage() -> None:
287
+ payload: dict[str, Any] = {"c": Color.BLUE, "d": datetime(2025, 1, 1, 0, 0, 0)}
288
+ s = json.dumps(payload, cls=CustomEncoder)
289
+ data = json.loads(s)
290
+ assert data["c"] == "blue"
291
+ assert data["d"] == "2025-01-01T00:00:00"
292
+
293
+
294
+ def test_set_plain_string_value(service: RedisService, redis_client: MagicMock) -> None:
295
+ redis_client.set.return_value = True
296
+ assert service.set("greeting", "hello") is True
297
+ value = (
298
+ redis_client.set.call_args.args[1]
299
+ if redis_client.set.call_args.args
300
+ else redis_client.set.call_args[0][1]
301
+ )
302
+ assert value == "hello"
303
+
304
+
305
+ def test_type_validation_errors(service: RedisService) -> None:
306
+ with pytest.raises(ValueError, match="Key must be a string"):
307
+ service.set(123, "x") # type: ignore[arg-type]
308
+
309
+ with pytest.raises(ValueError, match="Key must be a string"):
310
+ service.get(123, UserModel) # type: ignore[arg-type]
311
+
312
+ with pytest.raises(ValueError, match="Key must be a string"):
313
+ service.get_raw(123) # type: ignore[arg-type]
314
+
315
+ with pytest.raises(ValueError, match="Pattern must be a string"):
316
+ service.list_keys(123) # type: ignore[arg-type]
317
+
318
+ with pytest.raises(ValueError, match="Keys must be a list"):
319
+ service.mget("not-a-list", UserModel) # type: ignore[arg-type]