From 72bb572181bc0e39914e1abe75b1b21710cf9356 Mon Sep 17 00:00:00 2001 From: doctorpangloss <2229300+doctorpangloss@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:52:36 -0700 Subject: [PATCH] Cache requests in nodes --- comfy/cmd/execution.py | 4 ++- comfy/node_requests_caching.py | 39 ++++++++++++++++++++++++ pyproject.toml | 1 + tests/unit/test_requests_caching.py | 46 +++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 comfy/node_requests_caching.py create mode 100644 tests/unit/test_requests_caching.py diff --git a/comfy/cmd/execution.py b/comfy/cmd/execution.py index 9ad735af7..f05efaaff 100644 --- a/comfy/cmd/execution.py +++ b/comfy/cmd/execution.py @@ -44,6 +44,7 @@ from ..component_model.queue_types import QueueTuple, HistoryEntry, QueueItem, M ExecutionStatusAsDict from ..execution_context import context_execute_node, context_execute_prompt from ..execution_ext import should_panic_on_exception +from ..node_requests_caching import use_requests_caching from ..nodes.package_typing import InputTypeSpec, FloatSpecOptions, IntSpecOptions, CustomNode from ..nodes_context import get_nodes from comfy_execution.progress import get_progress_state, reset_progress_state, add_progress_handler, WebUIProgressHandler, \ @@ -467,7 +468,8 @@ async def execute(server: ExecutorToClientProgress, dynprompt: DynamicPrompt, ca :param pending_subgraph_results: :return: """ - with context_execute_node(node_id): + with context_execute_node(node_id), \ + use_requests_caching(): return await _execute(server, dynprompt, caches, node_id, extra_data, executed, prompt_id, execution_list, pending_subgraph_results, pending_async_nodes) diff --git a/comfy/node_requests_caching.py b/comfy/node_requests_caching.py new file mode 100644 index 000000000..5aa5385e8 --- /dev/null +++ b/comfy/node_requests_caching.py @@ -0,0 +1,39 @@ +import os.path +import pathlib + +import requests_cache +from contextlib import contextmanager + + +@contextmanager +def use_requests_caching( + cache_name='http_cache', + cache_control=True, + **kwargs +): + """ + A context manager to globally patch 'requests' with 'requests-cache' + for all code executed within its scope. + + This implementation uses the 'filesystem' backend, which is ideal + for large file responses. + + By default, it also sets 'use_cache_dir=True'. This automatically + tells requests-cache to store its cache files in the standard + user cache directory for your operating system. + + - On Linux, this respects the $XDG_CACHE_HOME environment variable. + - On macOS, it uses ~/Library/Caches// + - On Windows, it uses %LOCALAPPDATA%\\\\Cache + + You do not need to populate a directory variable; this parameter + handles it for you. + """ + + kwargs['backend'] = 'filesystem' + path_provided = isinstance(cache_name, pathlib.PurePath) or os.path.sep in str(cache_name) or '.' == str(cache_name)[0] + kwargs.setdefault('use_cache_dir', not path_provided) + kwargs.setdefault('cache_control', cache_control) + + with requests_cache.enabled(cache_name, **kwargs): + yield diff --git a/pyproject.toml b/pyproject.toml index 6501e5797..bd338cdca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,6 +111,7 @@ dependencies = [ "trimesh", # doesn't support linux correctly yet "stringzilla<4.2.0", + "requests_cache", ] [build-system] diff --git a/tests/unit/test_requests_caching.py b/tests/unit/test_requests_caching.py new file mode 100644 index 000000000..6c443d543 --- /dev/null +++ b/tests/unit/test_requests_caching.py @@ -0,0 +1,46 @@ +import pytest +import time +import requests +import logging + +from comfy.node_requests_caching import use_requests_caching + +logger = logging.getLogger(__name__) + +MIN_NETWORK_TIME_SEC = 0.1 + + +@pytest.mark.parametrize("test_url", [ + "https://fonts.gstatic.com/s/lato/v23/S6uyw4BMUTPHjxAwXiWtFCfQ7A.woff2" +]) +def test_caching_context_manager_works(test_url, tmp_path): + logger.info(f"\n[Test] Call 1 (Inside Context): Fetching... {test_url}") + start_time_1 = time.time() + with use_requests_caching(cache_name=tmp_path): + r1 = requests.get(test_url, timeout=10) + duration_1 = time.time() - start_time_1 + + logger.info(f"Call 1 took: {duration_1:.3f}s") + assert r1.status_code == 200 + assert r1.from_cache is False + assert "Cache-Control" in r1.headers, "Response must have 'Cache-Control' header for this test to be valid" + + logger.info(f"[Test] Call 2 (Inside Context): From cache... {test_url}") + start_time_2 = time.time() + with use_requests_caching(cache_name=tmp_path): + r2 = requests.get(test_url, timeout=10) + duration_2 = time.time() - start_time_2 + + logger.info(f"Call 2 took: {duration_2:.3f}s") + assert r2.status_code == 200 + assert r2.from_cache is True + + logger.info(f"[Test] Call 3 (Outside Context): Fetching again... {test_url}") + start_time_3 = time.time() + r3 = requests.get(test_url, timeout=10) + duration_3 = time.time() - start_time_3 + + logger.info(f"Call 3 took: {duration_3:.3f}s") + assert r3.status_code == 200 + # A standard response object has no 'from_cache' attribute + assert getattr(r3, 'from_cache', None) is None