Skip to main content

⚡️ FlipCache

Downloads total Downloads monthly PyPi Package Version PyPi status Supported python versions Github issues MIT License Views

PyCache Logo

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 redis
  • value_decoder: Custom function used to decode the value from redis
  • refresh_expire_time_on_get: Refresh Redis key expiration on access
  • redis_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)

Source distribution for FlipCache 1.3
File Size Uploaded
flipcache-1.3.tar.gz 103.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for FlipCache 1.3
File Interpreter ABI Platform
flipcache-1.3-py3-none-any.whl Python 3 none any Details

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

Release history Release notifications | RSS feed

This release

1.3 This release

2 release files

1.2

2 release files

1.1

2 release files

1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page