Files

158 lines
5.1 KiB
Python

"""Modal app-existence checks used before constructing the cloud entrypoint."""
from __future__ import annotations
import inspect
import json
import logging
import os
import shutil
import subprocess
from typing import Any, Callable, Mapping, Sequence
try:
from .settings import modal_deployment_app_name
except ImportError: # pragma: no cover - flat Modal-container import.
from settings import modal_deployment_app_name
logger = logging.getLogger(__name__)
def _modal_exception_types(
modal_module: Any,
names: Sequence[str],
) -> tuple[type[BaseException], ...]:
"""Return available Modal exception types from a Modal SDK-like module."""
exception_namespace = getattr(modal_module, "exception", None)
if exception_namespace is None:
return tuple()
error_types: list[type[BaseException]] = []
for error_name in names:
error_type = getattr(exception_namespace, error_name, None)
if isinstance(error_type, type) and issubclass(error_type, BaseException):
error_types.append(error_type)
return tuple(error_types)
def _modal_lookup_create_flag_name(app_lookup: Callable[..., Any]) -> str | None:
"""Return the App.lookup keyword that disables creation when available."""
try:
lookup_signature = inspect.signature(app_lookup)
except (TypeError, ValueError):
return None
for flag_name in ("create_if_missing", "create"):
if flag_name in lookup_signature.parameters:
return flag_name
return None
def _modal_app_exists_via_sdk(modal_module: Any, app_name: str) -> bool | None:
"""Check for a Modal app through a non-creating SDK lookup when possible."""
app_namespace = getattr(modal_module, "App", None)
app_lookup = getattr(app_namespace, "lookup", None)
if not callable(app_lookup):
return None
create_flag_name = _modal_lookup_create_flag_name(app_lookup)
if create_flag_name is None:
return None
not_found_errors = _modal_exception_types(modal_module, ("NotFoundError",))
try:
app_lookup(app_name, **{create_flag_name: False})
except not_found_errors:
return False
return True
def _app_name_from_modal_app_list_entry(entry: Mapping[str, Any]) -> str | None:
"""Extract an app name from one ``modal app list --json`` entry."""
for key in ("name", "app_name", "Name"):
value = entry.get(key)
if isinstance(value, str) and value.strip():
return value.strip()
return None
def _modal_app_exists_via_cli(app_name: str) -> bool | None:
"""Check for a Modal app by parsing the CLI's JSON output when available."""
modal_cli = shutil.which("modal")
if modal_cli is None:
return None
command = [modal_cli, "app", "list", "--json"]
try:
completed = subprocess.run(
command,
check=False,
capture_output=True,
text=True,
timeout=30,
)
except (OSError, subprocess.SubprocessError) as exc:
logger.warning("Unable to list Modal apps through the Modal CLI: %s", exc)
return None
if completed.returncode != 0:
logger.warning(
"Modal CLI app list failed with exit code %s: %s",
completed.returncode,
completed.stderr.strip(),
)
return None
try:
listed_apps = json.loads(completed.stdout)
except json.JSONDecodeError as exc:
logger.warning("Modal CLI app list returned invalid JSON: %s", exc)
return None
if not isinstance(listed_apps, list):
return None
return any(
_app_name_from_modal_app_list_entry(entry) == app_name
for entry in listed_apps
if isinstance(entry, Mapping)
)
def _running_inside_modal_container(modal_module: Any) -> bool:
"""Return whether this import is happening inside a Modal worker container."""
if os.getenv("MODAL_TASK_ID"):
return True
is_local = getattr(modal_module, "is_local", None)
if callable(is_local):
try:
return not bool(is_local())
except RuntimeError:
return False
return False
def _existing_modal_app_error_message(app_name: str) -> str:
"""Return the user-facing error for refusing to overwrite a Modal app."""
return (
f"Modal app {app_name!r} already exists. Delete the existing app before launching "
"ComfyUI-Modal with this configuration, especially after changing the workflow GPU "
"target or the COMFY_MODAL_GPU compatibility fallback, "
f"for example: modal app stop {app_name!r}."
)
def guard_against_existing_modal_app(
settings: Any,
modal_module: Any,
*,
error_type: type[RuntimeError],
) -> None:
"""Fail local app construction when the configured Modal app already exists."""
if _running_inside_modal_container(modal_module):
return
app_name = modal_deployment_app_name(settings)
app_exists = _modal_app_exists_via_sdk(modal_module, app_name)
if app_exists is None:
app_exists = _modal_app_exists_via_cli(app_name)
if app_exists:
raise error_type(_existing_modal_app_error_message(app_name))