mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-05-23 07:27:24 +08:00
Compare commits
10 Commits
a30f4e8a6d
...
12bd1c811a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12bd1c811a | ||
|
|
e35348aa53 | ||
|
|
cd8c7a2306 | ||
|
|
6bcd8b96ab | ||
|
|
d6e94b7dfc | ||
|
|
65c5dba4e3 | ||
|
|
2274a5d3d3 | ||
|
|
74547bf49b | ||
|
|
39e5f74129 | ||
|
|
187e9f03a9 |
2
.github/workflows/stable-release.yml
vendored
2
.github/workflows/stable-release.yml
vendored
@ -145,6 +145,8 @@ jobs:
|
||||
cp -r ComfyUI/.ci/windows_${{ inputs.rel_name }}_base_files/* ./
|
||||
cp ../update_comfyui_and_python_dependencies.bat ./update/
|
||||
|
||||
echo 'local-portable' > ComfyUI/.comfy_environment
|
||||
|
||||
cd ..
|
||||
|
||||
"C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma2 -mx=9 -mfb=128 -md=768m -ms=on -mf=BCJ2 ComfyUI_windows_portable.7z ComfyUI_windows_portable
|
||||
|
||||
12
api_server/utils/query_params.py
Normal file
12
api_server/utils/query_params.py
Normal file
@ -0,0 +1,12 @@
|
||||
from collections.abc import Mapping
|
||||
|
||||
|
||||
def parse_optional_int_query_param(query: Mapping[str, str], name: str) -> int | None:
|
||||
value = query.get(name)
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError(f"{name} must be an integer") from exc
|
||||
@ -26,6 +26,7 @@ import uuid
|
||||
from typing import Callable, Optional
|
||||
|
||||
import torch
|
||||
import tqdm
|
||||
|
||||
import comfy.float
|
||||
import comfy.hooks
|
||||
@ -1651,7 +1652,11 @@ class ModelPatcherDynamic(ModelPatcher):
|
||||
self.model.model_loaded_weight_memory += casted_buf.numel() * casted_buf.element_size()
|
||||
|
||||
force_load_stat = f" Force pre-loaded {len(self.backup)} weights: {self.model.model_loaded_weight_memory // 1024} KB." if len(self.backup) > 0 else ""
|
||||
logging.info(f"Model {self.model.__class__.__name__} prepared for dynamic VRAM loading. {allocated_size // (1024 ** 2)}MB Staged. {num_patches} patches attached.{force_load_stat}")
|
||||
log_key = (self.patches_uuid, allocated_size, num_patches, len(self.backup), self.model.model_loaded_weight_memory)
|
||||
in_loop = bool(getattr(tqdm.tqdm, "_instances", None))
|
||||
level = logging.DEBUG if in_loop and getattr(self, "_last_prepare_log_key", None) == log_key else logging.INFO
|
||||
self._last_prepare_log_key = log_key
|
||||
logging.log(level, f"Model {self.model.__class__.__name__} prepared for dynamic VRAM loading. {allocated_size // (1024 ** 2)}MB Staged. {num_patches} patches attached.{force_load_stat}")
|
||||
|
||||
self.model.device = device_to
|
||||
self.model.current_weight_patches_uuid = self.patches_uuid
|
||||
|
||||
24
server.py
24
server.py
@ -46,6 +46,7 @@ from app.subgraph_manager import SubgraphManager
|
||||
from app.node_replace_manager import NodeReplaceManager
|
||||
from typing import Optional, Union
|
||||
from api_server.routes.internal.internal_routes import InternalRoutes
|
||||
from api_server.utils.query_params import parse_optional_int_query_param
|
||||
from protocol import BinaryEventTypes
|
||||
|
||||
# Import cache control middleware
|
||||
@ -560,7 +561,7 @@ class PromptServer():
|
||||
buffer.seek(0)
|
||||
|
||||
return web.Response(body=buffer.read(), content_type=f'image/{image_format}',
|
||||
headers={"Content-Disposition": f"attachment; filename=\"{filename}\""})
|
||||
headers={"Content-Disposition": f"filename=\"{filename}\""})
|
||||
|
||||
if 'channel' not in request.rel_url.query:
|
||||
channel = 'rgba'
|
||||
@ -580,7 +581,7 @@ class PromptServer():
|
||||
buffer.seek(0)
|
||||
|
||||
return web.Response(body=buffer.read(), content_type='image/png',
|
||||
headers={"Content-Disposition": f"attachment; filename=\"{filename}\""})
|
||||
headers={"Content-Disposition": f"filename=\"{filename}\""})
|
||||
|
||||
elif channel == 'a':
|
||||
with Image.open(file) as img:
|
||||
@ -597,7 +598,7 @@ class PromptServer():
|
||||
alpha_buffer.seek(0)
|
||||
|
||||
return web.Response(body=alpha_buffer.read(), content_type='image/png',
|
||||
headers={"Content-Disposition": f"attachment; filename=\"{filename}\""})
|
||||
headers={"Content-Disposition": f"filename=\"{filename}\""})
|
||||
else:
|
||||
# Use the content type from asset resolution if available,
|
||||
# otherwise guess from the filename.
|
||||
@ -614,7 +615,7 @@ class PromptServer():
|
||||
return web.FileResponse(
|
||||
file,
|
||||
headers={
|
||||
"Content-Disposition": f"attachment; filename=\"{filename}\"",
|
||||
"Content-Disposition": f"filename=\"{filename}\"",
|
||||
"Content-Type": content_type
|
||||
}
|
||||
)
|
||||
@ -888,14 +889,15 @@ class PromptServer():
|
||||
|
||||
@routes.get("/history")
|
||||
async def get_history(request):
|
||||
max_items = request.rel_url.query.get("max_items", None)
|
||||
if max_items is not None:
|
||||
max_items = int(max_items)
|
||||
query = request.rel_url.query
|
||||
|
||||
offset = request.rel_url.query.get("offset", None)
|
||||
if offset is not None:
|
||||
offset = int(offset)
|
||||
else:
|
||||
try:
|
||||
max_items = parse_optional_int_query_param(query, "max_items")
|
||||
offset = parse_optional_int_query_param(query, "offset")
|
||||
except ValueError as exc:
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
|
||||
if offset is None:
|
||||
offset = -1
|
||||
|
||||
return web.json_response(self.prompt_queue.get_history(max_items=max_items, offset=offset))
|
||||
|
||||
39
tests-unit/server/utils/query_params_test.py
Normal file
39
tests-unit/server/utils/query_params_test.py
Normal file
@ -0,0 +1,39 @@
|
||||
import pytest
|
||||
|
||||
from api_server.utils.query_params import parse_optional_int_query_param
|
||||
|
||||
|
||||
def test_parse_optional_int_query_param_returns_none_when_missing():
|
||||
assert parse_optional_int_query_param({}, "offset") is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("raw_value", "expected"),
|
||||
[
|
||||
("0", 0),
|
||||
("5", 5),
|
||||
("-1", -1),
|
||||
],
|
||||
)
|
||||
def test_parse_optional_int_query_param_parses_integers(raw_value, expected):
|
||||
query = {"offset": raw_value}
|
||||
|
||||
assert parse_optional_int_query_param(query, "offset") == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("name", "raw_value"),
|
||||
[
|
||||
("offset", "not-an-integer"),
|
||||
("offset", "1.5"),
|
||||
("offset", ""),
|
||||
("max_items", "not-an-integer"),
|
||||
],
|
||||
)
|
||||
def test_parse_optional_int_query_param_rejects_invalid_integers(name, raw_value):
|
||||
query = {name: raw_value}
|
||||
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
parse_optional_int_query_param(query, name)
|
||||
|
||||
assert str(exc_info.value) == f"{name} must be an integer"
|
||||
@ -909,6 +909,20 @@ class TestExecution:
|
||||
|
||||
assert len(result) <= 1, "Should return at most 1 item when offset is near end"
|
||||
|
||||
def test_history_api_rejects_non_integer_max_items(self, client: ComfyClient):
|
||||
with pytest.raises(urllib.error.HTTPError) as exc_info:
|
||||
client.get_all_history(max_items="not-an-integer")
|
||||
|
||||
assert exc_info.value.code == 400
|
||||
assert json.loads(exc_info.value.read()) == {"error": "max_items must be an integer"}
|
||||
|
||||
def test_history_api_rejects_non_integer_offset(self, client: ComfyClient):
|
||||
with pytest.raises(urllib.error.HTTPError) as exc_info:
|
||||
client.get_all_history(offset="not-an-integer")
|
||||
|
||||
assert exc_info.value.code == 400
|
||||
assert json.loads(exc_info.value.read()) == {"error": "offset must be an integer"}
|
||||
|
||||
# Jobs API tests
|
||||
def test_jobs_api_job_structure(
|
||||
self, client: ComfyClient, builder: GraphBuilder
|
||||
|
||||
Loading…
Reference in New Issue
Block a user