diff --git a/requirements.txt b/requirements.txt index 341e68d..9af0c46 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +GitPython<4 mock==5.1.* pytest==7.4.* --e . \ No newline at end of file +-e . diff --git a/src/dependency_manager/__init__.py b/src/dependency_manager/__init__.py index 15231e1..34bf77e 100644 --- a/src/dependency_manager/__init__.py +++ b/src/dependency_manager/__init__.py @@ -1 +1 @@ -from .manager import DependencyManagerAbstract \ No newline at end of file +from .manager import DependencyManagerAbstract diff --git a/src/dependency_manager/manager.py b/src/dependency_manager/manager.py index 35077f4..50e1966 100644 --- a/src/dependency_manager/manager.py +++ b/src/dependency_manager/manager.py @@ -1,18 +1,19 @@ from abc import ABC, abstractmethod from pathlib import Path -import pkg_resources from typing import Union +import pkg_resources from .singleton import Singleton class DependencyManagerAbstract(Singleton, ABC): - def init(self): + def init(self, dry_run=False): """One-time class initialization.""" self.parent_directory = self.get_parent_dir() self.dependency_file = self._infer_dependency_files() self.dependencies = self._infer_dependencies() self.dependencies_changed = False self.dependency_file_changed = False + self.dry_run = dry_run @abstractmethod def get_parent_dir(self) -> Path: @@ -46,16 +47,28 @@ def remove(self, dependencies: list[str]): self.dependencies_changed = True def write(self): - if not self.dependency_file or not self.dependencies or not self.dependencies_changed: + """ + Write the updated dependency files if any changes were made. + If the dry_run flag is set, print it to stdout instead. + """ + if ( + not self.dependency_file + or not self.dependencies + or not self.dependencies_changed + ): return dependencies = [str(req) for req in self.dependencies] - self._write(dependencies) + if self.dry_run: + print("\n".join(dependencies)) + else: + self._write(dependencies) self.dependency_file_changed = True def _write(self, dependencies): with open(self.dependency_file, "w", encoding="utf-8") as f: f.writelines("\n".join(dependencies)) + def _infer_dependency_files(self) -> Union[Path, None]: try: # For now for simplicity only return the first file diff --git a/src/dependency_manager/singleton.py b/src/dependency_manager/singleton.py index acc1869..2eace72 100644 --- a/src/dependency_manager/singleton.py +++ b/src/dependency_manager/singleton.py @@ -20,6 +20,7 @@ def init(self, *args, **kwds): Subclasses should override this method for initialization, not __init__. """ pass + @classmethod def clear_instance(cls): """Delete the singleton's current instance.""" diff --git a/tests/test_manager.py b/tests/test_manager.py index cdc96e2..682194f 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -1,13 +1,34 @@ -import os from pathlib import Path +import os import pytest +import git from dependency_manager import DependencyManagerAbstract path_with_req = Path("tests/reqs_file_inside") +original_contents = """# file used to test dependency management +requests==2.31.0 +black==23.7.* +mypy~=1.4 +pylint>1 +""" + + +@pytest.fixture() +def path_with_req_with_cleanup(): + """ + Yields the directory with the requirements and ensures it returns to its original state after. + """ + path = "tests/reqs_file_inside" + yield Path(path) + repo = git.Git(os.getcwd()) + repo.execute(["git", "checkout", str(Path(os.getcwd()) / path)]) + + class TestManager: def test_must_define_parent_dir(self): class MyUnfinishedManager(DependencyManagerAbstract): pass + with pytest.raises(TypeError): MyUnfinishedManager() @@ -23,14 +44,15 @@ def get_parent_dir(self): def test_no_reqs_file_doesnt_error(self): dir_wo_reqs = Path("tests/no_reqs_files_inside") + class DependencyManager(DependencyManagerAbstract): def get_parent_dir(self): return dir_wo_reqs - dm = DependencyManager() - assert dm.parent_directory == dir_wo_reqs - assert dm.dependency_file == None - assert dm.dependencies == [] + manager = DependencyManager() + assert manager.parent_directory == dir_wo_reqs + assert manager.dependency_file is None + assert not manager.dependencies DependencyManager().write() @@ -39,63 +61,90 @@ class DependencyManager(DependencyManagerAbstract): def get_parent_dir(self): return path_with_req - dm = DependencyManager() - assert dm.dependency_file == path_with_req / "requirements.txt" - assert len(dm.dependencies) == 4 + manager = DependencyManager() + assert manager.dependency_file == path_with_req / "requirements.txt" + assert len(manager.dependencies) == 4 def test_add(self): class DependencyManager(DependencyManagerAbstract): def get_parent_dir(self): return path_with_req - dm = DependencyManager() - assert dm.dependency_file == path_with_req / "requirements.txt" - assert len(dm.dependencies) == 4 + manager = DependencyManager() + assert manager.dependency_file == path_with_req / "requirements.txt" + assert len(manager.dependencies) == 4 DependencyManager().add(["my_pkg==1"]) - assert len(dm.dependencies) == 5 - my_pkg = dm.dependencies[-1] + assert len(manager.dependencies) == 5 + my_pkg = manager.dependencies[-1] assert str(my_pkg) == "my_pkg==1" DependencyManager().add(["my_pkg_no_ver"]) - assert len(dm.dependencies) == 6 - my_pkg = dm.dependencies[-1] + assert len(manager.dependencies) == 6 + my_pkg = manager.dependencies[-1] assert str(my_pkg) == "my_pkg_no_ver" # don't add already existing dep DependencyManager().add(["my_pkg_no_ver"]) - assert len(dm.dependencies) == 6 + assert len(manager.dependencies) == 6 def test_cant_handle_urls(self): class DependencyManager(DependencyManagerAbstract): def get_parent_dir(self): return path_with_req - dm = DependencyManager() + manager = DependencyManager() - assert len(dm.dependencies) == 4 + assert len(manager.dependencies) == 4 DependencyManager().add(["git+https://github.com/someproject/"]) - assert len(dm.dependencies) == 4 + assert len(manager.dependencies) == 4 + def test_remove(self): class DependencyManager(DependencyManagerAbstract): def get_parent_dir(self): return path_with_req - dm = DependencyManager() - assert dm.dependency_file == path_with_req / "requirements.txt" - assert len(dm.dependencies) == 4 + manager = DependencyManager() + assert manager.dependency_file == path_with_req / "requirements.txt" + assert len(manager.dependencies) == 4 - first_dep = dm.dependencies[0] - dm.remove([str(first_dep)]) - assert len(dm.dependencies) == 3 + first_dep = manager.dependencies[0] + manager.remove([str(first_dep)]) + assert len(manager.dependencies) == 3 DependencyManager().remove(["git+https://github.com/someproject/"]) - assert len(dm.dependencies) == 3 + assert len(manager.dependencies) == 3 # Don't remove what isn't there DependencyManager().remove(["my_pkg==1"]) - assert len(dm.dependencies) == 3 + assert len(manager.dependencies) == 3 - def test_write(self): - # TODO: - pass \ No newline at end of file + def test_dry_run(self, capsys): + class DependencyManager(DependencyManagerAbstract): + def get_parent_dir(self): + return path_with_req + + manager = DependencyManager(dry_run=True) + first_dep = manager.dependencies[0] + manager.remove([str(first_dep)]) + manager.write() + with open(manager.dependency_file, "r", encoding="utf-8") as dep_file: + contents = dep_file.read() + assert contents == original_contents + expected = "black==23.7.*\nmypy~=1.4\npylint>1\n" + captured = capsys.readouterr() + assert captured.out == expected + + def test_write(self, path_with_req_with_cleanup): + class DependencyManager(DependencyManagerAbstract): + def get_parent_dir(self): + return path_with_req_with_cleanup + + manager = DependencyManager() + first_dep = manager.dependencies[0] + manager.remove([str(first_dep)]) + manager.write() + with open(manager.dependency_file, "r", encoding="utf-8") as dep_file: + contents = dep_file.read() + expected = "black==23.7.*\nmypy~=1.4\npylint>1" + assert contents == expected