Skip to content

Commit 3d5d89b

Browse files
Refactor modal_sandbox.py to fix lint violations
- Move inline functions to module level (_build_fresh_base_image, _build_final_image) - Add noqa comments for intentional broad exception catching 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4b11ebf commit 3d5d89b

File tree

1 file changed

+77
-68
lines changed

1 file changed

+77
-68
lines changed

scripts/modal_sandbox.py

Lines changed: 77 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,72 @@ def clear_image_cache() -> None:
185185
logger.info("Cleared cached image from %s", CACHE_FILE)
186186

187187

188+
def _build_fresh_base_image(
189+
app, dockerfile_path: str | None
190+
) -> tuple[modal.Image, str]:
191+
"""Build a fresh base image (no caching)."""
192+
if dockerfile_path is None:
193+
logger.info("Building default base image...")
194+
base_img = modal.Image.debian_slim(python_version="3.11").pip_install("pytest")
195+
else:
196+
logger.info("Building base image from %s with context_dir=.", dockerfile_path)
197+
base_img = modal.Image.from_dockerfile(dockerfile_path, context_dir=".")
198+
199+
base_img.build(app)
200+
# Materialize to get base image_id for caching
201+
temp_sandbox = modal.Sandbox.create(app=app, image=base_img, timeout=10)
202+
temp_sandbox.terminate()
203+
base_img_id = base_img.object_id
204+
# Cache the base image
205+
write_cached_image_id(base_img_id)
206+
logger.info("Cached base image_id to %s", CACHE_FILE)
207+
return base_img, base_img_id
208+
209+
210+
def _build_final_image(
211+
app,
212+
base_img: modal.Image,
213+
base_img_id: str,
214+
include_cwd: bool,
215+
copy_dirs: tuple[str, ...],
216+
ignore_patterns: list[str],
217+
) -> str:
218+
"""Build final image with cwd/copy-dirs on top of base. Returns image_id."""
219+
final_img = base_img
220+
221+
if include_cwd:
222+
logger.info("Adding current directory as /app...")
223+
final_img = final_img.add_local_dir(
224+
".", "/app", copy=True, ignore=ignore_patterns
225+
)
226+
227+
# Add user-specified directories
228+
for copy_spec in copy_dirs:
229+
if ":" not in copy_spec:
230+
logger.warning(
231+
"Invalid copy-dir format '%s', expected 'local:remote'",
232+
copy_spec,
233+
)
234+
continue
235+
local_path, remote_path = copy_spec.split(":", 1)
236+
if not os.path.isdir(local_path):
237+
logger.warning("Local directory '%s' not found, skipping", local_path)
238+
continue
239+
logger.info("Adding %s -> %s to image", local_path, remote_path)
240+
final_img = final_img.add_local_dir(
241+
local_path, remote_path, copy=True, ignore=ignore_patterns
242+
)
243+
244+
# Build and materialize the final image if we added anything
245+
if final_img is not base_img:
246+
final_img.build(app)
247+
temp_sandbox = modal.Sandbox.create(app=app, image=final_img, timeout=10)
248+
temp_sandbox.terminate()
249+
return final_img.object_id
250+
else:
251+
return base_img_id
252+
253+
188254
@cli.command("prepare")
189255
@click.argument("dockerfile_path", required=False, default=None)
190256
@click.option("--cached", is_flag=True, help="Use cached BASE image if available")
@@ -232,67 +298,6 @@ def prepare(
232298
sys.exit(1)
233299
app_name = "offload-dockerfile-sandbox"
234300

235-
def build_fresh_base_image(app) -> tuple[modal.Image, str]:
236-
"""Build a fresh base image (no caching)."""
237-
if dockerfile_path is None:
238-
logger.info("Building default base image...")
239-
base_img = modal.Image.debian_slim(python_version="3.11").pip_install(
240-
"pytest"
241-
)
242-
else:
243-
logger.info(
244-
"Building base image from %s with context_dir=.", dockerfile_path
245-
)
246-
base_img = modal.Image.from_dockerfile(dockerfile_path, context_dir=".")
247-
248-
base_img.build(app)
249-
# Materialize to get base image_id for caching
250-
temp_sandbox = modal.Sandbox.create(app=app, image=base_img, timeout=10)
251-
temp_sandbox.terminate()
252-
base_img_id = base_img.object_id
253-
# Cache the base image
254-
write_cached_image_id(base_img_id)
255-
logger.info("Cached base image_id to %s", CACHE_FILE)
256-
return base_img, base_img_id
257-
258-
def build_final_image(
259-
app, base_img: modal.Image, base_img_id: str
260-
) -> str:
261-
"""Build final image with cwd/copy-dirs on top of base. Returns image_id."""
262-
final_img = base_img
263-
264-
if include_cwd:
265-
logger.info("Adding current directory as /app...")
266-
final_img = final_img.add_local_dir(
267-
".", "/app", copy=True, ignore=ignore_patterns
268-
)
269-
270-
# Add user-specified directories
271-
for copy_spec in copy_dirs:
272-
if ":" not in copy_spec:
273-
logger.warning(
274-
"Invalid copy-dir format '%s', expected 'local:remote'",
275-
copy_spec,
276-
)
277-
continue
278-
local_path, remote_path = copy_spec.split(":", 1)
279-
if not os.path.isdir(local_path):
280-
logger.warning("Local directory '%s' not found, skipping", local_path)
281-
continue
282-
logger.info("Adding %s -> %s to image", local_path, remote_path)
283-
final_img = final_img.add_local_dir(
284-
local_path, remote_path, copy=True, ignore=ignore_patterns
285-
)
286-
287-
# Build and materialize the final image if we added anything
288-
if final_img is not base_img:
289-
final_img.build(app)
290-
temp_sandbox = modal.Sandbox.create(app=app, image=final_img, timeout=10)
291-
temp_sandbox.terminate()
292-
return final_img.object_id
293-
else:
294-
return base_img_id
295-
296301
with modal.enable_output():
297302
app = modal.App.lookup(app_name, create_if_missing=True)
298303

@@ -309,19 +314,23 @@ def build_final_image(
309314

310315
# Step 2: Build fresh base image if no cache
311316
if base_image is None:
312-
base_image, base_image_id = build_fresh_base_image(app)
317+
base_image, base_image_id = _build_fresh_base_image(app, dockerfile_path)
313318

314319
# Step 3: Build final image, catching cache invalidation errors
315320
try:
316-
image_id = build_final_image(app, base_image, base_image_id)
317-
except Exception as e:
321+
image_id = _build_final_image(
322+
app, base_image, base_image_id, include_cwd, copy_dirs, ignore_patterns
323+
)
324+
except Exception as e: # noqa: BLE001 - rebuild on any failure
318325
# Cached image no longer exists on Modal - rebuild from scratch
319326
logger.warning(
320327
"Failed to use cached image (%s), rebuilding from scratch...", e
321328
)
322329
clear_image_cache()
323-
base_image, base_image_id = build_fresh_base_image(app)
324-
image_id = build_final_image(app, base_image, base_image_id)
330+
base_image, base_image_id = _build_fresh_base_image(app, dockerfile_path)
331+
image_id = _build_final_image(
332+
app, base_image, base_image_id, include_cwd, copy_dirs, ignore_patterns
333+
)
325334

326335
sys.stdout.write("%s\n" % image_id)
327336

@@ -484,7 +493,7 @@ def create_from_image(
484493
logger.debug("[%.2fs] Loading image %s...", time.time() - t0, image_id)
485494
try:
486495
image = modal.Image.from_id(image_id)
487-
except Exception as e:
496+
except Exception as e: # noqa: BLE001
488497
logger.error("Failed to load image %s: %s", image_id, e)
489498
logger.error(
490499
"The image may have been garbage collected. "
@@ -507,7 +516,7 @@ def create_from_image(
507516
timeout=3600,
508517
secrets=secrets,
509518
)
510-
except Exception as e:
519+
except Exception as e: # noqa: BLE001
511520
logger.error("Failed to create sandbox with image %s: %s", image_id, e)
512521
logger.error(
513522
"The image may have been garbage collected. "

0 commit comments

Comments
 (0)