Skip to content

Modal

swerex.deployment.modal.ModalDeployment

ModalDeployment(*, logger: Logger | None = None, image: str | Image | PurePath, startup_timeout: float = 0.4, runtime_timeout: float = 1800.0, modal_sandbox_kwargs: dict[str, Any] | None = None, install_pipx: bool = True, deployment_timeout: float = 1800.0)

Bases: AbstractDeployment

Deployment for modal.com. The deployment will only start when the start method is being called.

Parameters:

Name Type Description Default
image str | Image | PurePath

Image to use for the deployment. One of the following: 1. modal.Image object 2. Path to a Dockerfile 3. Dockerhub image name (e.g. python:3.11-slim) 4. ECR image name (e.g. 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image:tag)

required
startup_timeout float

The time to wait for the runtime to start.

0.4
runtime_timeout float

The runtime timeout.

1800.0
deployment_timeout float

The deployment timeout.

1800.0
modal_sandbox_kwargs dict[str, Any] | None

Additional arguments to pass to modal.Sandbox.create

None
Source code in swerex/deployment/modal.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def __init__(
    self,
    *,
    logger: logging.Logger | None = None,
    image: str | modal.Image | PurePath,
    startup_timeout: float = 0.4,
    runtime_timeout: float = 1800.0,
    modal_sandbox_kwargs: dict[str, Any] | None = None,
    install_pipx: bool = True,
    deployment_timeout: float = 1800.0,
):
    """Deployment for modal.com. The deployment will only start when the
    `start` method is being called.

    Args:
        image: Image to use for the deployment. One of the following:
            1. `modal.Image` object
            2. Path to a Dockerfile
            3. Dockerhub image name (e.g. `python:3.11-slim`)
            4. ECR image name (e.g. `123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image:tag`)
        startup_timeout: The time to wait for the runtime to start.
        runtime_timeout: The runtime timeout.
        deployment_timeout: The deployment timeout.
        modal_sandbox_kwargs: Additional arguments to pass to `modal.Sandbox.create`
    """
    self._image = _ImageBuilder(install_pipx=install_pipx, logger=logger).auto(image)
    self._runtime: RemoteRuntime | None = None
    self._startup_timeout = startup_timeout
    self._sandbox: modal.Sandbox | None = None
    self._port = 8880
    self.logger = logger or get_logger("rex-deploy")
    self._app = modal.App.lookup("swe-rex", create_if_missing=True)
    self._user = _get_modal_user()
    self._runtime_timeout = runtime_timeout
    self._deployment_timeout = deployment_timeout
    if modal_sandbox_kwargs is None:
        modal_sandbox_kwargs = {}
    self._modal_kwargs = modal_sandbox_kwargs
    self._hooks = CombinedDeploymentHook()

app property

app: App

Returns the modal app

Raises:

Type Description
DeploymentNotStartedError

If the deployment is not started.

logger instance-attribute

logger = logger or get_logger('rex-deploy')

runtime property

runtime: RemoteRuntime

Returns the runtime if running.

Raises:

Type Description
DeploymentNotStartedError

If the deployment was not started.

sandbox property

sandbox: Sandbox

Returns the modal sandbox

Raises:

Type Description
DeploymentNotStartedError

If the deployment is not started.

add_hook

add_hook(hook: DeploymentHook)
Source code in swerex/deployment/modal.py
155
156
def add_hook(self, hook: DeploymentHook):
    self._hooks.add_hook(hook)

from_config classmethod

from_config(config: ModalDeploymentConfig) -> Self
Source code in swerex/deployment/modal.py
158
159
160
161
162
163
164
165
166
167
@classmethod
def from_config(cls, config: ModalDeploymentConfig) -> Self:
    return cls(
        image=config.image,
        install_pipx=config.install_pipx,
        startup_timeout=config.startup_timeout,
        runtime_timeout=config.runtime_timeout,
        deployment_timeout=config.deployment_timeout,
        modal_sandbox_kwargs=config.modal_sandbox_kwargs,
    )

get_modal_log_url

get_modal_log_url() -> str

Returns URL to modal logs

Raises:

Type Description
DeploymentNotStartedError

If the deployment was not started.

Source code in swerex/deployment/modal.py
200
201
202
203
204
205
206
def get_modal_log_url(self) -> str:
    """Returns URL to modal logs

    Raises:
        DeploymentNotStartedError: If the deployment was not started.
    """
    return f"https://modal.com/apps/{self._user}/main/deployed/{self.app.name}?activeTab=logs&taskId={self.sandbox._get_task_id()}"

is_alive async

is_alive(*, timeout: float | None = None) -> IsAliveResponse

Checks if the runtime is alive. The return value can be tested with bool().

Raises:

Type Description
DeploymentNotStartedError

If the deployment was not started.

Source code in swerex/deployment/modal.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
async def is_alive(self, *, timeout: float | None = None) -> IsAliveResponse:
    """Checks if the runtime is alive. The return value can be
    tested with bool().

    Raises:
        DeploymentNotStartedError: If the deployment was not started.
    """
    if self._runtime is None or self._sandbox is None:
        raise DeploymentNotStartedError()
    if self._sandbox.poll() is not None:
        msg = "Container process terminated."
        output = "stdout:\n" + self._sandbox.stdout.read()  # type: ignore
        output += "\nstderr:\n" + self._sandbox.stderr.read()  # type: ignore
        msg += "\n" + output
        raise RuntimeError(msg)
    return await self._runtime.is_alive(timeout=timeout)

start async

start()

Starts the runtime.

Source code in swerex/deployment/modal.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
async def start(
    self,
):
    """Starts the runtime."""
    self.logger.info("Starting modal sandbox")
    self._hooks.on_custom_step("Starting modal sandbox")
    t0 = time.time()
    token = self._get_token()
    self._sandbox = modal.Sandbox.create(
        "/bin/bash",
        "-c",
        self._start_swerex_cmd(token),
        image=self._image,
        timeout=int(self._deployment_timeout),
        unencrypted_ports=[self._port],
        app=self._app,
        **self._modal_kwargs,
    )
    tunnel = self._sandbox.tunnels()[self._port]
    elapsed_sandbox_creation = time.time() - t0
    self.logger.info(f"Sandbox ({self._sandbox.object_id}) created in {elapsed_sandbox_creation:.2f}s")
    self.logger.info(f"Check sandbox logs at {self.get_modal_log_url()}")
    self.logger.info(f"Sandbox created with id {self._sandbox.object_id}")
    await asyncio.sleep(1)
    self.logger.info(f"Starting runtime at {tunnel.url}")
    self._hooks.on_custom_step("Starting runtime")
    self._runtime = RemoteRuntime(
        host=tunnel.url, timeout=self._runtime_timeout, auth_token=token, logger=self.logger
    )
    remaining_startup_timeout = max(0, self._startup_timeout - elapsed_sandbox_creation)
    t1 = time.time()
    await self._wait_until_alive(timeout=remaining_startup_timeout)
    self.logger.info(f"Runtime started in {time.time() - t1:.2f}s")

stop async

stop()

Stops the runtime.

Source code in swerex/deployment/modal.py
242
243
244
245
246
247
248
249
250
async def stop(self):
    """Stops the runtime."""
    if self._runtime is not None:
        await self._runtime.close()
        self._runtime = None
    if self._sandbox is not None and not self._sandbox.poll():
        self._sandbox.terminate()
    self._sandbox = None
    self._app = None