3232from sagemaker .core .workflow .entities import Entity
3333from sagemaker .core .workflow .parameters import Parameter
3434from sagemaker .core .workflow .pipeline_context import _StepArguments
35+ from sagemaker .core .workflow .utilities import get_code_hash
3536
3637
3738class MockEntity (Entity ):
@@ -308,6 +309,49 @@ def test_get_training_code_hash_entry_point_only(self):
308309 assert len (result_with_deps ) == 64
309310 assert result_no_deps != result_with_deps
310311
312+ def test_get_training_code_hash_with_source_dir_none_dependencies (self ):
313+ """Test get_training_code_hash with source_dir and None dependencies does not raise TypeError"""
314+ with tempfile .TemporaryDirectory () as temp_dir :
315+ entry_file = Path (temp_dir , "train.py" )
316+ entry_file .write_text ("print('training')" )
317+
318+ # This is the exact scenario from the bug report: dependencies=None
319+ result = get_training_code_hash (
320+ entry_point = str (entry_file ), source_dir = temp_dir , dependencies = None
321+ )
322+
323+ assert result is not None
324+ assert len (result ) == 64
325+
326+ def test_get_training_code_hash_entry_point_only_none_dependencies (self ):
327+ """Test get_training_code_hash with entry_point only and None dependencies does not raise TypeError"""
328+ with tempfile .TemporaryDirectory () as temp_dir :
329+ entry_file = Path (temp_dir , "train.py" )
330+ entry_file .write_text ("print('training')" )
331+
332+ # entry_point only, no source_dir, dependencies=None
333+ result = get_training_code_hash (
334+ entry_point = str (entry_file ), source_dir = None , dependencies = None
335+ )
336+
337+ assert result is not None
338+ assert len (result ) == 64
339+
340+ def test_get_training_code_hash_default_dependencies (self ):
341+ """Test get_training_code_hash with default dependencies parameter (not passed)"""
342+ with tempfile .TemporaryDirectory () as temp_dir :
343+ entry_file = Path (temp_dir , "train.py" )
344+ entry_file .write_text ("print('training')" )
345+
346+ # Not passing dependencies at all - should use default None
347+ result = get_training_code_hash (
348+ entry_point = str (entry_file ), source_dir = temp_dir
349+ )
350+
351+ assert result is not None
352+ assert len (result ) == 64
353+
354+
311355 def test_get_training_code_hash_s3_uri (self ):
312356 """Test get_training_code_hash with S3 URI returns None"""
313357 result = get_training_code_hash (
@@ -325,6 +369,54 @@ def test_get_training_code_hash_pipeline_variable(self):
325369
326370 assert result is None
327371
372+ @pytest .mark .skip (reason = "Requires sagemaker-mlops module which is not installed in sagemaker-core tests" )
373+ def test_get_code_hash_training_step_none_requirements (self ):
374+ """Test get_code_hash with TrainingStep where source_code.requirements is None"""
375+ from sagemaker .mlops .workflow .steps import TrainingStep
376+
377+ with tempfile .TemporaryDirectory () as temp_dir :
378+ entry_file = Path (temp_dir , "train.py" )
379+ entry_file .write_text ("print('training')" )
380+
381+ mock_source_code = Mock ()
382+ mock_source_code .source_dir = temp_dir
383+ mock_source_code .requirements = None # This is the bug scenario
384+ mock_source_code .entry_script = str (entry_file )
385+
386+ mock_model_trainer = Mock ()
387+ mock_model_trainer .source_code = mock_source_code
388+
389+ mock_step_args = Mock (spec = _StepArguments )
390+ mock_step_args .func_args = [mock_model_trainer ]
391+
392+ mock_step = Mock (spec = TrainingStep )
393+ mock_step .step_args = mock_step_args
394+
395+ # This should not raise TypeError
396+ result = get_code_hash (mock_step )
397+
398+ assert result is not None
399+ assert len (result ) == 64
400+
401+ @pytest .mark .skip (reason = "Requires sagemaker-mlops module which is not installed in sagemaker-core tests" )
402+ def test_get_code_hash_training_step_no_source_code (self ):
403+ """Test get_code_hash with TrainingStep where source_code is None"""
404+ from sagemaker .mlops .workflow .steps import TrainingStep
405+
406+ mock_model_trainer = Mock ()
407+ mock_model_trainer .source_code = None
408+
409+ mock_step_args = Mock (spec = _StepArguments )
410+ mock_step_args .func_args = [mock_model_trainer ]
411+
412+ mock_step = Mock (spec = TrainingStep )
413+ mock_step .step_args = mock_step_args
414+
415+ result = get_code_hash (mock_step )
416+
417+ assert result is None
418+
419+
328420 def test_validate_step_args_input_valid (self ):
329421 """Test validate_step_args_input with valid input"""
330422 step_args = _StepArguments (
0 commit comments