Skip to content

Commit 10ce0b2

Browse files
rsareddy0329Roja Reddy Sareddy
andauthored
fix(spark): allow directory paths in submit_deps (#6368)
PySparkProcessor/SparkJarProcessor rejected a local directory passed via submit_py_files / submit_jars / submit_files: _stage_submit_deps required os.path.isfile(dep_path) and raised "is not a valid local file" for a directory. Users could only list individual files. Accept a directory as well: copy its tree into the staging tempdir (via shutil.copytree) so its contents are uploaded to the dependency input channel and mounted into the Spark container, alongside the existing single-file handling. Update the error message to mention directories. Add a unit test that a directory is staged (not rejected). Relates-to #2447 Co-authored-by: Roja Reddy Sareddy <rsareddy@amazon.com>
1 parent f3e4ef3 commit 10ce0b2

2 files changed

Lines changed: 47 additions & 8 deletions

File tree

‎src/sagemaker/spark/processing.py‎

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -448,17 +448,29 @@ def _stage_submit_deps(self, submit_deps, input_channel_name):
448448
# S3 URIs are included as-is in the spark-submit argument
449449
if dep_url.scheme in ["s3", "s3a"]:
450450
spark_opt_s3_uris.append(dep_path)
451-
# Local files are copied to temp directory to be uploaded to S3
451+
# Local files and directories are copied to a temp directory to be
452+
# uploaded to S3
452453
elif not dep_url.scheme or dep_url.scheme == "file":
453-
if not os.path.isfile(dep_path):
454+
if os.path.isdir(dep_path):
455+
logger.info(
456+
"Copying dependency directory from local path %s to tmpdir %s",
457+
dep_path,
458+
tmpdir,
459+
)
460+
shutil.copytree(
461+
dep_path,
462+
os.path.join(tmpdir, os.path.basename(os.path.normpath(dep_path))),
463+
)
464+
elif os.path.isfile(dep_path):
465+
logger.info(
466+
"Copying dependency from local path %s to tmpdir %s", dep_path, tmpdir
467+
)
468+
shutil.copy(dep_path, tmpdir)
469+
else:
454470
raise ValueError(
455-
f"submit_deps path {dep_path} is not a valid local file. "
456-
f"{self._submit_deps_error_message}"
471+
f"submit_deps path {dep_path} is not a valid local file "
472+
f"or directory. {self._submit_deps_error_message}"
457473
)
458-
logger.info(
459-
"Copying dependency from local path %s to tmpdir %s", dep_path, tmpdir
460-
)
461-
shutil.copy(dep_path, tmpdir)
462474
else:
463475
raise ValueError(
464476
f"submit_deps path {dep_path} references unsupported filesystem "

‎tests/unit/sagemaker/spark/test_processing.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,33 @@ def test_stage_submit_deps(mock_s3_uploader, jar_file, config, expected, sagemak
476476
assert spark_opt == expected[1]
477477

478478

479+
@patch("sagemaker.spark.processing.S3Uploader")
480+
def test_stage_submit_deps_accepts_directory(mock_s3_uploader, sagemaker_session):
481+
"""A local directory in submit_deps is staged rather than rejected (issue #2447)."""
482+
spark_processor = PySparkProcessor(
483+
base_job_name="sm-spark",
484+
role="AmazonSageMaker-ExecutionRole",
485+
framework_version="2.4",
486+
instance_count=1,
487+
instance_type="ml.c5.xlarge",
488+
image_uri="790336243319.dkr.ecr.us-west-2.amazonaws.com/sagemaker-spark:0.1",
489+
sagemaker_session=sagemaker_session,
490+
)
491+
492+
with tempfile.TemporaryDirectory() as dep_dir:
493+
(pathlib.Path(dep_dir) / "module.py").write_text("x = 1\n")
494+
(pathlib.Path(dep_dir) / "helper.py").write_text("y = 2\n")
495+
496+
input_channel, spark_opt = spark_processor._stage_submit_deps([dep_dir], "pySparkFiles")
497+
498+
# The directory was accepted (no ValueError) and staged as an input channel,
499+
# with its contents uploaded to the dependency location.
500+
assert input_channel is not None
501+
assert input_channel.destination == "/opt/ml/processing/input/pySparkFiles"
502+
assert spark_opt == "/opt/ml/processing/input/pySparkFiles"
503+
mock_s3_uploader.upload.assert_called_once()
504+
505+
479506
@pytest.mark.parametrize(
480507
"config, expected",
481508
[

0 commit comments

Comments
 (0)