-
Notifications
You must be signed in to change notification settings - Fork 0
fix: Pipeline TypeError: can only concatenate list (not "NoneType") to list Using Sou (#5518) #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -241,6 +241,34 @@ def test_get_processing_code_hash_code_only(self): | |
| finally: | ||
| os.unlink(temp_file) | ||
|
|
||
|
aviruthen marked this conversation as resolved.
|
||
| def test_get_processing_code_hash_with_none_dependencies_and_source_dir(self): | ||
| """Test get_processing_code_hash with None dependencies and source_dir""" | ||
| with tempfile.TemporaryDirectory() as temp_dir: | ||
| code_file = Path(temp_dir, "script.py") | ||
| code_file.write_text("print('hello')") | ||
|
|
||
| result = get_processing_code_hash( | ||
| code=str(code_file), source_dir=temp_dir, dependencies=None | ||
| ) | ||
|
|
||
|
aviruthen marked this conversation as resolved.
|
||
| assert result is not None | ||
| assert len(result) == 64 | ||
|
|
||
|
aviruthen marked this conversation as resolved.
|
||
| def test_get_processing_code_hash_with_none_dependencies_and_code_only(self): | ||
| """Test get_processing_code_hash with None dependencies and code only""" | ||
| with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: | ||
| f.write("print('hello')") | ||
| temp_file = f.name | ||
|
|
||
| try: | ||
| result = get_processing_code_hash(code=temp_file, source_dir=None, dependencies=None) | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: There's an extra blank line here (two blank lines between methods inside a class). PEP 8 convention for methods within a class is one blank line. This is minor but worth noting since CI may flag it. |
||
|
|
||
| assert result is not None | ||
| assert len(result) == 64 | ||
| finally: | ||
| os.unlink(temp_file) | ||
|
|
||
|
|
||
| def test_get_processing_code_hash_s3_uri(self): | ||
| """Test get_processing_code_hash with S3 URI returns None""" | ||
| result = get_processing_code_hash( | ||
|
|
@@ -308,6 +336,47 @@ def test_get_training_code_hash_entry_point_only(self): | |
| assert len(result_with_deps) == 64 | ||
| assert result_no_deps != result_with_deps | ||
|
|
||
| def test_get_code_hash_training_step_with_none_requirements(self): | ||
| """Test get_code_hash with TrainingStep whose source_code has requirements=None""" | ||
| from sagemaker.core.workflow.utilities import get_code_hash | ||
|
|
||
| with tempfile.TemporaryDirectory() as temp_dir: | ||
| entry_file = Path(temp_dir, "train.py") | ||
| entry_file.write_text("print('training')") | ||
|
|
||
| mock_source_code = Mock() | ||
| mock_source_code.source_dir = temp_dir | ||
| mock_source_code.requirements = None | ||
| mock_source_code.entry_script = str(entry_file) | ||
|
|
||
| mock_model_trainer = Mock() | ||
| mock_model_trainer.source_code = mock_source_code | ||
|
|
||
| mock_step_args = Mock() | ||
| mock_step_args.func_args = [mock_model_trainer] | ||
|
|
||
| mock_step = Mock() | ||
| mock_step.step_args = mock_step_args | ||
|
|
||
| with patch("sagemaker.core.workflow.utilities.isinstance") as mock_isinstance: | ||
| def isinstance_side_effect(obj, cls): | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test is extremely fragile and overly complex. Patching Instead, consider constructing a real def test_get_training_code_hash_with_none_dependencies(self):
"""Test get_training_code_hash with None dependencies does not raise TypeError"""
with tempfile.TemporaryDirectory() as temp_dir:
entry_file = Path(temp_dir, "train.py")
entry_file.write_text("print('training')")
result = get_training_code_hash(
entry_point=str(entry_file),
source_dir=temp_dir,
dependencies=None,
)
assert result is not None
assert len(result) == 64 |
||
| from sagemaker.mlops.workflow.steps import TrainingStep, ProcessingStep | ||
| if cls is ProcessingStep: | ||
| return False | ||
| if cls is TrainingStep: | ||
| return obj is mock_step | ||
| return builtins_isinstance(obj, cls) | ||
|
|
||
| import builtins | ||
| builtins_isinstance = builtins.isinstance | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Importing |
||
| mock_isinstance.side_effect = isinstance_side_effect | ||
|
|
||
| result = get_code_hash(mock_step) | ||
|
|
||
| assert result is not None | ||
| assert len(result) == 64 | ||
|
|
||
|
|
||
| def test_get_training_code_hash_s3_uri(self): | ||
| """Test get_training_code_hash with S3 URI returns None""" | ||
| result = get_training_code_hash( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good fix. However, for consistency and defense-in-depth, consider also updating the function signature of
get_processing_code_hashto defaultdependenciestoNone(if it doesn't already) and document thatNoneis acceptable. Currently the type hint saysList[str]— it should belist[str] | Noneto accurately reflect thatNoneis a valid input.Also,
get_training_code_hashshould be checked — the PR description mentions it already has guards, but therequirements or []fix on this line suggests it may not handleNonein all paths either. Please verify.