⚡️ FlipCache
Redis-backed hybrid caching for lightning-fast Python data access
🤷♂️ Why FlipCache?
- Seamlessly integrate Redis for accelerated data retrieval in your Python projects.
- Optimize performance with an in-memory cache layer backed by Redis persistence.
- Enjoy ease-of-use and flexible configuration
📥 Installation
pip install flipcache
🚀 Key Features
- Hybrid Caching: Transparent in-memory caching combined with Redis for scalable persistence.
- Expire Times: Set custom expiration times for cached data.
- Configurable: Tailor cache size, data types, and more.
👨💻 Usage Examples
Basic
from flipcache import FlipCache
cache = FlipCache("my_cache")
cache["my_key"] = "my_value"
print(cache["my_key"]) # Outputs: "my_value"
print(cache["unknown"]) # Outputs: None
print("my_key" in cache) # Outputs: True
Pros compared to using simple dictionary:
- Data persistence backed by Redis
- Seamless data conversion from Redis to Python
- Fast data access, compared to pure redis
- Returns None instead of raising an error on key indexing
Expiring Cache
import time
from flipcache import FlipCache
expiring_cache = FlipCache("expiring", local_max=0, expire_time=5)
expiring_cache["data"] = "This will expire"
time.sleep(6)
print(expiring_cache["data"]) # Outputs: None
In order to expiring-feature work with its full potential, we need to set local_max to 0, removing the caching layer.
You lose out on faster data retrieval, in order to get precise expiration results.
We can combine expire_time and local_max, in that case we can access data from cache memory that could have been expired.
JSON Cache
from flipcache import FlipCache, et
user_data = FlipCache(
"user_data",
local_max=100,
expire_time=et.THREE_DAYS,
value_type="json"
)
data = {
"state": 1,
"orders": [1, 2, 3, 4],
"items": {
"foo": 1,
"bar": True,
"baz": []
}
}
# Store data
user_data["some-uuid"] = data
print(user_data["some-uuid"]) # {'state': 1, 'orders': [1, 2, 3, 4], 'items': {'foo': 1, 'bar': True, 'baz': []}}
# Update data
data["state"] = 2
data["items"]["bar"] = False
user_data["some-uuid"] = data
print(user_data["some-uuid"]) # {'state': 2, 'orders': [1, 2, 3, 4], 'items': {'foo': 1, 'bar': False, 'baz': []}}
# Delete data
del user_data["some-uuid"]
print(user_data["some-uuid"]) # None
Custom Encoder/Decoder
from flipcache import FlipCache
from dataclasses import dataclass, field
@dataclass
class Shape:
name: str = "default"
dimensions: list[float] = field(default_factory=list)
edges: int = 0
area: float = 0
def __post_init__(self):
if not self.area and self.dimensions:
self.area = self.dimensions[0] * self.dimensions[1]
def encode_shape(shape: Shape) -> str:
return f"{shape.name}||{shape.dimensions}||{shape.edges}||{shape.area}"
def decode_shape(shape: str) -> Shape:
data = shape.split("||")
return Shape(
name=data[0],
dimensions=[float(num) for num in data[1].strip('[]').split(',') if num],
edges=int(data[2]),
area=float(data[3])
)
my_shape = Shape(name='womp', dimensions=[4.1, 3.4], edges=6, area=16.38)
shape2 = Shape(name='wat', dimensions=[11, 22])
custom = FlipCache(
"custom",
local_max=0,
key_type='int',
value_type='custom',
value_default=Shape(),
value_encoder=encode_shape,
value_decoder=decode_shape
)
custom[123] = my_shape
custom[456] = shape2
print(custom[123]) # Shape(name='womp', dimensions=[4.1, 3.4], edges=6, area=16.38)
print(custom[321]) # Shape(name='default', dimensions=[], edges=0, area=0.0)
print(custom[456]) # Shape(name='wat', dimensions=[11.0, 22.0], edges=0, area=242.0)
FIFODict Example (First-In, First-Out)
from flipcache import FIFODict
cache = FIFODict(max_items=3)
cache["a"] = "Apple"
cache["b"] = "Banana"
cache["c"] = "Cherry"
print(list(cache.keys())) # ['a', 'b', 'c']
cache["d"] = "Date" # Evicts 'a' (oldest)
print(list(cache.keys())) # ['b', 'c', 'd']
✅ Items are evicted in the order they were inserted.
LRUDict Example (Least Recently Used)
from flipcache import LRUDict
cache = LRUDict(max_items=3)
cache["a"] = "Alpha"
cache["b"] = "Beta"
cache["c"] = "Gamma"
_ = cache["a"] # Access 'a', making it most recently used
cache["d"] = "Delta" # Evicts 'b' (least recently used)
print(list(cache.keys())) # ['c', 'a', 'd']
✅ Accessing a key moves it to the end (most recently used).
For more usage examples and details, see examples
⚙️ Configuration Options
FlipCache
local_max: Maximum items in the in-memory cache.expire_time: Redis key expiration time.key_type: Expected key data type.value_type: Expected value data type.value_encoder: Custom function used to encode the value for redisvalue_decoder: Custom function used to decode the value from redisrefresh_expire_time_on_get: Refresh Redis key expiration on accessredis_protocol: custom redis.Redis instance to be passed
FIFODict and LRUDict
max_items: Maximum number of items the dict can hold (defaults 1000)
📊 Benchmarks
Setup
from flipcache import FlipCache
from redis import Redis
KEYS = 1_000
rdp = Redis(decode_responses=True)
cache = FlipCache(name="my_cache", redis_protocol=rdp, local_max=KEYS)
def redis_set():
for i in range(KEYS):
rdp.set(f"my_cache:{i}", i * 2)
def pycache_set():
for i in range(KEYS):
cache[i] = i * 2
def redis_get():
for _ in range(100):
for i in range(KEYS):
v = rdp.get(f"my_cache:{i}")
def pycache_get():
for _ in range(100):
for i in range(KEYS):
v = cache[i]
| Benchmark Name | Mean Time (s) | Standard Deviation |
|---|---|---|
| redis_set | 0.252 | 0.013 |
| flipcache_set | 0.242 | 0.003 |
| redis_get | 22.986 | 0.518 |
| flipcache_get | 0.0172 | 0.000 |
📋 Plans for future releases
- Make it possible to use other redis implementations such as aioredis
- Create readthedocs site for detailed documentation
- Optimize and add new functionality
- Make it threadsafe
- Add tests
Metadata
Release files for FlipCache 1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| flipcache-1.3.tar.gz | 103.5 kB | Without provenance | Details for flipcache-1.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| flipcache-1.3-py3-none-any.whl | Python 3 | none | any | Details for flipcache-1.3-py3-none-any.whl |
Total release size: 114.4 kB
Release files / flipcache-1.3.tar.gz
| Download URL | flipcache-1.3.tar.gz |
|---|---|
| Size | 103.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
25dd786863381bb1c7d796bfe3f60b0e8c40a28d9f45dfbc0d1e042dfd46b317
|
|
BLAKE2b-256 checksum How to use checksums |
3984654b7bc6d425b112b282526258a31acc6d6abc725a8e2c2b81b5581ccb3f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.9.21
|
Release files / flipcache-1.3-py3-none-any.whl
| Download URL | flipcache-1.3-py3-none-any.whl |
|---|---|
| Size | 10.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b4ba7ee0963dad58f042b1554297cf2d997a9faa611ac6084bed4f9bc221a7da
|
|
BLAKE2b-256 checksum How to use checksums |
f4c0df58405501eb7ef04fbce0eda0c5d1c5bf6b0d6e48e53b59b849c4c9d46e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.9.21
|
Security for FlipCache 1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.