📦 chore(cache): add cache module with cache manager, factory, base cache, and in-memory cache implementations
✨ feat(utils.py): add cache utility functions for memoization and file saving 🐛 fix(utils.py): fix cache folder creation to use the correct cache directory 🔧 chore(utils.py): refactor code to improve readability and remove unused imports
This commit is contained in:
parent
63a9b01bbc
commit
d5ad152250
6 changed files with 584 additions and 0 deletions
11
src/backend/langflow/services/cache/__init__.py
vendored
Normal file
11
src/backend/langflow/services/cache/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from . import factory, manager
|
||||||
|
from langflow.services.cache.manager import cache_manager
|
||||||
|
from langflow.services.cache.flow import InMemoryCache
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"cache_manager",
|
||||||
|
"factory",
|
||||||
|
"manager",
|
||||||
|
"InMemoryCache",
|
||||||
|
]
|
||||||
84
src/backend/langflow/services/cache/base.py
vendored
Normal file
84
src/backend/langflow/services/cache/base.py
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import abc
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCache(abc.ABC):
|
||||||
|
"""
|
||||||
|
Abstract base class for a cache.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get(self, key):
|
||||||
|
"""
|
||||||
|
Retrieve an item from the cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The key of the item to retrieve.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The value associated with the key, or None if the key is not found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def delete(self, key):
|
||||||
|
"""
|
||||||
|
Remove an item from the cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The key of the item to remove.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def clear(self):
|
||||||
|
"""
|
||||||
|
Clear all items from the cache.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def __contains__(self, key):
|
||||||
|
"""
|
||||||
|
Check if the key is in the cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The key of the item to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the key is in the cache, False otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@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.
|
||||||
|
"""
|
||||||
11
src/backend/langflow/services/cache/factory.py
vendored
Normal file
11
src/backend/langflow/services/cache/factory.py
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from langflow.services.cache.manager import CacheManager
|
||||||
|
from langflow.services.factory import ServiceFactory
|
||||||
|
|
||||||
|
|
||||||
|
class CacheManagerFactory(ServiceFactory):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(CacheManager)
|
||||||
|
|
||||||
|
def create(self, settings_service):
|
||||||
|
# Here you would have logic to create and configure a CacheManager
|
||||||
|
return CacheManager()
|
||||||
146
src/backend/langflow/services/cache/flow.py
vendored
Normal file
146
src/backend/langflow/services/cache/flow.py
vendored
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from langflow.services.cache.base import BaseCache
|
||||||
|
|
||||||
|
|
||||||
|
class InMemoryCache(BaseCache):
|
||||||
|
"""
|
||||||
|
A simple in-memory cache using an OrderedDict.
|
||||||
|
|
||||||
|
This cache supports setting a maximum size and expiration time for cached items.
|
||||||
|
When the cache is full, it uses a Least Recently Used (LRU) eviction policy.
|
||||||
|
Thread-safe using a threading Lock.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
max_size (int, optional): Maximum number of items to store in the cache.
|
||||||
|
expiration_time (int, optional): Time in seconds after which a cached item expires. Default is 1 hour.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
cache = InMemoryCache(max_size=3, expiration_time=5)
|
||||||
|
|
||||||
|
# setting cache values
|
||||||
|
cache.set("a", 1)
|
||||||
|
cache.set("b", 2)
|
||||||
|
cache["c"] = 3
|
||||||
|
|
||||||
|
# getting cache values
|
||||||
|
a = cache.get("a")
|
||||||
|
b = cache["b"]
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, max_size=None, expiration_time=60 * 60):
|
||||||
|
"""
|
||||||
|
Initialize a new InMemoryCache instance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
max_size (int, optional): Maximum number of items to store in the cache.
|
||||||
|
expiration_time (int, optional): Time in seconds after which a cached item expires. Default is 1 hour.
|
||||||
|
"""
|
||||||
|
self._cache = OrderedDict()
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self.max_size = max_size
|
||||||
|
self.expiration_time = expiration_time
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
"""
|
||||||
|
Retrieve an item from the cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The key of the item to retrieve.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The value associated with the key, or None if the key is not found or the item has expired.
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if key in self._cache:
|
||||||
|
item = self._cache.pop(key)
|
||||||
|
if (
|
||||||
|
self.expiration_time is None
|
||||||
|
or time.time() - item["time"] < self.expiration_time
|
||||||
|
):
|
||||||
|
# Move the key to the end to make it recently used
|
||||||
|
self._cache[key] = item
|
||||||
|
return item["value"]
|
||||||
|
else:
|
||||||
|
self.delete(key)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set(self, key, value):
|
||||||
|
"""
|
||||||
|
Add an item to the cache.
|
||||||
|
|
||||||
|
If the cache is full, the least recently used item is evicted.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The key of the item.
|
||||||
|
value: The value to cache.
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if key in self._cache:
|
||||||
|
# Remove existing key before re-inserting to update order
|
||||||
|
self.delete(key)
|
||||||
|
elif self.max_size and len(self._cache) >= self.max_size:
|
||||||
|
# Remove least recently used item
|
||||||
|
self._cache.popitem(last=False)
|
||||||
|
self._cache[key] = {"value": value, "time": time.time()}
|
||||||
|
|
||||||
|
def get_or_set(self, key, value):
|
||||||
|
"""
|
||||||
|
Retrieve an item from the cache. If the item does not exist, set it with the provided value.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The key of the item.
|
||||||
|
value: The value to cache if the item doesn't exist.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The cached value associated with the key.
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
if key in self._cache:
|
||||||
|
return self.get(key)
|
||||||
|
self.set(key, value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def delete(self, key):
|
||||||
|
"""
|
||||||
|
Remove an item from the cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The key of the item to remove.
|
||||||
|
"""
|
||||||
|
# with self._lock:
|
||||||
|
self._cache.pop(key, None)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""
|
||||||
|
Clear all items from the cache.
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
self._cache.clear()
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
"""Check if the key is in the cache."""
|
||||||
|
return key in self._cache
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
"""Retrieve an item from the cache using the square bracket notation."""
|
||||||
|
return self.get(key)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
"""Add an item to the cache using the square bracket notation."""
|
||||||
|
self.set(key, value)
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
"""Remove an item from the cache using the square bracket notation."""
|
||||||
|
self.delete(key)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""Return the number of items in the cache."""
|
||||||
|
return len(self._cache)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Return a string representation of the InMemoryCache instance."""
|
||||||
|
return f"InMemoryCache(max_size={self.max_size}, expiration_time={self.expiration_time})"
|
||||||
153
src/backend/langflow/services/cache/manager.py
vendored
Normal file
153
src/backend/langflow/services/cache/manager.py
vendored
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from typing import Any, Awaitable, Callable, List, Optional
|
||||||
|
from langflow.services.base import Service
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
class Subject:
|
||||||
|
"""Base class for implementing the observer pattern."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.observers: List[Callable[[], None]] = []
|
||||||
|
|
||||||
|
def attach(self, observer: Callable[[], None]):
|
||||||
|
"""Attach an observer to the subject."""
|
||||||
|
self.observers.append(observer)
|
||||||
|
|
||||||
|
def detach(self, observer: Callable[[], None]):
|
||||||
|
"""Detach an observer from the subject."""
|
||||||
|
self.observers.remove(observer)
|
||||||
|
|
||||||
|
def notify(self):
|
||||||
|
"""Notify all observers about an event."""
|
||||||
|
for observer in self.observers:
|
||||||
|
if observer is None:
|
||||||
|
continue
|
||||||
|
observer()
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncSubject:
|
||||||
|
"""Base class for implementing the async observer pattern."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.observers: List[Callable[[], Awaitable]] = []
|
||||||
|
|
||||||
|
def attach(self, observer: Callable[[], Awaitable]):
|
||||||
|
"""Attach an observer to the subject."""
|
||||||
|
self.observers.append(observer)
|
||||||
|
|
||||||
|
def detach(self, observer: Callable[[], Awaitable]):
|
||||||
|
"""Detach an observer from the subject."""
|
||||||
|
self.observers.remove(observer)
|
||||||
|
|
||||||
|
async def notify(self):
|
||||||
|
"""Notify all observers about an event."""
|
||||||
|
for observer in self.observers:
|
||||||
|
if observer is None:
|
||||||
|
continue
|
||||||
|
await observer()
|
||||||
|
|
||||||
|
|
||||||
|
class CacheManager(Subject, Service):
|
||||||
|
"""Manages cache for different clients and notifies observers on changes."""
|
||||||
|
|
||||||
|
name = "cache_manager"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._cache = {}
|
||||||
|
self.current_client_id = None
|
||||||
|
self.current_cache = {}
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def set_client_id(self, client_id: str):
|
||||||
|
"""
|
||||||
|
Context manager to set the current client_id and associated cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client_id (str): The client identifier.
|
||||||
|
"""
|
||||||
|
previous_client_id = self.current_client_id
|
||||||
|
self.current_client_id = client_id
|
||||||
|
self.current_cache = self._cache.setdefault(client_id, {})
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.current_client_id = previous_client_id
|
||||||
|
self.current_cache = self._cache.get(self.current_client_id, {})
|
||||||
|
|
||||||
|
def add(self, name: str, obj: Any, obj_type: str, extension: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
Add an object to the current client's cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The cache key.
|
||||||
|
obj (Any): The object to cache.
|
||||||
|
obj_type (str): The type of the object.
|
||||||
|
"""
|
||||||
|
object_extensions = {
|
||||||
|
"image": "png",
|
||||||
|
"pandas": "csv",
|
||||||
|
}
|
||||||
|
if obj_type in object_extensions:
|
||||||
|
_extension = object_extensions[obj_type]
|
||||||
|
else:
|
||||||
|
_extension = type(obj).__name__.lower()
|
||||||
|
self.current_cache[name] = {
|
||||||
|
"obj": obj,
|
||||||
|
"type": obj_type,
|
||||||
|
"extension": extension or _extension,
|
||||||
|
}
|
||||||
|
self.notify()
|
||||||
|
|
||||||
|
def add_pandas(self, name: str, obj: Any):
|
||||||
|
"""
|
||||||
|
Add a pandas DataFrame or Series to the current client's cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The cache key.
|
||||||
|
obj (Any): The pandas DataFrame or Series object.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, (pd.DataFrame, pd.Series)):
|
||||||
|
self.add(name, obj.to_csv(), "pandas", extension="csv")
|
||||||
|
else:
|
||||||
|
raise ValueError("Object is not a pandas DataFrame or Series")
|
||||||
|
|
||||||
|
def add_image(self, name: str, obj: Any, extension: str = "png"):
|
||||||
|
"""
|
||||||
|
Add a PIL Image to the current client's cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The cache key.
|
||||||
|
obj (Any): The PIL Image object.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, Image.Image):
|
||||||
|
self.add(name, obj, "image", extension=extension)
|
||||||
|
else:
|
||||||
|
raise ValueError("Object is not a PIL Image")
|
||||||
|
|
||||||
|
def get(self, name: str):
|
||||||
|
"""
|
||||||
|
Get an object from the current client's cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The cache key.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The cached object associated with the given cache key.
|
||||||
|
"""
|
||||||
|
return self.current_cache[name]
|
||||||
|
|
||||||
|
def get_last(self):
|
||||||
|
"""
|
||||||
|
Get the last added item in the current client's cache.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The last added item in the cache.
|
||||||
|
"""
|
||||||
|
return list(self.current_cache.values())[-1]
|
||||||
|
|
||||||
|
|
||||||
|
cache_manager = CacheManager()
|
||||||
179
src/backend/langflow/services/cache/utils.py
vendored
Normal file
179
src/backend/langflow/services/cache/utils.py
vendored
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
import base64
|
||||||
|
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
|
||||||
|
from appdirs import user_cache_dir
|
||||||
|
|
||||||
|
CACHE: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
CACHE_DIR = user_cache_dir("langflow", "langflow")
|
||||||
|
|
||||||
|
|
||||||
|
def create_cache_folder(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
# Get the destination folder
|
||||||
|
cache_path = Path(CACHE_DIR) / 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
cache_path = Path(CACHE_DIR) / PREFIX
|
||||||
|
if not content:
|
||||||
|
raise ValueError("Please, reload the file in the loader.")
|
||||||
|
data = content.split(",")[1]
|
||||||
|
decoded_bytes = base64.b64decode(data)
|
||||||
|
|
||||||
|
# Create the full file path
|
||||||
|
file_path = os.path.join(cache_path, file_name)
|
||||||
|
|
||||||
|
# Save the binary content to the file
|
||||||
|
with open(file_path, "wb") as file:
|
||||||
|
file.write(decoded_bytes)
|
||||||
|
|
||||||
|
return file_path
|
||||||
|
|
||||||
|
|
||||||
|
@create_cache_folder
|
||||||
|
def save_uploaded_file(file, folder_name):
|
||||||
|
"""
|
||||||
|
Save an uploaded file to the specified folder with a hash of its content as the file name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file: The uploaded file object.
|
||||||
|
folder_name: The name of the folder to save the file in.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The path to the saved file.
|
||||||
|
"""
|
||||||
|
cache_path = Path(CACHE_DIR)
|
||||||
|
folder_path = cache_path / folder_name
|
||||||
|
|
||||||
|
# Create the folder if it doesn't exist
|
||||||
|
if not folder_path.exists():
|
||||||
|
folder_path.mkdir()
|
||||||
|
|
||||||
|
# Create a hash of the file content
|
||||||
|
sha256_hash = hashlib.sha256()
|
||||||
|
# Reset the file cursor to the beginning of the file
|
||||||
|
file.seek(0)
|
||||||
|
# Iterate over the uploaded file in small chunks to conserve memory
|
||||||
|
while chunk := file.read(8192): # Read 8KB at a time (adjust as needed)
|
||||||
|
sha256_hash.update(chunk)
|
||||||
|
|
||||||
|
# Use the hex digest of the hash as the file name
|
||||||
|
hex_dig = sha256_hash.hexdigest()
|
||||||
|
file_name = hex_dig
|
||||||
|
|
||||||
|
# Reset the file cursor to the beginning of the file
|
||||||
|
file.seek(0)
|
||||||
|
|
||||||
|
# Save the file with the hash as its name
|
||||||
|
file_path = folder_path / file_name
|
||||||
|
with open(file_path, "wb") as new_file:
|
||||||
|
while chunk := file.read(8192):
|
||||||
|
new_file.write(chunk)
|
||||||
|
|
||||||
|
return file_path
|
||||||
Loading…
Add table
Add a link
Reference in a new issue