Caching System
CountryFlag includes a robust caching system to improve performance when working with repeated country name lookups.
The caching system features automatic cache sharing, thread-safe operations, and intelligent key normalization for optimal performance.
Automatic Cache Sharing
Starting from version 1.0.1, CountryFlag automatically provides shared caching across instances when no explicit cache is provided. Enhanced in version 1.1.1 with improved performance.
from countryflag.core import CountryFlag
# These instances automatically share the same cache
cf1 = CountryFlag()
cf2 = CountryFlag()
# First call (cache miss)
flags1, _ = cf1.get_flag(["Germany"])
# Second call from different instance (cache hit!)
flags2, _ = cf2.get_flag(["Germany"]) # Much faster
# Verify cache sharing
assert cf1._cache is cf2._cache # True
This behavior provides significant performance improvements as cache data accumulates across the application lifecycle, while maintaining full backward compatibility.
Global Cache Management
The shared cache can be easily managed for testing or reset scenarios:
# Clear the global cache
CountryFlag.clear_global_cache()
# Check cache statistics
hits = CountryFlag._global_cache.get_hits()
print(f"Cache hits: {hits}")
Thread Safety
All cache operations in CountryFlag are thread-safe, using threading.Lock to ensure data integrity in concurrent environments.
import threading
from countryflag.core import CountryFlag
def worker():
"""Worker function for concurrent access."""
cf = CountryFlag()
flags, _ = cf.get_flag(["Germany", "France"])
return flags
# Create multiple threads
threads = []
for i in range(10):
thread = threading.Thread(target=worker)
threads.append(thread)
thread.start()
# Wait for completion
for thread in threads:
thread.join()
print("All threads completed safely")
Thread Safety Features:
All cache operations (get, set, delete, clear) are protected by locks
Concurrent access to global cache is safe across multiple instances
No data corruption or race conditions
Performance optimized with fine-grained locking
Key Normalization
CountryFlag uses intelligent key normalization to improve cache hit rates by creating deterministic cache keys regardless of input order.
cf = CountryFlag()
# These two calls will use the same cache entry
flags1, _ = cf.get_flag(["Germany", "France", "Italy"])
flags2, _ = cf.get_flag(["Italy", "Germany", "France"]) # Cache hit!
# The results are reordered to match input order
# but the underlying cache key is normalized
Key Normalization Benefits:
Order Independence: Same countries in different orders share cache entries
Higher Hit Rates: Reduces cache misses due to input variations
Memory Efficiency: Eliminates duplicate cache entries for equivalent requests
Intelligent Filtering: Only valid string entries are used for key generation
How It Works:
Filter out invalid/empty entries from country list
Sort valid country names alphabetically
Create deterministic cache key with separator
Store result with normalized key
Reorder cached results to match current input order
Backward Compatibility and Migration
Important
Breaking Change in v1.0.1: The caching behavior has changed for CountryFlag instances created without an explicit cache parameter. Further optimized in v1.1.1.
Before v1.0.1:
# These instances had NO caching
cf1 = CountryFlag() # cache = None
cf2 = CountryFlag() # cache = None
# Each call was computed from scratch
flags1, _ = cf1.get_flag(["Germany"]) # No caching
flags2, _ = cf2.get_flag(["Germany"]) # No caching
From v1.0.1 onwards (enhanced in v1.1.1):
# These instances automatically share a global cache
cf1 = CountryFlag() # cache = CountryFlag._global_cache
cf2 = CountryFlag() # cache = CountryFlag._global_cache
# Cache data is shared between instances
flags1, _ = cf1.get_flag(["Germany"]) # Cache miss, stores result
flags2, _ = cf2.get_flag(["Germany"]) # Cache hit!
Migration Guide:
No Code Changes Required: Existing code will continue to work
Performance Improvement: Applications will automatically benefit from shared caching
Custom Cache Users: No changes needed - explicit cache parameters work as before
Testing Code: Use
CountryFlag.clear_global_cache()to reset cache state between tests
Disabling Global Cache (if needed):
from countryflag.cache.base import NoOpCache
# Create instance with no caching (similar to old behavior)
cf = CountryFlag(cache=NoOpCache())
Impact on Performance:
Positive: Significantly faster repeated operations
Memory: Minimal increase due to singleton pattern
Thread Safety: Enhanced with proper locking mechanisms
Built-in Cache Types
Memory Cache
The simplest and fastest caching option, storing data in memory:
from countryflag.core import CountryFlag
from countryflag.cache import MemoryCache
# Create a memory cache
cache = MemoryCache()
# Create a CountryFlag instance with caching
cf = CountryFlag(cache=cache)
# First call (cache miss)
flags1 = cf.get_flag(["United States", "Canada"])
# Second call (cache hit - much faster)
flags2 = cf.get_flag(["United States", "Canada"])
Disk Cache
Persistent caching that survives program restarts:
from countryflag.cache import DiskCache
# Create a disk cache
cache = DiskCache("/path/to/cache/dir")
# Use it with CountryFlag
cf = CountryFlag(cache=cache)
Redis Cache
For distributed systems, use the Redis cache plugin:
from countryflag.plugins.redis_cache import RedisCache
# Create a Redis cache
cache = RedisCache(host="localhost", port=6379)
# Use it with CountryFlag
cf = CountryFlag(cache=cache)
Cache Configuration
Common configuration options:
# Memory cache with size limit
cache = MemoryCache(max_size=1000)
# Disk cache with TTL
cache = DiskCache(
cache_dir="/path/to/cache",
ttl=3600 # 1 hour
)
# Redis cache with custom configuration
cache = RedisCache(
host="localhost",
port=6379,
db=0,
password="optional_password",
ttl=3600
)
Creating Custom Caches
You can create custom cache implementations by extending the base Cache class:
from countryflag.cache.base import Cache
from typing import Optional, Any
class CustomCache(Cache):
def get(self, key: str) -> Optional[Any]:
# Implementation
pass
def set(self, key: str, value: Any) -> None:
# Implementation
pass
def delete(self, key: str) -> None:
# Implementation
pass
def clear(self) -> None:
# Implementation
pass
def contains(self, key: str) -> bool:
# Implementation
pass
Cache Performance
Benchmarking results for different cache types:
Operation |
No Cache |
Memory Cache |
Disk Cache |
|---|---|---|---|
First Call |
100ms |
100ms |
100ms |
Second Call |
100ms |
1ms |
10ms |
Best Practices
Choose the Right Cache: - Use MemoryCache for single-process applications - Use DiskCache for persistence between runs - Use RedisCache for distributed systems
Configure Cache Size: - Set appropriate size limits - Monitor cache usage - Implement cache eviction policies
Handle Cache Failures: - Implement fallback mechanisms - Log cache errors - Monitor cache performance
Cache Invalidation: - Implement clear cache policies - Use TTL for time-sensitive data - Provide cache clearing mechanisms
API Reference
Base cache interface for the countryflag package.
This module contains the base Cache interface that all cache implementations must implement.
- class countryflag.cache.base.Cache[source]
Bases:
ABCAbstract base class for caching implementations.
This class defines the interface that all cache implementations must adhere to.
Memory-based cache implementation for the countryflag package.
This module contains the MemoryCache class, which implements in-memory caching.
- class countryflag.cache.memory.MemoryCache[source]
Bases:
CacheIn-memory cache implementation.
This class implements a simple in-memory cache using a dictionary.
- _cache
Dictionary that stores the cached values.
Initialize the memory cache.
- get(key)[source]
Get a value from the cache.
- Parameters:
key (str) – The cache key.
- Returns:
The cached value, or None if the key is not in the cache.
- Return type:
Any | None
Example
>>> cache = MemoryCache() >>> cache.set("key", "value") >>> cache.get("key") 'value' >>> cache.get("nonexistent") None
- set(key, value)[source]
Set a value in the cache.
Example
>>> cache = MemoryCache() >>> cache.set("key", "value") >>> cache.get("key") 'value'
- delete(key)[source]
Delete a value from the cache.
- Parameters:
key (str) – The cache key to delete.
- Return type:
None
Example
>>> cache = MemoryCache() >>> cache.set("key", "value") >>> cache.delete("key") >>> cache.get("key") None
- clear()[source]
Clear all values from the cache.
Example
>>> cache = MemoryCache() >>> cache.set("key1", "value1") >>> cache.set("key2", "value2") >>> cache.clear() >>> cache.get("key1") None >>> cache.get("key2") None
- Return type:
None
- contains(key)[source]
Check if a key exists in the cache.
- Parameters:
key (str) – The cache key to check.
- Returns:
True if the key exists in the cache, False otherwise.
- Return type:
Example
>>> cache = MemoryCache() >>> cache.set("key", "value") >>> cache.contains("key") True >>> cache.contains("nonexistent") False
Disk-based cache implementation for the countryflag package.
This module contains the DiskCache class, which implements on-disk caching.
- class countryflag.cache.disk.DiskCache(cache_dir)[source]
Bases:
CacheDisk-based cache implementation.
This class implements an on-disk cache using JSON files.
- _cache_dir
Path to the cache directory.
- _index
Dictionary that maps cache keys to filenames.
Initialize the disk cache.
- Parameters:
cache_dir (str) – Path to the cache directory.
- Raises:
CacheError – If the cache directory cannot be created.
- __init__(cache_dir)[source]
Initialize the disk cache.
- Parameters:
cache_dir (str) – Path to the cache directory.
- Raises:
CacheError – If the cache directory cannot be created.
- Return type:
None
- get(key)[source]
Get a value from the cache.
- Parameters:
key (str) – The cache key.
- Returns:
The cached value, or None if the key is not in the cache.
- Raises:
CacheError – If the cache file cannot be read.
- Return type:
Any | None
Example
>>> cache = DiskCache("/tmp/countryflag_cache") >>> cache.set("key", "value") >>> cache.get("key") 'value' >>> cache.get("nonexistent") None
- set(key, value)[source]
Set a value in the cache.
- Parameters:
- Raises:
CacheError – If the cache file cannot be written.
- Return type:
None
Example
>>> cache = DiskCache("/tmp/countryflag_cache") >>> cache.set("key", "value") >>> cache.get("key") 'value'
- delete(key)[source]
Delete a value from the cache.
- Parameters:
key (str) – The cache key to delete.
- Return type:
None
Example
>>> cache = DiskCache("/tmp/countryflag_cache") >>> cache.set("key", "value") >>> cache.delete("key") >>> cache.get("key") None
- clear()[source]
Clear all values from the cache.
Example
>>> cache = DiskCache("/tmp/countryflag_cache") >>> cache.set("key1", "value1") >>> cache.set("key2", "value2") >>> cache.clear() >>> cache.get("key1") None >>> cache.get("key2") None
- Return type:
None
- contains(key)[source]
Check if a key exists in the cache.
- Parameters:
key (str) – The cache key to check.
- Returns:
True if the key exists in the cache, False otherwise.
- Return type:
Example
>>> cache = DiskCache("/tmp/countryflag_cache") >>> cache.set("key", "value") >>> cache.contains("key") True >>> cache.contains("nonexistent") False