diff --git a/aws_lambda_builders/workflows/python_pip/workflow.py b/aws_lambda_builders/workflows/python_pip/workflow.py index 6273277aa..d6c84e198 100644 --- a/aws_lambda_builders/workflows/python_pip/workflow.py +++ b/aws_lambda_builders/workflows/python_pip/workflow.py @@ -6,6 +6,7 @@ from aws_lambda_builders.actions import CleanUpAction, CopySourceAction, LinkSourceAction from aws_lambda_builders.path_resolver import PathResolver +from aws_lambda_builders.validator import RuntimeValidator from aws_lambda_builders.workflow import BaseWorkflow, BuildDirectory, BuildInSourceSupport, Capability from aws_lambda_builders.workflows.python_pip.validator import PythonRuntimeValidator @@ -78,6 +79,8 @@ def __init__(self, source_dir, artifacts_dir, scratch_dir, manifest_path, runtim if osutils is None: osutils = OSUtils() + self._use_pip = False + if not self.download_dependencies and not self.dependencies_dir: LOG.info( "download_dependencies is False and dependencies_dir is None. Copying the source files into the " @@ -92,6 +95,7 @@ def __init__(self, source_dir, artifacts_dir, scratch_dir, manifest_path, runtim # If a requirements.txt exists, run pip builder before copy action. if self.download_dependencies: + self._use_pip = True if self.dependencies_dir: # clean up the dependencies folder before installing self._actions.append(CleanUpAction(self.dependencies_dir)) @@ -161,4 +165,6 @@ def _should_create_parent_packages(self): return False def get_validators(self): + if not self._use_pip: + return [RuntimeValidator(runtime=self.runtime, architecture=self.architecture)] return [PythonRuntimeValidator(runtime=self.runtime, architecture=self.architecture)] diff --git a/tests/integration/workflows/python_pip/test_python_pip.py b/tests/integration/workflows/python_pip/test_python_pip.py index 80591b40a..28b439e6a 100644 --- a/tests/integration/workflows/python_pip/test_python_pip.py +++ b/tests/integration/workflows/python_pip/test_python_pip.py @@ -303,6 +303,39 @@ def test_must_log_warning_if_requirements_not_found(self): "requirements.txt file not found. Continuing the build without dependencies." ) + def test_build_succeeds_with_mismatched_runtime_when_no_requirements(self): + """ + When no requirements.txt exists, build should succeed even if the target runtime + binary validation would fail (issue #678). The base RuntimeValidator is used + which only checks if the runtime is supported, not if the binary exists. + """ + # Mock PythonRuntimeValidator to always fail - simulating missing Python binary + with mock.patch( + "aws_lambda_builders.workflows.python_pip.workflow.PythonRuntimeValidator" + ) as mock_validator_class: + mock_validator = mock.Mock() + mock_validator.validate.side_effect = Exception("Python binary not found") + mock_validator_class.return_value = mock_validator + + # This should succeed because without requirements.txt, we use base RuntimeValidator + # not PythonRuntimeValidator + self.builder.build( + self.source_dir, + self.artifacts_dir, + self.scratch_dir, + os.path.join("non", "existent", "manifest"), + runtime=self.runtime, + experimental_flags=self.experimental_flags, + ) + + # Should just copy source files + expected_files = self.test_data_files + output_files = set(os.listdir(self.artifacts_dir)) + self.assertEqual(expected_files, output_files) + + # Verify PythonRuntimeValidator was never instantiated (we used base RuntimeValidator) + mock_validator_class.assert_not_called() + @skipIf(IS_WINDOWS, "Skip in windows tests") def test_without_download_dependencies_with_dependencies_dir(self): source_dir = os.path.join(self.source_dir, "local-dependencies") diff --git a/tests/unit/workflows/python_pip/test_workflow.py b/tests/unit/workflows/python_pip/test_workflow.py index 71d5b7e19..b8ec16da8 100644 --- a/tests/unit/workflows/python_pip/test_workflow.py +++ b/tests/unit/workflows/python_pip/test_workflow.py @@ -5,6 +5,7 @@ from aws_lambda_builders.actions import CopySourceAction, CleanUpAction, LinkSourceAction from aws_lambda_builders.path_resolver import PathResolver +from aws_lambda_builders.validator import RuntimeValidator from aws_lambda_builders.workflows.python_pip.utils import OSUtils, EXPERIMENTAL_FLAG_BUILD_PERFORMANCE from aws_lambda_builders.workflows.python_pip.validator import PythonRuntimeValidator from aws_lambda_builders.workflows.python_pip.workflow import PythonPipBuildAction, PythonPipWorkflow @@ -60,6 +61,41 @@ def test_workflow_validator(self): for validator in self.workflow.get_validators(): self.assertTrue(isinstance(validator, PythonRuntimeValidator)) + def test_workflow_validator_without_requirements_skips_python_validation(self): + """When no requirements.txt exists, should use base RuntimeValidator (no Python binary check).""" + self.osutils_mock.file_exists.return_value = False + self.workflow = PythonPipWorkflow( + "source", + "artifacts", + "scratch_dir", + "manifest", + runtime="python3.12", + osutils=self.osutils_mock, + experimental_flags=self.experimental_flags, + ) + validators = self.workflow.get_validators() + self.assertEqual(len(validators), 1) + self.assertIsInstance(validators[0], RuntimeValidator) + self.assertNotIsInstance(validators[0], PythonRuntimeValidator) + + def test_workflow_validator_without_download_dependencies_skips_python_validation(self): + """When download_dependencies=False, should use base RuntimeValidator (no Python binary check).""" + self.osutils_mock.file_exists.return_value = True + self.workflow = PythonPipWorkflow( + "source", + "artifacts", + "scratch_dir", + "manifest", + runtime="python3.12", + osutils=self.osutils_mock, + download_dependencies=False, + experimental_flags=self.experimental_flags, + ) + validators = self.workflow.get_validators() + self.assertEqual(len(validators), 1) + self.assertIsInstance(validators[0], RuntimeValidator) + self.assertNotIsInstance(validators[0], PythonRuntimeValidator) + def test_workflow_resolver(self): for resolver in self.workflow.get_resolvers(): self.assertTrue(isinstance(resolver, PathResolver))