Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions aws_lambda_builders/workflows/python_pip/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 "
Expand All @@ -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))
Expand Down Expand Up @@ -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)]
33 changes: 33 additions & 0 deletions tests/integration/workflows/python_pip/test_python_pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
36 changes: 36 additions & 0 deletions tests/unit/workflows/python_pip/test_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down
Loading