diff --git a/aws_lambda_builders/actions.py b/aws_lambda_builders/actions.py index 547935da8..a7154f66a 100644 --- a/aws_lambda_builders/actions.py +++ b/aws_lambda_builders/actions.py @@ -5,6 +5,7 @@ import logging import os import shutil +from typing import Set, Iterator, Tuple from aws_lambda_builders.utils import copytree @@ -124,14 +125,9 @@ def __init__(self, source_dir, artifact_dir, destination_dir): self.dest_dir = destination_dir def execute(self): - source = set(os.listdir(self.source_dir)) - artifact = set(os.listdir(self.artifact_dir)) - dependencies = artifact - source - - for name in dependencies: - dependencies_source = os.path.join(self.artifact_dir, name) - new_destination = os.path.join(self.dest_dir, name) + deps_manager = DependencyManager(self.source_dir, self.artifact_dir, self.dest_dir) + for dependencies_source, new_destination in deps_manager.yield_source_dest(): if os.path.isdir(dependencies_source): copytree(dependencies_source, new_destination) else: @@ -153,14 +149,9 @@ def __init__(self, source_dir, artifact_dir, destination_dir): self.dest_dir = destination_dir def execute(self): - source = set(os.listdir(self.source_dir)) - artifact = set(os.listdir(self.artifact_dir)) - dependencies = artifact - source - - for name in dependencies: - dependencies_source = os.path.join(self.artifact_dir, name) - new_destination = os.path.join(self.dest_dir, name) + deps_manager = DependencyManager(self.source_dir, self.artifact_dir, self.dest_dir) + for dependencies_source, new_destination in deps_manager.yield_source_dest(): # shutil.move can't create subfolders if this is the first file in that folder if os.path.isfile(dependencies_source): os.makedirs(os.path.dirname(new_destination), exist_ok=True) @@ -197,3 +188,36 @@ def execute(self): shutil.rmtree(target_path) else: os.remove(target_path) + + +class DependencyManager: + """ + Class for handling the management of dependencies between directories + """ + + # Ignore these files when comparing against which dependencies to move + # This allows for the installation of dependencies in the source directory + IGNORE_LIST = ["node_modules"] + + def __init__(self, source_dir, artifact_dir, destination_dir) -> None: + self._source_dir: str = source_dir + self._artifact_dir: str = artifact_dir + self._dest_dir: str = destination_dir + self._dependencies: Set[str] = set() + + def yield_source_dest(self) -> Iterator[Tuple[str, str]]: + self._set_dependencies() + for dep in self._dependencies: + yield os.path.join(self._artifact_dir, dep), os.path.join(self._dest_dir, dep) + + def _set_dependencies(self) -> None: + source = self._get_source_files_exclude_deps() + artifact = set(os.listdir(self._artifact_dir)) + self._dependencies = artifact - source + + def _get_source_files_exclude_deps(self) -> Set[str]: + source_files = set(os.listdir(self._source_dir)) + for item in self.IGNORE_LIST: + if item in source_files: + source_files.remove(item) + return source_files diff --git a/tests/unit/test_actions.py b/tests/unit/test_actions.py index 0178c1a38..e4dcb0338 100644 --- a/tests/unit/test_actions.py +++ b/tests/unit/test_actions.py @@ -1,5 +1,8 @@ +from pathlib import Path +from typing import List, Tuple from unittest import TestCase -from mock import patch, ANY +from mock import patch, ANY, Mock +from parameterized import parameterized from aws_lambda_builders.actions import ( BaseAction, @@ -8,6 +11,7 @@ CopyDependenciesAction, MoveDependenciesAction, CleanUpAction, + DependencyManager, ) @@ -128,3 +132,47 @@ def test_must_copy(self, path_mock, listdir_mock, isdir_mock, rmtree_mock, rm_mo listdir_mock.assert_any_call(target_dir) rmtree_mock.assert_any_call("dir") rm_mock.assert_any_call("file") + + +class TestDependencyManager(TestCase): + @parameterized.expand( + [ + ( + ["app.js", "package.js", "libs", "node_modules"], + ["app.js", "package.js", "libs", "node_modules"], + [("artifacts/node_modules", "dest/node_modules")], + None, + ), + ( + ["file1, file2", "dep1", "dep2"], + ["file1, file2", "dep1", "dep2"], + [("artifacts/dep1", "dest/dep1"), ("artifacts/dep2", "dest/dep2")], + ["dep1", "dep2"], + ), + ( + ["file1, file2"], + ["file1, file2", "dep1", "dep2"], + [("artifacts/dep1", "dest/dep1"), ("artifacts/dep2", "dest/dep2")], + ["dep1", "dep2"], + ), + ] + ) + @patch("aws_lambda_builders.actions.os.listdir") + def test_excludes_dependencies_from_source( + self, source_files, artifact_files, expected, mock_dependencies, patched_list_dir + ): + dependency_manager = DependencyManager("source", "artifacts", "dest") + dependency_manager.IGNORE_LIST = ( + dependency_manager.IGNORE_LIST if mock_dependencies is None else mock_dependencies + ) + patched_list_dir.side_effect = [source_files, artifact_files] + source_destinations = TestDependencyManager._convert_strings_to_paths( + list(dependency_manager.yield_source_dest()) + ) + expected_paths = TestDependencyManager._convert_strings_to_paths(expected) + for expected_source_dest in expected_paths: + self.assertIn(expected_source_dest, source_destinations) + + @staticmethod + def _convert_strings_to_paths(source_dest_list): + return map(lambda item: (Path(item[0]), Path(item[1])), source_dest_list)