🔨 refactor(base.py): refactor BaseCache class to use abstract methods
This commit refactors the BaseCache class to use abstract methods instead of concrete methods. This makes the class more flexible and allows for different implementations of the cache. The abstract methods include get, set, delete, clear, __contains__, __getitem__, __setitem__, and __delitem__.
This commit is contained in:
parent
bdbc46f01d
commit
a27e5d58dd
1 changed files with 82 additions and 141 deletions
223
src/backend/langflow/cache/base.py
vendored
223
src/backend/langflow/cache/base.py
vendored
|
|
@ -1,154 +1,95 @@
|
||||||
import base64
|
import abc
|
||||||
import contextlib
|
|
||||||
import functools
|
|
||||||
import hashlib
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import tempfile
|
|
||||||
from collections import OrderedDict
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
import dill # type: ignore
|
|
||||||
|
|
||||||
CACHE: Dict[str, Any] = {}
|
|
||||||
|
|
||||||
|
|
||||||
def create_cache_folder(func):
|
class BaseCache(abc.ABC):
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
# Get the destination folder
|
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX
|
|
||||||
|
|
||||||
# Create the destination folder if it doesn't exist
|
|
||||||
os.makedirs(cache_path, exist_ok=True)
|
|
||||||
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def memoize_dict(maxsize=128):
|
|
||||||
cache = OrderedDict()
|
|
||||||
|
|
||||||
def decorator(func):
|
|
||||||
@functools.wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
hashed = compute_dict_hash(args[0])
|
|
||||||
key = (func.__name__, hashed, frozenset(kwargs.items()))
|
|
||||||
if key not in cache:
|
|
||||||
result = func(*args, **kwargs)
|
|
||||||
cache[key] = result
|
|
||||||
if len(cache) > maxsize:
|
|
||||||
cache.popitem(last=False)
|
|
||||||
else:
|
|
||||||
result = cache[key]
|
|
||||||
return result
|
|
||||||
|
|
||||||
def clear_cache():
|
|
||||||
cache.clear()
|
|
||||||
|
|
||||||
wrapper.clear_cache = clear_cache # type: ignore
|
|
||||||
wrapper.cache = cache # type: ignore
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
PREFIX = "langflow_cache"
|
|
||||||
|
|
||||||
|
|
||||||
@create_cache_folder
|
|
||||||
def clear_old_cache_files(max_cache_size: int = 3):
|
|
||||||
cache_dir = Path(tempfile.gettempdir()) / PREFIX
|
|
||||||
cache_files = list(cache_dir.glob("*.dill"))
|
|
||||||
|
|
||||||
if len(cache_files) > max_cache_size:
|
|
||||||
cache_files_sorted_by_mtime = sorted(
|
|
||||||
cache_files, key=lambda x: x.stat().st_mtime, reverse=True
|
|
||||||
)
|
|
||||||
|
|
||||||
for cache_file in cache_files_sorted_by_mtime[max_cache_size:]:
|
|
||||||
with contextlib.suppress(OSError):
|
|
||||||
os.remove(cache_file)
|
|
||||||
|
|
||||||
|
|
||||||
def compute_dict_hash(graph_data):
|
|
||||||
graph_data = filter_json(graph_data)
|
|
||||||
|
|
||||||
cleaned_graph_json = json.dumps(graph_data, sort_keys=True)
|
|
||||||
return hashlib.sha256(cleaned_graph_json.encode("utf-8")).hexdigest()
|
|
||||||
|
|
||||||
|
|
||||||
def filter_json(json_data):
|
|
||||||
filtered_data = json_data.copy()
|
|
||||||
|
|
||||||
# Remove 'viewport' and 'chatHistory' keys
|
|
||||||
if "viewport" in filtered_data:
|
|
||||||
del filtered_data["viewport"]
|
|
||||||
if "chatHistory" in filtered_data:
|
|
||||||
del filtered_data["chatHistory"]
|
|
||||||
|
|
||||||
# Filter nodes
|
|
||||||
if "nodes" in filtered_data:
|
|
||||||
for node in filtered_data["nodes"]:
|
|
||||||
if "position" in node:
|
|
||||||
del node["position"]
|
|
||||||
if "positionAbsolute" in node:
|
|
||||||
del node["positionAbsolute"]
|
|
||||||
if "selected" in node:
|
|
||||||
del node["selected"]
|
|
||||||
if "dragging" in node:
|
|
||||||
del node["dragging"]
|
|
||||||
|
|
||||||
return filtered_data
|
|
||||||
|
|
||||||
|
|
||||||
@create_cache_folder
|
|
||||||
def save_binary_file(content: str, file_name: str, accepted_types: list[str]) -> str:
|
|
||||||
"""
|
"""
|
||||||
Save a binary file to the specified folder.
|
Abstract base class for a cache.
|
||||||
|
|
||||||
Args:
|
|
||||||
content: The content of the file as a bytes object.
|
|
||||||
file_name: The name of the file, including its extension.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The path to the saved file.
|
|
||||||
"""
|
"""
|
||||||
if not any(file_name.endswith(suffix) for suffix in accepted_types):
|
|
||||||
raise ValueError(f"File {file_name} is not accepted")
|
|
||||||
|
|
||||||
# Get the destination folder
|
@abc.abstractmethod
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX
|
def get(self, key):
|
||||||
if not content:
|
"""
|
||||||
raise ValueError("Please, reload the file in the loader.")
|
Retrieve an item from the cache.
|
||||||
data = content.split(",")[1]
|
|
||||||
decoded_bytes = base64.b64decode(data)
|
|
||||||
|
|
||||||
# Create the full file path
|
Args:
|
||||||
file_path = os.path.join(cache_path, file_name)
|
key: The key of the item to retrieve.
|
||||||
|
|
||||||
# Save the binary content to the file
|
Returns:
|
||||||
with open(file_path, "wb") as file:
|
The value associated with the key, or None if the key is not found.
|
||||||
file.write(decoded_bytes)
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
return file_path
|
@abc.abstractmethod
|
||||||
|
def set(self, key, value):
|
||||||
|
"""
|
||||||
|
Add an item to the cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The key of the item.
|
||||||
|
value: The value to cache.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
@create_cache_folder
|
@abc.abstractmethod
|
||||||
def save_cache(hash_val: str, chat_data, clean_old_cache_files: bool):
|
def delete(self, key):
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX / f"{hash_val}.dill"
|
"""
|
||||||
with cache_path.open("wb") as cache_file:
|
Remove an item from the cache.
|
||||||
dill.dump(chat_data, cache_file)
|
|
||||||
|
|
||||||
if clean_old_cache_files:
|
Args:
|
||||||
clear_old_cache_files()
|
key: The key of the item to remove.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def clear(self):
|
||||||
|
"""
|
||||||
|
Clear all items from the cache.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
@create_cache_folder
|
@abc.abstractmethod
|
||||||
def load_cache(hash_val):
|
def __contains__(self, key):
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX / f"{hash_val}.dill"
|
"""
|
||||||
if cache_path.exists():
|
Check if the key is in the cache.
|
||||||
with cache_path.open("rb") as cache_file:
|
|
||||||
return dill.load(cache_file)
|
Args:
|
||||||
return None
|
key: The key of the item to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the key is in the cache, False otherwise.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def __getitem__(self, key):
|
||||||
|
"""
|
||||||
|
Retrieve an item from the cache using the square bracket notation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The key of the item to retrieve.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The value associated with the key, or None if the key is not found.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
"""
|
||||||
|
Add an item to the cache using the square bracket notation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The key of the item.
|
||||||
|
value: The value to cache.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def __delitem__(self, key):
|
||||||
|
"""
|
||||||
|
Remove an item from the cache using the square bracket notation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The key of the item to remove.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue