Skip to content

Commit 612cc49

Browse files
Incorporate PR feedback: improve examples
- Replace CompressionWrapper with LoggingWrapper in trading_data example (disk cache is already compressed, so logging is more useful) - Move logging.basicConfig() from module-level to main() in all examples (prevents side effects on import) - Fix fragile wrapper attribute chains by using explicit variable references (improves maintainability and removes type: ignore comments) - Update README documentation to match code changes Addresses feedback from @strawgate and CodeRabbit review. Co-authored-by: William Easton <strawgate@users.noreply.github.com>
1 parent 06d4e7d commit 612cc49

File tree

4 files changed

+43
-38
lines changed

4 files changed

+43
-38
lines changed

examples/chat_app/chat_app.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@
1919
from key_value.aio.wrappers.ttl_clamp.wrapper import TTLClampWrapper
2020
from pydantic import BaseModel
2121

22-
# Configure logging
23-
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
24-
2522

2623
class ChatMessage(BaseModel):
2724
"""A chat message with sender, content, and timestamp."""
@@ -47,12 +44,9 @@ def __init__(self):
4744
# 1. StatisticsWrapper - Track operation metrics
4845
# 2. TTLClampWrapper - Enforce max TTL of 24 hours (86400 seconds)
4946
# 3. LoggingWrapper - Log all operations for debugging
50-
wrapped_store = LoggingWrapper(
51-
key_value=TTLClampWrapper(
52-
key_value=StatisticsWrapper(key_value=base_store),
53-
max_ttl=86400, # 24 hours in seconds
54-
)
55-
)
47+
stats = StatisticsWrapper(key_value=base_store)
48+
ttl_clamped = TTLClampWrapper(key_value=stats, max_ttl=86400) # 24 hours
49+
wrapped_store = LoggingWrapper(key_value=ttl_clamped)
5650

5751
# PydanticAdapter for type-safe message storage/retrieval
5852
self.adapter: PydanticAdapter[ChatMessage] = PydanticAdapter[ChatMessage](
@@ -61,7 +55,7 @@ def __init__(self):
6155
)
6256

6357
# Store reference to statistics wrapper for metrics
64-
self.stats_wrapper = wrapped_store.key_value.key_value # type: ignore[attr-defined]
58+
self.stats_wrapper = stats
6559

6660
async def send_message(self, conversation_id: str, sender: str, content: str) -> str:
6761
"""
@@ -136,6 +130,9 @@ def get_statistics(self) -> dict[str, int]:
136130

137131
async def main():
138132
"""Demonstrate the chat application."""
133+
# Configure logging for the demo
134+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
135+
139136
chat = ChatApp()
140137

141138
# Send some messages

examples/trading_data/README.md

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
# Trading Data Cache Example
22

33
A trading data caching application demonstrating advanced py-key-value patterns
4-
including compression, multi-tier caching, and retry logic.
4+
including logging, multi-tier caching, and retry logic.
55

66
## Overview
77

88
This example shows how to build an efficient trading data cache using
99
py-key-value. The implementation features a two-tier caching strategy
10-
(memory + disk) with compression for optimal storage efficiency and fast access
11-
to recent data.
10+
(memory + disk) with logging for observability and fast access to recent data.
1211

1312
## Features
1413

1514
- **Type-safe price data storage** using PydanticAdapter
1615
- **Multi-tier caching** with PassthroughCacheWrapper (memory → disk)
17-
- **Data compression** with CompressionWrapper for efficient storage
16+
- **Operation logging** with LoggingWrapper for debugging and observability
1817
- **Automatic retry** with RetryWrapper for transient failure handling
1918
- **Cache metrics** tracking with StatisticsWrapper
2019
- **Symbol-based isolation** using collection-based storage
@@ -26,15 +25,14 @@ The wrapper stack (applied inside-out):
2625
1. **StatisticsWrapper** - Tracks cache hit/miss metrics and operation counts
2726
2. **RetryWrapper** - Handles transient failures with exponential backoff (3
2827
retries)
29-
3. **CompressionWrapper** - Compresses data before writing to disk
28+
3. **LoggingWrapper** - Logs all operations for debugging and monitoring
3029
4. **PassthroughCacheWrapper** - Two-tier caching: fast memory cache with disk
3130
persistence
3231

3332
Data flow:
3433

35-
- **Write**: Data → Memory cache → Compressed → Disk storage
36-
- **Read**: Check memory cache → If miss, load from disk → Decompress → Cache in
37-
memory
34+
- **Write**: Data → Memory cache → Logged → Disk storage
35+
- **Read**: Check memory cache → If miss, load from disk → Log → Cache in memory
3836

3937
## Requirements
4038

@@ -224,9 +222,14 @@ from key_value.aio.stores.redis.store import RedisStore
224222
memory_cache = RedisStore(url="redis://localhost:6379/0")
225223
disk_cache = DiskStore(root_directory="historical_data")
226224

225+
# Build wrapper stack with logging
226+
stats = StatisticsWrapper(key_value=disk_cache)
227+
retry_wrapper = RetryWrapper(key_value=stats, max_retries=3, base_delay=0.1)
228+
disk_with_logging = LoggingWrapper(key_value=retry_wrapper)
229+
227230
cache_store = PassthroughCacheWrapper(
228231
cache=memory_cache,
229-
key_value=CompressionWrapper(key_value=disk_cache)
232+
key_value=disk_with_logging
230233
)
231234
```
232235

@@ -235,8 +238,12 @@ Example with encryption:
235238
```python
236239
from key_value.aio.wrappers.encryption.wrapper import FernetEncryptionWrapper
237240

238-
encrypted_disk = FernetEncryptionWrapper(
239-
key_value=disk_cache,
241+
# Add encryption to the wrapper stack
242+
stats = StatisticsWrapper(key_value=disk_cache)
243+
encrypted_stats = FernetEncryptionWrapper(
244+
key_value=stats,
240245
key=b"your-32-byte-encryption-key-here"
241246
)
247+
retry_wrapper = RetryWrapper(key_value=encrypted_stats, max_retries=3, base_delay=0.1)
248+
disk_with_logging = LoggingWrapper(key_value=retry_wrapper)
242249
```

examples/trading_data/trading_app.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
This example shows how to:
55
- Use PydanticAdapter for type-safe price data storage
6-
- Apply CompressionWrapper for efficient historical data storage
6+
- Use LoggingWrapper for observability and debugging
77
- Use PassthroughCacheWrapper for multi-tier caching (memory + disk)
88
- Use RetryWrapper for handling transient failures
99
- Use StatisticsWrapper to track cache hit/miss metrics
@@ -17,15 +17,12 @@
1717
from key_value.aio.adapters.pydantic import PydanticAdapter
1818
from key_value.aio.stores.disk.store import DiskStore
1919
from key_value.aio.stores.memory.store import MemoryStore
20-
from key_value.aio.wrappers.compression.wrapper import CompressionWrapper
20+
from key_value.aio.wrappers.logging.wrapper import LoggingWrapper
2121
from key_value.aio.wrappers.passthrough_cache.wrapper import PassthroughCacheWrapper
2222
from key_value.aio.wrappers.retry.wrapper import RetryWrapper
2323
from key_value.aio.wrappers.statistics.wrapper import StatisticsWrapper
2424
from pydantic import BaseModel
2525

26-
# Configure logging
27-
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
28-
2926

3027
class PriceData(BaseModel):
3128
"""Trading price data for a symbol."""
@@ -38,11 +35,11 @@ class PriceData(BaseModel):
3835

3936
class TradingDataCache:
4037
"""
41-
Trading data cache with multi-tier caching and compression.
38+
Trading data cache with multi-tier caching and logging.
4239
4340
Uses a memory cache for fast access to recent data, with disk-backed
44-
persistence for historical data. Compression reduces storage requirements
45-
for large datasets.
41+
persistence for historical data. Logging provides observability into
42+
cache operations.
4643
"""
4744

4845
def __init__(self, cache_dir: str = ".trading_cache"):
@@ -52,19 +49,19 @@ def __init__(self, cache_dir: str = ".trading_cache"):
5249
# Tier 1: Memory cache for fast access
5350
memory_cache = MemoryStore()
5451

55-
# Tier 2: Disk cache with compression for historical data
52+
# Tier 2: Disk cache for historical data
5653
disk_cache = DiskStore(root_directory=cache_dir)
5754

5855
# Wrapper stack (applied inside-out):
5956
# 1. StatisticsWrapper - Track cache metrics
6057
# 2. RetryWrapper - Handle transient failures (3 retries with exponential backoff)
61-
# 3. CompressionWrapper - Compress data before storage
58+
# 3. LoggingWrapper - Log operations for debugging
6259
# 4. PassthroughCacheWrapper - Two-tier caching (memory → disk)
63-
disk_with_compression = CompressionWrapper(
64-
key_value=RetryWrapper(key_value=StatisticsWrapper(key_value=disk_cache), max_retries=3, base_delay=0.1)
65-
)
60+
stats = StatisticsWrapper(key_value=disk_cache)
61+
retry_wrapper = RetryWrapper(key_value=stats, max_retries=3, base_delay=0.1)
62+
disk_with_logging = LoggingWrapper(key_value=retry_wrapper)
6663

67-
cache_store = PassthroughCacheWrapper(cache=memory_cache, key_value=disk_with_compression)
64+
cache_store = PassthroughCacheWrapper(cache=memory_cache, key_value=disk_with_logging)
6865

6966
# PydanticAdapter for type-safe price data storage/retrieval
7067
self.adapter: PydanticAdapter[PriceData] = PydanticAdapter[PriceData](
@@ -73,7 +70,7 @@ def __init__(self, cache_dir: str = ".trading_cache"):
7370
)
7471

7572
# Store reference to statistics wrapper for metrics
76-
self.stats_wrapper = disk_with_compression.key_value.key_value # type: ignore[attr-defined]
73+
self.stats_wrapper = stats
7774
self.cache_dir = cache_dir
7875

7976
async def store_price(self, symbol: str, price: float, volume: int, ttl: int | None = None) -> str:
@@ -170,6 +167,9 @@ async def cleanup(self):
170167

171168
async def main():
172169
"""Demonstrate the trading data cache."""
170+
# Configure logging for the demo
171+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
172+
173173
cache = TradingDataCache(cache_dir=".demo_trading_cache")
174174

175175
try:

examples/web_scraper_cache/scraper.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
from key_value.aio.wrappers.ttl_clamp.wrapper import TTLClampWrapper
2626
from pydantic import BaseModel
2727

28-
# Configure logging
29-
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
3028
logger = logging.getLogger(__name__)
3129

3230

@@ -199,6 +197,9 @@ async def simulate_scrape(url: str) -> tuple[str, dict[str, str]]:
199197

200198
async def main():
201199
"""Demonstrate the web scraper cache."""
200+
# Configure logging for the demo
201+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
202+
202203
# Generate a key for this demo (in production, load from secure storage)
203204
encryption_key = Fernet.generate_key()
204205
# Only show fingerprint, never the actual key

0 commit comments

Comments
 (0)