mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-07-03 21:20:49 +08:00
Merge d224df799b into 35c1470935
This commit is contained in:
commit
d2e6934cf6
@ -54,7 +54,7 @@ def _require_assets_feature_enabled(handler):
|
|||||||
return _build_error_response(
|
return _build_error_response(
|
||||||
503,
|
503,
|
||||||
"SERVICE_DISABLED",
|
"SERVICE_DISABLED",
|
||||||
"Assets system is disabled. Start the server with --enable-assets to use this feature.",
|
"Assets system is unavailable.",
|
||||||
)
|
)
|
||||||
return await handler(request)
|
return await handler(request)
|
||||||
|
|
||||||
@ -102,6 +102,15 @@ def disable_assets_routes() -> None:
|
|||||||
_ASSETS_ENABLED = False
|
_ASSETS_ENABLED = False
|
||||||
|
|
||||||
|
|
||||||
|
def assets_enabled() -> bool:
|
||||||
|
"""Return whether the asset routes are currently serving requests.
|
||||||
|
|
||||||
|
Reflects live backend availability: False once disable_assets_routes() has
|
||||||
|
been called (e.g. after a database init failure or missing dependencies).
|
||||||
|
"""
|
||||||
|
return _ASSETS_ENABLED
|
||||||
|
|
||||||
|
|
||||||
def _build_error_response(
|
def _build_error_response(
|
||||||
status: int, code: str, message: str, details: dict | None = None
|
status: int, code: str, message: str, details: dict | None = None
|
||||||
) -> web.Response:
|
) -> web.Response:
|
||||||
|
|||||||
@ -239,7 +239,9 @@ database_default_path = os.path.abspath(
|
|||||||
os.path.join(os.path.dirname(__file__), "..", "user", "comfyui.db")
|
os.path.join(os.path.dirname(__file__), "..", "user", "comfyui.db")
|
||||||
)
|
)
|
||||||
parser.add_argument("--database-url", type=str, default=f"sqlite:///{database_default_path}", help="Specify the database URL, e.g. for an in-memory database you can use 'sqlite:///:memory:'.")
|
parser.add_argument("--database-url", type=str, default=f"sqlite:///{database_default_path}", help="Specify the database URL, e.g. for an in-memory database you can use 'sqlite:///:memory:'.")
|
||||||
parser.add_argument("--enable-assets", action="store_true", help="Enable the assets system (API routes, database synchronization, and background scanning).")
|
# Deprecated no-op: the asset system is now always enabled. Kept (hidden) so that
|
||||||
|
# existing launchers/containers still passing --enable-assets don't fail argparse.
|
||||||
|
parser.add_argument("--enable-assets", action="store_true", help=argparse.SUPPRESS)
|
||||||
parser.add_argument("--enable-asset-hashing", action="store_true", help="Compute blake3 content hashes when scanning assets. Hashing enables future asset-portability features (deduplication, cross-machine model resolution) but adds startup cost and per-output cost on large models directories. Off by default; enable to opt in.")
|
parser.add_argument("--enable-asset-hashing", action="store_true", help="Compute blake3 content hashes when scanning assets. Hashing enables future asset-portability features (deduplication, cross-machine model resolution) but adds startup cost and per-output cost on large models directories. Off by default; enable to opt in.")
|
||||||
parser.add_argument("--feature-flag", type=str, action='append', default=[], metavar="KEY[=VALUE]", help="Set a server feature flag. Use KEY=VALUE to set an explicit value, or bare KEY to set it to true. Can be specified multiple times. Boolean values (true/false) and numbers are auto-converted. Examples: --feature-flag show_signin_button=true or --feature-flag show_signin_button")
|
parser.add_argument("--feature-flag", type=str, action='append', default=[], metavar="KEY[=VALUE]", help="Set a server feature flag. Use KEY=VALUE to set an explicit value, or bare KEY to set it to true. Can be specified multiple times. Boolean values (true/false) and numbers are auto-converted. Examples: --feature-flag show_signin_button=true or --feature-flag show_signin_button")
|
||||||
parser.add_argument("--list-feature-flags", action="store_true", help="Print the registry of known CLI-settable feature flags as JSON and exit.")
|
parser.add_argument("--list-feature-flags", action="store_true", help="Print the registry of known CLI-settable feature flags as JSON and exit.")
|
||||||
|
|||||||
@ -103,7 +103,7 @@ _CORE_FEATURE_FLAGS: dict[str, Any] = {
|
|||||||
"max_upload_size": args.max_upload_size * 1024 * 1024, # Convert MB to bytes
|
"max_upload_size": args.max_upload_size * 1024 * 1024, # Convert MB to bytes
|
||||||
"extension": {"manager": {"supports_v4": True}},
|
"extension": {"manager": {"supports_v4": True}},
|
||||||
"node_replacements": True,
|
"node_replacements": True,
|
||||||
"assets": args.enable_assets,
|
"assets": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
# CLI-provided flags cannot overwrite core flags
|
# CLI-provided flags cannot overwrite core flags
|
||||||
@ -162,4 +162,13 @@ def get_server_features() -> dict[str, Any]:
|
|||||||
Returns:
|
Returns:
|
||||||
Dictionary of server feature flags
|
Dictionary of server feature flags
|
||||||
"""
|
"""
|
||||||
return SERVER_FEATURE_FLAGS.copy()
|
features = SERVER_FEATURE_FLAGS.copy()
|
||||||
|
# Advertise the assets capability based on live backend availability rather
|
||||||
|
# than a static default, so clients degrade gracefully when the database
|
||||||
|
# failed to initialize or its dependencies are missing.
|
||||||
|
try:
|
||||||
|
from app.assets.api.routes import assets_enabled
|
||||||
|
features["assets"] = assets_enabled()
|
||||||
|
except Exception:
|
||||||
|
features["assets"] = False
|
||||||
|
return features
|
||||||
|
|||||||
@ -6,15 +6,11 @@ import os
|
|||||||
def enrich_output_with_assets(output_ui: dict) -> dict:
|
def enrich_output_with_assets(output_ui: dict) -> dict:
|
||||||
"""Register file-type output entries as assets and inject their ``id``.
|
"""Register file-type output entries as assets and inject their ``id``.
|
||||||
|
|
||||||
Runs at output-processing time, once per produced output, when
|
Runs at output-processing time, once per produced output. Returns a new
|
||||||
--enable-assets is set. Returns a new dict; entries without a resolvable
|
dict; entries without a resolvable on-disk file path are left unchanged.
|
||||||
on-disk file path are left unchanged. Errors are caught per-entry so a
|
Errors are caught per-entry so a failure never blocks execution or the
|
||||||
failure never blocks execution or the other entries.
|
other entries.
|
||||||
"""
|
"""
|
||||||
from comfy.cli_args import args
|
|
||||||
if not args.enable_assets:
|
|
||||||
return output_ui
|
|
||||||
|
|
||||||
import folder_paths
|
import folder_paths
|
||||||
from app.assets.services.ingest import register_file_in_place, DependencyMissingError
|
from app.assets.services.ingest import register_file_in_place, DependencyMissingError
|
||||||
|
|
||||||
|
|||||||
23
main.py
23
main.py
@ -20,6 +20,7 @@ from app.logger import setup_logger
|
|||||||
setup_logger(log_level=args.verbose, use_stdout=args.log_stdout)
|
setup_logger(log_level=args.verbose, use_stdout=args.log_stdout)
|
||||||
|
|
||||||
from app.assets.seeder import asset_seeder
|
from app.assets.seeder import asset_seeder
|
||||||
|
from app.assets.api.routes import disable_assets_routes
|
||||||
from app.assets.services import register_output_files
|
from app.assets.services import register_output_files
|
||||||
import itertools
|
import itertools
|
||||||
import utils.extra_config
|
import utils.extra_config
|
||||||
@ -457,9 +458,15 @@ def setup_database():
|
|||||||
try:
|
try:
|
||||||
if dependencies_available():
|
if dependencies_available():
|
||||||
init_db()
|
init_db()
|
||||||
if args.enable_assets:
|
|
||||||
if asset_seeder.start(roots=("models", "input", "output"), prune_first=True, compute_hashes=args.enable_asset_hashing):
|
if asset_seeder.start(roots=("models", "input", "output"), prune_first=True, compute_hashes=args.enable_asset_hashing):
|
||||||
logging.info("Background asset scan initiated for models, input, output")
|
logging.info("Background asset scan initiated for models, input, output")
|
||||||
|
else:
|
||||||
|
# Optional DB dependencies are missing, so init_db() is skipped and the
|
||||||
|
# asset backend has no database. Disable assets so /api/assets/* returns
|
||||||
|
# a clean 503 instead of 500s against an uninitialized DB.
|
||||||
|
logging.warning("Optional database dependencies are missing; assets system disabled.")
|
||||||
|
disable_assets_routes()
|
||||||
|
asset_seeder.disable()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if "database is locked" in str(e):
|
if "database is locked" in str(e):
|
||||||
logging.error(
|
logging.error(
|
||||||
@ -468,16 +475,10 @@ def setup_database():
|
|||||||
" --database-url sqlite:///path/to/another.db"
|
" --database-url sqlite:///path/to/another.db"
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if args.enable_assets:
|
# The database is unusable. Fail safe by disabling assets so endpoints
|
||||||
logging.error(
|
# return 503 (service unavailable) rather than 500s on every request.
|
||||||
f"Failed to initialize database: {e}\n"
|
disable_assets_routes()
|
||||||
"The --enable-assets flag requires a working database connection.\n"
|
asset_seeder.disable()
|
||||||
"To resolve this, try one of the following:\n"
|
|
||||||
" 1. Install the latest requirements: pip install -r requirements.txt\n"
|
|
||||||
" 2. Specify an alternative database URL: --database-url sqlite:///path/to/your.db\n"
|
|
||||||
" 3. Use an in-memory database: --database-url sqlite:///:memory:"
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
logging.error(f"Failed to initialize database. Please ensure you have installed the latest requirements. If the error persists, please report this as in future the database will be required: {e}")
|
logging.error(f"Failed to initialize database. Please ensure you have installed the latest requirements. If the error persists, please report this as in future the database will be required: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -251,11 +251,7 @@ class PromptServer():
|
|||||||
else args.front_end_root
|
else args.front_end_root
|
||||||
)
|
)
|
||||||
logging.info(f"[Prompt Server] web root: {self.web_root}")
|
logging.info(f"[Prompt Server] web root: {self.web_root}")
|
||||||
if args.enable_assets:
|
|
||||||
register_assets_routes(self.app, self.user_manager)
|
register_assets_routes(self.app, self.user_manager)
|
||||||
else:
|
|
||||||
register_assets_routes(self.app)
|
|
||||||
asset_seeder.disable()
|
|
||||||
routes = web.RouteTableDef()
|
routes = web.RouteTableDef()
|
||||||
self.routes = routes
|
self.routes = routes
|
||||||
self.last_node_id = None
|
self.last_node_id = None
|
||||||
@ -437,7 +433,6 @@ class PromptServer():
|
|||||||
|
|
||||||
resp = {"name" : filename, "subfolder": subfolder, "type": image_upload_type}
|
resp = {"name" : filename, "subfolder": subfolder, "type": image_upload_type}
|
||||||
|
|
||||||
if args.enable_assets:
|
|
||||||
try:
|
try:
|
||||||
tag = image_upload_type if image_upload_type in ("input", "output") else "input"
|
tag = image_upload_type if image_upload_type in ("input", "output") else "input"
|
||||||
result = register_file_in_place(abs_path=filepath, name=filename, tags=[tag])
|
result = register_file_in_place(abs_path=filepath, name=filename, tags=[tag])
|
||||||
|
|||||||
@ -109,7 +109,6 @@ def comfy_url_and_proc(comfy_tmp_base_dir: Path, request: pytest.FixtureRequest)
|
|||||||
"main.py",
|
"main.py",
|
||||||
f"--base-directory={str(comfy_tmp_base_dir)}",
|
f"--base-directory={str(comfy_tmp_base_dir)}",
|
||||||
f"--database-url={db_url}",
|
f"--database-url={db_url}",
|
||||||
"--enable-assets",
|
|
||||||
"--listen",
|
"--listen",
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
"--port",
|
"--port",
|
||||||
|
|||||||
@ -1,16 +1,9 @@
|
|||||||
"""Tests for enrich_output_with_assets in comfy_execution/asset_enrichment.py."""
|
"""Tests for enrich_output_with_assets in comfy_execution/asset_enrichment.py."""
|
||||||
import os
|
import os
|
||||||
import types
|
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
|
||||||
def _make_args(enable_assets: bool):
|
|
||||||
a = types.SimpleNamespace()
|
|
||||||
a.enable_assets = enable_assets
|
|
||||||
return a
|
|
||||||
|
|
||||||
|
|
||||||
def _make_register_result(ref_id="ref-id-2"):
|
def _make_register_result(ref_id="ref-id-2"):
|
||||||
result = MagicMock()
|
result = MagicMock()
|
||||||
result.ref.id = ref_id
|
result.ref.id = ref_id
|
||||||
@ -22,9 +15,8 @@ def _make_register_result(ref_id="ref-id-2"):
|
|||||||
_DEFAULT_BASE = os.path.join(__import__("tempfile").gettempdir(), "asset-enrichment-test-base")
|
_DEFAULT_BASE = os.path.join(__import__("tempfile").gettempdir(), "asset-enrichment-test-base")
|
||||||
|
|
||||||
|
|
||||||
def _mocked_modules(*, enable_assets=True, register_file_in_place=None, directory=_DEFAULT_BASE):
|
def _mocked_modules(*, register_file_in_place=None, directory=_DEFAULT_BASE):
|
||||||
return {
|
return {
|
||||||
"comfy.cli_args": MagicMock(args=_make_args(enable_assets)),
|
|
||||||
"folder_paths": MagicMock(get_directory_by_type=MagicMock(return_value=directory)),
|
"folder_paths": MagicMock(get_directory_by_type=MagicMock(return_value=directory)),
|
||||||
"app.assets.services.ingest": MagicMock(
|
"app.assets.services.ingest": MagicMock(
|
||||||
register_file_in_place=register_file_in_place or MagicMock(return_value=_make_register_result()),
|
register_file_in_place=register_file_in_place or MagicMock(return_value=_make_register_result()),
|
||||||
@ -33,10 +25,9 @@ def _mocked_modules(*, enable_assets=True, register_file_in_place=None, director
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _call(output_ui, *, enable_assets=True, file_exists=True, register_result=None, directory=_DEFAULT_BASE):
|
def _call(output_ui, *, file_exists=True, register_result=None, directory=_DEFAULT_BASE):
|
||||||
register_mock = MagicMock(return_value=register_result or _make_register_result())
|
register_mock = MagicMock(return_value=register_result or _make_register_result())
|
||||||
mocked = _mocked_modules(
|
mocked = _mocked_modules(
|
||||||
enable_assets=enable_assets,
|
|
||||||
register_file_in_place=register_mock,
|
register_file_in_place=register_mock,
|
||||||
directory=directory,
|
directory=directory,
|
||||||
)
|
)
|
||||||
@ -53,11 +44,6 @@ def _call(output_ui, *, enable_assets=True, file_exists=True, register_result=No
|
|||||||
|
|
||||||
class TestEnrichOutputWithAssets(unittest.TestCase):
|
class TestEnrichOutputWithAssets(unittest.TestCase):
|
||||||
|
|
||||||
def test_disabled_returns_unchanged(self):
|
|
||||||
output = {"images": [{"filename": "a.png", "subfolder": "", "type": "output"}]}
|
|
||||||
result = _call(output, enable_assets=False)
|
|
||||||
self.assertNotIn("id", result["images"][0])
|
|
||||||
|
|
||||||
def test_non_list_value_passed_through(self):
|
def test_non_list_value_passed_through(self):
|
||||||
output = {"text": "hello"}
|
output = {"text": "hello"}
|
||||||
result = _call(output)
|
result = _call(output)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user