Files

129 lines
4.6 KiB
Python

"""Administrative operations for persistent Modal objects."""
from __future__ import annotations
import asyncio
import logging
from typing import Any
if __package__:
from .settings import ModalSyncSettings
else: # pragma: no cover - flat import inside the Modal container.
from settings import ModalSyncSettings
logger = logging.getLogger(__name__)
try:
import modal # type: ignore
except ModuleNotFoundError: # pragma: no cover - optional local dependency.
modal = None
def _modal_not_found_error_types() -> tuple[type[BaseException], ...]:
"""Return Modal SDK exception classes that mean a named object is absent."""
if modal is None:
return ()
exception_namespace = getattr(modal, "exception", None)
candidates = [
getattr(exception_namespace, "NotFoundError", None),
getattr(exception_namespace, "InvalidError", None),
]
return tuple(
candidate
for candidate in candidates
if isinstance(candidate, type) and issubclass(candidate, BaseException)
)
def _modal_cache_dict_names(settings: ModalSyncSettings) -> list[str]:
"""Return the configured persistent Modal Dict names used as local-reset caches."""
return [
settings.interrupt_dict_name,
settings.node_output_cache_dict_name,
settings.session_bridge_dict_name,
settings.sync_index_dict_name,
settings.snapshot_profile_dict_name,
]
async def _call_modal_sdk(method: Any, *args: Any, **kwargs: Any) -> Any:
"""Call a Modal SDK method without blocking the aiohttp event loop."""
async_method = getattr(method, "aio", None)
if callable(async_method):
return await async_method(*args, **kwargs)
return await asyncio.to_thread(method, *args, **kwargs)
async def _delete_modal_named_object(
namespace: Any, name: str, *, object_label: str
) -> None:
"""Delete a named Modal object using the supported manager API when available."""
objects_manager = getattr(namespace, "objects", None)
manager_delete = getattr(objects_manager, "delete", None)
if callable(manager_delete):
await _call_modal_sdk(manager_delete, name, allow_missing=True)
return
instance = await _call_modal_sdk(namespace.from_name, name, create_if_missing=False)
delete_method = getattr(instance, "delete", None)
if not callable(delete_method):
raise RuntimeError(f"Modal {object_label} {name!r} does not expose delete().")
await _call_modal_sdk(delete_method, name)
async def delete_modal_cache_dicts(settings: ModalSyncSettings) -> dict[str, Any]:
"""Delete all configured Modal Dict caches and return a reset summary."""
if modal is None:
raise RuntimeError("Modal SDK is unavailable; cannot delete Modal caches.")
modal_dict = getattr(modal, "Dict", None)
if modal_dict is None:
raise RuntimeError(
"Modal SDK does not expose modal.Dict; cannot delete Modal caches."
)
deleted: list[str] = []
skipped: list[str] = []
not_found_errors = _modal_not_found_error_types()
for dict_name in _modal_cache_dict_names(settings):
try:
await _call_modal_sdk(
modal_dict.from_name,
dict_name,
create_if_missing=False,
)
except not_found_errors:
skipped.append(dict_name)
continue
await _delete_modal_named_object(modal_dict, dict_name, object_label="Dict")
deleted.append(dict_name)
logger.info("Deleted Modal cache Dicts deleted=%s skipped=%s.", deleted, skipped)
return {"deleted": deleted, "skipped": skipped}
async def delete_modal_volume(settings: ModalSyncSettings) -> dict[str, Any]:
"""Delete the configured Modal Volume and return a reset summary."""
if modal is None:
raise RuntimeError("Modal SDK is unavailable; cannot delete Modal volume.")
modal_volume = getattr(modal, "Volume", None)
if modal_volume is None:
raise RuntimeError(
"Modal SDK does not expose modal.Volume; cannot delete Modal volume."
)
try:
await _call_modal_sdk(
modal_volume.from_name,
settings.volume_name,
create_if_missing=False,
)
except _modal_not_found_error_types():
logger.info("Skipped deleting missing Modal Volume %s.", settings.volume_name)
return {"deleted": [], "skipped": [settings.volume_name]}
await _delete_modal_named_object(
modal_volume, settings.volume_name, object_label="Volume"
)
logger.info("Deleted Modal Volume %s.", settings.volume_name)
return {"deleted": [settings.volume_name], "skipped": []}