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
cledar/redis/README.md ADDED
@@ -0,0 +1,536 @@
1
+ # Redis Service
2
+
3
+ ## Purpose
4
+
5
+ The `cledar.redis` package provides a typed, high-level interface over Redis for simple key/value storage with JSON serialization, plus helpers for bulk reads and a lightweight configuration store built on Redis pub/sub and keyspace notifications.
6
+
7
+ ### Key Features
8
+
9
+ - **Typed API with Pydantic**: Validate JSON payloads into Pydantic models on read
10
+ - **Async/Sync Support**: Both `AsyncRedisService` (async/await) and `RedisService` (synchronous) available
11
+ - **Safe Serialization**: Custom JSON encoder for `Enum` (to lowercase names) and `datetime` (ISO 8601)
12
+ - **Ergonomic Helpers**: `get`, `get_raw`, `set`, `list_keys`, `mget`, `delete`
13
+ - **Bulk Reads**: `mget` returns a list with typed results, `None`, or `FailedValue` for per-key errors
14
+ - **Error Mapping**: Consistent custom exceptions for connection, serialization, deserialization, and operation errors
15
+ - **Config Store**: `RedisConfigStore` with local cache, version tracking, and watchers via keyspace events
16
+ - **Well Tested**: Fast unit tests and Redis-backed integration tests
17
+
18
+ ### Use Cases
19
+
20
+ - Caching results of computations as JSON documents
21
+ - Persisting lightweight application state across processes
22
+ - Reading and writing typed configuration objects
23
+ - Bulk retrieval of many keys while tolerating per-key failures
24
+ - Observing and reacting to configuration changes in near real-time
25
+ - Asynchronous I/O for high-performance applications (FastAPI, aiohttp, etc.)
26
+
27
+ ## Installation
28
+
29
+ This package is part of the `cledar-python-sdk`. Install dependencies using:
30
+
31
+ ```bash
32
+ # Install with uv (recommended)
33
+ uv sync --all-groups
34
+
35
+ # Or with pip (editable install from repo root)
36
+ pip install -e .
37
+ ```
38
+
39
+ ## Usage Examples
40
+
41
+ ### Synchronous Usage
42
+
43
+ ```python
44
+ from pydantic import BaseModel
45
+ from cledar.redis import RedisService, RedisServiceConfig
46
+
47
+
48
+ class UserModel(BaseModel):
49
+ user_id: int
50
+ name: str
51
+
52
+
53
+ # Configure and create service
54
+ config = RedisServiceConfig(
55
+ redis_host="localhost",
56
+ redis_port=6379,
57
+ redis_db=0,
58
+ )
59
+ service = RedisService(config)
60
+
61
+ # Health check
62
+ assert service.is_alive() is True
63
+
64
+ # Write a typed value (automatically serialized to JSON)
65
+ user = UserModel(user_id=1, name="Alice")
66
+ service.set("user:1", user)
67
+
68
+ # Read and validate back into the model
69
+ loaded = service.get("user:1", UserModel)
70
+ print(loaded) # UserModel(user_id=1, name='Alice')
71
+
72
+ # Raw access (no validation/decoding beyond Redis decode_responses)
73
+ service.set("greeting", "hello")
74
+ print(service.get_raw("greeting")) # "hello"
75
+
76
+ # List keys by pattern and bulk-fetch
77
+ keys = service.list_keys("user:*")
78
+ bulk = service.mget(keys, UserModel)
79
+ # bulk is a list of UserModel | None | FailedValue
80
+
81
+ # Delete
82
+ service.delete("greeting")
83
+ ```
84
+
85
+ ### Asynchronous Usage
86
+
87
+ ```python
88
+ import asyncio
89
+ from pydantic import BaseModel
90
+ from cledar.redis import AsyncRedisService, RedisServiceConfig
91
+
92
+
93
+ class UserModel(BaseModel):
94
+ user_id: int
95
+ name: str
96
+
97
+
98
+ async def main():
99
+ # Configure and create async service
100
+ config = RedisServiceConfig(
101
+ redis_host="localhost",
102
+ redis_port=6379,
103
+ redis_db=0,
104
+ )
105
+ service = AsyncRedisService(config)
106
+ await service.connect()
107
+
108
+ try:
109
+ # Health check
110
+ assert await service.is_alive() is True
111
+
112
+ # Write a typed value (automatically serialized to JSON)
113
+ user = UserModel(user_id=1, name="Alice")
114
+ await service.set("user:1", user)
115
+
116
+ # Read and validate back into the model
117
+ loaded = await service.get("user:1", UserModel)
118
+ print(loaded) # UserModel(user_id=1, name='Alice')
119
+
120
+ # Raw access (no validation/decoding beyond Redis decode_responses)
121
+ await service.set("greeting", "hello")
122
+ print(await service.get_raw("greeting")) # "hello"
123
+
124
+ # List keys by pattern and bulk-fetch
125
+ keys = await service.list_keys("user:*")
126
+ bulk = await service.mget(keys, UserModel)
127
+ # bulk is a list of UserModel | None | FailedValue
128
+
129
+ # Delete
130
+ await service.delete("greeting")
131
+
132
+ finally:
133
+ # Always close the connection
134
+ await service.close()
135
+
136
+
137
+ if __name__ == "__main__":
138
+ asyncio.run(main())
139
+ ```
140
+
141
+ ### FastAPI Integration Example
142
+
143
+ ```python
144
+ from contextlib import asynccontextmanager
145
+ from fastapi import FastAPI, Depends
146
+ from pydantic import BaseModel
147
+ from cledar.redis import AsyncRedisService, RedisServiceConfig
148
+
149
+
150
+ class UserModel(BaseModel):
151
+ user_id: int
152
+ name: str
153
+
154
+
155
+ # Global service instance
156
+ redis_service: AsyncRedisService | None = None
157
+
158
+
159
+ @asynccontextmanager
160
+ async def lifespan(app: FastAPI):
161
+ # Startup: initialize Redis service
162
+ global redis_service
163
+ config = RedisServiceConfig(
164
+ redis_host="localhost",
165
+ redis_port=6379,
166
+ redis_db=0,
167
+ )
168
+ redis_service = AsyncRedisService(config)
169
+ await redis_service.connect()
170
+
171
+ yield
172
+
173
+ # Shutdown: close Redis connection
174
+ if redis_service:
175
+ await redis_service.close()
176
+
177
+
178
+ app = FastAPI(lifespan=lifespan)
179
+
180
+
181
+ def get_redis() -> AsyncRedisService:
182
+ if redis_service is None:
183
+ raise RuntimeError("Redis service not initialized")
184
+ return redis_service
185
+
186
+
187
+ @app.get("/users/{user_id}")
188
+ async def get_user(user_id: int, redis: AsyncRedisService = Depends(get_redis)):
189
+ user = await redis.get(f"user:{user_id}", UserModel)
190
+ if user is None:
191
+ return {"error": "User not found"}
192
+ return user
193
+
194
+
195
+ @app.post("/users")
196
+ async def create_user(user: UserModel, redis: AsyncRedisService = Depends(get_redis)):
197
+ await redis.set(f"user:{user.user_id}", user)
198
+ return {"status": "created", "user": user}
199
+ ```
200
+
201
+ ## Development
202
+
203
+ ### Project Structure
204
+
205
+ ```
206
+ cledar/redis/
207
+ ├── __init__.py
208
+ ├── exceptions.py # Custom exceptions
209
+ ├── logger.py # Module logger
210
+ ├── model.py # Base config type for RedisConfigStore
211
+ ├── redis.py # RedisService and AsyncRedisService
212
+ ├── redis_config_store.py # Config store with caching and watchers
213
+ ├── example.py # Small example of using RedisConfigStore
214
+ ├── tests/
215
+ │ ├── test_redis_service.py # Sync unit tests (mocked Redis)
216
+ │ ├── test_async_redis_service.py # Async unit tests (mocked Redis)
217
+ │ ├── test_integration_redis.py # Sync integration tests with testcontainers
218
+ │ └── test_async_integration_redis.py # Async integration tests with testcontainers
219
+ └── README.md # This file
220
+ ```
221
+
222
+ ## Running Linters
223
+
224
+ The SDK configures common linters in `pyproject.toml`.
225
+
226
+ ### Installing Linters
227
+
228
+ ```bash
229
+ pip install pylint mypy black
230
+
231
+ # Or with uv
232
+ uv pip install pylint mypy black
233
+ ```
234
+
235
+ ### Running Linters
236
+
237
+ Run these from the SDK root directory:
238
+
239
+ ```bash
240
+ # From the SDK root directory
241
+ cd /path/to/cledar-python-sdk
242
+
243
+ # Pylint
244
+ pylint redis_service/
245
+
246
+ # Mypy (strict mode configured in pyproject)
247
+ mypy redis_service/
248
+
249
+ # Black (check and/or format)
250
+ black --check redis_service/
251
+ black redis_service/
252
+ ```
253
+
254
+ ### Run All Linters
255
+
256
+ ```bash
257
+ pylint redis_service/ && \
258
+ mypy redis_service/ && \
259
+ black --check redis_service/
260
+ ```
261
+
262
+ ## Running Unit Tests
263
+
264
+ Unit tests use `unittest.mock` to isolate logic without a real Redis instance.
265
+
266
+ ### Run All Unit Tests
267
+
268
+ ```bash
269
+ # From the SDK root directory
270
+ cd /path/to/cledar-python-sdk
271
+
272
+ PYTHONPATH=$PWD uv run pytest redis_service/tests/test_redis_service.py -v
273
+ ```
274
+
275
+ ### Run Specific Test
276
+
277
+ ```bash
278
+ PYTHONPATH=$PWD uv run pytest redis_service/tests/test_redis_service.py::test_set_with_pydantic_model_serializes_and_sets -v
279
+ ```
280
+
281
+ ### Unit Test Details
282
+
283
+ - **Test Framework**: pytest, pytest-asyncio
284
+ - **Mocking**: unittest.mock (sync), AsyncMock (async)
285
+ - **Test Count**: 60 unit tests (30 sync + 30 async)
286
+
287
+ ## Running Integration Tests
288
+
289
+ Integration tests use [testcontainers](https://testcontainers-python.readthedocs.io/) to run a real Redis container.
290
+
291
+ ### Prerequisites
292
+
293
+ **Required**:
294
+ - Docker installed and running
295
+ - Network access to pull Docker images
296
+
297
+ ### Run Integration Tests
298
+
299
+ ```bash
300
+ # From the SDK root directory
301
+ cd /path/to/cledar-python-sdk
302
+
303
+ PYTHONPATH=$PWD uv run pytest redis_service/tests/test_integration_redis.py -v
304
+ ```
305
+
306
+ ### Integration Test Details
307
+
308
+ - **Test Framework**: pytest, pytest-asyncio + testcontainers
309
+ - **Container**: Redis
310
+ - **Image**: `redis:7.2-alpine`
311
+ - **Test Count**: 17 integration tests (8 sync + 9 async)
312
+
313
+ ### Run All Tests (Unit + Integration)
314
+
315
+ ```bash
316
+ PYTHONPATH=$PWD uv run pytest redis_service/tests/ -v
317
+
318
+ # With coverage
319
+ PYTHONPATH=$PWD uv run pytest redis_service/tests/ \
320
+ --cov=redis_service \
321
+ --cov-report=html \
322
+ --cov-report=term \
323
+ -v
324
+
325
+ open htmlcov/index.html
326
+ ```
327
+
328
+ ## CI/CD Integration
329
+
330
+ ### GitLab CI Example
331
+
332
+ ```yaml
333
+ test-unit:
334
+ stage: test
335
+ image: python:3.12
336
+ script:
337
+ - pip install uv
338
+ - uv sync --all-groups
339
+ - PYTHONPATH=$PWD uv run pytest redis_service/tests/test_redis_service.py -v
340
+
341
+ test-integration:
342
+ stage: test
343
+ image: python:3.12
344
+ services:
345
+ - docker:dind
346
+ variables:
347
+ DOCKER_HOST: tcp://docker:2375
348
+ DOCKER_TLS_CERTDIR: ""
349
+ script:
350
+ - pip install uv
351
+ - uv sync --all-groups
352
+ - PYTHONPATH=$PWD uv run pytest redis_service/tests/test_integration_redis.py -v
353
+ ```
354
+
355
+ ### GitHub Actions Example
356
+
357
+ ```yaml
358
+ name: Tests
359
+ on: [push, pull_request]
360
+
361
+ jobs:
362
+ unit-tests:
363
+ runs-on: ubuntu-latest
364
+ steps:
365
+ - uses: actions/checkout@v3
366
+ - uses: actions/setup-python@v4
367
+ with:
368
+ python-version: '3.12'
369
+ - name: Install dependencies
370
+ run: |
371
+ pip install uv
372
+ uv sync --all-groups
373
+ - name: Run unit tests
374
+ run: PYTHONPATH=$PWD uv run pytest redis_service/tests/test_redis_service.py -v
375
+
376
+ integration-tests:
377
+ runs-on: ubuntu-latest
378
+ steps:
379
+ - uses: actions/checkout@v3
380
+ - uses: actions/setup-python@v4
381
+ with:
382
+ python-version: '3.12'
383
+ - name: Install dependencies
384
+ run: |
385
+ pip install uv
386
+ uv sync --all-groups
387
+ - name: Run integration tests
388
+ run: PYTHONPATH=$PWD uv run pytest redis_service/tests/test_integration_redis.py -v
389
+ ```
390
+
391
+ ## API Reference
392
+
393
+ ### RedisServiceConfig
394
+
395
+ Dataclass configuring the Redis connection.
396
+
397
+ ```python
398
+ from dataclasses import dataclass
399
+
400
+
401
+ @dataclass
402
+ class RedisServiceConfig:
403
+ redis_host: str
404
+ redis_port: int
405
+ redis_db: int = 0
406
+ redis_password: str | None = None
407
+ ```
408
+
409
+ ### RedisService (Synchronous)
410
+
411
+ High-level service over `redis.Redis` with JSON handling and typed reads.
412
+
413
+ #### Methods
414
+
415
+ - `is_alive() -> bool` — Ping Redis to check connectivity
416
+ - `set(key: str, value: Any) -> bool` — Serialize and store a value; supports dict/list, Pydantic models, primitives
417
+ - `get(key: str, model: type[T]) -> T | None` — Read and validate JSON into the given Pydantic model
418
+ - `get_raw(key: str) -> Any | None` — Read raw value (usually string) without validation
419
+ - `list_keys(pattern: str) -> list[str]` — List keys matching a glob-like pattern
420
+ - `mget(keys: list[str], model: type[T]) -> list[T | None | FailedValue]` — Bulk read with per-key error details
421
+ - `delete(key: str) -> bool` — Delete a key; returns True if a key was removed
422
+
423
+ ### AsyncRedisService (Asynchronous)
424
+
425
+ High-level async service over `redis.asyncio.Redis` with JSON handling and typed reads.
426
+
427
+ #### Methods
428
+
429
+ All methods are async (use `await`):
430
+
431
+ - `connect() -> None` — Establish connection to Redis (must be called before using other methods)
432
+ - `close() -> None` — Close the Redis connection
433
+ - `is_alive() -> bool` — Ping Redis to check connectivity
434
+ - `set(key: str, value: Any) -> bool` — Serialize and store a value; supports dict/list, Pydantic models, primitives
435
+ - `get(key: str, model: type[T]) -> T | None` — Read and validate JSON into the given Pydantic model
436
+ - `get_raw(key: str) -> Any | None` — Read raw value (usually string) without validation
437
+ - `list_keys(pattern: str) -> list[str]` — List keys matching a glob-like pattern
438
+ - `mget(keys: list[str], model: type[T]) -> list[T | None | FailedValue]` — Bulk read with per-key error details
439
+ - `delete(key: str) -> bool` — Delete a key; returns True if a key was removed
440
+
441
+ #### Exceptions
442
+
443
+ - `RedisConnectionError` — Connection/transport errors
444
+ - `RedisSerializationError` — Failures before sending to Redis (e.g., unsupported object)
445
+ - `RedisDeserializationError` — Invalid JSON or model validation errors on read
446
+ - `RedisOperationError` — Other Redis command errors
447
+
448
+ ### CustomEncoder
449
+
450
+ `json.JSONEncoder` subclass used internally:
451
+
452
+ - `Enum` → lowercase of member name (e.g., `Color.RED` → `"red"`)
453
+ - `datetime` → ISO 8601 string (e.g., `"2025-01-01T00:00:00"`)
454
+
455
+ ### FailedValue
456
+
457
+ Dataclass used by `mget` to signal per-key errors without failing the whole call:
458
+
459
+ ```python
460
+ from dataclasses import dataclass
461
+
462
+
463
+ @dataclass
464
+ class FailedValue:
465
+ key: str
466
+ error: Exception
467
+ ```
468
+
469
+ ## RedisConfigStore
470
+
471
+ `RedisConfigStore` provides a simple configuration layer on top of Redis:
472
+
473
+ - Caches last known values per key (string or list-backed history)
474
+ - Tracks a simple per-key version (`1` for string, list length for list)
475
+ - Watches keys using Redis keyspace notifications and updates local cache
476
+ - Provides `fetch`, `update`, `delete`, `versions`, `cached_version`, and `watch`
477
+
478
+ ### Usage
479
+
480
+ ```python
481
+ from dataclasses import dataclass
482
+ from redis import Redis
483
+ from cledar.redis.redis_config_store import RedisConfigStore
484
+ from redis_service.model import BaseConfigClass
485
+
486
+
487
+ @dataclass
488
+ class ExampleConfig(BaseConfigClass):
489
+ name: str
490
+ index: int
491
+ data: dict[str, str]
492
+
493
+
494
+ r = Redis(host="localhost", port=6379, db=0, decode_responses=False)
495
+ store = RedisConfigStore(r, prefix="app:")
496
+
497
+ key = "example_config"
498
+ cfg = ExampleConfig(name="demo", index=1, data={})
499
+
500
+ # Set/update (appends new version for list-backed keys)
501
+ store[key] = cfg
502
+
503
+ # Fetch typed config and current cached version
504
+ fetched = store.fetch(ExampleConfig, key)
505
+ version = store.cached_version(key)
506
+
507
+ # Watch for updates (optional callback)
508
+ store.watch(key)
509
+ ```
510
+
511
+ Note: Keyspace notifications must be enabled in Redis to receive events, for example:
512
+
513
+ ```bash
514
+ # Enable keyspace and keyevent notifications (example; tailor to your needs)
515
+ redis-cli CONFIG SET notify-keyspace-events Ex
516
+ ```
517
+
518
+ ## Running Pre-commit Checks
519
+
520
+ ```bash
521
+ uv run black redis_service/
522
+ uv run mypy redis_service/
523
+ uv run pylint redis_service/
524
+ PYTHONPATH=$PWD uv run pytest redis_service/tests/ -v
525
+ ```
526
+
527
+ ## License
528
+
529
+ See the main repository LICENSE file.
530
+
531
+ ## Support
532
+
533
+ For issues, questions, or contributions, please refer to the main repository's contribution guidelines.
534
+
535
+
536
+
@@ -0,0 +1,17 @@
1
+ """Redis service module for the Cledar SDK."""
2
+
3
+ from .redis import (
4
+ AsyncRedisService,
5
+ CustomEncoder,
6
+ FailedValue,
7
+ RedisService,
8
+ RedisServiceConfig,
9
+ )
10
+
11
+ __all__ = [
12
+ "AsyncRedisService",
13
+ "CustomEncoder",
14
+ "FailedValue",
15
+ "RedisService",
16
+ "RedisServiceConfig",
17
+ ]
@@ -0,0 +1,112 @@
1
+ """Example usage of AsyncRedisService with async/await.
2
+
3
+ This example demonstrates:
4
+ - Connecting to Redis asynchronously
5
+ - Setting and getting typed values
6
+ - Concurrent operations
7
+ - Proper connection lifecycle management
8
+ """
9
+
10
+ import asyncio
11
+
12
+ from pydantic import BaseModel
13
+
14
+ from cledar.redis import AsyncRedisService, RedisServiceConfig
15
+
16
+
17
+ class UserModel(BaseModel):
18
+ """Simple user model for demonstration."""
19
+
20
+ user_id: int
21
+ name: str
22
+ email: str
23
+
24
+
25
+ async def basic_usage_example() -> None:
26
+ """Demonstrate basic async Redis operations."""
27
+ print("=== Basic Async Usage ===")
28
+
29
+ # Configure service
30
+ config = RedisServiceConfig(
31
+ redis_host="localhost",
32
+ redis_port=6379,
33
+ redis_db=0,
34
+ )
35
+
36
+ # Create and connect
37
+ service = AsyncRedisService(config)
38
+ await service.connect()
39
+
40
+ try:
41
+ # Health check
42
+ is_alive = await service.is_alive()
43
+ print(f"Redis is alive: {is_alive}")
44
+
45
+ # Store typed data
46
+ user = UserModel(user_id=1, name="Alice", email="alice@example.com")
47
+ await service.set("user:1", user)
48
+ print(f"Stored user: {user}")
49
+
50
+ # Retrieve and validate
51
+ retrieved = await service.get("user:1", UserModel)
52
+ print(f"Retrieved user: {retrieved}")
53
+
54
+ # Store raw string
55
+ await service.set("greeting", "Hello, async world!")
56
+ greeting = await service.get_raw("greeting")
57
+ print(f"Greeting: {greeting}")
58
+
59
+ finally:
60
+ # Always close connection
61
+ await service.close()
62
+ print("Connection closed")
63
+
64
+
65
+ async def concurrent_operations_example() -> None:
66
+ """Demonstrate concurrent async operations."""
67
+ print("\n=== Concurrent Operations ===")
68
+
69
+ config = RedisServiceConfig(
70
+ redis_host="localhost",
71
+ redis_port=6379,
72
+ redis_db=0,
73
+ )
74
+
75
+ service = AsyncRedisService(config)
76
+ await service.connect()
77
+
78
+ try:
79
+ # Create multiple users concurrently
80
+ users = [
81
+ UserModel(user_id=i, name=f"User{i}", email=f"user{i}@example.com")
82
+ for i in range(1, 11)
83
+ ]
84
+
85
+ # Store all users concurrently
86
+ set_tasks = [service.set(f"user:{u.user_id}", u) for u in users]
87
+ results = await asyncio.gather(*set_tasks)
88
+ print(f"Stored {sum(results)} users concurrently")
89
+
90
+ # Retrieve all users concurrently
91
+ keys = [f"user:{i}" for i in range(1, 11)]
92
+ retrieved = await service.mget(keys, UserModel)
93
+ user_count = len([r for r in retrieved if isinstance(r, UserModel)])
94
+ print(f"Retrieved {user_count} users")
95
+
96
+ # Clean up concurrently
97
+ delete_tasks = [service.delete(key) for key in keys]
98
+ await asyncio.gather(*delete_tasks)
99
+ print("Cleaned up all users")
100
+
101
+ finally:
102
+ await service.close()
103
+
104
+
105
+ async def main() -> None:
106
+ """Run all examples."""
107
+ await basic_usage_example()
108
+ await concurrent_operations_example()
109
+
110
+
111
+ if __name__ == "__main__":
112
+ asyncio.run(main())