FlipCache


NameFlipCache JSON
Version 1.0 PyPI version JSON
download
home_pageNone
SummaryRedis-backed hybrid caching for lightning-fast Python data access
upload_time2024-04-21 01:18:53
maintainerNone
docs_urlNone
authorNone
requires_python>=3.6
licenseNone
keywords cache fast_access memory redis wrapper
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <img align="right" src="assets/logo.jpg" alt="PyCache Logo" height="250px" width="auto">

# ⚡️ 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
```bash
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

```python
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

```python
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

```python
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

```python
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)
```

For more usage examples and details, see [examples](./examples)

## ⚙️ Configuration Options
- `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

## 📊 Benchmarks

<details>
    <summary>Setup</summary>

```python
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]
```
</details>

| 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
            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "FlipCache",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.6",
    "maintainer_email": "Jasur Yusupov <jasuryusupov14@gmail.com>",
    "keywords": "cache, fast_access, memory, redis, wrapper",
    "author": null,
    "author_email": "Jasur Yusupov <jasuryusupov14@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/e1/d9/17ca5581312bb2f004d6d9c9be11c4b11011563784e959b50a4785f0b363/flipcache-1.0.tar.gz",
    "platform": null,
    "description": "<img align=\"right\" src=\"assets/logo.jpg\" alt=\"PyCache Logo\" height=\"250px\" width=\"auto\">\n\n# \u26a1\ufe0f FlipCache\n\nRedis-backed hybrid caching for lightning-fast Python data access\n\n## \ud83e\udd37\u200d\u2642\ufe0f Why FlipCache?\n\n- Seamlessly integrate Redis for accelerated data retrieval in your Python projects.\n- Optimize performance with an in-memory cache layer backed by Redis persistence.\n- Enjoy ease-of-use and flexible configuration\n\n\n## \ud83d\udce5 Installation\n```bash\npip install flipcache\n```\n\n## \ud83d\ude80 Key Features\n- **Hybrid Caching**: Transparent in-memory caching combined with Redis for scalable persistence.\n- **Expire Times**: Set custom expiration times for cached data.\n- **Configurable**: Tailor cache size, data types, and more.\n\n## \ud83d\udc68\u200d\ud83d\udcbb Usage Examples\n### Basic\n\n```python\nfrom flipcache import FlipCache\n\ncache = FlipCache(\"my_cache\")\n\ncache[\"my_key\"] = \"my_value\"\nprint(cache[\"my_key\"])  # Outputs: \"my_value\"\nprint(cache[\"unknown\"])  # Outputs: None\nprint(\"my_key\" in cache)  # Outputs: True\n\n```\nPros compared to using simple dictionary: \n- Data persistence backed by Redis\n- Seamless data conversion from Redis to Python\n- Fast data access, compared to pure redis\n- Returns None instead of raising an error on key indexing\n\n### Expiring Cache\n\n```python\nimport time\nfrom flipcache import FlipCache\n\nexpiring_cache = FlipCache(\"expiring\", local_max=0, expire_time=5)\n\nexpiring_cache[\"data\"] = \"This will expire\"\ntime.sleep(6)\nprint(expiring_cache[\"data\"])  # Outputs: None\n```\nIn order to expiring-feature work with its full potential, we need to set `local_max` to `0`, removing the caching layer. \nYou lose out on faster data retrieval, in order to get precise expiration results.\nWe can combine `expire_time` and `local_max`, in that case we can access data from cache memory that could have been expired.\n\n### JSON Cache\n\n```python\nfrom flipcache import FlipCache, et\n\nuser_data = FlipCache(\n    \"user_data\",\n    local_max=100,\n    expire_time=et.THREE_DAYS,\n    value_type=\"json\"\n)\n\ndata = {\n    \"state\": 1,\n    \"orders\": [1, 2, 3, 4],\n    \"items\": {\n        \"foo\": 1,\n        \"bar\": True,\n        \"baz\": []\n    }\n}\n\n# Store data\nuser_data[\"some-uuid\"] = data\nprint(user_data[\"some-uuid\"])  # {'state': 1, 'orders': [1, 2, 3, 4], 'items': {'foo': 1, 'bar': True, 'baz': []}}\n\n# Update data\ndata[\"state\"] = 2\ndata[\"items\"][\"bar\"] = False\nuser_data[\"some-uuid\"] = data\nprint(user_data[\"some-uuid\"])  # {'state': 2, 'orders': [1, 2, 3, 4], 'items': {'foo': 1, 'bar': False, 'baz': []}}\n\n# Delete data\ndel user_data[\"some-uuid\"]\nprint(user_data[\"some-uuid\"])  # None\n```\n\n### Custom Encoder/Decoder\n\n```python\nfrom flipcache import FlipCache\nfrom dataclasses import dataclass, field\n\n\n@dataclass\nclass Shape:\n    name: str = \"default\"\n    dimensions: list[float] = field(default_factory=list)\n    edges: int = 0\n    area: float = 0\n\n    def __post_init__(self):\n        if not self.area and self.dimensions:\n            self.area = self.dimensions[0] * self.dimensions[1]\n\n\ndef encode_shape(shape: Shape) -> str:\n    return f\"{shape.name}||{shape.dimensions}||{shape.edges}||{shape.area}\"\n\n\ndef decode_shape(shape: str) -> Shape:\n    data = shape.split(\"||\")\n    return Shape(\n        name=data[0],\n        dimensions=[float(num) for num in data[1].strip('[]').split(',') if num],\n        edges=int(data[2]),\n        area=float(data[3])\n    )\n\n\nmy_shape = Shape(name='womp', dimensions=[4.1, 3.4], edges=6, area=16.38)\nshape2 = Shape(name='wat', dimensions=[11, 22])\n\ncustom = FlipCache(\n    \"custom\",\n    local_max=0,\n    key_type='int',\n    value_type='custom',\n    value_default=Shape(),\n    value_encoder=encode_shape,\n    value_decoder=decode_shape\n)\n\ncustom[123] = my_shape\ncustom[456] = shape2\nprint(custom[123])  # Shape(name='womp', dimensions=[4.1, 3.4], edges=6, area=16.38)\nprint(custom[321])  # Shape(name='default', dimensions=[], edges=0, area=0.0)\nprint(custom[456])  # Shape(name='wat', dimensions=[11.0, 22.0], edges=0, area=242.0)\n```\n\nFor more usage examples and details, see [examples](./examples)\n\n## \u2699\ufe0f Configuration Options\n- `local_max`: Maximum items in the in-memory cache.\n- `expire_time`: Redis key expiration time.\n- `key_type`: Expected key data type.\n- `value_type`: Expected value data type.\n- `value_encoder`: Custom function used to encode the value for redis\n- `value_decoder`: Custom function used to decode the value from redis\n- `refresh_expire_time_on_get`: Refresh Redis key expiration on access\n- `redis_protocol`: custom redis.Redis instance to be passed\n\n## \ud83d\udcca Benchmarks\n\n<details>\n    <summary>Setup</summary>\n\n```python\nfrom flipcache import FlipCache\nfrom redis import Redis\n\nKEYS = 1_000\nrdp = Redis(decode_responses=True)\ncache = FlipCache(name=\"my_cache\", redis_protocol=rdp, local_max=KEYS)\n\n\ndef redis_set():\n    for i in range(KEYS):\n        rdp.set(f\"my_cache:{i}\", i * 2)\n\n\ndef pycache_set():\n    for i in range(KEYS):\n        cache[i] = i * 2\n\n\ndef redis_get():\n    for _ in range(100):\n        for i in range(KEYS):\n            v = rdp.get(f\"my_cache:{i}\")\n\n\ndef pycache_get():\n    for _ in range(100):\n        for i in range(KEYS):\n            v = cache[i]\n```\n</details>\n\n| Benchmark Name | Mean Time (s) | Standard Deviation |\n|----------------|---------------|--------------------|\n| redis_set      | 0.252         | 0.013              |\n| flipcache_set  | 0.242         | 0.003              |\n| redis_get      | 22.986        | 0.518              |\n| flipcache_get  | 0.0172        | 0.000              |\n\n\n\n## \ud83d\udccb Plans for future releases\n- Make it possible to use other redis implementations such as aioredis\n- Create readthedocs site for detailed documentation\n- Optimize and add new functionality\n- Make it threadsafe\n- Add tests",
    "bugtrack_url": null,
    "license": null,
    "summary": "Redis-backed hybrid caching for lightning-fast Python data access",
    "version": "1.0",
    "project_urls": {
        "Homepage": "https://github.com/goodeejay/FlipCache",
        "Issues": "https://github.com/goodeejay/FlipCache/issues"
    },
    "split_keywords": [
        "cache",
        " fast_access",
        " memory",
        " redis",
        " wrapper"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ddc1a5aae5a6158fce1f581e00f8a0645344567a90c9c3db62ee64c3c2095b0f",
                "md5": "52da4da2294314099801443b4d8855f0",
                "sha256": "143eb626c1b153f8ea9797c626edfdb15d2a3138e130be8117428e48cb67c5cc"
            },
            "downloads": -1,
            "filename": "flipcache-1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "52da4da2294314099801443b4d8855f0",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.6",
            "size": 6846,
            "upload_time": "2024-04-21T01:18:50",
            "upload_time_iso_8601": "2024-04-21T01:18:50.745196Z",
            "url": "https://files.pythonhosted.org/packages/dd/c1/a5aae5a6158fce1f581e00f8a0645344567a90c9c3db62ee64c3c2095b0f/flipcache-1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e1d917ca5581312bb2f004d6d9c9be11c4b11011563784e959b50a4785f0b363",
                "md5": "4bc539e96159268b3fa55c59b0b917ca",
                "sha256": "5f13df180dc838b7271ff6819778340d6a1277a999d82628ae0e0319272b6437"
            },
            "downloads": -1,
            "filename": "flipcache-1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "4bc539e96159268b3fa55c59b0b917ca",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6",
            "size": 87548,
            "upload_time": "2024-04-21T01:18:53",
            "upload_time_iso_8601": "2024-04-21T01:18:53.356583Z",
            "url": "https://files.pythonhosted.org/packages/e1/d9/17ca5581312bb2f004d6d9c9be11c4b11011563784e959b50a4785f0b363/flipcache-1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-21 01:18:53",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "goodeejay",
    "github_project": "FlipCache",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "flipcache"
}
        
Elapsed time: 0.24937s