Name | FlipCache JSON |
Version |
1.0
JSON |
| download |
home_page | None |
Summary | Redis-backed hybrid caching for lightning-fast Python data access |
upload_time | 2024-04-21 01:18:53 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.6 |
license | None |
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"
}