Skip to content

Commit 933faa9

Browse files
sndagsterDagster Devtools
authored andcommitted
fix: lazy-import boto3 in serverless io_manager so v2 user pods don't need it (#25605)
## Summary The serverless v2 (k8s) IO manager (`ServerlessPresignedURLIOManager`) does not use boto3 — it talks to S3 indirectly via presigned URLs handed out by the host cloud API. But `dagster_cloud.serverless.io_manager` still imports boto3 at module load time for the legacy ECS-based `PickledObjectServerlessIOManager`. This blows up on v2 user pods built from a plain `dagster-cloud` dependency (no `[serverless]` extra), which is what the `dg`-scaffolded projects ship. Move the boto3 import inside `PickledObjectServerlessIOManager._refresh_boto_session`. The ECS path still loads boto3 the first time it instantiates the legacy class; the v2 path now loads `dagster_cloud.serverless` cleanly without boto3 installed. Internal-RevId: 697f3c50c156f6ea2977cc0087c1c7e5d515b008
1 parent da72e34 commit 933faa9

2 files changed

Lines changed: 52 additions & 3 deletions

File tree

python_modules/dagster-cloud/dagster_cloud/serverless/io_manager.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
import io
33
import os
44
import pickle
5-
from typing import Any
5+
from typing import TYPE_CHECKING, Any
66

7-
import boto3
87
import dagster._check as check
98
import requests
109
from dagster import InputContext, OutputContext, UPathIOManager, io_manager
@@ -21,6 +20,9 @@
2120
from upath import UPath
2221
from urllib3.util.retry import Retry
2322

23+
if TYPE_CHECKING:
24+
import boto3
25+
2426
ECS_AGENT_IP = "169.254.170.2"
2527

2628

@@ -36,7 +38,11 @@ def __init__(
3638
base_path = UPath(s3_prefix) if s3_prefix else None
3739
super().__init__(base_path=base_path)
3840

39-
def _refresh_boto_session(self) -> tuple[boto3.Session, datetime.datetime]:
41+
def _refresh_boto_session(self) -> tuple["boto3.Session", datetime.datetime]:
42+
# boto3 is lazy-imported so this module can load in environments where
43+
# only the presigned-URL IO manager path is used and boto3 is not installed.
44+
import boto3
45+
4046
# We have to do this whacky way to get credentials to ensure that we get iam role
4147
# we assigned to the task. If we used the default boto behavior, it could get overriden
4248
# when users set AWS env vars.

python_modules/dagster-cloud/dagster_cloud_tests/serverless_tests/test_io_manager.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ruff: noqa: SLF001
2+
import importlib
23
import pickle
4+
import sys
35
from unittest import mock
46

57
import pytest
@@ -301,3 +303,44 @@ def test_serverless_io_manager_usable_in_job():
301303
resources={"io_manager": serverless_io_manager},
302304
)
303305
assert defs
306+
307+
308+
def test_module_imports_without_boto3_installed():
309+
# Simulates a v2 user pod whose image only depends on dagster-cloud (no
310+
# [serverless] extra) and therefore has no boto3 installed. The module must
311+
# still load cleanly so the presigned-URL IO manager path can be selected
312+
# at runtime via SERVERLESS_IO_MANAGER_USE_PRESIGNED_URL.
313+
cached = {
314+
name: sys.modules.pop(name)
315+
for name in (
316+
"boto3",
317+
"boto3.s3",
318+
"boto3.s3.transfer",
319+
"boto3.session",
320+
"botocore",
321+
"dagster_cloud.serverless",
322+
"dagster_cloud.serverless.io_manager",
323+
)
324+
if name in sys.modules
325+
}
326+
try:
327+
# `None` in sys.modules is the standard poison-pill that forces
328+
# `import boto3` to raise ImportError.
329+
with mock.patch.dict(sys.modules, {"boto3": None}):
330+
reloaded = importlib.import_module("dagster_cloud.serverless.io_manager")
331+
assert reloaded.ServerlessPresignedURLIOManager is not None
332+
# Instantiating the presigned-URL manager must not touch boto3.
333+
manager = reloaded.ServerlessPresignedURLIOManager(
334+
api_url=API_URL, api_token=API_TOKEN, timeout=30
335+
)
336+
assert manager is not None
337+
finally:
338+
# Drop anything we re-imported under the poison-pill and restore the
339+
# originals so other tests see a clean module state.
340+
for name in (
341+
"dagster_cloud.serverless",
342+
"dagster_cloud.serverless.io_manager",
343+
):
344+
sys.modules.pop(name, None)
345+
sys.modules.update(cached)
346+
importlib.import_module("dagster_cloud.serverless.io_manager")

0 commit comments

Comments
 (0)