aws_lambda_builders/workflows/python_pip/actions.py (74 lines of code) (raw):
"""
Action to resolve Python dependencies using PIP
"""
import logging
from typing import Optional, Tuple
from aws_lambda_builders.actions import ActionFailedError, BaseAction, Purpose
from aws_lambda_builders.architecture import X86_64
from aws_lambda_builders.binary_path import BinaryPath
from aws_lambda_builders.exceptions import MisMatchRuntimeError, RuntimeValidatorError
from aws_lambda_builders.workflows.python_pip.exceptions import MissingPipError
from aws_lambda_builders.workflows.python_pip.packager import (
DependencyBuilder,
PackagerError,
PipRunner,
PythonPipDependencyBuilder,
SubprocessPip,
)
from aws_lambda_builders.workflows.python_pip.utils import OSUtils
LOG = logging.getLogger(__name__)
class PythonPipBuildAction(BaseAction):
NAME = "ResolveDependencies"
DESCRIPTION = "Installing dependencies from PIP"
PURPOSE = Purpose.RESOLVE_DEPENDENCIES
LANGUAGE = "python"
def __init__(
self, artifacts_dir, scratch_dir, manifest_path, runtime, dependencies_dir, binaries, architecture=X86_64
):
self.artifacts_dir = artifacts_dir
self.manifest_path = manifest_path
self.scratch_dir = scratch_dir
self.runtime = runtime
self.dependencies_dir = dependencies_dir
self.binaries = binaries
self.architecture = architecture
self._os_utils = OSUtils()
def execute(self) -> None:
"""
Executes the build action for Python `pip` workflows.
"""
pip, python_with_pip = self._find_runtime_with_pip()
pip_runner = PipRunner(python_exe=python_with_pip, pip=pip)
dependency_builder = DependencyBuilder(
osutils=self._os_utils,
python_exe=python_with_pip,
pip_runner=pip_runner,
runtime=self.runtime,
architecture=self.architecture,
)
package_builder = PythonPipDependencyBuilder(
osutils=self._os_utils,
runtime=self.runtime,
python_exe=python_with_pip,
dependency_builder=dependency_builder,
)
try:
target_artifact_dir = self.artifacts_dir
# if dependencies folder is provided, download the dependencies into dependencies folder
if self.dependencies_dir:
target_artifact_dir = self.dependencies_dir
package_builder.build_dependencies(
artifacts_dir_path=target_artifact_dir,
scratch_dir_path=self.scratch_dir,
requirements_path=self.manifest_path,
)
except PackagerError as ex:
raise ActionFailedError(str(ex))
def _find_runtime_with_pip(self) -> Tuple[SubprocessPip, str]:
"""
Finds a Python runtime that also contains `pip`.
Returns
-------
Tuple[SubprocessPip, str]
Returns a tuple of the SubprocessPip object created from
a valid Python runtime and the runtime path itself
Raises
------
ActionFailedError
Raised if the method is not able to find a valid runtime
that has the correct Python and pip installed
"""
binary_object: Optional[BinaryPath] = self.binaries.get(self.LANGUAGE)
if not binary_object:
raise ActionFailedError("Failed to fetch Python binaries from the PATH.")
for python_path in binary_object.resolver.exec_paths:
try:
valid_python_path = binary_object.validator.validate(python_path)
if valid_python_path:
pip = SubprocessPip(osutils=self._os_utils, python_exe=valid_python_path)
return (pip, valid_python_path)
except (MisMatchRuntimeError, RuntimeValidatorError):
# runtime and mismatch exceptions should have been caught
# during the init phase
# we can ignore these and let the action fail at the end
LOG.debug(f"Python runtime path '{python_path}' does not match the workflow")
except MissingPipError:
LOG.debug(f"Python runtime path '{python_path}' does not contain pip")
raise ActionFailedError("Failed to find a Python runtime containing pip on the PATH.")