Skip to content

Commit

Permalink
Merge pull request #1 from pixee/dry-run
Browse files Browse the repository at this point in the history
Added dry-run option with tests
  • Loading branch information
andrecsilva authored Jul 31, 2023
2 parents 8d2a5bc + 69f621c commit a53f424
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 36 deletions.
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
GitPython<4
mock==5.1.*
pytest==7.4.*
-e .
-e .
2 changes: 1 addition & 1 deletion src/dependency_manager/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .manager import DependencyManagerAbstract
from .manager import DependencyManagerAbstract
21 changes: 17 additions & 4 deletions src/dependency_manager/manager.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/dependency_manager/singleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
109 changes: 79 additions & 30 deletions tests/test_manager.py
Original file line number Diff line number Diff line change
@@ -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()

Expand All @@ -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()

Expand All @@ -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
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

0 comments on commit a53f424

Please sign in to comment.