Source code for oumi.launcher.clouds.modal_cloud
# Copyright 2025 - Oumi
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Modal-backed :class:`BaseCloud`.
Each :class:`ModalCluster` instance maps to one or more Modal sandboxes
sharing a logical cluster name (the cluster name is a caller-provided
label; the sandboxes are addressed by ``Sandbox.object_id``). The cloud
keeps an in-process registry of clusters it has launched so that
``get_cluster`` / ``list_clusters`` can return them without
round-tripping to Modal's control plane.
"""
from __future__ import annotations
from oumi.core.configs import JobConfig
from oumi.core.launcher import BaseCloud, BaseCluster, JobStatus
from oumi.core.registry import register_cloud_builder
from oumi.launcher.clients.modal_client import ModalClient
from oumi.launcher.clusters.modal_cluster import ModalCluster
[docs]
class ModalCloud(BaseCloud):
"""A resource pool capable of dispatching jobs to Modal."""
def __init__(self) -> None:
"""Initializes a new instance of the ModalCloud class."""
self._modal_client: ModalClient | None = None
self._clusters: dict[str, ModalCluster] = {}
@property
def _client(self) -> ModalClient:
"""Returns a lazily-instantiated :class:`ModalClient`.
Delaying instantiation avoids importing ``modal`` (and therefore
loading credentials) at module import time.
"""
if self._modal_client is None:
self._modal_client = ModalClient()
return self._modal_client
[docs]
def up_cluster(self, job: JobConfig, name: str | None, **kwargs) -> JobStatus:
"""Spawns a Modal Sandbox and registers a cluster wrapping it."""
status = self._client.launch(job, cluster_name=name, **kwargs)
cluster = ModalCluster(status.cluster, self._client)
self._clusters[status.cluster] = cluster
return status
[docs]
def get_cluster(self, name: str) -> BaseCluster | None:
"""Gets the cluster with the specified name, or None if not found.
Falls back to constructing a :class:`ModalCluster` for any cluster
name we have not seen in this process. The cluster's sandbox
lookups happen lazily via ``ModalClient.find_sandboxes_for_cluster``
(Modal tag-based query) so a freshly-constructed instance still
sees prior sandboxes across worker restarts.
"""
if name in self._clusters:
return self._clusters[name]
# Construct on demand; resolution happens via ModalClient.get_call.
cluster = ModalCluster(name, self._client)
self._clusters[name] = cluster
return cluster
[docs]
def list_clusters(self) -> list[BaseCluster]:
"""Lists the clusters tracked by this process."""
return list(self._clusters.values())
@register_cloud_builder("modal")
def modal_cloud_builder() -> ModalCloud:
"""Builds a ModalCloud instance."""
return ModalCloud()