129 lines
4.6 KiB
Python
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": []}
|