generated from jCloud/repository-template
Initial release
This commit is contained in:
@@ -0,0 +1,153 @@
|
|||||||
|
# simple-cache
|
||||||
|
|
||||||
|
A simple library for caching data.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
You can install the library using `pip`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install simple-cache --index-url https://repo.jcloud-services.ddns.net/simple/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Full documentation
|
||||||
|
|
||||||
|
### `CacheItem`
|
||||||
|
A cache item.
|
||||||
|
|
||||||
|
#### Params
|
||||||
|
- `value` (type: `Any`): The value of the item
|
||||||
|
- `creation_time` (type: `Union[int, float]`): The time the item was created
|
||||||
|
|
||||||
|
#### Exceptions
|
||||||
|
|
||||||
|
No exceptions.
|
||||||
|
|
||||||
|
#### Methods
|
||||||
|
|
||||||
|
No methods.
|
||||||
|
|
||||||
|
### `Cache`
|
||||||
|
A thread-safe cache.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
- `generate_value_func` (type: `Optional[FunctionType]`, default: `None`): The function to generate a new value. It has to take one positional argument.
|
||||||
|
- `max_size` (type: `int`, default: `256`): The maximum size of the cache.
|
||||||
|
- `ttl` (type: `int`, default: `120`): The TTL (time to live) in seconds.
|
||||||
|
|
||||||
|
#### Exceptions
|
||||||
|
|
||||||
|
No exceptions.
|
||||||
|
|
||||||
|
#### Methods
|
||||||
|
|
||||||
|
##### `set`
|
||||||
|
|
||||||
|
Sets or updates an item (thread-safe).
|
||||||
|
|
||||||
|
###### Parameters
|
||||||
|
|
||||||
|
- `key` (type: `str`): The key.
|
||||||
|
- `value` (type: `Any`): The value.
|
||||||
|
|
||||||
|
###### Exceptions
|
||||||
|
|
||||||
|
No exceptions.
|
||||||
|
|
||||||
|
###### Returns
|
||||||
|
|
||||||
|
`None`
|
||||||
|
|
||||||
|
##### `delete`
|
||||||
|
Deletes an item (thread-safe).
|
||||||
|
|
||||||
|
###### Parameters
|
||||||
|
|
||||||
|
- `key` (type: `str`): The key.
|
||||||
|
|
||||||
|
###### Exceptions
|
||||||
|
|
||||||
|
- `KeyError`: If the item does not exist.
|
||||||
|
|
||||||
|
###### Returns
|
||||||
|
|
||||||
|
`None`
|
||||||
|
|
||||||
|
##### `get`
|
||||||
|
|
||||||
|
Returns an item (thread-safe).
|
||||||
|
If the item does not exist, it uses ``self.generate_value_func`` to
|
||||||
|
get the value. If no ``generate_value_func`` was specified in the
|
||||||
|
constructor of the ``Cache`` object, the value will be set to ``None``.
|
||||||
|
|
||||||
|
###### Parameters
|
||||||
|
|
||||||
|
- `key` (type: `str`): The key.
|
||||||
|
|
||||||
|
###### Exceptions
|
||||||
|
|
||||||
|
No exceptions.
|
||||||
|
|
||||||
|
###### Returns
|
||||||
|
|
||||||
|
The value.
|
||||||
|
Type: `Any`
|
||||||
|
|
||||||
|
##### `clean_item`
|
||||||
|
|
||||||
|
Removes an item if it is expired (thread-safe).
|
||||||
|
|
||||||
|
###### Parameters
|
||||||
|
|
||||||
|
- `key` (type: `key`): The key
|
||||||
|
|
||||||
|
###### Exceptions
|
||||||
|
|
||||||
|
No exceptions.
|
||||||
|
|
||||||
|
###### Returns
|
||||||
|
|
||||||
|
`None`
|
||||||
|
|
||||||
|
##### `clear_expired`
|
||||||
|
|
||||||
|
Removes all expired items (thread-safe).
|
||||||
|
|
||||||
|
###### Parameters
|
||||||
|
|
||||||
|
No parameters.
|
||||||
|
|
||||||
|
###### Exceptions
|
||||||
|
|
||||||
|
No Exceptions.
|
||||||
|
|
||||||
|
###### Returns
|
||||||
|
|
||||||
|
`None`
|
||||||
|
|
||||||
|
##### `clear`
|
||||||
|
|
||||||
|
Removes all items (thread-safe).
|
||||||
|
|
||||||
|
###### Parameters
|
||||||
|
|
||||||
|
No parameters.
|
||||||
|
|
||||||
|
###### Exceptions
|
||||||
|
|
||||||
|
No Exceptions.
|
||||||
|
|
||||||
|
###### Returns
|
||||||
|
|
||||||
|
`None`
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### Version 0.1.0
|
||||||
|
- initial release
|
||||||
|
- thread safety
|
||||||
|
- set, update, delete and get items
|
||||||
|
- TTL
|
||||||
|
- support for specifying a function to generate new values
|
||||||
|
- deletion of expired items when getting them
|
||||||
|
- deletion of the least used item when a new item is set and the maximum size is reached
|
||||||
|
- support for clearing the cache
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "simple-cache"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A simple library for caching data."
|
||||||
|
license = "Apache-2.0"
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Copyright 2026 jCloud Services GbR
|
||||||
|
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
'''
|
||||||
|
A simple library for caching data.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ._core import *
|
||||||
|
from ._core import __all__ as _core__all__
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
*_core__all__
|
||||||
|
]
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
from heapq import nsmallest
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from types import FunctionType
|
||||||
|
from typing import Optional, Any, Union
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'CacheItem',
|
||||||
|
'Cache'
|
||||||
|
]
|
||||||
|
|
||||||
|
def _genval(_) -> None:
|
||||||
|
'''
|
||||||
|
The fallback function for the ``generate_value_func`` argument for the
|
||||||
|
constructor of ``Cache``. It simply returns ``None``, without performing any
|
||||||
|
additional operations.
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CacheItem:
|
||||||
|
def __init__(self, value: Any, creation_time: Union[int, float]):
|
||||||
|
'''
|
||||||
|
A cache item.
|
||||||
|
|
||||||
|
:param value: The value of the item
|
||||||
|
:type value: Any
|
||||||
|
:param creation_time: The time the item was created
|
||||||
|
:type creation_time: Union[int, float]
|
||||||
|
'''
|
||||||
|
|
||||||
|
self.value = value
|
||||||
|
self.creation_time = creation_time
|
||||||
|
|
||||||
|
class Cache:
|
||||||
|
def __init__(self, generate_value_func: Optional[FunctionType] = None, max_size: int = 256, ttl: int = 120):
|
||||||
|
'''
|
||||||
|
A thread-safe cache.
|
||||||
|
|
||||||
|
:param generate_value_func: The function to generate a new value. It has to take one positional argument.
|
||||||
|
:type generate_value_func: Optional[FunctionType]
|
||||||
|
:param max_size: The maximum size of the cache.
|
||||||
|
:type max_size: int
|
||||||
|
:param ttl: The TTL (time to live) in seconds.
|
||||||
|
:type ttl: int
|
||||||
|
'''
|
||||||
|
|
||||||
|
if generate_value_func is None:
|
||||||
|
generate_value_func = _genval
|
||||||
|
|
||||||
|
self._cache = dict()
|
||||||
|
self.max_size = max_size
|
||||||
|
self.ttl = ttl
|
||||||
|
self.generate_value_func = generate_value_func
|
||||||
|
self._accesses = dict()
|
||||||
|
self._all_accesses = 0
|
||||||
|
self._lock = threading.RLock()
|
||||||
|
|
||||||
|
def _get_least_used_keys(self, number_of_keys: int = 1) -> list:
|
||||||
|
'''
|
||||||
|
Returns the keys that are used the least. The number of keys that are
|
||||||
|
returned can be specified with the parameter ``number_of_keys``.
|
||||||
|
|
||||||
|
:param number_of_keys: The number of keys to return.
|
||||||
|
:type number_of_keys: int
|
||||||
|
|
||||||
|
:return: The ``number_of_keys``-th least used keys.
|
||||||
|
:rtype: list
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not self._accesses or number_of_keys <= 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [k for k in nsmallest(number_of_keys, self._accesses, key = lambda k: self._accesses[k])]
|
||||||
|
|
||||||
|
def _set(self, key: str, value: Any) -> None:
|
||||||
|
'''
|
||||||
|
Sets or updated an item.
|
||||||
|
|
||||||
|
:param key: The key.
|
||||||
|
:type key: str
|
||||||
|
:param value: The value.
|
||||||
|
:type value: Any
|
||||||
|
'''
|
||||||
|
|
||||||
|
new_key = key not in self._cache.keys()
|
||||||
|
if len(self._cache) >= self.max_size and new_key:
|
||||||
|
self._clear_expired()
|
||||||
|
if len(self._cache) >= self.max_size:
|
||||||
|
least_used_keys = self._get_least_used_keys(1)
|
||||||
|
if least_used_keys:
|
||||||
|
self._delete(least_used_keys[0])
|
||||||
|
|
||||||
|
self._accesses[key] = 0 if new_key else self._accesses[key]
|
||||||
|
self._cache[key] = CacheItem(value, time.time())
|
||||||
|
|
||||||
|
def _delete(self, key: str) -> None:
|
||||||
|
'''
|
||||||
|
Deletes an item.
|
||||||
|
|
||||||
|
:param key: The key.
|
||||||
|
:type key: str
|
||||||
|
|
||||||
|
:raises KeyError: If the item does not exist.
|
||||||
|
'''
|
||||||
|
|
||||||
|
if key in self._cache:
|
||||||
|
self._all_accesses -= self._accesses[key]
|
||||||
|
del self._cache[key]
|
||||||
|
del self._accesses[key]
|
||||||
|
else:
|
||||||
|
raise KeyError(key)
|
||||||
|
|
||||||
|
def _get(self, key: str) -> Any:
|
||||||
|
'''
|
||||||
|
Returns an item.
|
||||||
|
If the item does not exist, it uses ``self.generate_value_func`` to
|
||||||
|
get the value. If no ``generate_value_func`` was specified in the
|
||||||
|
constructor of the ``Cache`` object, the value will be set to ``None``.
|
||||||
|
|
||||||
|
:param key: The key.
|
||||||
|
:type key: str
|
||||||
|
|
||||||
|
:return: The value.
|
||||||
|
:rtype: Any
|
||||||
|
'''
|
||||||
|
|
||||||
|
self._clean_item(key)
|
||||||
|
if key in self._cache:
|
||||||
|
value = self._cache[key].value
|
||||||
|
else:
|
||||||
|
value = self.generate_value_func(key)
|
||||||
|
self._set(key, value)
|
||||||
|
self._accesses[key] += 1
|
||||||
|
self._all_accesses += 1
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _clean_item(self, key: str) -> None:
|
||||||
|
'''
|
||||||
|
Removes an item if it is expired.
|
||||||
|
|
||||||
|
:param key: The key.
|
||||||
|
:type key: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
if key in self._cache:
|
||||||
|
ct = self._cache[key].creation_time
|
||||||
|
if time.time() - ct > self.ttl:
|
||||||
|
self._delete(key)
|
||||||
|
|
||||||
|
def _clear_expired(self) -> None:
|
||||||
|
'''
|
||||||
|
Removes all expired items.
|
||||||
|
'''
|
||||||
|
|
||||||
|
expired = [k for k, v in self._cache.items() if time.time() - v.creation_time > self.ttl]
|
||||||
|
for e in expired:
|
||||||
|
self._delete(e)
|
||||||
|
|
||||||
|
def _clear(self) -> None:
|
||||||
|
'''
|
||||||
|
Removes all items.
|
||||||
|
'''
|
||||||
|
|
||||||
|
self._all_accesses = 0
|
||||||
|
self._accesses = dict()
|
||||||
|
self._cache = dict()
|
||||||
|
|
||||||
|
def set(self, key: str, value: Any) -> None:
|
||||||
|
'''
|
||||||
|
Sets or updates an item (thread-safe).
|
||||||
|
|
||||||
|
:param key: The key.
|
||||||
|
:type key: str
|
||||||
|
:param value: The value.
|
||||||
|
:type value: Any
|
||||||
|
'''
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
self._set(key, value)
|
||||||
|
|
||||||
|
def delete(self, key: str) -> None:
|
||||||
|
'''
|
||||||
|
Deletes an item (thread-safe).
|
||||||
|
|
||||||
|
:param key: The key.
|
||||||
|
:type key: str
|
||||||
|
|
||||||
|
:raises KeyError: If the item does not exist.
|
||||||
|
'''
|
||||||
|
with self._lock:
|
||||||
|
self._delete(key)
|
||||||
|
|
||||||
|
def get(self, key: str) -> Any:
|
||||||
|
'''
|
||||||
|
Returns an item (thread-safe).
|
||||||
|
If the item does not exist, it uses ``self.generate_value_func`` to
|
||||||
|
get the value. If no ``generate_value_func`` was specified in the
|
||||||
|
constructor of the ``Cache`` object, the value will be set to ``None``.
|
||||||
|
|
||||||
|
:param key: The key.
|
||||||
|
:type key: str
|
||||||
|
|
||||||
|
:return: The value.
|
||||||
|
:rtype: Any
|
||||||
|
'''
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
return self._get(key)
|
||||||
|
|
||||||
|
def clean_item(self, key: str) -> None:
|
||||||
|
'''
|
||||||
|
Removes an item if it is expired (thread-safe).
|
||||||
|
|
||||||
|
:param key: The key.
|
||||||
|
:type key: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
self._clean_item(key)
|
||||||
|
|
||||||
|
def clear_expired(self) -> None:
|
||||||
|
'''
|
||||||
|
Removes all expired items (thread-safe).
|
||||||
|
'''
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
self._clear_expired()
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
'''
|
||||||
|
Removes all items (thread-safe).
|
||||||
|
'''
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
self._clear()
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
return self.get(key)
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.set(key, value)
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
self.delete(key)
|
||||||
Reference in New Issue
Block a user